Smallbin Attack
参考资料:https://www.yuque.com/cyberangel/rg9gdm/nd4xd5
什么是 Smallbin Attack:
Small Bin Attack通常指 House of Lore 是一种基于 glibc Small Bin 双向链表机制的堆漏洞利用技术,核心目标是劫持 malloc 流程,分配到攻击者指定地址的伪造堆块,从而实现任意地址读写。
House of Lore 的漏洞原理:
Small Bin 是 glibc 用于管理特定大小(通常 0x20–0x400 区间)空闲堆块的双向循环链表。malloc 时,会从对应 Small Bin 的尾部 ( bk 方向 ) 取出堆块分配。
攻击利用的关键是:
- 篡改 Small Bin 中合法堆块(victim)的 bk 指针,使其指向攻击者构造的 fake chunk。
- 精心伪造 fake chunk 的fd/bk指针,绕过 glibc 的双向链表完整性检查:victim->bk->fd == victim
- 当下次 malloc 该大小的堆块时,系统会将 fake chunk 从链表中取出并返回,从而获得对目标地址的控制权。
利用条件
- House Of Lore 存在于 glibc 2.31 版本之下
- 存在堆溢出 / 任意写漏洞,能够修改 Small Bin 中堆块的 bk 指针。
- 能够在内存中构造并控制 fake chunk 的 fd / bk 指针。
- 目标 fake chunk 所在内存区域可写(用于存放伪造的堆结构)。
- 程序会再次 malloc 对应大小的 Small Bin 堆块,触发分配流程。
House of Lore 的一般攻击流程:
- 准备环境:分配并释放若干堆块,使目标大小的堆块进入 Small Bin 链表。
- 篡改指针:通过漏洞修改 Small Bin 中最后一个堆块(victim)的
bk = fake_chunk1。 - 伪造堆块:
fake_chunk1->fd = victim(满足victim->bk->fd == victim检查)。fake_chunk1->bk = fake_chunk2(构造链式结构)。fake_chunk2->fd = fake_chunk1(进一步绕过检查)。
- 触发分配:调用 malloc 请求对应大小,系统从 Small Bin 尾部取出 fake_chunk1 并返回,完成劫持
House of Lore 与 Unlink Attack 的区别
- Small Bin Attack(House of Lore):利用 malloc 分配流程,目标是分配到 fake chunk,实现任意地址分配。
- Unlink Attack:利用 free 合并流程,目标是修改任意地址内存(通常是指针),实现任意地址写。
高版本下的 Lore(Tcache Stashing Unlink)
在 Glibc 2.27+ 引入 Tcache 后,经典 House of Lore 受限制。衍生出 Tcache Stashing Unlink
- 利用 calloc 绕开 Tcache,直接从 Small Bin 分配。
- 触发 Small Bin 向未满 Tcache 迁移的机制,将 fake chunk 挂入 Tcache,最终分配到目标地址。
Smallbin 的执行机制:
1 | //malloc.c中第3405-3434行 |
针对于 House Of Lore 漏洞的构造,我们主要看针对于 smallbin 的检查:
1 | else //第二种情况,smallbin中存在空闲的 chunk |
简单来说,我们想要走到这里需要满足两个基本条件:
- 所申请的 chunk 大小需要在 Smallbin 的范围之内。
- smallbin 中存在空闲的 chunk
什么是 victim?
- Small Bin 链表就像一个双向循环的「空闲堆块队列」,里面全是系统认可的合法空闲堆块;
- 当你调用 malloc(size) 时,glibc会找到对应大小的 Small Bin,从链表尾部找一个堆块分配,这个被选中的合法堆块就是我们所说的 victim
- bck 是 victim->bk 的简写(bk = back,指双向链表中前一个节点),也就是 victim 在 Small Bin 链表中的前驱节点。
smallbin 的基本性质:
Small Bin 是双向循环链表,满足:
- 合法堆块 A 的 fd 指向后继堆块 B ==> A->fd = B
- 合法堆块 B 的 bk 指向前驱堆块 A ==> B->bk = A。因此必然满足 A->bk->fd = A、B->fd->bk = B
比如 Small Bin 链表是:bin_head <-> chunk1 <-> chunk2 <-> bin_head(循环):
- 若 victim = chunk2,则 bck = chunk2->bk = chunk1
- 检查 bck->fd == victim ==> chunk1->fd == chunk2,结果为真,链表合法。

