使用 qemu 模拟和调试不同架构的二进制程序

笔者之前也写过 qemu 类似的博客,但是发现以前对 qemu 理解并不深刻,并且有些方法已经过时,在此重新更新一版,并且覆盖了使用 qemu 的各种场景。我们的目的只有一个,模拟固件中的单个二进制程序。本篇博客会讲述各种场景下,使用 qemu 来运行和调试二进制程序,涵盖了 qemu 所有主流的用法。

0x10 qemu 的安装

qemu 发展这么多年,已经相当稳定了,你可以使用源码编译的方式,也可以使用 apt 安装,甚至还可以使用 Windows 平台下的 .exe 一键安装。

0x11 源码编译

安装 qemu 需要的依赖

sudo apt-get install zlib1g-dev libglib2.0-0 libglib2.0-dev libtool libsdl1.2-dev autoconf
sudo apt-get install libpixman-1-dev

qemu 官方站点 提供了源码,版本在不断更新。最新的 5.2.0 版本已经使用 ninja 进行编译。

Ninja 是Google的一名程序员推出的注重速度的构建工具,一般在Unix/Linux上的程序通过make/makefile来构建编译,而Ninja通过将编译任务并行组织,大大提高了构建速度。

本次还是使用 5.0.1 版本,因为其编译方式与大众所熟悉的 Linux C 编译方式一样,具有通用性。

下载源码,解压

wget https://download.qemu.org/qemu-5.0.1.tar.xz
tar -xf qemu-5.0.1.tar.xz 

在源码中新建 build 目录,用于存放编译生成的过程文件

mkdir build
cd build

自定义配置需要安装 qemu 中的哪些功能

使用 qemu 模拟和调试不同架构的二进制程序

  • xxx-softmmu 将编译 qemu-system-xxx,这是一个用于 xxx 架构(系统仿真,system mode)的仿真机器。重置时,起点将是该架构的重置向量。
  • xxx-linux-user 则编译 qemu-xxx,可以在 xxx 架构中运行用户应用程序(用户模式仿真,user mode),这将寻找用户应用程序的主要功能,并从那里开始执行。

简言之:如果你想在 x86 中模拟一个完整的其他架构的系统,使用 qemu-system,如果只是想单纯的运行一个其他架构的二进制程序,那么就使用 qemu-xxx

../configure --target-list=arm-softmmu,arm-linux-user --prefix=./out

编译和安装

make -j$(nproc)
sudo make install
备注:新版本的 qemu 貌似不会在运行 make install 之后将编译好的 qemu 二进制复制到目标文件夹,而是放在编译生成的中间文件。
如下所示,编译好的二进制可以直接使用
┌──(lys㉿kali)-[~/Tools/qemu-5.0.1/build]
└─$ find ./ -name "qemu-system*"
./arm-softmmu/qemu-system-arm
./arm-softmmu/out/bin/qemu-system-arm
                                                                                                      
┌──(lys㉿kali)-[~/Tools/qemu-5.0.1/build]
└─$ find ./ -name "qemu-arm*"   
./arm-linux-user/out/bin/qemu-arm
./arm-linux-user/qemu-arm

0x12 apt 安装

如果不想使用源码编译,最为简单的方法就是通过 debian 自带的包管理器进行安装。

sudo apt search qemu

按照需求安装即可

使用 qemu 模拟和调试不同架构的二进制程序
这里,我们选择 qemu-user-binfmt 以及 qemu-system

# sudo apt-get install qemu-user-static	# 静态链接的 qemu-user
sudo apt-get install qemu-user-binfmt	# 此命令有用,会在 /usr/bin 目录下生成 qemu-*
sudo apt-get install qemu-system		# 此命令有用,会在 /usr/bin 目录下生成 qemu-system-*

如果嫌弃 qemu 安装太占空间(所有 system 模式的架构大概 800MB)当然也可以指定只安装我们需要模拟的架构,例如 qemu-system-arm

sudo apt-get install qemu-system-arm

安装好的二进制如下

使用 qemu 模拟和调试不同架构的二进制程序

0x13 Windows 平台安装 qemu

