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
awesome! I was unsuccessful at debugging it, although I did try patching dwReason to 1 and 2, I kept getting caught out once its imagebase was called upon for a change of privilege.
ReplyDeleteTo be honest, I was pretty wasted with all that floating point crap.
Hi, good blog post! but the link to the idb file is down, could you please upload it again? Thanks
ReplyDelete