IO攻击总结

IO攻击总结

_IO_FILE结构体相关

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

struct _IO_FILE_complete
{
  struct _IO_FILE _file;
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
  _IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
# else
  void *__pad1;
  void *__pad2;
  void *__pad3;
  void *__pad4;
# endif
  size_t __pad5;
  int _mode;
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif
};

对于_IO_FILE_plus结构体来说,就是上述结构体的基础上封装了一个跳转表

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

其中值得关注的部分如下:

 int _flags;        /* High-order word is _IO_MAGIC; rest is flags. */
  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. */
  int _fileno;  //文件描述符
  int _mode; //文件操作模式
  struct _IO_FILE *_chain; //指向下一个FILE结构体
  struct _IO_jump_t *vtable  //函数跳转表,这个很重要

vtable是指向_IO_jump_t结构体类型的指针,_IO_jumpt_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);
};

这里面的函数都是在IO操作中可能用到的,也是我们进行劫持的主要地方。
其它的赋值相关具体细节可以看fopen系列源码。

IO_FILE 攻击要点

IO调用的vtable 函数

fopen函数是在分配空间,建立FILE结构体,未调用vtable中的函数。

fread函数中调用的vtable函数有:

• _IO_sgetn函数调用了vtable的_IO_file_xsgetn。

• _IO_doallocbuf函数调用了vtable的_IO_file_doallocate以初始化输入缓冲区。

•vtable中的_IO_file_doallocate调用了vtable中的GIIO_file_stat以获取文件信息。_

_•__underflow函数调用了vtable中的_IO_new_file_underflow实现文件数据读取。

• vtable中的_IO_new_file_underflow调用了vtable__GI__IO_file_read最终去执行系统调用read。

fwrite 函数调用的vtable函数有:

• _IO_fwrite函数调用了vtable的_IO_new_file_xsputn。

•_IO_new_file_xsputn函数调用了vtable中的_IO_new_file_overflow实现缓冲区的建立以及刷新缓冲区。

•vtable中的_IO_new_file_overflow函数调用了vtable的_IO_file_doallocate以初始化输入缓冲区。

•vtable中的_IO_file_doallocate调用了vtable中的GIIO_file_stat以获取文件信息。_

_•new_do_write中的_IO_SYSWRITE调用了vtable_IO_new_file_write最终去执行系统调用write。

fclose函数调用的vtable函数有:

• 在清空缓冲区的_IO_do_write函数中会调用vtable中的函数。_

_•关闭文件描述符_IO_SYSCLOSE函数为vtable中的close函数。

•_IO_FINISH函数为vtable中的finish函数。

其他的IO函数功能相类似的调用的应该都差不多,可以参考下。

FSOP

IO_FILE结构体通过_IO_link_in函数链接进入_IO_list_all,我们可以伪造IO_FILE结构体来实现劫持。
glibc中有一个函数_IO_flush_all_lockp,该函数的功能是刷新所有FILE结构体的输出缓冲区

实上,会_IO_flush_all_lockp调用函数的时机包括:

• libc执行abort函数时。
• 程序执行exit函数时。
• 程序从main函数返回时。

IO_FILE 攻击构造

glibc 2.23 伪造vtable

类似于实现堆块任意分配的时候,我们可以在vtable 附近找合适的堆块,改写vtable的地址(注意是在libc的数据段上),然后在合适的偏移处写上one gadget,那么调用相对于vtable函数的时候就会调用one gadget,具体伪造方式也写在下面哪个2.24里面了。

glibc 2.24开始的vtable check及其绕过

这个博客里面有细讲,这里具体说一下怎么构造。可以根据这个看,这是一个正常的IO结构体

pwndbg> p *_IO_list_all
$1 = {
  file = {
    _flags = -72540025,  #这里是伪造成0
    _IO_read_ptr = 0x7f5769d605a3 <_IO_2_1_stderr_+131> "", #有检查的话这里就要伪造成合适的size 0x61或者0xb1之类的。 
    _IO_read_end = 0x7f5769d605a3 <_IO_2_1_stderr_+131> "", 
    _IO_read_base = 0x7f5769d605a3 <_IO_2_1_stderr_+131> "", 
    _IO_write_base = 0x7f5769d605a3 <_IO_2_1_stderr_+131> "", 这里是0
    _IO_write_ptr = 0x7f5769d605a3 <_IO_2_1_stderr_+131> "",  这里是1,其实就是_IO_write_ptr  > _IO_write_base的一个绕过
    _IO_write_end = 0x7f5769d605a3 <_IO_2_1_stderr_+131> "", 
    _IO_buf_base = 0x7f5769d605a3 <_IO_2_1_stderr_+131> "",  #binsh指针,因为它会被当成参数
    _IO_buf_end = 0x7f5769d605a4 <_IO_2_1_stderr_+132> "", 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0x0, 
    _IO_save_end = 0x0, 
    _markers = 0x0, 
    _chain = 0x7f5769d60600 <_IO_2_1_stdout_>, 
    _fileno = 2, 
    _flags2 = 0, 
    _old_offset = -1, 
    _cur_column = 0, 
    _vtable_offset = 0 '\000', 
    _shortbuf = "", 
    _lock = 0x7f5769d61750 <_IO_stdfile_2_lock>, 
    _offset = -1, 
    _codecvt = 0x0, 
    _wide_data = 0x7f5769d5f640 <_IO_wide_data_2>, 
    _freeres_list = 0x0, 
    _freeres_buf = 0x0, 
    __pad5 = 0, 
    _mode = 0, # 这里要小于等于0
    _unused2 = '\000' <repeats 19 times>
  }, 
  vtable = 0x7f5769d5c440 <__GI__IO_file_jumps> #伪造成fake_table,有检查的话就是_IO_str_jumps的地址-8
}
后面跟上system的地址或者,one gadget,这都是根据偏移算出的。

IO 任意写

1.设置_IO_read_end等于_IO_read_ptr

2.设置_flag &~ _IO_NO_READS_flag &~ 0x4

3.设置_fileno为0。

4.设置_IO_buf_basewrite_start_IO_buf_endwrite_end;且使得_IO_buf_end-_IO_buf_base大于fread要读的数据。

IO任意读

stdout会将数据拷贝至输出缓冲区,并将输出缓冲区中的数据输出出来,所以如果可控stdout结构体,通过构造可实现利用其进行任意地址读以及任意地址写。

这里面就是改flag的值,并且把write_base的最后一个字节置NULL一般就可以泄露了。


   转载规则


《IO攻击总结》 时钟 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
SUCTF2019 SUCTF2019
SUCTF2019playfmt格式化不在栈上,读取flag,总的来说就是一个格式化的题EXP #!/usr/bin/env python from pwn import * from LibcSearcher import LibcSea
2020-02-12
下一篇 
glibc 2.24开始的vtable check及其绕过 glibc 2.24开始的vtable check及其绕过
glibc 2.24开始的vtable check及其绕过原理在2.23及其之前,IO结构体里面的vtable里面的相关函数在调用的时候不存在检查,这就使得容易被构造从而使得vtable容易被攻击。 在此基础之上glibc 2.24加入了v
2020-02-10
  目录