QEMU Binaries for Windows 为我们提供了 32 位和 64 位的 qemu 二进制安装程序以及相关的文档。选择一个合适的版本,下载后就可以直接安装了。

安装好之后,会在安装目录下生成相应的 exe,跟 Linux 是类似的,我们将安装目录放到环境变量,就可以直接在 cmd 中使用相关命令了。

使用 qemu 模拟和调试不同架构的二进制程序

0x20 模拟和调试

我们以一个实际的案例,来分析如何在 x86 平台中使用 qemu 运行 arm 架构的程序。以 Tenda AC 15 路由器为例,从官网下载含漏洞版本固件 V15.03.1.16。下载后,用 binwalk 解压。

binwalk -Me AC15.bin

解压出来的文件系统如下

使用 qemu 模拟和调试不同架构的二进制程序
架构为 arm32,且是动态链接的。现在有两个思路来运行其中的程序

  • 方法一:使用 qemu-arm,在宿主机中直接运行
  • 方法二:使用 qemu-system-arm,建立一个 arm 的虚拟机,在虚拟机中运行相应的 arm 程序

0x21 运行目标程序

用户模式

方法一:设置环境变量,指定相应根目录

如果是静态链接的程序,可以直接用 qemu-arm 运行。但是如今固件中的程序大多是动态链接的,直接运行,会提示找不到库

┌──(lys㉿kali)-[~/Documents/IoT/_AC15.bin.extracted/squashfs-root]
└─$ qemu-arm ./bin/ip                                                                                                                                                                                                                127 ⨯
qemu-arm: Could not open '/lib/ld-uClibc.so.0': No such file or directory

因此需要设置共享库的目录,qemu 提供了设置共享库的环境变量,设置完之后,即可成功运行

┌──(lys㉿kali)-[~/Documents/IoT/_AC15.bin.extracted/squashfs-root]
└─$ export QEMU_LD_PREFIX=./                                                                                                                                                                                                         255 ⨯
                                                                                                                                                                                                                                           
┌──(lys㉿kali)-[~/Documents/IoT/_AC15.bin.extracted/squashfs-root]
└─$ qemu-arm ./bin/ip       
Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }
       ip [ -force ] [-batch filename
where  OBJECT := { link | addr | addrlabel | route | rule | neigh | ntable |
                   tunnel | maddr | mroute | monitor | xfrm }
       OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |
                    -f[amily] { inet | inet6 | ipx | dnet | link } |
                    -o[neline] | -t[imestamp] }

方法二:使用 qemu-user 提供的参数指定目录

qemu-user 模式提供了 -L 参数

Argument             Env-variable      Description
-L path              QEMU_LD_PREFIX    set the elf interpreter prefix to 'path'

运行结果如下

┌──(lys㉿kali)-[~/Documents/IoT/_AC15.bin.extracted/squashfs-root]
└─$ qemu-arm -L ./ ./bin/ip                                                                                                                                                                                                          130 ⨯
Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }
       ip [ -force ] [-batch filename
where  OBJECT := { link | addr | addrlabel | route | rule | neigh | ntable |
                   tunnel | maddr | mroute | monitor | xfrm }
       OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |
                    -f[amily] { inet | inet6 | ipx | dnet | link } |
                    -o[neline] | -t[imestamp] }

方法三:chroot 命令更改当前系统根目录

chroot 命令用来在指定的根目录下运行指令。chroot,即 change root directory (更改 root 目录)。在 linux 系统中,系统默认的目录结构都是以 / ,即是以根 (root) 开始的。而在使用 chroot 之后,系统的目录结构将以指定的位置作为 / 位置

┌──(lys㉿kali)-[~/Documents/IoT/_AC15.bin.extracted/squashfs-root]
└─$ sudo chroot ./ ./qemu-arm-static ./bin/ip                                                   125 ⨯
[sudo] password for lys: 
Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }
       ip [ -force ] [-batch filename
where  OBJECT := { link | addr | addrlabel | route | rule | neigh | ntable |
                   tunnel | maddr | mroute | monitor | xfrm }
       OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |
                    -f[amily] { inet | inet6 | ipx | dnet | link } |
                    -o[neline] | -t[imestamp] }

