点击蓝字 关注我们
0x00 前言
AFL则是Fuzzing的一个很好用的工具,全称是American Fuzzy Lop,由Google安全工程师Michał Zalewski开发的一款开源Fuzzing测试工具,可以高效地对二进制程序进行Fuzzing,挖掘可能存在的内存安全漏洞,如栈溢出、堆溢出、UAF、Double free等。由于需要在相关代码处插桩,因此AFL主要用于对开源软件进行测试。当然配合QEMU等工具,也可对闭源二进制代码进行Fuzzing,但执行效率会受到影响。
0x01 开始Fuzz!
安装
wget http://lcamtuf.coredump.cx/afl.tgztar xfz afl.tgzcd afl*/sudo make install
当补全出以下命令的时候,代表安装成功。
copy师傅的测试用例代码,但是现在先不用,我们先分析一下afl-gcc.c源码
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;}
0x02 源码分析
我们今天主要用到了
afl-gcc和afl-fuzz,所以这里就分析这两个的源码。首先看一下
afl-gcc.c中的find_as。可以使用
source Insight这个工具来读源码效果很不错,下面是效果图。
afl_gcc.c
find_as
static u8* as_path; /* Path to the AFL 'as' wrapper */static u8** cc_params; /* Parameters passed to the real CC */static u32 cc_par_cnt = 1; /* Param count, including argv0 */static u8 be_quiet, /* Quiet mode */clang_mode; /* Invoked as afl-clang*? *//* Try to find our "fake" GNU assembler in AFL_PATH or at the location derivedfrom argv[0]. If that fails, abort. */static void find_as(u8* argv0) {u8 *afl_path = getenv("AFL_PATH");u8 *slash, *tmp;if (afl_path) {tmp = alloc_printf("%s/as", afl_path);if (!access(tmp, X_OK)) {as_path = afl_path;ck_free(tmp);return;}ck_free(tmp);}slash = strrchr(argv0, '/');if (slash) {u8 *dir;*slash = 0;dir = ck_strdup(argv0);*slash = '/';tmp = alloc_printf("%s/afl-as", dir);if (!access(tmp, X_OK)) {as_path = dir;ck_free(tmp);return;}ck_free(tmp);ck_free(dir);}if (!access(AFL_PATH "/as", X_OK)) {as_path = AFL_PATH;return;}FATAL("Unable to find AFL wrapper binary for 'as'. Please set AFL_PATH");}
首先获取 AFL_PATH环境变量的值,并使用getenv函数存储到afl_path指针中;如果
afl_path不为空,则尝试在afl_path目录下查找afl-as可执行文件。首先使用alloc_printf函数将afl_path和/as拼接成一个完整路径;通过access函数判断该路径是否可执行,如果可执行的话将as_path指向afl_path,并free释放tmp;如果
afl_path为空或者查找失败,尝试在argv[0]所在目录下查找afl-as可执行文件。首先使用strrchr函数在argv[0]中查找最后一个'/'字符的位置,将其替换为0并将结果保存至slash指针中;然后使用ck_strdup函数复制argv[0],保存其结果至dir指针中,将slash位置的字符重新赋值为'/',这样dir就是一个指向argv[0]所在目录的字符串。接下来使用alloc_printf函数将dir和/afl-as拼接为一个完整路径字符串tmp;最后使用access函数检查该路径是否可以执行,如果可以执行则将as_path指向dir并使用ck_free释放掉临时路径字符串tmp和dir。如果以上查找均失败,则尝试在默认路径"
/usr/local/lib/as"下查找afl-as可执行文件。如果可执行则将as_path指向该路径,并使用return函数退出;否则则报错退出。源码中已经给出了注释此函数的作用,尝试查找
AFL模糊测试工具中的“伪”GNU汇编器(即afl-as可执行文件)的路径,并将as_path指向该路径。最后如果没找到,则输出报错信息结束程序。这是
as使用:https://blog.csdn.net/K346K346/article/details/89088671)
/* Try to find our "fake" GNU assembler in AFL_PATH or at the location derivedfrom argv[0]. If that fails, abort. */
edit_params
static void edit_params(u32 argc, char** argv) {u8 fortify_set = 0, asan_set = 0;u8 *name;u8 m32_set = 0;cc_params = ck_alloc((argc + 128) * sizeof(u8*));name = strrchr(argv[0], '/');if (!name) name = argv[0]; else name++;if (!strncmp(name, "afl-clang", 9)) {clang_mode = 1;setenv(CLANG_ENV_VAR, "1", 1);if (!strcmp(name, "afl-clang++")) {u8* alt_cxx = getenv("AFL_CXX");cc_params[0] = alt_cxx ? alt_cxx : (u8*)"clang++";} else {u8* alt_cc = getenv("AFL_CC");cc_params[0] = alt_cc ? alt_cc : (u8*)"clang";}} else {/* With GCJ and Eclipse installed, you can actually compile Java! Theinstrumentation will work (amazingly). Alas, unhandled exceptions donot call abort(), so afl-fuzz would need to be modified to equatenon-zero exit codes with crash conditions when working with Javabinaries. Meh. */#ifdef __APPLE__if (!strcmp(name, "afl-g++")) cc_params[0] = getenv("AFL_CXX");else if (!strcmp(name, "afl-gcj")) cc_params[0] = getenv("AFL_GCJ");else cc_params[0] = getenv("AFL_CC");if (!cc_params[0]) {SAYF("n" cLRD "[-] " cRST"On Apple systems, 'gcc' is usually just a wrapper for clang. Please use then"" 'afl-clang' utility instead of 'afl-gcc'. If you really have GCC installed,n"" set AFL_CC or AFL_CXX to specify the correct path to that compiler.n");FATAL("AFL_CC or AFL_CXX required on MacOS X");}if (!strcmp(name, "afl-g++")) {u8* alt_cxx = getenv("AFL_CXX");cc_params[0] = alt_cxx ? alt_cxx : (u8*)"g++";} else if (!strcmp(name, "afl-gcj")) {u8* alt_cc = getenv("AFL_GCJ");cc_params[0] = alt_cc ? alt_cc : (u8*)"gcj";} else {u8* alt_cc = getenv("AFL_CC");cc_params[0] = alt_cc ? alt_cc : (u8*)"gcc";}}while (--argc) {u8* cur = *(++argv);if (!strncmp(cur, "-B", 2)) {if (!be_quiet) WARNF("-B is already set, overriding");if (!cur[2] && argc > 1) { argc--; argv++; }continue;}if (!strcmp(cur, "-integrated-as")) continue;if (!strcmp(cur, "-pipe")) continue;if (!strcmp(cur, "-m32")) m32_set = 1;if (!strcmp(cur, "-fsanitize=address") ||!strcmp(cur, "-fsanitize=memory")) asan_set = 1;if (strstr(cur, "FORTIFY_SOURCE")) fortify_set = 1;cc_params[cc_par_cnt++] = cur;}cc_params[cc_par_cnt++] = "-B";cc_params[cc_par_cnt++] = as_path;if (clang_mode)cc_params[cc_par_cnt++] = "-no-integrated-as";if (getenv("AFL_HARDEN")) {cc_params[cc_par_cnt++] = "-fstack-protector-all";if (!fortify_set)cc_params[cc_par_cnt++] = "-D_FORTIFY_SOURCE=2";}if (asan_set) {/* Pass this on to afl-as to adjust map density. */setenv("AFL_USE_ASAN", "1", 1);} else if (getenv("AFL_USE_ASAN")) {if (getenv("AFL_USE_MSAN"))FATAL("ASAN and MSAN are mutually exclusive");if (getenv("AFL_HARDEN"))FATAL("ASAN and AFL_HARDEN are mutually exclusive");cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";cc_params[cc_par_cnt++] = "-fsanitize=address";} else if (getenv("AFL_USE_MSAN")) {if (getenv("AFL_USE_ASAN"))FATAL("ASAN and MSAN are mutually exclusive");if (getenv("AFL_HARDEN"))FATAL("MSAN and AFL_HARDEN are mutually exclusive");cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";cc_params[cc_par_cnt++] = "-fsanitize=memory";}if (!getenv("AFL_DONT_OPTIMIZE")) {/* On 64-bit FreeBSD systems, clang -g -m32 is broken, but -m32 itselfworks OK. This has nothing to do with us, but let's avoid triggeringthat bug. */if (!clang_mode || !m32_set)cc_params[cc_par_cnt++] = "-g";cc_params[cc_par_cnt++] = "-g";cc_params[cc_par_cnt++] = "-O3";cc_params[cc_par_cnt++] = "-funroll-loops";/* Two indicators that you're building for fuzzing; one of them isAFL-specific, the other is shared with libfuzzer. */cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1";cc_params[cc_par_cnt++] = "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1";}if (getenv("AFL_NO_BUILTIN")) {cc_params[cc_par_cnt++] = "-fno-builtin-strcmp";cc_params[cc_par_cnt++] = "-fno-builtin-strncmp";cc_params[cc_par_cnt++] = "-fno-builtin-strcasecmp";cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp";cc_params[cc_par_cnt++] = "-fno-builtin-memcmp";cc_params[cc_par_cnt++] = "-fno-builtin-strstr";cc_params[cc_par_cnt++] = "-fno-builtin-strcasestr";}cc_params[cc_par_cnt] = NULL;}/* Main entry point */
edit_params函数主要是用来处理用户传入的编译器参数,生成最终的编译器参数列表cc_params。首先,它会动态分配一个内存空间,用于存储编译器选项,并尝试获取argv[0]中最后一个'/'字符出现的位置,将其后面的字符串赋给name。接着,根据name的值判断当前是否处于afl-clang模式,如果是,则设置相应标志和环境变量,并添加相应的编译参数。否则,根据name的值添加相应的编译参数。然后,它遍历剩余的参数列表,将非特定参数添加到
cc_params中。在此过程中,会跳过-B -integrated-as -pipe这三个特定的编译器选项,因为edit_params函数会自动添加这些选项。而对于其他特定的编译器参数(如"-fsanitize=address"等),它会根据参数名称来设置相应的标志,并将该参数添加到cc_params中。最后,它根据一些环境变量(如
AFL_HARDEN、AFL_USE_ASAN、AFL_USE_MSAN等)向cc_params中添加一些特殊的编译器参数,并最终以NULL作为参数列表的结束标志。main
int main(int argc, char** argv) {if (isatty(2) && !getenv("AFL_QUIET")) {SAYF(cCYA "afl-cc " cBRI VERSION cRST " by <[email protected]>n");} else be_quiet = 1;if (argc < 2) {SAYF("n""This is a helper application for afl-fuzz. It serves as a drop-in replacementn""for gcc or clang, letting you recompile third-party code with the requiredn""runtime instrumentation. A common use pattern would be one of the following:nn"" CC=%s/afl-gcc ./configuren"" CXX=%s/afl-g++ ./configurenn""You can specify custom next-stage toolchain via AFL_CC, AFL_CXX, and AFL_AS.n""Setting AFL_HARDEN enables hardening optimizations in the compiled code.nn",BIN_PATH, BIN_PATH);exit(1);}find_as(argv[0]);edit_params(argc, argv);execvp(cc_params[0], (char**)cc_params);FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]);return 0;}
主要是有三块调用,
find_as、edit_params、execvp,先调用find_as(argv[0])获取使用的汇编器,再调用edit_params(argc, argv)对编译选项进行编辑,再通过execvp去进行编译。还有一个
afl_fuzz.c,8000行代码分析起来篇幅过长,准备下一次另开一篇文章进行分析。
0x03 AFL 插桩编译(有源码)
3.1 基本信息
先是用
afl-gcc编译源代码,然后创建两个文件夹in和out作为fuzz的输入和输出目录,in目录还需要创建一个testcase文件。启动afl-fuzz程序,将in文件夹内的testcase文件的内容(文件内容随便输入即可)作为程序的输入执行程序,afl会在这个输入的基础上进行自动变异输入,使得程序产生crash。编译程序
afl-gcc -g -o afl_test demo.c,共对程序进行了10次插桩。
IDA打开编译后文件,能看到多个__afl_maybe_log,这是afl-gcc编译时通过afl-as生成的,当执行到这段代码,fuzzer知道这段代码被触发,从而统计每次输入样本的边缘覆盖率。
运行会出现此错误,设置一下
core_pattern即可。
echo core >/proc/sys/kernel/core_pattern然后再一次执行 afl-fuzz -i in -o out ./afl_test就可以出现以下页面
3.2 页面介绍
页面总共分了几个部分,具体代表的意义可以查看官方文档
https://lcamtuf.coredump.cx/status_screen.txt接下来就由我为大家翻译一下(机翻)
Process timing可以理解为状态栏,主要是展示程序的运行时间、执行最新路径的时间、崩溃、超时的时间。
overall results这里展示的程序运行周期,崩溃次数、总路径数、超时次数。
开始扫描的时候,显示为洋红色,如果发现新的,则显示为黄色--蓝色--绿色,绿色的时候一般就没有价值了,
Ctrl+c就行了。还有好多,基本上看界面上的基本也能知道这个代表什么意思,就不一一说明了,可以看官方文档(其实就是我懒)。
3.3 fuzz结果
经过一丢丢的时间,可以看一下我们
Fuzz出来的crash,可以在out目录找到crashes文件夹,来分析在什么地方触发了崩溃。
2.查看第一个,可以发现这个是符合我们代码中的这一段,开头为F长度为6。
else if(str[0] == 'F' && len == 6)这里就是栈溢出导致的
crash。
再往下看,这个明显就是符合我们这段代码 fuzz出来的结果
if(str[0] == 'A' && len == 66)符合格式化字符串导致的
crash
0x04 无源码 Fuzz
上面是通过 afl-gcc进行编译插桩进行的fuzz,大多数情况我们都是无源码的。所以我们下面就用gcc来编译进行fuzz
gcc demo.c -g -o gcc_test直接快进到
fuzz,指定一下-Q
3.哎嗨?报错了
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;}0
我们可以使用以下办法。
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
17秒便有了14个crash可以简单看一下
可以看到
fuzz出来有符合代码规则的还有栈溢出造成的crash,还有很多就没有一一去看。
总结
鄙人才疏学浅难免有些地方解释的不到位,还请各位师傅斧正!
这里只是简单了解使用了一下此工具,连入门都算不上,AFL可以根据自己的需要定制魔改,后续也会继续学习。
Reference:
https://xz.aliyun.com/t/4314#toc-2
https://blog.csdn.net/m0_60363092/article/details/122105234
https://bbs.kanxue.com/thread-273639.htm
https://bbs.kanxue.com/thread-265936.htm
https://bbs.kanxue.com/thread-276769.htm
宸极实验室隶属山东九州信泰信息科技股份有限公司,致力于网络安全对抗技术研究,是山东省发改委认定的“网络安全对抗关键技术山东省工程实验室”。团队成员专注于 Web 安全、移动安全、红蓝对抗等领域,善于利用黑客视角发现和解决网络安全问题。
团队自成立以来,圆满完成了多次国家级、省部级重要网络安全保障和攻防演习活动,并积极参加各类网络安全竞赛,屡获殊荣。
对信息安全感兴趣的小伙伴欢迎加入宸极实验室,关注公众号,回复『招聘』,获取联系方式。
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……




还没有评论,来说两句吧...