We’ll next explore how to find the data the debugged application is using. First, let’s launch the debugger and set breakpoints onmainand thegreetingfunction. In this sec- tion, again, the memory addresses shown will likely be different from the memory addresses you see, so be sure to check where a value is coming from in this example out- put before using it directly yourself.
C:\grayhat>c:\debuggers\cdb.exe meet Mr Haxor ...
0:000> bm meet!main
*** WARNING: Unable to verify checksum for meet.exe 1: 00401060 meet!main
0:000> bm meet!*greet*
2: 00401020 meet!greeting 0:000> g
Breakpoint 1 hit ...
meet!main:
00401060 55 push ebp 0:000>
From looking at the source, we know thatmainshould have been passed the com- mand line used to launch the program via theargccommand string counter andargv, which points to the array of strings. To verify that, we’ll usedvto list the local variables, and then poke around in memory withdtanddbto find the value of those variables.
0:000> dv /V
0012fee4 @ebp+0x08 argc = 3
0012fee8 @ebp+0x0c argv = 0x00320e00
250
251
PARTIII
0:000> dt argv
Local var @ 0x12fee8 Type char**
0x00320e00
-> 0x00320e10 "meet"
From thedvoutput, we see thatargcandargvare, indeed, local variables withargc stored 8 bytes past the localebp, andargvstored atebp+0xc. Thedtcommand shows the data type ofargv to be a pointer to a character pointer. The address 0x00320e00 holds that pointer to 0x00320e10 where the data actually lives. Again, these are our val- ues—yours will probably be different.
0:000> db 0x00320e10
00320e10 6d 65 65 74 00 4d 72 00-48 61 78 6f 72 00 fd fd meet.Mr.Haxor...
Let’s continue on until we hit our second breakpoint at thegreetingfunction.
0:000> g
Breakpoint 2 hit ...
meet!greeting:
00401020 55 push ebp 0:000> kP
ChildEBP RetAddr
0012fecc 00401076 meet!greeting(
char * temp1 = 0x00320e15 "Mr", char * temp2 = 0x00320e18 "Haxor") 0012fedc 004013a0 meet!main(
int argc = 3,
char ** argv = 0x00320e00)+0x16 0012ffc0 77e7eb69 meet!mainCRTStartup(void)+0x170 0012fff0 00000000 kernel32!BaseProcessStart+0x23
You can see from the stack trace (or the code) thatgreetingis passed the two argu- ments we passed into the program aschar *. So you might be wondering, “how is the stack currently laid out?” Let’s look at the local variables and map it out.
0:000> dv /V
0012fed4 @ebp+0x08 temp1 = 0x00320e15 "Mr"
0012fed8 @ebp+0x0c temp2 = 0x00320e18 "Haxor"
0012fd3c @ebp-0x190 name = char [400] "???"
The variablenameis 0x190 aboveebp. Unless you think in hex, you need to convert that to decimal to put together a picture of the stack. You can use calc.exe to compute that or just ask the debugger to show the value 190 in different formats, like this:
0:000> .formats 190 Evaluate expression:
Hex: 00000190 Decimal: 400
So it appears that our variablenameis 0x190 (400) bytes aboveebp. Our two argu- ments are a few bytes afterebp. Let’s do the math and see exactly how many bytes are between the variables and then reconstruct the entire stack frame. If you’re following
252
along, step past the function prolog where the correct values are popped off the stack before trying to match up the numbers. We’ll go through the assembly momentarily.
For now, just pressPthree times to get past the prolog and then display the registers. (pr disables and enables the register display along the way.)
0:000> pr
meet!greeting+0x1:
00401021 8bec mov ebp,esp 0:000> p
meet!greeting+0x3:
00401023 81ec90010000 sub esp,0x190 0:000> pr
eax=00320e15 ebx=7ffdf000 ecx=00320e18 edx=00320e00 esi=00000000 edi=00085f38 eip=00401029 esp=0012fd3c ebp=0012fecc iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000206 meet!greeting+0x9:
00401029 8b450c mov eax,[ebp+0xc] ss:0023:0012fed8=00320e18
All right, let’s build up a picture of the stack, starting from the top of this stack frame (esp). Atesp(0x0012fd3c for us; it might be different for you), we find the function vari- ablename, which then goes on for the next 400 (0x190) bytes. Let’s see what comes next:
0:000> .formats esp+190 Evaluate expression:
Hex: 0012fecc
Okay,esp+0x190 (oresp+400 bytes) is 0x0012fecc. That value looks familiar. In fact, if you look at the preceding registers display (or use thercommand), you’ll see thatebp is 0x0012fecc. Soebpis stored directly aftername. We know thatebpis a 4-byte pointer, so let’s see what’s after that.
0:000> dd esp+190+4 l1 0012fed0 00401076
NOTE TheI1 (the letterlfollowed by the number1) after the address tells the debugger to display only one of whatever type is being displayed. In this case, we are displaying double words (4 bytes) and we want to display one (1) of them. For more info on range specifiers, see the debugger.chm HTML help topic “Address and Address Range Syntax.”
That’s another value that looks familiar. This time, it’s the function return address:
0:000> k
ChildEBP RetAddr
0012fecc 00401076 meet!greeting+0x9 0012fedc 004013a0 meet!main+0x16
0012ffc0 77e7eb69 meet!mainCRTStartup+0x170 0012fff0 00000000 kernel32!BaseProcessStart+0x23
253
PARTIII
When you correlate the next adjacent memory address and the stack trace, you see that the return address (savedeip) is stored next on the stack. And aftereipcome our function parameters that were passed in:
0:000> dd esp+190+4+4 l1 0012fed4 00320e15 0:000> db 00320e15
00320e15 4d 72 00 48 61 78 6f 72-00 fd fd fd fd ab ab ab Mr.Haxor...
Now that we have inspected memory ourselves, we can believe the graph shown in Chapter 7, shown again in Figure 11-1.