随着5G时代的到来,物联网扮演的角色也越来越重要,同时也伴随更多的安全风险。IOT安全涉及内容广泛,本系列文章将从技术层面谈一谈笔者对IOT漏洞研究的理解。笔者将从固件、web、硬件、IOT协议、移动应用五个维度分别探讨,由于水平能力有限,不当或遗漏之处欢迎大家指正补充。
IoT固件基础
之所以将固件作为第一个探讨的主题,因为比较基础,IOT漏洞研究一般无法绕过。以下将介绍固件解密(若加密)、解包打包、模拟和从固件整体上作安全评估四部分。
1.1 固件解密
有些IOT设备会对固件加密甚至签名来提高研究门槛和升级时的安全性,因为加解密比较耗费资源,这类设备一般配置会比较高,比如一些路由器和防火墙。
1.1.1 固件加密判断
判断固件是否加密比较简单,有经验的小伙伴有二进制编辑器打开就能看出一二,一般会存在以下特性。
除了固件指示头没有可见字符,(除去header)数据按比特展开01频率基本一致
binwalk(-e)无法解析固件结构,且(-A)没有识别出任何cpu架构指令
如果满足上述特点,就会猜测固件已被加密,固件解密一般会从这几个角度,但也不局限于下面的方法。
1.1.2 硬件获取密钥
此种方法只限于固件始终以加密状态存在,当系统启动时才通过解密解包加载至flash,且设备缺乏(UART/JTAG等)动态调试手段。由于flash中有完整的解密过程,可以通过编程器读取flash,逆向解密算法和密钥,达到解密固件的目的。比如从某设备的读取的flash内存分布如下:
0x000000-0x020000 boot section
0x020000-0x070000 encrypt section
0x070000-0x200000 encrypt section
0x200000-0x400000 config section
显然我们需要的加密过程在boot section中,我们需要从中找到加密算法和密钥,一般加密都采用AES等公开分组算法,关键是找到分组模式,IV(非ECB)和密钥。将boot加载到IDA pro中,并没有自动识别:
可以通过对比ARM代码最开始部分的中断向量表结构手动识别,常见的入口代码如下所示。
.globl _start
_start:
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
...
_irq:
.word irq
之后可以就可以逆向得到加密算法为AES,密钥通过设备序列号的sha256哈希获得。
通过IDA pro识别此类结构将在后文介绍RTOS时探讨,利用这种固件加密方式的设备安全级别教高,一般设备只在升级时进行解密验证。
1.1.3 调试直接读取
这个方法最容易理解,即在设备启动后利用UART、JTAG、Console或网络等手段把固件(打包)回传,这样就绕过了解密环节。值得注意的是需要设备提供这些接口,具体方法因设备不同而异,这些接口的使用将会在硬件篇里作介绍。
1.1.4 对比边界版本
此种方法适用于厂商一开始没采用加密方案,即旧版固件未加密,在某次升级中添加了解密程序,随后升级使用加密固件。这样我们就可以从一系列固件中找到加密和未加密之间的边界版本,解包最后一个未加密版本逆向升级程序即可还原加密过程。
通过下载上图所示某路由器固件,解包后通过搜索包含诸如“firmware”、“upgrade”、“update”、“download”等关键字的组合定位升级程序位置。当然存在调试手段也可以在升级时ps查看进程更新定位升级程序和参数:
/usr/sbin/encimg -d -i <fw_path> -s <image_sign>
通过IDA pro逆向encimg程序很快得到加解密过程代码,采用了AES CBC模式:
AES_set_decrypt_key (
// user input key
const unsigned char *userKey,
// size of key
const int bits,
// encryption key struct which will be used by
// encryption function
AES_KEY *key
)
AES_cbc_encrypt (
// input buffer
const unsigned char *in,
// output buffer
unsigned char *out,
// buffer length
size_t length,
// key struct return by previous function
const AES_KEY *key,
// initializatin vector
unsigned char *ivec,
// is encryption or decryption
const int enc
)
1.1.5 逆向升级程序
此种方法适用于已经通过接口或边界版本得到升级程序,可以利用分组算法的盒检测工具来判别加密算法和定位位置,当然binwalk也可以解析某些简单情况,比如某工控HMI固件:
iot@attifyos ~/Documents> binwalk hmis.tar.gz
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
34 0x22 OpenSSL encyption, salted, salt:0x5879382A7
直接加载升级程序,定位openssl调用很容易就得到解密命令:
1.1.6 漏洞获取密钥
如果找不到边界版本,又找不到调试接口或不熟悉硬件调试,可以考虑采用历史版本漏洞先获取设备控制权,在拿到升级程序逆向加密算法。这种方法比较取巧,需要设备存在RCE漏洞的历史固件,通过降级操作植入漏洞获取权限,下载所需升级程序,然后逆向得到加密算法。
1.2 固件解包
初入IOT安全研究的小伙伴会觉得固件解包很简单,直接binwalk -Me
就可以了,但是理想很丰满,现实很骨感,固件测试多了就会发现binwalk很多情况下都解不开。
IOT固件一般分为两类,一类存在文件系统,大多基于linux/BSD,另一类固件是一个整体,即我们所说的RTOS(Real-time operating system)。
1.2.1 存在文件系统
binwalk大家应该都很熟悉,使用binwalk能直接得到rootfs文件系统的情况这里不再赘述,笔者认为binwalk的强大之处在于可以解析识别多重格式的header,为解包提供参考。以下介绍几种需要绕点弯的情况,当然固件千差万别,全看设计者设计,不能一一列举。
1.2.1.1 UBI(Unsorted Block Image)
UBI格式的固件算比较常见的,binwalk并不能直接解包,但是网上有现成的工具ubi_reader,这里有个需要注意的地方:
UBI_reader解包,UBI文件必须是1024bytes的整数倍,需要增删内容对其
比如通过分析某路由器,发现其rootfs是UBI格式:
# binwalk ROM/wifi_firmware_c91ea_1.0.50.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
684 0x2AC UBI erase count header, version: 1, EC: 0x0, VID header offset: 0x800, data offset: 0x1000
首先安装ubi_reader:
$ sudo apt-get install liblzo2-dev
$ sudo pip install python-lzo
$ git clone https://github.com/jrspruitt/ubi_reader
$ cd ubi_reader
$ sudo python setup.py install
或者直接
$ sudo pip install ubi_reader
然后将根据地址将UBI结构提取出来,利用ubireader_extract_files [options] path/to/file
即可解包。
1.2.1.2 PFS
有些固件binwalk可以识别出header,但是无法解开,比如下面这个固件
iot@attifyos ~/Documents> binwalk -Me v2912_389.all
Scan Time: 2020-11-04 18:39:13
Target File: /home/iot/Documents/v2912_389.all
MD5 Checksum: 180c60197aae7e272191695e906c941e
Signatures: 396
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
1546799 0x179A2F gzip compressed data, last modified: 2042-04-26 20:13:56 (bogus date)
1717744 0x1A35F0 LZ4 compressed data
4171513 0x3FA6F9 SHA256 hash constants, little endian
4179098 0x3FC59A Copyright string: "Copyright (c) 1998-2000 by XXXXX Corp."
4214532 0x404F04 Base64 standard index table
4224780 0x40770C HTML document header
4232369 0x4094B1 SHA256 hash constants, little endian
4307839 0x41BB7F SHA256 hash constants, little endian
4314017 0x41D3A1 XML document, version: "1.0"
4702230 0x47C016 Base64 standard index table
4707197 0x47D37D Certificate in DER format (x509 v3), header length: 4, sequence length: 873
4727609 0x482339 Base64 standard index table
4791281 0x491BF1 PFS filesystem, version 1.0, 12886 files
4807401 0x495AE9 Base64 standard index table
...
iot@attifyos ~/Documents> ls _v2912_389.all.extracted/pfs-root/000/
WEBLOGIN.HTM _WEBLOGIN.HTM.extracted/
iot@attifyos ~/Documents> ls _v2912_389.all.extracted/pfs-root/000/_WEBLOGIN.HTM.extracted/
3CB 3CB.zlib E235 E235.zlib
运行binwalk后查看结果,发现没有发现任何可识别的东西,此时可以手动分析或者去搜索一些相关工具。
在网上找到相关工具,直接根据提示使用命令就可解开固件。
iot@attifyos ~/D/draytools> python draytools.py -F v2910_61252.all
v2910_61252.all.out written, 12816484 [0x00C39064] bytes
FS extracted to [/home/iot/Documents/draytools/fs_out], 429 files extracted
iot@attifyos ~/D/draytools> ls fs_out/
v2000/ v2910.lst
iot@attifyos ~/D/draytools> ls fs_out/v2000/
act_sta.htm CSS/ header.htm INDEX2.HTM ivr_711u/ jg/ l_m.htm menu.htm STATUS.HTM SYSINFO_C.TXT UPNP/ webauth.htm
CGI-BIN/ DOC/ IMAGES/ ivr_711a/ ivr_729/ JS/ LOGIN.HTM rpage.htm STYLE.CSS SYSINFO.TXT VLAN/
这里简单看一下固件解包关键代码,关键在于找到类似’\xA5\xA5\xA5\x5A\xA5\x5A’的header,之后根据具体格式解包解压即可,所以固件解包说到底还是数据格式分析。
def decompress_firmware(data):
flen = len(data)
sigstart = data.find('\xA5\xA5\xA5\x5A\xA5\x5A')
if sigstart <= 0:
sigstart = data.find('\x5A\x5A\xA5\x5A\xA5\x5A')
if sigstart > 0:
if draytools.verbose:
print 'Signature found at [0x%08X]' % sigstart
lzosizestart = sigstart + 6
lzostart = lzosizestart + 4
lzosize = unpack('>L', data[lzosizestart:lzostart])[0]
return data[0x100:sigstart+2] \
+ pydelzo.decompress('\xF0' + pack(">L",0x1000000) \
+ data[lzostart:lzostart+lzosize])
...
1.2.1.3 Openwrt Lua
lua结构解析放在解包这里可能不太恰当,但鉴于Openwrt的使用基数很大,在这里简单提一下。Lua是一门方便嵌入并可扩展的轻量级脚本语言,Openwrt开发中会使用该脚本语言。值得注意的是,有些设备的lua并不是纯文本,存在混淆,需要使用luadec反编译。
openwrt中的lua脚本和传统的luajit编译后的有点不一样,需要打几个补丁才能正常使用luadec进行反编译,命令如下:
$ cd ..
$ mkdir luadec
$ cd luadec/
$ git clone https://github.com/viruscamp/luadec
$ cd luadec/
$ git submodule update --init lua-5.1
$ cd lua-5.1
$ make linux
$ make clean
$ mkdir patch
$ cd patch/
$ get https://dev.openwrt.org/export/HEAD/trunk/package/utils/lua/patches/010-lua-5.1.3-lnum-full-260308.patch
$ wget https://dev.openwrt.org/export/HEAD/trunk/package/utils/lua/patches/030-archindependent-bytecode.patch
$ wget https://dev.openwrt.org/export/HEAD/trunk/package/utils/lua/patches/011-lnum-use-double.patch
$ wget https://dev.openwrt.org/export/HEAD/trunk/package/utils/lua/patches/015-lnum-ppc-compat.patch
$ wget https://dev.openwrt.org/export/HEAD/trunk/package/utils/lua/patches/020-shared_liblua.patch
$ wget https://dev.openwrt.org/export/HEAD/trunk/package/utils/lua/patches/040-use-symbolic-functions.patch
$ wget https://dev.openwrt.org/export/HEAD/trunk/package/utils/lua/patches/050-honor-cflags.patch
$ wget https://dev.openwrt.org/export/HEAD/trunk/package/utils/lua/patches/100-no_readline.patch
$ wget https://dev.openwrt.org/export/HEAD/trunk/package/utils/lua/patches/200-lua-path.patch
$ wget https://dev.openwrt.org/export/HEAD/trunk/package/utils/lua/patches/300-opcode_performance.patch
$ mv patch/ patches
$ for i in ../patches/*.patch; do patch -p1 <$i ; done
$ for i in ./patches/*.patch; do patch -p1 <$i ; done
$ make linux
修改 lua-5.1/src/MakeFile:
# USE_READLINE=1
+PKG_VERSION = 5.1.5
-CFLAGS= -O2 -Wall $(MYCFLAGS)
+CFLAGS= -fPIC -O2 -Wall $(MYCFLAGS)
- $(CC) -o $@ -L. -llua $(MYLDFLAGS) $(LUA_O) $(LIBS)
+ $(CC) -o $@ $(LUA_O) $(MYLDFLAGS) -L. -llua $(LIBS)
- $(CC) -o $@ -L. -llua $(MYLDFLAGS) $(LUAC_O) $(LIBS)
+ $(CC) -o $@ $(LUAC_O) $(MYLDFLAGS) -L. -llua $(LIBS)
接着执行:
$ make linux
$ ldconfig
$ cd ../luadec
$ make LUAVER=5.1
$ sudo cp luadec /usr/local/bin/
利用luadec显示代码结构:
$ luadec -pn squashfs-root/usr/lib/lua/luci/sgi/uhttpd.lua
0
0_0
0_0_0
0_0_1
0_0_2
利用luadec反编译指定的函数 (函数 0 包含 子函数):
$ luadec -f 0 squashfs-root/usr/lib/lua/luci/sgi/uhttpd.lua
需要注意的是,luadec编译与架构相关,用官方luadec无法解析arm环境下的lua文件,但网上也有相应的工具,这里不再赘述。
1.2.2 RTOS
很多IOT设备都采用RTOS(实时操作系统)架构,固件本身就是一个可执行文件,不存在文件系统,启动后直接加在运行。对RTOS的分析最重要就是两点:
(一) 固件程序入口
(二) 固件程序符号
1.2.2.1 vxworks
首先从应用较广且有套路可循的vxworks说起,VxWorks是Wind River System公司推出的一个实时操作系统,广泛应用在通信、军事、航空、航天嵌入式设备领域。因为有标准,所以好识别,以下面这个固件为例:
iot@attifyos ~/Documents> binwalk image_vx5.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
335280 0x51DB0 PEM certificate
...
3721556 0x38C954 GIF image data, version "89a", 10 x 210
8518936 0x81FD18 VxWorks operating system version "5.5.1" , compiled: "Mar 5 2015, 15:56:18"
9736988 0x94931C SHA256 hash constants, little endian
...
13374599 0xCC1487 Copyright string: "Copyright 1999-2001 Wind River Systems."
13387388 0xCC567C VxWorks symbol table, big endian, first entry: [type: function, code address: 0xF4A09A00, symbol address: 0xF813C800]
13391405 0xCC562D VxWorks symbol table, little endian, first entry: [type: function, code address: 0xB8BD, symbol address: 0xD000C800]
binwalk已经识别出固件为Vxworks 5.5.1,并且给出了符号表位置。首先需要识别固件的入口点,如果固件被封装成ELF格式,直接利用readelf就可以得到基地址,这里显然不适用。
iot@attifyos ~/Documents> readelf -a image_vx5_arm_little_eniadn.bin
readelf: Error: Not an ELF file - it has the wrong magic bytes at the start
iot@attifyos ~/Documents> binwalk -A image_vx5.bin |more
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
244 0xF4 ARM instructions, function prologue
408 0x198 ARM instructions, function prologue
440 0x1B8 ARM instructions, function prologue
472 0x1D8 ARM instructions, function prologue
608 0x260 ARM instructions, function prologue
通过binwalk -A
得到固件架构是ARM,直接用IDA pro载入:
分析固件开头跳转判断加载地址为0x1000。对于Vxworks一般判断基址办法有:
分析固件头部的初始化代码,找到vxworks启动的第一个函数usrInit跳
根据BSS区初始化特征找到BSS边界,根据偏移计算固件加载地址
然后根据binwalk指示的位置,修复符号表名。
函数表存放了函数名和函数地址,通过两者定位也可以反过来验证基地址的正确性,比如上图所示的0x00c813f8是函数名:
0x009aa0f4是函数地址:
由于基地址和架构有关,在这里就不详细展开,对于vxworks的分析我们可以借助一款能自动化修复入口和符号的插件–vxhunter。
以Ghidra为例,载入固件后直接选择vxhunter_firmware_init.py插件和vxworks版本,就可以自动修复入口和符号:
1.2.2.2 U-boot
boot类的固件也是我们常会遇见的一类无文件系统固件,比如很多IOT设备会采用U-boot作引导,因为U-boot开源,我们可以参照源代码分析,对于有些架构的U-boot也可以采用固定套路,比如mips可以根据$gp寄存器等。
1.2.2.3 Chip firmware
有些IOT固件没有资料,逆向困难,比如下面某款ARM芯片的固件,将其载入IDA pro发现没有识别出任何函数:
这样我们就需要对固件有一个整体的分析了,我们看到固件0x100的位置十分有趣:
4字节排列后都以0x2开头,这里既不是代码,也不是数据,那就很可能是地址了,这里应该是一张表,所以基地址很可能是0x200000。我们rebase之后再查看字符串:
看到许多类似函数名的字符串,找到具体位置后,在固件中二进制搜索0x16852A,即wlc_probresp_attach地址(小端)。
可以看到真的搜索到了,而且也是一个表的结构:
根据基址找到在IDA pro中的位置:
可以看到完成了部分的交叉引用,后续分析比较复杂,这里就不再展开,实际上0x100位置是函数地址表,在该固件中这样表有很多。所以类似地址表,字符串都是我们分析固件基址和函数的重要线索。
1.3 固件打包
拆东西容易装东西难,这个道理也适用于固件打包。如果设备留有调试接口,一般不用打包操作,毕竟安全研究是以逆向思维为主。
有时缺乏调试手段,我们就要在解开的固件中手动添加。一般将交叉编译好的telnetd,dropbear(sshd),gdb放入固件文件,再替换启动脚本打包。
linux的启动脚本套路众多,尤其在IOT设备中,这里笔者一般采用比较讨巧的方法,比如确定/sbin/xxxd服务会开机运行,可以将其替换:
# mv rootfs/sbin/xxxd sbin/xxxdd
# touch rootfs/sbin/xxxd
# chmod +x rootfs/sbin/xxxd
之后在sbin/xxxd添加
#!/bin/sh
/usr/sbin/telnetd -F -l /bin/sh -p 1234 &
/sbin/xxxdd &
这样开机启动xxxd时就会先运行telnetd。
1.3.1 交叉编译
如果能从正向开发角度来打包当然最方便,也就是交叉编译的事。笔者研究过的一些设备中,主要是路由器固件会部分遵循GPL,就是开源一部分代码软件(一般本来就是基于开源工具),并提供剩下软件的二进制文件和整个固件的打包工具(方法)。
比如之前研究的某款路由设备就提供了开源下载:
下载该zip包,根据自己的需求编译rootfs,最后利用zip包中自带的工具打包:
./packet -k %s -f rootfs -b compatible_r6400.txt
-ok kernel -oall image -or rootfs -i ambitCfg.h
1.3.2 firmware-mod-kit
firmware-mod-kit(fmk)可能是最常用的基于binwalk的解打包工具,但是由于很久没用更新,使用场景有限。
fmk的安装使用都比较简单,如下所示:
# For ubuntu
$ sudo apt-get install git build-essential zlib1g-dev liblzma-dev python-magic bsdmainutils autoconf
# For redhat/centos
$ yum groupinstall "Development Tools"
$ yum install git zlib1g-dev xz-devel python-magic zlib-devel util-linux
# 使用
$ ./extract-firmware.sh firmware.bin //解包
$ cp new-telnetd fmk/rootfs/usr/sbin/telnetd //按需修改
$ ./build-firmware.sh //打包
1.3.3 手动分析
打包的难度在于固件要与原固件一致,并通过各种校验,否则轻则刷机失败,重则设备变砖。笔者之前有一篇关于netgear upnp漏洞的文章,涉及netgear固件打包过程,有兴趣的小伙伴可以看一看。
固件一般会分成许多section,为了方便解析,每个section会有指示头,头中可能会存放标志、大小和crc校验等信息,这些信息都为解打包提供依据。
比如可以先获取固件大小(十六进制),根据固件大小端拆分字节,一般是4字节,然后在固件头上寻找类似字节(固件头上的指示长度会减去头长度),接着从指示大小的字节往后分析就可以澄清格式,和分析网络协议的过程很像。
当然大部分头都是有标准的,可以根据标准格式一一对应。值得注意的是,有些厂商会给固件签名,这个就会增加打包的难度。此时我们可以寻找一些遵循GPL的官方打包工具,或者利用openssl生成公私钥对,覆盖设备中的验证公钥,这里当然要有漏洞,不然就掉进鸡生蛋蛋生鸡的循环。当然还有一个比较好且廉价的办法—固件模拟。
1.4 固件模拟
固件模拟根据不同需要可能有以下3个场景:
(一) 只需模拟某个应用,比如web、upnpd、dnsmasq等,目的是对该应用作调试。此时可以直接用模拟工具运行该程序,只需考虑动态库是否能加载。
(二) 需要模拟固件shell,与整个系统有所交互。这里可以通过chroot改变根路径,并利用模拟工具执行/bin/sh。同时可以挂在/proc,这样在ps查看进程时看上去更加真实。
(三) 需要模拟整个固件启动,并且网卡等也能正常使用。这里要利用可模拟img系统的工具直接加载整个系统,也可以利用“套娃”大法,先模拟该架构的debian.img,再用chroot起设备的roofs。
下面介绍几个常用的模拟工具。
1.4.1 Qemu
Qemu是最老牌的多架构模拟工具,上述3个使用场景qemu都可以满足。qemu可以下载安装也可以直接利用源安装,这里要注意的是如果模拟应用不仅需要qemu,还需安装qemu-user。
# For ubuntu
$ sudo apt-get install qemu
$ sudo apt-get install qemu-user qemu-uesr-static
- qemu模拟应用
以模拟ARM固件为例,解开固件得到rootfs,下面是利用qemu模拟执行busybox:
iot@attifyos ~/Document> cp (which qemu-arm-static) ./rootfs/
iot@attifyos ~/Document> sudo chroot ./rootfs /qemu-arm-static /bin/busybox
BusyBox v0.47 (2018.08.30-14:14+0000) multi-call binary -- GPL2
Usage: busybox [function] [arguments]...
or: [function] [arguments]...
BusyBox is a multi-call binary that combines many common Unix
utilities into a single executable. Most people will create a
link to busybox for each function they wish to use, and BusyBox
will act like whatever it was invoked as.
Currently defined functions:
busybox, cat, chgrp, chmod, chown, cp, date, dd, df, echo, free,
grep, gunzip, gzip, halt, hex, hostname, id, init, kill, killall,
ln, ls, mkdir, mknod, more, mount, mv, ping, ps, pwd, reboot,
rm, rmdir, sh, sleep, sync, syslogd, tail, tar, touch, tty, umount,
uname, zcat
- qemu模拟shell
利用qemu模拟sh与上述情况相似,可以先mount proc,使得ps查看进程时显得更加真实:
iot@attifyos ~/Document> cd ./rootfs
iot@attifyos ~/Document> rm -rf proc
iot@attifyos ~/Document> sudo mount -t proc /proc ./proc
iot@attifyos ~/Document> cd ..
iot@attifyos ~/Document> sudo chroot ./rootfs /qemu-arm-static /bin/sh
BusyBox v0.47 (2018.08.30-14:14+0000) Built-in shell
Enter 'help' for a list of built-in commands.
/ # ls
bin gm sbin usr
dev lib store var
etc qemu-arm-static sys
/ # ps
PID PPID Uid Mem CPU St Command
1 0 0 29936 0.0 S init splash
2 0 0 0 0.0 S [kthreadd]
7 2 0 0 0.6 S [ksoftirqd/0]
332 1 0 26776 0.0 S systemd-journald
358 1 0 6568 0.0 S systemd-udevd
378 1 0 28144 0.0 S vmware-vmblock-fuse /run/vmblock-fuse -o rw,su
495 1 100 18500 0.0 S systemd-timesyncd
607 1 0 8244 0.0 S haveged --Foreground --verbose=1 -w 1024
608 1 0 42756 0.0 S VGAuthService
615 1 0 38824 0.0 S vmtoolsd
619 1 0 3740 0.0 S cron -f
621 1 103 6216 0.0 S avahi-daemon: running [attifyos.local]
...
- qemu模拟系统
qemu的功能很强大,可以像在vmware、virtualbox中一样安装系统:
$ qemu-img create -f qcow2 arm.qcow2 10G
$ qemu-system-arm -m 1024M -sd arm.qcow2 -M vexpress-a9 -cpu cortex-a9 -kernel ../Downloads/vmlinuz-3.2.0-4-vexpress -initrd ../Downloads/initrd.gz -append "root=/dev/ram" -no-reboot
利用qemu模拟完整固件最常规的办法就是将rootfs制作成img或qcow2文件,再用相应架构的qemu模拟执行,这里我们介绍一下前面提到的“套娃”大法。
首先下载Debian官方ARM构架的系统镜像。
挂在qcow2镜像,将rootfs拷贝至qcow2镜像:
$ sudo apt-get install libguestfs-tools
# 利用guestmount挂在qcow2镜像
$ guestmount -a debian_wheezy_armel_standard.qcow2 -m /dev/sda1 /mnt
# 拷贝rootfs
$ cp -rf ./rootfs /mnt/root/rootfs
$ guestunmount /mnt
然后启动qcow2文件:
qemu-system-arm -M versatilepb -kernel vmlinuz-3.2.0-4-versatile -initrd initrd.img-3.2.0-4-versatile -hda debian_wheezy_armel_standard.qcow2 -append "root=/dev/sda1"
利用chroot“套娃”系统:
$ chroot -u ./rootfs /bin/sh
BusyBox v0.47 (2018.08.30-14:14+0000) Built-in shell
Enter 'help' for a list of built-in commands.
/ #
使用debian现成的qcow2镜像可以省去网卡和其他一些配置过程,提高模拟效率。
值得注意的是,qemu在对程序黑盒测试时也经常使用,比如AFL的qemu_mode,在后面的篇章里我们会探讨,当然AFL同样可以使用unicorn_mode,使用的就是下面介绍的模拟器unicorn。
1.4.2 Unicorn
Unicorn一个基于Qemu的cpu指令模拟框架,也就是可以模拟任何的cpu指令。我们一般可以利用Unicorn指令级模拟特性:
对(IOT)程序作模糊测试
用于gdb插件,或代码模拟执行的插桩,修改代码逻辑
模拟执行一些复杂混淆代码,提高人工逆向效率
关于Unicorn模拟执行修改代码逻辑的教程比较多,这里不再赘述,下面介绍一款基于Unicorn的IDA pro插件,该插件可以在IDA pro中模拟执行,并给出执行结果,UnicornFuzz相关内容会在后面的篇章中介绍。
#include <stdlib.h>
int calc(int a,int b){
int sum;
sum = a+b;
return sum;
}
int main(){
calc(2,3);
}
上面非常简单的计算器C代码是该插件的官方示例,其mipsel IDA pro反汇编代码如下:
.text:00400640 .globl calc
.text:00400640 calc: # CODE XREF: main+18↓p
.text:00400640
.text:00400640 var_10 = -0x10
.text:00400640 var_4 = -4
.text:00400640 arg_0 = 0
.text:00400640 arg_4 = 4
.text:00400640
.text:00400640 addiu $sp, -0x18
.text:00400644 sw $fp, 0x18+var_4($sp)
.text:00400648 move $fp, $sp
.text:0040064C sw $a0, 0x18+arg_0($fp)
.text:00400650 sw $a1, 0x18+arg_4($fp)
.text:00400654 lw $v1, 0x18+arg_0($fp)
.text:00400658 lw $v0, 0x18+arg_4($fp)
.text:0040065C addu $v0, $v1, $v0
.text:00400660 sw $v0, 0x18+var_10($fp)
.text:00400664 lw $v0, 0x18+var_10($fp)
.text:00400668 move $sp, $fp
.text:0040066C lw $fp, 0x18+var_4($sp)
.text:00400670 addiu $sp, 0x18
.text:00400674 jr $ra
.text:00400678 nop
.text:00400678 # End of function calc
创建一个 emu object
Python>a = EmuMips()
配置模拟地址和参数
Python>a.configEmu(0x00400640,0x00400678,[2,3])
[*] Init registers success...
[*] Init code and data segment success!
[*] Init Stack success...
[*] set args...
开始指令模拟
Python>a.beginEmu()
[*] emulating...
[*] Done! Emulate result return: 0x5
通过Unicorn模拟执行得到sum(2+3)的结果为0x5。
1.4.3 Qiling
Qiling是一款很年轻的基于Unicorn的模拟器,专为IOT研究而生。Qiling可以作为IDA pro插件,也可以利用Qiling Unicornalf进行fuzz,还可以模拟IOT设备固件。Qiling用python3开发,可以直接用pip3 install qiling
安装,以下是官方模拟ARMj架构路由器固件的部分代码:
import os, socket, sys, threading
sys.path.append("..")
from qiling import *
def patcher(ql):
...
def nvram_listener():
...
def my_sandbox(path, rootfs):
ql = Qiling(path, rootfs, output = "debug")
ql.add_fs_mapper("/dev/urandom","/dev/urandom")
ql.hook_address(patcher ,ql.loader.elf_entry)
ql.run()
if __name__ == "__main__":
nvram_listener_therad = threading.Thread(target=nvram_listener, daemon=True)
nvram_listener_therad.start()
my_sandbox(["rootfs/bin/httpd"], "rootfs")
可以看到Qiling框架进一步简化了模拟所需代码,同时提供了指令级插桩功能,还是很强大的。
1.4.4 Firmadyne
Firmadyne是一个自动化和可扩展的系统,用于对基于Linux的嵌入式固件执行仿真和动态分析。Firmadyne也是是基于Qemu,使用起来大同小异,网上教程也很多,这里主要介绍一款使用Firmadyne的固件灰盒fuzz工具的–Firm-AFL。
Firm-AFL的安装分为用户模式和系统模式,
用户模式:
$ cd user_mode/
$ ./configure --target-list=mipsel-linux-user,mips-linux-user,arm-linux-user --static --disable-werror
$ make
系统模式:
$ cd qemu_mode/DECAF_qemu_2.10/
$ ./configure --target-list=mipsel-softmmu,mips-softmmu,arm-softmmu --disable-werror
$ make
以Dlink DIR-815固件为例,首先安装好Firmadyne,做一些初始化工作:
$ cd firmadyne
$ ./sources/extractor/extractor.py -b dlink -sql 127.0.0.1 -np -nk "../firmware/DIR-815_FIRMWARE_1.01.ZIP" images
$ ./scripts/getArch.sh ./images/9050.tar.gz
$ ./scripts/makeImage.sh 9050
$ ./scripts/inferNetwork.sh 9050
$ cd ..
$ python FirmAFL_setup.py 9050 mipsel
根据路由器架构修改image_9050目录中的run.sh:
ARCH=mipsel
QEMU="./qemu-system-${ARCH}"
KERNEL="./vmlinux.${ARCH}_3.2.1"
IMAGE="./image.raw"
MEM_FILE="./mem_file"
${QEMU} -m 256 -mem-prealloc -mem-path ${MEM_FILE} -M ${QEMU_MACHINE} -kernel ${KERNEL} \
然后就可以启动fuzz脚本开始测试:
$ cd image_9050
$ sudo python start.py 9050
1.5 总结
固件研究是IOT漏洞研究的基础,本篇尽可能全的介绍了固件研究方面的内容,一方面笔者经验有限,还有些点未能涉及,另一方面篇幅有限,许多内容没能进一步展开,以后有机会再和大家一起探讨。下一篇将介绍IOT web漏洞研究的相关内容。
参考资料
https://cloud.tencent.com/developer/article/1005700
https://5alt.me/2017/08/某智能设备固件解密/
http://blog.nsfocus.net/hmi-firmware-decryption-0522/
https://www.ershicimi.com/p/8e818120ac6352368837ef614dd496e4
http://blog.nsfocus.net/hmi-firmware-decryption-0522/
https://gorgias.me/2019/12/27/固件提取系列-UBI文件系统提取以及重打包/
https://paper.seebug.org/771/
https://paper.seebug.org/1090/
https://blog.csdn.net/yalecaltech/article/details/104113779
https://dassecurity-labs.github.io/HatLab_IOT_Wiki/firmware_security/firmware_security_tools/IDA_unicorn_base_tool/
来源:freebuf.com 2020-11-08 15:50:59 by: klovey
请登录后发表评论
注册