JMSWRNR™ 设置

破解智能家居设备

我如何逆向工程一个基于 ESP32 的智能家居设备,以获得远程控制访问权限并将其与 Home Assistant 集成。 类型 文章 发布 2024年2月3日 阅读时长 68 分钟

简介

最近,我有点沉迷于将我房子里的所有东西都连接到 open_in_newHome Assistant。 在一个应用程序中连接和自动化所有内容是如此令人满意; 我终于可以忘记每个不同品牌的智能产品的随机移动应用程序。 但是我拥有一种产品,它顽固地无法连接到除其自身的移动应用程序以外的任何东西。 这是一个时尚的空气净化器,但不幸的是,其令人失望的应用程序让它黯然失色。 如此多的现代产品依赖于互联网连接和云帐户来实现基本功能,而且谁知道它们会收集哪些不必要的数据或为家庭网络增加哪些技术漏洞? 我想像控制其他智能设备一样控制这个昂贵的空气净化器。 这标志着这个充满挑战但无疑有趣的旅程的开始。 是时候破解空气净化器了! 😆 顺便说一句,如果您喜欢我的内容,可以 open_in_newBuy Me a Coffee 以支持我的内容创作! warning 免责声明 这篇文章的内容旨在用于教育目的,介绍逆向工程 IoT 智能设备和网络协议的过程。 黑客行为可能是一个可怕的术语,因此我想明确说明我的意图仅仅是升级我购买的智能设备,以与我的智能家居系统集成。 这样做不会影响此产品的任何其他实例或其云服务。 因此,任何敏感的特定于产品的数据(例如私钥、域名或 API 端点)都已从这篇文章中混淆或删除。 摆弄您的设备可能会使任何保修失效,并且存在永久损坏设备的风险; 请自行承担风险。

计划

如果我们要破解此设备以通过自定义软件控制,我们将需要了解其当前功能并计划攻击点,从而需要最少的工作量才能实现我们的目标。 该设备已经支持使用其自身的移动应用程序进行远程控制,但是恼人的是需要一个云帐户才能使用。 通过切换手机的蓝牙、WiFi 和 5G,我可以确认该应用程序需要互联网连接才能控制设备。 无法通过蓝牙或 WiFi 在本地进行远程控制。 这意味着移动应用程序和设备必须连接到云服务器,才能实现远程控制。 因此,在该网络中的某个地方,设备与其云服务器之间的数据必须是风扇速度和应用程序控制的所有其他内容。 因此,这是我们的攻击点:

移动应用程序分析

我研究的第一件事是远程控制移动应用程序。 这可以是一种快速收集一些信息的方法,因为 Android 应用程序可能相对容易拆解。 Android 上的应用程序存储为 .apk 文件。 通过快速在线搜索,您可以找到一个网站来下载特定应用程序的最新 .apk。 如果您不知道,.apk 的格式在技术上是一个 .zip 文件! 您可以简单地提取它们以浏览应用程序的内容。 Android 应用程序包括已编译的 Java 可执行文件,通常命名为 classes.dex。 您可以使用 open_in_newdex2jar 将它们转换为 .jar 文件,并使用 open_in_newjd-gui 浏览内容,作为重构的源代码。 定位到应用程序的 MainActivity.class 后发现它是用 React Native 构建的!

package com.smartdeviceapp;import com.facebook.react.ReactActivity;publicclassMainActivityextendsReactActivity{protected String getMainComponentName(){return"SmartDeviceApp";}}

对于使用 React Native 构建的 Android 应用程序,您可以在 assets/index.android.bundle 中找到 JavaScript 包。 快速扫描应用程序的包后发现它使用了安全的 WebSocket 连接:

self.ws =newWebSocket("wss://smartdeviceapi.---.com");

此 Android 应用程序中没有太多有趣的之处; 正如预期的那样,它与他们的云服务器连接以远程控制智能设备。 由于获得一些可读源代码的简单性,因此值得快速浏览一下。 我们始终可以引用此包,以查看是否可以在其中找到任何共享值或逻辑。

