前言
在glibc2.34
中取消了 malloc_hook/free_hook
等关键 hook
。这使得原本利用修改这些 hook
来达成控制程序流的方法不再适用。所以在进入glibc高版本后出现了很多利用IO流来控制程序流程的方法,house of kiwi
就是其中之一
适用范围
glibc2.23-glibc2.35(漏洞所需要的malloc_assert函数在2.36中被修改,而在2.37中则完全被删除)
使用条件
- 能够达成任意已知地址写
- 能够触发
__malloc_assert
(要把top_chunk的size位的prev_inuse置为0
,再申请一个比top_chunk大的堆块就可以触发了这个断言,因此我们往往需要修改top_chunk的size位)
如果需要打ORW的话还需要_IO_helper_jumps
可写(只有一些特殊的版本下是可写的,大多时候还是不可写的)
利用原理
在glibc中有下列这样一段函数
#if IS_IN (libc)
#ifndef NDEBUG
# define __assert_fail(assertion, file, line, function) \
__malloc_assert(assertion, file, line, function)
extern const char *__progname;
static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion);
fflush (stderr);
abort ();
}
#endif
#endif
从源码中我们可以看到函数调用了fflush(stderr)
,而这个函数调用后会调用_IO_file_jumps
中的sync
指针
下面我们来看看fflush函数
具体是如何实现的
#define fflush(s) _IO_fflush (s)
int
_IO_fflush (FILE *fp)
{
if (fp == NULL)
return _IO_flush_all ();
else
{
int result;
CHECK_FILE (fp, EOF);
_IO_acquire_lock (fp);
result = _IO_SYNC (fp) ? EOF : 0;
_IO_release_lock (fp);
return result;
}
}
libc_hidden_def (_IO_fflush)
我们使用gdb调试进入fflush函数中
# 此时的rbp指向_IO_file_jumps这个虚表的地址
0x7f1ac0617d45 <fflush+117> mov rcx, rbp
0x7f1ac0617d48 <fflush+120> sub rcx, rdx
0x7f1ac0617d4b <fflush+123> cmp rax, rcx
0x7f1ac0617d4e <fflush+126> jbe fflush+216
0x7f1ac0617d50 <fflush+128> mov rdi, rbx
► 0x7f1ac0617d53 <fflush+131> call qword ptr [rbp + 0x60] <setcontext+61>
rdi: 0x7f1ac077d5e0 (_IO_2_1_stderr_) ◂— 0xfbad2887
rsi: 0x7ffedb61cd20 ◂— 0x616d203a6e69616d ('main: ma')
rdx: 0x7f1ac077d8c0 (_IO_helper_jumps) ◂— 0x0
rcx: 0xc00
0x7f1ac0617d56 <fflush+134> xor r8d, r8d
0x7f1ac0617d59 <fflush+137> test eax, eax
0x7f1ac0617d5b <fflush+139> setne r8b
0x7f1ac0617d5f <fflush+143> neg r8d
0x7f1ac0617d62 <fflush+146> test dword ptr [rbx], 0x8000
进入fflush函数
之后我们发现它的其中有一个call qword ptr [rbp + 0x60]
的汇编代码,在执行这行语句时rbp
指向_IO_file_jumps
这个虚表的地址。而rbp+0x60
则是一个叫做__sync
的指针
那么我们很自然的就会想到,如果我们能够控制_IO_file_jumps
这个虚表,然后控制/修改__sync
这个指针比如修改为one_gadget
,随后想办法调用fflush函数
就可以拿到shell了
kiwi怎么打ORW
条件:kiwi打ORW需要_IO_file_jumps
可写
利用方法和原理:
如果我们要打ORW的话我们就需要利用到setcontext函数
利用setcontext来进行ORW-优快云博客
我们可以将__sync
这个指针修改为setcontext+61
,而高版本setcontext
中的代码片段赋值用的是rdx寄存器+偏移
,而在执行上述的call qword ptr [rbp + 0x60]
的时候rdx寄存器正好指向_IO_helper_jumps
,所以rdx+偏移
都是_IO_helper_jumps
中的内容,这也就是为什么说需要_IO_helper_jumps
可写。
但是_IO_helper_jumps
在高版本中大多时候不可写,只有一些小版本可写。
那么如果我们可以控制_IO_helper_jumps
那么我们要做的就是
- 布置好
ROP
- 控制
_IO_file_jumps + 0x60
为setcontext + 61
- 控制
_IO_helper_jumps+0xa0
和_IO_helper_jumps+0xa8
分别为ROP地址
和ret地址
kiwi利用方法总结
1.拿shell
- 能够达成任意已知地址写,来想办法控制
_IO_file_jumps
中的__sync
指针,修改__sync
指针为one_gadget
- 控制
top_chunk
的size位的大小
并将prev_inuse置为0
- 申请一个比我们修改后的top_chunk的size大的chunk块,就可以触发
__malloc_assert
2.打ORW
- 能够达成任意已知地址写,来想办法控制
_IO_file_jumps
中的__sync
指针为setcontext+61
- 在可控空间中布置ROP(一般是堆块中布置)
- 控制
_IO_helper_jumps+0xa0
和_IO_helper_jumps+0xa8
分别为ROP地址
和ret地址
- 控制
top_chunk
的size位的大小
并将prev_inuse置为0
- 申请一个比我们修改后的top_chunk的size大的chunk块,就可以触发
__malloc_assert
注意:
在glibc-2.36中__malloc_assert
就被修改掉了
_Noreturn static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
const char *function)
{
__libc_message (do_abort, "\
Fatal glibc error: malloc assertion failure in %s: %s\n",
function, assertion);
__builtin_unreachable ();
}
而glibc-2.37则更是逆天
直接删除了__malloc_assert
参考:
1.House OF Kiwi-安全KER - 安全资讯平台
2.文章 - house of kiwi(shell及orw例题分析) - 先知社区
3.house系2 - 望权栈 - 博客园
4.文章 - IO利用之House of kiwi & House of emma - 先知社区
5.Glibc堆利用之house of系列总结 - roderick - record and learn!