☰
03|Unsorted Bin Attack:改一个 bk,让 glibc 帮你写 libc 地址
2026/10/28 10:03:40 网站建设 项目流程

Unsorted Bin Attack:改一个 bk,让 glibc 帮你写 libc 地址

第一次看这个技巧的时候,我没想通一件事:凭什么我改一个指针,就能往任意地址写东西?

后来在 magicheap 上手动走了一遍才明白——不是我在写,是 glibc 在写,我只是把它要写的地方挪了一下。

这篇就按我当时踩坑的顺序写。

先说清楚前提

这个技巧能用,得凑齐几件事。

块得真的进 unsorted bin。fastbin 范围(≤0x80)进不去,2.26 以后 tcache 范围内(≤0x410)也进不去,这两条先排除。

块后面得有一块活着的块挡着。原因下面讲,我在这上面白折腾过一次。

你的漏洞得能改到这个块的 bk。UAF 也行,相邻块溢出也行,反正得能碰。

最后,glibc 不能太新。2.29 之后这招就废了,具体在文末。

它为什么会进 unsorted bin

malloc 返回的不是一块裸内存,前面有 0x10 的头:

+0x00 prev_size +0x08 size +0x10 fd +0x18 bk +0x20 用户数据(malloc 返回的就是这里)

free 的时候,glibc 会按顺序判断:太小进 fastbin,tcache 没满进 tcache,能跟邻居合并就先合并,合并完挨着 top 就并进 top。四条都不满足,才扔进 unsorted bin。

所以「块后面要有东西挡着」这条,是为了不让它并进 top。我第一次写 demo 就漏了这个,free 完去看 bins,空的——因为全并进 top 了。

摘链的时候发生了什么

下次 malloc,_int_malloc会先把 unsorted 清一遍。核心就几行:

while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) { bck = victim->bk; if (size == nb) { /* 尺寸正好够 */ unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av); /* ← 就这一行 */ return victim; } }

unsorted_chunks(av)是 bin 的头,它不长在堆上,而是在 libc 的 main_arena 里,2.23 上等于main_arena + 0x58。

所以最后那行干了这么件事:往bck + 0x10写一个 libc 地址。

于是就能算 offset 了

fd 在 chunk 里偏移 0x10,所以:

写入位置 = bck + 0x10 想让写入落在 target → bck = target - 0x10

magicheap 里 magic 在 0x4040f0,所以 bk 要写成 0x4040e0。

注意改的是 bk,不是 fd——代码读的是victim->bk当作 bck。

能写到哪,和能写什么,是两回事

这是我最开始搞混的地方。

写到哪:你说了算。
写什么:unsorted_chunks(av),一个常量,你说了不算。

所以它不是「任意地址写任意值」。它只能把某个位置变成一个很大的 libc 地址,写不了system那种精确值。

很多 writeup 管这个叫 Write-Anything-Anywhere,那是错的。

一个能跑的 demo

#include <stdio.h> #include <stdlib.h> unsigned long target_var = 0; int main(void) { unsigned long *p = malloc(0x400); unsigned long *guard = malloc(0x100); /* 没有它,下面的 free 会并进 top */ (void)guard; free(p); printf("fd = %#lx bk = %#lx\n", p[0], p[1]); p[1] = (unsigned long)&target_var - 0x10; malloc(0x400); /* 尺寸要对上,才走精确匹配 */ printf("target_var = %#lx\n", target_var); return 0; }

2.23 上跑,会看到 fd 和 bk 都是0x7ffff7dd1b78这种地址(每次不一样),最后target_var也变成这个值。

2.27 上要改尺寸。0x400 请求拿到的是 0x410 的 chunk,还在 tcache 范围里,free 完直接进了 tcache,unsorted 里什么都没有。换成 0x500 才行——这个我试了两次才反应过来。

别和 unlink 搞混

网上经常把这段代码叫 unlink,其实不是。

真正的 unlink 是个宏,在合并相邻块时调用,它有两处写,还有完整性检查:

if (FD->bk != P || BK->fd != P) malloc_printerr ("corrupted double-linked list");

而 unsorted 这里是把「victim 的 fd 一定是 head」写死了,只留一处写 + 一处 head 更新,2.23 上没有任何检查。

也正是这个差别,决定了两件事:

unlink 的写入值来自P的fd/bk,你能控制,所以能写任意值;
unsorted 摘链的写入值是 head 的地址,你控制不了,所以只能写一个大数。

2.29 把检查补进这段内联代码之后,unsorted bin attack 就没了。报错是:

malloc(): corrupted unsorted chunks 3

不是corrupted double-linked list,那是 unlink 宏的。

版本

glibc

情况

≤ 2.28

可用

2.29

加校验,触发即退出

2.31 及以后

只能拿来泄 libc,写任意地址不行了

2.34 之后__free_hook也被删了,就算堆能打通,落点也没了。

最后

这个技巧本身价值在「改变量」:判据、标志位、size 字段。

真要拿 shell,还得靠 fastbin 或 tcache 接一刀——它们能写任意值,只是需要先有入口。

---
📌 相关阅读
· 上一篇:《02|Fastbin Attack:fasttop 只防连着两次,A-B-A 就能绕》
· 下一篇:《04|Large Bin Attack:把一个堆地址写到任意位置》
· 实战配套:《Unsorted Bin Attack 实战:把 main_arena 写进全局变量(magicheap)》

全部 exp 与 gdb 脚本:<GitHub 链接>

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询