Fork me on GitHub

AFL初体验

AFL全程American Fuzzy Lop,是由安全研究员Michał Zalewski(@lcamtuf)开发的一款基于覆盖引导(Coverage-guided)的模糊测试工具,它通过记录输入样本的代码覆盖率,从而调整输入样本以提高覆盖率,增加发现漏洞的概率。

AFL安装

AFL在ubuntu下可以直接使用以下指令安装:

1
sudo apt 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
/* 
gcc -fno-stack-protector -z execstack vuln.c -o vuln
*/


#include <stdio.h>
#include <string.h>

int main(){
char login[32];
char password[32];

printf("Username: \n");
gets(login);

printf("Password: \n");
gets(password);

if(strcmp(login, "root") == 0){
if(strcmp(password, "qwer1234") == 0){
printf("Access Granted!\n");
return 0;
}
}

printf("Access Denied.\n");
return 1;
}

本程序就是一个简单的验证用户名和密码的小程序,本程序根据输入结果有三条执行流:

  • 用户名错误且密码错误
  • 用户名正确但密码不正确
  • 用户名正确且密码正确

使用afl-gcc编译源代码:

1
gcc -fno-stack-protector -z execstack vuln.c -o vuln

运行结果:

1
2
3
4
5
6
7
8
9
10
11
root@e73955bef93e:/pwn/AFL_learning# afl-gcc -fno-stack-protector -z execstack vuln.c -o vuln
afl-cc 2.52b by <lcamtuf@google.com>
vuln.c: In function 'main':
vuln.c:15:5: warning: implicit declaration of function 'gets'; did you mean 'fgets'? [-Wimplicit-function-declaration]
gets(login);
^~~~
fgets
afl-as 2.52b by <lcamtuf@google.com>
[+] Instrumented 6 locations (64-bit, non-hardened mode, ratio 100%).
/tmp/ccudSZMx.o: In function `main':
/pwn/AFL_learning/vuln.c:15: warning: the `gets' function is dangerous and should not be used.

使用checksec命令的结果:

1
2
3
4
5
6
7
8
root@e73955bef93e:/pwn/AFL_learning# checksec vuln
[*] '/pwn/AFL_learning/vuln'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments

测试用例

为了模拟上文提到的程序运行的三条执行流分别对应的结果,创建两个目录分别作为AFL的输入目录和输出目录。比如如下目录结构:

1
2
3
4
5
6
7
8
/AFL_testFolder/
|
|-------/testcases/----|
| |test1.txt
| |test2.txt
| |test3.txt
|
|-------/results/

其中testcases中各个文件的文件内容如下:

test1.txt

模拟用户名和密码都不正确的情况

1
2
aasdqf
sqwdqfs

test2.txt

模拟用户名正确但是密码不正确的情况

1
2
root
asdfasdfasd

text3.txt

模拟用户名和密码都正确的情况

1
2
root
qwer1234

开始测试

将以上文件布置好之后即可开始fuzzing:

1
afl-fuzz -i ./testcases/ -o ./results/ ./vuln

测试跑起来之后的输出:

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
58
root@e73955bef93e:/pwn/AFL_learning# afl-fuzz -i ./testcases/ -o ./results/ ./vuln
afl-fuzz 2.52b by <lcamtuf@google.com>
[+] You have 2 CPU cores and 5 runnable tasks (utilization: 250%).
[!] WARNING: System under apparent load, performance may be spotty.
[*] 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 './testcases/'...
[+] No auto-generated dictionary tokens to reuse.
[*] Creating hard links for all input files...
[*] Validating target binary...
[*] Attempting dry run with 'id:000000,orig:test1.txt'...
[*] Spinning up the fork server...
[+] All right - fork server is up.
len = 11, map size = 3, exec speed = 3453 us
[*] Attempting dry run with 'id:000001,orig:test2.txt'...
len = 12, map size = 5, exec speed = 3751 us
[*] Attempting dry run with 'id:000002,orig:test3.txt'...
len = 14, map size = 4, exec speed = 2962 us
[+] All test cases processed.

[+] Here are some useful stats:

Test case count : 3 favored, 0 variable, 3 total
Bitmap range : 3 to 5 bits (average: 4.00 bits)
Exec timing : 2962 to 3751 us (average: 3389 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 (vuln)

┌─ process timing ─────────────────────────────────────┬─ overall results ─────┐
│ run time : 0 days, 0 hrs, 0 min, 42 sec │ cycles done : 5 │
│ last new path : none yet (odd, check syntax!) │ total paths : 3 │
│ last uniq crash : 0 days, 0 hrs, 0 min, 41 sec │ uniq crashes : 1 │
│ last uniq hang : none seen yet │ uniq hangs : 0 │
├─ cycle progress ────────────────────┬─ map coverage ─┴───────────────────────┤
│ now processing : 0 (0.00%) │ map density : 0.00% / 0.01% │
│ paths timed out : 0 (0.00%) │ count coverage : 1.00 bits/tuple │
├─ stage progress ────────────────────┼─ findings in depth ────────────────────┤
│ now trying : havoc │ favored paths : 3 (100.00%) │
│ stage execs : 252/256 (98.44%) │ new edges on : 3 (100.00%) │
│ total execs : 16.0k │ total crashes : 39 (1 unique) │
│ exec speed : 358.2/sec │ total tmouts : 0 (0 unique) │
├─ fuzzing strategy yields ───────────┴───────────────┬─ path geometry ────────┤
│ bit flips : 0/208, 0/205, 0/199 │ levels : 1 │
│ byte flips : 0/26, 0/23, 0/17 │ pending : 0 │
│ arithmetics : 0/1451, 0/172, 0/0 │ pend fav : 0 │
│ known ints : 0/140, 0/643, 0/748 │ own finds : 0 │
│ dictionary : 0/0, 0/0, 0/18 │ imported : n/a │
│ havoc : 1/6144, 0/5760 │ stability : 100.00% │
│ trim : 29.73%/7, 0.00% ├────────────────────────┘
│─────────────────────────────────────────────────────┘ [cpu000:287%]

其中AFL的界面介绍可以在https://www.freebuf.com/articles/system/191536.html看到,

在此案例中,可以看到total paths字段的值为3。当所有执行流都执行完之后差不多就可以终止fuzz了,因为再fuzz下去也不会出现什么新的结果了。

fuzz结果查看

终止fuzz之后可以在results目录下找到crash记录,通过回放crash可以找到程序的漏洞,比如我fuzz结束后crash目录下有如下文件。

1
./results/crash/id:000000,sig:11,src:000000,op:havoc,rep:128

将此文件输入到文件中:

1
2
3
4
5
root@e73955bef93e:/pwn/AFL_learning# ./vuln  < ./results/crashes/id\:000000\,sig\:11\,src\:000000\,op\:havoc\,rep\:128
Login:
Password:
Access Denied.
Segmentation fault

就可以还原crash的情况了,比如这里发现了一个Segmentation fault。

关于fuzz测试还有很多要学的,好好加油吧。

您的支持是我最大的动力🍉