网络检查

接下来,是时候查看设备与其云服务器之间的网络流量了; 这就是我们试图拦截并理想地模拟的。 我在本地使用 Pi-hole,这是一个 DNS 服务器,可以阻止跟踪和一些广告,但它也具有有用的功能,可以按设备浏览 DNS 查询。 通过导航到 Tools > Network 页面并选择设备的本地网络地址,我们可以看到它正在查询 DNS 服务器以获取云服务器域的地址: 现在我们知道了它连接的云服务器的域名,我们可以使用 Local DNS 功能将该网络流量发送到我的本地工作站 (192.168.0.10) 而不是他们的云服务器: 然后,我们可以使用 open_in_newWireshark 来查看来自智能设备的流量。 我们可以通过使用 ip.addr == 192.168.0.61(智能设备地址)的过滤器来监视工作站网络接口来做到这一点。 通过这样做,我能够看到 UDP 数据包从智能设备发送到工作站的端口 41014

数据包分析

因此,我们知道智能设备使用 UDP 与其云服务器通信。 但是现在,它正在尝试与我的工作站通信,并希望它像其云服务器一样做出响应。 我们可以使用一个简单的 UDP 代理,让我们的工作站充当智能设备与其云服务器之间的中继。 我使用 open_in_newCloudflare's DNS resolver (1.1.1.1) 来查找其云服务器的真实 IP 地址(因为我的 Pi-hole DNS 只会解析到我的工作站的本地 IP 地址)。 然后我使用 open_in_newnode-udp-forwarder 作为一种简单的方法来将流量转发到他们的云服务器:

udpforwarder \
--destinationPort 41014--destinationAddress X.X.X.X \
--protocol udp4 --port 41014

X.X.X.X 是其云服务器的真实 IP 地址。 再次查看 Wireshark,我们可以看到智能设备与其云服务器之间的所有网络流量! 启动设备时,它会将一个数据包发送到服务器,其中包含如下数据:

Hex View 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000 55 00 31 02 01 23 45 67 89 AB CD EF FF 00 01 EF U.1..#Eg........
00000010 1E 9C 2C C2 BE FD 0C 33 20 A5 8E D6 EF 4E D9 E3 ..,....3 ....N..
00000020 6B 95 00 8D 1D 11 92 E2 81 CA 4C BD 46 C9 CD 09 k.........L.F...
00000030 0E                         .

然后,服务器将使用以下内容做出响应:

Hex View 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000 55 00 2F 82 01 23 45 67 89 AB CD EF FF 37 34 9A U./..#Eg.....74.
00000010 7E E6 59 7C 5D 0D AF 71 A0 5F FA 88 13 B0 BE 8D ~.Y|]..q._......
00000020 ED A0 AB FA 47 ED 99 9A 06 B9 80 96 95 C0 96   ....G..........

此后的所有数据包似乎都共享类似的结构。 它们不包含任何可读的字符串,但充满了看似随机的数据字节; 这可能是指向加密的 open_in_newAvalanche effect。 我四处搜索以查看此数据包结构是否是现有协议。 我读到一些智能设备使用基于 UDP 的 DTLS。 但是,Wireshark 支持检测 DTLS 数据包,但将此数据包列为 UDP,这意味着它无法从数据中确定基于 UDP 的协议。 我使用 DTLS 规范再次检查,但该规范描述的标头格式与我们在数据包中看到的格式不同,因此我们知道此处未使用 DTLS。 至此,我们遇到了一个阻碍; 我们不了解数据在这些数据包中的格式,这意味着我们还无法操纵或模拟任何内容。 如果它使用有据可查的协议,这将容易得多,但是那有什么乐趣呢?

物理拆卸

