Linux下 AFL 的安装与使用

1 从一个test程序开始

1.1 安装AFL

首先,检查本地的clang和llvm是否安装,若未安装则安装一下

1
2
sudo apt-get install clang
sudo apt-get install llvm

以上环境确认好后,下载并安装AFL

1
2
3
4
5
wget http://lcamtuf.coredump.cx/afl/releases/afl-2.52b.tgz
tar -zxvf afl-2.52b.tgz
cd afl-2.52b
make
sudo make install

以上操作做完,AFL就算是安装完毕啦。现在来看看AFL安装前后代码目录中文件的差别。以下先是安装前的目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
drwxr-xr-x 10  500  500   4096 Nov  5  2017 .
drwxr-xr-x 3 root root 4096 Jun 2 09:49 ..
-rw-r--r-- 1 500 500 23755 Nov 5 2017 afl-analyze.c
-rw-r--r-- 1 500 500 15273 Nov 5 2017 afl-as.c
-rw-r--r-- 1 500 500 21090 Nov 5 2017 afl-as.h
-rwxr-xr-x 1 500 500 11392 Nov 5 2017 afl-cmin
-rw-r--r-- 1 500 500 206076 Nov 5 2017 afl-fuzz.c
-rw-r--r-- 1 500 500 8597 Nov 5 2017 afl-gcc.c
-rw-r--r-- 1 500 500 5336 Nov 5 2017 afl-gotcpu.c
-rwxr-xr-x 1 500 500 4913 Nov 5 2017 afl-plot
-rw-r--r-- 1 500 500 16527 Nov 5 2017 afl-showmap.c
-rw-r--r-- 1 500 500 25235 Nov 5 2017 afl-tmin.c
-rwxr-xr-x 1 500 500 3655 Nov 5 2017 afl-whatsup
-rw-r--r-- 1 500 500 12565 Nov 5 2017 alloc-inl.h
-rw-r--r-- 1 500 500 11216 Nov 5 2017 config.h
-rw-r--r-- 1 500 500 6574 Nov 5 2017 debug.h
drwxr-xr-x 2 500 500 4096 Nov 5 2017 dictionaries
drwxr-xr-x 4 500 500 4096 Nov 5 2017 docs
drwxr-xr-x 12 500 500 4096 Nov 5 2017 experimental
-rw-r--r-- 1 500 500 2065 Nov 5 2017 hash.h
drwxr-xr-x 2 500 500 4096 Nov 5 2017 libdislocator
drwxr-xr-x 2 500 500 4096 Nov 5 2017 libtokencap
drwxr-xr-x 2 500 500 4096 Nov 5 2017 llvm_mode
-rw-r--r-- 1 500 500 6987 Nov 5 2017 Makefile
drwxr-xr-x 3 500 500 4096 Nov 5 2017 qemu_mode
lrwxrwxrwx 1 500 500 24 Nov 5 2017 QuickStartGuide.txt -> docs/QuickStartGuide.txt
lrwxrwxrwx 1 500 500 11 Nov 5 2017 README -> docs/README
drwxr-xr-x 6 500 500 4096 Nov 5 2017 testcases
-rw-r--r-- 1 500 500 789 Nov 5 2017 test-instr.c
-rw-r--r-- 1 500 500 2292 Nov 5 2017 types.h

