hacktricks/src/binary-exploitation/libc-heap/unsorted-bin-attack.md

135 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 上可靠地使用 unsortedbin 写入:
- Tcache 干扰:对于落入 tcache 的大小free 会被导向 tcache 而不会触及 unsorted bin。要么
- 使用大于 MAX_TCACHE_SIZE 的请求(在 64bit 上默认 ≥ 0x410要么
- 填满对应的 tcache bin7 个条目),使更多的 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
目标:使用 unsortedbin 插入原语,实现一次将 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.352.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 Bs 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 个 chunkschunk0 用于滥用 overflowchunk1 将被 overflowchunk2 用来阻止 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`
然后,可以用索引 2use 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}}