> For the complete documentation index, see [llms.txt](https://blog.kaisuping.cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://blog.kaisuping.cn/technology/heap-exploitation.md).

# Heap Exploitation

### 前言

笔者最近跟学委吴一起接了软件安全测试中的 Heap Exploitation Project，借此 blog 梳理学习思路。

### 什么是堆？

借 stackoverflow & geeksforgeeks 的回答，先大致了解一下堆是个什么东西，与我们所熟悉的栈又有怎样的区别和联系。

<figure><img src="/files/vgyFqiRqF1U6chfyLfB4" alt=""><figcaption><p><a href="https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap">https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap</a></p></figcaption></figure>

<figure><img src="/files/BKzJbiw3TavsFwzseX7D" alt=""><figcaption><p><a href="https://www.geeksforgeeks.org/stack-vs-heap-memory-allocation/#">https://www.geeksforgeeks.org/stack-vs-heap-memory-allocation/#</a></p></figcaption></figure>

通过以上两张图片，相信读者已经能建立起对堆的初步印象。接下来就可以愉快地讨论堆的实现方式与利用。再次声明，以下讨论的堆，均指**运行内存中的堆区域**，该区域由程序员手动申请、手动释放，被同一个进程中的所有线程全局共享。

### 堆的实现有哪些？

从古到今，笔者了解到的堆实现方式有以下七种：

```
dlmalloc  – General purpose allocator
ptmalloc2 – glibc 
jemalloc  – FreeBSD and Firefox
tcmalloc  – Google
libumem   – Solaris
nedmalloc
Hoar
```

堆多种的实现方式，也间接提示我们 heap exploitation 不是件轻松活。那么 heap exploitation 究竟难在什么地方呢？

> Heap-based vulnerabilities can be very dependent on how the internal implementation of the heap allocator actually works.

因此为了“不耍流氓”，定义讨论的范围为采用 **ptmalloc2** 的 **glibc**，接下来让我们去一睹 glibc 堆管理的实现吧！

### glibc 实现

#### chunk 介绍

堆的最小单元为 chunk，由 metadata 与 userdata 组成。metadata 指下图中的 **PREV\_SIZE & CHUNK SIZE**，用于记录 userdata 的元信息，方便 glibc 进行内存管理。

<figure><img src="/files/3pmGdieSM6ja91ciaOfz" alt=""><figcaption><p>glibc 对 chunk 的介绍</p></figcaption></figure>

metadatas 中 PREV\_SIZE 的作用：

1. 给前一块 chunk 作为 userdata 存储数据使用（见 Fig.2）；
2. 在 bin 中表示前项 chunk 的大小。

<figure><img src="/files/77DNxMta2DTw94qeDP62" alt=""><figcaption><p>chunk 示意图</p></figcaption></figure>

<figure><img src="/files/3Sv1Rz1h3TUDh2jdst0f" alt="" width="375"><figcaption><p>Fig.2</p></figcaption></figure>

metadata 中 CHUNK SIZE 记录了整个 chunk 的大小，其计算方式如下图所示。需要注意的是，glibc 中规定了 alignment 是 double word，即在 32bits 机器中要求 8bytes 地址对齐，64bits 机器中要求 16bytes 地址对齐。

<figure><img src="/files/m9g6jN33jy81hIqs3B8W" alt=""><figcaption><p>32bits 中的 chunk 组成</p></figcaption></figure>

#### chunk 释放策略与细节

glibc 对于被 free 掉的 chunk 都丢给了“小弟”——bin去管理。bin 要考虑的问题是：该把这块 chunk 改造成什么样，丢到哪里去，才方便后续分配嘞？

<figure><img src="/files/Fg5OTrhvjbeiWtxWmtsJ" alt=""><figcaption><p>chunk 在 bin 中的样子</p></figcaption></figure>

bin 根据用途分为了五类：small bin、large bin、unsorted bin、fast bin、tcache bin。其中发挥基础作用的是 small bin 和 large bin，small bin 实现对 512bytes 以下的 chunk 管理，large bin 实现对 512bytes 及以上的 chunk 管理。unsorted bin、fast bin 和 tcache bin 充当 cache layer 的作用。具体可参考下图以及联想存储器多级缓存的架构理解：

<figure><img src="/files/qCUeBf9TLhJ44YfsNKSk" alt="" width="375"><figcaption></figcaption></figure>

**先从高空俯瞰一下 chunk 释放的策略：**

> if pointer is NULL “do nothing” else convert the pointer back to a chunk
>
> → Perform sanity checks on the chunk, and abort when fail
>
> → chunk fits into a *tcache* bin, store it there.
>
> → chunk has the *M* bit set, give it back to the os via *munmap*.
>
> → Otherwise we obtain the arena heap lock and then:&#x20;
>
> 1. fits into a fastbin, put it on the corresponding fastbin, and we’re done.
> 2. size > 64KB, consolidate the fastbins, put the resulting merged chunks → unsorted bin.
> 3. Merge the chunk with neighboring freed chunks in the small, large, and unsorted bins.
> 4. Merge the result chunk if neighboring the top chunk.
> 5. Otherwise store it in the *unsorted bin*

第一次看到这些策略，肯定是一头雾水，这时候就需要结合细节去理解。

**sanity checks：**

> 1. A check that the allocation [is aligned](https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=blob;f=malloc/malloc.c;h=6e766d11bc85b6480fa5c9f2a76559f8acf9deb5;hb=HEAD#l4182) on an 8-byte (or 16-byte on 64-bit) boundary, since *malloc* ensures all allocations are aligned.
> 2. A check that the chunk’s size field isn’t impossible–either because it is [too small](https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=blob;f=malloc/malloc.c;h=6e766d11bc85b6480fa5c9f2a76559f8acf9deb5;hb=HEAD#l4318), too large, not an aligned size, or [would overlap the end of the process’ address space](https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=blob;f=malloc/malloc.c;h=6e766d11bc85b6480fa5c9f2a76559f8acf9deb5;hb=HEAD#l4175).
> 3. A check the chunk lies [within the boundaries of the arena](https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=blob;f=malloc/malloc.c;h=6e766d11bc85b6480fa5c9f2a76559f8acf9deb5;hb=HEAD#l4318).
> 4. A check that the chunk is [not already marked as free](https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=blob;f=malloc/malloc.c;h=6e766d11bc85b6480fa5c9f2a76559f8acf9deb5;hb=HEAD#l4182) by checking the corresponding “P” bit that lies in the metadata at the start of the next chunk.

**五类 bin 的具体实现：**

* bin 的整体管理是将各类 bin 放到一个数组中，包括 62 small bins, 63 large bins, 1 unsorted bin, 10 fast bins and 64 tcache bins per thread

  <figure><img src="/files/38N071FL0JQF8TlDI2pU" alt=""><figcaption></figcaption></figure>
* small bin

  * *Every chunk less than 512 bytes on 32-bit systems (or than 1024 bytes on 64-bit systems) has a corresponding small bin. (the size of allocated memory is stable)*&#x20;

  <figure><img src="/files/4s1XIbfIcssSmItkSFIH" alt=""><figcaption><p>small bin</p></figcaption></figure>
* large bin

  * 基于用户分配的空间趋向于小块的事实，large bin 倾向于作为 small bin 的补充。注意下图中 BETWEEN 是表 AMONG 的意思。

  <figure><img src="/files/jH1tVwPzG8yFNUKTahSj" alt=""><figcaption><p>large bin</p></figcaption></figure>
* unsorted bin

  * cluster free & malloc 聚集在某一个时间段时，将其放入 unsorted bin，即拿即用。

  <figure><img src="/files/xJlyIk8uJb46jNSZSGWZ" alt="" width="190"><figcaption><p>unsorted bin</p></figcaption></figure>
* fast bin

  * 为了减少内存碎片对系统的影响，fast bin 用于管理程序释放掉的较小的内存块（小于 88bytes）。

  <figure><img src="/files/bBwQ5TXOzvsJf9VV0Tft" alt=""><figcaption><p>fast bin</p></figcaption></figure>
* tcache bin

  * 因为堆是全局共享的，对于多线程的情况，则不可避免会遇上需要等待别的线程释放锁，才能申请资源的情况。为了避免此类开销，设计了 tcache。
  * *Each bin contains a maximum of* [*7 same-size chunks*](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=2527e2504761744df2bdb1abdc02d936ff907ad2;hb=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#l323) *ranging from* [*24 to 1032 bytes on 64-bit systems and 12 to 516 bytes on 32-bit systems*](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=2527e2504761744df2bdb1abdc02d936ff907ad2;hb=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#l315)*.*

  <figure><img src="/files/w2dcihyvlCLr6j7r48Bh" alt=""><figcaption><p>tcache bin</p></figcaption></figure>

#### chunk 分配策略与细节

为了避免频繁的系统调用，在程序第一次 malloc 过后（即便申请很小的内存，如 malloc(0)），os 会分配一块 0x21000 大小的内存给 glibc，并拍了拍 glibc 的肩膀说，“小老弟，拿去耍吧。这些应该足够你应付来申请内存的小鬼了，如果实在不够再来找我”。glibc 捧着这块 0x21000 大小的内存，屁颠屁颠得开始了它的打工生活——替 os 处理内存申请与释放的工作。

**老规矩，先瞅一眼 chunk 分配的策略：**

> tcache&#x20;
>
> → mmap
>
> → obtain arena heap lock → fast bin → small bin
>
> → unsorted bin，if suitable stop，otherwise put into small / large bin
>
> → large bin
>
> → merge unsorted bin、fast bin、small bin、large bin
>
> → sbrk&#x20;
>
> → mmap
>
> → return NULL

想必你初见觉得晦涩难懂。没事，咱们 step by step 来瞅瞅看这究竟是怎么回事。

**chunk 分配机制**

1. *If there is a previously-freed chunk of memory, and that chunk is big enough to service the request, the heap manager will use that freed chunk for the new allocation.*                                                  ![](/files/M8uAVVeONUDrzRBlVMbQ)
2. *Otherwise, if there is available space at the top of the heap, the heap manager will allocate a new chunk out of that available space and use that.*     ![](/files/95h0OUeIzFdj5pbdFFtm)
3. *Otherwise, the heap manager will ask the kernel to add new memory to the end of the heap, and then allocates a new chunk from this newly allocated space.* ![](/files/ZWVdnVJQekp22UdlTXSi)
4. *`mmap` provides greater flexibility, can map files or anonymous memory, and can map memory to non-contiguous addresses. This helps overcome some limitations of `brk`, such as heap fragmentation.*                                                                           ![](/files/Sn7qFvtUaVuIsshijHwc)
5. *If all these strategies fail, the allocation can’t be serviced, and malloc returns NULL.*

### UAF

有了以上的基础，就可以愉快尝试一下 heap exploitation。笔者近期了解到的 heap exploitation 如下：

* **Heap Overflow（堆溢出）：** 攻击者通过向动态分配的堆内存写入超出分配的内存空间的数据，覆盖相邻内存的内容，可能导致程序崩溃或执行恶意代码。
* **Use-After-Free（释放后使用）：** 攻击者利用程序中已经释放的堆内存块，尝试重新使用这些内存块来执行恶意操作。
* **Double Free（重复释放）：** 这种攻击涉及重复释放相同的堆内存块，从而可能导致内存管理数据结构被破坏，以及潜在的漏洞利用机会。
* **Off-by-One Error（溢出一个字节）：** 攻击者试图溢出一个字节的内存，通常用于更细微的堆利用攻击。
* **Fastbin Attack（快速分配块攻击）：** 这种攻击针对 glibc 中的 fastbins，攻击者试图伪造 fastbin 链表，以便获取堆上的未分配内存块。
* **Tcache Attack（Tcache攻击）：** 这种攻击利用glibc 的 tcache 机制，攻击者试图伪造 tcache 链表，以获取未分配内存块。
* **Unlink Attack（取消链接攻击）：** 这种攻击涉及伪造一个被取消链接的堆块，以触发漏洞利用机会。
* **Poison Null Byte Attack（毒零字节攻击）：** 这种攻击利用零字节终止字符串，攻击者试图修改内存块的大小字段，以实现漏洞利用。

以下借 ctf-wiki 的[示例](https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/use-after-free/)介绍 **Use-After-Free(UAF)**。

UAF 的实现机理是观察堆的分配与释放，构造合法的内存写入，利用 dangling pointer 访问原先分配的地址，通过这种未定义行为发起恶意攻击（如劫持控制流），其本质是如下 L4 所述的 “arbitrary effects”。

<figure><img src="/files/gWDf3rTJHjuIzNOpu9OR" alt=""><figcaption><p>free in malloc.c</p></figcaption></figure>

示例[源码](https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/use_after_free/hitcon-training-hacknote)可通过链接获取，注意将 `Makefile` 修改成如下所示，将 PIE 保护关闭。温馨提示： `exp.py`中的 magic 变量值是通过反汇编（如，将可执行文件拖入 IDA） ，查看函数的起始位置得到的。其它具体的实现过程参照示例即可。

```makefile
hacknote:hacknote.c
        gcc hacknote.c -m32 -no-pie -fstack-protector -o hacknote 
```

<figure><img src="/files/xVnz8fil0CgA1KDnmvhE" alt=""><figcaption></figcaption></figure>

示例代码架构与利用步骤如下：

<figure><img src="/files/GqSpEIw9dOui3J8079tm" alt=""><figcaption></figcaption></figure>

UAF 在此题的体现：首先申请两个 node，然后删除两个 node。两个 node 共计四块 chunk 被加入到 tcache 中管理。此时再申请一个新的 node，如果申请的 size 得当，glibc 就会将 node0 与 node1 的两块 **printnote** 分配给新的 node 使用，注意到 printnote 是一个函数指针！此时 content2 是从标准输入读入的，即可以被用户所控制的。那么先往 content2 写入 magic 函数地址，再利用 node0（此时为 dangling pointer）访问 content2 这块区域，RIP 就会访问 magic，成功实现控制流的劫持。&#x20;

<figure><img src="/files/OQptxceMHCNhUBxjekw0" alt=""><figcaption></figcaption></figure>

### **附：Linux kernel heap feng shui in 2022 学习笔记**

*该文内容较杂，因此采用分点记录的方式。*

1. 该文总结了 allocator 的实现机制对堆利用的影响；
2. kernel 中两种常见的动态分配机制如下:
   1. General-purpose allocations performed using `kmalloc/kzalloc/...` API
   2. Special-purpose allocations via `kmem_cache_create/kmem_cache_alloc`
3. Slab 是 Linux 内核中的一种内存分配机制，用于高效地管理内核数据结构和对象的内存分配和释放。Slab 分配器的主要目标是减少内存分配和释放的开销，以提高系统性能。**Slab 分配器通过预分配一定大小的内存块并将它们组织成 slabs，然后在需要时分配这些内存块，从而减少了内存碎片和频繁的内存分配操作。**&#x53;lab 分配器通常管理多个 slab 缓存，每个缓存用于不同大小的内存块。这些缓存通常包括三种类型：
   1. Slab缓存：用于保存已分配的内存块。
   2. Slab未分配缓存：用于保存已分配但尚未使用的内存块，以便后续分配。
   3. Slab空闲缓存：用于保存完全未使用的内存块。
4. **对齐问题的潜在利用**：攻击者可以利用对齐问题来修改数据结构的大小，进而改变其行为。例如，他们可能故意构造一个对象，以便它与某个通用缓存别名化，从而可以操纵通用缓存并导致不安全操作。但安全机制近年来也在不断地更新：
   * `cred_jar` 在 kernel version > 4.4 的情况下加入 SLAB\_ACCOUNT 标签，使得其 standalone 免受其攻击。
   * 安全的函数（`copy_to_user` / `copy_from_user`）被逐步引入，更加严格的检测机制使漏洞利用难以下手。

     <figure><img src="/files/OH8nCzH79TL1Yq94nPR9" alt="" width="497"><figcaption></figcaption></figure>
   * *A freelist pointer randomisation `CONFIG_SLAB_FREELIST_RANDOM` was introduced in 4.8 and is now enabled by default on most modern distributions.*

### References

1. [系统理解 Arm Heap Exploitation](< https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/>)
2. [Understanding glibc malloc](https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/)
3. [glibc malloc source code](https://github.com/iromise/glibc/blob/master/malloc/malloc.c)
4. [了解 linux kernel heap](< https://www.blackhat.com/presentations/bh-usa-07/Ferguson/Whitepaper/bh-usa-07-ferguson-WP.pdf>)
5. [对以往 linux heap 的研究总结](<https://duasynt.com/blog/linux-kernel-heap-feng-shui-2022 >)
6. [ctfwiki-UAF](https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/use-after-free/)
7. [深入理解 heap exploitation](https://github.com/shellphish/how2heap)

<figure><img src="/files/1nSdku1Eg7RUVjnaRdAU" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/DZ3oPtjIPFlXv4lCb2QO" alt=""><figcaption><p>first contribution</p></figcaption></figure>
