house of cat
例题是hgame的week4,比赛的时候摆烂了,昨天学长提了所以补一下
原理
高版本变化
关于_IO_flush_all_lockp
触发FSOP的途径有三个:
- 程序执行exit
- 程序从main函数正常返回
- 触发malloc_printerr
但是从2.27开始,abort函数不再执行fflush,第三种利用方式就不成立了
但还有一个assert可以利用
assert的宏定义如下,可见如果assert的表达式为假会执行__assert_fail
1 |
|
malloc.c中有一个__assert_fail函数(2.36中删除了fflush,2.37删除了__malloc_assert函数):
- 2.35
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19#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 - 2.36用__libc_message替换了__fxprintf,且用__builtin_unreachable替换了fflush和abort,没有fflush assert的利用在2.36无法执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14#ifndef NDEBUG
# define __assert_fail(assertion, file, line, function) \
__malloc_assert(assertion, file, line, function)
_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 ();
}
#endif - 2.37虽然malloc.c中没有了__assert_fail函数,但assert.c中有__assert_fail函数,且也调用了fflush
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28void
__assert_fail (const char *assertion, const char *file, unsigned int line,
const char *function)
{
__assert_fail_base (_("%s%s%s:%u: %s%sAssertion `%s' failed.\n%n"),
assertion, file, line, function);
}
void
__assert_fail_base (const char *fmt, const char *assertion, const char *file,
unsigned int line, const char *function)
{
char *str;
#ifdef FATAL_PREPARE
FATAL_PREPARE;
#endif
int total;
if (__asprintf (&str, fmt,
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion, &total) >= 0)
{
(void) __fxprintf (NULL, "%s", str);
(void) fflush (stderr);
……
综上,2.36无法使用assert触发漏洞,2.35以前和2.37可以
利用__malloc_assert中执行的fflush函数(不用在意stderr,之后这个参数就没有用了)触发漏洞
主要使用的触发assert的部分在sysmalloc中。在_int_malloc中,如果top chunk不够分配会调用sysmalloc,在sysmalloc中如果请求大小小于mp_.mmap_threshold(0x20000)会拓展top chunk,这里有一个assert
1 |
|
触发条件:
- top chunk大小小于MINSIZE(0x20)
- pre_inuse位为0
- top chunk没有页对齐
关于vtable
2.24增加了对vtable位置的检查,2.28又将常利用的相对地址引用替换成了malloc和free,堵死了这条调用链
看一眼跳转表的宏定义,发现只有JUMP跳转有vtable检查,而WJUMP没有
_IO_wfile_jumps结构体使用WJUMP,那么我们可以考虑伪造_IO_wfile_jumps的vtable,_IO_wfile_jumps结构体如下:
1 |
|
house of cat
_IO_wfile_jumps结构体中的_IO_wfile_seekoff源码如下:
1 |
|
要调用_IO_switch_to_wget_mode必须要通过was_writing的检查,即fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
_IO_switch_to_wget_mode源码如下,_IO_WOVERFLOW是通过跳转表调用的:
1 |
|
再看看_IO_switch_to_wget_mode的汇编代码:
我们可以通过rdi控制rdx和rax,在有沙箱的情况下调用setcontext
具体利用过程见例题
调用链
1 |
|
例题
hgame2023 week4 without hook
思路
uaf不解释了,直接开始
泄露libc和堆地址就不说了,large bin attack将堆地址写入_IO_list_all然后调用exit正常退出
接下来就是疯狂调试伪造_IO_FILE_plus结构体,步骤如下:
- fp->_mode<=0,fp->_IO_write_ptr>fp->_IO_write_base
- vtable指向_IO_wfile_jumps+48(利用偏移使_IO_flush_all_lockp调用_IO_OVERFLOW实际进入_IO_wfile_seekoff
- 函数进入_IO_switch_to_wget_mode,要求伪造fp->_wide_data
- 伪造_IO_wide_data结构体,_wide_data->_IO_write_ptr>_wide_data->_IO_write_base
- 伪造vtable指向堆地址,利用_IO_WOVERFLOW指向setcontext+53
- 寄存器布置边调试边改就行
exp
1 |
|