0%

fuzz入门+AFL的使用

Fuzz 简述

模糊测试(Fuzzing)是一种基于黑盒的自动化软件模糊测试技术,简单的说一种懒惰且暴力的技术融合了常见的以及精心构建的数据文本进行网站、软件安全性测试

好用的工具:AFL、LibFuzzer、honggfuzz

AFL 简述

AFL(American Fuzzy Lop)是由安全研究员 Michał Zalewski 开发的一款基于覆盖引导(Coverage-guided)的模糊测试工具

其工作流程大致如下:

  • 从源码编译程序时进行插桩,以记录代码覆盖率(Code Coverage)
  • 选择一些输入文件,作为初始测试集加入输入队列(queue)
  • 将队列中的文件按一定的策略进行“突变”
  • 如果经过变异文件更新了覆盖范围,则将其保留添加到队列中
  • 上述过程会一直循环进行,期间触发了 crash 的文件会被记录下来

1658796625409

参考:AFL漏洞挖掘技术漫谈(一)

AFL 安装

AFL 是一种面向安全的模糊器,它采用新型的编译时检测和遗传算法来自动发现有趣的测试用例,这些用例会触发目标二进制文件中的新内部状态

安装 llvm 环境:

1
2
sudo apt-get install clang llvm-dev llvm
sudo apt-get install clang-3.4

使用如下命令下载 AFL 安装包:

1
wget https://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz

在解压后的文件中执行 make 进行编译,输入 afl-fuzz 以验证是否安装成功:

1
2
➜  afl-2.52b afl-fuzz
afl-fuzz 2.52b by <lcamtuf@google.com>

安装好的目录中应该有如下的文件:

1
2
3
4
5
6
7
8
9
➜  afl-2.52b ls
afl-analyze afl-fuzz afl-showmap debug.h Makefile
afl-analyze.c afl-fuzz.c afl-showmap.c dictionaries qemu_mode
afl-as afl-g++ afl-tmin docs QuickStartGuide.txt
afl-as.c afl-gcc afl-tmin.c experimental README
afl-as.h afl-gcc.c afl-whatsup hash.h testcases
afl-clang afl-gotcpu alloc-inl.h libdislocator test-instr.c
afl-clang++ afl-gotcpu.c as libtokencap types.h
afl-cmin afl-plot config.h llvm_mode
  • afl-gcc 和 afl-g++ 分别对应的是 gcc 和 g++ 的封装
  • afl-clang 和 afl-clang++ 分别对应 clang 的 c 和 c++ 编译器封装À
  • afl-fuzz 是 AFL 的主体,用于对目标程序进行 fuzz
  • afl-analyze 可以对用例进行分析,通过分析给定的用例,看能否发现用例中有意义的字段
  • afl-qemu-trace 用于 qemu-mode,默认不安装,需要手工执行 qemu-mode 的编译脚本进行编译,后面会介绍
  • afl-plot 生成测试任务的状态图
  • afl-tmin 和 afl-cmin 对用例进行简化
  • afl-whatsup 用于查看 fuzz 任务的状态
  • afl-gotcpu 用于查看当前 CPU 状态
  • afl-showmap 用于对单个用例进行执行路径跟踪

AFL 使用

AFL 需要一些初始输入数据(也叫种子文件)作为 Fuzzing 的起点,这些输入甚至可以是毫无意义的数据,AFL 可以通过启发式算法自动确定文件格式结构

AFL 源码中自带了一个语料库:testcases

测试代码如下:

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

int vuln(char *str)
{
int len = strlen(str);
if(str[0] == 'A' && len == 66)
{
raise(SIGSEGV);
}
else if(str[0] == 'F' && len == 6)
{
raise(SIGSEGV);
}
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;
}

使用 afl-gcc 进行插桩编译:

1
2
➜  test afl-gcc test.c -o test_afl
➜ test gcc test.c -o test_gcc
  • afl-gcc 插桩编译后,fuzz 的速度会快一些

接下来就可以进行 fuzz 了:

1
➜  test afl-fuzz -i ./fuzz_in -o ./fuzz_out ./test_afl -f
  • fuzz_in 为语料库,我这里直接使用 AFL 自带的 testcases
  • fuzz_out 就是输出的内容

AFL 状态窗口如下:

1658801946787

详细介绍如下:

1658802035629

  • Process timing:Fuzzer 运行时长、以及距离最近发现的路径、崩溃和挂起经过了多长时间
  • Overall results:Fuzzer 当前状态的概述(尤其注意 uniq crashes)
  • Cycle progress:我们输入队列的距离
  • Map coverage:目标二进制文件中的插桩代码所观察到覆盖范围的细节
  • Stage progress:Fuzzer 现在正在执行的文件变异策略、执行次数和执行速度
  • Findings in depth:有关我们找到的执行路径,异常和挂起数量的信息
  • Fuzzing strategy yields:关于突变策略产生的最新行为和结果的详细信息
  • Path geometry:有关 Fuzzer 找到的执行路径的信息
  • CPU load:CPU利用率

Crashes 分析

fuzz_out->crashes 中有许多 crashes 信息,在 AFL 源码的 experimental/crash_triage 目录中有一个名为 triage_crashes.sh 的脚本,可以帮助我们触发收集到的 crashes

1
2
3
4
5
6
7
➜  test ./triage_crashes.sh ./fuzz_out ./test_afl 2>&1 | grep SIGNAL
+++ ID 000000, SIGNAL 11 +++
+++ ID 000001, SIGNAL 06 +++
+++ ID 000002, SIGNAL 06 +++
+++ ID 000003, SIGNAL 06 +++
+++ ID 000004, SIGNAL 06 +++
+++ ID 000005, SIGNAL 11 +++
  • “11”代表了 SIGSEGV 信号,有可能是因为缓冲区溢出导致进程引用了无效的内存
  • “6”代表了 SIGABRT 信号,通常由 libc 和其他库在出现严重错误时中止程序(也有可能是触发 canary 产生的错误)

AFL 练习

项目地址:Exercises to learn how to fuzz with American Fuzzy Lop