基于AFLFast的fuzz自动化漏洞挖掘(2)
这里主要讲讲实验的环境配置和论文中主要涉及到的环境复现。还是看到我们的项目地址:mboehme/aflfast: AFLFast (extends AFL with Power Schedules)
在开始之前呢,这里有个快速上手指南挺重要的,也简单放这里看一下(究极省流版本,还是看看吧):
=====================
AFL quick start guide(AFL快速开始指南)
=====================
You should read docs/README. It's pretty short. If you really can't, here's
how to hit the ground running:
建议先阅读docs/README文档(内容很简短)。如果实在来不及,以下是快速上手指南:
1) Compile AFL with 'make'. If build fails, see docs/INSTALL for tips.
使用'make'命令编译AFL。若构建失败,请查阅docs/INSTALL中的解决方案。
2) Find or write a reasonably fast and simple program that takes data from
a file or stdin, processes it in a test-worthy way, then exits cleanly.
If testing a network service, modify it to run in the foreground and read
from stdin. When fuzzing a format that uses checksums, comment out the
checksum verification code, too.
准备一个处理速度较快、逻辑简单的测试程序,要求:
从文件或stdin读取数据
对数据进行可测试的处理
处理完成后正常退出
若测试网络服务,需修改为前台运行并从stdin读取
若测试含校验码的格式,建议先注释掉校验验证代码
The program must crash properly when a fault is encountered. Watch out for
custom SIGSEGV or SIGABRT handlers and background processes. For tips on
detecting non-crashing flaws, see section 11 in docs/README.
程序遇到错误时必须能正常崩溃。注意排查自定义的SIGSEGV/SIGABRT处理程序及后台进程。非崩溃类缺陷检测技巧见docs/README第11节。
3) Compile the program / library to be fuzzed using afl-gcc. A common way to
do this would be:
使用afl-gcc编译目标程序/库,典型编译方式:
CC=/path/to/afl-gcc CXX=/path/to/afl-g++ ./configure --disable-shared
make clean all
If program build fails, ping <afl-users@googlegroups.com>.
若编译失败,请联系afl-users@googlegroups.com
4) Get a small but valid input file that makes sense to the program. When
fuzzing verbose syntax (SQL, HTTP, etc), create a dictionary as described in
dictionaries/README.dictionaries, too.
准备:
小而有效的输入样本
对于复杂语法(SQL/HTTP等),建议dictionaries/README.dictionaries创建字典
5) If the program reads from stdin, run 'afl-fuzz' like so:
./afl-fuzz -i testcase_dir -o findings_dir -- \
/path/to/tested/program [...program's cmdline...]
If the program takes input from a file, you can put @@ in the program's
command line; AFL will put an auto-generated file name in there for you.
运行afl-fuzz:
从stdin读取的程序:
./afl-fuzz -i 测试用例目录 -o 输出目录 --
/path/to/测试程序 [程序参数...]
从文件读取的程序:
在命令行中用@@占位符,AFL会自动替换为生成的文件名
6) Investigate anything shown in red in the fuzzer UI by promptly consulting
docs/status_screen.txt.
实时关注fuzzer界面中的红色警告,具体含义参考docs/status_screen.txt
That's it. Sit back, relax, and - time permitting - try to skim through the
following files:
- docs/README - A general introduction to AFL,
- docs/perf_tips.txt - Simple tips on how to fuzz more quickly,
- docs/status_screen.txt - An explanation of the tidbits shown in the UI,
- docs/parallel_fuzzing.txt - Advice on running AFL on multiple cores.
基础操作就是这些。后续建议抽空阅读:
docs/README AFL基础介绍
docs/perf_tips.txt 性能优化技巧
docs/status_screen.txt 界面元素说明
docs/parallel_fuzzing.txt 多核并行fuzzing方案
基础环境设置:
先简单说说本地环境:ubuntu22.04,用的vmware启的虚拟机。
安装依赖项:
sudo apt update
sudo apt install -y build-essential clang llvm-dev libtool automake bison flex
(本地提前也是有python3/git这些的,缺啥自己单独再去拉就ok)
zip包拉下来,在项目文件夹里面直接:
make
即可。
检查结果:
./afl-fuzz
会显示有相关的help文档:
这里为了方便,所以我在~/.bashrc里面创建了符号链接,这个看个人需要吧,把路径写进去更新就可以了。记得重新关闭一下终端。
同样这里方便起见,需要重新导一下as路径:
sudo vim ~/.bashrc
export PATH="$PATH:/home/linux/Desktop/AFLFast/aflfast-master"
export AFL_PATH=/home/linux/Desktop/AFLFast/aflfast-master
关闭终端就可以生效了。
构建clang-fast:
(这个是基于llvm的插桩,简单来说就是更快一点)
cd llvm_mode/
make
但是这里直接执行会产生报错,本地llvm版本是14,会导致版本太高了。变动也有两种方式,一个是直接改源代码适应llvm14;另一个是直接启用虚拟环境的形式安装需要的llvm6。
这里暂时先搁置一下,先用gcc演示完了再说,ubuntu对llvm的支持不太好后面再补充。
最后简单说下项目结构:
aflfast-master/
├── afl-fuzz, afl-gcc, afl-cmin, afl-analyze ... ← 核心工具编译产物
├── docs/ ← 📘 官方文档说明
├── testcases/ ← 📂 样例输入文件(fuzz 测试用)
├── experimental/ ← 🧪 实验性调度器、脚本、原型
├── llvm_mode/ ← 🧠 支持 LLVM 编译器的插桩逻辑
├── qemu_mode/ ← ⚙️ QEMU 模式源码(支持二进制fuzz)
├── libdislocator/ ← 🧨 自定义 malloc 实现(便于检测溢出)
├── libtokencap/ ← 📚 Token 捕捉器(自动构建字典)
├── dictionaries/ ← 📖 输入格式字典样例
├── Makefile ← 构建入口
├── README / Readme.md / QuickStartGuide.txt ← 🔰 快速上手 / 介绍文档
└── test-instr.c, *.c, *.h ← 工具源码
(感谢gpt)
实验复现:
test-instr.c编译实现最小功能测试:
这里使用的是基于gcc的,编译首页项目文件里的test-instr.c:
afl-gcc test-instr.c -o test-instr
(一般来说按照上面的流程走完了不会报错)
编译完成。
这个东西是AFL程序自带的一个微型测试样例:
- 读取输入文件内容(通过
argv[1]
); - 判断输入是否包含特定字符组合,比如:
"0" -> "1" -> "2" -> "3"
,模拟一个“深路径”。
目的:
- 验证 AFL 是否能通过变异探索更多路径;
- 模拟真实程序中的“逻辑触发路径”,以观测 AFL 的覆盖率追踪效果;
- 是 AFL 的最简使用示例,复现实验从它开始非常合适。
跑通最小demo:
创建输入种子:
mkdir in
echo "0" > in/input.txt
创建输出并运行:
mkdir out
./afl-fuzz -i in -o out -- ./test-instr @@
接下来执行即可:
然后简单说说这个功能模块,这里因为没有特定的停止时间,所以可以手动终止。
test-instr说明:
/*
american fuzzy lop - a trivial program to test the build
--------------------------------------------------------
Written and maintained by Michal Zalewski <lcamtuf@google.com>
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char** argv) {
char buf[8];
if (read(0, buf, 8) < 1) {
printf("Hum?\n");
exit(1);
}
if (buf[0] == '0')
printf("Looks like a zero to me!\n");
else
printf("A non-zero value? How quaint!\n");
exit(0);
}
为什么要看这个呢,主要是为了看最小测试样例,通过简单的代码审计,可以清晰的看出来这里变异成0的时候会出现不一样的结果。
所以前面seed给一个0就有问题:(
这里正确做法是:seed给一个非0的数字,然后执行:
afl-fuzz -i in -o out -- ./test-instr
在out里面会记录这个产生异常的点:
到这里最最简单的测试demo就跑完了。
仪盘表功能说明:
顶部信息栏
scss
复制编辑
american fuzzy lop (fast) 2.51b (test-instr)
- fast:当前使用的 power schedule(如:fast / explore / exploit)
- 2.51b:AFLFast 版本
- test-instr:被 fuzz 的程序名
🔵 左上角:Process Timing(执行过程时间)
字段 | 含义 |
---|---|
run time | 当前模糊测试已运行的总时长 |
last new path | 上次发现新路径的时间。如果一直是 none yet ,说明没找到新路径 |
last uniq crash | 上次发现“唯一崩溃”的时间 |
last uniq hang | 上次发现“唯一 hang”输入的时间(超时) |
⚠️ check syntax!
:这通常提示测试程序没有正确处理输入,或种子文件不符合格式。
🔵 中上:Cycle Progress(测试周期进展)
字段 | 含义 |
---|---|
now processing | 当前正在处理的路径在整个输入队列中的进度 |
paths timed out | 超时的路径占比 |
🔵 中部:Stage Progress(执行阶段进度)
字段 | 含义 |
---|---|
now trying | 当前使用的变异策略阶段,如 havoc |
stage execs | 当前阶段已完成的执行次数和百分比 |
execs done | 累计总执行次数(对测试程序的调用次数) |
exec speed | 每秒执行次数(越高越好) |
🔵 右上角:Overall Results(测试总体结果)
字段 | 含义 |
---|---|
cycles done | 输入队列轮询的次数 |
total paths | 发现的不同路径数量(路径覆盖) |
uniq crashes | 发现的唯一崩溃输入数量 |
uniq hangs | 发现的唯一超时输入数量 |
🔵 右中:Map Coverage(覆盖信息)
字段 | 含义 |
---|---|
map density | 覆盖图中活跃位的密度,越高说明代码覆盖越多 |
count coverage | 每个路径平均 hit 的覆盖位数量(越高越复杂) |
🔵 中右:Findings in Depth(发现的输入信息)
字段 | 含义 |
---|---|
favored paths | 被 AFL 判定为优先变异的路径数量 |
new edges | 找到的新边数量(控制流路径点) |
total crashes | 总共发现的崩溃数 |
total timeouts | 超时的总数 |
🔵 左下:Fuzzing Strategy Yields(各策略效果统计)
AFL 会用多种策略(bit flip、arith、havoc 等)变异输入,这里显示每种策略找到多少新路径:
类型 | 说明(每列是不同位宽) |
---|---|
bit flips | 单比特、双比特、四比特翻转 |
byte flips | 单字节翻转 |
arith | 算术操作变异(+/- 数值) |
known ints | 插入已知“魔法数”整数(如 0x100) |
dictionary | 使用字典替换 |
havoc | 混合乱序变异(主要方式) |
trim | 剪枝优化率 |
🔵 右下角:Path Geometry(路径结构)
字段 | 含义 |
---|---|
levels | 当前队列中的最大路径深度 |
pending | 待处理的输入数量 |
pending fav | 优先处理的输入数量 |
own finds | 当前 fuzzer 发现的新路径数量(多 fuzzer 场景下可比较) |
imported | 从其他 AFL 实例导入的路径数量(用于 parallel 模式) |
stability | 目标程序行为是否稳定(波动大时不可信) |
🟩 右下角小方块:CPU 使用情况
- 类似
[cpu000:108%]
:表示当前绑定 CPU 负载状态 - 如果跑多个实例,每个都可以绑定不同核心以充分利用多核
问题解决:
问题1:系统崩溃模式导致错误
大概就是fuzz会收集crush报错,但是ubuntu原生的报错把这个给拦截了。
解决方法:把这个对应的模块关掉就行:
sudo sh -c 'echo core > /proc/sys/kernel/core_pattern'
cat /proc/sys/kernel/core_pattern
#用来检查模式
临时设置,在系统重新启动后会还原。