-
-
[原创]_IO_FILE劫持的一点总结
-
发表于: 2021-1-3 20:47 7959
-
本文首先介绍文件流中涉及到的重要struct,并从fopen打开一个文件开始分析了打开文件的过程,之后通过分析fread函数调用,并从汇编层面分析vtable函数跳转表执行的过程,给出可利用方案,最后分析了libc对该劫持的漏洞利用缓解措施。本文源码参考glibc-2.23版本
struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */ char* _IO_read_ptr; /* Current read pointer */ char* _IO_read_end; /* End of get area. */ char* _IO_read_base; /* Start of putback+get area. */ char* _IO_write_base; /* Start of put area. */ char* _IO_write_ptr; /* Current put pointer. */ char* _IO_write_end; /* End of put area. */ char* _IO_buf_base; /* Start of reserve area. */ char* _IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */ struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno; #if 0 int _blksize; #else int _flags2; #endif _IO_off_t _old_offset; /* This used to be _offset but it's too small. */ #define __HAVE_COLUMN /* temporary */ /* 1+column number of pbase(); 0 is unknown. */ unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; /* char* _save_gptr; char* _save_egptr; */ _IO_lock_t *_lock; _IO_off64_t _offset; /* Wide character stream stuff. */ struct _IO_codecvt *_codecvt; struct _IO_wide_data *_wide_data; struct _IO_FILE *_freeres_list; void *_freeres_buf; size_t __pad5; int _mode; /* Make sure we don't get into trouble again. */ char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)]; #endif };
struct _IO_FILE_plus { _IO_FILE file; const struct _IO_jump_t *vtable; };;
struct locked_FILE { struct _IO_FILE_plus fp; _IO_lock_t lock; struct _IO_wide_data wd; } *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE));
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps); _IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
通过_IO_file_init将fp链入全局输入输出流链表_IO_list_all _IO_file_init (&new_f->fp); //该列表上的stdin,stdout,stderr是存储在libc中的,DEF_STDFILE是初始化其对应的结构体的 //_IO_file_init (&new_f->fp); DEF_STDFILE(_IO_2_1_stdin_, 0, 0, _IO_NO_WRITES); DEF_STDFILE(_IO_2_1_stdout_, 1, &_IO_2_1_stdin_, _IO_NO_READS); DEF_STDFILE(_IO_2_1_stderr_, 2, &_IO_2_1_stdout_, _IO_NO_READS+_IO_UNBUFFERED); struct _IO_FILE_plus *_IO_list_all = &_IO_2_1_stderr_; //_IO_list_all可以通过libc的基地址计算出来
char* _IO_read_ptr; /* Current read pointer */ // 内容指针 char* _IO_read_end; /* End of get area. */ char* _IO_read_base; /* Start of putback+get area. */ char* _IO_write_base; /* Start of put area. */ char* _IO_write_ptr; /* Current put pointer. */ char* _IO_write_end; /* End of put area. */ char* _IO_buf_base; /* Start of reserve area. */ //缓冲区的基地址 char* _IO_buf_end; /* End of reserve area. */ // 缓冲区的大小,读取的内容存放的地方
glibc-2.23\glibc-2.23\libio\genops.c _IO_doallocbuf -> _IO_DOALLOCATE (fp) ===> _IO_file_doallocate F:\源码阅读\glibc-2.23\glibc-2.23\libio\filedoalloc.c int _IO_file_doallocate (_IO_FILE *fp){ ... p = malloc (size); if (__glibc_unlikely (p == NULL)) return EOF; _IO_setb (fp, p, p + size, 1); // __IO_setb会设置_IO_buf_base }
have = fp->_IO_read_end - fp->_IO_read_ptr; if (want <= have) // want为需要的字节长度,have为已读取到的字节长度,如果满足已经读取到的字节长度满足需要读取的字节长度,则将缓冲区中的数据cp到读取区域 { memcpy (s, fp->_IO_read_ptr, want);// 拷贝到缓冲区 fp->_IO_read_ptr += want; want = 0; }
if (have > 0) { #ifdef _LIBC s = __mempcpy (s, fp->_IO_read_ptr, have); #else memcpy (s, fp->_IO_read_ptr, have); s += have; #endif want -= have; fp->_IO_read_ptr += have; }
\glibc-2.23\libio\genops.c __underflow -> _IO_UNDERFLOW ===> _IO_new_file_underflow \glibc-2.23\libio\fileops.c _IO_new_file_underflow _IO_new_file_underflow (_IO_FILE *fp){ ... fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_buf_base;//__IO_read_base指向 _IO_buf_base fp->_IO_read_end = fp->_IO_buf_base; fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end = fp->_IO_buf_base; count = _IO_SYSREAD (fp, fp->_IO_buf_base, fp->_IO_buf_end - fp->_IO_buf_base);//_IO_file_read fileops.c fp->_IO_read_end += count; ... }
if (fp->_IO_buf_base && want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base)) { if (__underflow (fp) == EOF) break; continue; }
_IO_size_t _IO_sgetn (_IO_FILE *fp, void *data, _IO_size_t n) { /* FIXME handle putback buffer here! */ return _IO_XSGETN (fp, data, n); //fileops.c -> _IO_file_xsgetn }
((*(__typeof__ (((struct _IO_FILE_plus){}).vtable) *)(((char *) ((fp))) + __builtin_offsetof (struct _IO_FILE_plus, vtable)))->__xsgetn) (fp, data, n)
sub rsp, 8 ; Alternative name is '_IO_sgetn' mov rax, [fp + 0D8h] ; fp的偏移0xd8的vtable的值,32 位的 vtable 偏移为 0x94,64 位偏移为 0xd8 call qword ptr [rax+40h] ; 调用指定函数,[rax+40h]指向libc上 add rsp, 8 retn ####
$47 = { __dummy = 0, __dummy2 = 0, __finish = 0x7ffff7ab81d3 <_IO_new_file_finish>, __overflow = 0x7ffff7ab8981 <_IO_new_file_overflow>, __underflow = 0x7ffff7ab8724 <_IO_new_file_underflow>, __uflow = 0x7ffff7ab96da <__GI__IO_default_uflow>, __pbackfail = 0x7ffff7aba2ad <__GI__IO_default_pbackfail>, __xsputn = 0x7ffff7ab7c6b <_IO_new_file_xsputn>, __xsgetn = 0x7ffff7ab7ebc <__GI__IO_file_xsgetn>, __seekoff = 0x7ffff7ab73a2 <_IO_new_file_seekoff>, __seekpos = 0x7ffff7ab9907 <_IO_default_seekpos>, __setbuf = 0x7ffff7ab724b <_IO_new_file_setbuf>, __sync = 0x7ffff7ab7191 <_IO_new_file_sync>, __doallocate = 0x7ffff7aac841 <__GI__IO_file_doallocate>, __read = 0x7ffff7ab7b69 <__GI__IO_file_read>, __write = 0x7ffff7ab7bc6 <_IO_new_file_write>, __seek = 0x7ffff7ab7930 <__GI__IO_file_seek>, __close = 0x7ffff7ab7165 <__GI__IO_file_close>, __stat = 0x7ffff7ab7bad <__GI__IO_file_stat>, __showmanyc = 0x7ffff7aba3f5 <_IO_default_showmanyc>, __imbue = 0x7ffff7aba3fb <_IO_default_imbue> }
_IO_size_t _IO_sgetn (_IO_FILE *fp, void *data, _IO_size_t n) { /* FIXME handle putback buffer here! */ return _IO_XSGETN (fp, data, n); }
#在跳转的时候函数定义为JUMP2(FUNC,THIS,X1,X2) #define _IO_XSGETN(FP, DATA, N) JUMP2 (__xsgetn, FP, DATA, N) #define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)
libc_base_address -> _IO_list_all -> 任意结构体地址 -> 偏移计算vtable -> vtable表地址 libc_base_address -> _std_in/_std_out/_std_err地址 -> 偏移计算vtable -> vtable表地址
//libc-2.23 # define _IO_JUMPS_FUNC(THIS) _IO_JUMPS_FILE_plus (THIS) //libc-2.24 # define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS)))
static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) { /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ 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)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; }
本文首先介绍文件流中涉及到的重要struct,并从fopen打开一个文件开始分析了打开文件的过程,之后通过分析fread函数调用,并从汇编层面分析vtable函数跳转表执行的过程,给出可利用方案,最后分析了libc对该劫持的漏洞利用缓解措施。本文源码参考glibc-2.23版本
- IO_FILE 的结构体如下
- IO_FILE_PLUS 的 结构体
- 通过 fopen 打开文件的时候,会在 堆上 为该文件申请一片 locked_FILE 结构体的空间,用来存储该文件的文件流信息
- 随后会初始化 结构体中fp的值
- 将该堆结构链入全局变量 _IO_list_all
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-1-5 18:03
被seana编辑
,原因:
赞赏
他的文章
- [原创]使用一个虚拟机解不同版本的libc 题目 10726
- [原创]一个虚拟机解不同libc 的pwn 题 5974
- [原创]_IO_FILE劫持的一点总结 7960
- [求助] gdb 调试求助 4906
- [原创]PWN入门的一些总结 17146
看原图
赞赏
雪币:
留言: