使用QEMU在ARM64系统上调试x86_64程序
最近有时候会做一些CTF的逆向/二进制利用题目,目前的环境是Ubuntu 22.04.2 ARM64(MacOS上的Parallels Desktop虚拟机),然而大部分题目的可执行文件都是x86_64的,虽然可以利用Rosetta来正常运行x86_64的程序,但是没办法顺利调试。
由于实在不想在两台电脑上来回切换,所以花了两三天时间找了找解决方案,目前是在我的Ubuntu ARM64系统上用QEMU又套了一个x86_64的虚拟机,用GDB远程调试。在这里记录下配置的全过程~
之前的尝试
Windows笔记本 + VMware虚拟机
没什么问题,就是感觉搞两台电脑太麻烦了,所以才有了本文...
QEMU user mode + gdb-multiarch
首先,这种方式是可以成功调试的,具体步骤为:
qemu-x86_64 -g 1234 /path/to/binary
,这时会在1234
端口上监听,等待GDB连接;
然后新开一个终端,执行:gdb-multiarch /path/to/binary
;
进入GDB后,执行target remote :1234
,就可以调试了。
(这一步如果使用GDB插件GEF,需要进入GDB后执行gef-remote --qemu-user --qemu-binary /path/to/binary localhost 1234
,参考gef-remote qemu-mode。)
然而QEMU user mode和GDB远程协议交换的信息不全,不足以在本地建立起等价的/proc/[pid]/maps
(参考issue),这会导致一些原生命令如info proc mappings
或者插件命令vmmap
、search-pattern
等无法正常工作。
当前解决
所以,最后还是决定用QEMU模拟一个完整的x86_64虚拟机,一方面可以解决GDB调试的问题,另一方面有了完整的x86_64环境,也方便用来做一些其他的事情。
安装QEMU和x86_64系统
sudo apt install qemu-user qemu-system-x86
接下来选择一个磁盘镜像,我选择的是debian-12-nocloud-amd64.qcow2
,可以直接在这里下载,注意选择nocloud
版本,最轻量,默认用户root
,无密码。
这一步原则上只要是x86_64的系统就行,也可以从.iso
镜像安装,或者选择一些其他的最小化Linux发行版(有的只有几十MB大小)。个人感觉这个Debian镜像站还不错,可以下载.qcow2
文件,QEMU可以免安装运行;并且包含glibc
(Alpine就不包含,不过Alpine镜像更小),和大部分题目的环境一致。
尝试启动系统:qemu-system-x86_64 -m 1024 -hda /path/to/debian-12-nocloud-amd64.qcow2 -nographic
-hda
:下载的磁盘镜像路径;
-nographic
:不启动图形界面,直接输出到终端。
登录系统,用户名root
,默认无密码。
看到下面的结果就说明成功了~