我们知道有两个应用程序了解如何读取此数据包数据:智能设备及其云服务器。 并且,我手边没有他们的云服务器,因此是时候查看智能设备内部了! 用一些容易获得的螺丝很容易拆卸。 内部是包含微控制器的主 PCB、连接到风扇的端口和连接到前面板上控制面板的带状电缆。 主控制器标记为 ESP32-WROOM-32D。 此微控制器通常用于智能设备,并具有 WiFi 和蓝牙功能。 我偶然发现了 open_in_newESP32-reversing GitHub 存储库,其中包含一个不错的 ESP32 相关逆向工程资源列表。

串口连接

ESP32 包含一个闪存芯片,该芯片最有可能存储包含应用程序逻辑的固件。 ESP32 的制造商提供了一个名为 open_in_newesptool 的实用程序,用于与 ESP32 中的 ROM 引导加载程序进行通信。 使用此工具,可以从闪存读取数据,但是首先,我们必须建立串行连接! 参考 open_in_newESP32 datasheet,我们可以找到引脚布局图: 在这里,我们可以看到 TXD0(35) 和 RXD0(34) 引脚。 我们需要将电线连接到这两个引脚和一个接地引脚以进行串行连接。 该设备 PCB 有一些引脚孔,这些引脚孔通常连接到用于调试和刷写的引脚; 我能够从视觉上跟踪这两个串行引脚到这些孔的轨迹! 这使我可以轻松地焊接上可以临时插入跳线的断接排针。 否则,我很可能会小心地直接焊接在芯片引脚上。 通过将万用表设置为连续性模式,我可以通过参考 ESP32 上的 GND(38) 引脚来找到哪个孔接地。 现在,我们需要一个端口来处理此 UART 串行通信。 我使用了我的 open_in_newFlipper Zero,它在 GPIO 类别下有一个方便的 USB-UART Bridge 应用程序。 使用 3 根跳线,我将它们连接在一起:

info 提示 TXRX 电线在此处有意交叉; 我们希望将数据传输到另一台设备的接收线! 在 Windows 设备管理器中的 Ports (COM & LPT) 类别下,我找到了 Flipper Zero UART 设备,即 COM7。 使用 open_in_newPutty 配置为 COM7 上的串行连接,速度为 115200,我能够成功连接到 Flipper Zero。 在四处搜索时,我看到此速度通常用于 ESP32,因此我决定在此处使用它。 启动智能设备时,我注意到来自串行输出的一堆日志数据:

rst:0x1(POWERON_RESET),boot:0x13(SPI_FAST_FLASH_BOOT)configsip:0, SPIWP:0xeeclk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00mode:DIO, clock div:2load:0x3fff0030,len:4476ho 0 tail 12 room 4load:0x40078000,len:13512ho 0 tail 12 room 4load:0x40080400,len:3148entry 0x400805f0**********************************  Starting SmartDevice  **********************************This is esp32 chip with2 CPU core(s), WiFi/BT/BLE, silicon revision 1, 4MB external flash
Minimum free heap size:280696bytesnvs_flash_init ret:0Running app from: factory
Mounting FAT filesystem
csize:1122 KiB total drive space.0 KiB available.FAT filesystem mounted
SERIAL GOOD
CapSense Init
Opening[rb]:/spiflash/serial
Serial Number: 0123456789abcdefff
Opening[rb]:/spiflash/dev_key.key
Device key ready
Base64 Public Key:**REDACTED**Opening[rb]:/spiflash/SmartDevice-root-ca.crt
Opening[rb]:/spiflash/SmartDevice-signer-ca.crt
Addtimeout:10000,id:0RELOAD FALSE
Opening[rb]:/spiflash/server_config
MP PARSE DONE
Server: smartdeviceep.---.com:41014

我们可以从此输出中挑选出一些有用的信息:

转储闪存

太棒了,现在我们有了一个有效的串行连接,我们可以专注于转储闪存,希望它包含有关如何读取这些数据包的信息! 要读取闪存,我们需要以不同的模式启动 ESP32,具体来说,就是它所谓的 Download Boot 模式。 这在数据表的 Strapping Pins 部分中进行了技术性解释。 但是 TL;DR,我在启动时将 Flipper Zero 上的 GND 端口的跳线连接到 ESP32 上的 IO0(25) 引脚。 使用 Putty 检查串行输出,我们可以看到这成功地将智能设备启动到 Download Boot 模式:

