栈迁移原理深入理解以及实操
字数 1418 2025-08-20 18:17:00
栈迁移原理深入理解及实战应用
前言
栈迁移技术主要用于解决栈溢出空间受限的情况。当题目只允许覆盖ebp和ret_addr等少量字节时,传统ROP链无法完整布置,此时需要通过栈迁移将栈转移到其他可控制区域(如bss段)来执行攻击。
栈迁移核心原理
关键指令分析
栈迁移依赖于leave; ret指令组合,这两个指令的功能如下:
-
leave指令:
- 等价于
mov esp, ebp; pop ebp - 先将ebp值赋给esp,使esp和ebp指向同一地址
- 然后弹出栈顶内容到ebp寄存器
- 等价于
-
ret指令:
- 等价于
pop eip - 将栈顶内容弹出到eip寄存器,作为下一条执行指令
- 等价于
栈迁移执行流程
-
第一次leave;ret:
mov esp, ebp:esp和ebp指向同一地址pop ebp:将栈顶内容(攻击者控制的迁移目标地址)存入ebpret:执行第二次leave;ret
-
第二次leave;ret:
mov esp, ebp:esp迁移到目标地址pop ebp:弹出无用内容ret:执行目标地址处的ROP链
栈迁移实战案例
案例1:ciscn_2019_es_2(32位)
题目特点
- 32位程序,开启NX保护
- 存在printf格式化字符串漏洞
- 输入缓冲区仅40字节,但允许读入48字节
利用思路
-
泄露ebp地址:
- 利用printf不自动补\0的特性,填满缓冲区泄露ebp
-
构造ROP链:
- 在bss段布置system地址和"/bin/sh"字符串
- 计算s参数地址与ebp的偏移(0x38)
-
栈迁移执行:
pl2 = 'aaaa' + p32(system) + p32(1) + p32(binsh) + "/bin/sh\00" pl2 = pl2.ljust(0x28,'p') pl2 += p32(s) + p32(leave_ret)
关键点
- 使用send而非sendline避免添加\0截断
- 精确计算binsh字符串位置(ebp-0x28)
案例2:gyctf_2020_borrowstack(64位)
题目特点
- 64位程序
- 第一个read可溢出16字节(覆盖rbp和ret_addr)
- 第二个read允许在bss段写入大量数据
利用思路
-
迁移到bss段:
- 将栈迁移到bank+0xd0位置,避开关键区域
-
泄露libc地址:
pl2 = 'a'*0xd0 + p64(0) + p64(rdi_addr) + p64(puts_got) + p64(puts_plt) + p64(main) -
二次迁移执行onegadget:
pl4 = p64(0) + p64(one_gadget) + p64(0)*10
关键点
- bss段布局需避开got表等关键区域
- 需要保持栈结构完整性(填充无用数据)
案例3:[Black Watch入群题]PWN
题目特点
- 第一个read无溢出,第二个read存在溢出
- 需在bss段构造完整ROP链
利用思路
-
首次输入布置ROP:
pl = p32(0) + p32(puts_plt) + p32(main) + p32(puts_got) -
栈迁移泄露libc:
pl2 = 'a'*0x18 + p32(s_addr) + p32(leave_ret) -
二次迁移getshell:
pl3 = p32(0) + p32(system) + p32(0) + p32(binsh)
通用利用技巧
-
地址计算:
- 32位:参数地址通常为ebp-0x28
- 64位:注意bss段与got表的距离
-
指令查找:
leave; ret指令可通过ROPgadget工具查找- 32位常见地址:0x080485FD
- 64位常见地址:0x0000000000400699
-
输入方式选择:
- read函数:优先使用send
- gets/scanf函数:必须使用sendline
-
栈结构维护:
- 迁移后需保持栈平衡
- 必要时填充无用数据(如p64(0)*10)
总结
栈迁移技术通过精心控制ebp和esp寄存器,将执行环境转移到攻击者可控区域。关键在于:
- 精确计算目标地址偏移
- 合理布置两次leave;ret的执行流程
- 根据题目特点选择适当的输入方式和栈结构维护方法
掌握栈迁移技术可有效解决栈溢出空间受限的难题,是现代pwn题中的重要攻击手段。