初探afl-fuzz

前言

早就想开始学习fuzz了,结果拖了得有半年了。一方面是源码的阅读能力太弱,另一方面可能还是对于学习投入的精力不太够吧。甚至期间九哥还请hollk师傅做过一次fuzz的讲解,可惜当时学习了一下基本用法之后就没有再进行深入的学习了。

本篇文章主要还是讲解afl-fuzz的一个基本用法

参考文章:

https://xz.aliyun.com/t/4314?time__1311=n4%2BxnD0D9DgDcBQKDtD%2Fia4BKmqE%3DWDI2hhrrD&alichlgref=https%3A%2F%2Fwww.google.com%2F

https://tttang.com/archive/1508/

AFL-Fuzz介绍

Fuzzing是指通过构造测试输入,对软件进行大量测试来发现软件中的漏洞的一种模糊测试方法。在CTF中,fuzzing可能不常用,但在现实的漏洞挖掘中,fuzzing因其简单高效的优势,成为非常主流的漏洞挖掘方法。

AFL则是fuzzing的一个很好用的工具,全称是American Fuzzy Lop,由Google安全工程师Michał Zalewski开发的一款开源fuzzing测试工具,可以高效地对二进制程序进行fuzzing,挖掘可能存在的内存安全漏洞,如栈溢出、堆溢出、UAF、double free等。由于需要在相关代码处插桩,因此AFL主要用于对开源软件进行测试。当然配合QEMU等工具,也可对闭源二进制代码进行fuzzing,但执行效率会受到影响

工作原理:

通过对源码进行重新编译时进行插桩(简称编译时插桩)的方式自动产生测试用例来探索二进制程序内部新的执行路径。AFL也支持直接对没有源码的二进制程序进行测试,但需要QEMU的支持。

AFL界面介绍

image-20240113005059484

process timing

这里展示了当前fuzzer的运行时间、最近一次发现新执行路径的时间、最近一次崩溃的时间、最近一次超时的时间。

值得注意的是第2项,最近一次发现新路径的时间。如果由于目标二进制文件或者命令行参数出错,那么其执行路径应该是一直不变的,所以如果从fuzzing开始一直没有发现新的执行路径,那么就要考虑是否有二进制或者命令行参数错误的问题了。对于此状况,AFL也会智能地进行提醒

overall results

这里包括运行的总周期数、总路径数、崩溃次数、超时次数。

其中,总周期数可以用来作为何时停止fuzzing的参考。随着不断地fuzzing,周期数会不断增大,其颜色也会由洋红色,逐步变为黄色、蓝色、绿色。一般来说,当其变为绿色时,代表可执行的内容已经很少了,继续fuzzing下去也不会有什么新的发现了。此时,我们便可以通过Ctrl-C,中止当前的fuzzing

stage progress

这里包括正在测试的fuzzing策略、进度、目标的执行总次数、目标的执行速度

执行速度可以直观地反映当前跑的快不快,如果速度过慢,比如低于500次每秒,那么测试时间就会变得非常漫长。如果发生了这种情况,那么我们需要进一步调整优化我们的fuzzing

以上是简单的介绍,如果要看完整的可以查看官方的文档

安装

先进入官网进行源码下载:https://gitcode.com/mirrors/google/afl/overview

进行编译,以及后续的源码阅读

1
2
make
sudo make install

输入以上命令后基本就能安装成功了,在终端输入afl-后tab,就能出现以下这些命令了

image-20240113004432472

使用AFL插桩程序(有源码)

选择的是afl-training当中的quickstart

quickstart通过fuzz一个简单的demo来体验afl的使用过程。

编译demo的方法是:

1
2
cd quickstart
CC=afl-clang-fast AFL_HARDEN=1 make

make默认会编译all,all编译的是vulnerable,所以最终会形成afl-clang-fast -g -w vulnerable.c -o vulnerable。

