- About us
- Code of Conduct
- Google SoC
- Recent posts
- Security Workshops
The last spreading malware version of Waledac, a notorious spamming botnet that has been taken down in a collaborative effort lead by Microsoft earlier this year, contained some neat anti-debugging tricks in order to make reverse-engineering more difficult. Felix Leder and I have been presenting about the approach at SIGINT 2010 in Cologne yesterday, and as the method seems to be not publicly known yet, I will quickly describe it here as well.
The md5 hash of the sample I was playing with is
79f24cefd98c162565da71b4aa01e87b. When you load it into a debugger, you see two functions, one calling the other. Apparently, somewhere in there must be a branch into the real code. So let's look more closely. The instructions of the second function starting at offset
0x004010CB are pretty standard. What they do is to put an additional exception handler in the SEH chain that would be called on, e.g., memory access violations:
0x004010A9 has been pushed on the stack as return address by the previous call, so the code starting at this offset would be called upon exceptions. This is what the stack looks like now:
After the new exception handler is in place, an access violation is triggered by subtracting the value in
[0x00401000], which is in a non-writeable memory section. This is the code line triggering the exception:
Let's have a look at what the exception hander does. The first three instructions basically load
[ESP+C] in the
ECX register, which then points to the so-called
ContextRecord that also contains the faulting instruction's offset. (The dereferencing could have been done in a single instruction relative to
ESP, and I don't quite understand why this code uses
EAX for it - but hey, who's perfect.) Have a look at crash course on Win32 exception handling if you want to learn how to get the faulting address out of the context information.
Next, the handler decrements a stack variable that was initialized with
0x15000 at offset
0x0040109E. We will see shortly that this variable does in fact serve as some kind of loop counter. The decremented value is written back into the variable at offset
0x004010C5. What happens now is that the exception handler will continue execution at the faulting instruction, because this is where
_CONTEXT.Eip points to. Of course, the memory location is still not writeable, so we will end up in our exception handler again. Things are getting interesting when the counter variable is decremented to 0. Have a look at the following line:
0x0A to the saved faulting address, the offset where execution is continued after the exception handler returns is changed to
0x004010DD. So let's interpret the bytes at this address as instructions.
The picture below displays the disassembly of the code that is executed after the stack variable reaches 0. The first two instructions restore the SEH chain by resetting the pointer at
FS:. Then the image base is calculated from the return address of the call mentioned above by zeroing
EAX is set to 1 - we will see in a bit what that is good for.
As you can see, the jump instruction takes execution to an
INT 2E at offset
0x004010F0. This instruction calls a ring-0 subroutine and takes the system service's index in
EAX and a pointer to a parameter block in
EDX. In this case, the
AccessCheck() routine (i.e. routine
0x1) is being called. The purpose here is to trigger an access violation, so that is why
EDX has been loaded with
0x00400000. When this service routine throws an error, the error code is returned in
EDX contains the address of the instruction following the failing one. So that should be
0x004010F2 in this case. However, if the program is being executed either inside VirtualBox or with OllyDbg or also ImmunityDebugger attached, the
EDX register would contain
0xFFFFFFFF instead. So this fact may be used to detect if the process is being debugged or running inside a virtual machine.
What Waledac does with the instructions starting at
0x004010F3 is to derive an offset from
EDX and to push this value on the stack. It would then jump back to the
RETN instruction at
0x004010DC which, in turn, takes the computed value on the stack as return address and jumps there.
The problem is, how can we easily get the correct offset here? I decided to use a little trick I tried with success previously when analyzing SEH-based anti-debugging code: You can put a SoftICE-style breakpoint at offset
0x00401020, i.e. just before we jump back to the return instruction, by patching in a
JMP -2. This results in an endless loop. Now you can dump the patched version, execute it, wait a bit in order for the SEH loop to finish, and then attach a debugger. The picture above shows the situation when I re-attached - the highlighted instruction implements the "breakpoint". By inspecting the top-most stack entry, we see that the target offset is
0x0040561E. The final step is to restore the original jump instruction and continue execution inside the debugger. By the way, I did this in qemu which worked quite nicely.
The next stage functions independent from the previous parts. All it requires is an address to calculate the image base address from again. As there is still one on the stack (it is the one taken from
EDX after the
INT 2E, see above), we are fine. Here is the code of this part. Again, this could have been done more efficiently.
The jump instruction will take us to some self-modifying code at
0x00403000 that makes extensive use of floating point instructions, most likely to confuse analysts (I haven't looked into them too much). The decoded part starts at
0x004031E5 if you want to have a look yourself. In a nutshell, what it does is:
0x00405EF9to restore a valid PE file
0x00400000by overwriting the current image
PAGE_READONLYfor the import and data section respectively
This part does contain some trivial anti-debugging checks like embedded
INT3 instructions which are easy to handle. We can now dump the process or assemble the original binary from the new sections. If we take a look at the unpacked code in a disassembler, we observe two IP addresses this Waledac version is apparently trying to connect to. However, this is now standard reverse-engineering stuff and beyond this blog post. So I will leave it with that.
What we have seen is how this Waledac version tries protect itself from being debugged. It implements multiple code stages with the goal to confuse debuggers, but eventually restores its original code. By using debugging tools in a smart fashion we can still track and control the individual steps.