Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CMF-Watch-Pro-2-BLE-Protocol — 逆向工程CMF Watch Pro 2的BLE协议,记录了GATT布局、AES-128-CBC加密命令帧、认证握手以及健康数据同步,用于开发替代配套应用。 | Kitploit
工具/GitHubGitHub/joshuapassos/cmf-watch-pro-2-ble-protocol
嵌入式系统安全蓝牙安全物联网安全逆向工程无线安全密码学移动安全硬件与物联网安全固件分析

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享
GitHubjoshuapassos/cmf-watch-pro-2-ble-protocol

CMF-Watch-Pro-2-BLE-Protocol

逆向工程CMF Watch Pro 2的BLE协议,记录了GATT布局、AES-128-CBC加密命令帧、认证握手以及健康数据同步,用于开发替代配套应用。

查看仓库网站
3422个月前尚未审核

CMF Watch Pro 2 — BLE 协议(逆向工程)

非官方。 本文档描述了 CMF Watch Pro 2(CMF by Nothing)的蓝牙低功耗(BLE)协议, 通过逆向工程重建,用于替代配套应用。它与 Nothing/CMF 无关联,也未获其认可。使用风险自负。

帧头和操作码中的所有多字节整数均为大端序。 命令载荷内部的整数除非另有说明,否则为小端序 (这与设备固件一致)——请注意例外情况(GOALS_SET、GPS_PUSH、批量传输的偏移量/长度为 大端序)。

置信度标记

以下每项非显而易见的声明都标注了其确定方式:

  • ✅ 已在设备上验证 — 在解密后的实时捕获中观察到,或已针对真实手表进行测试。
  • 🔎 来自固件 / APK 逆向 — 通过反编译固件(1.0.0.73)或官方 APK(3.5.7)提取; 与代码一致,但未经运行时测试。
  • 🟡 部分证实 — 结构上已确定(离线、语料库范围或通过逆向),但剩余步骤需要手表, 尚未执行。
  • ⚠️ [不确定] — 推断而来,未经确认;可能有误。

如果后面的章节纠正了前面的内容,前面的文本会保留并附上指引,而不是删除——了解哪些解读 已被尝试并推翻,可以为下一个人省去同样的弯路。

所有捕获的测试设备:CMF Watch Pro 2-5485,固件 1.0.0.73,序列号 CI04102520008192, MCU Actions ATS3089C(Cortex-M4),屏幕 466×360。


1. GATT 布局

手机是 GATT 客户端;手表是外设,广播名为 CMF Watch Pro 2-XXXX(4 个十六进制字符)。

用途服务特征属性
命令 写入0000fff0-0000-1000-8000-00805f9b34fb0000fff2-…写入
命令 通知0000fff0-…0000fff1-…通知
Shell 写入(AT)—77d4ff01-2fe2-2334-0d35-9ccd078f529c写入
Shell 通知(AT)—77d4ff02-…通知
批量 数据写入—02f00000-0000-0000-0000-00000000ffe1写入
批量 数据通知—02f00000-…ffe2通知

通过向每个 CCCD(00002902-…)写入 01 00 来启用通知。命令通道(fff1/fff2) 承载下述帧协议。Shell 通道(77d4…)承载纯 AT 风格文本(例如 AT GETSECRET;见 §14)。 数据通道(02f0…)承载大型二进制数据块(表盘、固件、AGPS),由命令通道上的控制操作码协调。

服务 UUID — 手表广播约 10 个主要服务。在真实设备上枚举得到: 0xfff0(命令)、0x180f(电池)、0x180a(设备信息)、0xefe7、0xffd0、 02f00000-…ffe0 和 02f00000-…fe00(数据)、77d4e67c-2fe2-2334-0d35-9ccd078f529c (Shell / 配对)、e49a3001-f69a-11e8-8eb2-f2801f1b9fd1、f48a23c0-f69a-11e8-8eb2-f2801f1b9fd1。

⚠️ Shell 服务 UUID 是 77d4e67c-…,而非 77d4ff00-…。本文档的早期修订版 假定该服务与其特征(77d4ff01/77d4ff02,§14)共享 ff00 前缀——事实并非如此, 至少在本次检查的设备上是这样(发现来自 freethinkel/fmc, 见 §来源)。特征 UUID 未变。尚未验证 77d4e67c 在不同设备间是否稳定——请枚举而非硬编码。

🌐 Web Bluetooth 提示。 Chromium 只会发现页面在 optionalServices 中列出的服务, 即使是对未过滤的 getPrimaryServices() 调用也是如此——列出 3 个服务的页面只能看到 3 个, 而 chrome://bluetooth-internals(Chrome 自身的 C++ 层,无作用域限制)会显示全部 10 个。 如果你编写浏览器客户端,请提前列出上述每一个 UUID,否则配对会因明明存在的服务而失败。 Firefox/Safari 不支持 Web Bluetooth;需要用户手势 + HTTPS/localhost。

