查看: 74|回复: 8

[Pwn] IO_FILE结构体大全

[复制链接]

4

主题

20

帖子

0

精华

初级会员

Rank: 4

学币
4
荣耀
0
rank
0
违规
0

    发表于 2021-7-7 10:59:57 | 显示全部楼层 |阅读模式

    相关题目:

    在此总结了一些关于IO_FILE的结构体,以便后查








    温馨提示:
    1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
    2.回复帖子不仅是对作者的最好奖励,还可以获得学币奖励,请尊重作者的劳动成果,拒绝做伸手党!
    3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。
    学逆向论坛-免费的逆向学习论坛

    4

    主题

    20

    帖子

    0

    精华

    初级会员

    Rank: 4

    学币
    4
    荣耀
    0
    rank
    0
    违规
    0

       楼主| 发表于 2021-7-7 11:03:03 | 显示全部楼层
      _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;       /* 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
      };


      学逆向论坛-免费的逆向学习论坛

      4

      主题

      20

      帖子

      0

      精华

      初级会员

      Rank: 4

      学币
      4
      荣耀
      0
      rank
      0
      违规
      0

         楼主| 发表于 2021-7-7 11:04:44 | 显示全部楼层
        每个文件流都有自己的 FILE 结构体。我们可以在 libc.so 中找到 stdin\stdout\stderr 等符号,这些符号是指向 FILE 结构的指针,真正结构的符号是
        _IO_2_1_stderr_
        _IO_2_1_stdout_
        _IO_2_1_stdin_
        在 ida 中搜索 _IO_2_1_stdxxx_ 或者 stdxx 这个变量会存储 FILE 结构体地址:


        学逆向论坛-免费的逆向学习论坛

        4

        主题

        20

        帖子

        0

        精华

        初级会员

        Rank: 4

        学币
        4
        荣耀
        0
        rank
        0
        违规
        0

           楼主| 发表于 2021-7-7 11:05:36 | 显示全部楼层
          gdb 调试中查看结构体内容:
          进程中的 FILE 结构会通过 _chain 域彼此连接形成一个链表(上图可见指向 _IO_2_1_stdout ),链表头部用全局变量 _IO_list_all 表示,通过这个值我们可以遍历所有的 FILE 结构(FSOP 攻击利用到这个特性)。

          _fileno 是当前文件流的文件描述符,上图是 stderr 对应就是 2 。


          学逆向论坛-免费的逆向学习论坛

          4

          主题

          20

          帖子

          0

          精华

          初级会员

          Rank: 4

          学币
          4
          荣耀
          0
          rank
          0
          违规
          0

             楼主| 发表于 2021-7-7 11:07:05 | 显示全部楼层
            _IO_FILE_plus 结构
            但是事实上 _IO_FILE 结构外包裹着另一种结构 _IO_FILE_plus ,其中包含了一个重要的指针 vtable 指向了一系列函数指针。

            在 libc2.23 版本下,32 位的 vtable 偏移为 0x94,64 位偏移为 0xd8
            struct _IO_FILE_plus
            {
                _IO_FILE    file;
                _IO_jump_t   *vtable;
            }
            _IO_FILE_plus 结构体 & 各个偏移,当中 0x0 ~ 0xc4 其实就是 _IO_FILE 结构,最后加上 vtable 指针指向 _IO_jump_t :

            //p *((struct _IO_FILE_plus*)[地址])
            0x0   _flags
            0x8   _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  _lock
            //IO_FILE_complete
            0x90  _offset
            0x98  _codecvt
            0xa0  _wide_data
            0xa8  _freeres_list
            0xb0  _freeres_buf
            0xb8  __pad5
            0xc0  _mode
            0xc4  _unused2
            0xd8  vtable


            学逆向论坛-免费的逆向学习论坛

            4

            主题

            20

            帖子

            0

            精华

            初级会员

            Rank: 4

            学币
            4
            荣耀
            0
            rank
            0
            违规
            0

               楼主| 发表于 2021-7-7 11:07:38 | 显示全部楼层
              _IO_jump_t 结构
              vtable 是 _IO_jump_t 类型的指针,指向的 _IO_jump_t 结构体中保存了一堆函数指针,这有点像 c++ 的虚函数结构体,在后面我们会看到在一系列标准 IO 函数中会调用这里面的函数指针。

              在 ida 中可以找 _IO_2_1_stderr_ 结构体后面的 dq offset _IO_file_jumps 跳转到结构体。或者直接搜索 _IO_file_jumps ,vtable 实际指向的结构体名字。

              //p *((struct _IO_jump_t*)[地址])
              void * funcs[] = {
                  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
              };


              学逆向论坛-免费的逆向学习论坛

              4

              主题

              20

              帖子

              0

              精华

              初级会员

              Rank: 4

              学币
              4
              荣耀
              0
              rank
              0
              违规
              0

                 楼主| 发表于 2021-7-7 11:09:23 | 显示全部楼层
                _IO_str_jumps
                libc 中不仅仅只有 _IO_file_jumps 一个 vtable ,还有一个叫 _IO_str_jumps 的 ,这个 vtable 不在 check 范围之内。

                比如 _IO_str_jumps(该符号在 strip 后会丢失):

                // libio/strops.c

                const struct _IO_jump_t _IO_str_jumps libio_vtable =
                {
                  JUMP_INIT_DUMMY,
                  JUMP_INIT(finish, _IO_str_finish),
                  JUMP_INIT(overflow, _IO_str_overflow),
                  JUMP_INIT(underflow, _IO_str_underflow),
                  JUMP_INIT(uflow, _IO_default_uflow),
                  JUMP_INIT(pbackfail, _IO_str_pbackfail),
                  JUMP_INIT(xsputn, _IO_default_xsputn),
                  JUMP_INIT(xsgetn, _IO_default_xsgetn),
                  JUMP_INIT(seekoff, _IO_str_seekoff),
                  JUMP_INIT(seekpos, _IO_default_seekpos),
                  JUMP_INIT(setbuf, _IO_default_setbuf),
                  JUMP_INIT(sync, _IO_default_sync),
                  JUMP_INIT(doallocate, _IO_default_doallocate),
                  JUMP_INIT(read, _IO_default_read),
                  JUMP_INIT(write, _IO_default_write),
                  JUMP_INIT(seek, _IO_default_seek),
                  JUMP_INIT(close, _IO_default_close),
                  JUMP_INIT(stat, _IO_default_stat),
                  JUMP_INIT(showmanyc, _IO_default_showmanyc),
                  JUMP_INIT(imbue, _IO_default_imbue)
                };

                // libio/libioP.h

                #define JUMP_INIT_DUMMY JUMP_INIT(dummy, 0), JUMP_INIT (dummy2, 0)
                _IO_str_jumps 中包含了一个叫做 _IO_str_overflow 的函数,该函数中存在相对地址的引用(可伪造):

                int
                _IO_str_overflow (_IO_FILE *fp, int c)
                {
                  int flush_only = c == EOF;
                  _IO_size_t pos;
                  if (fp->_flags & _IO_NO_WRITES)
                      return flush_only ? 0 : EOF;
                  if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
                    {
                      fp->_flags |= _IO_CURRENTLY_PUTTING;
                      fp->_IO_write_ptr = fp->_IO_read_ptr;
                      fp->_IO_read_ptr = fp->_IO_read_end;
                    }
                  pos = fp->_IO_write_ptr - fp->_IO_write_base;
                  if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))                       // 条件 #define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
                    {
                      if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
                    return EOF;
                      else
                    {
                      char *new_buf;
                      char *old_buf = fp->_IO_buf_base;
                      size_t old_blen = _IO_blen (fp);
                      _IO_size_t new_size = 2 * old_blen + 100;                                 // 通过计算 new_size 为 "/bin/sh\x00" 的地址
                      if (new_size < old_blen)
                        return EOF;
                      new_buf
                        = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);     // 在这个相对地址放上 system 的地址,即 system("/bin/sh")
                    [...]
                // libio/strfile.h

                struct _IO_str_fields
                {
                  _IO_alloc_type _allocate_buffer;
                  _IO_free_type _free_buffer;
                };

                struct _IO_streambuf
                {
                  struct _IO_FILE _f;
                  const struct _IO_jump_t *vtable;
                };

                typedef struct _IO_strfile_
                {
                  struct _IO_streambuf _sbf;
                  struct _IO_str_fields _s;
                } _IO_strfile;
                所以可以像下面这样构造:

                fp->_flags = 0
                fp->_IO_buf_base = 0
                fp->_IO_buf_end = (bin_sh_addr - 100) / 2
                fp->_IO_write_ptr = 0xffffffff
                fp->_IO_write_base = 0
                fp->_mode = 0
                有一点要注意的是,如果 bin_sh_addr 的地址以奇数结尾,为了避免除法向下取整的干扰,可以将该地址加 1。另外 system (“/bin/sh”) 是可以用 one_gadget 来代替的,这样似乎更加简单。

                完整的调用过程:malloc_printerr -> __libc_message -> __GI_abort -> _IO_flush_all_lockp -> __GI__IO_str_overflow。

                与传统的 house-of-orange 不同的是,这种利用方法不再需要知道 heap 的地址,因为 _IO_str_jumps vtable 是在 libc 上的,所以只要能泄露出 libc 的地址就可以了。


                学逆向论坛-免费的逆向学习论坛

                4

                主题

                20

                帖子

                0

                精华

                初级会员

                Rank: 4

                学币
                4
                荣耀
                0
                rank
                0
                违规
                0

                   楼主| 发表于 2021-7-7 11:10:11 | 显示全部楼层
                  在 _IO_str_jumps 中,还有另一个函数 _IO_str_finish,它的检查条件比较简单:
                  void
                  _IO_str_finish (_IO_FILE *fp, int dummy)
                  {
                    if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))             // 条件
                      (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);     // 在这个相对地址放上 system 的地址
                    fp->_IO_buf_base = NULL;

                    _IO_default_finish (fp, 0);
                  }
                  只要在 fp->_IO_buf_base 放上 “/bin/sh” 的地址,然后设置 fp->_flags = 0 就可以了绕过函数里的条件。

                  那么怎样让程序进入 _IO_str_finish 执行呢,fclose(fp) 是一条路,但似乎有局限。还是回到异常处理上来,在 _IO_flush_all_lockp 函数中是通过 _IO_OVERFLOW 执行的 __GI__IO_str_overflow,而 _IO_OVERFLOW 是根据 __overflow 相对于 _IO_str_jumps vtable 的偏移找到具体函数的。所以如果我们伪造传递给 _IO_OVERFLOW(fp) 的 fp 是 vtable 的地址减去 0x8,那么根据偏移,程序将找到 _IO_str_finish 并执行。

                  所以可以像下面这样构造:

                  fp->_mode = 0
                  fp->_IO_write_ptr = 0xffffffff
                  fp->_IO_write_base = 0
                  fp->_wide_data->_IO_buf_base = bin_sh_addr (也就是 fp->_IO_write_end)
                  fp->_flags2 = 0
                  fp->_mode = 0
                  完整的调用过程:malloc_printerr -> __libc_message -> __GI_abort -> _IO_flush_all_lockp -> __GI__IO_str_finish。

                  学逆向论坛-免费的逆向学习论坛

                  4

                  主题

                  20

                  帖子

                  0

                  精华

                  初级会员

                  Rank: 4

                  学币
                  4
                  荣耀
                  0
                  rank
                  0
                  违规
                  0

                     楼主| 发表于 2021-7-7 11:11:08 | 显示全部楼层
                    _IO_wstr_jumps 也是一个符合条件的 vtable,总体上和上面讲的 _IO_str_jumps 差不多:
                    // libio/wstrops.c

                    const struct _IO_jump_t _IO_wstr_jumps libio_vtable =
                    {
                      JUMP_INIT_DUMMY,
                      JUMP_INIT(finish, _IO_wstr_finish),
                      JUMP_INIT(overflow, (_IO_overflow_t) _IO_wstr_overflow),
                      JUMP_INIT(underflow, (_IO_underflow_t) _IO_wstr_underflow),
                      JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
                      JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wstr_pbackfail),
                      JUMP_INIT(xsputn, _IO_wdefault_xsputn),
                      JUMP_INIT(xsgetn, _IO_wdefault_xsgetn),
                      JUMP_INIT(seekoff, _IO_wstr_seekoff),
                      JUMP_INIT(seekpos, _IO_default_seekpos),
                      JUMP_INIT(setbuf, _IO_default_setbuf),
                      JUMP_INIT(sync, _IO_default_sync),
                      JUMP_INIT(doallocate, _IO_wdefault_doallocate),
                      JUMP_INIT(read, _IO_default_read),
                      JUMP_INIT(write, _IO_default_write),
                      JUMP_INIT(seek, _IO_default_seek),
                      JUMP_INIT(close, _IO_default_close),
                      JUMP_INIT(stat, _IO_default_stat),
                      JUMP_INIT(showmanyc, _IO_default_showmanyc),
                      JUMP_INIT(imbue, _IO_default_imbue)
                    };


                    利用函数 _IO_wstr_overflow:
                    _IO_wint_t
                    _IO_wstr_overflow (_IO_FILE *fp, _IO_wint_t c)
                    {
                      int flush_only = c == WEOF;
                      _IO_size_t pos;
                      if (fp->_flags & _IO_NO_WRITES)
                          return flush_only ? 0 : WEOF;
                      if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
                        {
                          fp->_flags |= _IO_CURRENTLY_PUTTING;
                          fp->_wide_data->_IO_write_ptr = fp->_wide_data->_IO_read_ptr;
                          fp->_wide_data->_IO_read_ptr = fp->_wide_data->_IO_read_end;
                        }
                      pos = fp->_wide_data->_IO_write_ptr - fp->_wide_data->_IO_write_base;
                      if (pos >= (_IO_size_t) (_IO_wblen (fp) + flush_only))    // 条件 #define _IO_wblen(fp) ((fp)->_wide_data->_IO_buf_end - (fp)->_wide_data->_IO_buf_base)
                        {
                          if (fp->_flags2 & _IO_FLAGS2_USER_WBUF) /* not allowed to enlarge */
                        return WEOF;
                          else
                        {
                          wchar_t *new_buf;
                          wchar_t *old_buf = fp->_wide_data->_IO_buf_base;
                          size_t old_wblen = _IO_wblen (fp);
                          _IO_size_t new_size = 2 * old_wblen + 100;              // 使 new_size * sizeof(wchar_t) 为 "/bin/sh" 的地址

                          if (__glibc_unlikely (new_size < old_wblen)
                              || __glibc_unlikely (new_size > SIZE_MAX / sizeof (wchar_t)))
                            return EOF;

                          new_buf
                            = (wchar_t *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size
                                                        * sizeof (wchar_t));                      // 在这个相对地址放上 system 的地址
                        [...]


                    利用函数 _IO_wstr_finish:

                    void
                    _IO_wstr_finish (_IO_FILE *fp, int dummy)
                    {
                      if (fp->_wide_data->_IO_buf_base && !(fp->_flags2 & _IO_FLAGS2_USER_WBUF))    // 条件
                        (((_IO_strfile *) fp)->_s._free_buffer) (fp->_wide_data->_IO_buf_base);     // 在这个相对地址放上 system 的地址
                      fp->_wide_data->_IO_buf_base = NULL;

                      _IO_wdefault_finish (fp, 0);
                    }


                    学逆向论坛-免费的逆向学习论坛
                    关闭

                    论坛公告上一条 /1 下一条

                    快速回复 返回顶部 返回列表