Well, I changed my tune when I discovered that VirtualAlloc was not receptive to my suggestions for where to allocate memory. (WinDbg: bp <callsite>; g; ed esp <lpAddress>; p). Without a consistent shellcode base address, none of my annotations from the IDA memory snapshot I took were lining up with the actual shellcode in subsequent debug sessions.
Edit January 14th, 2018: At this point, we have a choose-your-own-adventure on our hands:
- If you use remote debugging, and/or you like to see IDA Pro annotations superimposed over your debugger session, and your shellcode itself allocates additional memory and executes code there, then you might be better off reading my fireeye.com blog article titled Debugging Complex Malware that Executes Code on the Heap.
- If you don't use remote debugging, then you might be satisfied capturing snapshots of your debugging VM at critical points in the debug session so you can iteratively debug and understand the shellcode.
- Finally, if your shellcode does not execute additional code on the heap and you just want to give it a uniform memory map in which to iteratively debug it, then read on...
For simple cases, you can reinvent the wheel and write your own shellcode loader to force shellcode to live at the same virtual address each time you debug it. But no need to start from scratch; here's the path of least resistance...
Assuming you have the shellcode as a raw binary, use xxd's feature of outputting shellcode as a C include file:
xxd -i myshellcode > myshellcode.c
That gives you a hexdump in C form:
unsigned char myshellcode[] = { 0x55, 0x8b, 0xec, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, ... 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, }; unsigned int myshellcode_len = 4242;
So now, write your loader:
#include "myshellcode.c" typedef void (*fptr)(void); int main(void) { fptr sc = (fptr) myshellcode; __asm int 3 ; Safety - so I don't execute this on my analysis box (or worse!) sc(); }
Then do this (in an SDK prompt):
cl.exe loader.c
If you don't have Visual Studio, just get Microsoft's compiler for Python 2.7.
After compiling and linking, you'll get this:
I was worried about execute permissions when calling into my shellcode, but happily, it Just Works, perhaps because I ran cl.exe directly without using Visual Studio to specify its usual flags. The program loads in IDA as a PE-COFF, it can be debugged using IDA's debugging plugins, and the shellcode is always at the same address (in my case, unk_40A000). Therefore, you can annotate the shellcode without using the IDA Pro memory snapshot facilities to save it from a debugger session, and (this is the important part) without worrying that VirtualAlloc will return a different address during the next debug session, rendering your annotations less useful to you. The same applies to breakpoints: they will actually work, from session to session. That makes life easier.
No comments:
Post a Comment