安装完成后,代码目录中的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
drwxr-xr-x 10  500  500   4096 Jun  2 09:47 .
drwxr-xr-x 7 root root 4096 Jun 1 18:42 ..
-rwxr-xr-x 1 root root 90792 Jun 2 09:46 afl-analyze
-rw-r--r-- 1 500 500 23755 Nov 5 2017 afl-analyze.c
-rwxr-xr-x 1 root root 50808 Jun 2 09:46 afl-as
-rw-r--r-- 1 500 500 15273 Nov 5 2017 afl-as.c
-rw-r--r-- 1 500 500 21090 Nov 5 2017 afl-as.h
lrwxrwxrwx 1 root root 7 Jun 2 09:46 afl-clang -> afl-gcc
lrwxrwxrwx 1 root root 7 Jun 2 09:46 afl-clang++ -> afl-gcc
-rwxr-xr-x 1 500 500 11392 Nov 5 2017 afl-cmin
-rwxr-xr-x 1 root root 582872 Jun 2 09:46 afl-fuzz
-rw-r--r-- 1 500 500 206076 Nov 5 2017 afl-fuzz.c
lrwxrwxrwx 1 root root 7 Jun 2 09:46 afl-g++ -> afl-gcc
-rwxr-xr-x 1 root root 35376 Jun 2 09:46 afl-gcc
-rw-r--r-- 1 500 500 8597 Nov 5 2017 afl-gcc.c
-rwxr-xr-x 1 root root 32240 Jun 2 09:46 afl-gotcpu
-rw-r--r-- 1 500 500 5336 Nov 5 2017 afl-gotcpu.c
-rwxr-xr-x 1 500 500 4913 Nov 5 2017 afl-plot
-rwxr-xr-x 1 root root 76840 Jun 2 09:46 afl-showmap
-rw-r--r-- 1 500 500 16527 Nov 5 2017 afl-showmap.c
-rwxr-xr-x 1 root root 106280 Jun 2 09:46 afl-tmin
-rw-r--r-- 1 500 500 25235 Nov 5 2017 afl-tmin.c
-rwxr-xr-x 1 500 500 3655 Nov 5 2017 afl-whatsup
-rw-r--r-- 1 500 500 12565 Nov 5 2017 alloc-inl.h
lrwxrwxrwx 1 root root 6 Jun 2 09:46 as -> afl-as
-rw-r--r-- 1 500 500 11216 Nov 5 2017 config.h
-rw-r--r-- 1 500 500 6574 Nov 5 2017 debug.h
drwxr-xr-x 2 500 500 4096 Nov 5 2017 dictionaries
drwxr-xr-x 4 500 500 4096 Nov 5 2017 docs
drwxr-xr-x 12 500 500 4096 Nov 5 2017 experimental
-rw-r--r-- 1 500 500 2065 Nov 5 2017 hash.h
drwxr-xr-x 2 500 500 4096 Nov 5 2017 libdislocator
drwxr-xr-x 2 500 500 4096 Nov 5 2017 libtokencap
drwxr-xr-x 2 500 500 4096 Nov 5 2017 llvm_mode
-rw-r--r-- 1 500 500 6987 Nov 5 2017 Makefile
drwxr-xr-x 3 500 500 4096 Nov 5 2017 qemu_mode
lrwxrwxrwx 1 500 500 24 Nov 5 2017 QuickStartGuide.txt -> docs/QuickStartGuide.txt
lrwxrwxrwx 1 500 500 11 Nov 5 2017 README -> docs/README
drwxr-xr-x 6 500 500 4096 Nov 5 2017 testcases
-rw-r--r-- 1 500 500 789 Nov 5 2017 test-instr.c
-rw-r--r-- 1 500 500 2292 Nov 5 2017 types.h

可以看到afl安装后,文件夹下多了些二进制文件,是不是很简单。但是,在使用AFL进行fuzz测试前,一定要设置一下core_pattern,否则会报错哦。

1
2
3
4
5
root@x:~/afl-test# cat /proc/sys/kernel/core_pattern
|/usr/share/apport/apport %p %s %c %d %P
root@x:~/afl-test# echo core > /proc/sys/kernel/core_pattern
root@x:~/afl-test# cat /proc/sys/kernel/core_pattern
core

1.2 跑起来看看

测试程序的代码使用了先知社区的示例代码,如下。平时我们编译c代码使用的是gcc,现在为了使用AFL进行fuzz,我们必须使用1.1节中编译出来的afl-gcc对目标代码进行插桩编译。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h> 
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>

