Vivotek摄像头固件漏洞挖掘-AFL-FUZZ实战(二)
- Published on
简介: 本文章讲述了使用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所依赖的动态链接库如下所示。
qemu-arm -L . ./usr/sbin/httpd
即可。但是执行后发现程序并没有报错,而是运行了很久直接被killed了。 既然程序可以执行,按照报错提示是无法切换到/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
但是需要注意的是,如果之后要使用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_PREFIX
和AFL_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!