作者:fenix@知道创宇404实验室
时间:2017年11月27日
据 IT 研究与顾问咨询公司 Gartner
预测,2017 年全球物联网设备数量将达到 84 亿,比 2016 年的 64 亿增长31%,而全球人口数量为 75 亿。2020 年物联网设备数量将达到 204 亿。
而与如此快的发展速度相对应的,物联网的安全问题也日趋凸显,尤其是网络摄像头、路由器等常见设备。我们可以从以下两个案例大致感受一下物联网设备严峻的安全形势。
物联网设备数量的快速增长和其安全性的严重滞后形成了鲜明对比。同时也给恶意攻击者和安全研究人员提供了新的土壤,这场正邪的博弈在新的战场上正激烈上演。
这是一篇详细的入门级别的教程,献给众多想入门智能设备安全的爱好者们。(本文完成于2017年,时隔一年对外发布。)
固件(Firmware)就是写入 EROM
(可擦写只读存储器)或 EEPROM
(电可擦可编程只读存储器)中的程序。特殊的,对于市面上大部分的路由器和摄像头来说,固件就是电路板上的 25 系列 Flash 芯片中的程序,其中存放着嵌入式操作系统,通常是 Linux 系统。
获取固件是分析挖掘固件漏洞的前提,本文将以摄像头为例,介绍如何 Dump Flash 芯片中的固件以及获取固件之后的一些玩法思路。
通常情况下,有以下几种获取固件的途径。
本文涉及后两种方式提取固件的方式。
在开始正式的固件提取之前,先来熟悉几个基础概念。
串口(Serial port)又称“序列端口”,主要用于串行式逐位数据传输。
UART(Universal Asynchronous Receiver/Transmitter) 是一种异步串口通信协议。串口遵循 UART 协议按位(bit)异步发送和接收字节,通常情况下需要连接三对针脚,连线方式如下所示(图片来自网络):
上图中,TX 为接收端,RX 为传输端,GND 为接地端。按照图示方式连接板子的调试串口和 USB 转 TTL 串口线,设置好波特率、数据位、停止位和奇偶校验等重要参数后,双方就可以正常发送 ASCII 码字符,从而进行异步串口通信。
u-boot 是一种普遍用于嵌入式系统中的引导程序,它在操作系统运行之前执行,用来初始化软硬件环境,并最终启动系统内核。
本节我们将从一款无线监控摄像头
入手,讲解如何通过调试串口获取系统的 Shell。
使用 nmap
探测该摄像头的开放端口及服务,结果如下
Host is up (0.0031s latency). Not shown: 996 closed ports PORT STATE SERVICE VERSION 100/tcp open http Mongoose httpd 554/tcp open rtsp 1935/tcp open tcpwrapped 100100/tcp open soap gSOAP 2.8
监听在 100 端口的 Mongoose 是一个嵌入式的 Web 服务器,gSOAP 是一个跨平台的,用于开发 Web Service 服务端和客户端的工具。RTSP(Real Time Streaming Protocol),实时流传输协议,是 TCP/IP 协议体系中的一个应用层协议,该协议定义了一对多应用程序如何有效地通过 IP 网络传送多媒体数据。
之后可以通过 Fidder
、wireshark
等工具对服务进行抓包分析,然而这不是我们今天的重点。下面我们将从硬件的角度去分析。
制造路由器、摄像头等设备的厂商通常会在设备上留下调试串口方便开发或售后过程中的调试,为了和设备进行通信,我们首先需要找到这些 "后门"。用工具将摄像头拆开,根据主板上芯片上的型号可以识别出芯片的用途。如图,我们找到了处理器和存储器芯片的位置,处理器是国科 IPC 芯片 GK7102,存储器芯片是 25 系列 flash 芯片 IC25LP128 。主板上空闲的接口有三个(右图),左下、右下、右下偏上,经过测试,左下那个是 4 针 debug 串口(波特率 115200),串口的第一个针脚为 Tx
,第三个针脚为 Rx
,分别与 USB-转-TTL
的 Rx
,Tx
连接(USB 转 TTL 串口线和主板由同一个 Hub 供电,VCC
相差不大,没有连接 GND
)。
至于如何找到设备上的调试串口,可参考 reverse-engineering-serial-ports,此处不再赘述。
minicom
是一款 Linux 平台上的串口工具,在控制台键入以下命令和串口进行通信。
# Use the following Bash code: minicom -D /dev/ttyUSB0
在这步操作的时候很容易遇到权限的问题,介绍一个很粗暴的方法。
sudo chmod 777 /dev/ttyUSB0
笔记本正确连接主板串口,供电后,在终端可以看到以下系统启动过程中的调试信息。
Flash 芯片的分区信息如下
开机后系统启动了以下服务,可能是摄像头服务的主进程。
系统启动完成后,提供了Shell 的登陆界面。
通过观察启动流程,我们已经获得了很多有用的信息,对 u-boot
如何引导系统的启动也有了一个大致的认识。
最后,我们尝试使用弱密码获取系统的 Shell,遗憾的是,经过多次尝试,均已失败告终。
如果你使用过 Linux 系统,或多或少的经历过忘记系统密码导致无法进入系统的尴尬境地。我们的解决方案也堪称简单粗暴,直接进入 grub 引导修改密码。所以,如果设备触手可及,几乎不存在进不入系统的问题。
在摄像头这种运行着嵌入式 Linux 操作系统的设备上,也有一个类似 grub
的存在,它就是 u-boot
。
重启设备,根据提示键入组合键进入到 u-boot
命令行界面。
u-boot
命令行内置了很多常用命令供我们使用,键入 h
查看帮助。
通过 printenv
打印出 u-boot
传递给内核的参数信息。
从部分参数的内容可以看到 u-boot
引导程序是如何移交控制权给内核的。
console=${consoledev},${baudrate} noinitrd mem=${mem} rw ${rootfstype} init=linuxrc
将内核从 Flash 加载到内存中
跳转到内存中内核的起始地址并执行
我们来重点看下启动参数的 init
字段。
init
字段设置内核执行的初始化进程名,比如上面的 linuxrc
,它是位于文件系统根目录下的一段程序代码,负责后续的系统初始化工作。
是否可以直接修改 init=/bin/sh
从而实现在系统未初始化完成的时候访问根文件系统呢?我们不妨试一下,在 u-boot
命令行中修改参数 sfboot
中 init
字段的值为 /bin/sh
并保存,修改后效果如下。(修改前做好参数的备份)
console=${consoledev},${baudrate} noinitrd mem=${mem} rw ${rootfstype} init=/bin/sh
重启设备,正如我们所猜想的,修改内核执行的初始进程,我们成功获得了一个 Shell
。
由于没有经过 linuxrc
的初始化过程,这样获得的 Shell 功能是很受限的。在该 shell 下编辑 /etc/shadow
文件,擦除或者破解 root
用户的密码,重启到 u-boot
命令行界面中修改回原来的启动参数并保存,再次重启到 Shell 登陆界面,即可获得一个具有完整功能的 Shell。
经过上面的步骤,我们已经可以登录到一个功能完整的 Shell
,使用 tar
和 tftp
命令打包上传根文件系统到 tftp 服务器即可。
在 u-boot
中提供了相关命令操作 Flash 芯片,所以也可以按照如下方式提取固件。(这种 cat 内存的方式只是一种思路,速度是内伤)
本小节我们以另一款基于 gSOAP 协议的摄像头为例(固件存储芯片型号 MX25LP128
),介绍如何用编程器读写 Flash 芯片,从而打开该摄像头的 telnet 服务。
MX25L128
这款 25 系列 Flash 芯片可以直接在线读取,用 夹子
夹住 Flash 芯片
,连接编程器即可读取其中的固件。
点击 智能识别SmartID
,芯片型号识别成功后点击 读取 Read
,最后保存成文件即可。如下图,读取过程非常顺利。
binwalk
是 devttys0 大神开发的一款固件分析工具,强烈推荐使用 Github
上的教程安装,直接 apt-get
安装会缺少很多依赖。
使用 binwalk
查看固件结构
内核编译(make)之后会生成两个文件,一个 Image,一个 zImage,其中 Image 为内核映像文件,而 zImage为内核的一种映像压缩文件。
那么 uImage 又是什么的?它是 uboot 专用的映像文件,它是在 zImage 之前加上一个长度为 64 字节的头部,说明这个内核的版本、加载位置、生成时间、大小等信息;其 0x40
之后与 zImage 没有区别。
固件使用的是 squashfs
文件系统,它是一套供 Linux 核心使用的 GPL 开源只读压缩文件系统。所以设备正常运行的时候是不能对固件进行修改的,在前面那部分,我们从串口进去通过修改内核的初始进程的方式进入系统,是由于系统尚未初始化完成,从而获得了对文件系统的读写权限。
在固件的后一部分,包含一个可以写入的区域。一个 JFFS2
文件系统,它是在闪存上使用非常广泛的读/写文件系统,设备运行过程中修改过的配置信息和其他数据将被写入这个文件系统中。
squashfs
文件系统开始于 0x3100000
, 大小为 6963644
字节, 用 dd
命令提取该文件系统,用 unsquashfs
命令解压。
熟悉文件系统结构和已有的命令
很明显,该固件的 Shell
是基于 busybox
提供的。从 file
指令的结果可以判断该摄像头是 32位 ARM 指令架构。
这个 busybox
是静态链接的,不依赖其他的库文件。可以直接利用 qemu-arm
模拟运行。
当然,我们也可以搭建一个 qemu
虚拟机。
在这个网站下载 qemu 虚拟机镜像文件,然后按照如下方式启动虚拟机。
现在我们已经可以确定目标文件系统是存在 telnetd
命令的。在根目录下的 boot.sh
文件末尾添加以下内容,使设备在开启时自动启动 telnet 服务。
现在,对文件系统的简单修改已经完成了,我们该如何重新打包固件,以便重新刷回到设备呢?
还是从固件结构入手,如下
我们自定义的只是中间的文件系统部分。即 0x3100000 - 0xB00000
这一段。同时,这一段的长度并不等于 squashfs
文件系统的大小 6963644
字节,squashfs
文件系统末至下一段开始之前有一段 0xff
的填充部分。
从 uImage 头信息可以看到,image size
为 2217456
, 而 squashfs
文件系统的起始位置为 3670016
,没有对 squashfs
文件系统做 CRC
检验。
根据以上结论判断,我们只需要在不改变原始固件结构的前提下,将修改后的文件系统重新打包成固件。
利用 cat
将各段连接起来
Cheers,重新打包完成。利用编程器将修改后的固件离线刷入固件存储芯片即可。(在线刷各种坑,建议离线写入)
可以看到,我们成功开启了该摄像头的 telnet
服务。
对智能设备的软硬件有足够的了解是深入挖掘设备漏洞的基础。本文是在对摄像头等物联网设备研究过程中的一些经验总结,希望对大家有所帮助。