一、背景
OpenWrt 是一个广受欢迎的开源固件项目,许多厂家,如小米、摩托罗拉、TP-ILNK等,都基于 OpenWrt 开发自己的固件。对于一些特定的应用场景,我们也可能需要自行构建 OpenWrt 固件。如果能够仿真OpenWrt固件,那么将会提升相关的漏洞挖掘的效率与成本控制,大大增强对固件的掌控能力,为安全研究提供便利。
二、面临的问题
目前,常用的仿真工具如 FirmAE 并不支持直接仿真 OpenWrt 固件。同时,采用传统的 QEMU 启动并进行网络桥接的方法,例如我个人开发的 Sevnup,也无法直接仿真 OpenWrt。这是因为 OpenWrt 的启动过程具有特定的初始化特性,无法在系统启动后完成一些重要的初始化操作。详细的启动流程可以参考 OpenWrt系统启动流程分析。
如下图所示,OpenWrt 固件需要在正式进入shell之前进行许多的初始化工作。在 QEMU 启动后,由于权限或者文件缺失等问题这些初始化工作将无法正常运行,妨碍了后续的仿真工作。后面我们进行实验详细说明。

我们可以做一个详细的实验,如果使用常规QEMU方法模拟motorola cx2l也就是NepCTF的题目固件,看看会如何。首先解压固件:

接下来,使用Sevnup进行常规仿真(注意接下来都是qemu系统层面的模拟),成功进入shell:

我们可以看到关于httpd有很多初始化的脚本尚未运行:

强行启动lighttpd服务,会说明没有ubus:

那么需要进行相关的初始化,但是手动启动缺少很多配置信息。这个时候我们联想到了刚才的OpenWrt启动流程,尝试按照其启动流程进行初始化。

第一步执行/etc/preinit发现缺失许多文件:

那么我们再尝试第二步/sbin/init,也是类似问题:

由于前面两步骤都不能正常执行,后面也就无法一起初始化,也就无法正常仿真,这就是常QEMU无法仿真的具体现象。
在部分情况下会说明要求初始化文件是PID为1的情况下才可运行。此处也可以举一个TL-XTR10280的例子,首先使用Sevnup进入系统层面模拟:

尝试运行初始化程序,发现除了缺少相关文件信息,还要求PID为1才能init:

此时也许有人会提出疑问,既然是lighttpd,是否可以像复现Vigor的Nday一样,直接用lighttpd -f 去启动呢?答案是可以,但是服务并不能正常进行:

无法进行截图演示,登录后将会直接又返回首页。

以上说明,鉴于 OpenWrt 的独特性,常规的方法目前无法实现对其固件的直接仿真。
三、回到 NepCTF2024 题目——NepWRT
在加入 Nepnep 团队一周年之际,我有幸参与出题,设计了两道题目,一道关于 VxWorks,另一道关于 OpenWrt。
VxWorks 的题目部分基于目前的研究成果,待相关 CVE 公布后也许会再详细探讨。
这次关于 OpenWrt 的题目颇具新意,至少对我而言,在过往的比赛中并未遇到类似的仿真类题目。在本题中,我将 flag 加密后分散到固件的各个部分,选手需要成功仿真整个固件后才能在根目录下发现 flag,否则只能多重排列组合后破解加密,题目难度因此转化为一项综合挑战。
那么如何实现预期的解答呢?这便是本篇的主题。以下是简化的步骤:
解压固件并提取文件系统。
下载 OpenWrt 官方内核及镜像。Malta 下载链接
解压官方镜像获取文件系统,将目标固件的文件系统使用
rsync -av更新覆盖至官方镜像的文件系统。将更新后的文件系统打包为镜像文件,并利用 QEMU 启动该镜像。可以考虑在初始化脚本中设置 root 密码以便直接登录 shell 查看 flag。
这种方法区别于传统的QEMU启动并使用网络桥接,直接利用 OpenWrt 官方镜像进行初始化,并覆盖关键的配置信息,使固件能够正常启动,规避了传统 QEMU 仿真中遇到的限制。
不过,为实现完全的仿真,还需配置网络接口。OpenWrt 的网络接口配置不同于常规的 ifconfig 配置方式,而是通过修改 /etc/config/network 配置文件实现。如下示例是设置本地主机 IP 为 10.10.10.1,OpenWrt 仿真 IP 为 10.10.10.2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | config interface 'loopback' option device 'lo' option proto 'static' option ipaddr '127.0.0.1' option netmask '255.0.0.0'config globals 'globals' option ula_prefix 'fd7d:c944:ab04::/48' option packet_steering '1'config device option name 'br-lan' option type 'bridge' list ports 'eth0' option promisc '1'config interface 'lan' option ifname 'eth0' option proto 'static' option ipaddr '10.10.10.2' option netmask '255.255.255.0' |
在启动 QEMU 时,宿主机器需配置以下网卡设置:
1 2 3 4 5 | sudo tunctl -t tap0 -u $(whoami)sudo ip addr add 10.10.10.1/24 dev tap0sudo ip link set dev tap0 upip addr show tap0cat <<'EOF' | sudo tee /etc/qemu-ifup >/dev/null |
如此一来,宿主机和仿真环境便可连通。
在最开始的时候我们讨论了常规方法的限制,现在我们使用新方法看看仿真的信息输出进行对比(右边是新方法的成功输出,左边是失败输出):

