Over a million developers have joined DZone.

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
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
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 [
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
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
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
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:


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])


Published at DZone with permission of Sasha Goldshtein, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}