vulnerable.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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
$ cat vulnerable.c
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#define INPUTSIZE 100

int process(char *input)
{
char *out;
char *rest;
int len;
if (strncmp(input, "u ", 2) == 0)
{ // upper case command
char *rest;
len = strtol(input + 2, &rest, 10); // how many characters of the string to upper-case
rest += 1; // skip the first char (should be a space)
out = malloc(len + strlen(input)); // could be shorter, but play it safe
if (len > (int)strlen(input))
{
printf("Specified length %d was larger than the input!\n", len);
return 1;
}
else if (out == NULL)
{
printf("Failed to allocate memory\n");
return 1;
}
for (int i = 0; i != len; i++)
{
out[i] = rest[i] - 32; // only handles ASCII
}
out[len] = 0;
strcat(out, rest + len); // append the remaining text
printf("%s", out);
free(out);
}
else if (strncmp(input, "head ", 5) == 0)
{ // head command
if (strlen(input) > 6)
{
len = strtol(input + 4, &rest, 10);
rest += 1; // skip the first char (should be a space)
rest[len] = '\0'; // truncate string at specified offset
printf("%s\n", rest);
}
else
{
fprintf(stderr, "head input was too small\n");
}
}
else if (strcmp(input, "surprise!\n") == 0)
{
// easter egg!
*(char *)1 = 2;
}
else
{
return 1;
}
return 0;
}

int main(int argc, char *argv[])
{
char *usage = "Usage: %s\n"
"Text utility - accepts commands and data on stdin and prints results to stdout.\n"
"\tInput | Output\n"
"\t------------------+-----------------------\n"
"\tu <N> <string> | Uppercased version of the first <N> bytes of <string>.\n"
"\thead <N> <string> | The first <N> bytes of <string>.\n";
char input[INPUTSIZE] = {0};

// Slurp input
if (read(STDIN_FILENO, input, INPUTSIZE) < 0)
{
fprintf(stderr, "Couldn't read stdin.\n");
}

int ret = process(input);
if (ret)
{
fprintf(stderr, usage, argv[0]);
};
return ret;
}

看起来程序获取来输入后调用process函数进行处理,根据输入的不同进行不同的处理:

u :对字符串的前n个字节变成大写字符串;
head :截取字符串的前n个字符;
surprise!:隐藏功能,直接触发崩溃。
运行afl-fuzz对程序进行测试:

1
afl-fuzz -i inputs -o out ./vulnerable

inputs目录是输入的种子目录,由用户提供,应该是精心准备的样本以有效提高fuzz效率,可以看到系统提供的inputs目录中包含触发uhead的样例:

1
2
3
4
5
6
7
8
9
youlin@ubuntu:~/afl/afl-training/quickstart$ ls
afl-screenshot.png inputs Makefile out README.md vulnerable vulnerable.c
youlin@ubuntu:~/afl/afl-training/quickstart$ ls inputs/
head u
youlin@ubuntu:~/afl/afl-training/quickstart$ cat inputs/head
head 20 This string is going to be truncated at the 20th position.
youlin@ubuntu:~/afl/afl-training/quickstart$ cat inputs/u
u 4 capsme
youlin@ubuntu:~/afl/afl-training/quickstart$

fuzz结果(当时让他fuzz之后就去干其他事情了,其实很快就fuzz好了):

image-20240113005059484

out有相应的产出,其中crashes目录存储的是崩溃样本;queue目录存储的是成果触发新路径的样本即有趣的样本。

image-20240113005245259

crash输入演示

image-20240113010011207

使用AFL插桩程序(无源码)

使用的是hollk师傅提供的RV130X_FW_1.0.3.55.bin固件,首先使用binwalk解包

1
binwalk -Me RV130X_FW_1.0.3.55.bin

可以解出完整的文件系统

然后使用qemu用户态模拟看jsonparse的输入格式