✅ 整个真实会话仅在单一命令通道上运行——在一次 160 秒的高强度使用捕获中,除显式的 OTA/表盘传输外,数据/固件或 Shell 通道上没有流量。


2. 帧格式(0xF5)

每条命令通道消息都被封装在一个或多个 11 字节头的帧中:``` +------+-----------+--------+-------------+-------------+--------+-------------------+ | 0xF5 | chunkLen | cmd1 | chunkCount | chunkIndex | cmd2 | chunk bytes … | | 1 B | 2 B (BE) | 2 B BE | 2 B BE | 2 B BE | 2 B BE | chunkLen bytes | +------+-----------+--------+-------------+-------------+--------+-------------------+ __________________________ 11-byte header ____________________________/

- `cmd1`/`cmd2` 共同构成**操作码**(见 §6)。🔎 已对照官方应用的数据帧构建器(`C6117b.m30831g`)确认。
- `chunkCount` = 该命令的总分块数;`chunkIndex` 从 **1** 开始计数。
- `chunkLen` = 本帧中 `chunk` 的字节数。
- 单次 BLE 写入可能因链路 MTU 而被分片;接收端会缓冲原始字节并重新提取完整帧。大载荷会被拆分为多个分块(`cmd1/cmd2` 相同,`chunkIndex` 递增)并按顺序重组。

### 操作码约定(✅ 已在线上确认)

- `cmd1 = 0xFFFF`:`cmd2` 为 `0x80xx`/`0x90xx` 时 = 手机→手表(请求/设置);为 `0x00xx`/`0xa0xx` 时 = 手表→手机(应答)。配对通过低字节匹配(`0x9055`↔`0xa055`,`0x8051`↔`0x0051`)。
- 特定功能的 `cmd1`:`cmd2` 后缀 = `0x0001` **SET**(设置)、`0x0002` **GET**(获取)、`0x0003` **ACK**(确认)。

### 分块主体

每个分块的主体为 `payloadPiece ‖ CRC32_LE(payloadPiece)`(4 字节 CRC,小端序,zlib/IEEE)。如果命令是**加密**的(见 §3),则整个 `payloadPiece ‖ CRC` 会再经过 AES-128-CBC/PKCS7 加密,该密文即成为帧的 `chunk`。

**明文怪癖:** 对于明文操作码,手表在 `chunkLen` 中*计入* 4 字节 CRC,但**不会**传输它。因此解码明文帧时,实际数据长度为 `chunkLen − 4`。(加密帧照常将 CRC 包含在密文内部。)

分块大小(使加密分块落在 AES 块边界上),其中 `maxWrite = mtu − 3`:
- 加密:`floor((maxWrite − 11) / 16) * 16 − 4 − 1`
- 明文:`maxWrite − 11 − 4 − 1`

✅ 所有观察到的加密帧 `chunkLen` 值均为 16 的倍数(块对齐成立)。

---

## 3. 加密原语

- **AES-128-CBC**,采用 **PKCS7** 填充和**固定 IV**(来自固件 `CmfCharacteristic.AES_IV`):
  `50 51 52 53 54 55 56 57 60 61 62 63 64 65 66 5A`。
- **CRC32**(zlib/IEEE),以 4 个字节的小端序输出。
- **SHA-256**,作用于各部分的拼接结果。

密钥派生:```
authkey      = SHA256( rnd1 ‖ rnd2 ‖ secret )[0..16]      // persisted across sessions
sessionKey   = SHA256( nonce ‖ authkey )[0..16]           // per connection
  • secret = 16 字节设备密钥(可通过 shell 命令从手表获取 AT GETSECRET → GETSECRET:<32-hex>,OK)。
  • rnd1 = 手机选择的 16 个随机字节;rnd2 = 来自手表的 16 个随机字节。
  • nonce = 来自手表 nonce 回复的字节。

密钥设置后,除 §5 中列出的明文操作码外,所有命令通道帧均经过 AES 加密。

✅ 两种派生方式均已验证:从已 root 手机的 ntwatch.db 中恢复的 authkey 与从捕获的 rnd1/rnd2/secret 派生的值匹配;从捕获的 nonce 复现的 sessionKey 可解密实时帧。


4. 认证 / 配对握手

两条入口路径共享相同的 nonce/confirm 尾部。

