环境:
Ubuntu 20.04 x86/64
AFL++
下载NDK
https://developer.android.com/ndk/downloads
下载后解压,设置环境路径:
~/.bashrc
文件中增加:
`export PATH="/home/bugchong/Downloads/android-ndk-r27c/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH"
安装Qiling
Qiling 框架是一个基于 Unicorn 的开源二进制仿真和模拟器框架,Unicorn 是一个 CPU 仿真器,仅限于仿真原始指令而不考虑操作系统上下文。虽然 Unicorn 处理低级仿真,但 Qiling 框架负责高级任务,包括支持不同的可执行文件格式、动态链接器以及系统调用和输入/输出处理程序。这使得 Qiling 能够执行通常需要原生操作系统才能运行的二进制文件。由于 Qiling 框架支持 ARM64 Android 二进制文件,我们可以在项目结构中找到这些动态库。
$ git clone https://github.com/qilingframework/qiling
$ cd qiling
$ git submodule update --init --recursive
下载 Qiling 框架后,我们可以在目录 examples/rootfs/arm64_android/system 中验证运行应用程序所需的文件是否存在:
安装及编译AFL++
根据官方指导手册,安装相关依赖:
sudo apt-get update
sudo 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 14 and install the distro default if that fails
sudo apt-get install -y lld-14 llvm-14 llvm-14-dev clang-14 || sudo apt-get install -y lld llvm llvm-dev clang
sudo 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 apt-get install -y ninja-build # for QEMU mode
git clone https://github.com/AFLplusplus/AFLplusplus
cd AFLplusplus
make distrib
sudo make install
需要注意的是,如果你需要调试目标应用,必须取消注释调用 signal_init() 的那行代码。否则,调试器将无法接收到应用执行过程中发出的信号。
AFLplusplus/qemu_mode/qemuafl/linux-user/main.c:
注释掉对 signal_init() 函数的调用。该脚本运行的命令可能会覆盖本地修改,因此为了确保 main.c 中的更改不会丢失,在运行脚本之前必须注释掉这些命令。
AFLplusplus/qemu_mode/build_qemu_support.sh:
注释掉 QEMU 构建脚本中的所有 git 命令。该脚本运行的命令可能会覆盖本地修改,因此为了确保 main.c 中的更改不会丢失,在运行脚本之前必须注释掉这些命令。
修改完成后 ,使用如下命令编译QEMU支持:
sudo apt install ninja-build
cd qemu_mode
CPU_TARGET=aarch64 ./build_qemu_support.sh
Fuzzing
假设我们可以访问 Android 应用程序的动态库,下面展示了简化的 C 代码(选择这种方法来简化过程可以避免处理 ARM64 代码的逆向工程复杂性)。
在实际情况下,需要在动态库中识别一个合适的目标函数,并使用反汇编工具如 IDA Pro、Ghidra、radare2 或 Hopper 进行代码分析。strcpy 函数将源字符串复制到目标位置,但如果目标位置没有足够的空间存储源字符串,可能会导致内存损坏,从而引起内存泄露和潜在的安全漏洞。
#include <stdio.h>
#include <string.h>
int checkBuffer(const char *data)
{
char localBuffer[256];
if (data[0] == 'c') {
if (data[1] == 'o') {
if (data[2] == 'n') {
if (data[3] == 'v') {
strcpy(localBuffer, data);
return 0;
}
}
}
}
return 1;
}
然后使用下面的命令生成ARM架构的so文件:
$ aarch64-linux-android35-clang libfuzzconviso.c -o libfuzzconviso.so -shared -fPIC
在获取并分析动态库之后,我们将创建一个装载动态库并使用 AFL++生成的变异输入作为参数传递给目标函数的框架。
在本实验中,为 checkBuffer 函数。AFL++将监控框架的行为,以识别应用程序中的潜在故障或崩溃。框架将获取由 AFL++生成的文件路径,将其内容读入缓冲区,并传递给目标函数。
#include <stdio.h>
extern int checkBuffer(char *);
int main(int argc, char *argv[])
{
char buffer[4096];
FILE *fp = fopen(argv[1], "r");
fread(buffer, 4096, 1, fp);
checkBuffer(buffer);
fclose(fp);
return 0;
}
使用命令进行编译:
$ aarch64-linux-android35-clang harness.c -o harness -lfuzzconviso -L .
有了应用的动态库和测试框架(harness),还需要配置两个环境变量:
$ export QEMU_LD_PREFIX="/home/thiago/Conviso/qiling/examples/rootfs/arm64_android/"
$ export QEMU_SET_ENV=LD_LIBRARY_PATH="/home/thiago/Conviso/workspace/"
-
QEMU_LD_PREFIX: 配置 QEMU 查找模拟架构共享库的位置。此变量必须设置为 Android 的/system 目录路径,该路径包含在 Qiling 框架中,允许在 QEMU 中执行 Android 编译的二进制文件,Android 的/system 目录包含操作系统文件,包括二进制文件、库和系统应用程序。
-
QEMU_SET_ENV: 配置由 QEMU 模拟过程的环境变量,在使用 AFL++和 QEMU 进行 fuzzing 的上下文中,我们将指定 LD_LIBRARY_PATH 环境变量,该变量定义了测试框架使用的附加动态库路径,例如 libfuzzconviso.so 所在的目录。
$ mkdir afl_in
$ echo "AAAA" > afl_in /input.txt
然后创建一个目录来存储 fuzzing 过程中使用的语料,在实际场景中,语料库通常会根据要 fuzz 的目标类型来选择,以最大化覆盖率和 fuzzing 效率。
对于本实验的 checkBuffer 示例,一个简单的文本文件就足够了。
最初,该目录中将只包含一个文本文件,该文件代表将传递给 checkBuffer 函数的缓冲区内容。AFL++ 将修改该文件,并将其作为输入提供给 Android 应用程序动态库中的目标函数,通过测试框架来实现。
准备好语料后,我们就可以使用以下命令通过 afl-fuzz 开始 fuzzing 了:
$ AFL_INST_LIBS=1 afl-fuzz -Q -i afl_in/ -o afl_out/ -- ./harness @@
- AFL_INST_LIBS=1 :此环境变量配置 AFL++ 以检测动态库中包含的代码,由于我们的目标是检测库文件 ,因此需要设置这个环境变量。
- -Q :此选项告诉 AFL++ 使用 QEMU 的检测模式来监视和分析目标应用程序在执行期间的行为
- -i afl_in/ :指定 AFL++ 用于模糊测试过程的语料库文件所在的输入目录
- -o afl_out/ :定义 AFL++ 存储模糊测试结果的目录,例如导致崩溃的测试用例和执行日志
- — :为分隔符,标记 AFL-fuzz 选项的结束和要执行的目标应用程序命令行的开始,在本例中为harness
- ./harness :指定在模糊测试过程中要执行的应用程序路径
- @@ :这是 AFL++ 使用的占位符,它将被替换为fuzzer在执行期间生成的输入文件的路径
首次运行 AFL++ 时,可能会遇到以下错误:
要解决该问题,只需要使用root权限输入:
echo core > /proc/sys/kernel/core_pattern
即可。
顺利开启Fuzz:
当运行一段时间后,就会出现crash,crash样本会被保存在./afl_out/default/crashes
目录中,我们可以使用以下命令通过afl-qemu-trace
检测应用程序的执行情况,从而使我们能够监视输入是否导致任何错误或失败:
AFLplusplus/afl-qemu-trace ./harness afl_out/default/crashes/id\:000000\,src\:001035\,time\:1046267\,execs\:1570466\,op\:havoc\,rep\:3
通过上图,可以看到crash样本的输入导致了应用程序中出现段错误(Segmentation fault)。
参考资料
https://blog.convisoappsec.com/en/introduction-to-fuzzing-android-native-components/