rst:0x1(POWERON_RESET),boot:0x3(DOWNLOAD_BOOT(UART0/UART1/SDIO_REI_REO_V2))waiting for download

现在我们可以关闭 Putty 并切换到终端以使用 esptool。 我们可以使用以下命令从 ESP32 转储整个 4MB 的闪存数据:

esptool -p COM7-b 115200 read_flash 00x400000 flash.bin

我多次转储闪存以确保我有一个良好的读取,并备份它们,以防我们意外地破坏某些东西,因为这样我们可以将转储文件刷回。 info 提示 为了使用 Flipper Zero 成功读取闪存,我必须更改其配置以指定 115200 的波特率,而不是 Host

闪存分析

我们已将 ESP32 闪存转储到一个二进制文件中,现在我们需要理解它。 我发现 open_in_newesp32knife 是最好的实用程序。 它读取闪存文件并提取一堆有用的信息。 它也是唯一成功地将此转储重新格式化为 ELF 格式并正确映射虚拟内存的实用程序,但稍后会详细介绍! 让我们看看我们能找到什么:

python esp32knife.py --chip=esp32 load_from_file ./flash.bin

这将记录出大量信息并将输出数据保存到 ./parsed 文件夹。 这里第一个感兴趣的文件是 partitions.csv,此表映射闪存中的数据区域:

# ESP-IDF Partition Table
# Name,  Type, SubType, Offset,  Size, Flags
nvs,   data, nvs,0x9000,  16K,otadata, data, ota,0xd000,  8K,phy_init, data, phy,0xf000,  4K,factory, app, factory,0x10000, 768K,ota_0,  app, ota_0,0xd0000, 768K,ota_1,  app, ota_1,0x190000, 768K,storage, data, fat,0x250000, 1M,

在这里,我们可以看到几个有趣的条目:

📌 更新 其他读者提到,如果设备启用了闪存加密(在这种情况下没有启用),则可以保护此闪存转储。

设备存储

我最初很好奇要看看 nvs 键值存储分区中有什么数据。 此数据的最新状态已提取到 part.0.nvs.cvs,我能看到的唯一有趣的数据是我的 WiFi SSID 和密码。 但我还发现了 part.0.nvs.txt 中值的完整历史更改日志,该日志揭示了几个以前使用的 WiFi 凭据; 什么**!?** 之前有人用过这东西吗?😆 之后,是时候查看 FAT storage 分区的内容了。 我发现 open_in_newOSFMount 是一个很棒的 Windows 应用程序; 它将文件系统映像挂载为虚拟磁盘,并允许对其进行写入! 这揭示了一些我们之前从串行输出中看到的一些有趣的文件:

dev_info
dev_key.key
serial
server_config
SmartDevice-root-ca.crt
SmartDevice-signer-ca.crt
wifi_config

我检查了这些文件的内容,发现:

dev_key.key 文件以 -----BEGIN EC PRIVATE KEY----- 开头,这是一个椭圆曲线私钥; 我使用 open_in_newopenssl 对此进行了验证:

openssl ec -in dev_key.key -text -noout

并且这两个 .crt 文件以 -----BEGIN CERTIFICATE----- 开头,我也使用 openssl 进行了验证:

openssl x509 -in./SmartDevice-root-ca.crt -text -noout
openssl x509 -in./SmartDevice-signer-ca.crt -text -noout

设备上存储的证书和设备密钥强烈表明它们用于加密 UDP 网络数据包数据。

初始静态分析

现在我们已经查看了存储,是时候查看设备上运行的应用程序了。 我们知道它正在运行 factory 分区,因此我在 [![](https://jmswrnr.com/api/favicon/eyJhbGciOiJIUzI1NiJ9.eyJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2dpdGh1Yi