4.1 首次配对(拥有设备密钥)```

phone → (shell) AT GETSECRET watch → (shell) GETSECRET:<32hex>,OK phone: rnd1 = random16 ; signed1 = SHA256(rnd1 ‖ secret) phone → AUTH_PAIR_REQUEST (plaintext) payload = rnd1(16) ‖ signed1(32) // 48 B watch → AUTH_PAIR_REPLY (plaintext) payload = rnd2(16) ‖ signed2(32) // 48 B phone verifies signed2 == SHA256(rnd2 ‖ secret) phone: authkey = SHA256(rnd1 ‖ rnd2 ‖ secret)[0..16] → set crypto key = authkey phone → AUTH_PHONE_NAME (encrypted) payload = 0xA5 ‖ model(UTF-8) // e.g. "CMF Watch Pro 2" watch → AUTH_WATCH_MAC (encrypted) phone → AUTH_NONCE_REQUEST (encrypted) payload = 0xA5 watch → AUTH_NONCE_REPLY (encrypted) payload = nonce phone: sessionKey = SHA256(nonce ‖ authkey)[0..16] → set crypto key = sessionKey phone → AUTHENTICATED_CONFIRM_REQUEST (encrypted) payload = 0xA5 watch → AUTHENTICATED_CONFIRM_REPLY (encrypted) → state = Initialized

在 `AUTH_FAILED (0xFFFF,0xA061)` 或签名不匹配时,身份验证失败。

### 4.2 重新连接(authkey 已知)```
        set crypto key = authkey (persisted)
phone → AUTH_PHONE_NAME      (encrypted)  payload = 0xA5 ‖ model
watch → AUTH_WATCH_MAC       (encrypted)
phone → AUTH_NONCE_REQUEST   (encrypted)  payload = 0xA5
watch → AUTH_NONCE_REPLY     (encrypted)  payload = nonce
        sessionKey = SHA256(nonce ‖ authkey)[0..16]   → set crypto key = sessionKey
phone → AUTHENTICATED_CONFIRM_REQUEST (encrypted)  payload = 0xA5
watch → AUTHENTICATED_CONFIRM_REPLY   (encrypted)  → Initialized

✅ 重连顺序(无 shell 流量)在真实抓包中观察完整。

4.3 认证后初始化(阶段 2)

⚠️→✅ 数据查询前必须先发送 TIME。 在 Initialized 之后,手表在会话中收到 TIME (FFFF 8004) 之前不会响应 BATTERY、SERIAL_NUMBER_GET 或 ACTIVITY_FETCH_* 握手——没有它, 只会收到一条主动上报的 FIRMWARE_VERSION_RET,其余全部超时。✅ 已在 Pixel 8a 上实测确认: 不发送 TIME 直接发送三个 GET → 仅固件回复;先发送 TIME → 电量 和序列号开始回复。

推荐的阶段 2 顺序:TIME → FIRMWARE_VERSION_GET → SERIAL_NUMBER_GET → BATTERY (0xA5) → 配置推送 → 健康同步(§8)。

4.4 GET → SET 回显模式(✅)

大多数设置没有独立的“读取”操作码。发送 *_GET(cmd2 = 0x0002,载荷 0xA5)会使手表以 SET 操作码回复(cmd2 = 0x0001)并携带当前值。 SET 命令以 cmd2 = 0x0003 和空主体确认。


5. 明文与加密

一旦设置密钥,帧即进行 AES 加密,但以下操作码除外,它们始终为明文:

  • AUTH_PAIR_REQUEST (FFFF 8047)、AUTH_PAIR_REPLY (FFFF 0048)
  • DATA_CHUNK_WRITE_WATCHFACE (FFFF 9064)、DATA_CHUNK_WRITE_FIRMWARE (FFFF 9042)、 DATA_CHUNK_WRITE_AGPS (FFFF 905F)

帧头(cmd1/cmd2)始终明文传输,因此即使没有密钥,任何抓包中命令序列也可见——只有加密的载荷需要 sessionKey。


6. 操作码参考 (cmd1, cmd2)

GET/SET/REQUEST = 手机→手表;RET/REPLY/ACK/RESPONSE/DATA = 手表→手机。

会话 / 设备

名称cmd1,cmd2
TIMEFFFF 8004
FIRMWARE_VERSION_GET / _RETFFFF 8006 / FFFF 0006
SERIAL_NUMBER_GET / _RET00DE 0002 / 00DE 0001
BATTERY005C 0001
TRIGGER_SYNC005C 0002
USER_INFO_SET / _RET 🔎✅0095 0001 / 0095 0003
FACTORY_RESET009A 0001
DEVICE_REBOOT 🔎FFFF 9080
RESOLUTION_GET 🔎 (→ 466×360)FFFF 907F
GPS_PUSH / _RETFFFF 906A / FFFF A06A
UNBIND_SET / _RETFFFF 907A / FFFF A07A

认证

名称cmd1,cmd2
AUTH_PHONE_NAMEFFFF 8049
AUTH_WATCH_MACFFFF 0049
AUTH_PAIR_REQUEST / _REPLYFFFF 8047 / FFFF 0048
AUTH_NONCE_REQUEST / _REPLYFFFF 804B / FFFF 004C
AUTHENTICATED_CONFIRM_REQUEST / _REPLYFFFF 804D / FFFF 0004
AUTH_FAILEDFFFF A061
下载工具