house of obstack
漏洞成因
堆溢出
适用范围
2.23
—— 至今- 可以执行一次
largebin attack
- 可以触发
IO
流操作
简述
house of obstack 是 large bin attack(或能达到同样效果的手法也可以)+ FSOP 组合出来的攻击,通过 large bin attack 向 _IO_list_all 中写入一个堆地址以此来伪造一个 IO_FILE 。在 glibc2.23 之后的版本针对 IO_FILE 结构体的 vtable 地址做了检查,该手法通过小幅度改变 vtable 地址,让执行流偏离了正常的调用链,通过伪造 IO_FILE 结构体中的某些字段,最终实现任意地址执行且参数可控的效果.
利用条件为:
- 可以泄露 libc 地址和堆地址
- 可以使用任意地址写一个堆地址(通常是使用 large bin attack )
- 可以从 main 函数返回或者调用 exit 函数
利用原理
说明: 以下的 glibc
源代码均来自 glibc-2.36
先来看下 exit 函数的宏观调用链
exit => __run_exit_handlers => _IO_cleanup => _IO_flush_all_lockp
第一次去改变 exit
正常的执行流是在这里的 _IO_OVERFLOW
这个宏 ,它位于 _IO_flush_all_lockp
函数中(如下)
int
_IO_flush_all_lockp (int do_lock)
{
int result = 0;
FILE *fp;
......
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
)
&& _IO_OVERFLOW (fp, EOF) == EOF)
result = EOF;
......
return result;
}
伪造 IO_FILE
结构体时, vtable
也是可控的(对这个地址做了范围的合法性检查),将原本 vtable
中的 _IO_file_jumps
改成 _IO_obstack_jumps
可以绕过检查(二者的距离很近)
_IO_OVERFLOW
寻找的函数指针是位于起始地址(正常情况下的 _IO_file_jumps
)偏移 0x18
的位置,因此我们只要将正常的 _IO_file_jumps
改成 _IO_obstack_jumps+32
,实际上 _IO_OVERFLOW
执行的就是 _IO_obstack_jumps+32+0x18
的函数指针,该位置的函数指针为 _IO_obstack_xsputn
(如下)
pwndbg> p &_IO_obstack_jumps
$16 = (const struct _IO_jump_t *) 0x7ffff7e163c0 <_IO_obstack_jumps>
pwndbg> telescope 0x7ffff7e163c0+0x18+32
00:0000│ 0x7ffff7e163f8 (_IO_obstack_jumps+56) —▸ 0x7ffff7c88590 (_IO_obstack_xsputn) ◂— endbr64
根据上面所说的伪造完成后(先不考虑要绕过检查的字段),此时程序会执行到 _IO_obstack_xsputn
函数上,从该函数开始会存在一条调用链如下
_IO_obstack_xsputn => _obstack_newchunk => CALL_CHUNKFUN => (*(h)->chunkfun)((h)->extra_arg, (size))
一条新的利用链,伪造 vtable
为_IO_obstack_jumps
,然后调用到_IO_obstack_xsputn
,紧接着调用 obstack_grow
,其代码为:
#define obstack_grow(OBSTACK, where, length) \
__extension__ \
({
struct obstack *__o = (OBSTACK); \
int __len = (length); \
if (_o->next_free + __len > __o->chunk_limit) \
_obstack_newchunk (__o, __len); \
memcpy (__o->next_free, where, __len); \
__o->next_free += __len; \
(void) 0; })
然后在_obstack_newchunk
调用了 CALL_CHUNKFUN
这个宏
void
_obstack_newchunk (struct obstack *h, int length)
{
struct _obstack_chunk *old_chunk = h->chunk;
struct _obstack_chunk *new_chunk;
long new_size;
long obj_size = h->next_free - h->object_base;
long i;
long already;