House Of Lore 利用方式:
对于 House Of Lore 我们来看一个例子,这个例子非常不错:
1 |
|
直接 gdb 调试,把断点下在 31 行:看一下堆情况:
先简单说一下前 29 行的内容,定义了两个数组,通过改变下标来在不同地址存放自己需要的指针以及指向。
1 | pwndbg> x/100gx 0x603300 |
1 | pwndbg> x/16gx &stack_buffer_2 |
断点第 31 行呈现的其实就是前 31 行的具体操作:
首先我们在栈上申请了两个 stack_buffer(fake chunk),然后将 stack_buffer_2 的 fd 指针修改为&stack_buffer_1,将 stack_buffer_1 的 fd 修改为&malloc(100),将其 bk 修改为&stack_buffer_2。
free((void*)victim);
在 free(victim)之后 chunk 将会加入 fastbin 当中。我们直接单步走到 free 之后看一下 bins

malloc(1200);:


这时候去申请一个 chunk,触发fastbin的合并使得victim进入 unsortedbin 中处理,最终被整理到 small bin 中
victim 是 malloc(100) 分配的堆块(大小≈0x68),free(victim) 后会进入 fastbin:单链表,无双向指针,无法篡改 bk;而我们的攻击需要 victim 进入 smallbin:双向链表,有 fd/bk 指针可篡改。
触发「fastbin 堆块合并」,让 victim 离开 fastbin
- fastbin 的堆块默认不会和其他堆块合并。当你申请一个超大堆块1200 字节时,glibc 发现当前空闲堆块不够,会触发堆整理:把 fastbin 里的空闲堆块victim取出来,尝试和 top chunk堆顶空闲块合并。
- 合并过程中,victim 会先被从 fastbin 移除,临时放入 unsortedbin(未分类的空闲堆块链表)。
触发「unsortedbin 归类」,让 victim 进入 smallbin
- unsortedbin 是临时中转站,glibc 会把 unsortedbin 里的堆块按大小归类到对应 bin:
- victim 大小是 100 字节(0x68),属于 smallbin 的范围(通常 0x20~0x400);glibc 会把 victim 从 unsortedbin 移到「0x68 大小的 smallbin 双向链表」中;此时 victim 就有了 fd/bk 双向指针 指向 smallbin 的表头,我们才能篡改它的 bk 指针指向栈上的 fake chunk。
1 | pwndbg> x/300gx 0x603000 |
victim[1] = (intptr_t)stack_buffer_1;
1 | fprintf(stderr, "现在模拟一个可以覆盖 victim 的 bk 指针的漏洞,让他的 bk 指针指向栈上\n"); |
1 | pwndbg> x/300gx 0x603000 |

1 | pwndbg> x/16gx 0x7fffffffdca0 |
void *p3 = malloc(100);:
1 | fprintf(stderr, "然后申请跟第一个 chunk 大小一样的 chunk\n"); |

1 | pwndbg> x/300gx 0x603000 |
1 | pwndbg> x/16gx stack_buffer_2 |
我们可以发现 stack_buffer_1 的 fd 指针发生了改变,从一开始的指向&malloc(100);边整理指向 main_arena 的地址,

char *p4 = malloc(100);
1 | char *p4 = malloc(100); |
1 | pwndbg> x/16gx stack_buffer_2 |
由此可见:利用 house of lore 可以分配任意指定位置的 chunk,从而修改任意地址的内存。
更新: 2026-03-07 00:22:12
原文: https://www.yuque.com/idcm/wnemg9/lzf8iwm2x9k8ruqo