从零开始的 Pwn 之旅 - Malloc_hook 和 Free_hook

前言

在栈的利用中, 我们通过劫持程序的返回地址来实现劫持程序的控制流
要想通过堆来进行漏洞利用, 可能会想到通过在堆上写入 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”),就能执行该命令。