IoT 固件分析和模拟入门
最近打算搞 IoT 静态分析, 先来入门一下 IoT.
固件提取
IoT 固件从简单到复杂有:
- 微控制器 / 单片机, 如 ESP32
- 实时操作系统(RTOS), 如 FreeRTOS, Zephyr
- 嵌入式 Linux, 如 Raspberry Pi, OpenWrt
这里主要研究嵌入式 Linux 的. 之后若无特殊说明, 「固件」均表示「嵌入式 Linux 系统的固件」. 而一般这种在路由器上用的很多, 国内 IoT CTF 题一般也都是路由器.
固件获取的途径有很多种1, 非常黑客的方法是直接从硬件里通过各种办法获取. 不过暂时不需要 (也没有这个条件). 另外一个简单的方法就是从官网上下载固件包. 一般来说会是一个文件. 需要使用一些方法解压甚至解密出来, 才能得到基于 Linux 的固件文件系统. 一般是使用 binwalk 来查看, 如果没有加密什么的还能够直接提取.
嵌入式的文件系统也分很多种, 常见的有: SquashFS, ext2, JFFS2, UBIFS, YAFFS 等. 提取/查看文件系统可能需要对应的工具.
以 RT-N300_3.0.0.4_378_9317-g2f672ff.trx2 为例, 先用 file
和 binwalk
查看信息:
RT-N300_3.0.0.4_378_9317-g2f672ff.trx: u-boot legacy uImage, \003, Linux/MIPS, OS Kernel Im
age (lzma), 6207056 bytes, Tue Jun 7 08:57:31 2016, Load Address: 0X80000000, Entry Point:
0X8000C150, Header CRC: 0XC5B2B0CB, Data CRC: 0XD2BC6DF
DECIMAL | HEXADECIMAL | DESCRIPTION |
---|---|---|
0 | 0x0 | uImage firmware image, header size: 64 bytes, data size: 6207056 bytes, compression: lzma, CPU: MIPS32, OS: Linux, image type: OS Kernel Image, load address: 0x80000000, entry point: 0x8000C150, creation time: 2016-06-07 08:57:31, image name: "" |
可以看到是一个 u-boot 的固件镜像. 接下来用 binwalk -e
提取:
.
├── extractions
│ ├── RT-N300_3.0.0.4_378_9317-g2f672ff.trx.extracted
│ │ └── 0
│ │ └── \u{3}.bin
│ └── RT-N300_3.0.0.4_378_9317-g2f672ff.trx -> /home/wings/Workspace/g5/stati
c/iot/RT-N300_3.0.0.4_378_9317-g2f672ff.trx
└── RT-N300_3.0.0.4_378_9317-g2f672ff.trx
他从偏移 0 的地方提取了一个 bin 文件. 再来 binwalk
分析一下这个文件:
DECIMAL | HEXADECIMAL | DESCRIPTION |
---|---|---|
0 | 0x0 | LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, compressed size: 925766 bytes, uncompressed size: 2892004 bytes |
925824 | 0xE2080 | SquashFS file system, little endian, version: 4.0, compression: xz, inode count: 858, block size: 131072, image size: 5281232 bytes, created: 2016-06-07 08:57:29 |
这回有两个部分了, 第一部分是压缩的东西, 可能是 OS 内核, 第二部分是 SquashFS 文件系统. 用 binwalk -e
提取:
.
├── extractions
│ ├── \u{3}.bin.extracted
│ │ ├── 0
│ │ │ └── decompressed.bin
│ │ └── E2080
│ │ └── squashfs-root
│ │ ├── asus_jffs
│ │ ├── bin
│ │ ├── cifs1
│ │ ├── cifs2
│ │ ├── dev
│ │ ├── etc_ro
│ │ ├── jffs
│ │ ├── lib
│ │ ├── mmc
│ │ ├── proc
│ │ ├── ra_SKU
│ │ ├── rom
│ │ ├── sbin
│ │ ├── sys
│ │ ├── sysroot
│ │ ├── tmp
│ │ ├── usr
│ │ ├── www
│ │ ├── etc -> tmp/etc
│ │ ├── home -> tmp/home
│ │ ├── mnt -> tmp/mnt
│ │ ├── opt -> tmp/opt
│ │ ├── root -> tmp/home/root
│ │ └── var -> tmp/var
│ └── \u{3}.bin -> /home/wings/Workspace/g5/static/iot/extraction
s/RT-N300_3.0.0.4_378_9317-g2f672ff.trx.extracted/0/\u{3}.bin
└── \u{3}.bin
成功把 SquashFS 文件系统提取出来.
固件解密不在讨论范围, 要用到再看这个吧 固件解密 | IOT 固件安全 All in One
模拟运行
EMBA
找到3个最近还在维护 IoT 分析框架叫 EMBA, 包含了模拟功能, 看上去是根据 firmadyne 和 FirmAE 来模拟的. 这里用他的另一个仓库 EMBArk 来一键安装环境.
它漏了 rsync
和 psmisc
依赖, 得手动安装.
EMBArk 提供了 Web UI, 可以很方便操控 EMBA, 包括上传固件, 选择分析内容并进行分析等等. 不过, 要在 host 上访问 vm 里的 server, 得配置一下网络. qemu nat 模式, 需要映射 80, 8001 (用于 ws 监控 log), 同时主机 hosts 加入 127.0.0.1 embark.local
, 因为 embark 用的 apache, 必须要用正确的域名访问.
试了一下好像功能很丰富, 从用户态到内核态都能扫, 就是实在是太慢了没等他跑完, 中途还 OOM 了. 感觉以后打比赛什么的拿他分析一波说不定好用.
不过, 关于如何在 EMBArk 中进行模拟并没有看到文档记录什么的, 所以不太会弄. 但是 EMBA 倒是有4. 安装后在 embark 底下找到了 emba, 运行如下命令即可跳过一堆耗时的分析5, 直接开始尝试模拟:
./emba.sh -f ~/firmware.bin -l ~/firmware-log -p ./scan-profiles/default-scan-emulation.emba -m s115 -m l10 -m f199
运行结束后会在 ~/firmware-log/l10_system_emulation/archive*
里创建一个 qemu 环境供模拟. 这里随便拿了一个 FirmAE 数据集里的固件 Archer_C20_150831 试了一下. 跑起来挺慢的, 貌似是在尝试如何模拟. 最后结果如下:
archive-squashfs-root_mipsel-3079-13884
├── emulation_config.txt
├── nmap_emba_5-192.168.0.1-br0-eth0-6-bridge.txt
├── nmap_emba_5-192.168.0.1-br0-eth0-6-bridge.txt.gnmap
├── nmap_emba_5-192.168.0.1-br0-eth0-6-bridge.txt.nmap
├── nmap_emba_5-192.168.0.1-br0-eth0-6-bridge.txt.xml
├── nmap_emba_5-192.168.0.1-br0-eth0-6-bridge_dedicated.gnmap
├── nmap_emba_5-192.168.0.1-br0-eth0-6-bridge_dedicated.nmap
├── nmap_emba_5-192.168.0.1-br0-eth0-6-bridge_dedicated.xml
├── qemu.serial.log
├── run.sh
├── squashfs-root_mipsel-3079
└── vmlinux.mipsel.4
可以看到, 不仅有内核 vmlinux
, 还有 run.sh
脚本, 非常友好. 这个东西直接在主机上起就可以了, 需要注意的是, run.sh
中使用 tunctl
来创建 tap 设备, 我本机没有 (好像 gentoo 压根没有这包), 其实可以替换成 ip tunctl
. 修改后的 run.sh
如下:
#!/bin/bash -p
echo -e "Creating TAP device tap22_0\n"
command -v tunctl > /dev/null || (echo "Missing tunctl ... check your installation - install uml-utilities package" && exit 1)
# tunctl -t tap22_0
ip tuntap add dev tap22_0 mode tap group kvm
echo -e "Bringing up HOSTNETDEV \033[0;33mtap22_0.6\033[0m / VLAN ID \033[0;33m6\033[0m / TAPDEV \033[0;33mtap22_0\033[0m.\n"
ip link add link tap22_0 name tap22_0.6 type vlan id 6
ip link set tap22_0 up
echo -e "Bringing up HOSTIP \033[0;33m192.168.0.2\033[0m / IP address \033[0;33m192.168.0.1\033[0m / TAPDEV \033[0;33mtap22_0\033[0m.\n"
ip link set tap22_0.6 up
ip addr add 192.168.0.2/24 dev tap22_0.6
ifconfig -a
route -n
echo -e "[*] Starting firmware emulation \033[0;33mqemu-system-mipsel / mipsel / squashfs-root_mipsel-3079 / 192.168.0.1\033[0m ... use Ctrl-a + x to exit\n"
echo -e "[*] For emulation state please monitor the \033[0;33mqemu.serial.log\033[0m file\n"
echo -e "[*] For shell access check localhost port \033[0;33m4321\033[0m via telnet\n"
qemu-system-mipsel -m 2048 -M malta -kernel ./vmlinux.mipsel.4 -drive if=ide,format=raw,file=./squashfs-root_mipsel-3079 -append "root=/dev/sda1 console=ttyS0 nandsim.parts=64,64,64,64,64,64,64,64,64,64 rdinit=/firmadyne/preInit.sh rw debug ignore_loglevel print-fatal-signals=1 EMBA_NET=true EMBA_NVRAM=true EMBA_KERNEL=true EMBA_ETC=true user_debug=0 firmadyne.syscall=1" -nographic -device e1000,netdev=net0 -netdev tap,id=net0,ifname=tap22_0,script=no -device e1000,netdev=net1 -netdev socket,id=net1,listen=:2001 -device e1000,netdev=net2 -netdev socket,id=net2,listen=:2002 -device e1000,netdev=net3 -netdev socket,id=net3,listen=:2003 -serial file:./qemu.serial.log -serial telnet:localhost:4321,server,nowait -serial unix:/tmp/qemu.squashfs-root_mipsel-3079.S1,server,nowait -monitor unix:/tmp/qemu.squashfs-root_mipsel-3079,server,nowait ; pkill -9 -f tail.*-F.*.
echo -e "Deleting route ...\n"
ip route flush dev tap22_0.6
echo -e "Bringing down TAP device ...\n"
ip link set tap22_0 down
echo -e "Removing VLAN ...\n"
ip link delete tap22_0.6
echo -e "Deleting TAP device ...\n"
# tunctl -d tap22_0
ip link del tap22_0
运行之后的输出大概如下 (tun0 是我自己代理用的, 请忽略):
Creating TAP device tap22_0
Missing tunctl ... check your installation - install uml-utilities package
Bringing up HOSTNETDEV tap22_0.6 / VLAN ID 6 / TAPDEV tap22_0.
Bringing up HOSTIP 192.168.0.2 / IP address 192.168.0.1 / TAPDEV tap22_0.
enp0s31f6: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
ether f4:a8:0d:a9:9f:b5 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device interrupt 16 memory 0x82300000-82320000
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 52608 bytes 125045644 (119.2 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 52608 bytes 125045644 (119.2 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
tap22_0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
ether 06:96:87:65:6c:d1 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
tap22_0.6: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 192.168.0.2 netmask 255.255.255.0 broadcast 0.0.0.0
ether 06:96:87:65:6c:d1 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
tun0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST> mtu 9000
inet 172.100.0.1 netmask 255.255.255.252 destination 172.100.0.1
inet6 fd00::1 prefixlen 126 scopeid 0x0<global>
inet6 fe80::c7a1:871a:cbe:5c74 prefixlen 64 scopeid 0x20<link>
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 500 (UNSPEC)
RX packets 61724 bytes 132017521 (125.9 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 61928 bytes 131988589 (125.8 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.207.16.222 netmask 255.255.0.0 broadcast 10.207.255.255
inet6 fe80::b53c:ede8:12a5:9483 prefixlen 64 scopeid 0x20<link>
inet6 2400:dd01:103a:4032:a58d:dc22:be72:1bd1 prefixlen 64 scopeid 0x0<global>
ether d0:39:57:db:3d:6f txqueuelen 1000 (Ethernet)
RX packets 882456 bytes 199895663 (190.6 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 35954 bytes 9144961 (8.7 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.207.255.254 0.0.0.0 UG 3003 0 0 wlan0
10.207.0.0 0.0.0.0 255.255.0.0 U 3003 0 0 wlan0
172.100.0.0 0.0.0.0 255.255.255.252 U 0 0 0 tun0
192.168.0.0 0.0.0.0 255.255.255.0 U 0 0 0 tap22_0.6
[*] Starting firmware emulation qemu-system-mipsel / mipsel / squashfs-root_mipsel-3079 / 192.168.0.1 ... use Ctrl-a + x to exit
[*] For emulation state please monitor the qemu.serial.log file
[*] For shell access check localhost port 4321 via telnet
访问 192.168.0.1 就可以看到成功模拟啦

脚本改了网络把我代理搞坏了. 经过 Catop 的指点, 发现启动后不久会加一条路由 (虽然不知道是哪条命令导致的), 下面第一行就是新加的:
default via 192.168.0.1 dev tap22_0.6 proto dhcp src 192.168.0.100 metric 1014
default via 10.207.255.254 dev wlan0 proto dhcp src 10.207.16.222 metric 3003
10.207.0.0/16 dev wlan0 proto dhcp scope link src 10.207.16.222 metric 3003
172.100.0.0/30 dev tun0 proto kernel scope link src 172.100.0.1
192.168.0.0/24 dev tap22_0.6 proto dhcp scope link src 192.168.0.100 metric 1014
Catop 说这条没有必要, 反而会和我的 tun 代理冲突. 删了就行
ip route delete default via 192.168.0.1 dev tap22_0.6
FirmAE
TODO, 下次有机会试试.
qemu usermode run cgi
(见过的所有路由器的题都是用 cgi 起的, 其他的了解过了以后再记录吧)
基本原理就是有一个 httpd 或者其他轻量处理请求的程序, 他解析请求并且执行 (是的, fork + exec ) 对应的 cgi 二进制程序, 这个程序里再完成一系列的功能. httpd 会在执行 cig 程序时通过环境变量来传送 http 请求中的参数和内容, cgi 程序可以获取 (还看到什么 nvram 的, 不了解). 所以可以直接用用户态 qemu 执行 cgi 程序, 注意传输对应的环境变量即可. 整套流程可能需要工具, 但是针对 cgi 分析的话 (比如做题或者静态分析什么的), 只考虑一个 cgi 也可以.