My Fuzzing Development Diary

本文最后更新于:2023年9月1日 上午

该文章将持续记录本人在 vul337 实习过程中,参与的 fuzz 项目的开发经历。

0x01 了解 fuzz

来 vul 337 实习之前,我对 fuzz 处于一无所知的状态,当初抱着“我可能本科毕业后需要找工作,学一下这个看起来很厉害的技术,到时候有口饭吃”的想法,给 chao 老师发送了邮件,申请成为337的一个实习生。


从综述论文开始

刚刚入组实习,X学长为我打开了 fuzz 的大门,我先阅读了 Fuzzing A Survey 这篇综述论文;这篇论文概述了 fuzzing 技术的原理、发展,以及不同类型 fuzzing 的区别。

Fuzzing 是什么

如下图:

自动化地向目标程序(被测试程序)输入测试案例,并从执行结果中获取一些感兴趣的信息,这就是 Fuzzing。

关于 Fuzzing 的流程

我们向被测试的程序输入 cases,待程序执行完成后,统计执行结果,以上步骤进行若干轮循环后,集中分析被统计的感兴趣的结果。而后根据日志信息,尝试复现触发的 crash ,并进行分析。

被测试程序、初始输入 target、input

被测试程序就是一个具有向外 api 接口的程序或函数;

初始输入是未经过变异的、我们根据被测试程序构造出来的一串字符串 or binary input。

变异 mutate

变异(mutate)是指每轮 Fuzzing 中,对 input 进行一定的改变的操作,例如:

  • input 为 123 ,经过一定的变异策略,变异后的输入会变为 124 or etc;

    常见的变异策略有:

  • bitflip:位翻转,将 input 中某一位的0改为1,或1改为0;

  • splice:拼接,将多个输入按某些顺序地拼接为一个较长的输入;

  • havoc:大破坏,即胡乱地变化;

  • Radamsa:一种集成了多种基本变异策略的mutator;(后面我会详细叙述,因为这个是我负责插件化写入 xfuzz 项目的一个 mutator)

感兴趣的结果,crash

感兴趣的结果包括:发现了新的执行路径、触发了一些 error 、程序崩溃掉了等等;

复现、crash分析

这个部分目前我还不是很会做,不好意思。

至此,我了解到了 Fuzzing 这个技术是如何对程序进行测试的,以及该流程中一些名词的具体含义。


接下来,我学习了AddressSanitizer: A Fast Address Sanity Checker这篇文章,及其主要技术——ASAN。

关于 ASAN

什么是 ASAN

简单来说,ASAN 就是一个通过插桩实现的,检测内存非法访问的工具。

ASAN 带给我了什么启发

ASAN 正式将插桩(instrumentation)这个概念介绍给了我。

更多关于 ASAN 的详细内容我之前的博客有写。


至此,我花了三天的时间将 Fuzzing 大概是个啥,搞清楚了一点;

随后,就开始探索这个最经典的 Fuzzer —— AFL

0x02 从 AFL 探索起

AFL 可谓是初学 fuzz 一定会接触的一个 fuzzer,我的第一个 fuzz 软件也是 AFL

什么是AFL

  • 全称 American Fuzzy Lop

  • google 公司的工程师 Michal Zalewski 开发

  • 可以高效地对目标进行模糊测试

  • 使用 coverage-based 基于覆盖率的 fuzz

  • 可以对 closed source program 进行测试

下载、安装AFL

请参考官方文章:https://lcamtuf.coredump.cx/afl/QuickStartGuide.txt

关于插桩

插桩(instrumentation)是指向目标程序内植入一些代码,以达到监测状态,发现执行路径的作用

目前有编译时插桩,容易看出,这种插桩方法需要有源码;

也有 qemu 等插桩,以及其他 tricks,无源码的插桩方法仍需多研究。(注:正在研究,2023/08/31)

通过案例去感受

可以简单写一个 C 文件,核心函数如下

1
2
3
4
5
6
7
8
void foo()
{
if(buf[0] == 'f')
if(buf[1] == 'o')
if(buf[2] == 'o')
if(buf[3] == '!')
abort(); //crash!
}

简单来说,就是 input 为 foo!时,会触发 abort()函数,会报出 crash

