AFL技术介绍

安装

1.下载最新源代码
2.安装

1
2
$ make
$ sudo make install

有源码测试

1.可执行文件的输入为一个文件时
用AFL对一个开源的加壳软件UPX进行了测试,记录一下具体过程:

1
2
3
4
5
6
7
$ git clone https://github.com/upx/upx.git
$ cd upx
$ vim Makefile
CC = /usr/local/bin/afl-gcc # 加这一句
$ cd src
$ vim Makefile
CXX ?= /usr/local/bin/afl-g++ # 将CXX改成afl-g++

编译upx还需要下载两个库否则会出错:

1
$ git submodule update --init --recursive # 下载lzma-sdk

下载ucl,并编译安装:

1
2
3
4
5
$ cd ucl-1.03
$ ./configure
$ make
$ sudo make install
export UPX_UCLDIR=/path/to/ucl-1.03

具体操作可以看官方文档
编译upx,并对upx进行测试:

1
2
3
4
$ make all # 会在src目录下产生upx.out可执行文件
$ mkdir afl_in afl_out
$ mv /usr/bin/uuencode afl_in
$ afl-fuzz -i afl_in -o afl_out ./src/upx.out @@

用AFL对UPX进行测试发现了好多crash,最后经过对crash进行分析,发现了一个UPX的漏洞
2.可执行文件的输入为标准输入时
编写源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void test (char *buf) {
int n = 0;
if(buf[0] == 'b') n++;
if(buf[1] == 'a') n++;
if(buf[2] == 'd') n++;
if(buf[3] == '!') n++;

if(n == 4) {
raise(SIGSEGV);
}
}

int main(int argc, char *argv[]) {
char buf[5];
FILE* input = NULL;
scanf("%4c", &buf);
test(buf);
return 0;
}

编译测试:

1
2
3
4
5
$ afl-gcc -o crash crash.c
$ su
$ vim afl_in/file
fanrong
# afl-fuzz -i afl_in -o afl_out ./crash

无源码测试

对只有二进制文件的情况进行fuzz,有两种方法:
1.对二进制文件进行插桩
这种方法是通过编译一个AFL版的qemu实现的

1
2
$ cd qemu_mode 
$ ./build_qemu_support.sh

编译安装完AFL版的qemu后,在进行模糊测试时,指定-Q选项即可。
可执行文件的输入为一个文件:

1
2
3
4
5
6
$ type readelf
readelf is /usr/bin/readelf
$ cp /usr/bin/readelf .
$ mkdir alf_in afl_out
$ cp test_elf afl_in
$ afl_fuzz -i afl_in -o afl_out -Q readelf -a @@ # -Q选项是在没有源码时,用qemu模拟程序执行的模式

2.进行传统的模糊测试,指定-n选项即可。

原理

AFL采用代码插桩的方法,对遗传算法进行指导,提高代码覆盖率。具体说来,插桩代码把当前地址放到一个全局变量中,类似一个hashmap的地图,设置遗传算法,能够产生不同路径的地图优先度高。
下图是对上文中afl-gcc编译的crash可执行文件的反汇编,可以看到有一些__afl_maybe_log插桩代码。
AFL的官方说明更加详细,本文只是对一些简单应用进行了说明。
reference
http://lcamtuf.coredump.cx/afl/
https://sourceforge.net/p/upx/discussion/6806/thread/0b5c5343/