IoT固件分析入门

robots

 

把前段时间GitHub 上 star 了的一个项目学一遍,地址:IoT_Sec_Tutorial
访问慢的话,gitee上也有镜像可看

Update:感觉算是一个很不错的IoT固件分析入门教程,今天收到《路由器0day》后在路上粗略地看了下目录,除了没有涉及到硬件外,这个教程差不多把固件分析的起始工作都涉及到了(至于是不是 a bit out of date 就另当别论,不过总的来说也还好⑧)

 

0x0准备

因为kali是刚上大学的时候装的,现在都出到2021了,我的版本还是2019,所以先升级一波

echo "deb http://http.kali.org/kali kali-rolling main non-free contrib" | sudo tee /etc/apt/sources.list
sudo apt update && sudo apt -y full-upgrade
[ -f /var/run/reboot-required ] && sudo reboot -f
  • 更新完后可以查看一下系统版本:
grep VERSION /etc/os-release
  • 更新系统时间(我的时间好像之前一直都不对orz)
apt-get install -y ntpdate
rm -rf etc/localtime
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
ntpdate -u ntp.api.bz
  • 切换shell(为啥升级后zsh没有直接变成默认orzz)
    先查看系统中有几种shell:
cat /etc/shells

kali自带了zsh,直接切换就行了:

cp -i /etc/skel/.zshrc ~/
chsh -s /bin/zsh

zsh配合oh-my-zsh比较好用,安装:

wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | sh

添加全路径显示:

gedit ~/.oh-my-zsh/themes/robbyrussell.zsh-theme
#然后把%{$fg[cyan]%}%c%{$reset_color%}的%c改为[$PWD]

如果想用别的桌面系统:

update-alternatives --config x-session-manager

 

0x1提取固件

之前用过binwalk,但大多都是在misc题目里处理压缩文件、图片啥的,没有仔细看过binwalk的命令
其实除了binwalk之外,还有其他的固件分析/提取工具,在GitHub上用“firmware analysis”之类的关键词能查到

给了个华硕RT-N300路由器的固件,binwalk直接提取即可。