int vuln(char *str)
{
int len = strlen(str);
if(str[0] == 'A' && len == 66)
{
raise(SIGSEGV);
//如果输入的字符串的首字符为A并且长度为66,则异常退出
}
else if(str[0] == 'F' && len == 6)
{
raise(SIGSEGV);
//如果输入的字符串的首字符为F并且长度为6,则异常退出
}
else
{
printf("it is good!\n");
}
return 0;
}

int main(int argc, char *argv[])
{
char buf[100]={0};
gets(buf);//存在栈溢出漏洞
printf(buf);//存在格式化字符串漏洞
vuln(buf);

return 0;
}

接下来,需要做以下三件事:

(1)使用afl-gcc编译目标代码(即插桩)

1
$ afl-gcc test.c -g -o test

(2)新建测试用例(测试用例即待测程序的输入,可以随意给个文件作为起始用例。AFL会在此用例的基础上进行变异,好的测试用例有助于更快发现更多bug)

1
2
3
4
5
$ mkdir testcase
$ cd testcase
$ vim file123
123
qwe

(3)输出文件夹(发现的bug等信息会输出到该目录下)

1
$ mkdir output

最后,指定好以上三项的位置,执行如下命令,就会进入AFL主面板开始fuzz啦

1
afl-fuzz -i testcase/ -o output/ ./test

这里给一下我fuzz完成后(ctrl +c)的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
root@x:~/afl-test# afl-fuzz -i testcase/ -o output/ ./test
afl-fuzz 2.52b by <lcamtuf@google.com>
[+] You have 8 CPU cores and 1 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...
[+] Output directory exists but deemed OK to reuse.
[*] Deleting old session data...
[+] Output dir cleanup successful.
[*] Scanning 'testcase/'...
[+] No auto-generated dictionary tokens to reuse.
[*] Creating hard links for all input files...
[*] Validating target binary...
[*] Attempting dry run with 'id:000000,orig:file1'...
[*] Spinning up the fork server...
[+] All right - fork server is up.
len = 8, map size = 5, exec speed = 306 us
[+] All test cases processed.

[+] Here are some useful stats:

Test case count : 1 favored, 0 variable, 1 total
Bitmap range : 5 to 5 bits (average: 5.00 bits)
Exec timing : 306 to 306 us (average: 306 us)

[*] No -t option specified, so I'll use exec timeout of 20 ms.
[+] All set and ready to roll!

american fuzzy lop 2.52b (test)

┌─ process timing ─────────────────────────────────────┬─ overall results ─────┐
│ run time : 0 days, 0 hrs, 24 min, 9 sec │ cycles done : 2574 │
│ last new path : 0 days, 0 hrs, 24 min, 9 sec │ total paths : 3 │
│ last uniq crash : 0 days, 0 hrs, 14 min, 10 sec │ uniq crashes : 6 │
│ last uniq hang : none seen yet │ uniq hangs : 0 │
├─ cycle progress ────────────────────┬─ map coverage ─┴───────────────────────┤
│ now processing : 2 (66.67%) │ map density : 0.01% / 0.01% │
│ paths timed out : 0 (0.00%) │ count coverage : 1.00 bits/tuple │
├─ stage progress ────────────────────┼─ findings in depth ────────────────────┤
│ now trying : splice 9 │ favored paths : 3 (100.00%) │
│ stage execs : 47/48 (97.92%) │ new edges on : 3 (100.00%) │
│ total execs : 5.41M │ total crashes : 385k (6 unique) │
│ exec speed : 3330/sec │ total tmouts : 1 (1 unique) │
├─ fuzzing strategy yields ───────────┴───────────────┬─ path geometry ────────┤
│ bit flips : 0/128, 0/125, 0/119 │ levels : 2 │
│ byte flips : 0/16, 0/13, 0/7 │ pending : 0 │
│ arithmetics : 2/896, 0/129, 0/9 │ pend fav : 0 │
│ known ints : 0/78, 0/339, 0/308 │ own finds : 2 │
│ dictionary : 0/0, 0/0, 0/0 │ imported : n/a │
│ havoc : 4/2.31M, 2/3.09M │ stability : 100.00% │
│ trim : 83.10%/16, 0.00% ├────────────────────────┘
^C────────────────────────────────────────────────────┘ [cpu000: 23%]

