# IO_FILE(1)
前言:为了好好学习 house-of-orange 的利用,先在 house-of-orange 专题之前写一些 IO_FILE 的利用
# _IO_FILE 结构
FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中。我们常定义一个指向 FILE 结构的指针来接收这个返回值 —— 文件描述符(eg:stdin=0;stdout=1)。
在标准 I/O 库中,每个程序启动时有三个文件流是自动打开的:stdin、stdout、stderr,分别对应文件描述符:0、1、2。假设现在第一次用 fopen 打开一个文件流,这个文件流的文件描述符就为 3 。默认打开的三个文件流分配 libc data 段。fopen 等文件流控制函数创建的文件流是分配在堆上。
FILE 结构体定义在 libio.h :
struct _IO_FILE { | |
int _flags; /* 高位字为_IO_MAGIC;剩下的就是旗帜。 */ | |
#define _IO_file_flags _flags | |
/* 以下指针对应于 C++ Streambuf 协议。 */ | |
/* 注意:Tk 直接使用 _IO_read_ptr 和 _IO_read_end 字段。 */ | |
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; | |
#ifdef _IO_USE_OLD_IO_FILE | |
}; |
其实进程中的 FILE 结构会通过 _chain
域彼此连接形成一个链表,链表头部用全局变量 _IO_list_all
表示,通过这个值我们可以遍历所有的 FILE 结构。
每个文件流都有自己的 FILE 结构体。我们可以在 libc.so
中找到 stdin\stdout\stderr
等符号,这些符号是指向 FILE 结构的指针,真正结构的符号是
_IO_2_1_stderr_
_IO_2_1_stdout_
_IO_2_1_stdin_
但是 file 结构其实只是一小部分,还有一个叫 vtable
指针,同属于 _IO_File_plus:
struct _IO_FILE_plus | |
{ | |
_IO_FILE file; | |
IO_jump_t *vtable; | |
} | |
//32 位下的偏移是 0x94,而 64 位下偏移是 0xd8 |
gdb
pwndbg> p *(struct _IO_FILE_plus *) 0x7ffff7bc5620 $11 = { file = { _flags = -72537977, _IO_read_ptr = 0x7ffff7bc56a3 <_IO_2_1_stdout_+131> "\n", _IO_read_end = 0x7ffff7bc56a3 <_IO_2_1_stdout_+131> "\n", _IO_read_base = 0x7ffff7bc56a3 <_IO_2_1_stdout_+131> "\n", _IO_write_base = 0x7ffff7bc56a3 <_IO_2_1_stdout_+131> "\n", _IO_write_ptr = 0x7ffff7bc56a3 <_IO_2_1_stdout_+131> "\n", _IO_write_end = 0x7ffff7bc56a3 <_IO_2_1_stdout_+131> "\n", _IO_buf_base = 0x7ffff7bc56a3 <_IO_2_1_stdout_+131> "\n", _IO_buf_end = 0x7ffff7bc56a4 <_IO_2_1_stdout_+132> "", _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7ffff7bc48e0 <_IO_2_1_stdin_>, _fileno = 1, _flags2 = 0, _old_offset = -1, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "\n", _lock = 0x7ffff7bc6780 <_IO_stdfile_1_lock>, _offset = -1, _codecvt = 0x0, _wide_data = 0x7ffff7bc47a0 <_IO_wide_data_1>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = -1, _unused2 = '\000' <repeats 19 times> }, vtable = 0x7ffff7bc36e0 <_IO_file_jumps>}
Vtable 存着一些跳转的函数指针
void * funcs[] = { | |
1 NULL, // "extra word" | |
2 NULL, // DUMMY | |
3 exit, // finish | |
4 NULL, // overflow | |
5 NULL, // underflow | |
6 NULL, // uflow | |
7 NULL, // pbackfail | |
8 NULL, // xsputn #printf | |
9 NULL, // xsgetn | |
10 NULL, // seekoff | |
11 NULL, // seekpos | |
12 NULL, // setbuf | |
13 NULL, // sync | |
14 NULL, // doallocate | |
15 NULL, // read | |
16 NULL, // write | |
17 NULL, // seek | |
18 pwn, // close | |
19 NULL, // stat | |
20 NULL, // showmanyc | |
21 NULL, // imbue | |
}; |
pwndbg> p _IO_file_jumps | |
$12 = { | |
__dummy = 0, | |
__dummy2 = 0, | |
__finish = 0x7ffff78799d0 <_IO_new_file_finish>, | |
__overflow = 0x7ffff787a740 <_IO_new_file_overflow>, | |
__underflow = 0x7ffff787a4b0 <_IO_new_file_underflow>, | |
__uflow = 0x7ffff787b610 <__GI__IO_default_uflow>, | |
__pbackfail = 0x7ffff787c990 <__GI__IO_default_pbackfail>, | |
__xsputn = 0x7ffff78791f0 <_IO_new_file_xsputn>, | |
__xsgetn = 0x7ffff7878ed0 <__GI__IO_file_xsgetn>, | |
__seekoff = 0x7ffff78784d0 <_IO_new_file_seekoff>, | |
__seekpos = 0x7ffff787ba10 <_IO_default_seekpos>, | |
__setbuf = 0x7ffff7878440 <_IO_new_file_setbuf>, | |
__sync = 0x7ffff7878380 <_IO_new_file_sync>, | |
__doallocate = 0x7ffff786d190 <__GI__IO_file_doallocate>, | |
__read = 0x7ffff78791b0 <__GI__IO_file_read>, | |
__write = 0x7ffff7878b80 <_IO_new_file_write>, | |
__seek = 0x7ffff7878980 <__GI__IO_file_seek>, | |
__close = 0x7ffff7878350 <__GI__IO_file_close>, | |
__stat = 0x7ffff7878b70 <__GI__IO_file_stat>, | |
__showmanyc = 0x7ffff787cb00 <_IO_default_showmanyc>, | |
__imbue = 0x7ffff787cb10 <_IO_default_imbue> | |
} | |
> | |
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); | |
#if 0 | |
get_column; | |
set_column; | |
#endif | |
}; |
_IO_FILE_plus 结构中存在 vtable,一些函数会取出 vtable 中的指针进行调用。
因此伪造 vtable 劫持控制流程的思想就是针对_IO_File_plus 的 vtable 动手脚,通过把 vtable 指向我们控制的内存,并在其中部署函数指针来实现
所以 vtable 劫持分为 2 种,一种是直接改写 vtable 的函数的指针,通过任意地址写就可以实现。另一种是覆盖 vtable 的指针为我们控制的内存,然后在其中布置函数指针。
# 小结
-
stdin、stdout、stderr 文件流位于
libc.so
的数据段。而我们使用 fopen 创建的文件流是分配在堆内存上 -
stdin、stdout、stderr,分别对应文件描述符:0、1、2,开启新的文件流文件描述符从 3 开始递增
-
每个文件流都单独的
_IO_FILE
、_IO_FILE_plus
结构体,_IO_jump_t *vtable
只有一个各个文件流公用 -
指针
vtable
指向了一系列函数指针,各种 IO 操作均是通过vtable
指向各个具体函数实现功能 -
文件流通过
_chain
构成链表,链表头部用全局变量 _IO_list_all 表示 -
ida 中通过搜索文件流名可以找到
_IO_FILE
、_IO_FILE_plus
,根据偏移(结构体最后位置)找到vtable
(eg:IO_2_1_stderr)
先暂时了解这么多…
参考
https://github.com/skyedai910/wiki.mrskye.cn/blob/master/docs/Pwn/IO_FILE/Pwn_IO_FILE.md
https://xz.aliyun.com/t/6468?time__1311=n4%2BxnD0Dg70%3DG%3DDOz3DsA3EPoYe7KG8Q%2FQzYReD
https://www.cnblogs.com/hawkJW/p/13546416.html
https://xz.aliyun.com/t/6567?time__1311=n4%2BxnD0Dg7G%3DBhDBqooGkDRiG5i%3DdYDB7oD