Thursday, February 19, 2009

Making Conficker Cough Up the Goods

I'm not a malware gal. I really dislike analyzing the stuff. It could be an artifact of a life spent pulling apart Microsoft binaries. When Microsoft releases a binary, everything looks the same; it's not a challenge to figure out what's going on. The only challenge is how fast can you find the bug, and then how fast can you traverse the flow of the code to get there with some input. With malware though, this is anything but the case, and experience with exploitation doesn't really go very far. All you can do is apply your knowledge of asm and systems and hope you have the time to make sense of it. Conficker wasn't as frustrating as some, but it still had me cringing. The goal was to take the dll, and make it spit out some dns traffic so we could test our SO rule conficker dns detection engine which was written with a generation algorithm provided through the MAPP program in conjunction with Microsoft. We'd paired it down a good bit, and some information about randomness from other write-ups around the net conflicted with what was provided to us. Not wanting to put out incorrect protection, we decided to prove it's use with a few live samples.

The first step was to attempt to get it running. I'd read elsewhere that it had a few anti-debugging and anti-vmware techniques embedded in it, but I was confident that I could recognize those and circumvent them in the debugger. I whipped up a quick little wrapper program that simply calls LoadLibrary on the DLL, since the only export seems to be DllMain(). Apparently, this isn't enough for conficker, and it will immediately error out. The quick and dirty fix to get it to do it's thang, is to change the second argument (dwReason) from zero to one and let it roll. At this point, you can dump out the first set of unpacked code. The first section is UPX packed, and a quick look at the end of the unpacker will show you a jump to 0x1000442b. I set a breakpoint there and allowed it to unpack itself. As I originally figured that this would be all it intended to unpack, I dumped out the new code to a binary file. !vadump showed the currently executing code page to have the following properties:
BaseAddress:       10001000
RegionSize: 00019000
State: 00001000 MEM_COMMIT
Protect: 00000040 PAGE_EXECUTE_READWRITE
Type: 01000000 MEM_IMAGE

So, to dump the code to a binary file which can be examined in IDA, you would type

.writemem conficker-loader.bin 0x10001000 0x1001bfff

After loading up this binary into IDA, rebasing it to 0x10001000, and beginning a decode, it became quickly apparent where the function table for the new code lay. All over the code there are calls such as:
seg000:10004415                 push    eax
seg000:10004416 call ds:dword_1000507C

seg000:1000507C dword_1000507C dd 77C2C21Bh ; DATA XREF: sub_10004380+96r

At 0x10005000 we find a list of dwords all in a similar range, all referenced as DATA XREF's from the code. If we walk this list, and resolve it in windbg, we find that it is the function pointer table containing all the resolved library functions for the virus. This makes reading the code in IDA a much simpler task:
seg000:10004415                 push    eax
seg000:10004416 call ds:free

Once the loading code is readable, you can look around and begin to tweak the flow of the virus to go where you'd like. I noticed that one function called LoadLibrary a number of times, but required dwReason to be 3, so I made a change and walked a few different paths. Finally, I was able to get the LoadLibrary call for conficker to return a positive value, indicating success. Since it returned so quickly, I assumed there had to be another thread of execution which was doing all of the real work, so I added a while(1) to my loader, and broke in as it toiled away. The other thread was assuredly running, and in an odd area: 0x003a1000. Dumping this code area once more and repeating the tricks above provided a working idb of the meat of the conficker virus. Knowing that the virus produces new names every day allowed me to traverse the list of imported functions to find the only two calls to GetSystemTime, and work out from there, adding comments to the file and building out the genDNSName function now shown.

The one very interesting thing is that it takes no function arguments. So if all you really want is to confirm DNS name creation, you can simply set EIP equal to the start of that function, 0x003ADD9B and set a breakpoint at the end of the loop (but before registers are incremented) at 0x003ADE77. Pointing the windbg memory window at the address allocated to store the string (which can be found at ebp+edi*4-488h) will provide you with your list of names:

0015f3b0 v n f e l x p q v . n e t . .

In conclusion, virii are a serious pain in the rear, and I'd really appreciate if you'd stop writing them so I don't have to keep reversing them L
Add to Technorati Favorites Digg! This
Post a Comment