堆的检测绕过总结

0x0 简介

这里总结一下堆里面安全性的检测和如何绕过

unlink 用来将一个双向链表(只存储空闲的 chunk)中的一个元素取出来,可能在以下地方使用

  • malloc

    • 从恰好大小合适的 large bin 中获取 chunk。
      • 这里需要注意的是 fastbin 与 small bin 就没有使用 unlink,这就是为什么漏洞会经常出现在它们这里的原因。
      • 依次遍历处理 unsorted bin 时也没有使用 unlink 。
    • 从比请求的 chunk 所在的 bin 大的 bin 中取 chunk。
  • free

    • 后向合并,合并物理相邻低地址空闲 chunk。
    • 前向合并,合并物理相邻高地址空闲 chunk(除了 top chunk)。
  • malloc_consolidate

    • 后向合并,合并物理相邻低地址空闲 chunk。
    • 前向合并,合并物理相邻高地址空闲 chunk(除了 top chunk)。
  • realloc

    • 前向扩展,合并物理相邻高地址空闲 chunk(除了 top chunk)。

源码

由于 unlink 使用非常频繁,所以 unlink 被实现为了一个宏,如下

总结下它的检测

1
2
3
// 由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致。
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
malloc_printerr ("corrupted size vs. prev_size");
  1. 当需要unlink一个堆块时首先检测大小是否等于后一块的prev_size

    1
    2
    3
    4
    FD = P->fd;                                                                      \
    BK = P->bk; \
    // 防止攻击者简单篡改空闲的 chunk 的 fd 与 bk 来实现任意写的效果。
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
  2. 接着检查unlink的堆块是否在链表中

0x2 _int_malloc

总结一下

1
2
3
4
5
6
7
8
9
// 检查取到的 chunk 大小是否与相应的 fastbin 索引一致。
// 根据取得的 victim ,利用 chunksize 计算其大小。
// 利用fastbin_index 计算 chunk 的索引。
if (__builtin_expect(fastbin_index(chunksize(victim)) != idx, 0)) {
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr(check_action, errstr, chunk2mem(victim), av);
return NULL;
}
  1. 从fastbin中取出chunk后,检查size是否属于fastbin
1
2
3
4
5
6
7
// 获取 small bin 中倒数第二个 chunk 。
bck = victim->bk;
// 检查 bck->fd 是不是 victim,防止伪造
if (__glibc_unlikely(bck->fd != victim)) {
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
  1. 从smallbin中除去chunk后,检查victim->bk->fd == victim
  2. 从unsortbin取chunk时,要检查2*sizet < 内存总分配量
  3. 从 largebin取chunk时,切分后的chunk要加入unsortedbin,需要检查 unsortedbin的第一个chunk的bk是否指向unsortedbin
  4. 如果freebin中有合适大小的堆块那么执行unlink操作

0x3 free

  1. free的检查主要是根据本chunk的size检测下一块的inuse位,查看是否有double free的情况发生

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    mchunkptr    old     = *fb, old2;
    unsigned int old_idx = ~0u;
    do {
    /* Check that the top of the bin is not the record we are going to
    add
    (i.e., double free). */
    // so we can not double free one fastbin chunk
    // 防止对 fast bin double free
    if (__builtin_expect(old == p, 0)) {
    errstr = "double free or corruption (fasttop)";
    goto errout;
    }
    /* Check that size of fastbin chunk at the top is the same as
    size of the chunk that we are adding. We can dereference OLD
    only if we have the lock, otherwise it might have already been
    deallocated. See use of OLD_IDX below for the actual check. */
    if (have_lock && old != NULL)
    old_idx = fastbin_index(chunksize(old));
    p->fd = old2 = old;
    } while ((old = catomic_compare_and_exchange_val_rel(fb, p, old2)) !=
    old2);
  2. 检查当前free的chunk是否与fastbin中的第一个chunk相同,相同则报错

  3. 根据当前的inuse以及后一块的后一块的inuse判断是否需要合并,如果需要合并则对在链表中的freebin进行unlink操作

-------------本文结束感谢您的阅读-------------
+ +