mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
135 lines
12 KiB
Markdown
135 lines
12 KiB
Markdown
# 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**:
|
||
<pre class="language-c"><code class="lang-c">gef➤ p &__free_hook
|
||
$1 = (void (**)(void *, const void *)) 0x7ff1e9e607a8 <__free_hook>
|
||
gef➤ x/60gx 0x7ff1e9e607a8 - 0x59
|
||
<strong>0x7ff1e9e6074f: 0x0000000000000000 0x0000000000000200
|
||
</strong>0x7ff1e9e6075f: 0x0000000000000000 0x0000000000000000
|
||
0x7ff1e9e6076f <list_all_lock+15>: 0x0000000000000000 0x0000000000000000
|
||
0x7ff1e9e6077f <_IO_stdfile_2_lock+15>: 0x0000000000000000 0x0000000000000000
|
||
</code></pre>
|
||
- 如果我们设法在该位置获得一个大小为 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}}
|