1. 三种attack攻击方式(总结)
Linux Pwn(堆漏洞利用)中针对GNU C 库(glibc)内存分配器(ptmalloc)的三种经典攻击手段:Unsorted Bin attack、fastbin attack、Large bin attack。它们的核心区别在于利用的内存链表机制不同、对漏洞版本的要求不同,以及最终达到的攻击效果不同:
| 名称/属性 | Bin链表类型 | 漏洞触发原理 | 造成效果 | 现代 glibc (2.31+) 有效性 |
| Fastbin attack | 单向循环链表(LIFO) | 劫持fd指针,或制造Double Free循环链表 | 任意地址分配,可以直接改写目标地址的值 | 纯原生利用已失效(被Tcache拦截且引入指针加密) |
| Unsorted Bin Attack | 双向循环链表(FIFO) | 篡改bk指针,利用脱链(Unlink)时的写入操作 | 任意地址写一个大数值(Libc相关的地址),常用于修改控制变量 | 已失效(2.29引入了强力的bck->fd != victim校验) |
| Large Bin Attack | 带跳表(Skip List)的双向循环链表 | 篡改bk_nextsize或bk指针,利用大块内存分类插入时的指针写入 | 任意地址写一个堆地址,现代Pwn中用于绕过限制或改写风控变量 | 依然有效(尽管2.30增加了部分校验,但特定利用链仍存活) |
2. Unsorted Bin attack
Unsorted Bin Attack是Linux Pwn(堆漏洞利用)中一种经典的、针对GNU C库(glibc)内存分配器的进阶利用技术。它的核心目标是实现任意地址写一个大数值,通常用于改写全局变量、修改循环控制条件、或者将global_max_fast变量篡改为一个极大的值,从而将更大尺寸的内存块强行伪装成Fastbin,为进一步的任意代码执行铺平道路。
2.1 核心原理回顾
glibc的ptmalloc管理中,Unsorted Bin是一个双向循环链表,有且仅有这一个链表:
- 缓冲机制:当一个大于Fastbin范围或者无法与Fastbin合并的堆块(Chunk)被释放时,它不会立刻回到传统的Small Bin或Large Bin中,而是先进入Unsorted Bin作为缓冲区;
- 指针结构:每个在Unsorted Bin中的空闲Chunk,都包含两个关键指针:
- fd:指向链表中的下一个Chunk;
- bk:指向链表中的上一个Chunk;
当一个新的内存请求产生且Fastbin/Small Bin无法满足时,分配器(ptmalloc)就会遍历Unsorted Bin链表:
- 如果遍历到的Chunk大小正好合适,则直接分配;
- 如果不合适,分配器(ptmalloc)就会把这个Chunk放到对应的Small Bin或Large Bin中;
2.2 攻击机制
这种攻击手段的核心在于触发Unsorted Bin链表的脱链(Unlink)操作;当分配器发现Unsorted Bin中的某个Chunk被重新申请使用,或者需要被移动到其他Bin时,会执行以下标准的双向链表脱链操作:
// 将当前Chunk (victim) 从链表中移除的标准C代码
victim->fd->bk = victim->bk;
victim->bk->fd = victim->fd;
如果攻击者利用堆溢出或者UAF漏洞篡改了Unsorted Bin中victim Chunk的bk指针,则会触发以下的操作流程:
- 攻击者将victim->bk修改为 (目标内存地址 – 2 * 属性指针大小) (在64位系统中通常是 目标地址 – 0x10);
- 当触发脱链操作时,分配器执行 victim->bk->fd = victim->fd;
- 此时,victim->bk->fd对应的实际物理地址正好就是攻击者指定的目标内存地址;
- 最终,目标内存地址被强行写入了victim->fd的值(即Unsorted Bin链表头部的地址main_arena相关的libc地址,这是一个非常大的正整数);
从glibc 2.29开始,移出Unsorted Bin时增加了bk指针完整性校验:
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks");
因为攻击者篡改了victim->bk(即bck),其bck->fd(目标地址处的伪造指针)几乎不可能正好等于victim的地址。因此,这行检查会立刻触发崩溃,宣告传统的Unsorted Bin Attack彻底死亡。
还有,从glibc2.26版本开始,大内存释放时可能会先落入Tcache,减少了Chunk直接进入Unsorted Bin的机会,增加了利用步骤。
虽然无法再通过它实现任意写,但由于Unsorted Bin的fd/bk默认指向main_arena,它依然是绕过ASLR(地址空间配置随机化)并泄露Libc基地址的最核心手段。
3. fastbin attack
Fastbin Attack是Linux Pwn(堆漏洞利用)中一种经典的堆溢出(Heap Overflow)高级利用技术。它专门针对GNU C库(glibc)内存分配器中的Fastbins机制,通过篡改内存指针,最终达到任意内存地址写的目的,甚至可以直接劫持程序控制流。
3.1 核心原理回顾
在glibc内存管理中,为了提高性能,大小通常在16到80字节(不同架构和版本略有差异)之间的小内存块(Chunk)在被释放(free)后,不会立刻合并,而是会被放入Fastbins链表中:
- 单向链表:Fastbins是一个单向链表,采用 后进先出 (LIFO) 的原则;
- 空闲指针 (fd):每个被释放的Chunk的数据区头部,会存放一个fd(Forward Pointer)指针,指向链表中的下一个空闲Chunk;
- 不检查合并:为了追求极致的速度,fastbin在释放时不会清除fd指针,也不会向前或向后合并相邻的空闲块;
3.2 攻击机制
黑客通常利用程序中的漏洞(如UAF/Use-After-Free、Double Free或Heap Overflow)来操纵这个单向链表:Fastbin Double Free(双重释放)和Fastbin Arbitrary Write(任意地址写)。
3.2.1 Fastbin Double Free(双重释放)
这是最著名的Fastbin漏洞利用方式。由于旧版本glibc的Fastbin只检查当前释放的Chunk是否与链表头部的Chunk相同,攻击者可以通过交替释放来绕过检查:
- 1. 释放Chunk A:Fastbin链表:A;
- 2. 释放Chunk B:Fastbin链表:B->A;
- 3. 再次释放Chunk A:Fastbin链表:A->B->A,构成循环链表;
- 4. 此时申请回Chunk A,修改其fd指针指向一个恶意的伪造地址;
- 5. 此时的链表结构形式:B->A->恶意地址;
- 6. 连续申请3次内存后,第4次申请直接分配该恶意地址,实现控制流劫持;
3.2.2 Fastbin Arbitrary Write(任意地址写)
利用堆溢出漏洞,直接覆盖处于Fastbin链表中的某个空闲Chunk的fd指针。将其强行修改为目标受害地址(如__malloc_hook、__free_hook或Got表地址),当程序后续调用malloc时,就能在目标地址写入恶意数据(如system函数地址或Shellcode)。
4. large bin attack
Large Bin Attack是现代Linux Pwn(堆漏洞利用)中极具代表性的高级技术。随着GNU C库(glibc)在2.29和2.32版本中陆续封杀了Unsorted Bin Attack和纯原生Fastbin Attack,Large Bin Attack 成为了现代高版本glibc(如glibc2.31、2.35甚至2.39+)环境下,少数依然存活且极其核心的“任意地址写堆地址”的破局手段。
4.1 核心原理回顾
glibc的ptmalloc内存管理器中,当释放的内存块(Chunk)大于1024字节(在64位系统下)且无法被Tcache或Fastbin接收时,它们最终会被分流到Large Bin中。Large Bin的结构比其他的Bin都要复杂:
- 1. 双向链表:它使用fd(前驱)和bk(后继)两个指针维护一个双向循环链表;
- 2. 大小分级:一个Large Bin索引里包含的是一个大小在一定范围内的内存块集合(例如1024-1080字节都在同一个Bin中),并且链表中的Chunk是按大小从大到小降序排列的;
- 3. 跳表机制(Skip List):为了在分配时快速跳过大小相同的Chunk,Large Bin额外引入了两个指针:
- fd_nextsize:指向下一个大小不同的更小的空闲Chunk;
- bk_nextsize:指向下一个大小不同的更大的空闲Chunk;
4.2 攻击机制
Large Bin Attack的核心触发点在于:当一个处于Unsorted Bin中的新Chunk,被放入到一个已经有数据的Large Bin的过程中。
当分配器(ptmalloc)遍历Unsorted Bin并试图将一个新Chunk(假设为A)归位到对应的Large Bin时,它需要将A按照大小插入到合适的位置,并修改相邻节点的指针;
如果攻击者利用UAF(Use-After-Free) 或堆溢出漏洞,篡改了已经在Large Bin中的某个空闲Chunk(假设为B)的指针:
- 方法一:篡改B->bk_nextsize
- 攻击者将B->bk_nextsize修改为目标地址-0x20,使其fd_nextsize刚好对准目标地址(根据不同架构,地址也不同);
- 当新Chunk A插入时,由于其大小小于B,分配器会顺着跳表找到末尾,并执行跳表的插入赋值;
- 此时,fwd->bk_nextsize->fd_nextsize = A; 实际执行的操作就是:将新Chunk A的实际堆地址,精准写入到攻击者指定的目标地址中;
A->bk_nextsize = fwd->bk_nextsize; // fwd 即 B
A->fd_nextsize = fwd;
fwd->bk_nextsize->fd_nextsize = A; // 核心触发点!
fwd->bk_nextsize = A;
- 方法二:篡改B->bk
同样的逻辑,如果攻击者修改B->bk为目标地址 – 0x10,在双向链表标准插入时执行:
A->bk = bck; // bck即B->bk
A->fd = fwd;
bck->fd = A; // 核心触发点
同样也能将新Chunk A的堆地址写入到目标地址。
4.3 现代Pwn的Large Bin Attack攻击手段
由于Large Bin Attack过于强大,glibc核心团队在glibc 2.30版本中对其引入了安全校验(即针对bk的完整性检查):
if (__glibc_unlikely (bck->fd != fwd))
malloc_printerr ("malloc(): largebin double free or corruption (next)");
虽然针对bk链表的检查变严了,但在特定大小序列的插入逻辑中,针对bk_nextsize指针的篡改依然可以完美绕过检查,更高版本2.3.5+中,攻击者依然可以向任意地址写入一个当前堆的指针。
如果只能往目标地址写一个“堆地址”,那么攻击者可以利用:
- 1. 劫持_IO_FILE结构体(FSOP/IO攻击):现代glibc版本删除了__malloc_hook 和__free_hook,攻击者的目标转向了_IO_list_all结构或虚表。利用Large Bin Attack,可以将一个伪造的FILE 结构体(位于堆上)的地址,强行写入到Libc的关键全局变量中,配合后续的exit或返回触发system(“/bin/sh”);
- 2. 改写Tcache结构体的count或entries:Tcache的管理结构体通常放在堆的头部。利用Large Bin Attack往Tcache结构体中写入一个堆地址,可以欺骗Tcache,让它以为某个地址有空闲块,从而在下一次malloc(Tcache)时直接实现任意地址分配;
- 3. 绕过指针混淆:由于Fastbin/Tcache的指针被异或加密,攻击者需要知道堆地址基头。Large Bin Attack可以直接在某个没有加密的全局变量处“白嫖”写入一个未加密的堆地址,从而借此泄露堆基址(Heap Base)。
1. 一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。