Translated ['src/binary-exploitation/libc-heap/unsorted-bin-attack.md']

This commit is contained in:
Translator 2025-08-28 16:53:26 +00:00
parent bc7c255265
commit 3bcdcecbd3

View File

@ -2,55 +2,109 @@
{{#include ../../banners/hacktricks-training.md}}
## Basic Information
## 基本信息
有关 unsorted bin 是什么的更多信息请查看此页面:
有关未排序的 bin 的更多信息,请查看此页面:
{{#ref}}
bins-and-memory-allocations.md
{{#endref}}
未排序列表能够将地址写入 `unsorted_chunks (av)` 的块的 `bk` 地址。因此,如果攻击者能够**修改未排序 bin 中块的 `bk` 指针的地址**,他就能够**将该地址写入任意地址**,这可能有助于泄露 Glibc 地址或绕过某些防御
Unsorted 列表能够在 chunk 的 `bk` 地址写入 `unsorted_chunks (av)` 的地址。因此,如果攻击者能够**修改位于 unsorted bin 中的 chunk 的 `bk` 指针地址**,他就可能**把该地址写入任意地址**,这对于泄露 Glibc 地址或绕过某些防护很有用
因此,基本上,这种攻击允许**在任意地址设置一个大数字**。这个大数字是一个地址,可能是堆地址或 Glibc 地址。一个典型的目标是**`global_max_fast`**,以允许创建更大尺寸的快速 bin并从未排序 bin 攻击转到快速 bin 攻击)。
所以,基本上,这个攻击允许在任意地址**设置一个很大的数值**。这个大数值是一个地址,可以是堆地址或 Glibc 地址。传统的目标是 **`global_max_fast`**,以便允许创建更大尺寸的 fast bin并从 unsorted bin 攻击转为 fast bin 攻击)。
- 现代注glibc ≥ 2.39`global_max_fast` 变成了一个 8 位全局变量。盲目通过 unsorted-bin 写入指针到此处会破坏相邻的 libc 数据,并且不再可靠地提升 fastbin 限制。在针对 glibc 2.39+ 时,优先选择其他目标或其他原语。见下文“现代限制”并考虑在得到稳定原语后结合其他技术如 [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) 中提供的示例,并使用 0x4000 和 0x5000 代替 0x400 和 0x500 作为块大小(以避免 Tcache可以看到**如今**错误**`malloc(): unsorted double linked list corrupted`**被触发
> T> aking 查看由 [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 大小从 0x400 和 0x500 改为 0x4000 和 0x5000以避免 Tcache可以看到 **现在** 会触发错误 **`malloc(): unsorted double linked list corrupted`**。
>
> 因此,这种未排序 bin 攻击现在(除了其他检查)还要求能够修复双向链表,以便绕过 `victim->bk->fd == victim``victim->fd == av (arena)`,这意味着我们想要写入的地址必须在其 `fd` 位置具有假块的地址,并且假块`fd` 指向 arena。
> 因此,这种 unsorted bin 攻击现在(以及由于其他检查)也要求能够修复双链表,从而绕过检查 `victim->bk->fd == victim``victim->fd == av (arena)`,这意味着我们想写入的地址的 `fd` 位置必须包含伪造 chunk 的地址,且该伪造 chunk `fd` 指向 arena。
> [!CAUTION]
> 请注意,这种攻击会破坏未排序 bin因此小和大也会。因此我们现在只能**使用来自快速 bin 的分配**(更复杂的程序可能会进行其他分配并崩溃),并且要触发这一点,我们必须**分配相同的大小,否则程序将崩溃。**
> 注意此攻击会破坏 unsorted bin因此也会影响 small 和 large。所以我们现在**只能使用来自 fast bin 的分配**(更复杂的程序可能做其他分配并崩溃),并且要触发利用我们必须**分配相同大小否则程序会崩溃。**
>
> 请注意,覆盖**`global_max_fast`**可能在这种情况下有所帮助,前提是快速 bin 能够处理所有其他分配,直到利用完成。
> 注意覆盖 **`global_max_fast`** 在这种情况下可能有帮助,假设 fast bin 能够处理所有其他分配直到利用完成。
来自 [**guyinatuxedo**](https://guyinatuxedo.github.io/31-unsortedbin_attack/unsorted_explanation/index.html) 的代码解释得很好,尽管如果您修改 malloc 以分配足够大的内存以避免 Tcache您会看到之前提到的错误出现阻止此技术**`malloc(): unsorted double linked list corrupted`**
来自 [**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 列表头部。
- 在插入过程中,分配器执行 `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在 64 位默认为 ≥ 0x410的大小请求或者
- 填满相应的 tcache bin7 个条目),以使额外的 free 到达全局 bins或者
- 如果能控制环境,禁用 tcache例如设置 GLIBC_TUNABLES glibc.malloc.tcache_count=0
- 对 unsorted 列表的完整性检查:在下一次检查 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 头)。选择那些只需要强制写入一个大的非零值就有用的目标。
- 现代利用中的典型稳定目标
- 应用或全局状态将“较大”值视为标志/限制。
- 间接原语(例如,为随后的一次 [fast bin attack]({{#ref}}fast-bin-attack.md{{#endref}}) 做设置,或为后续的 writewhatwhere 做准备)。
- 避免在新版 glibc 上使用 `__malloc_hook`/`__free_hook`:它们在 2.34 中被移除。对于 ≥ 2.39 避免使用 `global_max_fast`(见下一注)。
- 关于近期 glibc 的 `global_max_fast`
- 在 glibc 2.39+ 上,`global_max_fast` 是一个 8 位全局变量。把堆指针写入它(以扩大 fastbins的经典技巧不再能可靠工作并且很可能破坏相邻的分配器状态。优先考虑其他策略。
## 最小利用流程(现代 glibc
目标:使用 unsortedbin 插入原语,实现一次将堆指针写入任意地址的单次任意写,且不导致崩溃。
- 布局/修整
- 分配 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]
> • 如果不能通过 size 绕过 tcache请在释放被破坏的 chunk 之前先把所选 size 的 tcache bin 填满(释放 7 次),这样此次 free 会进入 unsorted。
> • 如果程序在下次分配时由于 unsorted-bin 校验直接 abort请重新检查在第一次写入后 `victim->fd` 是否仍等于 bin head以及你的 `TARGET` 在第一次写入后是否保存了精确的 `victim` 指针。
## Unsorted Bin Infoleak Attack
这实际上是一个非常基本的概念。未排序 bin 中的块将具有指针。未排序 bin 中的第一个块实际上将具有**`fd`**和**`bk`**链接**指向主 arenaGlibc的一部分**。\
因此,如果您能够**将一个块放入未排序 bin 并读取它**(使用后释放)或**在不覆盖至少 1 个指针的情况下再次分配它**,然后**读取**它,您就可以获得**Glibc 信息泄露**。
这实际上是一个非常基础的概念。unsorted bin 中的 chunks 会包含指针。unsorted bin 中的第一个 chunk 的 **`fd`** 和 **`bk`** 链接实际上会指向 main arena (Glibc) 的某一部分。\
因此,如果你能把一个 chunk 放入 unsorted bin 并读取它use after free或者再次分配它且在重新分配时至少不覆盖其中一个指针以便随后读取那么你就可以得到一个 **Glibc info leak**
在此 [**写作中使用的类似攻击**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw18_alienVSsamurai/index.html)中,利用了一个 4 块结构A、B、C 和 D - D 仅用于防止与顶部块合并),因此在 B 中使用了一个空字节溢出,使 C 指示 B 未使用。此外,在 B 中修改了 `prev_size` 数据,因此大小不再是 B 的大小,而是 A+B。\
然后 C 被释放,并与 A+B 合并(但 B 仍在使用中)。分配了一个大小为 A 的新块,然后将泄露的 libc 地址写入 B从中泄露了它们。
一个类似的[**attack used in this writeup**](https://guyinatuxedo.github.io/33-custom_misc_heap/csaw18_alienVSsamurai/index.html) 是滥用 4 个 chunk 的结构A、B、C 和 D —— D 仅用于防止与 top chunk 合并),通过在 B 中发生 1 字节的 null byte overflow 让 C 表示 B 未被使用。同时在 B 中修改 `prev_size` 数据,使得 size 不再是 B 的大小而是 A+B。\
然后释放 C使其与 A+B 合并(但 B 仍在使用)。之后分配一个大小为 A 的新 chunk然后把泄露出的 libc 地址写入 B从 B 中泄露出来
## References & Other examples
- [**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 的值覆盖全局变量,以便能够获取标志,并且未启用 PIE。
- 可以生成任意大小的块,并且存在所需大小的堆溢出。
- 攻击开始创建 3 个块chunk0 用于利用溢出chunk1 用于被溢出chunk2 以防止顶部块合并之前的块。
- 然后chunk1 被释放chunk0 被溢出到 chunk1 的 `bk` 指针指向:`bk = magic - 0x10`
- 然后,分配一个与 chunk1 相同大小的 chunk3这将触发未排序 bin 攻击并修改全局变量的值,从而使获取标志成为可能。
- [**https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html**](https://guyinatuxedo.github.io/31-unsortedbin_attack/0ctf16_zerostorage/index.html)
- 合并函数是脆弱的,因为如果传递的两个索引相同,它将重新分配并释放它,但返回指向该释放区域的指针,可以使用。
- 因此,**创建了 2 个块****chunk0** 将与自身合并chunk1 以防止与顶部块合并。然后,**合并函数被调用两次**,这将导致使用后释放。
- 然后,**`view`** 函数被调用,索引为 2即使用后释放块的索引这将**泄露一个 libc 地址**。
- 由于二进制文件具有保护措施,仅允许 malloc 大于 **`global_max_fast`** 的大小,因此不使用快速 bin将使用未排序 bin 攻击来覆盖全局变量 `global_max_fast`
- 然后,可以调用编辑函数,索引为 2使用后释放指针并覆盖 `bk` 指针以指向 `p64(global_max_fast-0x10)`。然后创建一个新块将使用之前被破坏的释放地址0x20将**触发未排序 bin 攻击**,覆盖 `global_max_fast`,这是一个非常大的值,现在允许在快速 bins 中创建块。
- 现在执行**快速 bin 攻击**
- 首先发现可以在**`__free_hook`**位置处理大小为 200 的快速**块**
- <pre class="language-c"><code class="lang-c">gef➤ p &__free_hook
- [**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 用于滥用 overflowchunk1 将被 overflowchunk2 用于防止 top chunk 合并之前的 chunks。
之后释放 chunk1 并 overflow chunk0使得 chunk1 的 `bk` 指向:`bk = magic - 0x10`。然后分配 chunk3与 chunk1 大小相同),这会触发 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 函数存在漏洞:如果传入的两个索引相同,它会在该块上做 realloc 然后 free但返回一个指向已释放区域的指针该指针可被利用。
因此创建了 2 个 chunks**chunk0**(将与自身合并)和 chunk1防止与 top chunk 合并)。然后对 chunk0 调用 merge 两次,导致 use after free。接着用 index 2 调用 `view`(即 use-after-free 的索引),这会泄露 libc 地址。由于二进制只能 malloc 大于 `global_max_fast` 的大小,因此不会使用 fastbin转而使用 unsorted bin attack 去覆盖全局变量 `global_max_fast`
然后可以对 index 2use-after-free 指针)调用 edit`bk` 指针改写为 `p64(global_max_fast-0x10)`。创建新 chunk 时会使用之前被污染的 free 地址0x20这会 **触发 unsorted bin attack** 覆盖 `global_max_fast` 为一个很大的值,从而允许创建 fast bin 的 chunks。
接下来执行 **fast bin attack**
首先发现可以在 `__free_hook` 位置使用大小为 0x200 的 fast chunk
<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
@ -58,16 +112,17 @@ gef➤ x/60gx 0x7ff1e9e607a8 - 0x59
0x7ff1e9e6076f <list_all_lock+15>: 0x0000000000000000 0x0000000000000000
0x7ff1e9e6077f <_IO_stdfile_2_lock+15>: 0x0000000000000000 0x0000000000000000
</code></pre>
- 如果我们设法在此位置获得大小为 0x200 的快速块,则可以覆盖将被执行的函数指针。
- 为此,创建一个大小为 `0xfc` 的新块,并调用合并函数两次,借此我们获得指向大小为 `0xfc*2 = 0x1f8` 的释放块的指针,在快速 bin 中。
- 然后,在此块中调用编辑函数以修改此快速 bin 的**`fd`**地址,使其指向之前的**`__free_hook`**函数。
- 然后,创建一个大小为 `0x1f8` 的块,以从快速 bin 中检索之前无用的块,因此创建另一个大小为 `0x1f8` 的块,以获取在**`__free_hook`**中获取的快速 bin 块,该块被覆盖为**`system`** 函数的地址。
- 最后,释放一个包含字符串 `/bin/sh\x00` 的块,调用删除函数,触发**`__free_hook`**函数,该函数指向 system参数为 `/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)
- 另一个示例是利用 1B 溢出以合并未排序 bin 中的块并获取 libc 信息泄露,然后执行快速 bin 攻击以用一个 gadget 地址覆盖 malloc hook。
- [**Robot Factory. BlackHat MEA CTF 2022**](https://7rocky.github.io/en/ctf/other/blackhat-ctf/robot-factory/)
- 我们只能分配大于 `0x100` 的块。
- 使用未排序 bin 攻击覆盖 `global_max_fast`(由于 ASLR成功率为 1/16因为我们需要修改 12 位,但必须修改 16 位)。
- 快速 bin 攻击以修改全局块数组。这提供了一个任意读/写原语,允许修改 GOT 并将某些函数指向 `system`
如果能在该位置获得一个大小为 0x200 的 fast chunk就可以覆盖一个将被执行的函数指针。
为此,创建一个大小为 `0xfc` 的新 chunk并对该指针调用 merge 两次,这样就得到一个大小为 `0xfc*2 = 0x1f8` 的已释放 chunk 放入 fast bin。然后对该 chunk 调用 edit将该 fast bin 的 `fd` 指向先前的 `__free_hook`。随后分配一个大小为 `0x1f8` 的 chunk 从 fast bin 取回之前的 useless chunk再分配另一个 `0x1f8` 的 chunk 以在 `__free_hook` 位置得到一个 fast bin chunk并将其覆盖为 `system` 的地址。最后创建包含字符串 `/bin/sh\x00` 的 chunk 并释放(调用 delete触发 `__free_hook`(现在指向 system`/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)
另一个示例:滥用 1B overflow 将 chunks 合并到 unsorted bin从而获得 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需要修改 12 位但实际必须修改 16 位,成功率约为 1/16
使用 Fast Bin attack 修改一个全局的 chunk 数组,从而得到任意读写原语,允许修改 GOT 并把某个函数指向 `system`
## References
- 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}}