Tutorial: Using WinDBG to call arbitrary functions — WinDBG kung-fu series
Most people developing for MS would praise how MSVS debugger is the best debugger out there (I doubt if many of them have actually used any other debuggers when they say that). That may be true for managed code debugging (.NET), but I just find that MSVS feels severely crippled when it comes to hard-core low level stuffs in C/C++.
This example shows you how to use the better tool for the job — WinDBG. While still severely crippled compared to GDB+Python scripting. Anyway…
The example program
#include <iostream> #include <string> class foo { public: foo(const char * name) { this->name = std::string(name); } void speak() { std::cout << "Hello, my name is " << name << std::endl; } private: std::string name; }; int main() { foo * fred = new foo("fred"); fred->speak(); delete fred; return 0; }
Our objectives
Have the output say “Hello, my name is chris” by creating a new foo object on the fly. Now the action
Starting up…
> cl /EHsc /Zi /Fefoo.exe /Fdfoo.pdb foo.cpp
> windbg foo.exe
0:000> bm main; g
F10 a few times to stop at the line fred->speak()
.
The first command we’ll introduce is .dvalloc
. We can allocate memory on the heap in the process’ address space.
Since foo::foo
needs a string, we’ll first create a string.
0:000> * just put some arbitrary size
0:000> .dvalloc 100
Allocated 1000 bytes starting at 00150000
You’ll notice that we got allocated 1000 bytes instead of 100 as we asked. That’s OK. They just like to give us whole pages.
Now we’ll put some string in that memory address
0:000> eza 0x150000 "chris"
Now we’ll call foo::foo
. All member methods have an implicit first parameter that should be this
. Compiler usually inject that for us, but here we need to specify it.
Now we’ll allocate some space for our foo
. This time around we’ll use malloc
0:000> .call malloc(100); g
Thread is set up for call, 'g' will execute.
WARNING: This can have serious side-effects,
including deadlocks and corruption of the debuggee.
.call returns:
void * 0x00c21310
foo!main+0x6b:
0126145b 8b4df0 mov ecx,dword ptr [ebp-10h] ss:002b:0032fe2c=00c21258
The thing about malloc
is that we can get the return value nicely stored in a pseudo-register $callret
. I usually prefer that to .dvalloc
. Having the return value in $callret
aids a lot in scripting WinDBG. We’ll not touch on that to not make things too difficult.
Now onto creating the foo
object:
0:000> * recall these addresses we allocated before
0:000>.call foo::foo(0x21310, 0x150000); g
Thread is set up for call, 'g' will execute.
WARNING: This can have serious side-effects,
including deadlocks and corruption of the debuggee.
foo!main+0x6b:
0126145b 8b4df0 mov ecx,dword ptr [ebp-10h] ss:002b:0032fe2c=00c21258
Great, the final touch is to swap out fred with our newly created object.
0:000>ep @@(&fred) 0x21310
0:000>g
Cool! You should get “Hello, my name is chris”