修改返回地址引发调用约定异常问题排查及返回式调用咨询
Let's break down why you're hitting that ESP assertion error and how to fix it. The core issue lies in how your g() function is being entered and how you're trying to modify its return address—since g() isn't being called via a normal call instruction, the stack structure isn't what your assembly code expects.
Why the Error Happens
When you modify f()'s return address to point to g(), f() uses a ret instruction to jump to g(). This is very different from a normal call g():
- A
callinstruction pushes the return address (the next instruction aftercall) onto the stack before jumping tog(). - But
f()'sretjust popsg()'s address from the stack and jumps there—no return address forg()is pushed onto the stack.
When g() starts executing, the compiler-generated prologue runs:
push ebp mov ebp, esp
At this point, [ebp+4] does NOT hold a return address (unlike in a normal call scenario). Instead, it points to whatever was on the stack before g() was entered (in your case, this is part of main()'s stack frame). When your assembly code does mov [ebp + 0x04], edx, you're overwriting a random stack value, not a valid return address.
When g() finishes, the compiler-generated epilogue runs:
pop ebp ret
The ret instruction tries to pop a return address from the stack—but there's no valid return address there (since g() wasn't called with call). This corrupts the stack pointer (ESP), leading to the Run-Time Check Failure when main() exits.
How to Fix It
Instead of trying to overwrite a non-existent return address in g(), you need to manually restore the stack to its correct state and jump directly to the original return address (stored in default_return_address). Here's the corrected g() function:
void g() { std::cout << "g ran\n"; __asm { // Restore EBP to its state before g() was entered (main()'s EBP) pop ebp // Jump directly to the original return address (skips g()'s compiler-generated ret) jmp default_return_address }; }
Why This Works
pop ebpundoes the compiler-generatedpush ebping()'s prologue, restoring EBP to the value it had wheng()was entered (which ismain()'s EBP).jmp default_return_addressjumps directly back to the instruction inmain()right afterf()was called, without usingret. This avoids trying to pop an invalid return address from the stack, keeping ESP balanced throughout the program.
Additional Notes
- Your code in
f()already correctly captures the original return address intodefault_return_address—keep that part as is. - When manipulating return addresses directly, avoid relying on compiler-generated prologues/epilogues. They assume a standard stack structure from a
callinstruction, which doesn't apply when jumping viaret.
内容的提问来源于stack exchange,提问作者user4332554

