逆向工程智能门锁
引言
最近我有机会研究一家知名安全公司(F-Secure)的发现,他们发现了Guardtec KeyWe智能门锁中的一个漏洞。F-Secure人员发现,由于设计缺陷,攻击者可以拦截和解密来自合法门锁所有者的流量。我觉得他们的博客(发布于2019年12月)非常引人入胜且信息丰富。很快,我受到激励,想看看是否能复制他们的努力,意识到F-Secure已经发布了公告,供应商也有机会减轻他们的风险。不幸的是,由于没有固件更新功能,他们的缓解选项非常有限。相反,他们选择在Android应用中使用混淆来隐藏更相关的代码部分(我必须说他们做得相当好)。
尽管F-Secure奠定了基础,但他们小心不透露太多信息,甚至删减了他们自己的一些工具,从而保留了“王国的钥匙”,正如他们所说。在我的旅程中,我发现自己不断回到他们的博客,尤其是当我发现自己新的相关信息时。本博客旨在不仅整合我的笔记和记录我的研究,还可能向其他人介绍一些很酷的工具和方法,用于逆向工程Android/iOS应用。
收到我的KeyWe智能门锁货物并创建一个测试夹具来安装它后,我将Android应用下载到我的手机并创建了我的账户。我熟悉了门锁的功能和移动应用的外观和感觉。我还运行了我的Nordic nRF Connect移动应用(可在Google Play商店免费获取)以获取有关我的门锁的有用信息,如蓝牙地址、主要服务UUID、特性等。注意:理解蓝牙低功耗GATT和GAP超出了本文的范围,但BLE规范可在此处轻松获取:https://www.bluetooth.com/specifications/,如果您想阅读的话。
有用信息
KeyWe(门锁)和(移动)应用之间的通信通过蓝牙低功耗(BLE)数据包传输,这些数据包使用标准ECB AES-128密码加密,以防止第三方窃听。消息通道的安全性完全基于用于加密和解密OTA(空中)AES-128数据包的3个秘密密钥。
- 通用密钥(CommonKey)用于初始密钥交换
- 应用密钥(AppKey)用于加密从应用到门锁发送的数据包
- 门锁密钥(DoorKey)用于加密从门锁到应用发送的数据包
密钥生成
CommonKey完全基于一个静态的16字节值,该值简单地枚举了设备蓝牙地址的最后5个字节,如下所示:
在进一步检查多个KeyWe设备上的CommonKey后,似乎所有检查的设备之间的唯一区别是设备蓝牙地址的最后两个字节!在我的情况下,值4C和93对我的设备是唯一的。这表明CommonKey是高度可预测的,并且完全基于每个设备的16字节静态值中的两个字节!
AppKey和DoorKey由两个算法(并 heavily obfuscated)方法makeAppKey和makeDoorKey创建。F-Secure人员创建了一个很好的工具,通过模拟这两个方法的操作来生成三个秘密密钥(尽管他们删减和混淆了他们的工作,使其通常无害)。然而,经过相当长的时间后,我能够自己逆向工程这两个方法的功能,使用一个方便的开源工具叫做Frida(稍后详细介绍)。
AppKey和DoorKey创建包括向makeAppKey和makeDoorKey函数传递两个参数。这两个参数分别是AppNumber和DoorNumber。
AppNumber是一个静态(硬编码)的12字节值(用四个零字节填充):92 4b 03 5f bd a5 6a e5 ef 0e d0 5a 00 00 00 00 注意:AppNumber用CommonKey加密,并作为用户会话的第一个数据包传输发送到门锁,从而启动会话的开始。
DoorNumber是一个动态(每次新会话更改)的12字节值(用四个零字节填充),由门锁生成。这个值(也用CommonKey加密)在收到AppNumber后发送到应用。(见下图)
注意:这两个“开场”传输(AppNumber和DoorNumber)完成了初始密钥交换过程,并允许创建AppKey和DoorKey,这将为应用和门锁之间的安全OTA通信提供保障。
现在AppNumber和DoorNumber已经创建并交换,我们有了生成剩余两个秘密密钥(AppKey和DoorKey)所需的两个组件。这是通过调用makeAppKey和makeDoorKey函数,以AppNumber和DoorNumber作为参数来完成的。这是在固件内部完成的,不通过OTA发送。
应用流程图
现在我们已经生成并交换了AppKey和DoorKey,每一方现在可以加密/解密发送或接收的数据包。所有由应用传输的数据包将使用AppKey加密,所有由门锁传输的数据包将使用DoorKey加密。双方都知道彼此的加密方案,因此可以解密数据包。
下图演示了典型用户会话的事件顺序:
应该注意的是,所有这些数据包在每次用户会话开始时通过OTA传输,并且所有13个数据包都根据传输方向使用AppKey或DoorKey加密。
逆向工程Android APK
所有移动应用都作为APK(Android应用包)文件下载。APK文件以ZIP格式保存,通常直接下载到Android设备,通常通过Google Play商店,但也可以在其他网站上找到。当逆向工程Android APK时,我发现使用第三方网站搜索相关APK的旧版本通常很有帮助。我的一个最爱是https://apkpure.com/。
软件要求
- Java版本1.8.0_251
- ADB(android调试桥)版本1.0.41
- APKStudio(Apktool的包装器)版本2.4.1
- Dex2Jar版本
- Jadx版本1.1.0
- Frida版本12.8.9
硬件要求
- 已root的Android手机
一个典型的APK包含一些非常有用的内容,如AndroidManifest.xml、classes.dex和resource.arsc文件;以及Meta-INF和res文件夹。有几种不同的方法可以打开驻留在PC上的APK。显然,因为它是一个ZIP文件,任何各种UNZIP提取器都可以正常工作,但是使用像Dex2Jar、Apktool和Jadx(仅举几例)这样的工具提供了额外的优势,如将.dex文件转换为java代码以提高可读性,以及GUI支持以便于导航代码。
DEX文件(Dalvik可执行文件)是用于初始化和执行Android移动平台应用的开发人员文件。像Apktool这样的工具可以将DEX(机器语言)文件反编译为Smali(汇编语言源)文件。我们还可以使用像dex2jar这样的工具将DEX文件转换为JAR(java)文件,并使用jadx GUI将JAR文件作为java源代码打开。Java源代码比Smali源代码更容易阅读。有许多选项可用于导航Android APK,包括我的一个最爱,APKStudio。
由于有许多选项可用,描述实现这些工具中任何一个所涉及的各种步骤超出了本文的范围。我建议下载它们并尝试几种技术以找到最适合您的方法。那里有很多有用的教程。
使用Frida
F-Secure研究人员在他们的博客中表示,他们能够使用一个叫做Frida的工具拦截Android应用中的函数调用。我不知道这个工具,所以我决定检查一下。这个工具太神奇了!理解如何实现Frida超出了本文的范围。然而, suffice to say, Frida允许研究人员附加到应用程序中的现有函数,并动态转储参数和返回值。这绝对值得检查!https://frida.re/docs/home/
Frida w/toolkit安装
|
|
要求:
- frida-server和adb(android调试桥)
- 已root的Android手机用于调试apk(我使用了一台旧的Samsung GS5)
在已root的手机上安装frida-server
要安装服务器,导航到https://github.com/frida/frida/releases并下载适用于特定手机平台的适当文件。 (如果您不确定手机的架构,下载并运行Droid Hardware Info(从Google Play商店))
将下载的文件复制到您的项目目录 导航到您的项目目录 使用以下命令解压缩.xz文件:xz -d -k frida-server-12.9.8-android-arm.xz 使用adb(android调试桥)工具将提取的文件推送到已root的手机:
初始设置:(在已root的手机上安装frida-server)
|
|
一旦frida-server安装在已root的手机上,开始一个新会话如下:
|
|
最初,我在协调学习新工具(Frida)方面遇到了困难,加上试图找到F-Secure人员在他们博客中引用的相同函数调用的额外负担。由于F-Secure公告,供应商 heavily obfuscated 了最新版本,意味着不再有’makeAppKey’或’makeDoorKey’函数可以附加到。我还发现供应商 incorporated 安全措施以防止在已root的手机上运行KeyWe应用。F-Secure研究人员使用python和Frida javascript创建了一个很酷的工具,附加到违规方法并注入java代码以始终返回false给’isRooted’。
不幸的是,由于我的Android APK新版本的 heavy obfuscation,‘RootTool’类不存在,我被迫搜索我的APK代码以尝试找到它的等效类。经过相当长的时间搜索代码后,我最终找到了root检查方法。结果发现’RootTool’类现在被引用为’n’,而’iSRooted’函数现在被引用为函数’b’。
根据我的搜索发现修改F-Secure的KeyWe-Tooling脚本
成功规避了root检测!
继续前进,我确定使用最新版本的Android应用比它值得的麻烦更多。混淆是巨大的,并且变得极其繁琐和令人沮丧,试图找到名称已更改为单个字母的函数。幸运的是,大约在这个时候,一位同事向我提供了一些旧KeyWe Android APK的链接https://apkpure.com/keywe-for-a-smarter-life/com.guardtec.keywe/versions。这被证明是一个伟大的发现,因为现在我可以抓取一个公告之前的版本,所有引用的函数仍然完好无损。我通过返回到原始(未修改)版本的F-Secure root-evasion脚本并它工作了来证明了这一点!此外,我了解到他们的’trace_java_functions’工具现在也工作了,为我提供了大量明确的数据来处理。
在以下Frida hexdump示例中,我们可以看到调用AES-128密码函数(由应用生成)传递两个byte_array参数(AppNumber, CommonKey)。这显然是用CommonKey加密AppNumber以用于OTA数据包传输到门锁,启动会话事件序列。
同样,此示例还显示了另一个调用AES-128密码函数,传递参数(DoorNumber, CommonKey),以加密DoorNumber用于OTA传输到应用。
最后,从此示例中,Frida允许我们看到传递的参数和生成AppKey和DoorKey的两个内部函数调用的返回值。
基于许多捕获的Frida会话和数据的 deciphering,我很快变得非常熟悉KeyWe应用,并很好地理解了重要密钥是如何以及在哪里生成的,以及启动时的事件序列。此外,我了解到在活动会话期间,门的状态通过应用和门锁之间交换的信息不断监控和更新。我的会话包括使用手机上的应用登录、解锁和锁定门。
F-Secure Frida java脚本: 规避root检测:disable_root_detection.js 跟踪注入的java函数:trace_java_functions.js(原始) 会话包括:
登录,等待连接和LOCKED(红色)状态 点击UNLOCK,等待几秒钟,点击LOCK,等待几秒钟 点击UNLOCK,等待自动LOCK,断开连接
C:\Users\rayfe\keywe-tooling\frida>start_root.py C:\Users\rayfe\keywe-tooling\frida>keywe_inject.py trace_java_functions.js
BTSNOOP(Android蓝牙HCI记录器)
最终,BTSNOOP必须是我在想要捕获中央(手机)和外围设备(门锁)之间的完整蓝牙会话时的最伟大发现之一,我尝试了各种方法来捕获我的OTA蓝牙会话,包括Nordic的nRF Sniffer开发板nRF52840-DK、Sena的UD100 dongle、Ubertooth-One和Texas Instruments CC2540 dongle。所有这些方法的问题是他们无法跟随连接 due to Bluetooth Low Energy (BLE) channel hopping。Nordic nRF52840-DK在使用它与Wireshark和Nordic的BLE sniffer插件时接近,但不幸的是,数据包捕获在链路层(而不是主机控制器接口层)导致加密数据无法解析。
由于它不是在HCI层捕获的,意味着它容易受到内置CCM AES-128 BLE安全密钥交换协议握手的影响。据我所能确定,这意味着如果nRF52840-DK在配对时没有嗅探,它将完全错过安全握手,导致数据包无法解密。
重要:nRF Sniffer的缺点! 似乎如果KeyWe门锁在配对后但在应用传输第一个数据包之前执行信道跳变,nRF Sniffer将错过初始CCM AES-128 BLE安全密钥交换。这将导致加密的“无用”数据包。这是nRF Sniffer无法跟随信道映射的缺点。注意:CCM AES-128 BLE安全密钥交换是在所有蓝牙低功耗OTA无线连接中发现的安全协议,以防止MiM窃听。
CCM AES-128 BLE安全密钥交换:(这是与KeyWe项目无关的一般信息) 临时密钥在蓝牙配对过程中使用。短期密钥用作第一次设备配对时加密连接的密钥。短期密钥通过使用三条信息生成:临时密钥,以及两个随机数,一个由从设备生成,一个由主设备生成。 一旦连接用短期密钥加密,其他密钥被分发。长期密钥替换短期密钥以加密连接。身份解析密钥用于隐私。连接签名密钥用于认证。
幸运的是,有一种极好的方法可以使用您的Android设备捕获蓝牙流量! 在您的Android手机上
转到设置 如果开发者选项未启用,现在启用它 转到开发者选项 启用选项启用蓝牙HCI snoop日志 执行需要捕获的操作(会话) 禁用选项启用蓝牙HCI snoop日志 使用ADB(Android调试桥)将文件复制到PC 感兴趣的文件是btsnoop_hci.log
注意:通常,我会留下选项启用蓝牙HCI snoop日志启用,因为它在我已root的测试手机上 获取完整蓝牙会话的btsnoop_hci.log 列出的Android文件
拉取的btsnoop_hci日志文件
将btsnoop_hci.log重命名为btsnoop_hci-07-31-20.log(附加我的会话日期)
WIRESHARK分析
将btsnoop_hci.log导入Wireshark,我们可以看到OTA加密数据包交换。这与Frida函数hexdumps结合提供了一种有价值的方式来交叉引用用户会话的活动。从我在研究KeyWe门锁期间生成的大量会话中,我可以确认这些数据包交换每次会话都遵循相同的顺序,并且从不变化。在以下示例中,我们可以看到开场密钥交换,由应用启动,随后是门锁。
示例1:应用发送AppNumber — 门锁返回DoorNumber — 门锁发送Hello
有趣注意:(在上面的屏幕截图中):fb2b28c68b3f99c514b98fada4bf0b89(传输#2)可以用CommonKey解密以重现DoorNumber! 这可以通过使用免费的在线AES-128密码工具验证:http://aes.online-domain-tools.com/ 输入加密数据包fb2b28c68b3f99c514b98fada4bf0b89并输入秘密密钥(CommonKey)c88ff4150f4a4c27934a6c5e6741efac,然后单击解密
使用在线AES-128密码工具是一种方便的方法,将Wireshark会话数据与Frida hexdump数据关联起来。接下来的几个屏幕截图显示了各种蓝牙OTA流量如何与应用的已知函数调用 coinciding 的示例。这为我们提供了关于程序流和执行的大量信息。
示例2:应用发送Welcome — 门锁发送START — 应用和门锁都交换doorMode
示例3:应用发送eKey — 门锁发送eKey(认证和授权)
示例4:门锁状态 — doorTimeSet交换
示例5:门锁状态交换
重放攻击
如屏幕截图(上方)所示,整个蓝牙会话可以在Wireshark中使用btsnoop_hci.log(捕获)进行分析,并与使用Frida工具找到的数据并排比较。还注意到,每一个通过空中(OTA)发送的加密数据包都可以通过简单地确定传输方向(应用到门锁或门锁到应用)并使用适当的密钥(AppKey或DoorKey)来解密。
现在我们有密钥并知道如何解释所有数据,我们可以尝试用重放攻击操作门锁。再次,F-Secure在他们的Github中提供了一个很好的工具,他们称之为’open_from_pcap’。基于他们预录制的pcap会话中的信息,这个工具允许他们重放会话并操作门锁。当然,当他们删减了他们的keys.py脚本时,这个工具变得无害。然而,正如我 earlier stated,我最终能够逆向工程功能。所以,通过将F-Secure的删减keys.py替换为我自己的版本,它允许我在我的btsnoop_hci.log捕获上实现’open_from_pcap’工具。
使用我的Sena UD100蓝牙USB适配器,运行’open_from_pcap’脚本的第一个结果如下所示:
显然,我修改的keys.py正确确定了CommonKey、AppKey和DoorKey,但在eKeyVerify阶段失败。
没有深入F-Secure的编码,open_from_pcap python脚本调用另一个脚本(decode_from_pcap)中的一个函数,该函数应该从会话pcap中检索eKey。不幸的是,这对我没有正常工作。也许这与他们的pcap文件格式结构与我的btsnoop_hci.log文件(在Wireshark会话中保存为pcap)的差异有关。无论如何,我决定放弃 deciphering 他们的代码,而是修改了’open_from_pcap’文件以使用我的硬编码eKey,而不是尝试检索它。
顺便说一下,F-Secure人员需要从pcap中检索密钥的原因是因为eKey在账户创建时在线存储,并且不存在于任何OTA传输中。然而,它可以使用Frida检测到,通过将javascript注入到eKeyVerify函数中,该函数提供返回值的hexdump(见下方)。