注意一下,需要把静态链接的 qemu-arm-static 复制到目标文件系统中,此功能才能生效。

系统模式

想使用 qemu 的系统模式运行 arm 架构的程序,必然需要一个 arm 架构的虚拟机。可以自己编译内核和文件系统,制作一个 arm 架构的 Linux 系统,但是这样毕竟花费很多精力,如笔者之前写过的博文 为 QEMU ARM 仿真器编译 Linux 内核:QEMU 模拟 ARM 环境

1 寻找合适的 qemu 虚拟机映像

好在已经有现成的专门适用于 qemuLinux 虚拟机。Debian 为我们提供了适配 qemu 的镜像
使用 qemu 模拟和调试不同架构的二进制程序
对于 arm32 ,分为 armelarmhf,一般来说,都可以使用,都是可以用于浮点计算,主要区别在于,armel 传参数用普通寄存器而 armhf 传参数用的是 fpu 的寄存器,因此 armhf 的浮点运算性能更高。

如何选择合适的 qemu 虚拟机,是根据该虚拟机支持的 arm 指令的版本,配合待测二进制的指令架构的版本来决定的。

就拿本例来说,我们待运行的是 tenda AC15 中的程序,其 arm 指令的具体架构是 armv7

使用 qemu 模拟和调试不同架构的二进制程序
而提供的 Debian qemu 两个 arm32 的虚拟机,具体的指令架构如下

Linux debian-armel 3.2.0-4-versatile #1 Debian 3.2.51-1 armv5tejl
Linux debian-armhf 3.2.0-4-vexpress #1 SMP Debian 3.2.51-1 armv7l

通过这个信息,相信你已经知道如何选择了吧。

使用 qemu 模拟和调试不同架构的二进制程序
文件说明

  • debian_wheezy,file 镜像文件,相当于实际的文件系统
  • initdrd.img,初始 RAM 磁盘,系统引导过程中挂载的一个临时根文件系统,用来支持两阶段的引导过程。initrd 文件中包含了各种可执行程序和驱动程序
  • vmlinuz,裁剪过的 Linux 内核

2 配置网络

为了方便 qemu 虚拟机和宿主机通信,我们在宿主机上新建一个新的虚拟网卡。在此之前先安装 tunctl

sudo apt-get install uml-utilities

配置虚拟网卡

sudo tunctl -t virtual0 -u lys       #virtual0是网卡名字 lsy是你已有账户名
sudo ifconfig virtual0 192.168.2.2/24     #给virtual0配置IP地址,这里配什么地址都可以

下载文件之后,使用 qemu-system 命令加载,即可成功启动虚拟机。

qemu-system-arm \
	-M vexpress-a9 \
	-kernel vmlinuz-3.2.0-4-vexpress \
	-initrd initrd.img-3.2.0-4-vexpress \
	-drive if=sd,file=debian_wheezy_armhf_standard.qcow2 \
	-append "root=/dev/mmcblk0p2 console=ttyAMA0" \
	-net nic -net tap,ifname=virtual0,script=no,downscript=no \
	-nographic
	

参数说明

-M              			// 选择开发板​
-m							// 指定内存大小
-drive         			    // 定义存储驱动器​
file=         			    // 定义镜像文件​
-net nic       			    // 创建客户机网卡​
-net tap                    // 创建 tap 设备,以桥接方式跟宿主机通信​
ifname=virtual0             // tap 设备与名为 virtual0 的虚拟网卡进行桥接通信​
-nographic                  // 以非图形化模式启动​
-append        			    // 内核启动附加参数​
-console=ttyAMA0		    // console指向串口,有此启动参数,内核启动日志才能输出到宿主机终端
-nographic					// 不再启用额外的终端界面

关于 qemu-system 参数的详细描述,可见 qemu-system-x86_64命令总结

使用 qemu 模拟和调试不同架构的二进制程序

用户名:user/root,密码:user/root

登录系统后,给 qemu 虚拟机中的网卡设置静态地址,方便与宿主机进行通信。

ifconfig eth0 192.168.2.3/24

3 待模拟的文件系统复制到 qemu 虚拟机

