吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3362|回复: 12
收起左侧

[CTF] [PWN] Linux IO_FILE Exploit

  [复制链接]
zhouzq709 发表于 2022-11-12 14:21
本帖最后由 zhouzq709 于 2022-11-12 14:22 编辑

IO_FILE Exploit

Structrue

_IO_FILE:

struct _IO_FILE
{
  int _flags;                /* High-order word is _IO_MAGIC; rest is flags. */

  /* The following pointers correspond to the C++ streambuf protocol. */
  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;
  int _flags2;
  __off_t _old_offset; /* This used to be _offset but it's too small.  */

  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

_IO_FILE_plus:

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

_IO_jump_t:

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

下面简单说说一些c函数对_IO_jump_t虚表里面函数的调用情况

  • printf/puts 最终会调用 _IO_file_xsputn
  • fclose      最终会调用 _IO_file_finish
  • fwrite      最终会调用 _IO_file_xsputn
  • fread       最终会调用 _IO_fiel_xsgetn
  • scanf/gets  最终会调用 _IO_file_xsgetn

_IO_strfile:

struct _IO_str_fields
{
  /* These members are preserved for ABI compatibility.  The glibc
     implementation always calls malloc/free for user buffers if
     _IO_USER_BUF or _IO_FLAGS2_USER_WBUF are not set.  */
  _IO_alloc_type _allocate_buffer_unused;
  _IO_free_type _free_buffer_unused;
};

/* This is needed for the Irix6 N32 ABI, which has a 64 bit off_t type,
   but a 32 bit pointer type.  In this case, we get 4 bytes of padding
   after the vtable pointer.  Putting them in a structure together solves
   this problem.  */

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

typedef struct _IO_strfile_
{
  struct _IO_streambuf _sbf;
  struct _IO_str_fields _s;
} _IO_strfile;
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */
#define _IO_UNBUFFERED 2
#define _IO_NO_READS 4 /* Reading not allowed */
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_EOF_SEEN 0x10
#define _IO_ERR_SEEN 0x20
#define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */
#define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/
#define _IO_IN_BACKUP 0x100
#define _IO_LINE_BUF 0x200
#define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */
#define _IO_CURRENTLY_PUTTING 0x800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
#define _IO_BAD_SEEN 0x4000
#define _IO_USER_LOCK 0x8000

FSOP

FSOP的核心思想就是劫持 _IO_list_all 的值来伪造链表和其中的 _IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。
FSOP选择的触发方法是调用 _IO_flush_all_lockp
这个函数会刷新 _IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用 _IO_FILE_plus.vtable 中的 _IO_overflow

_IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  FILE *fp;
  // ...
  for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
    {
      run_fp = fp;
      if (do_lock)
        _IO_flockfile (fp);

      if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
           || (_IO_vtable_offset (fp) == 0
               && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
                                    > fp->_wide_data->_IO_write_base))
           )
          && _IO_OVERFLOW (fp, EOF) == EOF)
        result = EOF;
      //...
    }
  // ...
  return result;
}

_IO_flush_all_lockp 的触发方式:

  1. 当执行 exit 函数时
  2. 当执行流从 main 函数返回时
  3. 当 libc 执行 abort 流程时 [glibc <= 2.23]

trace:

