Hindering debugging – by doing nothing

A common technique to make debugging harder and more time-consuming is scrambling the virus code and inserting “random” junk code that doesn’t really do anything useful. One example is the W32/Virut family. Despite already being a couple of years old, it is still one of the most active file infector families around. Its author(s) frequently update the way it tries to hide itself both from AV software and researchers.

It is polymorphic and has been manually adapted and extended by its author(s) multiple times. While analysing one of the latest W32/Virut variants, we came accross a block in the virus code that couldn’t be properly disassembled by the used debugger. Looking closer, it turned out that the problematic block of code contained a relatively unusual multi-byte no-operation instruction (NOP).

Fig. 1: Modern disassemblers and debuggers can decode the length of the NOP correctly.

Fig. 1: Even modern disassemblers and debuggers can't decode the length of the NOP correctly (OllyDbg 1.10).

The usual NOP instruction of x86 processors is one byte long (opcode 0x90). But there is also a multi-byte NOP with the opcode 0x0F 1F which is used in the virus code. The multi-byte NOP can take up to 9 bytes. NOP instructions can get used for padding the code to align it to 8 or 16 byte boundaries – on modern processors this can speed up the code, mostly for caching reasons.

Intel officially documented this multi-byte NOP in 2006, but it has already been present in older processors for quite a while – apparently since Pentium Pro, but not the Pentium MMX.

Fig. 2: Some disassemblers and debuggers don't cope well with the multi-byte NOP instructions.

Fig. 2: Older disassemblers and debuggers don't cope well with the multi-byte NOP instructions (older IDA variant).

The author(s) of the W32/Virut malware now use(s) the fact that these multi-byte NOP instructions are still quite unknown to complicate the analysis and to trick disassemblers and emulators. Some commonly used disassemblers and debuggers don’t support these opcodes. They cannot calculate the correct length of the instruction and as a result aren’t able to properly “translate” the code beyond this point.

Markus Hinderhofer
Engine Research & Development

Dirk Knop
Technical Editor