IO_FILE Exploit
Structrue
_IO_FILE
:
struct _IO_FILE
{
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
/* The following pointers correspond to the C++ streambuf protocol. */
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;
int _flags2;
__off_t _old_offset; /* This used to be _offset but it's too small. */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
_IO_FILE_plus
:
struct _IO_FILE_plus
{
FILE file;
const struct _IO_jump_t *vtable;
};
_IO_jump_t
:
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);
/* showmany */
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);
};
下面简单说说一些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_strfile
:
struct _IO_str_fields
{
/* These members are preserved for ABI compatibility. The glibc
implementation always calls malloc/free for user buffers if
_IO_USER_BUF or _IO_FLAGS2_USER_WBUF are not set. */
_IO_alloc_type _allocate_buffer_unused;
_IO_free_type _free_buffer_unused;
};
/* This is needed for the Irix6 N32 ABI, which has a 64 bit off_t type,
but a 32 bit pointer type. In this case, we get 4 bytes of padding
after the vtable pointer. Putting them in a structure together solves
this problem. */
struct _IO_streambuf
{
FILE _f;
const struct _IO_jump_t *vtable;
};
typedef struct _IO_strfile_
{
struct _IO_streambuf _sbf;
struct _IO_str_fields _s;
} _IO_strfile;
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */
#define _IO_UNBUFFERED 2
#define _IO_NO_READS 4 /* Reading not allowed */
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_EOF_SEEN 0x10
#define _IO_ERR_SEEN 0x20
#define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */
#define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/
#define _IO_IN_BACKUP 0x100
#define _IO_LINE_BUF 0x200
#define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */
#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
FSOP
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 (int do_lock)
{
int result = 0;
FILE *fp;
// ...
for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
{
run_fp = fp;
if (do_lock)
_IO_flockfile (fp);
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
)
&& _IO_OVERFLOW (fp, EOF) == EOF)
result = EOF;
//...
}
// ...
return result;
}
_IO_flush_all_lockp 的触发方式:
- 当执行 exit 函数时
- 当执行流从 main 函数返回时
- 当 libc 执行 abort 流程时 [glibc <= 2.23]
trace:
[#0] _IO_flush_all_lockp(do_lock=0x0)
[#1] _IO_cleanup()
[#2] __run_exit_handlers(...)
[#3] __GI_exit(status=<optimized out>)
[#4] __libc_start_main(...)
[#5] _start()
glibc < 2.24
由于位于 libc
数据段的 vtable
是不可以进行写入的,所以我们只能伪造 vtable
伪造 vtable
劫持程序流程的中心思想就是针对 _IO_FILE_plus
的 vtable
动手脚,通过把 vtable
指向我们控制的内存,并在其中布置函数指针来实现。
2.24 <= glibc <= 2.27
_IO_OVERFLOW
中加入 IO_validate_vtable
对 vtable 做了检测,
vtable
必须要满足在 __stop___IO_vtables
和 __start___libc_IO_vtables
之间,而我们伪造的vtable通常不满足这个条件。
但是我们找到一条出路,那就是 _IO_str_jumps
与 _IO_wstr_jumps
就位于二者之间,
所以我们是可以利用他们来通过 IO_validate_vtable
的检测的,只需要将 vtable
填成_IO_str_jumps
或 _IO_wstr_jumps
就行。
下面列出几种可行方法,但不限于这几种:
2.28 <= glibc
由于指针函数被替换为库函数:
_IO_str_finish
中 (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base)
被替换为 free (fp->_IO_buf_base)
_IO_str_overflow
中 (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size)
被替换为 malloc (new_size)
因此,上述两种方法不能再可行,但可以利用 _IO_str_overflow
中的 malloc
任意大小和 free
任意地址的 chunk。
Problem:
Ref:
利用 IO_write_base
实现 leak
修改 libc.sym["_IO_2_1_stdout_"] - 0x43
处的 chunk
为
payload = b''.join([
b'\0' * 0x33,
p64(0xfbad1887), # _flags
p64(0), # _IO_read_ptr
p64(0), # _IO_read_end
p64(0), # _IO_read_base
p64(libc.sym['__curbrk']), # _IO_write_base
p64(libc.sym['__curbrk'] + 8) # _IO_write_ptr
])
从而利用 _IO_new_file_xsputn
中的
/* Next flush the (full) buffer. */
if (_IO_OVERFLOW (f, EOF) == EOF)
// [#0] write
// [#1] _IO_new_file_write
// [#2] new_do_write
// [#3] _IO_new_do_write
// [#4] _IO_new_file_overflow
// [#5] _IO_new_file_xsputn
// [#6] _IO_puts
进行泄漏,泄漏后
count = new_do_write (f, s, do_write);
// [#0] write
// [#1] _IO_new_file_write
// [#2] new_do_write
// [#3] _IO_new_do_write
// [#4] _IO_new_file_xsputn
// [#5] _IO_puts
打印后续数据。
Refs