文章目录
- 【工具】AFL+Unicorn|二进制程序模糊测试基础工具(AFL+Unicorn)
- 写在最前
- 1. 系统环境
- 2. 软件版本
- 3. 背景知识
- 3.1 AFL vs AFLplusplus
- 3.2 QEMU vs Unicorn
- 3.3 Unicorn vs UnicornAFL
- 4. 工具安装
- 4.1 Ubuntu18
- 4.2 Ubuntu 20~22
- 4.3 收尾
- 5. Python 测试用例简单讲解
- 5.1 复杂使用示例
- 5.2 官方使用示例
【工具】AFL+Unicorn|二进制程序模糊测试基础工具(AFL+Unicorn)
写在最前
这篇文章参与华中大开源俱乐部开源工坊的“模糊测试理论与工具实践”分享,关于挖掘二进制程序漏洞。本篇没讲太技术细节的东西,就是简单介绍下工具的使用方式,便于分享后给同学们进行查漏补缺。【但我相信这种详细程度估计也是博客里罕见的了orz】
改天简单做个视频更清晰些,到时贴这里,关注更新。
1. 系统环境
- 系统:Linux,Ubuntu 18~22。
官方开发的 AFL 都是在 Linux 平台上使用的,
官方给的编译脚本都是 sh 格式的。
至于 Unicorn,它有 Python 版本的 API,其实应该是可以在 Windows 下使用的,但我不推荐这种使用方法,因为它也更新的很快。如果要从源码安装的话,要装 cmake 去编译它,Windows 下挑一个 cmake 版本麻烦太多了。
2. 软件版本
- AFLPlusplus:Release v4.20c.tar.gz 【2024年4月13日发布】
- UnicornAFL:AFLPlusplus 的对应版本,写在
AFLplusplus/unicorn_mode/UNICORNAFL_VERSION
里,这个版本对应的是 commit id 63aab0f。
3. 背景知识
如果对模糊测试的工作流程都不太熟的话,建议去搜一搜更基础的文章先,比如 AFL-FUZZ使用笔记(1),这篇文章开头的简介就还不错,我直接搬过来了【官方教程也都很详细只不过是英文的,接下来会继续介绍官方仓库之间的关系,所以看完简介就可以往后面继续看了、不用再重新找官方仓库了、我担心你们找偏了、因为我就找偏过】:
简单来说,模糊测试就是字面的意思,在测试一个程序的时候,把模糊化的数据批量喂给程序,然后等程序运行结束得到结果。被模糊化的数据就叫做输入文件,是初始测试集;已经模糊化了的数据,就叫做输入队列,这个模糊过程常常被叫做“按一定的策略进行突变“。
3.1 AFL vs AFLplusplus
AFL 是谷歌开发的一个2024年3月22号被归档了的一个开源的覆盖率引导的模糊测试器,
在 AFL 的基础上,衍生出来了一大堆的优秀的其他模糊测试器。
其中发展的最好的是 AFL++ 这个仓库:
AFL 这个工具,它的原理是对原来的二进制程序进行插桩,然后动态执行原有的二进制程序,所以它必须得有能够运行原来的二进制程序的结构的模式。里面常用的两个模式是 qemu 模式和 unicorn 模式。
3.2 QEMU vs Unicorn
众所周知,QEMU 是一个仿真器,在我们平时的网络攻防实践课程中,总是会用到这个仿真器,用过的都会觉得它是通过命令行使用的,一点都不好用(小声) 。
🦄 Unicorn 基于 qemu,但它是一个仿真器框架,而不是仿真器。它摘除了外设、中断等仿真时容易出错的部分,只保留了指令仿真的部分,并增加了许多便利的 Hook 函数,还把这些函数都写成了 Python 或者 c 语言的形式,方便调用。也就是说,qemu 是下图结构,而 unicorn 则是只保留了红色框那一部分【下图来源于 unicorn 的 blackct 的 PPT】。
那么问题来了,它把中断和外设这些都摘除了,那遇到有这些内容的二进制程序,它怎么保证它能正常的仿真呢?没错,它不能保证,要求用户自己实现。
❓ 到这,我有一个朋友就想问了,为什么这种要求用户重新实现一遍部分内容的框架,还有生存的空间呢?
主要原因是原来的实现实在是太不统一了,非常的麻烦,每新增一种外设,都要在 qemu 里定制化添加代码;许多研究人员试图通过一些模型,让外设的仿真变成一劳永逸的事情,所以这个框架的出现,也让这个事情能够有更加灵活的解决办法;但是它把中断给机制摘除了,这个我也不太理解为什么,巨!不!方!便!【我怀疑是他们还没有找到通用的加中断的方式,实现不了就删了】
3.3 Unicorn vs UnicornAFL
Unicorn 想和 AFL 结合,是需要通过 UnicornAFL 的。就像用 afl 直接测试 Python 的代码,也有个 Python-afl 库一样。
这个 UnicornAFL,就说来话长了。众所周知,AFL 在飞快更新,Unicorn 作为新兴的库也在飞快更新,甚至持续有版本的迭代,这就导致 UnicornAFL 作为它俩的桥梁也要飞快更新🚀。
AFL 在更新,AFLplusplus 更新更快;AFL 不更新了,AFLplusplus 还在更新。好几年前,谁会想到 AFL++ 才是现在的主流呢?所以最开始将 AFL 与 Unicorn 相结合的人他是基于 AFL 原版开发的,为了保证他的代码的不用维护性😂(这事儿得怪 AFL 官方始终不支持 unicorn_mode),它甚至是针对 AFL 和 Unicorn 的特定旧版本开发的,他的仓库是 afl-unicorn:https://github.com/Battelle/afl-unicorn。它基于的版本在现在的 afl 的官方仓库里面,甚至已经消失了,但令人啧啧称奇的是,只要找到那个版本的 AFL,这个仓库已经 7 年了但真的能用,甚至只需要改个版本号和行数就能继续兼容 Unicorn 1 的最高版本 Unicorn 1.0.3。
⭐afl-unicorn 仓库的原理是,给 unicorn 打 patch,给它加个接口能与 AFL 进行交互。所以 unicorn 一更新,它这个 patch 的行数也得更新,不然用不了。这个仓库的好处是只要重新编译 unicorn 就可以,单纯多个接口,不需要引入其他的编译流程。
AFLplusplus 官方支持 Unicorn_mode。定睛一看,对于 unicorn 1.x 版本来说,它的 unicornafl 1.x 仓库就是 afl-unicorn 演变而来的,没太大区别,就是会在 AFLplusplus/unicorn_mode 文件夹下多加个版本号而已。
⭐unicornafl 2.x 就不一样了,unicorn 2.x 的编译方式从原来的 make 改成了 cmake,原来的被 patch 的文件都不知道挪到哪里去了,可能是因为每次两边任一有更新 unicornafl 就要更新 patch,开发者也乏了,就额外写了更通用的 unicornafl,也开放 python 和 c 语言版本的接口。unicornafl 不对原有的 unicorn 打 patch 并重新编译,而是把与 afl 交互的部分挪到了 unicornafl 库中,分开编译。
❓ 看到这里,我有一个朋友就要问了:交互部分分开,那不是会增加调用的开销吗?直接编译进去,肯定开销更小啊,模糊测试这么需要性能,怎么能用 unicornafl 呢?
没错,我也有这个疑问,但是开发者告诉我,这点东西差别不大,赶紧换到 unicorn2,unicorn2 更快更强。
经过友好的交流之后,我相信他肯定不至于骗我。
与开发者友好的交流的 issue:https://github.com/AFLplusplus/unicornafl/issues/20
其实不是我想评估 是老师问我有没有性能差距
4. 工具安装
参考:
- https://github.com/AFLplusplus/AFLplusplus/blob/stable/docs/INSTALL.md,如果安装遇到问题,请直接阅读官方文档。以下安装是省流版。
- https://github.com/unicorn-engine/unicorn/blob/master/docs/COMPILE.md,这是 unicorn 的编译官方教程,如果 unicorn 编译不通过,请进一步结合阅读官方文档。以下编译都是省流版。
这个工具在安装的时候,c 语言的 so 文件依赖可能会安装的有点问题,所以可以重新 make install 一下,和官方的教程不大一样。
afl++ 更新的太快,我怕这个教程在以后会失效。所以以下安装脚本均是针对特定版本安装。如果需要安装最新版本,请把下面的安装脚本的 wget 改回 git clone。
4.1 Ubuntu18
完整安装脚本可以参考我们已经开发好的工具的 AFL 安装部分:https://github.com/IoTS-P/SEmu-Fuzz/blob/main/install_local_ubuntu18.sh
Ubuntu 18的 llvm 版本比较低,所以它需要重新安装一下相关的东西。官方教程并没有提太多,里面也很有可能会出现许多问题,我直接把能装好的安装流程贴上来,如果有需要优先参考下文吧:
# fix cmake
# CMake 3.13.4 or higher is required to build LLVM-13 from source.
# Ubuntu 18.04 comes with cmake 3.10.2
# Install the latest cmake (as of this writing)
wget -O cmake.sh https://github.com/Kitware/CMake/releases/download/v3.23.1/cmake-3.23.1-Linux-x86_64.sh && \
sudo sh ./cmake.sh --prefix=/usr/local --skip-license
# fix llvm for AFLplusplus
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo -S apt-key add -
echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-12 main" | sudo tee -a /etc/apt/sources.list
sudo -S apt-get update
sudo -S apt-get install -y build-essential python3-dev automake cmake git flex bison libglib2.0-dev libpixman-1-dev python3-setuptools cargo libgtk-3-dev
# try to install llvm 12 and install the distro default if that fails
sudo -S apt-get install -y lld-12 llvm-12 llvm-12-dev clang-12 || sudo -S apt-get install -y lld llvm llvm-dev clang
sudo -S apt-get install -y gcc-$(gcc --version|head -n1|sed 's/\..*//'|sed 's/.* //')-plugin-dev libstdc++-$(gcc --version|head -n1|sed 's/\..*//'|sed 's/.* //')-dev
sudo -S apt-get install -y ninja-build # for QEMU mode
# get afl
git config --global core.longpaths true
# Newest: git clone https://github.com/AFLplusplus/AFLplusplus
# Old:
wget https://github.com/AFLplusplus/AFLplusplus/archive/refs/tags/v4.20c.tar.gz
tar xvf v4.20c.tar.gz
mv AFLplusplus-4.20c AFLplusplus
cd AFLplusplus/
make || exit 1
sudo make install || exit 1
echo "[+] Success to build AFL!"
cd unicorn_mode/
./build_unicorn_support.sh || exit 1
# fix the libunicornafl.so
# make clean
# make -j1
# fix the libunicorn.so
cd ./unicorn/
mkdir build; cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j8
sudo make install || exit 1
cd ../../../
echo "[+] Success to build Unicornafl!"
4.2 Ubuntu 20~22
完整安装脚本可以参考我们已经开发好的工具的 AFL 安装部分:https://github.com/IoTS-P/SEmu-Fuzz/blob/main/install_local.sh
这两个版本的系统就是可以少装两个依赖,别的没太大差别。
# base dependency
sudo apt-get install -y build-essential python3-dev automake cmake git flex bison python3-setuptools cargo
# get afl
git config --global core.longpaths true
# Newest: git clone https://github.com/AFLplusplus/AFLplusplus
# Old:
wget https://github.com/AFLplusplus/AFLplusplus/archive/refs/tags/v4.20c.tar.gz
tar xvf v4.20c.tar.gz
mv AFLplusplus-4.20c AFLplusplus
cd AFLplusplus/
make || exit 1
sudo make install || exit 1
echo "[+] Success to build AFL!"
cd unicorn_mode/
./build_unicorn_support.sh || exit 1
# fix the build_unicorn_support.sh with the content below.
# install unicornafll
cd unicornafl/bindings/python
pip install .
cd ../../
# fix the libunicornafl.so
# make clean
# make -j1
# fix the libunicorn.so
cd ./unicorn/
mkdir build; cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j8
sudo make install || exit 1
cd ../../../
echo "[+] Success to build Unicornafl!"
4.3 收尾
记得编辑自己的终端配置文件,追加一项配置并 source。
例如我用的是zsh,对应的终端配置文件就是~/.zshrc
;Ubuntu 默认使用的是 bash,对应的终端配置文件就是 ~/.bashrc
。
vi ~/.bashrc
追加
export AFL_SKIP_CPUFREQ=1
source:
source ~/.bashrc
添加这个配置是因为跑模糊测试一般都是为了公司,为了实验室而跑,大家都跑在服务器上,不一定有管理员的系统配置权限。而 afl 的这一项功能恰好是需要开启一项系统权限的,与其麻烦系统管理员,不如自己追加配置,把这一项配置给禁用🤕……
5. Python 测试用例简单讲解
5.1 复杂使用示例
看了前面的你们应该也已经知道,Unicorn 是需要自己实现很多东西的😭。它只是一个仿真器框架罢了,灵活是灵活但是真只是个框架。
我毕设基于这个框架实现了点别的东西,把它缺失的外设和中断部分都加上去了,让二进制程序还算正常地能跑起来了。我对 unicorn 的毕生所学都在里面了(bushi),如果想看更复杂的使用示例,可以直接参考我们的 ⭐ SEmu-Fuzz 仓库:https://github.com/IoTS-P/SEmu-Fuzz/tree/main,是针对 ARM v7m 写的。【需要注意的是,我现在已经在开发新的了,这个仓库已经不太维护了,新的仓库代码更加的简洁清晰,毕设这个比较仓促,功能没太多问题但性能不好】
5.2 官方使用示例
官方也是提供了简单的测试用例的,就在 Readme 这里很清晰:
点了链接,跳转过去,就是 afl++ 的仓库里边:https://github.com/AFLplusplus/AFLplusplus/tree/stable/unicorn_mode/samples。
文件位置:https://github.com/AFLplusplus/AFLplusplus/tree/stable/unicorn_mode/samples/python_simple
文件结构:
简单概括一下这些文件相互之间的关系:simple_target.bin 这个二进制文件由 simple_target.c 根据 COMPLE.md 的过程编译而成,simple_test_harness.py 和 simple_test_harness_alt.py 都可以用于测试 simple_target.bin,不过,simple_test_harness.py 调用 uc_afl_fuzz
这个封装得更精致的 API,simple_test_harness_alt.py 仅仅调用 uc.afl_forkserver_start
,回调函数之类的都得自己重写,更加灵活。
但是🤕!我实际测试了,发现 simple_test_harness_alt.py 还没更新成 unicornafl2 的代码!也就是 simple_test_harness_alt.py 只不过是 unicorn1 时代的残留罢了……【如下两图所示】
总之,看 simple_test_harness.py。
运行方法以及整个代码的详细解释,在这个文件中都清清楚楚,明明白白的写出来了,在博客里,没有必要赘述什么:
被测用例的源码如下:
#define DATA_ADDRESS 0x00300000
int main(void) {
unsigned char *data_buf = (unsigned char *) DATA_ADDRESS;
if (data_buf[20] != 0) {
// Cause an 'invalid read' crash if data[0..3] == '\x01\x02\x03\x04'
unsigned char invalid_read = *(unsigned char *) 0x00000000;
} else if (data_buf[0] > 0x10 && data_buf[0] < 0x20 && data_buf[1] > data_buf[2]) {
// Cause an 'invalid read' crash if (0x10 < data[0] < 0x20) and data[1] > data[2]
unsigned char invalid_read = *(unsigned char *) 0x00000000;
} else if (data_buf[9] == 0x00 && data_buf[10] != 0x00 && data_buf[11] == 0x00) {
// Cause a crash if data[10] is not zero, but [9] and [11] are zero
unsigned char invalid_read = *(unsigned char *) 0x00000000;
}
return 0;
}
很明显,根据他的这个注释可以看出来他在什么情况下会触发 crash,一共有五种情况,为了便于解释,我们把下文中的每一个条件语句都记成数字 if1、if2.1、if2.2、if2.3、if3.1、if3.2、if3.3。5种 crash 的情况分别是:
✅ 代表 true
❌ 代表 false
- if1 ✅
- if1 ❌,if2.1 ✅,if2.2 ✅
- if1 ❌,if2.1 ❌,if3.1 ✅,if3.2 ✅,if3.3 ✅
- if1 ❌,if2.1 ✅,if2.2 ❌,if3.1 ✅,if3.2 ✅,if3.3 ✅
- if1 ❌,if2.1 ✅,if2.2 ✅,if2.3 ❌,if3.1 ✅,if3.2 ✅,if3.3 ✅
切换到测试文件的目录下面,然后执行这行指令就可以了:
cd unicorn_mode/samples/python_simple
afl-fuzz -U -m none -i ./sample_inputs -o ./output -- python3 simple_test_harness.py @@
这个指令的意思是,用 unicorn
模式(对应-U
)、不设置内存限制(对应-m none
)、指定输入种子所在的文件夹(对应-i ./sample_inputs
)、输出到output文件夹(对应-o ./output
),去测试 python3 simple_test_harness.py
这个程序,并把生成的种子文件作为命令行参数传递给这个程序(对应@@
占位符)。
被测用例有五条 crash 的路径,所以就有5个 crash,很快就能跑出来,大概500个种子就跑出来了:
我多跑了一会儿:
仔细看它的测试用例代码,就会感觉 100 来行的 Python 代码,就为了测个这个,要自己定义内存布局,要写各种映射,还有写钩子函数,好麻烦……
更可怕的是,写完了这一套代码之后,即使想方设法的把这个代码写的可以支持多个测试用例了,由于设计上的原因,切换测试其他架构的测试用例还是很麻烦,比如示例代码是 mips,如果要写 arm 的,还得重新写好多东西……【对 就是中断机制 对中断被挪走了的怨念从开头到结尾形成首尾呼应……】
想想就头秃啊。
但是平常自己普通测试一下,一些规模不大的,就像官方这种小代码应该也是挺方便的……【吧?】
本账号所有文章均为原创,欢迎转载,请注明文章出处:https://blog.csdn.net/qq_46106285/article/details/139004670。百度和各类采集站皆不可信,搜索请谨慎鉴别。技术类文章一般都有时效性,本人习惯不定期对自己的博文进行修正和更新,因此请访问出处以查看本文的最新版本。