在x86_64系统上安装gdbserver
sudo apt update
sudo apt install gdbserver
配置共享文件夹和端口转发
下面语境中“主机”指的是运行QEMU的Ubuntu 22.04.2 ARM64虚拟机。
在这一步,我们让QEMU的x86_64系统和主机之间共享一个目录,可以把题目放在共享文件夹里。(对于我的环境,我可以让MacOS、Ubuntu ARM64虚拟机以及Ubuntu中的QEMU的x86_64系统共享同一个文件夹,共享文件很方便。)
同时,目前我使用user
网络模式,QEMU的x86_64系统可以访问互联网,但是外部(包括主机)无法直接访问QEMU的x86_64系统,需要配置端口转发。这一步相当于把QEMU虚拟机的端口xxxx
转发到主机的端口yyyy
,然后主机可以通过localhost:yyyy
来访问对应的服务,这一步很关键,QEMU虚拟机上的SSH和gdbserver
都需要这种方式访问。
上述功能通过QEMU启动参数来实现配置:
qemu-system-x86_64 -m 1024 -hda /path/to/debian-12-nocloud-amd64.qcow2 \
-virtfs local,path=/media/psf/shared,mount_tag=shared,security_model=none,id=shared \
-device e1000,netdev=net0 \
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::1234-:1234,hostfwd=tcp::1235-:1235,hostfwd=tcp::1236-:1236 \
-nographic
-virtfs
行:启用虚拟文件系统共享,/media/psf/shared
是主机上的路径,后面的mount_tag=shared
是虚拟机上的挂载点标签,id=shared
是QEMU使用的标识符,可以自定义;
-device
行:模拟网卡,net0
是标识符,下面有用到;
-netdev
行:使用user
网络模式,转发了虚拟机上的22
、1234
、1235
、1236
四个端口,注意虚拟机的22
端口被转发到主机的2222
端口,因为主机的22
端口通常已经被占用;另外三个端口留给gdbserver
或其他目的使用,与转发给主机的端口号一致。
关闭QEMU虚拟机,用上述命令再次启动;
还需要在虚拟机中挂载共享目录:
cd /mnt
mkdir shared
mount -t 9p shared /mnt/shared
-t
:指定文件系统类型,QEMU的-virtfs
默认使用9P文件系统。
到这里,在QEMU虚拟机执行ls /mnt/shared
应当可以看到主机上的共享文件夹的内容。
gdbserver + pwndbg远程调试
对比了一下GEF和pwndbg,感觉pwndbg远程调试的命令更简洁一些,并且自带其他架构,不需要安装gdb-multiarch
,所以决定使用pwndbg。
在Ubuntu ARM64上安装pwndbg,在pwndbg/releases中选择pwndbg_yyyy.mm.dd_arm64.deb
下载。
sudo dpkg -i pwndbg_yyyy.mm.dd_arm64.deb
接下来在QEMU虚拟机中启动gdbserver
,监听1234
端口,并在主机中连接。
QEMU虚拟机终端中执行:gdbserver :1234 /mnt/shared/binary
,这里假设二进制文件binary
已经拷贝到共享目录中。
主机终端中执行:pwndbg
,进入调试界面说明前面安装成功了。
主机pwndbg调试界面执行:target remote :1234

至此就配置成功了!验证一下vmmap
和search
均可以正常使用~

更多配置
自动挂载共享目录
每次重启QEMU虚拟机都需要手动挂载共享目录比较麻烦,可以在/etc/fstab
中添加一行来实现自动挂载,在QEMU虚拟机中执行:
echo "shared /mnt/shared 9p trans=virtio,access=any 0 0" | sudo tee -a /etc/fstab
SSH
安装与配置
调试程序不需要SSH,但是建议装一下,便于从主机连接,可以实现远程开发之类的。
sudo apt install openssh-server
我使用的qcow2
镜像默认无密码,SSH连接时会提示Permission denied
,需要修改SSH配置文件sshd_config
。
在QEMU虚拟机中:sudo vi /etc/ssh/sshd_config
找到以下两行:
#PermitRootLogin prohibit-password
...
#PermitEmptyPasswords no
修改为:
PermitRootLogin yes
...
PermitEmptyPasswords yes
保存退出后,重启SSH服务:sudo systemctl restart sshd
在Ubuntu主机中连接:ssh -p 2222 root@localhost
-p
:指定端口,前面QEMU启动时已经将虚拟机的22
端口转发到主机的2222
端口,所以这里连接localhost:2222
。

下面语境中“主机”指的是MacOS。
如果需要,也可以在MacOS中SSH连接到QEMU虚拟机,我的Ubuntu ARM64 Parallels Desktop虚拟机使用“共享网络”模式,MacOS主机可以访问到Ubuntu虚拟机。在Ubuntu虚拟机中执行ip addr
,查到Ubuntu虚拟机的IP地址:

然后在MacOS主机中SSH连接:ssh -p 2222 root@10.211.55.3
