IO_FILE相关笔记
一、IO_FILE相关结构 _IO_FILE_plus
结构体的定义为:
1 2 3 4 5 struct _IO_FILE_plus { _IO_FILE file; const struct _IO_jump_t *vtable ; };
_IO_jump_t(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 };
_IO_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 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 };
进程中FILE
结构通过_chain
域构成一个链表,链表头部为_IO_list_all
全局变量,默认情况下依次链接了stderr
,stdout
,stdin
三个文件流,并将新建的流插入到头部,vtable
虚表为_IO_file_jumps
。
此外,还有_IO_wide_data
结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 struct _IO_wide_data { wchar_t *_IO_read_ptr; wchar_t *_IO_read_end; wchar_t *_IO_read_base; wchar_t *_IO_write_base; wchar_t *_IO_write_ptr; wchar_t *_IO_write_end; wchar_t *_IO_buf_base; wchar_t *_IO_buf_end; [...] const struct _IO_jump_t *_wide_vtable ; };
宏的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #define _IO_MAGIC 0xFBAD0000 #define _OLD_STDIO_MAGIC 0xFABC0000 #define _IO_MAGIC_MASK 0xFFFF0000 #define _IO_USER_BUF 1 #define _IO_UNBUFFERED 2 #define _IO_NO_READS 4 #define _IO_NO_WRITES 8 #define _IO_EOF_SEEN 0x10 #define _IO_ERR_SEEN 0x20 #define _IO_DELETE_DONT_CLOSE 0x40 #define _IO_LINKED 0x80 #define _IO_IN_BACKUP 0x100 #define _IO_LINE_BUF 0x200 #define _IO_TIED_PUT_GET 0x400 #define _IO_CURRENTLY_PUTTING 0x800 #define _IO_IS_APPENDING 0x1000 #define _IO_IS_FILEBUF 0x2000 #define _IO_BAD_SEEN 0x4000 #define _IO_USER_LOCK 0x8000
二、IO_FILE attack 之 FSOP (libc 2.23 & 2.24) 主要原理为劫持vtable
与_chain
,伪造IO_FILE
,主要利用方式为调用**IO_flush_all_lockp()**函数触发。IO_flush_all_lockp()
函数将在以下三种情况下被调用:
libc
检测到内存错误 ,从而执行abort
函数时(在glibc-2.26
删除)。
程序执行exit
函数时。
程序从main
函数返回时。
源码:
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 int _IO_flush_all_lockp (int do_lock) { int result = 0 ; struct _IO_FILE *fp ; int last_stamp; fp = (_IO_FILE *) _IO_list_all; while (fp != NULL ) { ... if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T || (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base))#endif ) && _IO_OVERFLOW (fp, EOF) == EOF) result = EOF; fp = fp->_chain; } [...] }
当满足以下条件,就会执行_IO_OVERFLOW()
1 2 fp->_mode = 0 fp->_IO_write_ptr > fp->_IO_write_base
所以在2.23的时候就可以这样构造来打FSOP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ._chain => chunk_addr chunk_addr { file = { _flags = "/bin/sh\x00" , _IO_read_ptr = 0x0 , _IO_read_end = 0x0 , _IO_read_base = 0x0 , _IO_write_base = 0x0 , _IO_write_ptr = 0x1 , ... _mode = 0x0 , _unused2 = '\000' <repeats 19 times> }, vtable = heap_addr } heap_addr { __dummy = 0x0 , __dummy2 = 0x0 , __finish = 0x0 , __overflow = system_addr, ... }
这时候chunk_addr里面的 _IO_write_ptr > _IO_write_base 且base=0 ,就会触发chunk_addr的 _IO_OVERFLOW 函数,执行 _IO_OVERFLOW(fp) ,也就是system(‘/bin/sh\x00’)
而libc-2.24
加入了对虚表的检查IO_validate_vtable()与 IO_vtable_check() ,若无法通过检查,则会报错:Fatal error: glibc detected an invalid stdio handle。
1 2 3 4 5 6 #define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH) #define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1) # define _IO_JUMPS_FUNC(THIS) \ (IO_validate_vtable \ (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS) \ + (THIS)->_vtable_offset)))
1 2 3 4 5 6 7 8 9 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; }
glibc
中有一段完整的内存存放着各个vtable
,其中__start___libc_IO_vtables
指向第一个vtable
地址_IO_helper_jumps
,而__stop___libc_IO_vtables
指向最后一个vtable_IO_str_chk_jumps
结束的地址。 若指针不在glibc
的vtable
段,会调用_IO_vtable_check()
做进一步检查,以判断程序是否使用了外部合法的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 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 ; } ... __libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n" ); }
因此,最好的办法是:我们伪造的vtable
在glibc
的vtable
段中,从而得以绕过该检查。 目前来说,有四种思路:利用_IO_str_jumps
中_IO_str_overflow()
函数,利用_IO_str_jumps
中_IO_str_finish()
函数与利用_IO_wstr_jumps
中对应的这两种函数,先来介绍最为方便的:利用_IO_str_jumps
中_IO_str_finish()
函数的手段。_IO_str_jumps
的结构体如下:
1 2 3 4 5 6 7 8 9 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), ... }
其中,_IO_str_finish
源代码如下:
1 2 3 4 5 6 7 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_fields
结构体与_IO_strfile_
结构体的定义:
1 2 3 4 5 6 7 8 9 10 11 struct _IO_str_fields { _IO_alloc_type _allocate_buffer; _IO_free_type _free_buffer; }; typedef struct _IO_strfile_ { struct _IO_streambuf _sbf ; struct _IO_str_fields _s ; } _IO_strfile;
可以看到,它使用了IO
结构体中的值当作函数地址来直接调用,如果满足条件,将直接将fp->_s._free_buffer
当作函数指针 来调用。 首先,仍然需要绕过之前的_IO_flush_all_lokcp
函数中的输出缓冲区的检查_mode<=0
以及_IO_write_ptr>_IO_write_base
进入到_IO_OVERFLOW
中。 我们可以将vtable
的地址覆盖成_IO_str_jumps-8
,这样会使得_IO_str_finish
函数成为了伪造的vtable
地址的_IO_OVERFLOW
函数(因为_IO_str_finish
偏移为_IO_str_jumps
中0x10
,而_IO_OVERFLOW
为0x18
)。这个vtable
(地址为_IO_str_jumps-8
)可以绕过检查,因为它在vtable
的地址段中。 构造好vtable
之后,需要做的就是构造IO FILE
结构体其他字段,以进入将fp->_s._free_buffer
当作函数指针的调用:先构造fp->_IO_buf_base
为/bin/sh
的地址,然后构造fp->_flags
不包含_IO_USER_BUF
,它的定义为#define _IO_USER_BUF 1
,即fp->_flags
最低位为0
。 最后构造fp->_s._free_buffer
为system_addr
或one gadget
即可getshell
。 由于libc
中没有_IO_str_jump
的符号,因此可以通过_IO_str_jumps
是vtable
中的倒数第二个表,用vtable
的最后地址减去0x168
定位。
持续更新中