AFL(american fuzzy lop)学习一
@sizaf
AFL 的模糊方法
- 基于改进的边缘覆盖
- 插桩法引导的遗传算法
流程:
插桩
-
从源码编译程序时进行插桩,以记录代码覆盖率(Code Coverage);
-
选择一些输入文件,作为初始测试集加入输入队列(queue);
-
将队列中的文件按一定的策略进行“突变”;
-
如果经过变异文件更新了覆盖范围,则将其保留添加到队列中;
-
上述过程会一直循环进行,期间触发了crash的文件会被记录下来。
二进制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FGnOVlku-1649639204647)(Q:/whx/Dropbox/Master/paper&project/fuzzer/afl/img_article/american%20fuzzy%20lop/xUp1dTYRcSakQZn.png)]
改进边缘覆盖:
向目标程序注入以下工具来记录采用了哪些分支和次数
- 一条边表示为当前基本块(分配了一个随机常数)与前一个基本块(即元组 (prev_location, cur_location))之间的 XOR
- 覆盖信息存储在一个紧凑共享的64KB的哈希表中,称为跟踪位图(bitmap)
- 当访问一条边时,通过增加与该特定边的哈希相对应的位图中的值来记录命中
cur_location = <COMPILE_TIME_RANDOM>;
shared_mem[cur_location ^ prev_location]++;
prev_location = cur_location >> 1;
Instrumenting programs for use with AFL
源代码已知的情况下:编译插桩afl工具:
通用方法:
$ CC=/path/to/afl/afl-gcc ./configure # c
$ CXX=/path/to/afl/afl-g++ ./configure # c++
$ make clean all
对于clang(afl-clang and afl-clang++) 也适用
$ CC=/path/to/afl/afl-clang-fast ./configure [...options...]
$ CXX=/path/to/afl/afl-clang-fast++ ./configure [...options...]
$ make
测试(libraries)时, 需要找到或编写一个简单的程序,该程序从stdin或文件中读取数据,并将数据传递给测试库。
将此可执行文件链接到已检测库的静态版本,或者确保在运行时加载正确的**.so**文件(通常通过设置
LD_LIBRARY _PATH
)。最简单的选择是静态构建,通常通过
$ CC(C++)=/path/to/afl/afl-XXX ./configure --disable-shared
Instrumenting binary-only apps
无法获得源码时,针对二进制文件进行fuzzer, 使用QEMU
$ cd qemu_mode
$ ./build_qemu_support.sh
源码
- afl-analyze:用来对用例进行分析,发现其中有意义的字段。
- afl-clang++:相当于c++的编译器的封装。
- afl-fuzz:AFL主体,用于对目标程序进行fuzz。
- afl-gcc:相当于gcc的封装。
- afl-plot:生成模糊测试任务的状态图。
- afl-tmin:对用例进行简化。
- afl-clang:相当于c的编译器封装。
- afl-cmin:对用例进行简化。
- afl-g++:相当于g++的封装。
- afl-gotcpu:用于查看当前CPU状态。
- afl-showmap:用于对单个用例执行路径跟踪。
- afl-whatsup:用于查看fuzz任务当前的状态。
- alf-clang-fast: 基于LLVM高效clang针对c
- afl-clang-fast++: 基于LLVM高效clang针对c++
输出
解释输出
在输出目录中创建了三个子目录并实时更新
queue/
每个独特的执行路径的测试用例,加上用户给出的所有开始文件。这就是第2节提到的合成语料。在将这个语料库用于任何其他目的之前,您可以使用afl-cmin
工具将它缩小到更小的大小。该工具将找到提供同等边缘覆盖的较小文件子集。crashes/
导致被测试程序接收致命信号的唯一测试用例(例如SIGSEGV, SIGILL, SIGABRT)。这些条目按接收到的信号分组。hangs/
导致被测试程序超时的唯一测试用例。在某些东西被归类为挂起之前的默认时间限制大于1秒和-t
参数的值。可以通过设置AFL_HANG_TMOUT
来微调该值,但很少需要这样做。
运行含义
1) Process timing 指示了Fuzzer测试的时间消耗
+----------------------------------------------------+
| run time : 0 days, 8 hrs, 32 min, 43 sec |
| last new path : 0 days, 0 hrs, 6 min, 40 sec |
| last uniq crash : none seen yet |
| last uniq hang : 0 days, 1 hrs, 24 min, 32 sec |
+----------------------------------------------------+
- process timing 指示了Fuzzer测试的时间消耗
- run time: 运行总时间
- last new path: path(触发新执行的测试用例的缩写),上次执行测试用例的时间,长时间未变化说明有程序有问题,过于简单无分支或内存太小会收到一个红色警告
- last uniq crash: 上次崩溃的时间
- last uniq hang: 上次挂起的时间
2) Overall results 汇总了fuzzer测试的执行结果。
+-----------------------+
| cycles done : 0 |
| total paths : 2095 |
| uniq crashes : 0 |
| uniq hangs : 19 |
+-----------------------+
- cycle done: 表明fuzzer的轮数
- 颜色说明
- 品红色处于 the first pass,
- 如果有新发现,颜色会变成黄色,
- 所有子过程完成后将会变成蓝色,
- 最后变成绿色的话表明已经长时间没有新的动作了,此时也提示我们应该手动ctrl-c去关闭fuzzing。
- 颜色说明
- total paths: 目前为止执行的测试用例
- uniq crashes: 目前为止发现的崩溃
- uniq hang: 目前为止发现的挂起
3) Cycle progress 展示了当前队列中fuzzer 执行了多少
+-------------------------------------+
| now processing : 1296 (61.86%) |
| paths timed out : 0 (0.00%) |
+-------------------------------------+
- ow processing: 当前测试用例的进程的ID 因为fuzzer是开启另一个进程进行测试的 结果写回共享内存中
- paths timed out: 根据超时决定是否放弃
4) Map coverage
+--------------------------------------+
| map density : 10.15% / 29.07% |
| count coverage : 4.03 bits/tuple |
+--------------------------------------+
-
map density:多少个分支元组,命中,与位图的容量成比例.号码在左边描述当前输入;右边的是整体的值是输入语料库。绝对值低于二百说的是表明了以下三种情况之一
程序极其简单;它没有被正确地使用(例如,由于链接到目标的非工具副本图书馆);或者它过早地退出了您的输入测试用例。绒毛会尝试用粉红色标记,只是为了让你意识到。超过70%的百分比可能很少发生在非常复杂的程序大量使用模板生成的代码。将以红色标记高百分比。但是一般不会有红色标记除非你用的是非常复杂的软件(比如v8, perl,ffmpeg)。
-
count coverage:另一行处理元组命中次数的可变性二进制文件。本质上,如果每一个被取的分支总是取一个固定数量的对于我们尝试过的所有输入,这将是“1.00”。当我们管理为了触发每个分支的其他命中计数,指针将开始移动接近“8.00”(8位地图命中的每一个位),但可能永远不会达到这一极端。
5) Stage progress 进一步展示了fuzzer的执行过程细节。
+-------------------------------------+
| now trying : interest 32/8 |
| stage execs : 3996/34.4k (11.62%) |
| total execs : 27.4M |
| exec speed : 891.7/sec |
+-------------------------------------+
-
now trying: 指明当前所用的变异输入的方法
参数含义
- calibration – 在fuzzing测试之前的阶段,主要是检查执行路径检测异常,建立基线执行速度。
- trim L/S – 同样也是在fuzzing测试之前的阶段,修建测试用例使其更短,但保证裁剪后仍能达到相同的执行路径。L表示length长度,S表示stepover步距,其值与文件大小是相关的。
- bitflip L/S – 确定性的比特位翻转。以S为增量,L长度的bit数被翻转。有以下几种变型模式:1/1, 2/1, 4/1, 8/8, 16/8, 32/8。
- arith L/8 – 确定性的算术运算。AFL会尝试去减去或者加上一些整数使其为8bit/16bit/32bit的值,步距永远是8bits。
- interest L/8 – 确定性的值覆盖。AFL自身保留了一些Interesting的8bit/16bit/32bit的值,用这些值去覆盖原有的测试用例,步距永远是8bits。
- extras – 确定性的字典注入。这里AFL自身有一个字典,当然也可以使用-x选项来指明使用用户提供的字典。
- havoc – 固定长度的堆叠随机扭曲。该阶段会尝试位翻转,用随机数或者Interesting的整数去覆盖,块删除,块复制,以及字典的相关操作。
- splice – 最后一种策略。在上述策略都执行完后将会执行该策略,它和havoc差不多,不过它会首先将队列中的两个随机输入先拼接在一起。
- sync – 这个是并行执行的策略选项,通过-M或者-S选项进行指定。该策略并不会涉及到真正的fuzzing,会导入从另一个fuzzer得到的输出和测试用例。
以上所有策略执行一遍也就是前文提到的the first pass。
-
stage execs: 当前阶段的进度指示
-
total execs: 全局的进度指示
-
exec speed: 执行速度但是基准测试应该理想地超过500次执行/秒大多数时候——如果它保持在100以下,这项工作可能会非常长。fuzzer也会明确地警告你缓慢的目标。如果发生这种情况,查看fuzzer中包含的perf_tips.txt文件,了解如何提高速度的事情了。
6) Findings in depth 应该是种子变异产生的信息
+--------------------------------------+
| favored paths : 879 (41.96%) |
| new edges on : 423 (20.19%) |
| total crashes : 0 (0 unique) |
| total tmouts : 24 (19 unique) |
+--------------------------------------+
-
favored paths: 基于最小化算法产生新的更好的路径
-
new edges on: 基于更好路径产生的新边
-
total crashes: 基于更好路径产生的崩溃
-
total tmouts: 基于更好路径产生的超时 包括所有超时的超时 即使这些超时不足以分类到hangs.
7) Fuzzing strategy yields 进一步展示了AFL所做的工作,在更有效路径上得到的结果比例
针对的应该是上文的 stage progress的now trying一栏的参数
+-----------------------------------------------------+
| bit flips : 57/289k, 18/289k, 18/288k |
| byte flips : 0/36.2k, 4/35.7k, 7/34.6k |
| arithmetics : 53/2.54M, 0/537k, 0/55.2k |
| known ints : 8/322k, 12/1.32M, 10/1.70M |
| dictionary : 9/52k, 1/53k, 1/24k |
| havoc : 1903/20.0M, 0/0 |
| trim : 20.31%/9201, 17.05% |
+-----------------------------------------------------+
- bit flips: 确定性的比特位翻转。以S为增量,L长度的bit数被翻转。有以下几种变型模式:1/1, 2/1, 4/1, 8/8, 16/8, 32/8。
- byte flips: 这个应该就是字节翻转了
- arithmetics: – 确定性的算术运算。AFL会尝试去减去或者加上一些整数使其为8bit/16bit/32bit的值,步距永远是8bits。
- known ints:
- dictionary:
- havoc:– 固定长度的堆叠随机扭曲。该阶段会尝试位翻转,用随机数或者Interesting的整数去覆盖,块删除,块复制,以及字典的相关操作。
- trim:– 同样也是在fuzzing测试之前的阶段,修建测试用例使其更短,但保证裁剪后仍能达到相同的执行路径。L表示length长度,S表示stepover步距,其值与文件大小是相关的。 这个修剪策略有一些不同/ 这一行中的第一个数字显示了从输入中删除的字节的比率,第二个数字表示实现这一目标需要的执行数,第三个数字显示了字节的比例,尽管不可能移除,被视为没有效果,在进行效果更好的fuzzer步骤的时候不是考虑的因素
8) Path geometry 汇总了路径测试的相关信息。
+---------------------+
| levels : 5 |
| pending : 1570 |
| pend fav : 583 |
| own finds : 0 |
| imported : 0 |
| stability : 100.00% |
+---------------------+
-
levels: 表示测试等级
-
pending: 表示还没有经过fuzzing的输入数量
-
pend fav: 表明fuzzer感兴趣的输入数量
-
own finds: 表示在fuzzing过程中新找到的,或者是并行测试从另一个实例导入的
-
imported: n/a表明不可用,即没有导入
-
stability: 表明相同输入是否产生了相同的行为,一般结果都是100%,如果低于100%并且变红,则需要查阅官方文档寻找解决步骤。
9) CPU load
[cpu: 25%]
显示了本地系统上的CPU利用率。它是计算处于“可运行”状态的进程数,然后将其与系统上的逻辑核数进行比较。
绿色-则表示使用的CPU核比可用的少
红色-则表示您的CPU可能已经超额订阅运行额外的fuzzers可能不会给你带来任何好处
1
10) Addendum: status and plot files
For unattended operation, some of the key status screen information can be also
found in a machine-readable format in the fuzzer_stats file in the output
directory. This includes:
- start_time - unix time indicating the start time of afl-fuzz
- last_update - unix time corresponding to the last update of this file
- fuzzer_pid - PID of the fuzzer process
- cycles_done - queue cycles completed so far
- execs_done - number of execve() calls attempted
- execs_per_sec - current number of execs per second
- paths_total - total number of entries in the queue
- paths_found - number of entries discovered through local fuzzing
- paths_imported - number of entries imported from other instances
- max_depth - number of levels in the generated data set
- cur_path - currently processed entry number
- pending_favs - number of favored entries still waiting to be fuzzed
- pending_total - number of all entries waiting to be fuzzed
- stability - percentage of bitmap bytes that behave consistently
- variable_paths - number of test cases showing variable behavior
- unique_crashes - number of unique crashes recorded
- unique_hangs - number of unique hangs encountered
- command_line - full command line used for the fuzzing session
- slowest_exec_ms- real time of the slowest execution in ms
- peak_rss_mb - max rss usage reached during fuzzing in mb
c 编译插桩测试
编写alftestc代码
//afltestc.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
void test (char *buf) {
int len = strlen(buf);
int i = 0;
if(buf[0] == 's' && buf[1] == 'i'){
if(buf[2] == 'z' && buf[3] == 'a'){
if(buf[4] == 'i' && buf[5] == 'f'){
if(len == 6){
printf("awesome found sizaif !\n");
raise(SIGSEGV);
}else{
printf("sorry not\n");
}
}
}
}
}
int main(int argc, char *argv[]) {
char buf[1000] = {0};
gets(buf);
test(buf);
return 0;
}
创建configure makefile等文件
root@0187031113b5:/home/workhome/afl-test/ins-c# ls
Makefile.am aclocal.m4 autom4te.cache compile config.h.in configure depcomp missing
Makefile.in afltestc.c autoscan.log config.h config.log configure.ac install-sh stamp-h1
使用afl-gcc
插桩
afl-gcc插桩
root@0187031113b5:/home/workhome/afl-test/ins-c# CC=afl-gcc ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
checking for gawk... no
checking for mawk... mawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for gcc... afl-gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether afl-gcc accepts -g... yes
checking for afl-gcc option to accept ISO C89... none needed
checking whether afl-gcc understands -c and -o together... yes
checking whether make supports the include directive... yes (GNU style)
checking dependency style of afl-gcc... gcc3
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
config.status: executing depfiles commands
编译运行
root@0187031113b5:/home/workhome/afl-test/ins-c# make afl_harden=1
make all-am
make[1]: Entering directory '/home/workhome/afl-test/ins-c'
afl-gcc -DHAVE_CONFIG_H -I. -g -O2 -MT afltestc.o -MD -MP -MF .deps/afltestc.Tpo -c -o afltestc.o afltestc.c
afl-cc 2.57b by <lcamtuf@google.com>
afltestc.c: In function 'main':
afltestc.c:26:5: warning: implicit declaration of function 'gets'; did you mean 'fgets'? [-Wimplicit-function-declaration]
26 | gets(buf);
| ^~~~
| fgets
afl-as 2.57b by <lcamtuf@google.com>
[+] Instrumented 11 locations (64-bit, non-hardened mode, ratio 100%).
mv -f .deps/afltestc.Tpo .deps/afltestc.Po
afl-gcc -g -O2 -o afltestc afltestc.o
afl-cc 2.57b by <lcamtuf@google.com>
/usr/bin/ld: afltestc.o: in function `main':
/home/workhome/afl-test/ins-c/afltestc.c:26: warning: the `gets' function is dangerous and should not be used.
make[1]: Leaving directory '/home/workhome/afl-test/ins-c'
创建 in
,crash
文件夹
in
中创建初始seed : hello.txt
# hello.txt
hello
afl-fuzz 运行
$ afl-fuzz -d -i in -o crash ./afltestc
root@0187031113b5:/home/workhome/afl-test/ins-c# afl-fuzz -d -i in -o crash ./afltestc
afl-fuzz 2.57b by <lcamtuf@google.com>
[+] You have 16 CPU cores and 3 runnable tasks (utilization: 19%).
[+] Try parallel jobs - see /usr/local/share/doc/afl/parallel_fuzzing.txt.
[*] Checking CPU core loadout...
[+] Found a free CPU core, binding to #0.
[*] Checking core_pattern...
[*] Setting up output directories...
[*] Scanning 'in'...
[+] No auto-generated dictionary tokens to reuse.
[*] Creating hard links for all input files...
[*] Validating target binary...
[*] Attempting dry run with 'id:000000,orig:hello.txt'...
[*] Spinning up the fork server...
[+] All right - fork server is up.
len = 6, map size = 4, exec speed = 164 us
[+] All test cases processed.
[+] Here are some useful stats:
Test case count : 1 favored, 0 variable, 1 total
Bitmap range : 4 to 4 bits (average: 4.00 bits)
Exec timing : 164 to 164 us (average: 164 us)
[*] No -t option specified, so I'll use exec timeout of 20 ms.
[+] All set and ready to roll!
crash
if(buf[0] == 's' && buf[1] == 'i'){ if(buf[2] == 'z' && buf[3] == 'a'){ if(buf[4] == 'i' && buf[5] == 'f'){ if(len == 6){ printf("awesome found sizaif !\n"); raise(SIGSEGV); }else{ printf("sorry not\n"); } } } }
当输入seed 突变为sizaif,且长度为6时,crash
plot
使用afl-clang-fast
插桩:
root@0187031113b5:/home/workhome/afl-test/ins-c# CC=afl-clang-fast ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
checking for gawk... no
checking for mawk... mawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for gcc... afl-clang-fast
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether afl-clang-fast accepts -g... yes
checking for afl-clang-fast option to accept ISO C89... none needed
checking whether afl-clang-fast understands -c and -o together... yes
checking whether make supports the include directive... yes (GNU style)
checking dependency style of afl-clang-fast... gcc3
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
config.status: executing depfiles commands
编译运行
oot@0187031113b5:/home/workhome/afl-test/ins-c# make afl_harden=1
make all-am
make[1]: Entering directory '/home/workhome/afl-test/ins-c'
afl-clang-fast -DHAVE_CONFIG_H -I. -g -O2 -MT afltestc.o -MD -MP -MF .deps/afltestc.Tpo -c -o afltestc.o afltestc.c
afl-clang-fast 2.57b by <lszekeres@google.com>
afltestc.c:26:5: warning: implicit declaration of function 'gets' is invalid in C99 [-Wimplicit-function-declaration]
gets(buf);
^
afl-llvm-pass 2.57b by <lszekeres@google.com>
[+] Instrumented 11 locations (non-hardened mode, ratio 100%).
1 warning generated.
mv -f .deps/afltestc.Tpo .deps/afltestc.Po
afl-clang-fast -g -O2 -o afltestc afltestc.o
afl-clang-fast 2.57b by <lszekeres@google.com>
/usr/bin/ld: afltestc.o: in function `main':
/home/workhome/afl-test/ins-c/afltestc.c:26: warning: the `gets' function is dangerous and should not be used.
make[1]: Leaving directory '/home/workhome/afl-test/ins-c'
创建 in
,crash
文件夹
in
中创建初始seed : hello.txt
# hello.txt
abc
afl-fuzz 运行
$ afl-fuzz -d -i in -o crash2 ./afltestc
root@0187031113b5:/home/workhome/afl-test/ins-c# afl-fuzz -d -i in -o crash2 ./afltestc
afl-fuzz 2.57b by <lcamtuf@google.com>
[+] You have 16 CPU cores and 2 runnable tasks (utilization: 12%).
[+] Try parallel jobs - see /usr/local/share/doc/afl/parallel_fuzzing.txt.
[*] Checking CPU core loadout...
[+] Found a free CPU core, binding to #0.
[*] Checking core_pattern...
[*] Setting up output directories...
[*] Scanning 'in'...
[+] No auto-generated dictionary tokens to reuse.
[*] Creating hard links for all input files...
[*] Validating target binary...
[*] Attempting dry run with 'id:000000,orig:hello.txt'...
[*] Spinning up the fork server...
[+] All right - fork server is up.
len = 4, map size = 3, exec speed = 152 us
[+] All test cases processed.
[+] Here are some useful stats:
Test case count : 1 favored, 0 variable, 1 total
Bitmap range : 3 to 3 bits (average: 3.00 bits)
Exec timing : 152 to 152 us (average: 152 us)
[*] No -t option specified, so I'll use exec timeout of 20 ms.
[+] All set and ready to roll!
crash
plot
c++ 编译插桩测试
编写afltestc++
代码
#include <iostream>
#include <string>
#include <signal.h>
using namespace std;
void test (string buf) {
int len = buf.length();
if ( len == 6 && buf[0] == 's' && buf[1]=='i' && buf[2]=='z'){
cout<<"awesome found sizaif !"<<endl;
raise(SIGSEGV);
}
else{
cout<<"sorry not"<<endl;
}
}
int main(int argc, char *argv[]) {
string buf = "";
cin>>buf
test(buf);
return 0;
}
使用afl-g++
插桩 :
root@0187031113b5:/home/workhome/afl-test/ins-c++# afl-g++ -g -o afltestc++ afltestc++.cpp
afl-cc 2.57b by <lcamtuf@google.com>
afl-as 2.57b by <lcamtuf@google.com>
[+] Instrumented 41 locations (64-bit, non-hardened mode, ratio 100%).
运行
root@0187031113b5:/home/workhome/afl-test/ins-c++# afl-fuzz -b 6 -i in -o crashc++ ./afltestc++
afl-fuzz 2.57b by <lcamtuf@google.com>
[+] You have 16 CPU cores and 2 runnable tasks (utilization: 12%).
[+] Try parallel jobs - see /usr/local/share/doc/afl/parallel_fuzzing.txt.
[*] Checking CPU core loadout...
[+] Found a free CPU core, binding to #6.
[*] Checking core_pattern...
[*] Setting up output directories...
[*] Scanning 'in'...
[+] No auto-generated dictionary tokens to reuse.
[*] Creating hard links for all input files...
[*] Validating target binary...
[*] Attempting dry run with 'id:000000,orig:hello.txt'...
[*] Spinning up the fork server...
[+] All right - fork server is up.
len = 29, map size = 15, exec speed = 212 us
[+] All test cases processed.
[+] Here are some useful stats:
Test case count : 1 favored, 0 variable, 1 total
Bitmap range : 15 to 15 bits (average: 15.00 bits)
Exec timing : 212 to 212 us (average: 212 us)
[*] No -t option specified, so I'll use exec timeout of 20 ms.
[+] All set and ready to roll!
crash
plot
使用afl-clang-fast
++
root@0187031113b5:/home/workhome/afl-test/ins-c++# afl-clang-fast++ -g -o afltestc++clang afltestc++.cpp
afl-clang-fast 2.57b by <lszekeres@google.com>
afl-llvm-pass 2.57b by <lszekeres@google.com>
[+] Instrumented 127 locations (non-hardened mode, ratio 100%).