前言:
感觉自己从上学期看IO_FILE
到现在以及很多很多遍了。。但是好像过两天就忘的差不多了的感觉。现在来写写看看我会不会忘了。往上看到一篇比较全的,借鉴一下。
正文:
总的那些名词符号的指向我梳理了一下,具体是这样的:
_IO_FILE_plus
结构体:
1 | struct _IO_FILE_plus |
在 libc2.23 版本下,32 位的 vtable 偏移为 0x94,64 位偏移为 0xd8。
所有的文件流通过链表连接,全局变量_IO_list_all
指向链表头部:
1 | // glibc/libio/stdfiles.c |
在程序启动时会创建三个文件流stdin
、stdout
和stderr
:
1 | // glibc/libio/libio.h |
并且这三个文件流位于libc的数据段,而使用fopen
创建的文件流位于堆中。文件流的结构体定义如下:
1 | // glibc/libio/libioP.h |
_IO_FILE
结构体包含了文件的所有属性:
1 | // glibc/libio/libio.h |
各个小部分的作用:
偏移 | 属性 | 作用 |
---|---|---|
0x00 | _flags | 高四位为魔数0xfbad0000,低四位为标志符 |
0x08 | _IO_read_ptr | 输入流指向的缓冲区 |
0x10 | _IO_read_end | 输入流缓冲区结束 |
0x18 | _IO_read_base | |
0x20 | _IO_write_base | |
0x28 | _IO_write_ptr | 输出流指向的缓冲区 |
0x30 | _IO_write_end | 输出流缓冲区结束 |
0x38 | _IO_buf_base | 保护区起始 |
0x40 | _IO_buf_end | 保护区结束 |
0x48 | _IO_save_base | |
0x50 | _IO_backup_base | |
0x58 | _IO_save_end | |
0x60 | _markers | |
0x68 | _chain | 指向下一个文件流 |
0x70 | _fileno | 文件描述符 |
0x74 | _flags2 | 标志符 |
0x78 | _old_offset | |
0x80 | _cur_column | |
0x82 | _vtable_offset | |
0x83 | _shortbuf | |
0x88 | _IO_stdfile_1_lock | 锁结构体 |
0x90 | _offset | 文件描述符的偏移 |
0x98 | _codecvt | |
0xa0 | _IO_wide_data_1 | 宽字节流 |
0xa8 | _freeres_list | |
0xb0 | _freeres_buf | |
0xb8 | __pad5 | |
0xc0 | _mode | 标记是否为宽字节 |
0xc4 | _unused2 |
_IO_jump_t
结构体包含了一些函数指针:
1 | // glibc/libio/libioP.h |
其作用如下:
偏移 | hook | 函数 | 作用 |
---|---|---|---|
0x00 | dummy | ||
0x08 | dummy2 | ||
0x10 | finish | 清理_IO_FILE对象 | |
0x18 | overflow | 刷新缓冲区 | |
0x20 | underflow | 返回get缓冲区的下一个字节 | |
0x28 | uflow | 返回输入流的下一个字节 | |
0x30 | pbackfail | 处理备份操作 | |
0x38 | xsputn | 向缓冲区写N个字符 | |
0x40 | xsgetn | 从缓冲区读N个字符 | |
0x48 | seekoff | 将流位置移动到新位置 | |
0x50 | seekpos | 将流位置移动到新的绝对位置 | |
0x58 | setbuf | 为文件开辟缓冲区 | |
0x60 | sync | 将文件内部数据结构与外部状态同步 | |
0x68 | doallocate | 告诉文件分配缓冲区 | |
0x70 | sysread | 读数据 | |
0x78 | syswrite | 写数据 | |
0x80 | sysseek | ||
0x88 | sysclose | 结束文件 | |
0x90 | sysstat | ||
0x98 | showmany | ||
0xa0 | imbue |
标志符:
_flag
:
宏 | 值 | 描述 |
---|---|---|
_IO_MAGIC | 0xFBAD0000 | 魔数 |
_OLD_STDIO_MAGIC | 0xFABC0000 | 兼容旧版魔数 |
_IO_MAGIC_MASK | 0xFFFF0000 | |
_IO_USER_BUF | 1 | / User owns buffer; don’t delete it on close. / |
_IO_UNBUFFERED | 2 | |
_IO_NO_READS | 4 | 不可读 |
_IO_NO_WRITES | 8 | 不可写 |
_IO_EOF_SEEN | 0x10 | |
_IO_ERR_SEEN | 0x20 | |
_IO_DELETE_DONT_CLOSE | 0x40 | / Don’t call close(_fileno) on cleanup. / |
_IO_LINKED | 0x80 | 链表连接标识符 |
_IO_IN_BACKUP | 0x100 | |
_IO_LINE_BUF | 0x200 | |
_IO_TIED_PUT_GET | 0x400 | put与get逻辑绑定 |
_IO_CURRENTLY_PUTTING | 0x800 | |
_IO_IS_APPENDING | 0x1000 | 附加模式 |
_IO_IS_FILEBUF | 0x2000 | 文件流标志符 |
_IO_BAD_SEEN | 0x4000 | |
_IO_USER_LOCK | 0x8000 |
_flag2
:
宏 | 值 | 描述 |
---|---|---|
_IO_FLAGS2_MMAP | 1 | 映射区域 |
_IO_FLAGS2_NOTCANCEL | 2 | |
_IO_FLAGS2_FORTIFY | 4 | |
_IO_FLAGS2_USER_WBUF | 8 | |
_IO_FLAGS2_SCANF_STD | 16 | |
_IO_FLAGS2_NOCLOSE | 32 | |
_IO_FLAGS2_CLOEXEC | 64 | |
_IO_FLAGS2_NEED_LOCK | 128 |
I/O函数:
fopen:
原型:
1 | FILE *fopen(char *filename, *type); |
在 fopen 内部会创建 FILE 结构并进行一些初始化操作。
首先在 fopen 对应的函数__fopen_internal 内部会调用 malloc 函数,分配 FILE 结构的空间。因此我们可以获知 FILE 结构是存储在堆上的:
1 | *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE)); |
之后会为创建的 FILE 初始化 vtable,并调用_IO_file_init 进一步初始化操作:
1 | _IO_JUMPS (&new_f->fp) = &_IO_file_jumps; |
在_IO_file_init 函数的初始化操作中,会调用_IO_link_in 把新分配的 FILE 链入_IO_list_all 为起始的 FILE 链表中:
1 | void |
之后__fopen_internal 函数会调用_IO_file_fopen 函数打开目标文件,_IO_file_fopen 会根据用户传入的打开模式进行打开操作,总之最后会调用到系统接口 open 函数。
1 | if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL) |
总结一下 fopen 的操作是
- 使用 malloc 分配 FILE 结构
- 设置 FILE 结构的 vtable
- 初始化分配的 FILE 结构
- 将初始化的 FILE 结构链入 FILE 结构链表中
- 调用系统调用打开文件
fclose…
参考链接:
http://abcdefghijklmnopqrst.xyz/2018/11/14/Binary_IO_FILE源码分析
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/io_file/introduction/