作者:Lightal @ PwnMonkey Security Lab
原文链接:https://bbs.pediy.com/thread-261679.htm
海康威视作为国际大厂,旗下如摄像头等产品早就被无数人分析过了,通过google和github等可以找到很多分析记录和分析工具。萤石是海康威视的一个子品牌,相比于海康威视,萤石的绝大部分产品侧重于家用领域,本文将要分析的智能门锁和网关就是萤石的产品。
之前果加门锁的分析文章中,我们分析了门锁和相关的app,这篇文章我们打算对门锁配套的网关进行一些分析(真正原因是门锁让我们搞坏了,虽然海康又补发了一个,但型号没对上)。刚刚看了看萤石的网上商城,最新的联网门锁已经没有外置网关了,我们在2019年分析的智能门锁还是有外置网关的款式,图片如下:
门锁和网关在拿到手之后,就已经是配对状态,我们不需要进行额外的配对操作。海康萤石智能门锁有一个配套使用的app,该app对服务端有证书校验,所以直接抓包是行不通的,由于我们这里是网关分析,所以就不讨论app的工作了,在本专题的后续文章中,还会遇到类似的问题,到那时我们在解决app的事。
按照正常思路,肯定是先抓包看一下网关与服务器的通信内容。这里我们通过交换机端口监控的方法抓取海康萤石网关的通信内容(用电脑开启热点,然后网关连到电脑的热点上也可以实现抓包的目的),如下图:
通过上图中的通信内容,我们大体上可以分析出一些内容:
A. 网关上电之后,与litedev.ys7.com进行通信。
B. 与litedev.ys7.com通信中,获取了另一个ip,即101.71.30.172。此后,终止了与litedev.ys7.com通信,并一直保持与101.71.30.172的tcp连接。
C. 通信内容看起来是加密的,通信内容中没有很多直接可见的明文。
既然抓包获取不了太多有用的信息,那么我们就要进一步研究网关固件是如何处理这些通信数据的。
这次并没有像之前的果加智能门锁那样顺利:萤石配套的app不能获取固件的下载地址,在海康和萤石的官网仔细翻找,同样没有查到固件的下载地址。那么接下来我们需要先对电路进行一些分析,以找到固件提取和调试的方法。
我们首先来看一看网关电路板上用哪些芯片,尤其注意主控MCU的型号,以及有没有外置的Flash芯片。电路板如下图所示:
上图中,最明显的就是中间的MCU,仔细观察可以确认品牌和型号为MEDIATEK MT7688AN,联发科的芯片,先下载一份芯片手册看一看,下载地址是:http://labs.mediatek.com/zh-cn/chipset/MT7688。
通过阅读芯片手册,可以找到关于Flash的部分内容:
上图可以看到该芯片并没有内置Flash,而是使用SPI通信的外置Flash,那么我们继续在电路板上寻找Flash芯片。
在图2-2中,MCU的上方是Winbond W9751G6KB芯片,该芯片是DDR2 SDRAM存储器,简单说就是断电丢数据的内存,固件程序不可能在里面。在MCU的下方有两个芯片,分别是PCM5100A和GD25Q127CSIG芯片, PCM5100A是个音频立体声DAC芯片,而GD25Q127CSIG芯片则是128M-bit的Flash芯片,我们要提取的固件文件应该就是在此Flash中。关于Flash中固件的提取方法,我们将在第4章中介绍。
IoT设备在开发和测试过程中,都会使用JTAG、UART等接口用于辅助调试工作。在设备的发行版中,MCU的这些引脚是否与电路板上的接口接通就不一定了,这需要我们测试一下。
在MT7688AN的芯片手册中,我们可以看到该芯片UART0接口和UART1接口。其中UART0接口在30和31引脚,如下图:
上图中,我们仅展示了UART0的位置,UART1可以自行查询芯片手册。如果海康萤石的网关没有屏蔽对UART的输入和输出,那么通过这些UART也许可以实现一些操作。通过万用表测量,可以确定芯片的UART是否被引出到了软排线接口上,如下图所示的。
为了接通软排线接口,我们需要购买一段20pin的软排线和转接板,淘宝可以买到,楼下手机店可能也有,软排线的尺寸间距我们在图3-4已经测量出来了。此外,我们还需要一个USB转TTL模块才能将UART口连接到电脑上,该模块用于调整USB电路和TTL电路的电平逻辑,软排线+转接板+USB转TTL模块的连接如下图所示。
我们在实际操作中,发现软排线的触点位置经常虚接,暂无法确定海康萤石的软排线接口是否需要特殊的软排线才能连接,但这样也勉强能用,后续我们会有更好的替代方法。
按照图3-5方式连接好之后,在电脑的“设备管理器”选项卡中就会出现一个COM口,可以使用xShell、SecureCRT或者sscom等串口调试工具来读取这个COM口上的通信数据,当使用xShell和SecureCRT的时候需要将连接方式选择为串行接口。
到此为止,如果操作无误,网关上电后,应该可以在串口调试工具中看到了类似下图的输出内容。
上图中的输出内容显然是网关上电之后的输出日志,待系统启动完成之后,我们可以试着输入一些内容,这时就会出现登陆嵌入式Linux系统的提示字符,但是登陆用户和登陆密码我们暂不知道。现在是时候去分析固件了。
我们在3.1节提到固件应该存储在电路板的Flash芯片中,要读取Flash芯片,只需要将芯片连到编程器上即可。如果情况允许,烧录夹是最优选择,这样我们就不需要将芯片焊下来了。不巧的是,烧录夹有点粗,海康萤石的门锁网关设计的很紧凑,烧录夹夹不上去。所以我们只好用热风枪和镊子就可以把Flash吹下来了,如下图所示:
如果没有热风枪,用电烙铁也是可以的,注意手别抖就好。然后将摘下来的芯片放在编程器上。支持读写GD25Q127CSIG芯片的编程器有很多种,我们这里选择一种较为实惠的MinPro100B,如下图:
将编程器连接到电脑上,使用编程器配套的软件,选择相应的Flash芯片型号后即可提取芯片内容,提取完成后可以将读出的内容保存成文件,并命名为OriginFirmware.bin。
提取出固件之后,我们可以尝试用binwalk对它进行分析,这里,我们直接用-Me参数提取固件内容,其中M参数表示递归提取:
根据binwalk的分析结果,我们可以判断固件应该包含一个嵌入式Linux操作系统,智能网关的主要功能逻辑应该由文件系统中的可执行程序完成。待binwalk运行完毕之后,会生成几个文件夹,分别是2个squashfs文件系统,2个LZMA压缩的数据,以及1个jffs2文件系统,如下图所示:
上图中,前2个文件夹即2个LZMA压缩的数据,解压之后会发现是cpio文件系统,binwalk会自动帮我们递归全部提取。
逐个浏览这些文件系统中的内容,可以得到以下信息:
A. jffs2文件系统保存着门锁和网关的相关信息,如id等;
B. 两个cpio文件系统中,其中一个应该是恢复出厂设置时的备份文件系统,另一个是当前正在使用的文件系统;
C. 两个squashfs文件系统中,一个保存的全部都是mp3文件,另一个保存着网关的主程序,该程序即为我们将要分析的主程序。
D. 在固件的cpio文件系统中,可以找到/etc/shadow文件,文件内容如下:
借助于彩虹表,就可以找到root用户的登陆密码为abc123。使用该用户名,就可以通过串口顺利登陆海康萤石的智能网关,并使用串口shell提供的各种功能了。
我们现在已经拿到了固件内容,但是在对主程序进行分析之前,我们还需要处理一个串口总是虚接的问题,调着程序唱着歌,突然就被麻匪劫了(划掉),串口断开连接了,也是一件很恼火的事情。
在海康萤石的网关固件中翻一翻,可以发现在/sbin目录中有telnetd程序,如果我们可以通过telnet连接智能网关,或许会稳定很多,如下图所示:
该程序是指向busybox的软链接,如果把解压缩出的固件内容拿到windows环境后,可能会导致/sbin目录中是空的。我们可以使用串口shell登陆设备,然后运行telnetd程序,但这就意味着每次设备重启之后,我们都要使用shell启动telnetd程序,这样操作很麻烦。
继续翻找,我们在squashfs文件系统中找到initrun.sh脚本,这个脚本是在网关上电启动后进行初始化操作的。如果我们在该脚本中启动telnetd,然后将固件重新打包烧录回去,这样应该就不需要软排线连接设备了,为此我们给initrun.sh增加telnetd命令,如下图:
接下来,就要考虑如何将固件重新打包,然后刷回至Flash中了。固件解包时,只要binwalk跑一下就完事了,但是重打包就相对麻烦一些。我们刚刚修改了initrun.sh文件,该文件在squashfs文件系统中,所以就需要重新打包squashfs文件系统,但mksquashfs在打包时,有很多细节的参数和配置,这些参数和配置将直接影响到我们重打包的系统是否可以正常运行,而且有些设备只能识别特定版本的mksquashfs打包出来的固件。
为解决squashfs文件系统打包的问题,我们最好参考一下MT7688AN的官方SDK。假设海康萤石的开发者基于该SDK的进行开发的,那么我们也根据SDK中的固件打包方法进行操作,得到的固件应该就是可运行的。首先在官网上下载MT7688 SDK,链接如下:http://labs.mediatek.com/zh-cn/platform/linkit-smart-7688。
在下载到的SDK文件中,我们可以在include文件夹中找到image.mk文件,在该文件中可以找到打包squashfs文件系统时的命令和参数,如下图所示:
同时,在‘staging_dir\host\bin’目录中可以找到图4-8 中的mksquashfs4程序.
我们尝试用SDK中的mksquashfs4程序和图4-8中的参数打包一下开启了telnetd程序的squashfs文件系统,如下图所示:
上图中,-comp xz是选择xz压缩格式,通过binwalk可以直接查看到原固件中的压缩格式,所以我们也选择该压缩格式。
最后,将打包后的文件系统重新放回固件文件中。我们用了一个颇为简单粗暴的方法,即用UltraEdit直接16进制编辑。4.2节中binwalk分析结果显示从地址0x700000开始的位置是squashfs文件系统的位置,我们只需要将重新打包的squashfs文件覆盖到此处即可。需要注意的是固件文件从地址0x900000起始就是另一个squashfs文件系统了,所以,覆盖完成后需要调整两个文件系统之间的填充字节数量以保证另一个squashfs文件系统的起始偏移仍然是0x900000。至此,我们完成了重打包工作。
重打包完成之后,还需要将打包的固件烧录到Flash中去。烧录和提取是类似的操作,使用编程器就可以完成,操作过程我们就不再赘述了。
按照4.3的方式重打包并烧录固件后,我们应该已经可以通过telnet连接到萤石的网关上了。telnet连上后执行“ps”指令,通过简单的排除法,就可以确定我们要分析主程序是:“/dav/davinci”(达芬奇?达文西?)。接下来我们就对这个程序以及部分它调用的动态库进行分析。
我们首先静态看一下davinci文件,通过代码可以看到这个程序是有运行日志的,只是把不重要的日志屏蔽了(debug、verbose等),只输出了较为严重的日志内容(fatal、error等)。那么我们修改几条指令,使其跳过对日志严重程度的判断,如下图:
除了要patch日志输出代码之外,davinci在启动之后,初始化了一个线程操作看门狗,该线程会不断向/dev/watchdog进行写操作。如果一段时间内/dev/watchdog没有收到任何数据,那么整个设备就会重启。由于我们计划调试davinci程序,如果在调试过程中,触发断点导致看门狗线程挂起,那么整个系统就会重启。为此,我们先把该线程patch掉,然后再弄一个写入/dev/watchdog的小脚本,用以专门喂狗防止重启。相关位置的代码截图如下:
喂狗的小脚本内容如下:
相信脚本内容一眼就能够看懂,所以我们不做太多的解释了。此外,还有个guard.sh是用于检测davinci程序是否运行的,但它不影响我们的操作,所以就不讨论该脚本了,有兴趣的读者可以去看看。
完成上述操作之后,就可以把被patch的davinci上传至海康萤石的智能网关,此处我们不再采用烧录固件的方法,因为过于麻烦。通过翻阅该设备的文件系统,可以在设备中发现tftp程序,这是一个使用tftp协议传输文件的程序,可以用它从tftpd服务器上传或下载文件。我们使用从这个链接下载到的tftpd程序:http://tftpd32.jounin.net/tftpd64_download.html ,运行后,调整tftpd服务器的文件目录和监听网卡。然后在智能网关设备中使用tftp程序获取相关文件即可,相关命令截图如下:
待相关文件传输到萤石网关中之后,就可以运行FeedWatchdog.sh脚本开始喂狗,然后终止原本的davinci进程,接着删除/home/pidfile文件,该文件相当于互斥体,用于控制davinci仅运行了一次,最后启动我们自己的davinci_1进程,相关命令如下:
待程序运行一段时间之后,就可以查看程序的运行日志,然后通过日志分析程序的各种行为。
待程序运行一段时间之后,就会在‘/applog/devlog’目录下生成日志文件,每个文件500KB左右,如下图所示:
用tftp程序将日志取回,然后打开程序日志,在日志中搜索litedev.ys7.com,这个地址是智能网关上电之后第一个访问的地址,我们第二章中介绍过。搜索结果如下图:
上图中,我们可以看到由litedev.ys7.com解析而来的ip地址:115.231.107.14,这与我们用wireshark抓包时得到的结果是相同的。
继续翻看日志,在距离上图不远的地方,可以看到另一条日志,看起来像是与海康萤石MQTT服务器相关的日志内容,截图如下:
从图中,我们可以看到另一个ip地址:101.71.30.172,该地址同样与我们wireshark抓包的结果吻合。MQTT通信协议是构建于TCP/IP协议之上的一种轻量级通信协议,经常出现在IoT设备系统中设备端与云端的通信过程中。
结合这两条日志内容和wireshark的抓包结果,我们可以进一步确认设备的工作流程:与litedev.ys7.com通信,获取了MQTT服务器的地址;然后与MQTT服务器通信,实现设备的逻辑功能。
在图5-7和图5-8中,我们分别用红框标识了一个关键字符串。在海康萤石智能网关的文件系统中,二进制搜索其中一个字符串“lbs_connect”,确认该字符串出现在libmicrokernel.so.1中。用IDA加载该so文件,可以找到与lbs_connect字符串有关系的函数,查找该函数的交叉引用,可以看到有多处代码调用了此函数,我们随便找一处点开看下,如下图所示:
可以看到在lbs_connect函数返回成功之后,就会调用send_authentication_i函数。这个函数看起来就是加密和认证相关的函数,接下来我们通过动态调试的方式分析网关加密和认证的流程。
对于嵌入式Linux操作系统,我们通常选用gdb和gdbserver作为调试工具。我们可以直接在设备上使用gdb进行本地调试,但gdb程序体积比较大,而且直接在设备上运行gdb会有很多不方便的地方,所以我们选择通过gdbserver进行远程调试。为了完成调试工作,我们首先需要一个可以运行在海康萤石智能网关设备上的gdbserver程序。
在4.3节中,我们使用MT7688 SDK中的工具对固件进行了重打包,所以当我们需要与芯片配套的gdbserver时,也是去MTK官方查阅资料。事实上,MT7688开发板的固件包中确实内置了一个gdbserver,但是当我们提取出这个gdbserver,并通过tftp传输到网关上开始运行后,发现并没有任何输出,可能是MTK官方定制了gdbserver的代码,导致我们无法使用该程序调试。
那么,就只能去找一找有没有其他可以正常运行在海康萤石智能网关中的gdbserver程序了。经过一番搜索后,在rapid7官方github账户上发现了已经编译好的gdbserver程序,链接如下:https://github.com/rapid7/embedded-tools/tree/master/binaries/gdbserver , 这里我们选择下载芯片对应的mipsle版本的gdbserver。
这里需要说明一下,rapid7提供的gdbserver,可能在某些设备上也无法正常使用,所以最靠谱的方法是使用SDK自己编译一个出来,但是这里既然有可用的,我们就不(懒)再(得)去费功夫了。
我们通过tftp将gdbserver传输到网关中,并使用 gdbserver启动davinci。如果使用附加方式调试,很可能会错过davinci程序与服务器通信的认证过程。所以我们选择使用gdbserver启动程序,这样一来,在远程调试器连接之前,davinci程序将处于挂起状态。命令如下:
此时,gdbserver就开始监听23946端口,等待远程调试器的连接。
接下来,我们可以选择gdb或IDA作为远程调试器连接gdbserver。IDA提供图形界面,可以帮助我们理解程序的逻辑;但gdb要更稳定,可以有效避免IDA调试时出现的奇怪错误。我们这里直接选择gdb作为远程调试器,在后续的文章中会有介绍用IDA作为调试器的例子。
由于我们需要调试的是MIPS指令集的程序,而gdb默认情况下,仅支持调试与当前环境采用相同指令集的程序(i386),所以我们需要安装可以调试MIPS指令集的gdb程序。安装方法比较简单,直接输入sudo apt install gdb-multiarch即可。还可以给gdb程序加一个pwndbg插件,用于辅助我们的调试工作,该插件的官方地址是:https://github.com/pwndbg/pwndbg ,只需要下载下来,然后运行./setup.sh即可。此插件并非必需品,但是推荐装上。
完成gdb的配置工作之后,就可以使用gdb连接gdbserver开始远程调试了。gdb的调试命令非常多,可以直接搜索到很多整理好的常用命令,在这里,我们就遇见什么指令就解释什么指令吧。运行gdb-multiarch,截图如下:
上图中,我们分别设置architecture为MIPS,读取davinci的符号文件,在main函数设置断点,并连接远程的gdbserver,关键位置已用绿框圈出。然后,我们用快捷键c(continue),让程序开始执行,过一会程序就会在main函数的入口处被断下来。
此时,参考图5-11中的file命令加载libmicrokernel.so.1的符号文件,然后就可以直接使用函数名lbs_connect下断点了,如下图:
稍等片刻,就会看到程序断在lbs_connect函数中,等待我们的调试命令,截图如下:
图5-13最下方,打印出了函数调用栈,可以看到,程序是从lbs_redirect调用过来的,我们可以继续在外层函数下断点调试,以帮助我们理解程序的逻辑。
至此,我们已经具备了调试能力,可以调试固件中的davinci程序以及libmicrokernel.so.1等动态连接库。为了节约篇幅,我们先将一些分析结论与各位分享,然后再详细调试其中的某个细节。
在海康萤石的智能网关中,完整的通信流程涉及到3个密钥,分别是share key、master key以及session key。其中,share key是智能网关与litedev服务器(与之对应的另一个服务器是MQTT服务器)共有的密钥,由设备序列号等常量经过多次MD5运算得到,每个智能网关设备的share key都是独一无二的;master key是智能网关与litedev服务器经过密钥协商而来,用于加密传输session key,关于master key的密钥协商过程就是我们将要分析的重点内容;最后session key是智能网关与MQTT服务器通信时使用的加密密钥,该密钥由litedev服务器直接下发给智能网关。
以上三个密钥在使用过程中,share key是与设备绑定,且固定不变的,一旦获取了share key,就可以完成所有的认证过程,最终被海康萤石认定为合法的网关设备;使用者通过手机app绑定智能网关时,会经过密钥协商生成master key,每绑定一次会就更新一次;最后的session key则每次运行davinci程序时,都会更新,这也意味着每次重启网关都会更新session key。三个不同级别的密钥,三个不同的密钥生存周期。
在对海康萤石智能网关的密钥体系有所了解之后,我们就可以聚焦到某些细节上,下面来着重看一下master key和share key的生成过程。
为了触发智能网关的密钥协商流程,我们需要先删除当前的master key,具体讲就是删除 “/cfg/dev_masterkey”文件,如下图所示:
删除当前master key之后,按照前一节所述的方法通过gdbserver启动davinci,即可开始调试了。
既然我们希望弄清楚加密和认证的流程,那么这个流程中网关和服务器通信数据的处理是我们要重点关注的。我们首先静态分析一下5.2节结尾提到的send_authentication_i函数,它调用的common_serialize和authentication_i_serialize用于构造将要发送的数据。我们就在common_serialize函数之前下断点,然后用gdb命令查看待发送数据的内容(本次调试时,地址为0x007d2dd0),如下图所示:
在上图中,可以看到待发送数据内容还是空的。待函数common_serialize执行完毕之后,重新打印该地址的数据,如下图所示:
该地址的前几个字节已经被赋值了。继续调试该程序,在authentication_i_serialize函数之后下断点,待程序中断时,重新打印该内存处的数据,如下图所示:
可以观察到,authentication_i_serialize函数执行完毕之后,待发送数据基本构造完毕。我们对比一下本次发送数据和之前我们通过wireshark抓包获取的发送数据,如下图所示:
可以看到,两组数据有一小部分是相同的,而不同的这部分应该就是网关密钥协商的关键部分。
为了找到产生不同的原因,我们来重点逆向authentication_i_serialize函数,可以在该函数中发现其调用了一个签名函数digital_sign_serialize函数:
在该签名函数之前和之后下断点,可以观察到需要签名的数据以及签名结果。结合之前用gdb调试send_authentication_i时获得的发送数据,就可以分析出发送的数据格式如下:
其中,dev_subserial是设备序列号;random_1是1字节随机数,每次密钥协商时都是不同的。由于random_1字节的不同,导致了digital_sign不同,所以总共有33(1+32)字节是每次通信都不同的。经过本次通信,智能网关将random_1参数上传至litedev服务器。
通过类似的分析方法,就可以获知认证过程中的其他3个函数的作用,这3个函数分别是:wait_authentication_ii、send_authentication_iii以及wait_authentication_iv。加上send_authentication_i函数,网关和服务器之间总共同步了4个随机数。
在上述的分析过程中,可以发现一个名为lbs_affair的结构体贯穿了整个认证过程,4个随机数也保存在结构体之中,该结构体的内容如下:
00000000 lbs_affair ``struct``00000000 random_1: .byte``00000001 random_2: .byte``00000002 random_3: .byte``00000003 random_4: .byte``00000004 dev_subserial: .byte 16``00000014 master_key: .byte 16``00000024 dev_id: .byte 32``00000044 session_key: .byte 16``00000054 share_key: .byte 32``00000074 share_key_len: .half``00000076 .byte``00000077 .byte``00000078 global_out_packet: lbs_packet``0000008C global_in_packet: lbs_packet``000000A0 lbs_net_work: .word``000000A4 lbs_affair ends
其中,random_1和random_3由智能网关生成,发送给服务器;random_2和randon_4由服务器生成,发送给智能网关。
通过这4个随机数以及share key就可以生成master key了,并进一步由master key获取session key。其master key的生成算法比较简单,可以在generate_masterkey函数中找到,如下图所示:
根据图中红框标出的偏移可以知道,master key的生成过程就是将random_1、random_2、random_3、random_4和share_key拼凑在一起,然后调用sha512函数,其hash结果就是最终的master key了。
继续分析其固件的后续内容可以发现以master key作为密钥,使用aes cbc算法解密session key相关的代码段,这里就不截图了。获取session key之后,通信数据的加密密钥就完全切换为session key,不再使用master key了。
相比于master key的生成过程,share key的生成无疑简单了很多。可以在generate_sharekey函数中找到关于share key的各种运算,通过阅读IDA反汇编后的代码,可以确认share key是通过对dev_subserial和dev_verification_code以及一个固定的盐进行多次MD5而得到,其中dev_verification_code是设备的认证码,该认证码被贴在海康萤石智能网关背面的标签上。在md5运算过程中,固定的盐值如下图所示:
上图中,“ 88075998”是海康的联系电话,在此处,“www.88075998.com”作为盐参与了第二次MD5运算中。
到此,关于海康萤石的分析文章就结束了。在这篇文章中,我们先是分别解决了电路分析,固件提取、分析、重打包,程序的动态调试等问题,最后分析了海康萤石的密钥体系,虽然写的有些模糊,但基本上涵盖了所有关键点,很多海康和萤石的其他IoT设备也使用了类似的密钥体系,本篇分析可以提供一个借鉴。其实关于海康萤石智能网关还有很多可以写的内容,但鉴于胖猴实验室和海康的良好关系,把海康萤石的网关设备分享得非常透彻也不太好,还是等有机会私下交流吧。
在笔者分析海康萤石智能网关时,Ghidra尚未发布且IDA尚不支持mips decompiler,但在写本文时已经出现了很多好用的工具,合理利用这些工具可以大幅度减少分析工作量。最后,希望本篇文章能给各位读者带来一些收获,如若有什么想商量或者讨论的,可以随时联系我们胖猴微信:PwnMonkey。在后续文章中,我们还会继续分享其他的研究案例,敬请期待。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1320/