提取出来发现没有进行加密(…16年,这也太不安全了吧orz,不过现在基本都有了

可以看到这个路由器用的是squashfs文件系统

其中squashfs-root可用于分析了

文件系统是操作系统的重要组成部分,是操作运行的基础。不同的路由器使用的文件系统格式不尽相同。根文件系统会被打包成当前路由器所使用的文件系统格式,然后组装到固件中。路由器希望文件系统越小越好,所以这些文件系统中各种压缩格式随处可见。

Squashfs是一个只读格式的文件系统,具有超高压缩率,其压缩率最高可达34%。当系统启动后,会将文件系统保存在一个压缩过的文件系统文件中,这个文件可以使用换回的形式挂载并对其中的文件进行访问,当进程需要某些文件时,仅将对应部分的压缩文件解压缩。

Squashfs文件系统常用的压缩格式有GZIP、LZMA、LZO、XZ(LZMA2)。路由器的根文件系统通常会按照Squashfs文件系统常用压缩格式中的一种进行打包,形成一个完整的Squashfs文件系统,然后与路由器操作系统的内核一起形成更新固件。

由于squashFS可以在不需要解压的情况下直接挂载,因此有许多应用场景,例如:
1、安装Linux时用的live cd
2、小型嵌入式设备中的rootfs。rootfs一般以压缩好的形式存放在ROM中,如果开机时把整个rootfs都解压到内存里再读取,对于ROM和RAM容量一般都很小的小型嵌入式设备来说性价比太低。

Binwalk命令选项

常规选项:

提取选项:

Diff:

文件签名:

熵值:

Raw Compression:

如何手动提取固件

squashfs文件系统头部特征较多,有sqsh、hsqs、qshs、shsq、hsqt、tqsh、sqlz。我们用hexdump搜索特征在文件中的地址

  • hexdump:一个二进制文件的查看工具,可转为OCT、DEC、HEX进制查看

得到如下搜索结果

hsqs位于文件的0xe20c0,用dd命令截取出固件:

  • 注:dd命令中skip指定的值只能为十进制。用shell转换进制可以使用:$((BASE#NUM))

得到了一个squashfs格式的文件

用unsquashfs解压得到squashfs-root,即用binwalk提取出的同名文件。

如果遇到binwalk之类的工具无法提取的情况,大多都是经过混淆,需要进一步处理

Binwalk如何进行提取:

通过maigc特征集与文件进行比对,但识别效率比file命令高多了
特征集:https://github.com/ReFirmLabs/binwalk/tree/62e9caa164/src/binwalk/magic

识别过程主要使用libmagic库的4个函数:

magic_t magic_open(int flags);//创建并返回一个magic cookie指针。

void magic_close(magic_t cookie);//关闭magic签名数据库并释放所有使用过的资源。

const char *magic_buffer(magic_t cookie,const void *buffer,size_t len);//读取buffer中指定长度的数据并与magic签名数据库进行对比,返回对比结果描述。

Int magic_load(magic_t cookie,const char *filename);//从filename指定文件加载magic签名数据库,Binwalk把多个magic签名文件组合到一个临时文件中用于加载

 

0x2 静态分析

给了个从Dlink固件里提取的样本,打开发现被加密了,得爆破。

kali自带了一些关于压缩文件的工具,比如生成字典用的crunch、rsmangler,爆破用的frackzip等,这些工具用法都不难

直接用frackzip破解,(根据教程的提示)得到密码beUT9Z

解压得到以下文件

  • .mbn:高通的一套用于加载网络环境的文件(modem software configuration)
  • .yaffs2:针对NAND芯片设计的嵌入式文件系统,可用unyaffs提取

unyaffs提取yaffs2

核心应该是2K-mdm-image-mdm9625.yaffs2,不确定的话可以把三个.yaffs2都提取了(然后就该复习一下嵌入式系统的目录结构了)

接下来查看配置文件,有可能从配置文件中发现敏感信息

其中的inadyn-mt.conf文件引起了我们注意,这是no-ip应用的配置文件,no-ip就是一个相当于花生壳的东西,可以申请动态域名

cat 一看,果然no-ip的用户名和密码都出现了(这么明显真的难以置信)

接下来使用firmwalker来自动化遍历

Firmwalker:

A simple bash script for searching the extracted or mounted firmware file system.

It will search through the extracted or mounted firmware file system for things of interest such as:

  • etc/shadow and etc/passwd
  • list out the etc/ssl directory
  • search for SSL related files such as .pem, .crt, etc.
  • search for configuration files
  • look for script files
  • search for other .bin files
  • look for keywords such as admin, password, remote, etc.
  • search for common web servers used on IoT devices
  • search for common binaries such as ssh, tftp, dropbear, etc.
  • search for URLs, email addresses and IP addresses
  • Experimental support for making calls to the Shodan API using the Shodan CLI

(其实就相当于一个遍历查找后缀、内容的批处理脚本)

使用脚本获得所有可能可以利用的文件(建议进入脚本目录执行)

除了配置文件外,分析存在风险的二进制程序也很重要。

在etc/init.d目录下存放启动时运行的程序和脚本,其中有一个叫start_appmgr,mgr一般指固件的主控。查看脚本:

把appmgr拖到ida

凭借一点点pwn的经验,我们发现了一个backdoor

这个漏洞被收录到CVE-2016-10178:Multiple vulnerabilities found in the Dlink DWR-932B (backdoor, backdoor accounts, weak WPS, RCE …) – IT Security Research by Pierre (pierrekim.github.io)

即向192.168.1.1:39889发送HELODBG可以直接getshell(不太清楚为啥是39889端口,静态看了好久没看出来,猜测是跟下图和label_66有关

update:用Ghidra搜到了

这个漏洞确实明显2333

这个固件还有好几个漏洞,太拉了吧Orz…

 

0x3 动态分析

QEMU和Firmadyne

QEMU这个模拟器想必都不陌生,一个近乎能够模拟所有硬件设备的软件;倒是第一次听说Firmadyne这个工具,查了一下是一个基于QEMU的分析平台,包含模拟、固件提取、调试等功能,但似乎支持的硬件设备较少?orz

部署Firmadyne

Tutorial里用的是attifyti提供的Ubuntu 14(因为作者说部署这玩意太麻烦了),但Firmadyne的作者在项目的某个issue里说了句“Ubuntu 14 也太早了”之类的话,于是打算自己部署一下
Also,如果想用直接用attifyti的AttifyOS,https://github.com/adi0x90/attifyos,目前的系统基于Ubuntu18.04,官方的下载地址在谷歌网盘

准备

因为涉及到GitHub上一些项目的下载,网络不太好的话可能需要一些帮助:

clash on kali:
下载clash并运行:https://github.com/Dreamacro/clash/releases
导入节点:wget -O ~/.config/clash/config.yaml  clash_url

配置代理:
gsettings set org.gnome.system.proxy mode 'manual'
gsettings set org.gnome.system.proxy.http port 7890
gsettings set org.gnome.system.proxy.http host '127.0.0.1'
gsettings set org.gnome.system.proxy.socks port 7891
gsettings set org.gnome.system.proxy.socks host '127.0.0.1'
gsettings set org.gnome.system.proxy ignore-hosts "['localhost', '127.0.0.0/8', '::1']"\

进行配置,访问:
http://clash.razord.top/

** 注:以下绕了好多弯,最后也没成功,用了AttifyOS ?????

【方案1】安装Firmadyne

apt-get install qemu-system-arm qemu-system-mips qemu-system-x86 qemu-utils
apt-get install busybox-static fakeroot git dmsetup kpartx netcat-openbsd nmap python-psycopg2 python3-psycopg2 snmp uml-utilities util-linux vlan
git clone --recursive https://github.com/firmadyne/firmadyne.git
cd ./firmadyne
./download.sh

配置Postgresql:

# 安装数据库
sudo apt-get install postgresql
# 创建用户,注意要设置密码为 firmadyne
sudo -u postgres createuser -P firmadyne
# 创建数据库
sudo -u postgres createdb -O firmadyne firmware
# 初始化数据库
sudo -u postgres psql -d firmware < ./firmadyne/database/schema

如果出现如下错误

could not connect to database template1: could not connect to server: No such file or directory.

Is the server running locally and accepting

connections on Unix domain socket “var/run/postgresql/.s.PGSQL.5432”?

有可能是没有初始化数据库(至少我是因为这个),用如下方法解决:

# 设置postgres用户的密码
passwd postgres

# 创建postgresql的文件夹
sudo mkdir /data
sudo chmod o+w /data
su - postgres
mkdir /data/postgresql
mkdir /data/postgresql/data

# postgres用户初始化数据库
/usr/lib/postgresql/13/bin/initdb -D /data/postgresql/data

# 启动数据库
/usr/lib/postgresql/13/bin/pg_ctl -D /data/postgresql/data -l logfile start

#查看是否监听了端口(结果应类似下图)
netstat -nlp |grep 5432

参考:https://www.cnblogs.com/0x200/p/14026460.html

接下来应该就能按照官方的Usage来使用了(没试):firmadyne: Platform for emulation and dynamic analysis of Linux-based firmware

【方案2】安装firmware-analysis-plus

因为用Firmadyne直接进行调试比较麻烦,所以用了FAP这个项目。

这是个国人写的中文项目,没啥好说的:liyansong2018/firmware-analysis-plus: 开源固件仿真平台,使用 firmadyne 一键模拟固件 (github.com)

安装作者提供的binwalk的时候一直报错(kali2021 & ubuntu18 both),导致一直卡在提取固件的步骤(emmmm哪位大哥部署成功后教我一下)

对此提了个issue

【方案3】AttifyOS

这个方法比较稳,自己部署也太折磨人了(外加考试周给娃弄傻了)

注:密码是attify

模拟执行固件

模拟固件运行:

通过192.168.0.50即可访问固件

调试固件

这个部分用到了Damn Vulnerable Router Firmware这个项目,大小400M+,建议上gitee clone

安装以下工具:

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

进入DVRF/Firmware/,用binwalk提取DVRF_v03.bin

提取出来的目录里有个文件夹pwnable,里面存放着漏洞程序示例,选取stack_bof_01程序进行实验,程序的源代码可以在DVRF/Pwnable Source/Intro/里查看

首先用reasdelf查看程序架构

!

(顺手试了一下checksec,这里居然有装?)

拷贝qwmu-mipsel-static到固件根目录:

cp (which qemu-mipsel-static) .

用qemu虚拟运行stack_bof_01:

以调试的方式启动程序,并在1234端口进行监听:

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

打开一个新的shell,运行以下命令:

gdb-multiarch pwnable/Intro/stack_bof_01

# 设置架构
set architecture mips

#设置调试端口
target remote 127.0.0.1:1234

创建trash触发溢出:

pattern create 300

带上它重新进行调试

gdb attach后继续让程序运行,触发vul

接下来就直接ret2system,但经过尝试后发现,如果直接把跳转地址设置为后门函数dat_shell的起始地址0x400950会触发异常

查看函数汇编代码(MIPS…看不懂的话可以边看边学一波,MIPS 通用寄存器_flyingqr的专栏-CSDN博客_mips寄存器MIPS汇编指令集 – 深海之炎 – 博客园 MIPS的汇编指令 · 语雀

调试中发现,当执行到0x400970时,gp寄存器指向了不可访问的地址

而gp的值是由上一条指令得到的

本来执行后v0要指向 指向__DT_MIPS_BASE_ADDRESS的指针

简单来说就是强行跳转到backdoor之后,因为t9(默认在运行中指向当前函数的起始地址)没有发生改变,导致在执行0x400970时产生异常访问

但可以发现(其实是按照exp来推…)main函数中的gp在-0x7fe4后刚好指向PTR__DT_MIPS_BASE_ADDRESS*(猜测原因是源代码中后门函数在main函数后面且没有被调用,导致编译时认为main函数和后门函数的 gp和表的偏移 相等)

于是得到


update:

main函数中

所以gp在函数执行完毕后依旧指向的是基地址表


感觉对于mips程序的分析,Ghidra比IDA好用些

从这题也能看出mips和x86、x64的不同之处,除了这种特殊情况外,大多数情况下还是应该寻找gadget来进行跳转改变t9寄存器

这一节就到这,DVRF这个项目还设计了一些别的漏洞程序可以再进行分析

 

0x4 解密固件

访问dlink的ftp服务器获得几个DIR-882的固件(图中选中的文件),时间跨度为2017~2020年

ftp://ftp2.dlink.com/PRODUCTS/DIR-882/REVA/

解压得到固件和对应的版本说明

加密固件发布方案

一般来说,有三种发布固件的方案

  • 出厂时未加密,解密例程在高版本固件v1.1中给出,为后续的加密固件做准备
    对于这个方案,我们可以通过解密v1.1来获得解密例程
  • 出厂时的固件已经加密,供应商决定更改高版本固件的加密方式,并发布了包含解密例程的未加密中间版本v1.2
    这一方案与上面那个类似
  • 出厂时的固件已经加密,供应商决定更改高版本固件的加密方式,并发布了包含解密例程的使用原加密方式加密的过渡版本v1.3
    这种方案对获取解密例程的难度较大,可从硬件中直接提取固件或对发布的v1.3进行分析

DIR-882的固件发布方案为第一种,示意图如下

虽然个人认为第三种方案才是较为常见的,但教程中并没有讲到。猜测除了从硬件中提取外,还可以通过模拟器模拟然后进行patch或拿头还原

解密过程

用binwalk分析最新和最早的两个固件

经过binwalk分析,FW104B02正是存在解密程序的中间版本(从文件名也能看出)

对于判断固件是否被加密/混淆还可以使用之前提到的binwalk -E 来查看文件各个区域的熵值

提取该固件

binwalk -eM DIR882A1_FW104B02_Middle_FW_Unencrypt.bin

在最终目录下搜索找到imgdecrypt,从名字看出是下个版本固件的解密例程

可以静态分析程序的解密算法,也可以直接运行程序来对加密固件进行解密。

在本地运行时依旧需要借助qemu-mipsel-static模拟器,使用方法和上一节的模拟过程类似,不表。

利用imgdecrypt还可以还原出ftp服务器上提供的最新的固件,所以可能后续版本和Dlink其它型号的路由器也能用这个程序还原固件?Orz

 

0x5 修复固件运行环境

有一些固件因为硬件依赖等原因导致qemu和firmadyne之类的软件无法正确模拟

比如下面这个

ftp://ftp2.dlink.com/PRODUCTS/DIR-605L/REVA/DIR-605L_FIRMWARE_1.13.ZIP

模拟固件运行的实质其实就是把固件的Web程序跑起来,而模拟失败则说明Web程序运行出错了,我们接下来就要看看Web程序报错的原因以及如何修复运行环境。

尝试运行固件

首先binwalk提取固件,进入文件系统目录squashfs-root-0

找到web服务程序Boa

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

用qemu-mips-static运行,结果产生了段错误

mips 是32位大端字节序

mipsel 是32位小端字节序

分析错误并修复

注:APMIB 是个Realtek的玩意(原来realtek还有做路由器相关的东西…)

有些CVE(如CVE-2019-19823)就跟APMIB有关 —TOTOLINK and other Realtek SDK based routers – full takeover (sploit.tech)

MIB:management information base,与SNMP有关,可在维基里进一步了解:Management information base – Wikipedia

由于没有flash,导致读mib失败

拖到反编译工具中分析。先定位到字符串“Initialize AP MIB failed!”的位置。注意到在输出这个字符串前有个调用APMIB初始化的跳转,在此下断点,IDA远程调试

QEMU的远程调试不需要gdbserver,-g 指定端口,ida 远程调试选项指定相应端口就行

简单调试后发现,程序进入APMIB初始化函数后将返回值赋给v0,返回后对返回值进行判断。(跟着教程做完后,发现其实用静态分析看的就很明显,但多调试总是没有坏处的嘛)

跳转回去的位置在这:

我们先试试看把原来的跳转patch一下能不能运行正常固件boa。

有以下两个可行方案:

  1. hxd(或其他二进制编辑器),把benz(0x14,不为0跳转)改为beqz(0x10,为0跳转)
    这个方法比较直接,定位到指令后把0x14改为0x10即可
  2. Ghidra,把bne改为beq(Ghidra中反编译出的原指令为bne)
  • 如何用Ghidra进行patch并保存:
  1. 下载python脚本ghidra_SavePatch 并放到Ghidra存放python脚本的目录(找不到目录的话,如图)。
  2. 按照下图导入脚本。
  3. patch
  4. 光标放在更改的指令,在script manager里运行脚本。

参考:Patching Binaries With Ghidra – RangeForce

  • 不用ida的原因:

把patch保存到文件中时,发现报错,稍微搜了一下,依然不知道是啥原因orz

418228: has no file mapping (original: 14 patched: 10)…skipping…

再次运行试试,发现又报错了:

再放到Ghidra里分析,依旧通过字符串定位错误触发点。

两个函数(调用的地方位于websAspInit)里的报错由open函数造成(图为create_chklist_file(),但两个报错类似,均为一开始打开某个文件出错)

用IDA调试发现报错后仍然继续运行,异常发生在执行apmib_get()时:

具体在0x4084c9b0时,把[0+v0]里的值赋给v1,而0x1001明显是一个访问不了的地址

查一下apmib_get是干啥的。似乎是用来获取硬件配置信息,但我们要想让固件跑起来可以不需要这个。那么直接把获得apmib_get入口后的跳转语句nop掉

重新尝试运行

固件会一直尝试朝 ioctl(设备驱动的控制接口)发送0x89f0(应该是一个SIOCDEVPRIVATE),我们模拟的固件并不支持,但没啥大影响。(用Google搜一下“Unsupported ioctl: cmd=0x89f0”可以找到一些蛮有意思的东西2333)

关于ioctl:ioctl()函数详解_shanshanpt的专栏-CSDN博客

查看报错的页面(用vim看代码舒服一些),嗯,前端的东西:

从文件名可以猜到是个跟路由器界面语言选择有关的文件。

文件不长,注意到有个函数跟语言和硬件有关:

那么我们可以不让它运行到这个页面。

查找调用了*LangSelect.asp的页面,发现只有一个first.asp

直接修改,重新运行完事

这个固件成功运行后可以顺便看一看这个洞: (CVE-2018-20057)D-Link DIR-619L&605L 命令注入漏洞 – Wiki ,直接用了后门

这节的错误解决方法均通过修改指令,《路由器0day》书中的方法是伪造.so来劫持函数,也值得一学:分析固件第一步

 

结束

纯初学者,如果有啥地方写的不到位或者出错了,还请指出
以上

(完)