仿真效果如下:

进入 shell:

赛博大厨:

四、脚本说明
下面是我自己使用的自动化脚本,接收第一个命令行参数作为固件解压后的文件系统路径,ext_backup 目录是官方 OpenWrt 的文件系统,ext 目录作为副本并更新为目标文件系统内容,打包成 img 文件以供仿真使用:
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 35 36 37 38 39 40 | # OPENWRT 仿真脚本IMG=root.imgecho -e "[o] config ip address for tap0 and http server..."sudo tunctl -t tap0 -u $(whoami)sudo ip addr add 10.10.10.1/24 dev tap0sudo ip link set dev tap0 upip addr show tap0cat <<'EOF' | sudo tee /etc/qemu-ifup >/dev/null#!/bin/ship addr showip link showcat /etc/qemu-ifup.bakEOFsudo chmod +x /etc/qemu-ifupif [ -n "$1" ]; then FOLDER=$1 sudo rm -rf $IMG sudo rm -rf ./ext sudo cp -r ./ext_backup/ ./ext sudo rsync -va $FOLDER/ ./ext/ dd if=/dev/zero of=$IMG bs=1M count=500 mkfs.ext4 $IMG sudo mkdir /mnt/rootfs sudo mount -o loop $IMG /mnt/rootfs sudo chmod 777 ./ext sudo cp -a ./ext/* /mnt/rootfs/ sudo umount /mnt/rootfs sudo rmdir /mnt/rootfs/fiecho -e "\033[32m[o]\033[0m Starting QEMU"sudo qemu-system-mipsel -M malta -s \ -drive file=$IMG,format=raw \ -kernel openwrt-malta-le-vmlinux.elf \ -nographic -append "root=/dev/sda console=ttyS0" -net nic -net tap,ifname=tap0 |
1. 设置网络接口
1 2 3 4 | IMG=root.imgsudo tunctl -t tap0 -u $(whoami)sudo ip addr add 10.10.10.1/24 dev tap0sudo ip link set dev tap0 up |
- 创建
tap0接口,并设置 IP 地址为10.10.10.1,然后启用该接口。
2. 创建 QEMU 网络配置脚本
1 2 3 4 5 6 7 8 | cat <<'EOF' | sudo tee /etc/qemu-ifup >/dev/null#!/bin/ship addr showip link showcat /etc/qemu-ifup.bakEOFsudo chmod +x /etc/qemu-ifup |
- 创建一个网络配置脚本
/etc/qemu-ifup,用于 QEMU 启动时显示网络状态,并赋予执行权限。
3. 准备镜像和文件系统
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | if [ -n "$1" ]; then FOLDER=$1 sudo rm -rf $IMG sudo rm -rf ./ext sudo cp -r ./ext_backup/ ./ext sudo rsync -va $FOLDER/ ./ext/ dd if=/dev/zero of=$IMG bs=1M count=500 mkfs.ext4 $IMG sudo mkdir /mnt/rootfs sudo mount -o loop $IMG /mnt/rootfs sudo cp -a ./ext/* /mnt/rootfs/ sudo umount /mnt/rootfs sudo rmdir /mnt/rootfs/fi |
- 如果提供了参数,清理旧镜像,创建新的
root.img,格式化为ext4,并将文件系统内容复制到镜像中。
4. 启动 QEMU
1 2 3 4 | sudo qemu-system-mipsel -M malta -s \ -drive file=$IMG,format=raw \ -kernel openwrt-malta-le-vmlinux.elf \ -nographic -append "root=/dev/sda console=ttyS0" -net nic -net tap,ifname=tap0 |
- 启动 QEMU 仿真,使用指定的镜像和内核,配置网络接口为
tap0,并在无图形模式下运行。
简单来说就是自动化配置了网卡,然后更新官方镜像并且打包,最后用QEMU启动。
五、再举一个例子
对于自编译的 OpenWrt 固件,同样可以采用上述方法仿真。例如,下图为一个 Netgear R6260 的 OpenWrt 固件,经按步骤配置打包后成功启动:

能够正常访问:

六、面临的限制
目前这个方法仅仅支持mipsel和armv7,对于其他固件的官方openwrt如果想要实验也可以直接在下载链接获取其他架构的官方镜像进行实验。也许我认为这是一个有意义的发现,能够为仿真固件带来一些新的思路。
- 目前仅仅支持mipsel和armv7,其他架构还可以继续实验探索。
- 暂时没有尝试最新常见的luci版。
柒、结论
本文探讨了在 QEMU 环境中仿真 OpenWrt 固件的有效方法,尤其是针对二次开发固件的需求。传统的仿真方法因 OpenWrt 的特定初始化要求而无法奏效,而我们提出的新方法通过使用官方镜像并覆盖必要的文件系统,成功解决了这一问题。
这种方法简化了固件的仿真过程,使得在测试和漏洞挖掘时能够更高效地运行和调试自定义固件或者相关长厂商的二次开发固件,为相关的安全研究者提供了实用的解决方案。也欢迎师傅们与我交流,一起开发支持OpenWrt二次开发固件仿真的自动化工具,让这个工作不再“头疼”。
