简介: 本文章讲述了使用QEMU的用户模式模拟运行摄像头固件上ARM二进制程序的两种方式,以及如何使用qemu-afl去fuzz该二进制程序。

安装afl的qemu-mode

在afl的qemu-mode的Readme中提到qemu-mode是afl-fuzz对于无源码二进制文件的高性能解决实现。我们知道afl是代码覆盖率为导向的FUZZ工具,对于有源码的程序会在编译阶段对基本快的头部进行插桩操作,从而在程序执行时可以得到代码覆盖率。但是对无源码的可执行程序,要得到程序运行时的代码覆盖率就需要qemu的帮助。当二进制文件在patched QEMU(根据afl的需要打过补丁的qemu)中运行时,由QEMU收集覆盖信息并传递给AFL。这部分的详细内容可以参考此博客

首先需要在在官网上下载afl

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

解压、make、安装。

tar zxvf afl-latest.tgz
cd afl-latest
make
sudo make install

接下来需要配置qemu的环境,执行afl提供的脚本。但是在此之前需要安装依赖的库。

sudo apt install python
sudo apt install libtool 
sudo apt-get install libtool-bin
sudo apt-get install zlib1g 
sudo apt install automake 
sudo apt install bison

如果准备执行的二进制文件与本机的CPU架构不一样,需要指定CPU_TARGET。如下对于32位arm架构下的摄像头固件,就需要CPU_TARGET=arm

cd qemu-mode
./build_qemu_support.sh CPU_TARGET=arm
如果遇到如下报错可以尝试通过这篇博客解决。


qemu user-mode检查

简介

查看该二进制文件的信息如下:

接下来,我将更详细地阐述下qemu的用户模式的内容。

我们知道作为一台虚拟机,会将虚拟的硬盘加载到操作系统中,并与虚拟的硬件设备如CPU、键盘、网络调制器等进行交互。我们所编写的程序会通过系统调用与系统提供的键盘、终端、屏幕、文件系统等进行交互。也就是说,一个程序是在用户空间进行执行,而通过系统调用请求内核与计算机系统中的其他部分进行交互的。在用户模式中,QEMU并没有模拟硬件设备而只有CPU。它通过模拟的CPU来处理其他架构下的指令,然后传递(Capture and forward)系统调用到宿主机的内核。这就是为什么用户模式下的QEMU只能叫系统调用的虚拟,因为其根本没有虚拟内核而是用的宿主机的内核。QEMU的系统模式和用户模式的对比图如下所示。这篇博客讲的很好,可以参考一下。

首先采用qemu用户模式完成程序运行对进程的模拟有两种方式,一是采用qemu-arm -L . xxx.bin的参数形式,在qemu的官方文档里提到,-L参数用来表示目标框架(e.g. arm)的动态链接库文件搜索时的前缀。第二种方法是使用chroot进入固件的根目录,然后执行qemu-arm。这种情况需要注意的是chroot之后根目录发生了转换,当把qemu-arm复制进根目录后由于qemu-arm依赖于其他的动态链接文件,所以会导致报错。这里需要手动将qemu-arm依赖的动态链接库拷贝到根文件目录下,具体如下所示。

方法一: qemu-arm + '-L'参数

首先采用第一种方法。我们在squash-root目录下,执行qemu-arm的命令。如果不加-L参数,会提示/lib/ld-uClibc.so.0: No such file or directory报错,也就是./usr/sbin/httpd所依赖动态链接库文件找不到。在qemu的系统模式下查看httpd所依赖的动态链接库如下所示。

我们知道该程序所依赖的动态链接库文件都在该squashfs-root目录下面,所以使用命令qemu-arm -L . ./usr/sbin/httpd即可。但是执行后发现程序并没有报错,而是运行了很久直接被killed了。
之后自己实在没有头绪之后去stackoverflow上发了个问题去请教-在这里。最后给的答案是QEMU的版本太老。我自己之前是通过apt下载的QEMU,默认版本是2.11.1,通过网站下载源码自己编译安装之后将QEMU升级成了4.1.0的版本。再此尝试发现程序可以成功执行。如下图所示。

既然程序可以执行,按照报错提示是无法切换到/mnt/ramdisk目录下,我首先尝试在/mnt目录下创建ramdisk文件夹,发现就可以成功运行了。之后挂载置于内存的./dev目录和./proc目录,修改hostname即可成功运行。至于为什么要修改hostname可以参考我的上一篇博客

mkdir /mnt/ramdisk
echo "127.0.0.1 Network-Camera localhost" >   /proc/sys/kernel/hostname
mount -o bind /dev ./dev
mount -t proc /proc ./proc
再次运行命令qemu-arm -L . ./usr/sbin/httpd,发现可以成功执行!!!


方法二: qemu-arm + chroot

我们再尝试第二种方法。首先我们将qemu-arm复制到文件的根目录。然后尝试运行命令chroot命令会得到报错,提示找不到qemu-arm。

cp $(whereis qemu-arm) .
sudo chroot . ./qemu-arm ./usr/sbin/httpd

其实chroot: failed to run command ‘./qemu-arm’: Not a directory这条报错并不准确,可能造成的原因有很多。我们已经将qemu-arm复制到根文件系统下了,所以有可能是缺少依赖的动态链接库文件,毕竟qemu-arm是依赖的linux下的/lib或者/usr/lib下的动态链接库文件,chroot之后肯定找不到了呀。使用ldd命令查看qemu-arm依赖的动态链接库文件,然后复制进根文件系统。

