Walking the Stack Without Symbols and With FPO (Frame Pointer Omission)
In the previous post on stack corruptions, we have discussed the case where the stack becomes corrupted but still contains a chain of EBP references which allows for manual reconstruction. (For background reading, see this article on EBP stack reconstruction and calling convention nightmares on x86.)
Below is a call stack from an application crash dump. The reported crash was an access violation inside a module called “HelperLibrary” for which we don’t have symbols or source code. The call stack doesn’t look promising:
0:000> kv
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
0028fcec 74ba339a 7efde000 0028fd38 77479ed2
HelperLibrary+0x1014
0028fcf8 77479ed2 7efde000 776a5346 00000000
kernel32!BaseThreadInitThunk+0xe (FPO: [1,0,0])
0028fd38 77479ea5 011212b2 7efde000 00000000
ntdll!__RtlUserThreadStart+0x70 (FPO: [SEH])
0028fd50 00000000 011212b2 7efde000 00000000
ntdll!_RtlUserThreadStart+0x1b (FPO: [2,2,0])
There are no real frames here other than HelperLibrary+0x1014, but we’re pretty sure that there should be other code on the stack, such as the application’s main function :-)
To reconstruct something from this stack, you need to understand who called HelperLibrary+0x1014, even though you don’t have accurate symbols. Usually, it would be a matter of traversing EBP references, but if it were that easy, the debugger would already have done it!
OK, so what happened to EBP?
0:000> r ebp
ebp=0034fbfc
0:000> ln ebp
0:000> u ebp
0034fbfc 08fc or ah,bh
0034fbfe 3400 xor al,0
0034fc00 9a33ba7400e0fd call FDE0:0074BA33
0034fc07 7e48 jle 0034fc51
0034fc09 fc cld
0034fc0a 3400 xor al,0
0034fc0c d29e477700e0 rcr byte ptr [
esi-1FFF88B9h],cl
0034fc12 fd std
In case you haven’t noticed, this isn’t actual code—it’s a bunch of data which is interpreted as instructions. It is entirely possible that EBP has been corrupted to point to a totally unrelated location, but there is also another possibility: that the current code is using FPO.
What’s FPO? FPO (Frame Pointer Omission) is an optimization technique whereas the compiler uses the EBP register as a scratch value for storing miscellaneous data, like any other general-purposes register. How are local variables and function parameters handled? Directly through ESP.
In other words, when using FPO (which you can enable with the /Oy compilation switch), the compiler is free to refrain from creating a “real” stack frame, with the previous EBP value. There is no linked list of stack frames starting at the current EBP value. The debugger isn’t capable of doing anything without FPO information, which is present in the symbol files, which we don’t have.
This leaves us with disassembling HelperLibrary+0x1014 and try to figure out manually where it returns. Let’s take a look at the vicinity of HelperLibrary+0x1014 (the offending instruction in bold):
0:000> u HelperLibrary+0x1014-0x14 L8
HelperLibrary+0x1000:
66951000 56 push esi
66951001 ff157c209566 call dword ptr [6695207c]
66951007 50 push eax
66951008 c60061 mov byte ptr [eax],61h
6695100b ff1578209566 call dword ptr [66952078]
66951011 83c408 add esp,8
66951014 c60661 mov byte ptr [esi],61h
66951017 c3 ret
This looks, indeed, like a frame with FPO – there is no EBP to be seen. However, the subsequent ret instruction is going somewhere – so we can look at ESP and find the return address there:
0:000> dps esp L1
0034fb9c 66951040 HelperLibrary+0x1040
OK, so what’s at HelperLibrary+0x1040?
0:000> u HelperLibrary+0x1040-0x20 LC
HelperLibrary+0x1020:
66951020 56 push esi
66951021 8bf0 mov esi,eax
66951023 56 push esi
66951024 ff157c209566 call dword ptr [6695207c]
6695102a 50 push eax
6695102b c60061 mov byte ptr [eax],61h
6695102e ff1578209566 call dword ptr [66952078]
66951034 03742410 add esi,dword ptr [esp+10h]
66951038 83c408 add esp,8
6695103b e8c0ffffff call HelperLibrary+0x1000
66951040 5e pop esi
66951041 c3 ret
Interesting. This frame doesn’t use EBP either, so we can expect the return value to be at ESP+4 (because of the pop instruction immediately prior to returning). But how do we figure out the value of ESP when in this function? Well, suppose that the previous function returns. It has removed four bytes (the return address) from the stack. The next value of ESP, then, is ESP+4, and we need to add another four bytes to account for the “pop esi” instruction.
0:000> dps esp+8 L1
0034fba4 6695106b HelperLibrary!ImportantFunction+0x1b
All right! We are making some real progress—we have a really small offset, and even though there are no symbols, ImportantFunction is probably an exported function, so we have its location in the DLL:
0:000> u HelperLibrary!ImportantFunction LD
HelperLibrary!ImportantFunction:
66951050 8b442404 mov eax,dword ptr [esp+4]
66951054 85c0 test eax,eax
66951056 7501 jne HelperLibrary!ImportantFunction+0x9
66951058 c3 ret
66951059 837c240c00 cmp dword ptr [esp+0Ch],0
6695105e 7e0e jle HelperLibrary!ImportantFunction+0x1e
66951060 8b4c2408 mov ecx,dword ptr [esp+8]
66951064 49 dec ecx
66951065 51 push ecx
66951066 e8b5ffffff call HelperLibrary+0x1020
6695106b 83c404 add esp,4
6695106e b801000000 mov eax,1
66951073 c3 ret
This is another function with no trace of EBP use in it. Note that it has parameters, and it accesses these parameters using direct offsets from ESP—which is a tell-tale sign of FPO. Where does this function return to? Well, after the previous function returns, ESP is already at ESP+0xC from its current value. ImportantFunction adds another four bytes, and then returns—so we need to look at ESP+0x10:
0:000> dps esp+0x10 L1
0034fbac 00ed100f MainApp!wmain+0xf
Yes! We’re out of the DLL, and back to symbol-land! The reconstructed stack, therefore, looks like this:
HelperLibrary!…somefunction…
HelperLibrary!…someotherfunction…
HelperLibrary!ImportantFunction
MainApp!wmain
None of this was provided by the debugger. For reference, here is the same call stack with symbols (which contain FPO information)—we were right on the money!
0:000> kv
ChildEBP RetAddr Args to Child
003dfee4 668e1040 00000001 668e106b 0000000f
HelperLibrary!AnotherHelperFunction+0x14 (FPO: [0,0,0])
003dfeec 668e106b 0000000f 010c100f 010c20f4
HelperLibrary!HelperFunction+0x20 (FPO: [1,0,4])
003dfef4 010c100f 010c20f4 00000010 00000001
HelperLibrary!ImportantFunction+0x1b (FPO: [3,0,0])
003dff04 010c1191 00000001 00771b78 00771c10
MainApp!wmain+0xf (FPO: [2,0,0])
003dff48 74ba339a 7efde000 003dff94 77479ed2
MainApp!__tmainCRTStartup+0x122 (FPO: [Non-Fpo])
003dff54 77479ed2 7efde000 777db3e9 00000000
kernel32!BaseThreadInitThunk+0xe (FPO: [1,0,0])
003dff94 77479ea5 010c12b2 7efde000 00000000
ntdll!__RtlUserThreadStart+0x70 (FPO: [SEH])
003dffac 00000000 010c12b2 7efde000 00000000
ntdll!_RtlUserThreadStart+0x1b (FPO: [2,2,0])