Unsorted Bin Attack
Reading time: 15 minutes
tip
学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
基本信息
有关什么是 unsorted bin 的更多信息请查看此页面:
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 或 fast bin attack。
tip
T> 看一下 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 的代码对此解释得很好,不过如果你修改 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](Fast Bin Attack) 做设置,或为之后的 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 的经典技巧不再干净可行,并且可能会破坏相邻的分配器状态。优先使用其他策略。
- 在 glibc 2.39+ 中,
最小利用流程(modern glibc)
目标:使用 unsorted‑bin 插入原语,实现一次将 heap 指针写入任意地址的任意写,且不崩溃。
- 布局/预处理
- 分配 A、B、C,大小足够大以绕过 tcache(例如 0x5000)。C 用以防止与 top chunk 合并。
- 损坏
- 从 A 溢出到 B 的 chunk header,设置
B->bk = (mchunkptr)(TARGET - 0x10)
。
- 从 A 溢出到 B 的 chunk header,设置
- 触发
free(B)
。在插入时分配器执行bck->fd = B
,因此*(TARGET) = B
。
- 后续
- 如果你打算继续分配且程序会使用 unsorted bin,预期分配器稍后会把
*(TARGET) = unsorted_chunks(av)
。这两个值通常都很大,在仅对“大”值做检查的目标中可能足以改变大小/限制语义。
- 如果你打算继续分配且程序会使用 unsorted bin,预期分配器稍后会把
Pseudocode skeleton:
// 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,是滥用一个 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
目标是用一个大于 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
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 <list_all_lock+15>: 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
另一个例子,滥用 1 字节 overflow 在 unsorted bin 中合并 chunks 以获取 libc infoleak,然后执行 fast bin attack 覆盖 malloc hook 为 one gadget 地址 - Robot Factory. BlackHat MEA CTF 2022
- 只能分配大于
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
tip
学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。