首页
社区
课程
招聘
[原创]_IO_FILE劫持的一点总结
发表于: 2021-1-3 20:47 7959

[原创]_IO_FILE劫持的一点总结

2021-1-3 20:47
7959

本文首先介绍文件流中涉及到的重要struct,并从fopen打开一个文件开始分析了打开文件的过程,之后通过分析fread函数调用,并从汇编层面分析vtable函数跳转表执行的过程,给出可利用方案,最后分析了libc对该劫持的漏洞利用缓解措施。本文源码参考glibc-2.23版本

















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;

  _IO_off64_t _offset;

  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;

  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
};
struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};;
  struct locked_FILE
  {
    struct _IO_FILE_plus fp;
    _IO_lock_t lock;
    struct _IO_wide_data wd;
  } *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE));
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
通过_IO_file_init将fp链入全局输入输出流链表_IO_list_all
_IO_file_init (&new_f->fp);

//该列表上的stdin,stdout,stderr是存储在libc中的,DEF_STDFILE是初始化其对应的结构体的
//_IO_file_init (&new_f->fp);
DEF_STDFILE(_IO_2_1_stdin_, 0, 0, _IO_NO_WRITES);
DEF_STDFILE(_IO_2_1_stdout_, 1, &_IO_2_1_stdin_, _IO_NO_READS);
DEF_STDFILE(_IO_2_1_stderr_, 2, &_IO_2_1_stdout_, _IO_NO_READS+_IO_UNBUFFERED);

struct _IO_FILE_plus *_IO_list_all = &_IO_2_1_stderr_;
//_IO_list_all可以通过libc的基地址计算出来
  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. */   // 缓冲区的大小,读取的内容存放的地方
glibc-2.23\glibc-2.23\libio\genops.c
_IO_doallocbuf 
    -> _IO_DOALLOCATE (fp) ===> _IO_file_doallocate
F:\源码阅读\glibc-2.23\glibc-2.23\libio\filedoalloc.c
int _IO_file_doallocate (_IO_FILE *fp){
    ...
  p = malloc (size);
  if (__glibc_unlikely (p == NULL))
    return EOF;
  _IO_setb (fp, p, p + size, 1); // __IO_setb会设置_IO_buf_base
  }
  have = fp->_IO_read_end - fp->_IO_read_ptr;
      if (want <= have) // want为需要的字节长度,have为已读取到的字节长度,如果满足已经读取到的字节长度满足需要读取的字节长度,则将缓冲区中的数据cp到读取区域
    {
      memcpy (s, fp->_IO_read_ptr, want);// 拷贝到缓冲区
      fp->_IO_read_ptr += want;
      want = 0;
    }
      if (have > 0)
        {
#ifdef _LIBC
          s = __mempcpy (s, fp->_IO_read_ptr, have);
#else
          memcpy (s, fp->_IO_read_ptr, have);
          s += have;
#endif
          want -= have;
          fp->_IO_read_ptr += have;
        }
\glibc-2.23\libio\genops.c
__underflow -> _IO_UNDERFLOW ===> _IO_new_file_underflow
\glibc-2.23\libio\fileops.c
_IO_new_file_underflow
_IO_new_file_underflow (_IO_FILE *fp){
  ...
   fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_buf_base;//__IO_read_base指向 _IO_buf_base
   fp->_IO_read_end = fp->_IO_buf_base;
   fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end
    = fp->_IO_buf_base;

  count = _IO_SYSREAD (fp, fp->_IO_buf_base,
               fp->_IO_buf_end - fp->_IO_buf_base);//_IO_file_read fileops.c
   fp->_IO_read_end += count;
 ...
 }
      if (fp->_IO_buf_base
          && want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base))
        {
          if (__underflow (fp) == EOF)
        break;

          continue;
        }
_IO_size_t
_IO_sgetn (_IO_FILE *fp, void *data, _IO_size_t n)
{
  /* FIXME handle putback buffer here! */
  return _IO_XSGETN (fp, data, n); //fileops.c -> _IO_file_xsgetn
}
 ((*(__typeof__ (((struct _IO_FILE_plus){}).vtable) *)(((char *) ((fp))) + __builtin_offsetof (struct _IO_FILE_plus, vtable)))->__xsgetn) (fp, data, n)