+++ Testing aborted by user +++
[+] We're done here. Have a nice day!

1.3 分析结果

fuzz完成后,根据AFL面板的信息我们知道有6个crash,因此我们进入output目录去分析

1
2
3
4
5
6
7
8
9
10
11
root@x:~/afl-test/output# ls -al
total 108
drwxr-xr-x 5 root root 4096 Jun 2 10:15 .
drwxr-xr-x 4 root root 4096 Jun 2 10:08 ..
drwx------ 2 root root 4096 Jun 2 10:25 crashes
-rw------- 1 root root 1 Jun 2 10:39 .cur_input
-rw------- 1 root root 65536 Jun 2 10:16 fuzz_bitmap
-rw------- 1 root root 739 Jun 2 10:39 fuzzer_stats
drwx------ 2 root root 4096 Jun 2 10:15 hangs
-rw------- 1 root root 15250 Jun 2 10:39 plot_data
drwx------ 3 root root 4096 Jun 2 10:15 queue
  • crashes/ 文件夹中存放了fuzz过程中产生的crash样例

  • hangs/ 文件夹中存放了fuzz过程中产生的超时样例

  • queue/ 文件夹中存放了fuzz过程中不同执行路径的测试用例

使用xxd命令查看产生的6个crash样例,然后可以将样例作为输入用gdb调试test程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
root@x:~/afl-test/output/crashes# xxd id:000000,sig:06,src:000000,op:havoc,rep:64
00000000: 1f20 0820 fe1f 2004 0020 200c 0064 1ffe . . .. .. ..d..
00000010: 017f 2033 ff20 2000 180b 2020 2008 20e2 .. 3. ... . .
00000020: 1f20 e21f ff01 00ff ff20 2020 2002 0024 . ....... ..$
00000030: ff00 0000 0000 0100 203a 200c 20e2 1f01 ........ : . ...
00000040: 2020 0019 0110 510b 2020 2002 0020 2020 ....Q. ..
00000050: 2020 2020 2020 2020 2020 2020 2020 2020
00000060: 2020 2020 2020 2020 2020 2020 2020 0820 .
00000070: 2020 0c20 e21f 2002 7f80 2020 6b20 0101 . .. ... k ..
00000080: 0101 0101 1d01 0101 0101 0101 0101 0101 ................
00000090: 0101 0101 0101 0101 0101 0101 40ff 2080 ............@. .
root@x:~/afl-test/output/crashes# xxd id:000001,sig:11,src:000002,op:arith8,pos:0,val:-28
00000000: 4662 6262 627f 001a Fbbbb...
root@x:~/afl-test/output/crashes# xxd id:000002,sig:06,src:000002,op:havoc,rep:128
00000000: 3685 c13e 3e3e 002f 3e3e 203e 3e6a 3e3e 6..>>>./>> >>j>>
00000010: 3ee4 343e 3e3e 3e53 3d5b 146a 3e3e 2e3e >.4>>>>S=[.j>>.>
00000020: 3e3e 3e4e 3e6a 3e5c 273e 3e3e 3e3e 6a3e >>>N>j>\'>>>>>j>
00000030: 3e8c 3e3e 3e3e 4a3e 6a3e 3e27 d9c5 a8c5 >.>>>>J>j>>'....
00000040: 5314 533e 3e1d 3e3e 3e3e 3e3e 3e3e 3e3e S.S>>.>>>>>>>>>>
00000050: 3e3e 3e3e 3e3e 3e3e 3e3e 3e3e 3e3e 3e3e >>>>>>>>>>>>>>>>
00000060: 3e3e 3e3e 3e2f 3e3e 343e 3e6a 3e3e 0080 >>>>>/>>4>>j>>..
00000070: 3e3e 3e27 fb9c 9c9c 9c9c 9c9c 9c9c c5c5 >>>'............
00000080: c5a6 c5e0 7fff ......
root@x:~/afl-test/output/crashes# xxd id:000003,sig:06,src:000001+000002,op:splice,rep:4
00000000: 2532 6e5a 627f fc19 %2nZb...
root@x:~/afl-test/output/crashes# xxd id:000004,sig:06,src:000001,op:havoc,rep:128
00000000: 4106 00f2 5fdf 03a0 c5f6 dfdf 40a0 dff4 A..._.......@...
00000010: f5ff e5f4 f4fe 04f5 f4f4 f4f4 f4f4 f4f4 ................
00000020: 0ef5 f4ff 800d f4b4 f400 f4f4 0edb eadf ................
00000030: f8df dfe7 f400 eadd dfe7 df02 dff8 00d7 ................
00000040: dfdf 01df dfdf ecdb eadf f8df dfe7 dfdf ................
00000050: dff8 1ad7 dfdf 01da dfdf dedf dfdf dfdf ................
00000060: dfdf dfdf dfdf dfdf dfdf dfde dfdf dfde ................
00000070: ed00 e3e3 e316 0d00 0000 1000 00c0 dfe7 ................
00000080: dfdf df00 d701 dfd6 0e .........
root@x:~/afl-test/output/crashes# xxd id:000005,sig:11,src:000002+000001,op:splice,rep:16
00000000: 4132 800f fe0f 0f0f 0f0f 220f 0f0f 0f0f A2........".....
00000010: 0f0f 0f0f 0f0f 0f0f 0f0f 0ef0 0f22 0f0f ............."..
00000020: 0f0f 0f0f 0f0f 0f0f 0f0f 0f0f 0f0f 0f0f ................
00000030: 0f0f 0f0f 0f0f 0f0f 0f0f 0f7f ff64 0f0f .............d..
00000040: fc0e 000f 0f0f 0f0f 0fff ..........

1.4 继续已停止的fuzz测试

1
afl-fuzz -i- -o output ./test

1.5 结合ASAN的fuzz

ASAN只推荐在32位机器上使用。如果在64位机器上使用,也需将AFL编译成32位的。

1
2
Method 1. set AFL_USE_ASAN=1 before calling ‘make clean all’
Method 2. add -fsanitize=address option into makefile

1.6 文件fuzz

1.2和1.3节讲的是从stdin读取输入的目标程序,这里补充一下从文件读取输入的目标程序,以 readelf -a xxfile 为例。

有源码插桩:

1
afl-fuzz -i in/ -o out/ ./readelf -a @@

无源码插桩:

1
afl-fuzz -i in/ -o out/ -Q ./readelf -a @@

2 无源码AFL fuzz

无源码时,即无法对源码使用功能afl-gcc/afl-g++进行插桩编译时,整个fuzz过程会变得异常缓慢。但确实没有源码的情况下可使用该方法。

步骤1:编译支持无源码fuzz的AFL

在afl源码目录下:

1
2
3
4
cd qemu_mode
./build_qemu_support.sh
cd ..
make install

步骤2:跟1.2和1.3节一样,只不过有两点差异:

  • 待测程序是未经afl-gcc插桩编译过的,如使用gcc编译

  • 执行afl-fuzz命令时多加了一个-Q参数

1
afl-fuzz -i testcase/ -o output/ -Q ./test

3 AFL文档学习

afl面板介绍:

https://lcamtuf.coredump.cx/afl/status_screen.txt

4 参考文档

初探AFL-Fuzz

小白初学AFL(American Fuzzy Lop)

AFL文件变异一览

AFL(American Fuzzy Lop) Cautions