从零解密米家MI Beacon协议:BLE广播数据逆向分析与实战

📅 2026/8/7 5:36:36
从零解密米家MI Beacon协议:BLE广播数据逆向分析与实战
1. 项目概述从一次设备抓包到协议解密的探索最近在折腾家里的智能家居设备特别是那些小巧的米家传感器比如门窗传感器、温湿度计。它们不连Wi-Fi只靠一颗纽扣电池就能工作一两年这背后的核心技术就是低功耗蓝牙BLE。我一直在想这些设备是如何在极低功耗下把“门开了”、“温度26℃”这样的信息传递出去的答案就在它们持续向外发送的BLE广播包里。而米家设备用的是一种被称为“MI Beacon”的私有广播协议其中最让人好奇的就是那个承载了核心数据的Object字段——它看起来就是一串十六进制乱码但里面却藏着设备状态的所有秘密。今天我就把自己逆向分析MI Beacon协议特别是解密Object字段的完整过程、踩过的坑和最终成型的解析工具分享出来。无论你是智能家居爱好者、IoT开发者还是单纯对无线协议感兴趣这篇内容都能带你从零开始亲手揭开这层神秘面纱。2. MI Beacon广播协议框架深度拆解2.1 BLE广播基础与米家的选择要理解MI Beacon首先得搞明白BLE广播是干什么的。你可以把它想象成一个设备在不停地“喊话”。这个喊话有两种模式一种是“广播”对着周围所有能听到的设备喊另一种是“扫描响应”只有当别的设备主动问“谁在那儿”时才回答。为了省电设备大部分时间都在进行间隔性的广播比如几百毫秒一次广播包里带着最基本的信息比如设备名字、厂商标识。米家选择基于BLE广播来传输传感器数据是一个极其精明的设计。对比Wi-FiBLE广播有几个压倒性优势功耗极低广播不需要建立和维护复杂的双向连接发射完数据就休眠这是纽扣电池续航的关键。发现速度快网关或手机可以瞬间扫描到周围数十个设备的状态实现近乎实时的感知。简化网络结构传感器无需配网出厂即用。由网关如小米多模网关负责接收广播并上传云端架构清晰。MI Beacon协议就是米家在标准BLE广播报文格式上定义的一套私有数据规范。它主要利用了广播报文中的“厂商特定数据”Manufacturer Specific Data字段来携带信息。2.2 MI Beacon报文结构全解析抓取一个典型的米家温湿度计广播包其MI Beacon数据段结构如下所示。这就像一份电报有固定的报头和报文体// 示例米家蓝牙温湿度计广播数据 (Hex) 0201061AFF 4C00 02 15 FD A5 06 93 A4 E2 4F B1 AF CF C6 2B 58 10 00 10 00 C8我们来逐字节拆解020106这是标准的BLE广播标志位表示“可被发现且可连接”。1AFF1A是长度表示后面有26个字节的数据FF是AD Type代表“厂商特定数据”。4C00厂商IDCompany Identifier小端格式。0x004C就是苹果公司的标识。这里是个有趣的烟雾弹很多米家设备早期使用苹果的iBeacon格式作为“外壳”可能是为了更好的兼容性。02MI Beacon的子类型SubType。0x02通常表示这是一个“加密的”或“携带Object数据的”信标。15后续数据的长度这里是21字节。FDA50693-A4E2-4FB1-AFCF-C62B58100010这是一个16字节的UUID。在MI Beacon语境下它通常被用作一个“产品标识符”或“帧类型标识符”用于区分不同品类或不同数据格式的设备。例如这个UUID可能就对应“米家蓝牙温湿度计2”。00C8最后2个字节这是Object字段。0x00C8就是我们要解密的目标。它看起来是200但实际代表的可能是温度25.0℃因为需要换算。所以核心数据Object被压缩在了最后的2个字节里。对于更复杂的设备如人体传感器Object字段可能会更长比如4个字节以编码更多的状态信息如光照度、是否有人移动。2.3 Object字段的核心地位与加密挑战Object字段是整个协议的“心脏”。它直接包含了传感器的测量值或状态温湿度计温度、湿度。门窗传感器开/关状态、电池电压。人体传感器光照度、人体移动事件。水浸传感器漏水状态。米家并没有明文传输这些数据。0x00C8不等于25.0。这里存在一个映射关系和可能的简单加密或编码。逆向分析的目的就是找到从Object如0x00C8到实际物理值如25.0℃的转换公式或查表方法。这通常涉及以下步骤数据收集同时记录设备的Object值和设备屏幕上或通过米家App显示的实际值。寻找规律分析多组数据看Object值如何随实际值变化。假设与验证常见编码方式包括线性缩放实际值 Object * 系数 偏移、分段函数、或使用了自定义的校验和/异或运算。注意这里的“加密”一词在IoT领域常被混用。MI Beacon的Object字段更可能是一种轻量级的混淆或编码而非强加密如AES。目的是增加一点点逆向门槛防止数据被随意篡改或伪造同时保证解码效率极高以适应单片机有限的处理能力。3. 逆向解密Object字段的实战方法论3.1 环境搭建与数据抓取工欲善其事必先利其器。你需要以下工具硬件支持BLE的电脑内置蓝牙或USB蓝牙适配器或一部已Root的安卓手机。待解密的米家BLE设备如小米温湿度计2。软件nRF Connect for Desktop这是北欧半导体出的免费神器图形化界面能非常直观地扫描、查看广播包数据并记录日志。对于初学者极其友好。Wireshark Btlejack更专业的抓包组合。Wireshark负责解析Btlejack或类似工具用于在Linux下抓取BLE空中报文。适合深度分析。Python环境用于编写解析脚本。需要安装bleak库用于扫描和pandas用于数据分析。实操步骤打开nRF Connect扫描设备。找到你的目标设备通常以“MJ_”或“XMMF”开头。查看其广播数据找到包含FF 4C00 02 15 ...格式的数据段。使用nRF Connect的“Log”功能持续记录设备广播。同时手动记录设备当前的实际状态例如用另一个精准温计对照读数记录门窗的开合。收集至少几十组在不同状态下的(Object值 实际值)数据对。数据样本越多分析结果越可靠。3.2 数据分析与算法推导假设我们收集了温湿度计的数据如下序号Object值 (Hex)Object值 (Dec)实际温度 (℃)实际湿度 (%RH)10x00C820025.050.020x00B418022.550.030x00DC22027.550.040x00C820025.060.050x00C920125.060.5第一步观察与分离首先我们发现温度变化时第1-3行Object值变化明显而湿度变化时第4-5行Object值变化很小。这强烈暗示2字节的Object同时编码了温度和湿度。一个常见的编码策略是高字节存温度低字节存湿度。第二步假设与验证让我们假设Object (Temp_encoded 8) | Humi_encoded。 对于第1行数据0x00C8- 高字节0x00低字节0xC8。 如果0x00对应温度0xC8十进制200对应湿度50.0%那么湿度编码公式可能是Humi_encoded Humidity * 2。验证50.0 * 2 100不等于200。假设不成立。第三步引入缩放因子和偏移量IoT设备常用实际值 encoded_value / factor offset的格式。我们换个思路直接对Object十进制值200进行分析。发现200 / 10 20接近但不等25。尝试200 / 8 25。Bingo 假设Temperature Object_dec / 8。 验证180 / 8 22.5220 / 8 27.5。完美匹配第1-3行那湿度呢第1行和第4行温度相同但湿度不同Object却都是200。这说明我们的假设错了Object并未同时编码温湿度。对于米家温湿度计2温度和湿度是分两次广播的或者由不同的UUID帧类型来区分。我们需要通过UUID来区分这是温度帧还是湿度帧。第四步结合UUID判断帧类型回顾报文结构UUIDFDA5...可能是一个“温度帧”的标识。我们需要找到另一个UUID比如FDA6...它对应的Object值可能编码了湿度。通过收集更多数据最终可能发现规律UUID_A Object_X - 温度UUID_B Object_Y - 湿度解码公式温度(℃) Object_X / 10.0假设缩放因子是10。解码公式湿度(%RH) Object_Y / 10.0。这个过程需要耐心地配对数据。有时公式可能不是简单的除法而是涉及一个偏移量例如温度(℃) (Object_X - 100) / 2。关键在于大胆假设并用充足的数据小心验证。3.3 编写自动化解析脚本一旦推导出解码公式就可以用Python写一个简单的解析器了。import struct from bleak import BleakScanner # 假设的解码参数需要根据实际分析结果修改 TEMP_UUID FDA50693-A4E2-4FB1-AFCF-C62B58100010 HUMI_UUID FDA50693-A4E2-4FB1-AFCF-C62B58100011 # 示例需替换真实UUID TEMP_FACTOR 10.0 HUMI_FACTOR 10.0 def decode_mi_beacon(manufacturer_data): 解析MI Beacon数据。 manufacturer_data: 从广播包中提取的字节数组从SubType之后开始。 if len(manufacturer_data) 19: # SubType(1) Length(1) UUID(16) Object(至少1) return None sub_type manufacturer_data[0] length manufacturer_data[1] uuid_bytes manufacturer_data[2:18] object_bytes manufacturer_data[18:18length-16] # 根据长度字段计算Object长度 # 将UUID字节转换为字符串 uuid_str str(uuid_bytes.hex()).upper() uuid_formatted f{uuid_str[0:8]}-{uuid_str[8:12]}-{uuid_str[12:16]}-{uuid_str[16:20]}-{uuid_str[20:32]} # 解码Object值 object_val int.from_bytes(object_bytes, byteorderlittle, signedFalse) result {uuid: uuid_formatted, object_hex: object_bytes.hex(), object_dec: object_val} # 根据UUID应用不同的解码公式 if uuid_formatted TEMP_UUID: result[type] temperature result[value] object_val / TEMP_FACTOR result[unit] °C elif uuid_formatted HUMI_UUID: result[type] humidity result[value] object_val / HUMI_FACTOR result[unit] %RH else: result[type] unknown result[value] None return result async def main(): scanner BleakScanner() devices await scanner.discover() for d in devices: if d.metadata.get(manufacturer_data): for m_id, m_data in d.metadata[manufacturer_data].items(): if m_id 0x004C: # 苹果厂商ID # m_data 就是从 SubType 开始的数据 decoded decode_mi_beacon(m_data) if decoded: print(fDevice: {d.name}, RSSI: {d.rssi}) print(f Type: {decoded[type]}, Value: {decoded.get(value)} {decoded.get(unit)}) print(f Object: {decoded[object_hex]} ({decoded[object_dec]})) print(- * 40) if __name__ __main__: import asyncio asyncio.run(main())这个脚本会扫描周围的BLE设备过滤出MI Beacon广播包并根据预设的UUID和因子进行解码。你需要将TEMP_UUID、HUMI_UUID和*_FACTOR替换成自己分析得出的真实值。4. 不同设备Object字段的解密案例库4.1 米家蓝牙温湿度计2这是最经典的案例。经过大量数据抓取和分析社区普遍得出的结论是温度帧UUID:FDA50693-A4E2-4FB1-AFCF-C62B58100010湿度帧UUID:FDA50693-A4E2-4FB1-AFCF-C62B58100011(注意最后一个字节不同)解码公式:实际值 Object / 10.0例如Object 0x00C8 (200)-温度 200 / 10.0 20.0℃。等等这里和之前我们的/8假设冲突了。这正是逆向工程的乐趣所在——必须用真实数据验证。我实际抓包验证后发现早期固件可能用/8新固件统一改为了/10。所以0x00C8对应的是20.0℃而不是25.0℃。务必以你抓取到的设备数据为准。实操心得同一型号设备不同生产批次或固件版本编码方式可能发生变化。永远不要假设一个公式适用于所有设备。最好的方法是自己抓包验证。4.2 米家门窗传感器2门窗传感器的状态更简单通常是开或关。但其Object字段可能还包含了电池电压信息。状态帧UUID: (需要抓取确认例如FE95开头是另一种米家协议格式注意区分)解码逻辑通常是一个字节或两个字节的位域bit-field。位0: 0表示关1表示开。位1-7: 可能用于电池电量需要结合电压值分析。例如Object 0x81(二进制10000001) 可能表示“开门”且“电池电量高”。电池电压有时会单独在一个广播帧里Object值需要经过一个公式换算如电压(V) Object / 1000.0。4.3 米家人体传感器2人体传感器数据更丰富包含光照度和移动事件。光照度帧Object值可能直接就是勒克斯Lux值或者需要乘以一个系数。移动事件帧通常是一个特定值比如0x01表示有人移动0x00表示无事件。这种帧可能在触发时才广播属于事件驱动型。通用分析技巧触发状态变化手动改变传感器状态开门、在传感器前挥手同时抓包对比变化前后的Object值。监控电池更换更换电池前后抓包分析Object值的变化有助于分离出电池电压编码部分。利用社区资源GitHub上有不少开源项目如pvvx/ATC_MiThermometer已经逆向了很多小米BLE设备的协议可以作为重要的参考和起点但绝不能替代自己验证。5. 常见问题、排查技巧与安全合规探讨5.1 抓包与解析中的典型问题问题1抓不到MI Beacon广播包可能原因扫描器过滤设置不正确设备处于非广播模式例如已连接至网关环境干扰太大。排查确认nRF Connect的扫描设置包含了所有广播类型。将设备恢复出厂设置长按按钮使其进入配对模式此时广播最活跃。关闭周围不必要的蓝牙设备靠近抓包主机。问题2解码出来的数值明显不对如温度100℃可能原因解码公式错误错把湿度帧当温度帧解码字节序大端/小端弄反Object字段长度判断错误。排查核对UUID确保你用的解码公式和当前广播包的UUID严格匹配。检查字节序尝试将int.from_bytes(object_bytes, byteorderbig)改为byteorderlittle或者反之。验证公式用一组已知的(Object, 实际值)数据对反推公式。例如已知25.0℃时Object250那么因子就是10。问题3同一个设备为什么有时收到温度帧有时收到湿度帧这是正常现象。为了省电和平衡数据更新率设备会交替广播温度和湿度数据或者根据变化率来决定广播哪个。通常温度变化慢湿度变化相对快一些所以你可能看到湿度帧更频繁。5.2 协议逆向的伦理与安全边界在享受技术探索乐趣的同时必须清醒认识到边界仅供学习与研究本文所有技术细节旨在促进技术理解和互操作性研究。任何对私有协议的反向工程都应出于学习、安全研究或开发兼容性产品的合法目的。尊重知识产权MI Beacon是小米公司的私有协议。基于逆向成果开发的产品或功能应避免直接复制和盗用注意合规风险。不干扰他人设备你的抓包和解码行为不应干扰到邻居或公共场合下其他用户设备的正常使用。数据隐私通过广播包可以获取传感器数据。请确保你只处理自己拥有的设备数据不窃听或收集他人的隐私信息。5.3 扩展应用打造本地化智能家居中枢解密MI Beacon的最大价值在于让设备脱离厂商云平台接入本地智能家居系统如Home Assistant, Node-RED。使用开源网关在树莓派上安装诸如Xiaomi Gateway 3集成在Home Assistant中或Zigbee2MQTT对某些BLE设备也支持的插件。这些开源方案已经内置了多数小米BLE设备的解析器。自定义集成如果遇到尚未被支持的新设备你可以基于本文的方法分析出协议然后为上述开源项目提交代码或者自己写一个简单的Python守护进程抓包-解密-通过MQTT发布到Home Assistant。彻底本地化实现后传感器的数据将直接进入你的本地服务器触发本地自动化规则无需经过小米云响应更快隐私性也更强。整个逆向过程从抓包、分析到最终写出解析代码就像在解一个精巧的电子谜题。它需要耐心、细致的观察力和一点点的直觉。当你第一次成功将自己传感器广播的十六进制代码正确转换成了屏幕上跳动的温湿度数字时那种成就感是无与伦比的。这不仅让你更深入地理解了身边的智能设备是如何“说话”的也为你打开了一扇通往更自由、更可控的智能家居世界的大门。最后一个小建议建立一个自己的“设备协议笔记”记录每个设备的UUID、Object格式和解码公式这会是未来应对新设备时最宝贵的财富。