- 应用名:Da Rings
- 包名:
com.moyoung.ring - 版本:1.3.0 (versionCode 33, targetSdk 35)
- 硬件厂商:Moyoung(冒蓝)智能戒指
- BLE 协议栈:
com.crrepa.ble(CRREPA BLE,国产手环/戒指通用库)+ Goodix(汇顶)OTA 模块 - APK 来源:从三星 SM-S9360 设备
base.apk直接拉取,jadx 反编译
Da Rings 与戒指之间是 经典 GATT + 自定义应用层帧协议,无中转云。要点:
| 项 | 结论 |
|---|---|
| 物理通道 | BLE 4.0+,GATT |
| 应用层帧头 | FD DA(与 GATT 服务名 …fdda… 呼应) |
| 帧长度字段 | 单字节,最大 255 B/帧 |
| 分片 | 大帧按协商 MTU 自动分片发送 |
| MTU | 连接后主动请求 512 |
| 校验 | 无应用层 CRC/校验和,完整性靠 BLE 链路层 CRC |
| 加密 | 无应用层加密(明文载荷) |
| 写类型 | 默认 WRITE_TYPE_NO_RESPONSE(writeWithoutResponse),更快 |
⚠️ 由于无加密、无应用层校验,任何能建立 GATT 连接的设备均可解析/伪造帧。绑定仅靠 MAC/用户态逻辑,非密码学绑定。
服务发现逻辑在 p185q1.C7224a,按 UUID 子串匹配。戒指暴露以下 GATT 服务/特征值:
主数据通道服务,UUID 形如 …fdda…(设备实际广播的完整 128-bit UUID 末段含 fdda)。
| 特征值 (UUID 含) | 方向 | 用途 | 代码引用 |
|---|---|---|---|
| fdd1 | Notify(收) | 主数据通道:戒指→App 业务数据(实时步数、历史数据、测量结果等) | m16663F → m16590B0 / m16637k1 |
| fdd2 | Write(发) | 主命令通道:App→戒指 默认写特征值 | C6886g.m16733o 默认分支 |
| fdd3 | Notify(收) | 辅助通知通道(与 fdd1 一起开 notify) | m18544c 列表第 1 项 |
| fdd5 | Write(发) | OTA/大数据写通道(命令字 0xF1) |
m18545d |
| fdd6 | Write(发) | OTA/大数据写通道(命令字 0xF2) |
m18548g |
设备识别:服务里必须同时存在
fdd1、fdd2、fdd3才被判定为 Moyoung 设备(C7224a.m18550i→ "isMoYoung")。
| 服务 UUID | 特征值 | 用途 |
|---|---|---|
0x180F(Battery) |
2A19 |
电量百分比(开 notify) |
0x180D(Heart Rate) |
2A37 |
标准心率测量(开 notify) |
0x180A(Device Information) |
2A28 软件版本 / 2A29 厂商名 / 2A24 |
设备信息(只读) |
启用 notify 的特征值集合:[fdd3, 2A19(电量), fdd1, 2A37(心率)](C7224a.m18544c)。CCCID 为标准 00002902-0000-1000-8000-00805f9b34fb。
注:代码里另有
a6ed0402 / a6ed0403两个 UUID(p002a1.InterfaceC0010a),但仅用于 Goodix 固件恢复(Restore)流程,普通业务通信不经过它们。
BluetoothGattCallback 实现在 p095h1.C5961a。状态机:
connectGatt()
│
▼
onConnectionStateChange(STATE_CONNECTED=2)
│ C7225b.m18553b(gatt) → 延迟 500ms 调 discoverServices()
▼
onServicesDiscovered()
│ C7224a 解析服务 → isMoYoung 校验
│ C6887h 对 [fdd3, 2A19, fdd1, 2A37] 逐个开启 notify(写 CCCD 0x0001)
▼
onDescriptorWrite() (每个 notify 使能回调)
│ 全部使能后 → gatt.requestMtu(512)
▼
onMtuChanged(512)
│ 设置内部 MTU=512、分片大小;状态 → STATE_CONNECTED(2),业务就绪
▼
业务收发
- MTU 强制 512:
BluetoothGatt.requestMtu(512)。后续单次写入按AbstractC5654k.m13162a()(= 协商后的有效负载大小)分片。 - 写类型:满足条件时
setWriteType(1)(WRITE_TYPE_NO_RESPONSE),即 fire-and-forget;但仍走onCharacteristicWrite驱动下一片。
组帧器:p052d1.AbstractC5654k.m13164c(type, feature, payload)。发送侧证实帧布局如下(接收侧 p155n1.C6880a.m16637k1 同步解析,完全一致):
偏移 字节 含义
────────────────────────────────────────────────────
[0] 0xFD 同步字节 1 (signed -3)
[1] 0xDA 同步字节 2 (signed -38) ← "FDDA"
[2] 0x10 标志/版本字节,固定 16 (其 bit0 兼作长度高位,恒为 0)
[3] N 总长度 (低 8 位),= payload 长度 + 6
[4] type 命令字 (command)
[5] feature 子命令字 (feature/sub-cmd)
[6..] payload 载荷(可选,长度 = N - 6)
- 总长度 N = payload.length + 6,写在
[3]。[2]固定0x10。 - 接收侧解码:
length = (((b[2] & 0x01) << 8) | (b[3] & 0xFF))。因b[2] & 1 == 0,实际有效长度 =b[3],单帧上限 255 字节。 - 重组:接收侧以同步字
FD DA起始识别一帧,按length字段累计字节直到收齐,再交给m16597F0按type/feature分发。跨多个 BLE 通知包自动拼接。 - 无 CRC、无加密:组帧器只填头 + 拷贝 payload,结尾不追加任何校验字节;接收侧也只校验同步字与长度。
- 字节序:多字节整数普遍 大端(如
AbstractC0011a.m33g(hi, lo) = (hi<<8)|lo)。
构造:AbstractC5639a0.m13114a(day) → m13164c(type=2, feature=13, payload={day}),设 day=0x00(今天):
FD DA 10 07 02 13 00
│ │ │ │ │ │ └── payload: {0x00} (day=0=今天)
│ │ │ │ │ └───── feature = 0x13 (历史步数汇总请求)
│ │ │ │ └──────── type = 0x02 (历史数据组)
│ │ │ └─────────── 总长 7 (=1+6)
│ │ └────────────── 标志 0x10
└──┘───────────────── 同步字 FDDA
⚠️ 早期版本误记:曾把m13218b(day,0)→(2,16,{day,0})当作步数请求,经读码实证 feature=16 实为 HRV 请求;步数汇总正确值为 feature=13、payload 仅{day}。详见第 5.3 节。
下列由反编译源码直接证实(
AbstractC5631k/C6326f/C6880a分发表),覆盖第 5.1/5.2 节中先前仅为"推断"的部分;Swift 实现见RingProtocol/(31 单测金标准)。
| 命令 | type | feature | payload(请求) | 响应载荷布局 |
|---|---|---|---|---|
| 历史步数汇总 | 2 | 13 | {day} |
16B:day(1) + steps/dist/cal/time 各 3B 大端(m46t),字段间 1B 填充。AbstractC6331k.m14842a |
| 历史步数明细 | 2 | 18 | {day} |
day(1) + N×uint16,每对线序小端 m33g(b[i+1],b[i]),30 分钟/桶,全天 48 桶。m14844c |
| 实时心率 开/关 | 1 | 9 | {01} / {00} |
响应同 type/feature,payload = 单字节 BPM。C6326f.m14819b;开关 AbstractC5660q.m13215d |
type=8是死代码:C6880a.m16597F0中case 8: default: return;——先前"实时测量在 type=8"的推断不成立,实时心率实际走type=1/feature=9。- 标准
2A37心率路径存在但 App 特化:m16654t读byte[1]为 BPM,并要求byte[2..3]==0x0400(大端) 才回调,非蓝牙规范解析;以type=1/feature=9为权威实时心率源。 - 多字节整数默认大端;唯一例外是步数明细(feature=18)的每桶 uint16 为小端。
- 单位(米/千卡/分钟)由
CRPStepsInfo字段名推断,帧内无单位标记。
命令由帧 [4]=type、[5]=feature 两级定位。下面是反编译统计出的主要命令组。
(type, feature)来自m13164c/m13163b调用统计;语义根据命名/回调推断。
| type | feature | 语义(推断) |
|---|---|---|
| 1 | 4,5,6,7,8,9 | 设备基本设置(含目标步数 m13219c → (1,7)) |
| 1 | 10 | 某开关(布尔)m13220d |
| 1 | 37,38,39,40 | 设置类(亮度/振动/显示等,(1,40) 高频) |
| 2 | 13–22, 27, 47 | 历史数据查询:步数/心率/睡眠/血氧/压力/HRV/体温/活动详情/训练 等(2,20 高频) |
| 3 | 9, 12 | 实时测量类 |
| 4 | 4 | (成对出现,疑似请求/查询) |
| 5 | 1,3,6,12 | 用户信息/目标类 |
| 6 | 2,8,11,14 | 闹钟/提醒类(6,2 高频) |
| 7 | 3,5,6,9,14 | 闹钟/提醒配置 |
| 9 | 0,1,2 | 常用命令(9,1 高频,疑似同步/查询) |
| 0xF1 | — | 走 fdd5 通道(OTA/大数据) |
| 0xF2 | — | 走 fdd6 通道(OTA/大数据) |
0xF1 / 0xF2由C6886g.m16744r / m16746t触发,发送时分别写入 fdd5 / fdd6,与普通命令(fdd2)分流。
接收在 C6880a.m16597F0,先按 type(b10)分组,再按 feature(b11)派发到具体 listener。
-
type = 2(历史/数据同步组):feature 2–44 共 30+ 子项,覆盖:
feature 数据类型 feature 数据类型 2 步数详情 14 睡眠 9 历史心率 15 压力 10 历史 HRV 16/17 体温 11 历史血氧 18 训练 12 测量结果 20/27 电压/活动 19 活动详情 26/40 远程控制 -
type = 8 组(实时测量):feature 9=心率实时、10=HRV、11=血氧、12=测量完成 等,分别回调
onHeartRate / onHrv / onBloodOxygen / onMeasureComplete。 -
其余 type 走各自的监听器(电量、SOS、远程控制、消息提醒、阈值、用户绑定等)。
完整业务回调接口见
com/crrepa/ble/conn/listener/(CRPStepsChangeListener、CRPHeartRateChangeListener、CRPSleepChangeListener、CRPBloodOxygenChangeListener、CRPHrvChangeListener、CRPStressChangeListener、CRPTempChangeListener等共 30+ 个)。
- 模块:
com.crrepa.ble.ota.goodix.*(汇顶 Goodix 芯片的固件恢复/升级)。 - 通道:大数据通过 fdd5(cmd 0xF1)/ fdd6(cmd 0xF2) 写入,与业务通道隔离。
- 备用 UUID:固件恢复模式下使用
a6ed0402 / a6ed0403(BleRestoreDeviceServices),并复用标准 CCCD00002902-…。 - 还有 LZO 压缩支持(
com.crrepa.ble.lzo.MiniLzoHelper),OTA 数据可能压缩传输。
- 明文协议:所有业务帧无加密、无签名。BLE 链路层加密(配对后)是唯一保护。
- 无应用层校验:组帧不含 CRC,仅靠同步字 + 长度。恶意邻居理论上可注入伪造帧。
- 绑定弱:用户 ID(
checkUserId/deleteUserId)是逻辑绑定,非密钥协商。 - 隐私数据明文:健康数据(心率、睡眠、位置相关 GPS 走
com.moyoung.ring.health.workout.gps)在 BLE 上明文传输。 - 若戒指未强制配对/加密配对,邻近攻击者可嗅探或重放命令。
建议(如做安全评估):用 nRF Connect / Wireshark + btsnoop 抓包验证;检查是否启用 LE Secure Connections 配对。
| 文件 | 作用 |
|---|---|
p185q1/C7224a.java |
GATT 服务/特征值绑定,fdd1~fdd6 映射 |
p185q1/C7225b.java |
服务发现单例,isMoYoung 判定 |
p095h1/C5961a.java |
BluetoothGattCallback:连接/MTU/notify/write 回调 |
p155n1/C6886g.java |
发送管理器:分片、写特征值选择、发送队列推进 |
p155n1/C6887h.java |
notify 使能管理(写 CCCD) |
p155n1/C6880a.java |
接收分发器:帧重组(m16637k1)+ type/feature 派发 |
p175p1/C7136c.java |
发送队列调度器 |
p175p1/C7134a.java |
命令对象(优先级 + 帧字节) |
p052d1/AbstractC5654k.java |
组帧器(m13164c):帧头/长度/结构 |
p052d1/AbstractC5661r.java 等 |
各命令构造(type,feature + payload) |
p003a2/AbstractC0011a.java |
字节/整数编解码工具(m33g 大端 uint16 等) |
com/crrepa/ble/conn/CRPBleConnection.java |
公开命令 API 接口(query*/enable*/send*) |
com/crrepa/ble/ota/goodix/* |
Goodix 固件 OTA |
反编译产物目录:/Users/netseye/Desktop/smartring/smartring-decompiled/
原始 APK:/Users/netseye/Desktop/smartring/apk/darings.apk
- 静态验证:用 nRF Connect 连接戒指,在 fdd2 上写入第 4 节示例帧,观察 fdd1 通知返回。
- 动态抓包:
- 开发者选项 → 启用「蓝牙 HCI 信息收集日志」,操作 App,
adb bugreport取 btsnoop_hci.log,用 Wireshark 解析。 - 或在已 root 设备上用
hcitool/ Android HCI snoop。
- 开发者选项 → 启用「蓝牙 HCI 信息收集日志」,操作 App,
- 重放/模糊测试:基于本协议格式构造帧,遍历
(type, feature)探测未知命令。
分析日期:2026-07-28 · 工具:jadx 1.5.5 · 设备:Samsung SM-S9360