目录:
进阶技巧里包括
[x] 栈迁移 (Stack Pivoting)
[ ] Ret2csu (通用 Gadget 利用)
[ ] SROP (Sigreturn Oriented Programming)
[ ] BROP (Blind ROP)
栈迁移
适用条件:
在打 Pwn 的过程中,我们经常会遇到一种情况:溢出空间太小了
比如,程序只允许你溢出 8 到 16 个字节。这点空间刚够覆盖 rbp/ebp 和 返回地址,根本写不下一条完整的 ROP 链(比如泄露 Libc + 调用 system)。
原理:
要实现栈迁移,我们必须利用程序结束时必定会执行的汇编指令组合:leave; ret
在底层,这两条指令等价于以下三个原子操作(以 32 位为例):
leave等价于:mov esp, ebp(将 ebp 的值赋给 esp)pop ebp(将当前栈顶的内容弹给 ebp,esp 随之 +4)
ret等价于:pop eip(将当前栈顶的内容弹给 eip,程序跳转)

第一次 leave; ret:修改 ebp 和 eip
假设我们在 main 函数里,利用微小的溢出,把栈上的 ebp 覆盖成了目标迁移地址(Target_Addr),把 返回地址 覆盖成了程序代码段里某处 leave; ret 指令的地址 (Gadget)。
mov esp, ebp:正常执行,esp 回到当前函数的 ebp 位置。pop ebp:异常出现: 此时弹给ebp的不再是原来的旧 ebp,而是我们伪造的 Target_Addr。ret (pop eip):控制流被劫持: eip 没有回到上一层函数,而是跳到了我们布置的leave; retGadget 去执行。

第二次 leave; ret:完成迁移
此时 CPU 到了 Gadget 处,开始执行第二次 leave; ret:
mov esp, ebp:前面说到ebp里现在装着的是 Target_Addr。这条指令直接把esp也扯到了 Target_Addr。至此,栈被成功搬家pop ebp:把新栈(Target_Addr)最顶端的数据弹给ebp(通常可以填些无意义的 padding,如aaaa)。ret (pop eip):将新栈上的第二个数据弹给eip。此时eip里装入的就是我们提前布置好的 ROP 链起点(比如system的地址)
实战经验谈:迁移去哪里?
当栈不可执行且没有开启 PIE:通常迁移到
.bss段。我们在第一阶段向.bss段写入巨长的 ROP 链,第二阶段把栈迁移过去执行。当已知某处栈地址:也可以把栈往回迁(重新迁回
main栈或其他已知 Buffer),榨干所有的输入空间。
例题
下面是一道经典的 64 位栈迁移例题。题目特点:溢出空间极小,但自带 puts 泄露了输入缓冲区的地址 buf_adr。
我们利用这极其有限的空间,将栈迁移回 buf_adr 所在的已知位置,分两步走:
第一步:构造 Payload,泄露 Libc 基址,然后返回到
main函数重置状态。第二步:再次利用迁移,调用
system("/bin/sh")。
1 | from pwn import * p = remote('gz.imxbt.cn', 20630) |
需要注意:
栈对齐问题:在 64 位 Ubuntu 系统中,如果打不通,就在调用
system前试试加上一个单ret指令,保证 16 字节栈平衡,否则movaps指令会直接让你 Segmentation Fault 崩溃跳回 Main 的位置:如果在 ROP 链里跳回
main函数,尽量跳过main汇编开头的那句push rbp(比如直接跳到rsp - 0x30分配空间的那句),以免新的一轮执行把我们辛辛苦苦迁移的栈结构又给破坏了。
- 本文链接: http://example.com/2026/03/14/PWN/pwn_attack/stack_overflow/栈溢出进阶技巧/
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
欢迎关注我的其它发布渠道