1
2
3
4
5
6
7
youlin@ubuntu:~/rw/黑盒/_RV130X_FW_1.0.3.55.bin.extracted$ qemu-arm-static -L ./squashfs-root/ ./squashfs-root/usr/sbin/jsonparse 

usage: jsonparse [file]

Below is a sample parse result:
test2() json_object_get_string(input)=
{ "pagination_response_record": { "page_index": "1", "last_index": "1", "total_records": "2", "self_link": "https:\/\/api-stage.cisco.com\/software\/stage\/v2.0\/metadata\/udi\/NAME:RV215W,DESCR:Cisco RV215W Wireless N VPN Firewall, PID:RV215W-A-K9-NA, VID:V01, SN:CCQ163612VP\/mdf_id\/284436489\/software_type_id\/282487380\/current_release\/7.0.220.0?output_release=LATEST", "page_records": "25", "title": "Get Metadata by UDI, MDF ID & Image Names â<80><93> Download API" }, "metadata_response": { "metadata_trans_id": "9746", "metadata_id_list": { "udi": "NAME:RV215W,DESCR:Cisco RV215W Wireless N VPN Firewall, PID:RV215W-A-K9-NA, VID:V01, SN:CCQ163612VP", "mdf_id": "284436489", "software_list": { "platform_list": [ { "release_list": [ { "release_version": "1.1.0.5", "release_fcs_date": "2013-04-10 00:00:00.0", "image_details": [ { "image_guid": "8893B41F4FD3DF71FA3D03B636205BD613BEE766", "related_image": "N", "image_name": "RV215W_FW_1.1.0.5.bin", "image_size": "10912768", "image_checksums": { "md5_checksum": "0e1792082f4a13b8eb256e92ff5ce501" }, "image_description": "RV215W_FW_1.1.0.5.bin", "encryption_software_indicator": "Y", "image_level_docs": null }, { "image_guid": "187A05709F5BDEEEE38E4002AE014F4659859C2B", "related_image": "N", "image_name": "USB_dynamic_module_file.zip", "image_size": "116182", "image_checksums": { "md5_checksum": "1d6f78a9edbea935026bb8a2be5cc715" }, "image_description": "The USB dynamic module files of RV215W 1.1.0.5 version", "encryption_software_indicator": "Y", "image_level_docs": null } ], "release_level_docs": { "release_doc_name": "Release Notes and Open Source Documentation for 1.1.0.5", "release_doc_url": "http:\/\/www.cisco.com\/en\/US\/products\/ps9923\/prod_release_notes_list.html", "release_doc_type": "null" }, "security_advisory": "\/en\/US\/products\/ps9923\/prod_security_advisories_list.html" } ] } ], "software_type_id": "282487380", "software_type_name": "Wireless Router Firmware" }, "mdf_concept_name": "Cisco RV215W Wireless-N VPN Router" } }, "service_status": { "status": "success" } }

根据hollk师傅所说尽量用短一点的数据进行模糊测试,所以使用下面的数据

接着就是fuzz了

1
QEMU_LD_PREFIX=./squashfs-root/ ~/tools/AFLplusplus-stable/afl-fuzz -Q -i ./input/ -o ./output/ -- ./squashfs-root/usr/sbin/jsonparse @@

可以看到还没跑多久就跑出了两个crashes,直接ctrl+c终止,在output目录下看看两个crashes

直接看,我还是有点看不懂,建议直接放到程序去跑一遍

1
qemu-arm-static -L ./squashfs-root/ ./squashfs-root/usr/sbin/jsonparse ./output/default/crashes/id\:000000\,sig\:11\,src\:000000\,time\:48\,execs\:89\,op\:havoc\,rep\:8

可以看到程序是存在栈溢出的,利用的话就得具体分析了


初探afl-fuzz
http://blogyoulin.top/2024/01/13/初探afl-fuzz/
Author
John Doe
Posted on
January 13, 2024
Licensed under