前言
在栈的利用中, 我们通过劫持程序的返回地址来实现劫持程序的控制流
要想通过堆来进行漏洞利用, 可能会想到通过在堆上写入 shellcode 的方式然后劫持返回地址到堆上
但是程序往往会开始 NX (堆栈不可执行) 保护导致利用失败
而且程序可能根本没有缓冲区溢出漏洞
__malloc_hook 和 __free_hook 正是为了应对这种情况的,与栈无关的堆劫持控制流的手法
原理
__malloc_hook
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
28
29
30
| void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0));
arena_get (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
/* Retry with another arena only if we were able to find a usable arena
before. */
if (!victim && ar_ptr != NULL)
{
LIBC_PROBE (memory_malloc_retry, 1, bytes);
ar_ptr = arena_get_retry (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
}
if (ar_ptr != NULL)
(void) mutex_unlock (&ar_ptr->mutex);
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
ar_ptr == arena_for_chunk (mem2chunk (victim)));
return victim;
}
|
注意这一段
1
2
3
4
| void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0));
|
这段代码会检测 __malloc_hook 中有没有内容,若有则执行其中的内容
若我们能够将 __malloc_hook 的内容替换成 one_gadget, 当我们再次 malloc 的时候就能 getshell
__free_hook
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
28
29
30
31
32
33
34
35
36
37
38
| void
__libc_free (void *mem)
{
mstate ar_ptr;
mchunkptr p; /* chunk corresponding to mem */
void (*hook) (void *, const void *)
= atomic_forced_read (__free_hook);
if (__builtin_expect (hook != NULL, 0))
{
(*hook)(mem, RETURN_ADDRESS (0));
return;
}
if (mem == 0) /* free(0) has no effect */
return;
p = mem2chunk (mem);
if (chunk_is_mmapped (p)) /* release mmapped memory. */
{
/* see if the dynamic brk/mmap threshold needs adjusting */
if (!mp_.no_dyn_threshold
&& p->size > mp_.mmap_threshold
&& p->size <= DEFAULT_MMAP_THRESHOLD_MAX)
{
mp_.mmap_threshold = chunksize (p);
mp_.trim_threshold = 2 * mp_.mmap_threshold;
LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
mp_.mmap_threshold, mp_.trim_threshold);
}
munmap_chunk (p);
return;
}
ar_ptr = arena_for_chunk (p);
_int_free (ar_ptr, p, 0);
}
|
注意这一段
1
2
3
4
5
6
7
| void (*hook) (void *, const void *)
= atomic_forced_read (__free_hook);
if (__builtin_expect (hook != NULL, 0))
{
(*hook)(mem, RETURN_ADDRESS (0));
return;
}
|
与上面的 __malloc_hook 有异曲同工之妙
但是我们注意到 *hook 的第一个参数是 mem (free 函数的参数) 是可能由我们控制的
当 one_gadget 的所有条件都不能满足时, 我们可以申请一个 heap 里面填入 /bin/sh, __free_hook 中填入 system 函数的地址就能够执行 system("/bin/sh")
注意
当 __free_hook 被设置为 system 函数地址后,调用 free(ptr) 实际上会变成调用 system(ptr),因此只要 ptr 指向的内容是一个合法的命令字符串(如 “/bin/sh”),就能执行该命令。