# 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

更新于 阅读次数