Hacking Bluetooth Sports Tech for Fun and Profit — Part 1: What Your Chest Strap Is Hiding
How much data is your heart rate monitor actually capable of sharing — and how much is it keeping to itself?
If you've ever worn a Bluetooth chest strap during a run or a ride, you've consumed exactly one number from it: your heart rate, updated once per second, streamed to your watch or phone as a tidy integer. Sixty-two. Sixty-three. Sixty-one.
What you probably haven't wondered — because why would you — is what else the strap knows that it isn't telling you.
I've spent the last decade idly curious about this. Chest straps sit against your skin with ECG-grade electrodes, sampling your heart's electrical signal hundreds of times per second. They compute a heart rate from that signal and hand you the summary. But the raw waveform? The beat-by-beat timing? The accelerometer data some of them collect? That stays locked inside the device, accessible only through proprietary apps and closed SDKs — if it's accessible at all.
I finally decided to find out what's actually in there.
The project
VESTIGATOR is a BLE (Bluetooth Low Energy) research toolkit I've been building to systematically discover, enumerate, and extract data from fitness wearables. The approach is straightforward: connect to a device, map its GATT services and characteristics, read everything readable, subscribe to everything streamable, and then start poking at the proprietary bits.
The tool is written in Python using bleak for cross-platform BLE, with device-specific protocol implementations for each strap I've investigated. The CLI is called disco — because that's what we're doing.
The devices
So far I've profiled seven devices across six manufacturers. The results have been... uneven.
| Device | Type | Standard HR | Raw ECG | Auth Required |
|---|---|---|---|---|
| Polar H10 | Chest strap | Yes | 130 Hz | No |
| Wahoo TRACKR HR | Chest strap | Yes | 18 Hz | No |
| Suunto Smart Sensor | Chest strap | Yes (+ RR) | No | No |
| Garmin HRM Pro Plus | Chest strap | Yes | No | No (live) |
| Garmin HRM 600 | Chest strap | Yes | No | Yes (files) |
| WHOOP 4.0 | Wrist band | Disabled | N/A (PPG) | Yes |
| FORM Smart Swim 2 | Swim goggles | N/A | N/A | Yes (HUD passkey) |
The champion for openness is the Wahoo TRACKR HR, which streams its raw 12-bit ECG waveform through a proprietary service with absolutely zero authentication. No pairing, no bonding, no handshake — just connect and ask.
The Polar H10 is the most capable, streaming raw ECG at 130 Hz alongside a 3-axis accelerometer at 200 Hz, simultaneously, with data arriving in clean int24 microvolts. Also no authentication.
The WHOOP 4.0, by contrast, disables its standard heart rate service entirely and gates everything behind BLE bonding. Even the battery level requires authentication.
What's next
A Raspberry Pi 5 and a pair of nRF52840 dongles are on their way. The Pi will handle BLE bonding via BlueZ (something macOS CoreBluetooth stubbornly refuses to initiate programmatically), and the nRF52840s will act as BLE sniffers — capturing the traffic between official apps and devices to reverse-engineer the authentication handshakes we can't yet bypass.
The targets:
- FORM Smart Swim 2 — the protocol is fully documented (protobuf over BLE), but the goggles require MITM-protected pairing with a 6-digit passkey displayed on the HUD. Once paired, we can push custom workouts and potentially download swim session data — all without a FORM subscription
- WHOOP 4.0 — crack the bonding sequence and see what's behind the proprietary wall
- Garmin HRM 600 — the V2 Multi-Link protocol is designed for BLE file transfer, but requires Secure Connection
- Polar H10 PFTP — the file system is there and responding, just gated behind an initial handshake
- Suunto Smart Sensor — sniff the Movescount app to decode the
98ae7120service, or flash Movesense firmware for 512 Hz ECG
In the meantime, the next post in this series goes deep on the Wahoo TRACKR HR — how we went from a mysterious "FORM Goggles" service to plotting raw ECG waveforms in a single session, with no documentation to guide us.
Comments ()