Some of you may know that I work as a research engineer and get to deal with fresh malware on a regular basis. Those of you who either work in the security sector or take part in the good old AV vs. VX vs. Blackhats challenge will also know that stepping through a piece of malcode isn't really the same as disassembling winmine.exe. Apart from the fact that most people who actually release malware into the wilderness just can't be convinced to include debugging information (come on guys, be "1337") we today face a lot of techniques that were specially designed to make an analyst's life harder.
Most notably we face Anti-Debugging and Anti-Reversing. (And of course anti-anti-anti-foo... the list goes on.)
Then there are some other techniques that aren't directly geared towards researchers but try to accomplish something else, like for example escalating privileges or circumventing the firewall. I'm not going into detail here either, but everyone who has a shortcut for "launch notepad.exe in OllyDBG, break on new thread and dump the process id to a file" should know what I mean.
And then there are those moments where you just want to shout, because someone invented something completely new. Some days ago I was facing some good malware. The name may not be mentioned, so let's just call it "XY" for clarity's sake. XY pulled about every trick in the book, including - among others - indirect calls, process injection, debugger detection and encryption. Those could be defeated using some careful engineering and lots of coffee. Somewhere in the code I ran into this statement (obfuscated):
-=ASM=-
push ebp
call loc_XYZ
pop ebp
-=/ASM=-
This is something we see almost every day. A regular internal function call. By quickly checking over XYZ, I could see that it contained a lot of crypto-foo. Thus I tried to see what it changed before going into more detail and decided to step over the function.
Interestingly enough, the malware crashed with a memory violation. Now that is not very unusual either, since anti-debugging techniques tend to lead to this result, but just to make sure, I loaded a snapshot and hit F9 from the same spot. But while it should have crashed again right then, it just exited gracefully. I decided to set a breakpoint on pop ebp manually, to check if this was a bug in the debugger. The results were the same as when stepping over. The malware crashed. This began to make me confused. But it still had to get a little worse. I removed the software breakpoint on pop ebp and replaced it with a hardware breakpoint. Now the program didn't crash, but it didn't stop either. It just ran through.
Confused enough yet? Bear with me for another minute.
I couldn't figure this one out. And luckily lunch-break came up quickly. While returning to the office, two of my coworkers were arguing over which way to walk. "This way is more direct" "This one is faster" "But I need to go to that store". Suddenly something in my brain shifted. When we arrived at the office, I was soon bent over laughing.
What happened?
Their conversation was the missing piece. It made me realize one essential fact: loc_XYZ never returned the flow of execution to the calling function at all. Whoever wrote this used the function call like a jmp instruction. It was a one way path. Later during the analysis I was able to discover a piece of code, that then checked if "pop ebp" had been modified. By setting a software breakpoint and stepping over, "pop ebp" had become "CC CC". The malware noticed this and faked a crash to hinder the analysis. This also explained why running the malware and setting a hardware breakpoint didn't do anything. push ebp remained unchanged.
Sometimes we need to keep our eyes open for new possibilities. To my friends from the AV: Good luck on generating a working fingerprint for this one.