You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

修改返回地址引发调用约定异常问题排查及返回式调用咨询

Fixing Run-Time Check Failure #0: ESP Not Properly Saved in Return Address Manipulation Experiment

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 call instruction pushes the return address (the next instruction after call) onto the stack before jumping to g().
  • But f()'s ret just pops g()'s address from the stack and jumps there—no return address for g() 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

  1. pop ebp undoes the compiler-generated push ebp in g()'s prologue, restoring EBP to the value it had when g() was entered (which is main()'s EBP).
  2. jmp default_return_address jumps directly back to the instruction in main() right after f() was called, without using ret. 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 into default_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 call instruction, which doesn't apply when jumping via ret.

内容的提问来源于stack exchange,提问作者user4332554

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 15:42:38