Tenda无线放大器固件提取以及分析
心血来潮有机会把Tenda无线放大器的flash提取出来了,然后在此记录下解包以及安全分析过程
固件提取
flash提取就不多说了,用热风枪+编程器一把梭即可
拿到flash提取的内存bin文件后,使用binwalk查看,发现了ubi文件,但是直接使用github已有的工具ubi_reader以及ubidump.py都无法成功提取。通过跟着ubidump.py一步步调试发现是两个问题:
- 该固件的ubi block_size过大,为0x20000。而ubidump.py中定义的太小,无法成功识别到下一个块的magic头
- 该固件每隔0x800字节都会有0x40字节的padding byte,需要删除掉。
解决以上两个问题后,即可看到squashfs文件,将其dump下来。
此处贴一下与ubidump.py的diff内容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
35base ❯ diff ubidump.py ubidump.py.bak
296c296
< for lnum in range(self.blks.vmap[self.volid].__len__()):
---
> for lnum in range(self.blks.maxlebs):
358,360c358,359
<
< for log_blocksize in range(0x40,0x30000, 4):
< self.fh.seek(log_blocksize)
---
> for log_blocksize in range(10,20):
> self.fh.seek(1<<log_blocksize)
363,364c362
< return log_blocksize
<
---
> return 1<<log_blocksize
383,390d380
< # patch to handle duplicate lnums in VTBL
< if vid.vol_id in self.vmap and vid.lnum in self.vmap[vid.vol_id]:
< if vid.vol_id == VTBL_VOLID:
< print("WARNING: duplicate VTBL lnum %d, skipping" % vid.lnum)
< continue
< else:
< print("WARNING: duplicate volid/vollnum/lnum vs ori lnum %x/%d/%d vs %d, overwriting" % (vid.vol_id, vid.lnum, lnum, self.vmap[vid.vol_id][vid.lnum]))
<
441,444c431,433
< # check if we at least have a valid VID header
< if self.nr_named == 0:
< self.vid = UbiVidHead()
< self.vtbl = [ UbiVtblRecord() ]
---
>
> self.vid = UbiVidHead()
> self.vtbl = [ UbiVtblRecord() ]
然后是每隔0x800字节进行patch的脚本:
1 | import os |
然后使用binwalk提取squashfs即可拿到文件系统。
漏洞分析
系统初始化服务
首先查看/etc_ro/init.d/rcS文件,发现主要工作为
- 创建相关目录文件以及配置环境变量
- 加载各种无线有线内核模块
- 其他网络配置
passwd文件密码:$1$nalENqL8$jnRFwb1x5S.ygN.3nwTbG1
httpd 服务分析
直接搜索httpd文件了,文件位于/bin/httpd。使用ida打开应该只是一个bind socket的功能,实际处理逻辑在其他二进制程序中。
查看字符串内容,发现一些uri。
这些字符串位于函数init_route_auth_file中(突然发现甚至没有去符号:-))。函数功能为写入route.txt(默认路径为/var/route.txt)以下内容:
1 | See route.txt file |
写入auth.txt(默认路径为/var/auth.txt)以下内容:
1 | auth.txt - Authorization data |
输入点提取
web的处理逻辑主要位于/lib/libgo.so中,其通过j_websDefineAction函数以及j_moduleRegister函数注册了相关的URL.
通过j_websDefineAction注册的URL如下,其实这里都是送入了formModules函数处理,该函数进一步分配给moduleRegister注册的函数
通过j_moduleRegister注册的URL如下:
还有一个位置对于webCgiDoUpload的处理:
可疑漏洞
1. (已验证)fast_setting_timezone函数时区参数没有校验参数
三个地方可能会溢出:sscanf, strcpy, SetValue
formSystemTimeSet函数也有类似的逻辑,当time_type设置为manual时,可以通过timeZone来实现溢出
2. fromSystemPwdSet函数”isQuick”是否可以绕过
感觉不会那么直接,可能workmode会有点含义功能在里面,无法模拟 flashinfo,有点困难
3. formLedCfgGet函数可能存在溢出
内部是否会对SetValue函数存在检查?
SetValue 函数定义位于 libcommmon.so 文件中,发现其会与 cfmd 程序进行通信。
cfmd 程序负责 flash 中的信息读写,由于模拟中无法模拟 flash 相关功能,导致无法对其进行验证
4. (已验证)formSystemTimeSet可能存在溢出
5. (已验证)upgrade函数固件上传漏洞命令注入漏洞
上传固件名没有作校验,可以直接进行命令执行。
构造文件名为’`touch eutopia.txt`.bin’ 即可
然后在下面升级固件中选择上传该固件即可
decry_firm逻辑分析
在upgrade逻辑中使用了decry_firm二进制程序进行解密:
该文件逻辑比较简单,对目标固件进行了签名验证以及解密。其中解密对应了516字节以后的内容。
首先使用openssl_aes_password获取到aes密钥,使用硬编码的signed_key来进行rsa解密:
查看解密函数openssl_aes_decrypt,其中包括了salt,aeskey等解密参数。使用cbc-128方式解密
固件模拟
尝试使用用户模拟加载 http 服务
进入 squash-root 目录,执行以下命令:
1 | sudo chroot . ./qemu-arm-static ./bin/httpd |
网站根目录为 ./squashfs_rootfs/webroot 不过软链接失效了,可以直接将 ./squashfs_rootfs/webroot_ro/ 移动过来
发现报错:
1 | Yes: |
怀疑是缺少网卡配置 br 网口,使用以下命令添加:
1 | sudo apt install uml-utilities bridge-utils |
ip命令(超绝简化版)
1 | sudo brctl addbr br0 |
添加后即可成功使目标监听在 80 端口,不过发现运行会有日志报错:
1 | connect: No such file or directory |
不影响模拟进行原因是 /bin/cfmd 没有启动,这个应该是控制了 default.cfg 的读取和写入的,然后会开启 socket 与 httpd 进行通信但是无法成功启动,应该是要有硬件支持才行(mtd)没有也可以模拟
接下来对以上提到的 可疑漏洞点 进行验证 (dokidoki的素捏 QWQ
使用浏览器访问 路由,发现如果访问 index.html 的话会无限循环重定向,问了 gpt 是没有给定合法的 Cookie,需要先访问 login.html 路由,访问该路由发现可以正常返回 登录页面:
逆向固件逻辑可以看到访问路由不能为127.0.0.1,改为br0的网桥ip地址即可。
看到kanxue上也有一位大佬做了相关型号的分析,比我的详细的多,可以参考:
[原创]TD路由器环境模拟与漏洞分析
这位大佬在 mtd 设备实现上采用了编程实现 hook 掉对应 cfm socket 的方法:
我们从 /bin/cfmd 的程序的模拟执行照搬照抄即可成功运行。这样前面的2,3两个洞就可以进行验证了。