最近在挖掘固件漏洞,有些路由器没有现成设备,要验证时只能通过qemu模拟的方式来完成。在此记录下QEMU的使用过程

参考文章:TOTOLINK 路由器漏洞分析+QEMU模拟入门

环境配置

QEMU安装

为安装方便,笔者直接选择使用apt install的方式来安装qemu,想要使用源码编译的话可以参考官网

1
2
sudo apt-get install qemu-system		# qemu系统模拟
sudo apt-get install qemu-user-static # qemu用户模拟

固件

固件型号:TOTOLink X6000R

固件版本:v9.4.0cu.852_20230719

固件架构:Aarch64

固件可以直接在官网下载,使用binwalk工具提取其文件系统。

QEMU-User模拟

qemu-aarch64-static程序复制到固件文件系统下,使用chroot设置文件系统为根目录,然后通过qemu-user模拟固件shttpd程序

1
sudo chroot . ./qemu-aarch64-static ./usr/sbin/shttpd

可以看到成功运行shttpd程序,不过如果要测试命令注入漏洞返回shell还是要使用系统模拟,并且用户模拟程序,我跑shttpd结果是打不开网页的。

QEMU-System模拟

system模拟方式则更加全面,包括cpu,外设等等。

基本条件:

  • 内核镜像
  • 虚拟磁盘镜像
  • 网卡(非必须,不过要测试路由器,还是需要的)

下载内核,虚拟磁盘镜像

Debian官网有提供针对QEMU的各类架构的Linux内核系统、虚拟磁盘镜像下。下载地址

这里我选择arm64-virt镜像。下载后重命名为zip文件,解压后得到7个文件

1
2
3
4
5
6
7
8
9
10
11
❯ tree ./
./
├── image.qcow2
├── initrd
├── kernel
├── readme.txt
├── ssh_user_ecdsa_key
├── ssh_user_ed25519_key
└── ssh_user_rsa_key

0 directories, 7 files

其中readme.txt给定了qemu启动命令,启动命令,你会发现打印很多日志后,成功获取到shell。

1
2
3
4
5
6
7
8
9
10
11
12
13
# (Try to) boot with:
sudo qemu-system-aarch64 \
-machine 'virt' \
-cpu 'cortex-a57' \
-m 1G \
-device virtio-blk-device,drive=hd \
-drive file=image.qcow2,if=none,id=hd \
-device virtio-net-device,netdev=net -netdev user,id=net,hostfwd=tcp::2222-:22 \
-kernel kernel \
-initrd initrd \
-nographic \
-append "root=LABEL=rootfs console=ttyAMA0"
# You can use Ctrl-a x to exit from QEMU.

出现shell后使用root:root登录即可,注意这里我设置了网卡为tap_qemu,因此如果需要宿主机和虚拟机相互连通,需要进行虚拟网卡的设置。

配置虚拟系统网卡

配置一张可供host,guest通信的虚拟网卡命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 安装 brctl 网桥配置工具
sudo apt-get install bridge-utils

# 安装 tunctl 虚拟网卡配置工具
sudo apt-get install uml-utilities

# 添加一个网桥 br0
sudo brctl addbr br0

# 设置网桥 br0 的网段及掩码,并启用网桥
sudo ifconfig br0 192.168.5.1/24 up

# 新建一张虚拟网卡 tap0
sudo tunctl -t tap0

# 设置虚拟网卡 tap0 的 ip 地址及掩码,并启用该虚拟网卡
sudo ifconfig tap0 192.168.5.11/24 up

# 将虚拟网卡 tap0 添加到网桥 br0 中
sudo brctl addif br0 tap0

