点击蓝字 关注我们
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.tgz
tar xfz afl.tgz
cd 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 derived
from 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 derived
from 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! The
instrumentation will work (amazingly). Alas, unhandled exceptions do
not call abort(), so afl-fuzz would need to be modified to equate
non-zero exit codes with crash conditions when working with Java
binaries. 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 itself
works OK. This has nothing to do with us, but let's avoid triggering
that 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 is
AFL-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
就可以出现以下页面
页面总共分了几个部分,具体代表的意义可以查看官方文档
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 安全、移动安全、红蓝对抗等领域,善于利用黑客视角发现和解决网络安全问题。
团队自成立以来,圆满完成了多次国家级、省部级重要网络安全保障和攻防演习活动,并积极参加各类网络安全竞赛,屡获殊荣。
对信息安全感兴趣的小伙伴欢迎加入宸极实验室,关注公众号,回复『招聘』,获取联系方式。
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...