ldd ./qemu-arm

然后类似地给这些文件复制到根文件系统中。这里需要注意的是,这就类似与在根文件系统里面让qemu-arm模拟在linux下去找文件,然后还需要自己创建文件夹,否则会cp不进去。

cp /usr/lib/x86_64-linux-gnu/libgmodule-2.0.so.0 ./usr/lib/x86_64-linux-gnu/libgmodule-2.0.so.0
cp /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0  ./usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
cp /lib/x86_64-linux-gnu/libz.so.1 ./lib/x86_64-linux-gnu/libz.so.1
cp /lib/x86_64-linux-gnu/librt.so.1  ./lib/x86_64-linux-gnu/librt.so.1
cp /usr/lib/x86_64-linux-gnu/libstdc++.so.6 ./usr/lib/x86_64-linux-gnu/libstdc++.so.6
cp /lib/x86_64-linux-gnu/libm.so.6  ./lib/x86_64-linux-gnu/libm.so.6
cp /lib/x86_64-linux-gnu/libgcc_s.so.1  ./lib/x86_64-linux-gnu/libgcc_s.so.1
cp /lib/x86_64-linux-gnu/libpthread.so.0  ./lib/x86_64-linux-gnu/libpthread.so.0
cp /lib/x86_64-linux-gnu/libc.so.6  ./lib/x86_64-linux-gnu/libc.so.6
cp /lib/x86_64-linux-gnu/libdl.so.2  ./lib/x86_64-linux-gnu/libdl.so.2
cp /lib/x86_64-linux-gnu/libpcre.so.3  ./lib/x86_64-linux-gnu/libpcre.so.3
cp /lib64/ld-linux-x86-64.so.2 ./lib64/ld-linux-x86-64.so.2
再次运行chroot命令,发现可以成功执行httpd二进制文件了!

但是需要注意的是,如果之后要使用afl进行FUZZ,请务必确保确保程序通过方法一可以正常运行!!!方法二仅作为使用qemu的用户模式运行二进制程序的参考。

使用AFL++对二进制程序进行FUZZ

安装AFL++

AFL++AFL的升级,3.01a 版本的AFL++QEMU模式中默认的QEMU版本为5.1,并在此基础上进行Patch(打补丁)。2.52b版本的AFL的默认QEMU版本为2.x, 该版本的QEMU在使用-L参数指向类似于根文件系统的路径时可能会出现错误。所以在此推荐使用AFL++代替AFL进行FUZZ。

首先,使用git命令下载AFL++并解压。

git clone https://github.com/AFLplusplus/AFLplusplus

进入AFLplusplus目录中,执行make命令。不过需要注意的是,make后面可以选择不同的参数,来指定AFL++安装的模式。因为我只是用来跑arm架构下的二进制程序,所以我选择了make binary-only.如果make过程中出现了报错,按照提示安装依赖包即可。

all: just the main AFL++ binaries
binary-only: everything for binary-only fuzzing: qemu_mode, unicorn_mode, libdislocator, libtokencap, radamsa
source-only: everything for source code fuzzing: llvm_mode, libdislocator, libtokencap, radamsa
distrib: everything (for both binary-only and source code fuzzing)

make之后来安装qemu-mode.执行以下命令,这部分会花费一些时间。因为需要先下载对应版本的qemu然后再进行patch, 耐心等待即可。由于我的qemu是用来跑arm下的二进制程序,所以需要首先指定CPU_TARGET.

cd qemu_mode
CPU_TARGET=arm ./build_qemu_support.sh

最后,回到AFL++的安装目录,执行make install.

cd ..
make install
可以尝试在命令行中输入afl-fuzz查看是否安装成功.


使用AFL++进行FUZZ

本次目标的fuzz程序是squashfs-root下的/bin/busybox awk程序。

首先进入squashfs-root目录下,在当前的命令行中添加环境变量QEMU_LD_PREFIXAFL_PATH, 分别用来指向程序所依赖的library文件的目录前缀(与qemu的usermode中-L参数相同)和afl++的安装目录。

export QEMU_LD_PREFIX=.
export AFL_PATH=/home/apple/afl-qemu/AFLplusplus

在fuzz之前,使用afl-showmap工具对程序进行测试,该命令会运行目标二进制程序,跟踪单个输入的执行路径,并打印出程序执行的输出,捕获元组,从而得到程序运行时的覆盖情况。使用command A | command B这样的形式使A的输出作为B的输入执行。

echo xxx|../afl-showmap -Q -o - -- bin/busybox awk '{print$1}'

afl-showmap可以正常运行后,开始使用afl-fuzz进行fuzz。具体命令如下所示,其中-i参数后跟测试用例的文件夹,-o参数后面跟目标输出的文件夹,-Q参数表示使用QEMU模式,最后输入需要测试的程序及其参数。

afl-fuzz -i fuzz_in -o fuzz_out -Q -- bin/busybox awk '{print$1}'
其中作为输入的文件夹fuzz_in的构造如下,作为简单的示例。
如果遇到[-] PROGRAM ABORT : Fork server handshake failed报错,这里提供两个解决思路。一是检查环境变量,确保afl-showmap以及qemu-mode+参数L的方式可以正常运行该二进制程序。第二个是fuzz_out文件夹是通过该命令生成的,不需要提前建立,如果afl-fuzz在执行时发现目标输出文件夹已经存在,也可能会导致该错误。
如果没有其他错误,即可顺利出现以下的界面。Have a nice day!