AFL

一、简单测试

1
2
3
4
5
6
7
8
9
10
11
int main(int argc, char const *argv[])
{
int a;
char buf[0x20];
read(0,&a,4);
if(a == 0x1234){
printf("You get it!\n");
read(0,buf,0x60);
}
return 0;
}

1.基础尝试

(1)白盒

①基础

首先使用简单的afl-clang-fast来编译

1
afl-clang-fast ./test.c -z execstack -fno-stack-protector -no-pie -z norelro -o afl_test

然后跑起来,速度大约是11.6k

image-20220930135610901

大概跑了6分钟,出现了第一个crash,查看一下,是大小为0x76

image-20220930140345079

然后蒸馏一下afl-tmin -i inputSeed -o outSeed programer

image-20220930141806144

大小变为了0x2d,减去开始的4个字节,为0x29,再看看IDA反汇编出来的

image-20220930142125276

可以看到确实距离rbp0x28,多溢出一个字节就可以覆盖到rbp了。

此外设置的标志int v4也在buf上面,而非正常编译的先声明的更靠近栈底。

另外实际gdb调试的,其main函数栈不再是通过leave ret来返回了,而是直接add rsp 0x38,而rbp为0

image-20220930142617012

main函数实际的返回地址距离buf0x28倒是也能说的过去,就是不知道为什么要这么做。

②尝试__AFL_INIT()

在源码中加入__AFL_INIT(),参考:

sakuraのAFL源码全注释(二)-安全客 - 安全资讯平台 (anquanke.com)

AFL-Training学习记录-安全客 - 安全资讯平台 (anquanke.com)

sakura师傅介绍说,目的是为了在某些情况下可以减少操作系统、链接与libc内部执行程序的成本

iskindar师傅介绍说,这是采用Deferred initialization的方式来提高AFL的性能,大概提高1.5x,最合适的地方是放在read函数前。也就是下面这几行代码。

1
2
3
#ifdef __AFL_HAVE_MANUAL_CONTROL
__AFL_INIT();
#endif

放入进去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

int main(int argc, char const *argv[])
{
int a;
char buf[0x20];
#ifdef __AFL_HAVE_MANUAL_CONTROL
__AFL_INIT();
#endif
read(0,&a,4);
if(a == 0x1234){
printf("You get it!\n");
#ifdef __AFL_HAVE_MANUAL_CONTROL
__AFL_INIT();
#endif
read(0,buf,0x60);
}
return 0;
}

之后正常编译

1
afl-clang-fast ./test.c -z execstack -fno-stack-protector -no-pie -z norelro -o afl_test

也相差无几,可能是程序太小,相关的libc库调用太少吧

image-20220930143810066

发现crash之后蒸馏得到的和不加__AFL_INIT()是一样的。

③尝试__AFL_LOOP(1000)

persistent模式,常常用来fuzz某些无状态的API

iskindar师傅介绍说,对于一些无状态的API库,可以复用进程来测试多个测试样例,从而减少fork系统调用的使用,进而减少OS的开销。

  • 状态:指的是交互过程中保存的会话信息,比如cookie、session

那么无状态的API指的就是在这个API里没有保存状态了。

先不使用__AFL_LOOP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "noStateAPI.h"

int main(int argc, char const *argv[])
{
int a;
char buf[0x60];
#ifdef __AFL_HAVE_MANUAL_CONTROL
__AFL_INIT();
#endif
read(0,&a,4);
//read(0,buf,0x60);
vul(&a,buf);
return 0;
}

库如下

1
2
3
4
5
6
7
8
#include "noStateAPI.h"
void vul(int* flag,char* data){
char buf[0x20];
if(*flag == 0x1234){
//strcpy(buf,data);
read(0,buf,0x60);
}
}

编译加FUZZ

1
2
afl-clang-fast ./testNoLoopAPI.c ./noStateAPI.c -z execstack -fno-stack-protector -no-pie -z norelro -o afl_API_noLoop_test
afl-fuzz -i otherIn -o afl_API_noLoop_out ./afl_API_noLoop_test

开始运行之后会有(odd, check syntax!),但是运行一段时间后就没有了,不太知道为啥

image-20220930194310537

最后大概五六分钟也能得到crash

加入__AFL_LOOP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "noStateAPI.h"

int main(int argc, char const *argv[])
{
int a;
char buf[0x60];

read(0,&a,4);
#ifdef __AFL_HAVE_MANUAL_CONTROL
while (__AFL_LOOP(1000)) {
#endif
vul(&a,buf);
#ifdef __AFL_HAVE_MANUAL_CONTROL
}
#endif
return 0;
}

然后编译FUZZ

1
2
afl-clang-fast ./testAPI.c ./noStateAPI.c -z execstack -fno-stack-protector -no-pie -z norelro -o afl_API_test
afl-fuzz -i otherIn -o afl_API_out ./afl_API_test

但是这个跑一个小时都没出结果,很奇怪,不知道为什么,难道说有read或者有比对值的代码

1
2
3
4
if(*flag == 0x1234){
//strcpy(buf,data);
read(0,buf,0x60);
}

就代表是有状态的API

(2)黑盒

此外使用正常的gcc编译后,采用黑盒测试时

1
afl-fuzz -Q -i in -o hei_out ./test

速度下降一大截

image-20220930142948733

不过还是能够发现crash,大概跑了十五六分钟。

image-20220930144540473

可以看到大小是0x4f,减去标志的4个字节即为0x4b,与期待的大概0x39字节还是相差一点

image-20220930144649388

此外黑盒好像没办法蒸馏

image-20220930144817799

二、变异策略

详见:AFL文件变异一览 - 记事本 (rk700.github.io)