sub     rsp, 8          ; Alternative name is '_IO_sgetn'
mov     rax, [fp + 0D8h] ; fp的偏移0xd8的vtable的值,32 位的 vtable 偏移为 0x94,64 位偏移为 0xd8
call    qword ptr [rax+40h] ; 调用指定函数,[rax+40h]指向libc上
add     rsp, 8
retn
####
$47 = {  __dummy = 0, 
  __dummy2 = 0, 
  __finish = 0x7ffff7ab81d3 <_IO_new_file_finish>, 
  __overflow = 0x7ffff7ab8981 <_IO_new_file_overflow>, 
  __underflow = 0x7ffff7ab8724 <_IO_new_file_underflow>, 
  __uflow = 0x7ffff7ab96da <__GI__IO_default_uflow>, 
  __pbackfail = 0x7ffff7aba2ad <__GI__IO_default_pbackfail>, 
  __xsputn = 0x7ffff7ab7c6b <_IO_new_file_xsputn>, 
  __xsgetn = 0x7ffff7ab7ebc <__GI__IO_file_xsgetn>, 
  __seekoff = 0x7ffff7ab73a2 <_IO_new_file_seekoff>, 
  __seekpos = 0x7ffff7ab9907 <_IO_default_seekpos>, 
  __setbuf = 0x7ffff7ab724b <_IO_new_file_setbuf>, 
  __sync = 0x7ffff7ab7191 <_IO_new_file_sync>, 
  __doallocate = 0x7ffff7aac841 <__GI__IO_file_doallocate>, 
  __read = 0x7ffff7ab7b69 <__GI__IO_file_read>, 
  __write = 0x7ffff7ab7bc6 <_IO_new_file_write>, 
  __seek = 0x7ffff7ab7930 <__GI__IO_file_seek>, 
  __close = 0x7ffff7ab7165 <__GI__IO_file_close>, 
  __stat = 0x7ffff7ab7bad <__GI__IO_file_stat>, 
  __showmanyc = 0x7ffff7aba3f5 <_IO_default_showmanyc>, 
  __imbue = 0x7ffff7aba3fb <_IO_default_imbue>
  }
_IO_size_t
_IO_sgetn (_IO_FILE *fp, void *data, _IO_size_t n)
{
  /* FIXME handle putback buffer here! */
  return _IO_XSGETN (fp, data, n);
}
#在跳转的时候函数定义为JUMP2(FUNC,THIS,X1,X2)
#define _IO_XSGETN(FP, DATA, N) JUMP2 (__xsgetn, FP, DATA, N)
#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)
libc_base_address -> _IO_list_all -> 任意结构体地址 -> 偏移计算vtable -> vtable表地址
libc_base_address -> _std_in/_std_out/_std_err地址 -> 偏移计算vtable -> vtable表地址
//libc-2.23
# define _IO_JUMPS_FUNC(THIS) _IO_JUMPS_FILE_plus (THIS)
//libc-2.24
# define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS)))
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
  /* Fast path: The vtable pointer is within the __libc_IO_vtables
     section.  */
  uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
  const char *ptr = (const char *) vtable;
  uintptr_t offset = ptr - __start___libc_IO_vtables;
  if (__glibc_unlikely (offset >= section_length))
    /* The vtable pointer is not in the expected section.  Use the
       slow path, which will terminate the process if necessary.  */
    _IO_vtable_check ();
  return vtable;
}

本文首先介绍文件流中涉及到的重要struct,并从fopen打开一个文件开始分析了打开文件的过程,之后通过分析fread函数调用,并从汇编层面分析vtable函数跳转表执行的过程,给出可利用方案,最后分析了libc对该劫持的漏洞利用缓解措施。本文源码参考glibc-2.23版本

  • IO_FILE 的结构体如下
  • IO_FILE_PLUS 的 结构体
  • 通过 fopen 打开文件的时候,会在 堆上 为该文件申请一片 locked_FILE 结构体的空间,用来存储该文件的文件流信息
  • 随后会初始化 结构体中fp的值
  • 将该堆结构链入全局变量 _IO_list_all 

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2021-1-5 18:03 被seana编辑 ,原因:
收藏
免费 2
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//