本文内容较长,所以将目录整理在最前面,用于方便各位读者查阅,目录如下:
下面就开始正文:
果加的智能门锁大体分为两个系列,其一是家用版智能门锁,其二是公寓版智能门锁。家用版智能门锁通过BLE与手机通信,并通过手机间接与果加云端通信;公寓版门锁通过433MHz与其对应的果加网关通信,网关与果加云端通信。尽管通信方式不同,但是家用版和公寓版门锁的架构和用到的芯片都是类似的,因此在本篇文章中,我们重点对家用版门锁的固件进行分析,偶尔提及公寓版门锁。
固件,通常是指驱动底层硬件的软件程序,固件直接与硬件打交道,控制硬件实现各种逻辑功能。而在IoT设备中,固件通常包含了RTOS(Real Time Operating System, 实时操作系统)和应用软件,提供了从底层驱动到高层应用的所有功能。
大部分IoT设备的固件存储在Flash中,在进行分析之前,首先需要设法获取设备固件。
家庭版果加智能门锁需要通过使用者的手机间接连接到果加的云端服务器,我们只要监听app的通信数据,就可以发现门锁更新时的固件下载地址,如下图2-1所示即为Fiddler的抓包。
图2-1 抓取果加家庭版门锁下载地址
相对来讲,公寓版的果加智能门锁要麻烦一些。公寓版的果加智能门锁在433MHz信道与其网关进行无线通信,网关通过有线连接到互联网,进而访问果加云端。我们在交换机上做了一次端口监控,通过分析镜像而来的通信数据找到了固件的下载地址。
图2-2 交换机端口监控
通常情况下,我们选择IDA作为固件分析工具。不同于Windows程序或者Android程序,IoT设备的固件在开始逆向之前要做一些准备。如果要分析Windows程序,一般都是直接拖到IDA中,IDA会自动识别为PE格式文件,并帮助我们选择好处理器型号等信息(MetaPC),如下图:
图2-3 IDA自动解析PE文件
而对于IoT设备,IDA无法自动完成上述分析,因为固件往往与IoT设备的底层硬件直接相关,而在底层硬件中MCU(Microcontroller Unit, 微控制单元)是最为关键的一部分,不同MCU会使用不同的指令集,以及不同的文件系统等。因此,要使用IDA分析固件,必须先确定MCU的相关信息。
正如我们在第二章开头所述,固件一般要包含一个实时操作系统,设备上电后就会引导启动这个RTOS。如果设备是基于嵌入式Linux操作系统运行的,那么就会引导Linux kernel程序。此时,在固件中可以发现Linux文件系统,而文件系统中的各个程序将是我们的分析重点。如下图所示:
图2-4 海康威视门锁网关固件
上图中可以看到海康威视智能门锁网关有Squashfs文件系统和JFFS2文件系统,还有U-Boot引导程序等。海康威视的智能门锁会在以后的文章中分析。
如果设备是运行其他RTOS,如FreeRTOS等,那么,此时的固件文件可能就完全是一段编译好的包含代码和数据的可执行文件。云丁鹿客的某些智能门锁型号是FreeRTOS系统,我们后面会酌情考虑要不要发出来与大家分享。我们这次要分析果加智能门锁的固件,也是一个单纯的可执行文件,binwalk分析结果如下图所示:
图2-5 果加门锁固件内容
此时,如果是嵌入式Linux操作系统,那么就可以开始分析他文件系统中的关键程序了。IDA可以自动解析出ELF文件格式,然后开始反汇编工作。但是果加智能门锁的固件完全是个单片机程序,那么IDA不能完成自动解析工作,所以我们需要手动指定一些解析信息。
首先我们需要确定MCU使用的指令集,通过MCU上刻印的芯片型号(STM32L071),可以在st官网上找到对应的Reference Manual或Datasheet,其地址是:https://www.st.com/zh/microcontrollers-microprocessors/stm32l071rz.html。通过这些文档可以确定MCU使用了Arm Cortex-M0+内核,该内核采用Armv6-M架构,如下图所示:
图2-6 果加智能门锁的MCU信息
实际分析中,虽然MCU是Armv6-M架构,我们在IDA中使用Armv7-M指令集来解析固件,因为Armv6-M是Armv7-M指令集的子集,所以用Armv7-M指令集解析并没有什么问题。为何不用Armv6-M呢?因为使用Armv6-M解析时会有部分指令无法解析。这很奇怪,我们也没找到问题的原因,如果有知道原因的读者可以告诉我们。加载时的配置信息如下图:
图2-补1 IDA加载配置信息
确定了指令集之后,我们还需要设置固件加载地址。通常MCU都会有一块固定的存储区域用于存储代码,这个区域的起始地址就是固件的加载地址,这些信息同样可以在References Manual或Datasheet中找到,下载地址见上文。果加智能门锁使用的STM32L071的相关信息如下图所示:
图2-7 STM32L071RZT6内存映射图
固件代码会加载到上图中Flash System Memory区域,即0x0800 0000地址之后的内存。那么我们在IDA加载过程中设置ROM起始地址为0x0800 0000,如下图:
图2-8 设置IDA的ROM加载地址
上文中已经给IDA指定了解析固件所需要的部分信息,等IDA加载完毕之后,我们发现IDA依旧将其当成一个binary文件,并未解析出任何代码,这是因为IDA不知道该从哪里开始解析。此时我们可以翻看programming mannual,查到与中断向量表有关的内容,如下图:
图2-9 中断向量表
由上图可知,固件的起始部分应是中断向量表。设备上电之后执行的代码,即Reset复位中断,位于中断描述表中第二项所指的位置,偏移0x4,截图如下:
2-10图 IDA解析中断向量表
跳转到此位置(0x8001BD),然后按’c’令IDA将此处解析为代码,IDA将解析出大量的代码段和函数。之后,我们就可以开始固件的逆向分析工作了。
图2-11 IDA解析代码
仔细看一下IDA解析出的代码,篇幅很多,没有调试信息,也没有可参考的字符串,这就导致很难弄清楚这些代码的作用,更遑论分析代码。
此时,我们需要一些信息来辅助分析。考虑到用户可以通过手机控制门锁,所以我们尝试以手机与门锁之间的通信为突破口,来研究一下这大片代码的含义。
通过逆向果加app可以确定门锁与手机通信时的一些常量,并利用这些常量定位固件中的关键代码。所以我们先开始app的逆向分析。
通常对app进行分析的第一步,是查看它输出的日志。幸运的是笔者在2018年分析的果加家庭版智能门锁app并没有隐藏日志内容,而是大大方方的打印了出来。通过搜索日志内容中的关键字符串,我们很容易定位到发送BLE开锁指令的关键代码在cn.igoplus.locker.ble包中,截图如下:
图3-1 BLE开锁时的关键代码
进一步追踪BLE通信数据的生成过程,可以发现所有BLE通信指令的生成全部都在native函数中,这些native函数的声明在cn.igoplus.locker.ble.cmd包中,如下图所示:
图3-2 BLE指令的生成函数
这些函数的定义在都在libBleCmd.so中,翻阅代码就可以找到该so的名称和加载位置,如下图所示:
图3-3 BLE通信数据由libBleCmd.so生成
那么,就开始分析该so的代码吧。
在apk中找到libBleCmd.so,并通过IDA加载该so文件, 待解析完毕之后,我们可以打开Exports选项卡,查看这个so文件的所有导出函数,如下图:
图3-4 libBleCmd.so的导出函数
在导出函数中可以找到所有BLE指令的生成函数,以及Decrypt和Encrypt用于加密或者解密BLE通信数据的函数,看函数命名应该是和Tea算法有点关系。在Tea算法中,存在几个常量用于加密或解密运算,而这几个常量就可以帮助我们定位关键代码。所以,我们跟进Decrypt函数看一下:
图3-5 Decrypt函数的实现
上图中,可以看到该函数是对Tea_encrypt函数的封装,在这个封装中,出现了2个常量,我们用红框标识出了这2个常量,接下来就通过这些常量分析一下固件代码。
按照上一章的分析结果,BLE通信中使用了Tea加密算法,我们在门锁固件中搜索Tea加密算法的常量,搜索结果如下:
图4-1 关键常量搜索结果
经过反复对比libBleCmd.so的Decrypt函数和固件F0-S1_1_1-H1_0-R.bin的代码之后,我可以确定固件中Encrypt和Decrypt函数的位置,如下图:
图4-2 固件中的Encrypt和Decrypt函数
上图中,其他位置的代码虽然也引用了常量‘0xCE6D’,但看代码内容不太像是Encrypt和Decrypt函数。
在此基础之上,我们查找Decrypt函数的交叉引用,可以定位到代码如下图所示:
图4-3 查找Decrypt函数的交叉引用
上图中,两处关键代码已经用绿框圈出。首先看第一处关键点,即0xBABEC0DE位置,在家庭版果加智能门锁app的日志中也出现过这个常量,部分日志内容截图如下:
图4-4 家庭版果加门锁app的日志
可以看到,BLE消息就是以0xBABEC0DE字节开头。由此可以推断,截图中的固件代码应该是在处理接收到的BLE消息。进一步分析代码,可以确定Decrypt函数的三个参数分别为:消息密文,消息长度和解密密钥,并由此推断内存MEMORY[0x20001848]开始的几个字节应该是存储了解密密钥。通过搜索MEMORY[0x20001848]的交叉引用,可以找到密钥的生成代码、使用代码等,并由此进一步扩大我们对固件代码的理解,但这里就不再深入分析了。
通过对上述代码的分析,我们可以猜测图4-3中的sub_800B528函数应该是对接收到的消息进行预处理,检验消息头是否正确,crc校验是否正确,是否可以成功解密等。如果一切都顺利,那么该函数返回0。搜索sub_800B528函数的交叉引用,可以定位到sub_800EB20函数,如下图所示:
图4-5 sub_800EB20函数
通过分析上图中的代码,可以确定该函数其实是对sub_800B528函数的封装,根据sub_800B528的返回值,设置某些关键的内存。此外,还可以分析得出以内存0x20002D44开始的几个字节中,存储的是收到的BLE消息密文;内存MEMORY[0x20000174]存储的是接收到的BLE数据长度。由此,我们对固件代码的理解逐步增加。
继续向上回溯调用sub_800EB20函数的位置此函数为整个固件中最复杂的函数,我们将其F5反汇编之后,整理一下代码,可以看到该函数函数只有入口,没有出口:
图4-6 整理后的sub_80000C8函数
然后我们再搜索下sub_80000C8函数的交叉引用,如下图所示:
图4-7 sub_80000C8函数的交叉引用
继续向上溯源,可以找到下图所示位置:
图4-8 loc_80000C0标签的交叉引用
可以看到,sub_80000C8函数是系统复位中断之后执行的第二个函数。一般情况下,复位中断会执行两个函数,第一个函数为init函数,即图4-8的sub_800D704函数;而第二个函数为main函数,即图4-8的loc_80000C0标签处的代码。此外,单片机的main函数一般都是死循环,不存在执行完毕后退出的情况,这与我们在图4-8中看到的while死循环相对应。与我们常见的运行于操作系统之上的程序不同,比如说’ ls’、’pwd’等常见的程序,他们完成各自的功能之后,就会退出,而单片机上电之后,MCU就要开始运行给他编写的程序,一直到单片机断电为止。
确定main函数之后,我们把目光重新聚焦在图4-5分析的sub_800EB20函数,当时我们把这个函数的功能概括为预处理,即完成消息头校验、crc校验、数据解密等工作的函数。那么在预处理之后,就应该是解析BLE数据、完成逻辑功能的代码。相关代码截图如下:
图4-9 BLE命令解析相关代码
上图中,我们可以根据代码结构进行一些猜测:MEMORY[0x20000140]和MEMORY[0x20000169]应该是两个标志位,其中一个标志位标识收到了BLE指令;v115是预处理函数的返回值,返回值为0代表预处理成功,这与上一篇的分析是一致的;绿框部分的代码,即为真正解析BLE指令的代码。
然后我们再来看看sub_800F9EC函数,即图4-9绿框中的代码。将代码反汇编后,稍作整理,可以看到下图所示的特征:
图4-10 BLE指令解析代码
再结合app的输出日志,如下图所示:
图4-11 家庭版果加智能门锁app部分日志
可以确定图4-10 中,变量v3是保存了门锁发给手机的BLE指令类型,如图4-11中,0x2004类型代表开锁命令的返回数据。
下面我们仔细阅读一下图2-5中case 0xE004部分的代码,该分支是门锁对于BLE开锁指令的回复信息,将相关代码截图如下:
图4-12 BLE开锁指令回复分支
进一步分析图中涉及到的函数,可以确定sub_80125C8函数的作用是日期转时间戳,其返回值v57就是时间戳。变量v57将会与v59和v60进行了比较,当v57不在[v59, v60]区间内时,就会调用sub_800D40C(0x1B)函数。此处的比较,我们是能够猜想到的:如果密码过期了,是无法开启门锁的,但是sub_800D40C(0x1B)函数就不太确定其含义了。所以我们再来研究一下sub_800D40C(0x1B)函数。
查找sub_800D40C(0x1B)函数的交叉引用,可以发现其调用位置有很多,如下图所示:
图4-13 函数sub_800D40C(0x1B)的交叉引用
上图中列出了大量调用sub_800D40C函数的位置。点开每个调用位置,可以发现每次调用该函数时都会传递一个常量作为参数。而且在main函数进入while循环之前,也会调用此函数,如下图所示:
图4-14 进入while循环之前调用sub_800D40C函数。
为确定sub_800D40C函数的真正含义,我们尝试更改函数的参数,然后将更改之后的固件刷入门锁。其更改方法并不复杂,直接进行16进制的固件文件编辑即可,如下图所示:
图4-15 sub_800D40C函数参数
上图中,红框部分为传入参数的数值,截图中分别是0x09和0x0A。在16进制编辑软件中打开固件文件,找到该偏移位置:
图4-16 十六进制编辑固件文件
将上图中的高亮字节0x09改为其他字节,如0xB,接着保存固件即可。然后将修改之后的固件刷到智能门锁设备中,刷入方法也比较简单,只需要在Fiddler中做一个AutoResponder,用修改过的固件替换原本固件,就可以了,具体操作如下图所示:
图4-17 利用Fiddler替换原本固件
待智能门锁完成固件更新之后,我们给门锁重新上电,当门锁运行至while循环之前的sub_800D40C(0xB)函数时,门锁发出提示音:“请输入新密码”,而不是往常的“设置成功”提示音。再次修改固件,使智能门锁调用sub_800D40C(0xC)函数,上电后门锁发出提示音:“请再次输入新密码”。由此,我们可以判定sub_800D40C函数正是发出语音提示的函数,而该函数的参数代表了不同语音提示内容。通过反复修改并刷入固件,我们整理了一部分语音提示内容的编号,如下表所示:
表4-1 家庭版果加智能门锁语音提示编号表
分析到此时,其实我们相当于在固件中找到了一个非常关键的函数:sub_800D40C函数。对于我们分析者来讲,这函数相当于日志输出的作用,在他的帮助之下,我们就能理解绝大部分代码,这里不再占用篇幅讨论更多内容了。
在表4-1中,我们可以看到有一个“进入测试模式”的语音输出。顺其自然的,我们想看看测试模式都干了点什么。通过sub_800D40C(0x3)函数,我们可以定位到原固件中进入测试模式的代码,如下图所示:
图5-1 进入测试模式的相关代码
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2020-5-15 16:27
被胡一米编辑
,原因: