dig into the afl

本文最后更新于:2023年12月28日 晚上

0x0:写在所有之前

在笔者看来,fuzz无疑是一个天才的想法,通过自动化的大量的随机数据测试,进行漏洞挖掘。

笔者一直很想学习这一方面的知识,在粗略的拜读完Fuzzing: A Survey for Roadmap之后,恰好暑假也有点空闲时间(主要是放了两礼拜假什么也没干有点良心不安),便打算从afl入手来学习fuzz。

0x1:初探

下载安装什么的网上可以自己找捏wakuwaku

首先呢可以自己先随遍写一些测试用例

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
#include <stdio.h> 
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>

int test(char *str)
{
int len = strlen(str);
if(str[0] == 'A' && len == 6)
{
raise(SIGSEGV);
}
else if(str[1] == 'F' && len == 66)
{
raise(SIGSEGV);
}
else if(str[2] == 'L' && len == 666)
{
raise(SIGSEGV);
}
else
{
puts(" OK ,it is safe");
}
return 0;
}

int main(int argc, char *argv, char *envp)
{
puts("welcome to korey0sh1's AFL test!!");
printf("pls input your content: ");
char buf[0x100]={0};
gets(buf);
test(buf);

return 0;
}

分别用gcc和afl-gcc编译

1
2
gcc ./test.c -o test
/path/to/afl-gcc ./test.c -o afl_test

然后用ida打开看看这两个elf文件有什么不同

img

可以看到afl_test里面出现了许多奇怪的东西,这其实就是afl-gcc在编译时对测试源码进行了插桩(主要是**__afl_maby_log**函数)

这些被插入的桩总的来讲类似于传感器,实现对branch、edge(分支、边缘)覆盖率的捕获和分支点的统计,在后续的fuzz测试中会根据这些反馈信息进行新的路径探索和测试。(其实铸币笔者也刚刚在看afl源码,又回到了被源码支配的日子了呜呜呜)

接下来可以试着来真正fuzz一下这个test

先搞两个空文件夹存放输入样例和输出样例,fuzz_in里面touch 一个testcase随便写点什么。

1
2
3
4
mkdir fuzz_in
mkdir fuzz_out

/path/to/afl-fuzz -i fuzz_in/ -o fuzz_out/ ./afl_test

第一次尝试可能会报错需要设置一下core_pattern

1
2
su root
echo > /proc/sys/kernel/core_pattern

然后就可以愉快的afl_fuzz啦

进去后可以看到这样一个界面,相信大家英文都比我好,各种参数的含义korey看得懂得各位看官老爷也一定看懂嘻嘻。

img

由于铸币笔者只能用垃圾笔记本跑(乐色13900h),所以跑了大概一个小时跑出来了8个crash

接下来便是进到fuzz_out中看看每个crash是什么样的

1
2
3
用:
xxd [the name of crash]
可以查看触发crash的输入

img

crash0满足了buf[0] = ‘A’且len = 6

crash1满足了buf[1] = ‘F’且len = 66

crash2因为gets函数造成了栈溢出

遗憾的是buf[2] = ‘L’且len = 666的crash并未触发

之后便可以使用gdb将test文件和crash样例组合调试

1
2
gdb ./test
run < [the name of crash]

img

用bt回溯可以看到stack smashing,造成栈溢出了

0x2:Fuzzing 101

这是个github上的项目,专门来帮助那些想要把fuzz应用于实战的带手子们。

项目中有十分详细的步骤,笔者就不在此赘述。笔者仅在此记录一些收获

exercise 1:Xpdf

第一个example是 CVE-2019-13288

在version 4.01.01的xpdf中,Parser.cc中的Parser::getObj()函数将会被无限调用导致内存耗尽,最终使程序崩溃,达到DoS攻击的效果。

感觉afl++比afl好用点

【-M】:主fuzzer,配合【-S】多开从属fuzzer效率可以提高

当输入为文件时,需要使用【@@】,否则为标准输入

插桩完后恢复成正常的gcc编译

1
CFLAGS="-g -O0" CXXFLAGS="-g -O0"

exercise 2:libexif

在version 0.6.14 的libexif中,存在heap buffer overflow CVE-2009-3895以及out-of-bounds readCVE-2012-2836

这边插桩时使用的是afl-clang-lto

但libexif只是一个解析、保存、编辑exif数据的库,所以还要有相应的可执行文件(exif,编译的时候制定一下链接库文件夹)才能正常实现功能

就最后的调试而言,项目中使用的eclipse,具有图形化界面,笔者还是GDB用的习惯,直接

gdb –args 指定参数就行了

img

exercise 3:tcpdump

目前为止最折磨的一集

在version 4.9.2的 Tcpdump中,BOOTP协议数据包可以触发out-of-bouds Read,其实也算是overflow的一种 CVE-2017-13028

这次采用速度更快的afl-clang-lto(但nmd还是fuzz了10个小时才出来8个crash,不过一看hollk师傅的博客,用afl-clang-fast15个小时出一个crash,心里突然平衡了一点)

且在编译tcpdump和所对应的动态链接库时,加入了ASAN选项

ASAN(Address Sanitizer)是针对 C/C++ 的快速内存错误检测工具,在运行时检测 C/C++ 代码中的多种内存错误

铸币笔者也不是很懂,附带一个官方说明书AddressSanitizer — Clang 18.0.0git documentation (llvm.org)

后面执行运行tcpdump就能直接看到报错,感觉还是挺方便的,就是ASAN运行时所需内存有点大,fuzz的时候要加上 -m none 的参数解除内存限制

贴个图意思一下

img

img

exercise 4:LibTiff

逐渐熟练,笑

前面还是用ASAN和AFL++进行一个fuzz操作

在此次exercise中,项目作者用到了lcov,一个基于GCC代码覆盖测试工具gcov的图形前端

在编译的时候

1
export CFLAGS="--coverage" LDFLAGS="--coverage" 

编译完成后,进入tiff文件夹

1
2
3
4
5
6
7
8
# 重置计数器
lcov --zerocounters --directory ./
# 返回包含每个插桩代码行零覆盖率的基于行的覆盖率数据文件
lcov --capture --initial --directory ./ --output-file app.info
# 运行程序
$HOME/fuzzing_tiff/install/bin/tiffinfo -D -j -c -r -s -w $HOME/fuzzing_tiff/tiff-4.0.4/test/images/palette-1c-1b.tiff
# 将当前状态覆盖状态保存到【app2.info】文件中
lcov --no-checksum --directory ./ --capture --output-file app2.info

笔者这边把前10个crash试了试,感觉代码覆盖率不是很理想

img

exercise 5:LibXml2

难绷,crash出不来就出不来吧

img

第一次完全没有看项目里的solution,自己解决,编译的时候看缺了个Python.h的头文件还自己编译了一个python(笑哭),好在最后成功跑起来了。

-x 】:指定字典

-D 】: 确定性突变

ASAN真好用,嘿嘿嘿嘿

exercise 6:GIMP

寄,我也不知道为什么fuzz101提供的demo会直接crash,怎么直接会有memory leak啊

img

试了好几个xcf文件都是这样

exercise 7: VLC

更抽象的一集

exercise 8: Adobe Reader

无源码直接fuzzelf文件


dig into the afl
http://example.com/2023/07/18/fuzz0/
作者
korey0sh1
发布于
2023年7月18日
许可协议