【技術分享】IoT固件分析入門

把前段時間GitHub 上 star 了的一個項目學一遍,地址:IoT_Sec_Tutorial
訪問慢的話,gitee上也有鏡像可看
Update:感覺算是一個很不錯的IoT固件分析入門教程,今天收到《路由器0day》后在路上粗略地看了下目錄,除了沒有涉及到硬件外,這個教程差不多把固件分析的起始工作都涉及到了(至于是不是 a bit out of date 就另當別論,不過總的來說也還好⑧)
01 準備
因為kali是剛上大學的時候裝的,現在都出到2021了,我的版本還是2019,所以先升級一波
echo "deb http://http.kali.org/kali kali-rolling main non-free contrib" | sudo tee /etc/apt/sources.listsudo 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 ntpdaterm -rf etc/localtimecp /usr/share/zoneinfo/Asia/Shanghai /etc/localtimentpdate -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
02 提取固件
之前用過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 cd2、小型嵌入式設備中的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簽名文件組合到一個臨時文件中用于加載
03 靜態分析
給了個從Dlink固件里提取的樣本,打開發現被加密了,得爆破。
kali自帶了一些關于壓縮文件的工具,比如生成字典用的crunch、rsmangler,爆破用的frackzip等,這些工具用法都不難
crunch:Kali使用crunch生成密碼字典 – 青檬小棧
直接用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 ormounted firmware file system.
It will search through the extracted or mounted firmwarefile system for things of interest such as:
- etc/shadowand etc/passwd
- list out theetc/ssl directory
- search forSSL related files such as .pem, .crt, etc.
- search forconfiguration files
- look forscript files
- search forother .bin files
- look forkeywords such as admin, password, remote, etc.
- search forcommon web servers used on IoT devices
- search forcommon binaries such as ssh, tftp, dropbear, etc.
- search forURLs, email addresses and IP addresses
- Experimentalsupport 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…
04
動態分析
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 7890gsettings set org.gnome.system.proxy.http host '127.0.0.1'gsettings set org.gnome.system.proxy.socks port 7891gsettings 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-utilsapt-get install busybox-static fakeroot git dmsetup kpartx netcat-openbsd nmap python-psycopg2 python3-psycopg2 snmp uml-utilities util-linux vlangit clone --recursive https://github.com/firmadyne/firmadyne.gitcd ./firmadyne./download.sh
配置Postgresql:
# 安裝數據庫sudo apt-get install postgresql# 創建用戶,注意要設置密碼為 firmadynesudo -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 /datasudo chmod o+w /datasu - postgresmkdir /data/postgresqlmkdir /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-multiarchwget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef.sh | shsudo 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這個項目還設計了一些別的漏洞程序可以再進行分析
05 解密固件
訪問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
06 修復固件運行環境
有一些固件因為硬件依賴等原因導致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還有做路由器相關的東西…)
- apmib_init(), 從 flash 讀出 mib 值寫入 RAM —Realtek apmib library @ 邱小新の工作筆記
有些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并保存:
- 下載python腳本ghidra_SavePatch 并放到Ghidra存放python腳本的目錄(找不到目錄的話,如圖)

- 按照下圖導入腳本。
- patch

- 光標放在更改的指令,在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來劫持函數,也值得一學:分析固件第一步
07 結束
純初學者,如果有啥地方寫的不到位或者出錯了,還請指出