创建完毕后ifconfig返回信息如下,发现新增br0和tap0两个网卡,其中br0负责桥接宿主机和客户机,tap0负责宿主机ip:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
❯ ifconfig
br0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 192.168.1.1 netmask 255.255.255.0 broadcast 192.168.1.255
ether f2:5f:4f:59:38:2c txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.178.131 netmask 255.255.240.0 broadcast 172.17.191.255
inet6 fe80::215:5dff:fee8:a09b prefixlen 64 scopeid 0x20<link>
ether 00:15:5d:e8:a0:9b txqueuelen 1000 (Ethernet)
RX packets 130737 bytes 132443978 (132.4 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 66566 bytes 6308721 (6.3 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 447386 bytes 1366419777 (1.3 GB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 447386 bytes 1366419777 (1.3 GB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

tap0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 192.168.1.11 netmask 255.255.255.0 broadcast 192.168.1.255
ether 8e:8a:46:56:b0:40 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

修改以上qemu启动脚本,添加网卡参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sudo qemu-system-aarch64 \
-machine 'virt' \
-cpu 'cortex-a57' \
-m 1G \
-device virtio-blk-device,drive=hd \
-drive file=image.qcow2,if=none,id=hd \
-netdev tap,id=tapnet,ifname=tap0,script=no \
# 配置一个主机侧的TAP网络设备,命名为tapnet,使用主机的tap0网卡
-device virtio-net-device,netdev=tapnet -netdev user,id=net,hostfwd=tcp::2222-:22 \
# 虚拟机使用虚拟网卡,对应设备为前面创建的tapnet设备
-kernel kernel \
-initrd initrd \
-nographic \
-append "root=LABEL=rootfs console=ttyAMA0"

此时登入shell,检查虚拟机的网络配置,这里我的虚拟机没有ifconfig命令,因此选择使用ip命令(不得不感慨chatgpt的强大与方便~~~)

1
2
3
4
5
root@debian:/# ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ff

发现已有eth0网卡,那么就要将其ip设置为与宿主机同一网段(192.168.1.0/24)。

1
2
3
4
# 设置ip地址
ip addr add 192.168.1.2/24 dev eth0
# 设置网关
ip route add default via 192.168.1.1

设置好后发现互相ping不通,显示目标不可达,查看ip route发现路径不对:

1
2
3
4
5
6
root@debian:/# ip route
default via 10.0.2.2 dev eth0 onlink
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15
172.17.176.0/20 dev eth0 proto kernel scope link src 172.17.178.111
172.17.178.0/24 dev eth0 proto kernel scope link src 172.17.178.111
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.2

因此更换default为192.168.1.0/24,同时删除其他错误项:

1
2
3
4
5
    ip route del default
ip route del 172.17.176.0/20
...
ip route add 192.168.1.0/24 dev eth0
ip route add default via 192.168.1.1 dev eth0

ip address show也显示有问题,使用ip addr del删去无关项即可

Tips:这里我用的是WSL,发现linux虚拟机无法ping通Windows主机,是Windows主机的防火墙拦截掉了。使用powershell管理员用户输入以下命令即可。

1
New-NetFirewallRule -DisplayName "WSL" -Direction Inbound  -InterfaceAlias "vEthernet (WSL)"  -Action Allow

SSH连接QEMU虚拟机

只能进行shell连接是不够的,添加ssh连接,方便后续上传固件以及gdbserver等内容

1
ssh root@192.168.1.2

上传并执行固件

系统已经成功模拟,接下来我们要让其能够模拟执行固件。

使用scp命令上传固件

1
scp -r ./squashfs-root root@192.168.1.2:/root/

回到qemu命令行,使用chroot更改根目录为固件的根目录并且启动web服务/usr/sbin/shttpd,不过这里发现访问页面404。发现是执行路径的原因

/web路径下执行/usr/sbin/shttpd解决该问题

WSL GUI问题解决

成功模拟固件后,还需要能够通过浏览器页面访问呀,因此这里再浅谈一下如何使用WSL2开启GUI的浏览器访问固件页面。

参考链接:在适用于 Linux 的 Windows 子系统上运行 Linux GUI 应用

这里仅介绍chrome浏览器的安装

1
2
3
4
5
6
# 将目录更改为 temp 文件夹:
cd /tmp
# 使用 wget 下载:
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
# 安装:
sudo apt install --fix-missing ./google-chrome-stable_current_amd64.deb

然后使用google-chrome命令即可,打开后成功访问固件模拟主页