利用条件
House of apple的利用条件如下:
- 程序从
main
函数返回或能调用exit
函数 - 能泄露出
heap
地址和libc
地址 - 能使用一次
largebin attack
apple系列都是对fp->_wide_data
的利用,其中apple1可以任意地址写已知值,类似于largebin attack。暂且不提
apple2延续了largebin attack的攻击_IO_FILE结构体的思想,由于_wide_data
的跳表指针使用的时候没有检查,所以可以劫持。
利用思路
利用思路如下:
- 劫持跳表指针为
_IO_wfile_jumps
; - 控制
_wide_data
为可控的堆地址; - 控制
_wide_data->_wide_vtable
为可控的堆地址; - 控制程序执行IO流函数调用,最终调用到
IO_WXXXXXX
函数即可。
利用链子
roderick01 师傅在文章中给出了三条到IO_WXXXXXX
的函数调用
- _IO_wfile_overflow
- _IO_wfile_underflow_mmap
- _IO_wdefault_xsgetn
1. _IO_wfile_overflow
wint_t
_IO_wfile_overflow (FILE *f, wint_t wch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return WEOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0)
{
/* Allocate a buffer if needed. */
if (f->_wide_data->_IO_write_base == 0)
{
_IO_wdoallocbuf (f);//执行目标
// ......
}
}
}
需要执行到_IO_wdoallocbuf(f)
函数,
void
_IO_wdoallocbuf (FILE *fp)
{
if (fp->_wide_data->_IO_buf_base)
return;
if (!(fp->_flags & _IO_UNBUFFERED))
if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)// 目标函数
return;
_IO_wsetb (fp, fp->_wide_data->_shortbuf,
fp->_wide_data->_shortbuf + 1, 0);
}
libc_hidden_def (_IO_wdoallocbuf)
可以发现有一步执行了_IO_WDOALL_CATE
函数,也就是_IO_WXXXX
函数,这里还带个参数fp。调用链如下,
_IO_wfile_overflow --> _IO_wdoallocbuf --> _IO_WDOALLOCATE
*(fp->_wide_data->_wide_vtable + 0x68)(fp)
_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
2. _IO_wfile_underflow_mmap
static wint_t
_IO_wfile_underflow_mmap (FILE *fp)
{
struct _IO_codecvt *cd;
const char *read_stop;
if (__glibc_unlikely (fp->_flags & _IO_NO_READS))
{
fp->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return WEOF;
}
if (fp->_wide_data->_IO_read_ptr < fp->_wide_data->_IO_read_end)
return *fp->_wide_data->_IO_read_ptr;
cd = fp->_codecvt;
/* Maybe there is something left in the external buffer. */
if (fp->_IO_read_ptr >= fp->_IO_read_end
/* No. But maybe the read buffer is not fully set up. */
&& _IO_file_underflow_mmap (fp) == EOF)
/* Nothing available. _IO_file_underflow_mmap has set the EOF or error
flags as appropriate. */
return WEOF;
/* There is more in the external. Convert it. */
read_stop = (const char *) fp->_IO_read_ptr;
if (fp->_wide_data->_IO_buf_base == NULL)
{
/* Maybe we already have a push back pointer. */
if (fp->_wide_data->_IO_save_base != NULL)
{
free (fp->_wide_data->_IO_save_base);
fp->_flags &= ~_IO_IN_BACKUP;
}
_IO_wdoallocbuf (fp); //目标函数
}
//......
同样可以执行到_IO_wdoallocbuf(fp)
函数,调用链是:
_IO_wfile_underflow_mmap --> _IOwdoallocbuf(fp) --> _IO_WDOALLOCATE
*(fp->_wide_data->_wide_vtable + 0x68)(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
3. _IO_wdefault_xsgetn
size_t
_IO_wdefault_xsgetn (FILE *fp, void *data, size_t n)
{
size_t more = n;
wchar_t *s = (wchar_t*) data;
for (;;)
{
/* Data available. */
ssize_t count = (fp->_wide_data->_IO_read_end
- fp->_wide_data->_IO_read_ptr);
if (count > 0)
{
if ((size_t) count > more)
count = more;
if (count > 20)
{
s = __wmempcpy (s, fp->_wide_data->_IO_read_ptr, count);
fp->_wide_data->_IO_read_ptr += count;
}
else if (count <= 0)
count = 0;
else
{
wchar_t *p = fp->_wide_data->_IO_read_ptr;
int i = (int) count;
while (--i >= 0)
*s++ = *p++;
fp->_wide_data->_IO_read_ptr = p;
}
more -= count;
}
if (more == 0 || __wunderflow (fp) == WEOF)
break;
}
return n - more;
}
libc_hidden_def (_IO_wdefault_xsgetn)
需要执行到__wunderflow (fp)
函数,
wint_t
__wunderflow (FILE *fp)
{
if (fp->_mode < 0 || (fp->_mode == 0 && _IO_fwide (fp, 1) != 1))
return WEOF;
if (fp->_mode == 0)
_IO_fwide (fp, 1);
if (_IO_in_put_mode (fp))
if (_IO_switch_to_wget_mode (fp) == EOF)
return WEOF;
// ......
}
在 _IO_switch_to_wget_mode (fp)
函数中会调用 _IO_WOVERFLOW (fp, WEOF)
函数,
int
_IO_switch_to_wget_mode (FILE *fp)
{
if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
if ((wint_t)_IO_WOVERFLOW (fp, WEOF) == WEOF)
return EOF;
// .....
}
调用链如下,
_IO_wdefault_xsgetn --> __wunderflow --> _IO_switch_to_wget_mode --> _IO_WOVERFLOW
*(fp->_wide_data->_wide_vtable + 0x18)(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
利用原理
stdin/stdout/stderr
这三个_IO_FILE
结构体使用的是_IO_file_jumps
这个vtable
,而当需要调用到vtable
里面的函数指针时,会使用宏去调用。以_IO_file_overflow
调用为例,glibc
中调用的代码片段分析如下
#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)` `#define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)` `# define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS)))
其中,IO_validate_vtable
函数负责检查vtable
的合法性,会判断vtable
的地址是不是在一个合法的区间。如果vtable
的地址不合法,程序将会异常终止。
观察struct _IO_wide_data
结构体,发现其对应有一个_wide_vtable
成员。
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;``};
在调用_wide_vtable
虚表里面的函数时,同样是使用宏去调用,仍然以vtable->_overflow
调用为例,所用到的宏依次为:
#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)` `#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)` `#define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS)` `#define _IO_WIDE_JUMPS(THIS) \`` ``_IO_CAST_FIELD_ACCESS ((THIS), ``struct` `_IO_FILE, _wide_data)->_wide_vtable
可以看到,在调用_wide_vtable
里面的成员函数指针时,没有关于vtable的合法性检查。
因此,我们可以劫持IO_FILE
的vtable
为_IO_wfile_jumps
,控制_wide_data
为可控的堆地址空间,进而控制_wide_data->_wide_vtable
为可控的堆地址空间。控制程序执行IO
流函数调用,最终调用到_IO_Wxxxxx
函数即可控制程序的执行流。