[#0] _IO_flush_all_lockp(do_lock=0x0)
[#1] _IO_cleanup()
[#2] __run_exit_handlers(...)
[#3] __GI_exit(status=<optimized out>)
[#4] __libc_start_main(...)
[#5] _start()

glibc < 2.24

由于位于 libc 数据段的 vtable 是不可以进行写入的,所以我们只能伪造 vtable

伪造 vtable 劫持程序流程的中心思想就是针对 _IO_FILE_plusvtable 动手脚,通过把 vtable 指向我们控制的内存,并在其中布置函数指针来实现。

2.24 <= glibc <= 2.27

_IO_OVERFLOW 中加入 IO_validate_vtable 对 vtable 做了检测,
vtable 必须要满足在 __stop___IO_vtables__start___libc_IO_vtables 之间,而我们伪造的vtable通常不满足这个条件。

但是我们找到一条出路,那就是 _IO_str_jumps_IO_wstr_jumps 就位于二者之间,
所以我们是可以利用他们来通过 IO_validate_vtable 的检测的,只需要将 vtable 填成_IO_str_jumps_IO_wstr_jumps 就行。

下面列出几种可行方法,但不限于这几种:

  • 利用 _IO_str_jumps 中的 _IO_str_finish 函数

    1. 为绕过 _IO_flush_all_lockp 中的检查进入到 _IO_OVERFLOW,需构造
      • _mode <= 0
      • _IO_write_ptr > _IO_write_base
    2. 构造 vtable = _IO_str_jumps - 0x8, 使 _IO_str_finish 函数替代 _IO_OVERFLOW 函数,(因为 _IO_str_finish_IO_str_jumps 中的偏移为 0x10,而_IO_OVERFLOW 在原 vtable 中的偏移为 0x18
    3. 构造 fp -> _IO_buf_base 作为参数
    4. 构造 fp->flags & _IO_USER_BUF == 0
    5. 构造 fp->_s._free_buffer = &system (_free_buffer = fp->vtable + 0x10)
    6. 调用 _IO_flush_all_lockp 函数,触发 _IO_str_finish,从而达到调用
      (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base)
  • 利用 _IO_str_jumps 中的 _IO_str_overflow 函数

    1. 为绕过 _IO_flush_all_lockp 中的检查进入到 _IO_OVERFLOW,需构造
      • _mode <= 0
      • _IO_write_ptr > _IO_write_base
    2. 构造 vtable = _IO_str_jumps,使 _IO_str_overflow 函数替代 _IO_OVERFLOW 函数,(因为 _IO_str_finish_IO_str_jumps 中的偏移为 0x18,而 _IO_OVERFLOW 在原vtable 中的偏移为 0x18
    3. 构造 fp->flags & _IO_USER_BUF == 0 && fp->flags & _IO_NO_WRITES == 0
    4. 构造 new_size = (fp->_IO_buf_end - fp->_IO_buf_base) * 2 + 100 = binsh_addr
    5. 构造 fp->_s._allocate_buffer = &system (fp->_s._allocate_buffer = fp->vtable + 0x8)
    6. 调用 _IO_flush_all_lockp 函数,触发 _IO_str_finish,从而达到调用
      (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size)

2.28 <= glibc

由于指针函数被替换为库函数:

  • _IO_str_finish(((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base) 被替换为 free (fp->_IO_buf_base)
  • _IO_str_overflow(*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size) 被替换为 malloc (new_size)

因此,上述两种方法不能再可行,但可以利用 _IO_str_overflow 中的 malloc 任意大小和 free 任意地址的 chunk。

Problem:

Ref:

利用 IO_write_base 实现 leak

修改 libc.sym["_IO_2_1_stdout_"] - 0x43 处的 chunk

payload = b''.join([
    b'\0' * 0x33,
    p64(0xfbad1887),                # _flags
    p64(0),                         # _IO_read_ptr
    p64(0),                         # _IO_read_end
    p64(0),                         # _IO_read_base
    p64(libc.sym['__curbrk']),      # _IO_write_base
    p64(libc.sym['__curbrk'] + 8)   # _IO_write_ptr
])

从而利用 _IO_new_file_xsputn 中的

/* Next flush the (full) buffer. */
if (_IO_OVERFLOW (f, EOF) == EOF)
// [#0] write
// [#1] _IO_new_file_write
// [#2] new_do_write
// [#3] _IO_new_do_write
// [#4] _IO_new_file_overflow
// [#5] _IO_new_file_xsputn
// [#6] _IO_puts

进行泄漏,泄漏后

count = new_do_write (f, s, do_write);
// [#0] write
// [#1] _IO_new_file_write
// [#2] new_do_write
// [#3] _IO_new_do_write
// [#4] _IO_new_file_xsputn
// [#5] _IO_puts

打印后续数据。

Refs

免费评分

参与人数 5威望 +2 吾爱币 +103 热心值 +4 收起 理由
78zhanghao87 + 1 谢谢@Thanks!
obitosec + 1 我很赞同!
Hmily + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
liesun + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
peiwithhao + 2 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

CuteCabbage 发表于 2022-11-13 09:38
最近正好在学IOFILE,写的很详细,收藏了!
hjw01 发表于 2022-11-13 19:36
yangyangliu 发表于 2022-11-16 19:18
cycy西柚 发表于 2022-11-18 09:44
FSOP的核心思想就是劫持
GNNU 发表于 2022-11-18 22:44
感谢楼主分享
my1889 发表于 2022-11-21 19:56

谢谢分享
CuteCabbage 发表于 2022-11-26 09:53
最近看到的写的最详细且容易理解的IOFILE
CuteCabbage 发表于 2022-11-28 12:55
再次前来学习
rinima 发表于 2022-12-2 12:09
感谢楼主分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 16:18

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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