对jsoncpp库进行亿次AFL模糊测试
字数 3674 2025-08-06 18:07:47
AFL模糊测试jsoncpp库的完整教学指南
1. AFL模糊测试基础
1.1 模糊测试(Fuzzing)概念
模糊测试是一种通过向目标系统提供非预期输入并监视异常结果来发现软件漏洞的方法:
- 属于黑盒测试技术
- 不需要过多人为参与
- 不需要分析人员有丰富的知识
- 通过随机数据攻击程序并观察破坏点
- 当前主流Fuzzer:AFL、libFuzzer、OSS-Fuzz等
1.2 AFL(American Fuzzy Lop)简介
AFL是由Michal Zalewski开发的基于覆盖引导(Coverage-guided)的模糊测试工具:
- 通过记录输入样本的代码覆盖率调整输入以提高覆盖率
- 工作流程:
- 源码编译时进行插桩记录代码覆盖率
- 选择初始测试集加入输入队列
- 将队列文件按策略进行"突变"
- 保留更新覆盖范围的变异文件
- 记录触发crash的文件
1.3 AFL插桩方式
-
有源码时:
- 常规模式:在汇编语言上进行插桩
- LLVM模式:在LLVM编译器中间代码插桩(性能更好)
-
无源码时:使用动态qume模式进行插桩
1.4 AFL结果分析
输出目录包含三个子目录:
queue/:每个独特执行路径的测试用例crashes/:导致致命信号的唯一测试用例(按信号分组)hangs/:导致程序超时的唯一测试用例
AFL界面关键信息解读:
-
Process timing:测试时间消耗
- run time:总运行时间
- last new path:上次发现新路径时间
- last uniq crash:上次崩溃时间
- last uniq hang:上次挂起时间
-
Overall results:测试执行结果汇总
- cycle done:fuzzer轮数
- total paths:已执行测试用例数
- uniq crashes:发现的崩溃数
- uniq hang:发现的挂起数
-
Cycle progress:当前队列执行进度
- now processing:当前测试进程ID
- paths timed out:超时放弃的路径数
-
Map coverage:
- map density:分支元组命中率(左边当前输入,右边整体语料库)
- count coverage:元组命中次数的可变性
-
Stage progress:执行过程细节
- now trying:当前变异输入方法
- stage execs:当前阶段进度
- total execs:全局进度
- exec speed:执行速度(理想>500次/秒)
-
Findings in depth:种子变异信息
- favored paths:基于最小化算法产生的新路径
- new edges on:基于新路径产生的新边
- total crashes:基于新路径产生的崩溃
- total tmouts:基于新路径产生的超时
-
Fuzzing strategy yields:有效路径上的结果比例
-
Path geometry:路径测试相关信息
- levels:测试等级
- pending:未fuzzing的输入数量
- pend fav:fuzzer感兴趣的输入数量
- own finds:fuzzing过程中新发现的输入
- imported:从其他实例导入的输入
- stability:相同输入是否产生相同行为(通常100%)
2. AFL测试jsoncpp实战
2.1 环境准备
-
安装AFL:
- 将
afl-clang和afl-clang++复制到/usr/bin/ - 设置环境变量:
export CC=afl-clang CXX=afl-clang++
- 将
-
jsoncpp库简介:
- 开源C++库,提供JSON字符串序列化/反序列化功能
- 主要类:
value:表示json对象和数组reader:用于反序列化json字符串writer:用于序列化json字符串
2.2 编译过程
-
对jsoncpp进行AFL插桩编译:
mkdir build && cd build cmake -DCMAKE_C_COMPILER=afl-clang -DCMAKE_CXX_COMPILER=afl-clang++ .. make -
检查插桩效果:
- 使用IDA查看生成的
libjsoncpp.a文件 - 确认基本块中插入了
__afl_maybe_log函数
- 使用IDA查看生成的
-
编写测试程序:
- 创建Value结构体
- 使用Reader解析json文件并输出
- 使用FastWrite写入并保存json文件
- 使用g++正常编译测试程序
2.3 测试用例处理
-
JSON测试用例获取:
- 从网站下载json文件作为初始测试用例
- 示例:省区地图数据(json格式)
-
测试用例精简:
- afl-cmin:移除执行相同代码的测试用例
afl-cmin -i fuzz_in -o fuzz_in_cmin ./afl_test - afl-tmin:减少单个输入文件大小
- Instrumented mode(默认):
afl-tmin -i testcase -o fuzz_in_tmin/testcase_tmin ./afl_test - Crash mode(使用-x参数):
afl-tmin -x -i testcase -o fuzz_in_tmin/testcase_tmin ./afl_test
- Instrumented mode(默认):
- afl-cmin:移除执行相同代码的测试用例
2.4 Fuzzing高级技巧
-
字典使用:
- AFL提供针对特定数据格式的字典优化
- jsoncpp可使用AFL自带的
json.dict - 命令示例:
afl-fuzz -x json.dict -m none -i ./input -o ./out ./xxx @@
-
多核并行测试:
- 每个afl-fuzz进程占用一个内核
- 主实例(Master):
./afl-fuzz -i testcase_dir -o sync_dir -M fuzzer01 [...] - 次实例(Slave):
./afl-fuzz -i testcase_dir -o sync_dir -S fuzzer02 [...] ./afl-fuzz -i testcase_dir -o sync_dir -S fuzzer03 [...] - 区别:
- 主实例执行确定性检查
- 次实例直接进行随机调整
- 使用
-s参数可让所有实例都不做确定性模糊
-
内存错误检查(ASAN):
- 设置环境变量:
export AFL_USE_ASAN=1 - 注意事项:
- 会消耗更多内存(32位约800MB,64位约20TB)
- 建议添加
CFLAGS=-m32限制为32位 - 或使用
-m选项指定内存上限
- 设置环境变量:
-
持久模式:
- 避免每次运行都fork()创建新进程
- 要求每次循环运行时擦除所有变量和缓冲区
- 安装llvm和clang后使用:
export LLVM_CONFIG=/usr/bin/llvm-config-6.0 afl-clang-fast/afl-clang-fast++ 进行编译
-
综合测试命令:
# 主实例 ./afl-fuzz -x json.dict -m none -i testcase_dir -o sync_dir -M fuzzer01 [...] # 次实例 ./afl-fuzz -x json.dict -m none -i testcase_dir -o sync_dir -S fuzzer02 [...] ./afl-fuzz -x json.dict -m none -i testcase_dir -o sync_dir -S fuzzer03 [...]
2.5 Havoc变异策略分析
AFL的havoc变异策略特点:
- 将一系列变异随机组合
- 基于字符"重要程度"有倾向性地havoc
- 文件可分为"元数据"和"数据":
- 元数据:如ELF文件中的program header, section header
- 数据:具体指令内容
- 变异"元数据"通常更容易引起新路径
- 随着变异进行,初始"元数据"准确性降低
改进思路:
- 提高"元数据"被变异的概率
- 当"元数据"准确性降到阈值时恢复原始havoc方式
- 关键挑战:自动区分文件中的"元数据"和"数据"
3. 测试结果与分析
3.1 测试数据
- 测试规模:4核并行,1亿余次测试
- 运行时间:约24小时
- 性能指标:
- 每个进程fuzzer平均轮数:90轮
- Totalpath:约3000
- map density:2% / 4%
- count coverage:5bits/tuple
- CPU利用率:150%
- 平均每个进程发现unique crash:20个
3.2 Crash分析
-
复现步骤:
- 以
-g模式重新编译测试程序 - 对crash得到的poc进行复现测试
- 以
-
常见问题:
- 栈溢出造成的程序崩溃
- 多个文件报同样位置的错误
- AFL标记为unique但实际是重复crash
3.3 遇到的问题与解决方案
-
主进程测试速度慢于副进程:
- 原因:主进程执行所有变异类型,副进程仅执行最后两种
- 验证:通过fuzzing strategy yield数据确认
- 解决方案:这是正常现象,无需特别处理
-
Crash重复问题:
- 现象:虽然内容不同但触发漏洞位置和类型相同
- 原因:AFL的unique判断标准与实际情况有差异
- 解决方案:人工筛选真正独特的crash
-
Havoc改进中的元数据区分:
- 挑战:自动区分"元数据"和"数据"的算法
- 当前状态:尚未找到理想解决方案
-
测试速度问题:
- 原因分析:
- 测试程序同时引用value、read、write三个功能
- 测试用例偏大且未进行tmin处理
- 优化建议:
- 分离测试不同功能
- 对测试用例进行tmin处理
- 原因分析:
4. 总结与最佳实践
4.1 AFL测试jsoncpp的最佳实践
-
编译阶段:
- 使用AFL插桩编译jsoncpp库
- 编写精简的测试程序调用关键功能
- 考虑启用ASAN检测内存错误
-
测试用例准备:
- 获取多样化的初始json测试用例
- 使用afl-cmin精简测试集
- 对保留的用例使用afl-tmin缩减大小
-
Fuzzing执行:
- 使用json专用字典(-x json.dict)
- 根据CPU核心数设置并行实例
- 主实例执行确定性检查,副实例执行随机变异
- 监控执行速度(目标>500次/秒)
-
结果分析:
- 检查crashes目录中的独特崩溃
- 人工验证crash的真实性和独特性
- 对真正独特的crash进行深入分析
4.2 性能优化建议
-
提高执行速度:
- 分离测试不同功能模块
- 减小测试用例大小
- 考虑使用持久模式
-
提高crash质量:
- 结合ASAN检测更多类型的内存错误
- 对初始测试用例进行更全面的筛选
- 定期更新字典文件
-
资源利用:
- 根据内存大小选择32位或64位编译
- 合理设置内存限制(-m参数)
- 监控CPU利用率,适当调整并行实例数
通过本指南的系统性方法,可以有效地对jsoncpp等开源库进行全面的模糊测试,发现潜在的安全漏洞和稳定性问题。关键在于正确配置AFL环境、准备合适的测试用例、合理利用各种高级功能,并对结果进行科学分析。