# 简介
IO_FILE 是描述 IO 的文件结构体,相关源码来自 libio/libioP.h 文件中。
IO_FILE 结构:在执行 fopen 等函数时创建。不同 IO_FILE 以链表形式串接起来。
# _IO_list_all
变量
_IO_list_all
变量:指向链表头部。默认链如下。
_IO_list_all - stderr -> stdout -> stdin |
# 三文件流
存在以下三种符号,指向他们对应的 file 结构。
其存放在 libc.so 中。
_IO_2_1_stderr_
_IO_2_1_stdout_
_IO_2_1_stdin_
# io file plus
包裹着 file 结构体。
vtable 的偏移
架构 | 偏移 |
---|---|
32 | 0x94 |
64 | 0xd8 |
struct _IO_FILE_plus
{
_IO_FILE file;
_IO_jump_t *vtable;
}
# file 结构
不同 file 结构体用 chain 域串起形成链表。
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;
#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
/* Wide character stream stuff. */
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;
# endif
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
};
# _IO_jump_t
是 vtable 指针指向的结构体,存放着各种函数跳转地址。
其中偏移为 7 的 xsputn 很有用。
libc.so 自带的 _IO_jump_t
一般不可修改。但我们可以伪造 _IO_jump_t
并修改 vtable 的值指向伪造的结构体。
#define JUMP_FIELD(TYPE, NAME) TYPE NAME
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); // printf函数会用
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_2_1_stdout_
中 vtable 的 setbuf 在程序退出时会执行,可修改为 one_gadget 进行利用。
# 输出输入函数调用栈
# fread
函数位于 /libio/iofread.c
size_t fread (void *buffer, size_t size, size_t count, FILE *stream);
- buffer 存放读取数据的缓冲区。
- size:指定每个记录的长度。
- count: 指定记录的个数。
- stream:目标文件流。
- 返回值:返回读取到数据缓冲区中的记录个数
其调用函数栈为
_IO_fread(buf, size, count, fp) // fread本体
_IO_sgetn(fp, (char*)buf, bytes_requested) //(fp,data,n)
_IO_XSGETN (fp, data, n) // 为vtable中的函数指针
_IO_file_xsgetn(fp, data, n) // 默认情况下_IO_XSGETN所指向
# fwrite
函数位于 /libio/iofwrite.c
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
- buffer: 是一个指针,对 fwrite 来说,是要写入数据的地址;
- size: 要写入内容的单字节数;
- count: 要进行写入 size 字节的数据项的个数;
- stream: 目标文件指针;
- 返回值:实际写入的数据项个数 count。
_IO_fwrite
_IO_XSPUTN // vtable 函数指针
||
_IO_new_file_xsputn
_IO_OVERFLOW // vtable 函数指针
||
_IO_new_file_overflow
write
# printf
vfprintf+11
_IO_file_xsputn
_IO_file_overflow
funlockfile
_IO_file_write
write
# file 结构创建
# fopen
fopen 在标准 IO 库中用于打开文件,函数原型如下
FILE *fopen(char *filename, *type);
- filename: 目标文件的路径
- type: 打开方式的类型
- 返回值:返回一个文件指针
在 fopen 内部会创建 FILE 结构并进行一些初始化操作,下面来看一下这个过程
首先在 fopen 对应的函数 __fopen_internal
内部会调用 malloc 函数,分配 FILE 结构的空间。因此我们可以获知 FILE 结构是存储在堆上的
*new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE));
之后会为创建的 FILE 初始化 vtable,并调用_IO_file_init 进一步初始化操作
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
_IO_file_init (&new_f->fp);
在 _IO_file_init
函数的初始化操作中,会调用 _IO_link_in
把新分配的 FILE 链入 _IO_list_all
为起始的 FILE 链表中
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 函数,这里不再深入。
if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL)
return __fopen_maybe_mmap (&new_f->fp.file);
总结一下 fopen 的操作是
- 使用 malloc 分配 FILE 结构
- 设置 FILE 结构的 vtable
- 初始化分配的 FILE 结构
- 将初始化的 FILE 结构链入 FILE 结构链表中
- 调用系统调用打开文件