IOT 固件分析入门
本文最后更新于 2022年12月21日 中午
01-提取 IoT 固件
首先我们需要安装固件提取工具 binwalk,由于该工具的安装流程比较繁琐,建议直接使用 Kali Linux,该系统默认安装有 binwalk。
1 |
|
提取固件系统的参数是-e,我们可以加上-t -vv 参数查看详细的提取过程。通过输出信息,可以得知该固件系统没有加密压缩,且系统为 Squashfs。
1 |
|
提取出来的文件夹为_RT-N300_3.0.0.4_378_9317-g2f672ff.trx.extracted,其中的 squashfs-root 就是我们想要的该固件的文件系统。
那么 binwalk 的是怎么实现提取的呢?原理就是,通过自带的强大的 magic 特征集,扫描固件中文件系统初始地址的特征码,若匹配成功,则将该段数据 dump 下来,这个 magic 特征集已公开。
1 |
|
以这个固件为例,是 Squashfs 文件系统,对应的扫描特征码为 hsqs。
我们可以做个实验,使用 hexdump 搜索 hsqs 的地址,为 0xe20c0,这个就是文件系统的初始地址。
1 |
|
使用 dd 命令截取地址 925888(0xe20c0)之后的数据,保存到 rt-n300-fs。
1 |
|
最后,使用 unsquashfs rt-n300-fs 命令解析 rt-n300-fs 文件,得到的 squashfs-root 就是固件系统,这个跟上述 binwalk 提取的那个是一样的。
1 |
|
02 静态分析 IoT 固件
得到固件后,若直接打开,会发现该固件被加了密,无法直接解压缩,这是厂商对该固件做了保护,防止大家逆向分析他的固件。
通过 frackzip 工具可以破解该 zip 的密码,时间要挺久的,我直接告诉你吧,密码是 beUT9Z。
解压后发现文件夹中有多个.yaffs2 后缀的文件,这些都是固件的文件。
yaffs2 里有几个看上去是 recovery 的镜像,核心的应该是 2K-mdm-image-mdm9625.yaffs2 ,我们下面就来提取该文件,我首先用 binwalk 来提取它,但提取出来的文件乱七八糟,不知道什么原因,后来看网上推荐直接用 yaffs 的原生工具 unyaffs 提取,就可以了,文件系统清晰明了。
1 |
|
接下来我们查找该路径下的所有.conf 文件,.conf 文件多是配置文件,有可能从中可以发现敏感的信息。
1 |
|
其中的 inadyn-mt.conf 文件引起了我们注意,这是 no-ip 应用的配置文件,no-ip 就是一个相当于花生壳的东西,可以申请动态域名。我们从中可以发现泄露的 no-ip 的登陆账号及密码。
1 |
|
除了上述泄露的 no-ip 账号密码,我们还从 shadow 文件中找到了 root 账号的密码,通过爆破可以得到 root 的密码为 1234。
1 |
|
其实并不止有.conf 文件会泄露信息,还有很多其他后缀的敏感文件会泄露信息,我们接下来使用 firmwalker 工具来自动化遍历该固件系统中的所有可疑文件。
1 |
|
命令如下,firmwalker 会将结果保存到 firmwalker.txt。
1 |
|
看了下该工具的源码,没啥亮点,就是遍历各种后缀的文件。
后缀都在 data 文件夹中的各个配置文件中。
分析完敏感的配置文件后,我们接下来分析存在风险的二进制程序。查看自启动的程序,一个 start_appmgr 脚本引起了我们注意,mgr 一般就是主控程序的意思。
该脚本会在开机的时候以服务的形式运行/bin.appmgr 程序。
通过 IDA 进行反编译,在 main 函数中发现了一个管理员当时为了方便调试留下的后门,只要连接该固件的 39889 端口并发送 HELODBG 的字符串,就可以进行远程执行命令。
POC 如下:
1 |
|
除了以上这些,该固件还存在多个漏洞,详细的报告可以参考:
1 |
|
03 动态分析 IoT 固件
动态分析固件之前,需要先把固件运行起来,但我们手头又没有路由器、摄像头之类的物联网硬件,该如何运行呢?这就需要虚拟执行,虚拟执行你就把它想象成一个虚拟机可以运行各种物联网 OS 就是了。虚拟执行 IoT 固件需要用到 firmadyne 工具,该工具很难安装,在 kali 上安装一直报错,你们可以尝试下,反正我是放弃了,最终还是乖乖地用 attifyti 提供的物联网渗透专用虚拟机,下载下来的文件直接用 vmware 导入就行了。
https://www.dropbox.com/sh/xrfzyp1ex2uii53/AAAF0mdA1qFaEBDYZoIxaQRma?dl=0
该系统为 ubuntu14,密码为 password@123,下面我们来演示虚拟执行一个 dlink 固件。
在 tools/fat 路径下运行 fat.py,输入固件路径 DWP2360b-firmware-v206-rc018.bin,然后是固件的品牌 dlink,依次输入数据库密码 firmadyne 和用户密码 password@123。
1 |
|
脚本执行成功后,会回显一个 IP 地址,这个 IP 就是模拟的固件地址。
以上只是简单地演示如何虚拟执行一个固件,下面我们就来实操如何通过动态调试分析一个固件,接下来的固件采用 DVRF,这是个网友自制的充满漏洞的固件,供学习用的。
1 |
|
开始之前,安装以下工具,动态调试中会用到。
1 |
|
安装 keystone-engine 时可能会报错,参考这个链接。
https://github.com/avatartwo/avatar2/issues/23
安装好工具后,就开始对固件进行分析啦,固件路径如下。
DVRF/Firmware/DVRF_v03.bin
使用 binwalk 提取固件文件系统。
1 |
|
提取出来的系统有个文件夹 pwnable,这个文件夹就是存放着有漏洞的程序示例,我们选取缓冲区漏洞程序 stack_bof_01 进行实验。首先使用 readelf 命令查看该程序的架构。
1 |
|
拷贝 qemu-mipsel-static 到当前目录,然后配合 chroot 虚拟执行 stack_bof_01 固件,可以成功执行。qemu 是一款轻型的虚拟机。
1 |
|
查看 stack_bof_01 的源码,可以发现明显的 strcpy 内存溢出漏洞,当参数 argv[1]超过 200 时,就会出现 buf 溢出的现象。
1 |
|
以调试的方式启动 stack_bof_01,在本地的 1234 端口监听调试。
1 |
|
运行以下命令开始调试。
1 |
|
gdb 运行后,会自动加载 gef 插件,然后设置固件架构为 mips。
1 |
|
设置完远程调试的 IP 和端口,就可开始调试 stack_bof_01 程序了。
1 |
|
调用命令查看样本的所有函数,可以看到各个函数的地址。
1 |
|
如果你感兴趣,可以使用命令查反汇编看下 main 函数的汇编码,这是 mips 架构的汇编码,跟 x86 的相差很大,完全看不懂。。
1 |
|
然后我们使用命令创建一个随机的 300 字节流,作为攻击字符串,用于测试参数溢出的点。
1 |
|
重新带参数调试 stack_bof_01。
1 |
|
gdb 挂上去后,输入 c 回车让程序跑起来,会发现程序崩溃了,SIGSEGV 内存出错,指针 ra 指向 0x63616162,对应的 ASCII 是”baac”。
使用命令查看该溢出点在攻击字符串的什么位置,是位于 pattern 的第 204 位。
1 |
|
然后反汇编 dat_shell 函数,查看其函数地址,我们的目的是要让程序本来执行 0x63616162,变成执行 dat_shell 的地址,理论上应该把 0x63616162 改成 0x400950,但书上说要略过前 3 条 gp 相关的指令,我也不知道为啥,有兴趣的同学 google 下吧。
1 |
|
将待执行的函数地址拼凑到 204 个字节后面,便可劫持程序执行流到 dat_shell 函数(0x40095C),从而实现缓冲区溢出攻击。
1 |
|
最后,试了下 0x400950 确实不行,会报错。
1 |
|
04 解密 dlink 固件
本次我们要研究的是路由器 Dlink-882 的固件,下载地址在:
1 |
|
解压出来的固件版本从旧到新依次为:
1 |
|
其中下图标记的两个固件是打包在同一个 zip 固件包中的,这里大家应该可以察觉到,FW104B02 有点特别,而且通过名字我们也猜测到它是未加密的中间版本。
目前最新的版本是 FW130B10,通过 binwalk 进行解析,会发现该固件被加密了,binwalk 无法解析。
再来看下最早版本的固件 FW100B07,使用 binwalk 解析是能直接提取的,没有被加密过。
综上,我们得知了,固件版本迭代的过程经历了固件未加密到固件加密,说明中间肯定有个中间版本(类似下图 V 2.0),下图是固件从未加密到加密的升级原理图。
从 binwalk 解析结果来看,能确定 FW100B07、FW101B02、FW104B02 是未加密的,而 FW104B02 之后的版本,FW110B02、FW111B01、FW120B06、FW130B10 均是加密的,那么 FW104B02 就可以确定是中间版本了。
确定了 FW104B02 是中间版本后,我们大概率可以从 FW104B02 的固件系统中找到解密程序了,方法是在/bin 和/usr/bin 目录下寻找 decrypt 字样的程序,很幸运,我们在 bin 目录下找到了 imgdecrypt,这个程序猜测就是解密程序了。
对比下最早版本的 FW100B07 版本,/bin 目录下就没有 imgdecrypt 程序。
接下来就可以分析下 imgdecrypt 的加密逻辑了,看下如何破解加密的固件,通过 file 命令,我们得知该程序是 MIPS 架构的可执行文件。
使用 IDA 分析,好吧,完全看不懂,放弃了。。这个是 mips 的汇编指令,跟 x86 汇编的语法不一样,感兴趣的同学可以学一下。
简单地从导入表中看下函数,发现有 RSA、AES,看来是较复杂的加密方式,不是简单的异或运算,头疼。
既然逆向不出加密算法,是不是就没办法解加密固件了呢?并不是,我们可以直接本地运行解密程序 imgdecrypt,来解被加密的 FW120806 固件。由于该程序是 mips 架构的,还得借助 qemu-mipsel-static 模拟器来运行。
使用 qemu-mipsel-static 之前要用 chroot 将当前固件系统路径设置为 root 路径,不然会提示缺少一些系统库(找不到库路径)。下图,我们就模拟运行 imgdecrypt 成功了,程序用法很简单,参数提供待解密的固件路径就行了。加密成功后,用 binwalk 就能解析出固件的系统结构了,进而提取固件系统。
同样的,最新的 FW130B10 版本也能通过该程序进行解密,按照这个逻辑,可能 Dlink 的其他系列路由器也能通过 imgdecrypt 解密,大家可以去试一下。
05-修复固件运行环境
之前我们学习了如何模拟运行 IoT 固件,但是,现实中并不是所有固件都能模拟运行成功,比如固件运行可能依赖于硬件,qemu 无法完全模拟,所以,本节我们就来学习如何修复固件的运行环境,从而成功模拟固件运行。
我们本次使用的固件是 D-Link 的 DIR-605L FW_113 固件,下载地址:
1 |
|
拿到固件后,我们惯用的使用 fat 工具进行模拟,会发现它报错了,Interfaces 那里也为空,说明模拟运行失败了。
上 github 上查询该错误,作者无奈地回复说其实不是所有固件都能用 firmadyne 模拟的,尤其是那些需要与硬件设备交互的固件。
好,那就换种思路,模拟固件运行的实质其实就是把固件的 Web 程序跑起来,而模拟失败则说明 Web 程序运行出错了,我们接下来就要针对看看 Web 程序报错的原因以及如何修复运行环境。首先用 binwalk 提取固件的文件系统,提取出来的路径为 squashfs-root-0。
我们可以使用 find ./ -name boa 命令来定位该固件的 Web 程序。Boa 程序是一个轻量级的 web 服务器程序,常见于嵌入式系统中。dlink 就是在 boa 开源代码的基础上新增了很多功能接口以实现路由器上的不同功能。boa 程序的路径为/bin/boa,同时我们发现在/etc/boa 路径下还有个 boa 的密码配置文件,我们可以直接获取到 boa 加密后的密码。
我们直接使用命令模拟运行程序,会发现报错了,提示初始化 MIB 错误。
1 |
|
使用 IDA 对 boa 程序进行逆向分析(需要先添加 mip 插件:https://github.com/devttys0/ida),定位到字符串“Initialize AP MIB failed”,接下来就在这里下断点,调试看下什么情况下会报这个错。
首先使用 qemu 调试命令运行 boa 程序。
然后在用 ida 打开 boa 程序,选择 Debugger 为 Remote GDB(我这里是远程调试 129 虚拟机里的 boa 程序),点击 Debugger -> Process Options 进行配置 IP 和端口。
然后点击 Debugger -> Attach to process 附加调试,直接运行到断点处,这里的 jalr 命令即为调用 apmib_init 函数。
google 下这个这个函数是干嘛的,原来是从 flash 中读取 mib 值到 RAM 中,模拟环境没有 flash 硬件,所以应该会读取失败。
经过调试,我们知道由于没有 flash 硬件,apmib_init 读取数据失败返回 0,赋值给%v0,然后 bnez 命令对$v0 进行检测,若为 0,则回显初始化失败,报错退出。
我直接在 IDA 中对字节码进行修改,将 bnez(0x14)命令改成 beqz(0x10),就可以进入正常的逻辑顺利运行下去了。
patch 完程序后,再次运行 boa,发现它不再报“Initialize AP MIB failed”错误了,但又来了 2 个“Create chklist file error!”错误以及一个内存崩溃错误。
前 2 个“Create chklist file error!”来自于 create_chklist_file 和 create_devInfo_file 函数,后面那个内存崩溃是 apmib_get 函数导致的。
定位到“Create chklist file error!”字符串,经分析确认这个错误是 open 函数的返回值导致,但由于不影响执行我们就细看了。
重点放到 apmib_get 函数,从上面我们可以知道该函数的功能是从 RAM 读取 MIB 值,所以有可能又是硬件依赖导致的错误。该函数正常情况下会返回 4 种值。
由于代码复杂,我们就不细致分析了,直接参考作者的代码编写劫持代码,目的是伪造 apmib_init 和 apmib_get 函数,让其返回正确的值,由于为了方便 IDA 调试,作者把 fork 也一并劫持了(IDA 遇到 fork 异常)。
编写好 apmib.c 后,使用 mips-linux-gnu-gcc 命令将该代码编译成 so,在这之前得先下载 mips 的 gcc。
1 |
|
然后使用命令进行编译。
1 |
|
使用 LD_PRELOAD 参数指定劫持 so,这样当 boa 执行到 apmib_init 和 apmib_get 时,就会调用到 apmib-ld.so 里面的函数,从而顺利运行,运行成功的效果如下。
我们可以看到 Web 服务以跑起来了,80 端口。
然而当我们打开页面时,页面自动跳转到了 Wizard_Easy_LangSelect.asp,程序又双叒崩溃了。。
看下该 ASP 文件的代码,通过文件名可以猜测功能是选择页面语言,而它尝试从硬件设备读取语言,显然又会出错了。
那我们就想办法不让它进入这个页面,查看入口网页,发现逻辑是先判断系统语言,成功则直接进入 Webcome 界面。
所以…我们直接魔改,两种情况都进入 Wizard_Easy_Welcome.asp 界面。
改完后再次访问,终于成功了,不容易,至此 Web 服务已经成功跑起来了,就可以开始挖洞了。
参考
微信支付
支付宝支付
“如果你觉得这个博客对你有帮助,欢迎打赏!”