模糊测试是一种非常流行和有用的技术,经常被研究人员用来寻找安全漏洞。
在这篇文章中,我们将介绍如何使用AFL++和Ghidra仿真引擎,对运行在采用”另类”架构的嵌入式设备上的程序进行模糊测试。当使用Qemu或Unicorn等仿真引擎无法轻易完成上面的任务时,这可能是一个不错的替代方案。
在这种情况下,我们可以为AFL++添加一个新的socket_mode选项。通过这个新功能,我们不仅可以实现代码覆盖功能,同时,还可以通过外部运行的程序(例如,Ghidra仿真引擎)进行模糊测试。
最后,我们将为读者详细介绍一个关于xTensa(ESP32)架构的用例。
关于模糊测试
为了更加高效地挖掘安全漏洞,研究人员经常使用fuzzing技术来寻找有潜在利用价值的编程错误。简单地说,fuzzing就是向程序注入一些“随机的”输入,同时对程序进行密切的监视,看看是否会发生异常的行为,例如崩溃或意想不到的问题。
关于盲法模糊测试(Blind fuzzing)
实际上,对目标程序进行模糊测试的方法有很多,这里要介绍的第一种方法被称为“盲法模糊测试”。在许多情况下,这是最简单的方法:只需向程序或嵌入式设备发送随机数据,直到出现异常行为为止,如崩溃、延迟响应或配置改变等。
为了尽可能高效地进行”盲法模糊测试”,需要在逆向过程中投入大量精力。事实上,我们需要了解应用程序的预期输入数据格式,以便最大限度地提高发现漏洞的机率,如大小字段、某些操作码字段、CRC等。如果没有这些前期工作,就很难触及代码中可能存在漏洞的部分。
基于代码覆盖率的模糊测试方法
如今,“基于代码覆盖率的模糊测试方法”是为获得预期结果而最常用的一种模糊测试方法,其主要思想是自动向模糊测试引擎提供新的输入,以便最大限度地执行可访问的代码。因此,在实践中,发现的每个新执行路径都会带来一个新的样本,并对其进行模糊突变处理。最初,这种方法仅用于对源代码进行模糊测试,因为在编译时应添加代码覆盖率检测。
American Fuzzy Lop (AFL)
AFL是目前最流行的模糊测试软件之一。它最初由Michal “lcamtuf” Zaleswski开发,其后继者AFL++现在由一个更大的社区进行维护。
AFL能够以代码覆盖率导向的方式对黑盒程序进行模糊处理。为了实现这个目标,AFL需要借助于像Qemu或Unicorn这样的仿真引擎来收集覆盖率信息,然后将其提供给AFL。关于这方面的更多信息,请参考AFL文档。
通常情况下,AFL和Unicorn用于裸机嵌入式设备。首先,Unicorn的目的是在仿真水平上设计插桩,所以对其进行开发(例如,仿真硬件行为)相对要容易一些。关于AFL-Unicorn的更多信息,请参考这篇文章。
不幸的是,Unicorn仍然基于旧的Qemu版本,而且它目前只支持有限的CPU架构,比如ARM、ARM64(ARMv8)、M68K、MIPS、SPARC和X86架构。那么,在一个其架构尚未提供支持的CPU上,我们如何使用AFL进行模糊测试呢?
GHIDRA仿真器
Ghidra是一套开源的软件逆向分析工具,它最初是由美国国家安全局开发的;同时,它提供了丰富的功能,例如:
- 反汇编。
- 反编译。
- 用Java或Python编写脚本。
- 仿真。
Ghidra不仅支持许多CPU架构,并且还易于添加新的CPU架构。简而言之,如果您想添加一个新的CPU架构,只需要描述相关的语义以及如何解码这些指令就可以了。然后,您就拥有了新支持的架构的所有Ghidra功能,如反汇编、反编译和仿真。
目前,网上已经涌现了许多关于如何添加CPU架构的教程(Implementing a New CPU Architecture for Ghidra)。
带有Ghidra仿真器的AFL
为了对“另类”架构中的某些代码进行模糊测试,我们需要借助AFL++和Ghidra来模拟其执行过程。为此,我们必须实现以下功能,才能充分利用代码覆盖率:
- 一个新的AFL++模式(socket_mode),用于通过TCP套接字获得执行路径。
- AFL++和Ghidra仿真器之间的桥接器(ahl_bridge_external.py)。
- 一个Ghidra仿真器(ahl_ghidra_emu),用于接收输入样本,并通知AFL++执行路径。
图1:AFL Ghidra仿真器PoC架构
本文中的代码都可以从我们的github页面下载:
- AFLplusplus-socket-mode,地址https://github.com/airbus-cyber/AFLplusplus-socket-mode。
- afl_ghidra_emu,地址https://github.com/airbus-cyber/afl_ghidra_emu。
AFL是如何工作的
首先,AFL++会监听TCP套接字(Ex: 22222/tcp),以获得关于样本代码执行路径的通知。
然后,AFL++运行一个trampoline脚本(afl_bridge_external.py),它负责转发样本,并通过TCP套接字(Ex: 127.0.0.1:6674/tcp)转发给Ghidra仿真,并维护AFL++配置。
最后,Ghidra中的一个python脚本(fuzz_xtensa_check_serial.py)负责模拟代码执行。它监听一个TCP套接字(127.0.0.1:6674/tcp)并等待来自蹦床脚本的输入数据。
一旦脚本收到输入数据,仿真就会开始。在执行过程中,被执行的路径地址会通过相应的套接字(127.0.0.1:22222)发送到AFL++。
然后,由仿真引擎报告最终的执行状态(例如:是否发生崩溃)给蹦床脚本(afl_bridge_external.py)。如果报告称状态出现了崩溃,蹦床脚本就会退出,并返回AFL++缓存的segfault错误信号。
安装方法
克隆AFLplusplus-socket-mode目录。
git clone https://github.com/airbus-cyber/AFLplusplus-socket-mode
编译AFLplusplus(读者可以阅读AFLplusplus-socket-mode/README.md,以了解更多选项)。
cd AFLplusplus-socket-mode
make
获取AFL Ghidra仿真器脚本和库:
cd AFLplusplus-socket-mode/utils/socket_mode
sh get_afl_ghidra_emulator.sh
将afl_ghidra_emu文件复制到ghidra的脚本目录下:
cp –r afl_ghidra_emu/* $USER_HOME/ghidra_scripts/
示例:对Xtensa的二进制代码进行模糊测试
为了进行演示,我们为Xtensa架构制作了keygenMe软件编译器,您可以从这里下载该软件。
由于Ghidra目前还没有正式支持Xtensa,因此,您需要首先按照以这里的说明进行安装。
载入Ghidra
- 在Ghidra中创建一个新项目。
- 导入文件./bin/keygenme_xtensa.elf(arch: Xtensa:LE:32)。
- 利用CodeBrowser打开该文件并进行自动分析。
- 通过“Window”子菜单中打开脚本管理器。
- 运行脚本fuzz_xtensa_check_serial.py。
进行模糊测试
创建AFL工作空间目录
mkdir input output
添加第一个样本
echo –n “BBBBBBBB” > input/sample1.bin
通过蹦床脚本启动AFL++。
afl-fuzz -p explore -D -Y 22222 -i input -o output -t 90000 /usr/bin/python2 afl_bridge_external.py -H 127.0.0.1 -P 6674 -a 127.0.0.1 -p 22222 -i @@
当AFL++检测到崩溃时,我们就能得到预期的序列:
cat output/default/crashes/id*
➔AZERTYUI
停止Ghidra仿真
./afl_bridge_external.py -H 127.0.0.1 -P 6674 –s
结束语
如本文所述,我们能够对Ghidra支持的所有架构的目标程序进行代码覆盖率导向的模糊测试。
虽然这些方法的性能较低,但我们可以通过同时启动几个模糊测试实例来获得更好的性能。此外,为了改进漏洞检测效果,还需要引入大量的插桩机制(例如,“类似ASAN”的机制)。
请注意,通过对AFL++进行修改(socket_mode),使其可以与其他仿真引擎或调试器进行集成。尽管如此,当我们与GDB调试器之类的工具进行集成时,仍然需要设计相应的插桩技术来通知AFL具体的执行路径。