1
./afl-fuzz -i testcase_dir -o findings_dir --

afl 安装好后,使用上面的命令运行,-i参数后为输入的目录,该目录下有你的输入文件;-o后则是存放 crash 信息的目录。

:真正使用 afl 时没有这么顺利,请善用 google 进行搜索,将错误信息复制到 google 内,一般都会有答案的 ( 0.o )

0x03 xfuzz

xfuzz,一种基于插件化的多策略集合 fuzzer。如今新的 fuzzing 策略层出不穷,四大顶刊上有关 fuzzing 的论文也是数不胜数,这些论文都提出了一些新的更高效的 fuzzing 策略,而我们想真正的将其利用起来却不太方便,单独看某个策略,会发现局限性比较大。而 xfuzz 项目的目的就是将这些策略以插件化的方式集成到项目中来,供使用者自由调度、自行搭配

xfuzz 是我在 vul337 实验室的参与的一个项目,我主要负责一些插件的编写。

xfuzz的历史

根据 gitlab 上的记录,xfuzz 项目早在 2021年(可能更早)就由一位组内的学长进行编写、维护。而最近重启之,应该是在23年的3月左右。

其间的一两年,xfuzz应该是停止开发的状态。

Persistent Mode

Persistent Mode 是我负责的第一个插件,简单来说,Persistent Mode 通过在某些情况下不使用程序的fork()而以修改源码,利用程序内的循环达到反复测试的目的

关于 Persistent Mode 我之前写过两篇博客,但回头看来,又有许多可以改进之处,故留个小坑,过些时于本文中详述下 Persistent Mode。

(TODO:详细总结 Persistent Mode 于此)

Radamsa Mutator

Frida Mode

0x05 Closed Source Fuzz

由于目前的任务是开发 executor 方向的 frida mode 插件,故着手了解一下无源码 fuzzing

参考文章:https://airbus-seclab.github.io/AFLplusplus-blogpost/

无源码插桩

实践一下

今日(2023/08/31),基于昨天配置好的 AFL++,开启了以下的尝试。

为什么不是 AFL?因为 AFL 的 qemu mode 需要 python2 而非 python3,且一些依赖的库过时严重,安装之的时候,把我虚拟机 Ubuntu Desktop 搞崩溃了,听说是显卡驱动的原因,反正修了一天没修好,所以决定用 AFL++ !

这是测试程序源代码:

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
int main(int argc, char** argv) {
char buf[100];
while (__AFL_LOOP(1000)) {
memset(buf, 0, 100);
read(0, buf, 100);
if (buf[0] == 'f') {
printf("one\n");
if (buf[1] == 'o') {
printf("two\n");
if (buf[2] == 'o') {
printf("three\n");
if (buf[3] == '!') {
printf("four\n");
abort();
}
}
}
}
} // end of while (__AFL_LOOP(1000))
return 0;
}

简而言之,就是输入的字符串为foo!时,会触发abort(),检测到 crash。

使用以下命令进行 fuzz:

1
afl-fuzz -i input -o output -Q vuls/foo
  • -i, -o 参数后为输入/输出文件夹;(我的 input 设置为 foo)
  • -Q 参数开启 Qemu mode,无源码 fuzz;
  • vuls/foo 是用 gcc 编译的上面的目标 C 文件;

测试结果:

不到一分钟,报出 crash:

进入output/default/crashes文件夹

使用xxd命令查看可得:

触发 crash 的输入正是 foo!

对比编译时插桩

使用以下指令进行编译/插桩

1
afl-clang-fast vuls/foo.c -o vul1

然后用以下指令进行 fuzz

1
afl-fuzz -i input -o output vuls/vul1

注:此处已不再需要-Q参数,因为是已经插桩好了的程序

运行时:

也是很快的速度就报出了 crash

经过对比,不难发现:

  • 有源码会比无源码快 2-5 倍;

  • 都很准确;

    无源码的体验暂时到这,接下来的计划:

  • 读 qemu mode 代码,了解下其运行时插桩的逻辑;

  • 了解 frida;

  • more…

qemu-mode

AFL++ 采用一种补丁后的 Qemu 模拟器的 User mode 进行覆盖率采集


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!