将宿主机上的待测文件系统打包

# tar -czvf squashfs-root.tar.gz squashfs-root
tar -cjpf squashfs-root.tar.bz2 squashfs-root

在宿主机上新建一个简单的服务器,可以使用 Kali 自带的 Apache2,也可以使用 python 自带的模块。

python -m SimpleHTTPServer 8000

qemu 虚拟机中,下载文件,并解压

wget http://192.168.2.2:8000/ squashfs-root
# tar -xzvf squashfs-root.tar.gz
tar -xvf squashfs-root.tar.bz2

4 运行目标程序

使用 chroot 命令更改系统根目录

chroot squashfs-root sh

运行目标文件系统中的进程

使用 qemu 模拟和调试不同架构的二进制程序

0x22 调试

用户模式

qemu-user 相关命令,自带了 gdbserver,通过 -g 参数指定即可。

$ qemu-arm -L squashfs-root -g 8888 squashfs-root/bin/ip 

新建另一个终端,使用 gdb-multiarch 调试不同架构的远程目标程序

# 安装 gdb-multiarch
sudo apt-get install gdb-multiarch
# 远程调试
$ gdb-multiarch
(gdb) file ./squashfs-root/bin/ip	# 也要在本地加载远程的目标程序
Reading symbols from ./squashfs-root/bin/ip...
(No debugging symbols found in ./squashfs-root/bin/ip)
(gdb) set architecture arm			# 设置架构
The target architecture is set to "arm".
(gdb) target remote localhost:8888

这里推荐使用 gef 插件,而不是 pedapeda 不能很好的识别相关寄存器。

git clone https://github.com/hugsy/gef.git
echo "source /home/lys/Tools/peda/peda.py" >> ~/.gdbinit

使用 qemu 模拟和调试不同架构的二进制程序

系统模式

需要下载支持 arm 架构的 gdbserver,好在已经有开源项目为我们编译好了静态链接的支持各种不同架构的 gdbserver。选择 gdbserver-7.7.1-armhf-eabi5-v1-sysv,下载,并放入 qemu 虚拟机。

gdbserver 192.168.2.3:8888

宿主机中的 gdb 客户端使用相同的 gdb-multiarch 连接即可。

0x30 Q&A

在我的一台电脑上的测试环境中(Kali 2020.4),启动 qemu 虚拟机的时候,出现了如下错误,大概就是 sd 卡大小不是 2 的幂(奇怪的是在我的另外一台电脑上,同样也是 Kali 2020.4 的虚拟机中,就没有出现该问题

qemu-system-arm: Invalid SD card size: 25 GiB
SD card size has to be a power of 2, e.g. 32 GiB.
You can resize disk images with 'qemu-img resize <imagefile> <new-size>'
(note that this will lose data if you make the image smaller than it currently is).

解决方案一:那就按照它的意思,使用 qemu-img 重新修改 SD 卡文件的大小

qemu-img resize debian_wheezy_armhf_standard.qcow2 32G

解决方案二:我们可以使用 armeldebian 提供的 armel 的开发板没有使用 sd 卡(该版本的虚拟机支持架构较老,即armv5,不建议使用此方案)。

qemu-system-arm \
	-M versatilepb \
	-kernel vmlinuz-3.2.0-4-versatile \
	-initrd initrd.img-3.2.0-4-versatile \
	-hda debian_wheezy_armel_standard.qcow2 \
	-append "root=/dev/sda1" \
	-net nic -net tap,ifname=virtual0,script=no,downscript=no

为了避免与刚刚所用的方法重复,这里,我们没有使用 -nographic 参数,并且删去了启动参数 console=ttyAMA0。这种方式会另外启动一个 qemu 窗口。如下所示

使用 qemu 模拟和调试不同架构的二进制程序

这种打开 qemu 模拟器窗口的方式不利于将宿主机中的命令复制到虚拟机中,所以还是建议使用之前的方法,即加上相应参数,不另外启动 qemu 窗口。

上一篇:qemu使用9pfs共享host目录


下一篇:从0开始使用QEMU模拟ARM开发环境之uboot通过sd卡加载uImage