# Unsorted Bin Attack {{#include ../../banners/hacktricks-training.md}} ## 基本信息 有关什么是 unsorted bin 的更多信息请查看此页面: {{#ref}} bins-and-memory-allocations.md {{#endref}} Unsorted lists 能够在 chunk 的 `bk` 字段中写入 `unsorted_chunks (av)` 的地址。因此,如果攻击者能够**修改位于 unsorted bin 内某个 chunk 的 `bk` 指针地址**,就可能**在任意地址写入该地址**,这对于 leak Glibc 地址或绕过某些防护很有帮助。 所以,基本上,这个攻击允许在任意地址**设置一个很大的数值**。这个大数值是一个地址,可能是 heap 地址或 Glibc 地址。传统目标是 **`global_max_fast`**,用于允许创建更大尺寸的 fast bin(并从 unsorted bin attack 转为 fast bin attack)。 - 现代说明 (glibc ≥ 2.39):`global_max_fast` 已变为 8 位全局变量。通过 unsorted-bin 写入指针到该位置将会破坏相邻的 libc 数据,并且不再可靠地提升 fastbin 限制。在对抗 glibc 2.39+ 时,优先考虑其他目标或其他原语。见下文“Modern constraints”,并在得到稳定原语后考虑结合其他技术,例如 [large bin attack](large-bin-attack.md) 或 [fast bin attack](fast-bin-attack.md)。 > [!TIP] > T> 看一下 [https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#principle) 提供的示例,并将 chunk sizes 从 0x400/0x500 改为 0x4000/0x5000(以避免 Tcache),可以看到**现在**会触发错误 **`malloc(): unsorted double linked list corrupted`**。 > > 因此,现在这个 unsorted bin 攻击(以及其他检查)也要求能够修复双向链表以绕过 `victim->bk->fd == victim` 或者 `victim->fd == av (arena)` 的检查,这意味着我们想写入的地址在其 `fd` 位置必须保存伪造 chunk 的地址,并且该伪造 chunk 的 `fd` 指向 arena。 > [!CAUTION] > 注意这个攻击会破坏 unsorted bin(因此也会影响 small 和 large)。因此我们现在只能**使用来自 fast bin 的分配**(更复杂的程序可能会执行其他分配并崩溃),并且为了触发这一点我们必须**分配相同大小否则程序会崩溃。** > > 注意覆盖 **`global_max_fast`** 在这种情况下可能有帮助,前提是信任 fast bin 能够处理所有其他分配直到 exploit 完成。 来自 [**guyinatuxedo**](https://guyinatuxedo.github.io/31-unsortedbin_attack/unsorted_explanation/index.html) 的代码对此解释得很好,不过如果你修改 mallocs 以分配足够大的内存从而不进入 Tcache,你会看到前文提到的错误阻止了该技术:**`malloc(): unsorted double linked list corrupted`** ### 写入是如何实际发生的 - unsorted-bin 写入在 `free` 时触发,当被释放的 chunk 被插入到 unsorted list 的头部。 - 在插入过程中,分配器执行 `bck = unsorted_chunks(av); fwd = bck->fd; victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;` - 如果你能在调用 `free(victim)` 之前将 `victim->bk` 设置为 `(mchunkptr)(TARGET - 0x10)`,最后的语句将执行写入:`*(TARGET) = victim`。 - 之后,当分配器处理 unsorted bin 时,完整性检查会验证(除其它检查外)`bck->fd == victim` 和 `victim->fd == unsorted_chunks(av)`,否则会中止并报 `malloc(): unsorted double linked list corrupted`。因为插入时已经把 `victim` 写入了 `bck->fd`(我们的 `TARGET`),如果写入成功这些检查可以被满足。 ## 现代限制(glibc ≥ 2.33) 为了在当前 glibc 上可靠地使用 unsorted‑bin 写入: - Tcache 干扰:对于落入 tcache 的大小,free 会被导向 tcache 而不会触及 unsorted bin。要么 - 使用大于 MAX_TCACHE_SIZE 的请求(在 64‑bit 上默认 ≥ 0x410),要么 - 填满对应的 tcache bin(7 个条目),使更多的 free 到达全局 bins,或者 - 如果环境可控,禁用 tcache(例如 GLIBC_TUNABLES glibc.malloc.tcache_count=0)。 - 对 unsorted list 的完整性检查:在下一次检查 unsorted bin 的分配路径上,glibc 会做检查(简化): - `bck->fd == victim` 和 `victim->fd == unsorted_chunks(av)`;否则会以 `malloc(): unsorted double linked list corrupted` 终止。 - 这意味着你目标的地址必须能容忍两次写入:第一次在 free 时写入 `*(TARGET) = victim`;随后在 chunk 被移除时,分配器会再次将 `*(TARGET) = unsorted_chunks(av)`(分配器会把 `bck->fd` 重写回 bin head)。选择那些单纯强制一个非零大值就有用的目标。 - 现代利用中典型的稳定目标 - 应用或全局状态中将“较大”值视作标志/限制的变量。 - 间接原语(例如,为随后的 [fast bin attack]({{#ref}}fast-bin-attack.md{{#endref}}) 做设置,或为之后的 write-what-where 做准备)。 - 在新 glibc 上避免 `__malloc_hook`/`__free_hook`:它们在 2.34 中被移除。对于 ≥ 2.39 避免 `global_max_fast`(见上注)。 - 关于近期 glibc 中的 `global_max_fast` - 在 glibc 2.39+ 中,`global_max_fast` 是一个 8 位全局变量。将堆指针写入其中以增大 fastbins 的经典技巧不再干净可行,并且可能会破坏相邻的分配器状态。优先使用其他策略。 ## 最小利用流程(modern glibc) 目标:使用 unsorted‑bin 插入原语,实现一次将 heap 指针写入任意地址的任意写,且不崩溃。 - 布局/预处理 - 分配 A、B、C,大小足够大以绕过 tcache(例如 0x5000)。C 用以防止与 top chunk 合并。 - 损坏 - 从 A 溢出到 B 的 chunk header,设置 `B->bk = (mchunkptr)(TARGET - 0x10)`。 - 触发 - `free(B)`。在插入时分配器执行 `bck->fd = B`,因此 `*(TARGET) = B`。 - 后续 - 如果你打算继续分配且程序会使用 unsorted bin,预期分配器稍后会把 `*(TARGET) = unsorted_chunks(av)`。这两个值通常都很大,在仅对“大”值做检查的目标中可能足以改变大小/限制语义。 Pseudocode skeleton: ```c // 64-bit glibc 2.35–2.38 style layout (tcache bypass via large sizes) void *A = malloc(0x5000); void *B = malloc(0x5000); void *C = malloc(0x5000); // guard // overflow from A into B’s metadata (prev_size/size/.../bk). You must control B->bk. *(size_t *)((char*)B - 0x8) = (size_t)(TARGET - 0x10); // write fake bk free(B); // triggers *(TARGET) = B (unsorted-bin insertion write) ``` > [!NOTE] > • 如果你无法通过大小绕过 tcache,在释放被破坏的 chunk 之前,先把所选大小的 tcache bin 填满(7 次 free),这样 free 会进入 unsorted。 > • 如果程序因为 unsorted-bin 的检查在下一次分配时立即 abort,请重新确认 `victim->fd` 仍然等于 bin head,并且在第一次写入后你的 `TARGET` 保存的是精确的 `victim` 指针。 ## Unsorted Bin Infoleak Attack 这其实是一个非常基本的概念。unsorted bin 中的 chunks 会包含指针。unsorted bin 中的第一个 chunk 的 **`fd`** 和 **`bk`** 链接实际上会指向 main arena (Glibc) 的一部分。 因此,如果你能 **put a chunk inside a unsorted bin and read it**(use after free),或者 **allocate it again without overwriting at least 1 of the pointers** 然后 **read** 它,你就能获得一个 **Glibc info leak**。 A similar [**attack used in this writeup**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw18_alienVSsamurai/index.html),是滥用一个 4 个 chunks 的结构(A、B、C 和 D — D 只是用来防止与 top chunk 合并),通过在 B 中的 1 字节 null overflow 使 C 显示 B 为未使用。此外,在 B 中修改了 `prev_size` 数据,使得其大小不再是 B 的大小,而是 A+B。 然后释放了 C,并与 A+B 合并(但 B 仍然被使用)。分配了一个大小为 A 的新 chunk,随后把 libc 地址写入到 B,从 B 中 leak 出来。 ## 参考与其他示例 - [**https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#hitcon-training-lab14-magic-heap**](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/unsorted_bin_attack/#hitcon-training-lab14-magic-heap) 目标是用一个大于 4869 的值覆盖一个全局变量,从而获取 flag,并且 PIE 未启用。 - 可以生成任意大小的 chunks,并且存在一个具有所需大小的 heap overflow。 - 攻击开始时创建 3 个 chunks:chunk0 用于滥用 overflow,chunk1 将被 overflow,chunk2 用来阻止 top chunk 合并前面的 chunks。 - 然后释放 chunk1 并对 chunk0 进行 overflow,使得 chunk1 的 `bk` 指向:`bk = magic - 0x10` - 随后,以与 chunk1 相同的大小分配 chunk3,这将触发 unsorted bin attack 并修改该全局变量的值,从而有可能拿到 flag。 - [**https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html**](https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html) merge function 存在漏洞,因为如果传入的两个索引相同,它会对该索引执行 realloc 然后 free,但返回的却是可以继续使用的指向已 free 区域的指针。 因此,**2 chunks are created**:**chunk0** 将与自身合并,chunk1 用于防止与 top chunk 合并。然后对 **merge function** 使用 chunk0 两次,这会导致 use after free。 随后,对索引 2 调用 **`view`** 函数(即 use after free 的 chunk 的索引),这将 **leak a libc address**。 由于该二进制有保护,只允许 malloc 大于 **`global_max_fast`** 的大小,所以不会使用 fastbin,因此采用 unsorted bin attack 来覆盖全局变量 `global_max_fast`。 然后,可以用索引 2(use after free 指针)调用 edit function 并将 `bk` 指针覆盖成指向 `p64(global_max_fast-0x10)`。随后创建新 chunk 时会使用之前被妥协的 free 地址(0x20),这将**触发 unsorted bin attack**,覆盖 `global_max_fast` 为一个很大的值,从而允许在 fast bins 中创建 chunks。 - 之后执行一个 **fast bin attack**: - 首先发现可以在 **`__free_hook`** 位置处理大小为 200 的 fast **chunks**:
gef➤  p &__free_hook
$1 = (void (**)(void *, const void *)) 0x7ff1e9e607a8 <__free_hook>
gef➤  x/60gx 0x7ff1e9e607a8 - 0x59
0x7ff1e9e6074f: 0x0000000000000000      0x0000000000000200
0x7ff1e9e6075f: 0x0000000000000000      0x0000000000000000
0x7ff1e9e6076f :      0x0000000000000000      0x0000000000000000
0x7ff1e9e6077f <_IO_stdfile_2_lock+15>: 0x0000000000000000      0x0000000000000000
- 如果我们设法在该位置获得一个大小为 0x200 的 fast chunk,就可以覆盖一个将被执行的函数指针。 - 为此,创建一个大小为 `0xfc` 的新 chunk,并对该指针调用 merge 函数两次,这样可以在 fast bin 中得到一个指向已 free 的、大小为 `0xfc*2 = 0x1f8` 的指针。 - 然后对该 chunk 调用 edit function,将该 fast bin 的 **`fd`** 地址修改为指向先前的 **`__free_hook`**。 - 随后,创建一个大小为 `0x1f8` 的 chunk 从 fast bin 中取出之前的废弃 chunk,然后再创建另一个大小为 `0x1f8` 的 chunk,以在 **`__free_hook`** 处得到一个 fast bin chunk,并用 **`system`** 的地址覆盖它。 - 最后,释放一个包含字符串 `/bin/sh\x00` 的 chunk(调用 delete 函数),触发指向 system 的 **`__free_hook`**,并以 `/bin/sh\x00` 作为参数执行。 - **CTF** [**https://guyinatuxedo.github.io/33-custom_misc_heap/csaw19_traveller/index.html**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw19_traveller/index.html) 另一个例子,滥用 1 字节 overflow 在 unsorted bin 中合并 chunks 以获取 libc infoleak,然后执行 fast bin attack 覆盖 malloc hook 为 one gadget 地址 - [**Robot Factory. BlackHat MEA CTF 2022**](https://7rocky.github.io/en/ctf/other/blackhat-ctf/robot-factory/) - 只能分配大于 `0x100` 的 chunks。 - 使用 Unsorted Bin attack 覆盖 `global_max_fast`(由于 ASLR,仅有 1/16 成功率,因为需要修改 12 位,但必须修改 16 位)。 - Fast Bin attack 修改全局 chunks 数组。这提供了任意读写原语,从而可以修改 GOT 并将某个函数指向 `system`。 ## 参考资料 - Glibc malloc unsorted-bin integrity checks (example in 2.33 source): https://elixir.bootlin.com/glibc/glibc-2.33/source/malloc/malloc.c - `global_max_fast` and related definitions in modern glibc (2.39): https://elixir.bootlin.com/glibc/glibc-2.39/source/malloc/malloc.c {{#include ../../banners/hacktricks-training.md}}