mahdi-salmanzade/BeatsBar
GitHub: mahdi-salmanzade/BeatsBar
macOS 菜单栏工具,实时显示 Powerbeats Pro 2 电量与心率,并附带对苹果专有 AACP 蓝牙协议及内核拦截机制的深度逆向工程文档。
Stars: 0 | Forks: 0
BeatsBar
在 macOS 菜单栏中实时显示您的 Powerbeats Pro 2 的电量和按需读取的心率。
此外,还包含一份对 Apple 专有 AACP 协议的深度逆向工程日志 —— 包括 IOBluetooth.framework 中阻止第三方应用像 iOS 那样读取心率的具体 ARM64 指令。
## 目前提供的功能
- 一个始终显示**实时电量**(左耳塞 · 右耳塞 · 充电盒)的菜单栏项目。
- 一个 **“Start HR session”**(启动心率会话)操作,会持续流式传输**实时 BPM** 直到你手动停止(使用标准的 BLE Heart Rate 配置文件 —— 请参阅下面的注意事项)。
- 一个 **“Launch at login”**(登录时启动)开关。
- 在 `research/PROTOCOL.md` 中提供了专为 Powerbeats Pro 2 制作的完整 **AACP 协议解码**(包括 PSM、握手协议、操作码、帧结构)—— 这是首个针对该设备公开发布的协议图。
- 一份记录了尝试突破 macOS 内核级 L2CAP 策略的文档,包括发生拒绝的具体反汇编位置。
## 状态
| | |
|---|---|
| 电量(播放音乐时的实时状态) | ✅ 已发布 |
| 心率(会话模式,BLE 0x180D) | ✅ 已发布 |
| Powerbeats Pro 2 的 AACP 协议已解码 | ✅ 已记录文档 |
| `IOBluetooth` 策略拦截已逆向工程 | ✅ 精确到 ARM64 指令级别 |
| 心率(通过 AACP 始终开启) | ❌ 在 macOS 内核层被阻止 —— 请参阅 [JOURNEY.md](research/JOURNEY.md) |
## 界面展示
菜单栏:
```
♥ 78 46%
```
点击展开详情:
```
🟢 Powerbeats Pro connected
Heart Rate: 78 bpm
Battery: Left 46% · Right 46% · Case 92%
Start HR session…
HR Mode ▶
Launch at login
About BeatsBar…
Quit
```
电池状态会连续以流式传输显示,没有任何多余操作。`♥ 78` 仅在心率会话运行期间出现。
## HR 的工作原理(以及附带的问题)
Powerbeats Pro 2 提供了两种获取心率的途径:
1. **标准 BLE Heart Rate 配置文件**(GATT 服务 `0x180D`)。仅在耳塞处于“健身设备配对”模式时可用 —— 可通过**双击并长按 b 按钮**触发。在此模式下,耳塞会断开音频连接。BeatsBar 使用此路径实现“Start HR session”。该功能可用,但并非始终开启,且每次会话都需要进行手势操作。
2. **Apple AACP**,通过 Bluetooth Classic L2CAP,PSM `0x1001` 传输。这是 iOS 在播放音乐时用来读取心率的通道 —— 无需模式切换,也无需手势。**macOS 阻止第三方应用打开此 PSM。** 该拦截机制存在于 `IOBluetooth.framework` 内部;即使使用 `sudo` 也无济于事。我们已经对其进行了最底层的挖掘;详情见下文及 [`research/JOURNEY.md`](research/JOURNEY.md)。
## AACP 协议(我们已记录的内容)
| 层级 | 详情 |
|---|---|
| 物理层 | Bluetooth Classic (BR/EDR),与音频链路共享 |
| L2CAP PSM | `0x1001`(在 Apple 自己的 SDK 头文件中名为 `kBluetoothL2CAPPSMAACP`) |
| SDP 服务名称 | `AAP Server` |
| 服务 UUID | `74ec2172-0bad-4d01-8f77-997b2be0722a` |
| 帧魔术字节 | `04 00 04 00` |
| 帧结构 | `04 00 04 00
` |
| 初始握手 | `00 00 04 00 01 00 02 00 00 00 00 00 00 00 00 00`(必须是第一个数据包) |
一旦通道打开并发送了握手信号:
```
# 订阅所有通知
04 00 04 00 0F 00 FF FF FE FF
# 启用 HRM (控制操作码 0x09, 标识符 0x30, 值 0x01)
04 00 04 00 09 00 30 01 00 00 00
```
电量报告通过操作码 `0x0004` 传入,格式为 `[count] ([component] 01 [level] [status] 01)*` —— 这与 LibrePods 为 AirPods Pro 2 记录的格式相同。50 Hz 的原始 IMU/PPG 数据流的操作码是 `0x0017`。完整的操作码映射表:[`research/PROTOCOL.md`](research/PROTOCOL.md)。
## macOS 的阻碍
对于任何第三方应用,`IOBluetoothDevice.openL2CAPChannelSync(_:withPSM: 0x1001, ...)` 都会返回 IOReturn `0xe00002bc`。我们对 `IOBluetooth.framework` 进行了反汇编,找到了产生此错误的具体指令对:
```
IOBluetooth`-[IOBluetoothDevice openL2CAPChannelAsync:withPSM:withConfiguration:delegate:]
+136: mov w25, #0x2bc
+140: movk w25, #0xe000, lsl #16 ; w25 = 0xe00002bc, the default error
```
`w25` 在函数顶部被加载为“默认错误”,并且只在成功的路径上被清除。对于 PSM `0x1001`,控制流永远不会到达清除错误的代码块 —— 请求在实际的 L2CAP CONN_REQ 通过网络发送出去之前就已经失败了。
我们尝试过:
- **公共 API `openL2CAPChannelSync`** → `0xe00002bc`。
- **已弃用的 `openL2CAPChannel:findExisting:newChannel:`** → 走的是相同的内部路径 → 相同的错误。
- **私有 API `_initWithDevice:andClassicPeer:PSM:withServiceUUID:`** + `setupL2CAPChannelForDevice` → 在用户空间创建了一个真实的 `IOBluetoothL2CAPChannel` 对象,但从未触发内核端的 IOService open。
- **`IOBluetoothHCIControllerDisableL2CAPKernelDrivers(true)`** —— 这是一个私有函数,它通过 `IORegistryEntrySetCFProperty` 设置一个内核属性 `DeviceL2CAPOnlyUserClients`。即使以 `root` 身份运行也返回 `0xe0000007`:它需要 AMFI 独立于 unix 权限单独检查的授权 (entitlements)。
- **`BluetoothHCISetupUserClient`** —— 同样的授权门槛,同样的拒绝。
- **监听 PSM 0x1001 上的传入 L2CAP 连接**(注册为服务器)→ 耳塞从不主动发起连接;它们只通过 Apple 自己的流程向“主要”配对主机发起 AAP。
- **嗅探 Continuity 0x07 BLE 近距离广播**以寻找隐藏在加密的 16 字节尾部中的 HR 数据 → 确认那是一个轮换的隐私 MAC,而不是遥测数据。
绕过内核拦截的现实方法(没有一种仅限于用户空间):
- **禁用 SIP + AMFI**(`csrutil disable`,`nvram boot-args="amfi_get_out_of_my_way=0x1"`),然后使用正确的授权 plist 对 helper 程序进行签名。
- **DriverKit / kext**,由 Apple 签名 —— 需要付费的开发者注册以及特定于 Bluetooth 的授权审批。
- **外部 USB Bluetooth 适配器**,通过 `_sendRawHCIRequest` 直接驱动无线电模块,手工构造 L2CAP CONN_REQ 数据包。
- **Linux + USB BT 直通** —— LibrePods 在 Linux 上使用相同的握手协议成功连接了相同的耳塞。
位于 [`interposer/aacp_unlock.m`](interposer/aacp_unlock.m) 的 interposer dylib 实现了上述所有用户空间尝试,作为一份记录在案的探索。它成功创建了通道对象,但未能突破内核层。
## 构建与运行
```
cd src
swift build -c release
.build/release/BeatsBar &
```
该应用程序将安装到您的菜单栏中(没有 Dock 图标)。首次启动时会提示授予 Bluetooth 权限 —— 请点击允许。
系统要求:
- macOS 13+ (Apple Silicon 或 Intel)
- Powerbeats Pro 2 已与您的 Mac 配对并连接用于音频播放
- Xcode 命令行工具
## 架构
```
BeatsBar/
├── src/ # Swift menu bar app + 0x180D HR session client
├── interposer/ # DYLD_INSERT_LIBRARIES dylib (Objective-C)
├── research/ # Protocol decoding scripts + writeups + opcode map
├── assets/ # Header image
├── docs/ # (reserved for future docs)
└── README.md
```
组件:
- **`BeatsBar`** (Swift,`src/Sources/BeatsBar`) —— 基于 `NSStatusItem` 的菜单栏应用。轮询 `system_profiler` 获取电量和连接状态,针对 0x180D 路径运行按需的 `CBCentralManager` HR 会话,并包含一个用于研究的、执行 dylib 的“Kernel mode”存根后端。
- **`aacp_helper`** (Swift,`src/Sources/aacp_helper`) —— 在 Kernel 模式下注入 dylib 启动的小型 CLI 工具;一旦链路层成功打开,它将在 stdout 输出 JSON 格式的 HR 读数。
- **`libaacp_unlock`** (Objective-C,`interposer/`) —— DYLD interposer,混写了 (swizzles) `openL2CAPChannelSync`,并尝试了我们发现的所有用户空间途径。
- **`research/`** —— 用于解析 PacketLogger `.pklg` 捕获数据的 Python 工具、AACP 操作码映射表 (`PROTOCOL.md`) 以及完整的逆向工程时间线 (`JOURNEY.md`)。
## 许可证
MIT —— 请参阅 `LICENSE`。标签:AACP协议, ARM64, Beats, BLE, CVE监控, GATT, IOBluetooth, L2CAP, Powerbeats Pro 2, Wayback Machine, 云资产清单, 反汇编, 威胁情报, 开发者工具, 心率监测, 电量显示, 硬件黑客, 系统扩展, 苹果生态, 菜单栏应用, 蓝牙, 逆向工具, 逆向工程