盒子
盒子
文章目录
  1. 前言:
  2. 正文:
    1. 标志符:
    2. I/O函数:
      1. fopen:
      2. fclose…
    3. 参考链接:

IO_FILE

前言:

感觉自己从上学期看IO_FILE到现在以及很多很多遍了。。但是好像过两天就忘的差不多了的感觉。现在来写写看看我会不会忘了。往上看到一篇比较全的,借鉴一下。

正文:

总的那些名词符号的指向我梳理了一下,具体是这样的:

736552A3-28B6-46FC-B6B1-09330D383D62

_IO_FILE_plus结构体:

1
2
3
4
5
struct _IO_FILE_plus
{
_IO_FILE file;
IO_jump_t *vtable;
}

在 libc2.23 版本下,32 位的 vtable 偏移为 0x94,64 位偏移为 0xd8。

所有的文件流通过链表连接,全局变量_IO_list_all指向链表头部:

1
2
3
// glibc/libio/stdfiles.c

struct _IO_FILE_plus *_IO_list_all = &_IO_2_1_stderr_;

在程序启动时会创建三个文件流stdinstdoutstderr

1
2
3
4
5
// glibc/libio/libio.h

extern struct _IO_FILE_plus _IO_2_1_stdin_;
extern struct _IO_FILE_plus _IO_2_1_stdout_;
extern struct _IO_FILE_plus _IO_2_1_stderr_;

并且这三个文件流位于libc的数据段,而使用fopen创建的文件流位于堆中。文件流的结构体定义如下:

1
2
3
4
5
6
7
// glibc/libio/libioP.h

struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};

_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
37
38
39
40
41
42
43
44
// glibc/libio/libio.h

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
};

各个小部分的作用:

偏移 属性 作用
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
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
// glibc/libio/libioP.h

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
};

其作用如下:

偏移 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
2
_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 链表中:

1
2
3
4
5
6
7
8
9
10
11
12
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 函数。

1
2
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 结构链表中
  • 调用系统调用打开文件

fclose…

参考链接:

http://abcdefghijklmnopqrst.xyz/2018/11/14/Binary_IO_FILE源码分析
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/io_file/introduction/

支持一下
扫一扫,支持v1nke
  • 微信扫一扫
  • 支付宝扫一扫