Google Fast Pair was designed to make Bluetooth pairing seamless: tap a notification and you're connected. But what happens when that seamless experience becomes a security liability? WhisperPair-PoC is a security research tool that exposes two critical vulnerability classes affecting millions of Bluetooth accessories: unauthorized pairing bypass and Find My Device Network tracking exploitation.
This post details the technical internals of WhisperPair-PoC, the protocol weaknesses it exploits, and what this means for the Bluetooth accessory ecosystem.
The Google Fast Pair specification explicitly states:
"If the optional Public Key field is present: If the device is not in pairing mode, ignore the write and exit."
This is the critical security gate. Devices should only respond to Key-Based Pairing requests when the user has explicitly put the device into pairing mode (typically by holding a button). This ensures user intent, so you can't pair with someone's earbuds while they're wearing them.
The Problem: Many manufacturers skip this check entirely. They process pairing requests regardless of pairing mode state, enabling:
Google's Find My Device Network (FMDN) allows tracking Bluetooth accessories via the crowd-sourced Android device network. This requires an Account Key, a 16-byte symmetric key linking the device to a Google account.
The Problem: The Account Key characteristic often accepts writes without authentication:
Before diving into the exploitation, let's understand the legitimate Fast Pair flow:
┌─────────────────────────────────────────────────────────────┐
│ BLE Advertisement │
├─────────────────────────────────────────────────────────────┤
│ Service UUID: 0xFE2C (Fast Pair) │
│ Service Data: │
│ [Pairing Mode] → 3 bytes: Model ID only │
│ [Not Pairing] → 4+ bytes: 0x00 + Account Key Filter │
└─────────────────────────────────────────────────────────────┘
The advertisement format reveals pairing state:
Seeker (Phone) Provider (Accessory)
│ │
│───── GATT Connect ──────────────────────────>│
│ │
│───── Discover Services ─────────────────────>│
│<──── Service: 0xFE2C ────────────────────────│
│ │
│───── Enable Notifications (0xFE2C1234) ─────>│
│ │
│───── Write Key-Based Pairing Request ───────>│
│ [16-byte encrypted block] │
│ [64-byte ECDH Public Key] (optional) │
│ │
│ ┌────────────────────────────────────┐ │
│ │ SECURITY CHECK: │ │
│ │ If Public Key present AND │ │
│ │ device NOT in pairing mode: │ │
│ │ → IGNORE and EXIT │ │
│ │ Else: │ │
│ │ → Process request │ │
│ └────────────────────────────────────┘ │
│ │
│<──── Notification: Encrypted Response ───────│
│ [Provider's BR/EDR Address] │
│ │
│═══════ Bluetooth Classic Pairing ═══════════>│
The vulnerability occurs when devices skip the "SECURITY CHECK" box entirely.
WhisperPair-PoC is a Python-based security research tool built on top of the Bleak BLE library. It operates in several phases:
┌────────────────────────────────────────────────────────────────┐
│ WhisperPair-PoC │
├────────────────────────────────────────────────────────────────┤
│ CLI Layer │
│ ├── Argument parsing (--target-name, --scan-duration) │
│ ├── TargetPolicy construction │
│ └── REPL initialization │
├────────────────────────────────────────────────────────────────┤
│ Discovery Engine │
│ ├── BLE scanning via Bleak │
│ ├── Advertisement parsing │
│ ├── Protocol detection (Fast Pair, FMDN, Swift Pair) │
│ └── Device fingerprinting (Model ID, OUI lookup) │
├────────────────────────────────────────────────────────────────┤
│ Check Engines │
│ ├── FastPairCheckEngine (passive advertisement analysis) │
│ ├── FastPairBypass (active CVE-2025-36911 testing) │
│ ├── FindHubCheckEngine (Account Key status detection) │
│ └── RiskScorer (composite vulnerability assessment) │
├────────────────────────────────────────────────────────────────┤
│ Connection Manager │
│ ├── GATT connect with MTU negotiation │
│ ├── Service/characteristic discovery │
│ ├── Read/Write/Notify operations │
│ └── Error handling and retry logic │
├────────────────────────────────────────────────────────────────┤
│ Exploitation Modules │
│ ├── ring_device() - Trigger locator sound │
│ ├── set_account_key() - Write Account Key │
│ └── Response parsing (BR/EDR address extraction) │
└────────────────────────────────────────────────────────────────┘
discovery.py)The scanner uses Bleak's detection callbacks to capture BLE advertisements:
async def _detection_callback(
self, device: BLEDevice, advertisement_data: AdvertisementData
) -> None:
"""Process each detected BLE advertisement."""
discovered = DiscoveredDevice(
address=device.address,
name=device.name or advertisement_data.local_name,
rssi=advertisement_data.rssi,
advertisement=self._convert_advertisement(advertisement_data),
first_seen=datetime.now(UTC),
last_seen=datetime.now(UTC),
)
self._devices[device.address] = discovered
For each device, the tool extracts:
0xFE2C, FMDN 0xFD44, etc.)0x00E0, Apple 0x004C)fastpair.py)Pairing mode is determined by analyzing the device's advertisement:
def _analyze_service_data(self, evidence: FastPairEvidence) -> None:
"""Analyze Fast Pair service data to infer pairing mode."""
data = evidence.service_data_bytes
# 3 bytes = Model ID only = Pairing Mode (discoverable)
if len(data) == 3:
evidence.inferred_pairing_mode = PairingModeState.IN_PAIRING_MODE
return
# 4+ bytes with version 0x00 = Not in pairing mode
if data[0] == 0x00:
akd_byte = data[1]
akd_type = akd_byte & 0x0F # Lower 4 bits
# Type 0x00 = Show UI, Type 0x02 = Hide UI
# Both indicate NOT in pairing mode
evidence.inferred_pairing_mode = PairingModeState.NOT_IN_PAIRING_MODE
This is crucial: if a device is determined to be not in pairing mode, and it still responds to the pairing request, it's vulnerable.
fastpair_attack.py)The core vulnerability test sends Key-Based Pairing requests and monitors for responses:
async def check_vulnerability(self, device: DiscoveredDevice) -> BypassCheckResult:
"""Test if device responds to pairing requests when not in pairing mode."""
# Enable notifications to receive response
await self.connection_manager.start_notify(
KEY_BASED_PAIRING_CHAR,
self._notification_handler,
)
# Build and send request (multiple strategies)
for strategy in self.strategies:
request, flags = self._build_strategy_request(device.address, strategy)
await self.connection_manager.write_characteristic(
KEY_BASED_PAIRING_CHAR,
request,
)
# Wait for response
try:
await asyncio.wait_for(
self._response_received.wait(),
timeout=self.response_timeout,
)
# Response received = VULNERABLE
return BypassCheckResult(result=BypassResult.VULNERABLE, ...)
except asyncio.TimeoutError:
# No response = Device correctly ignored request
continue
return BypassCheckResult(result=BypassResult.NOT_VULNERABLE, ...)
The tool implements multiple request strategies because different devices respond to different flag combinations:
| Strategy | Flags | Description |
|---|---|---|
RAW_KBP |
0x11 |
INITIATE_BONDING | EXTENDED_RESPONSE |
WITH_PUBLIC_KEY |
0x11 |
80-byte request with ECDH public key |
RETROACTIVE |
0x0A |
Bypasses some manufacturer checks |
EXTENDED |
0x10 |
For newer device firmware |
When a vulnerable device responds, the tool extracts the BR/EDR (Bluetooth Classic) address:
def _parse_response(self, response_data: bytes) -> str | None:
"""Extract provider's BR/EDR address from response."""
# Strategy 1: Standard response (type 0x01)
if response_data[0] == 0x01:
return self._extract_address(response_data, offset=1)
# Strategy 2: Extended response (type 0x02)
if response_data[0] == 0x02:
addr_count = response_data[2]
return self._extract_address(response_data, offset=3)
# Strategy 3: Brute force pattern matching
for offset in range(len(response_data) - 5):
addr = self._extract_address(response_data, offset)
if self._is_valid_mac(addr):
return addr
This BR/EDR address could be used to initiate Bluetooth Classic pairing (though WhisperPair-PoC stops at detection).
Research identified common implementation failures:
The most common issue: manufacturers simply don't implement the check:
// VULNERABLE: No pairing mode check
void handle_kbp_write(uint8_t* data, size_t len) {
if (len >= 80 && has_public_key(data)) {
// Should check: if (!is_in_pairing_mode()) return;
process_pairing_request(data); // Processes regardless
}
}
The Account Key characteristic should require authentication:
// VULNERABLE: No authentication required
void handle_account_key_write(uint8_t* key, size_t len) {
if (len == 16) {
store_account_key(key); // Accepts any key from anyone
}
}
// SECURE: Verify caller knows existing key
void handle_account_key_write_secure(uint8_t* encrypted_key, size_t len) {
if (!verify_encrypted_with_existing_key(encrypted_key)) {
return; // Reject unauthorized writes
}
store_account_key(decrypt(encrypted_key));
}
Devices advertising Fast Pair service data when not in pairing mode reveal:
WhisperPair-PoC uses a layered detection approach:
No connection required. The tool analyzes what the device broadcasts:
┌─────────────────────────────────────────────────────────────┐
│ Passive Checks │
├─────────────────────────────────────────────────────────────┤
│ ✓ Fast Pair service UUID present (0xFE2C) │
│ ✓ FMDN service UUID present (0xFD44) │
│ ✓ Pairing mode inferred from service data length │
│ ✓ Model ID extracted (when in pairing mode) │
│ ✓ Account Key Filter detected (when not in pairing mode) │
│ ✓ Address type analysis (static vs. random) │
└─────────────────────────────────────────────────────────────┘
Result: "Device advertising Fast Pair data while NOT in pairing mode"
→ Potential gating violation (needs active test to confirm)
Requires connection. The tool interacts with GATT services:
┌─────────────────────────────────────────────────────────────┐
│ Active Checks │
├─────────────────────────────────────────────────────────────┤
│ 1. Connect to device via BLE │
│ 2. Discover Fast Pair service (0xFE2C) │
│ 3. Locate Key-Based Pairing characteristic (0xFE2C1234) │
│ 4. Enable notifications │
│ 5. Write Key-Based Pairing request │
│ 6. Wait for response (with timeout) │
│ 7. Response received → VULNERABLE │
│ Timeout/Rejected → NOT VULNERABLE │
└─────────────────────────────────────────────────────────────┘
A composite risk score is computed:
| Signal | Score | Meaning |
|---|---|---|
| Gating violation (passive) | +2 | Advertising FP when not in pairing mode |
| Bypass confirmed (active) | +2 | Responded to KBP when not in pairing mode |
| FMDN without Account Key | +2 | Trackable via Find My network |
| Static BLE address | +1 | Device is persistently trackable |
| Address rotation observed | -1 | Privacy-preserving behavior |
Risk Levels:
WhisperPair-PoC includes controlled exploitation features to demonstrate impact:
ring command)Triggers the FMDN Beacon Actions characteristic to play the locator sound:
REDACTED FOR RELEASE
Impact: Attacker can harass victim by repeatedly triggering sound, or use it to locate a device they want to steal.
set account-key command)Writes a new Account Key to the device:
REDACTED FOR RELEASE
Impact:
WhisperPair-PoC intentionally stops short of full exploitation:
| Capability | Implemented | Reason |
|---|---|---|
| Vulnerability detection | ✅ Yes | Core purpose |
| BR/EDR address extraction | ✅ Yes | Evidence collection |
| Bluetooth Classic pairing | ❌ No | Requires platform-specific code |
| HFP audio hijacking | ❌ No | Beyond scope of BLE tool |
| Persistent implant | ❌ No | Malicious capability |
if (has_public_key(request) && !is_in_pairing_mode()) {
return; // Ignore request per spec
}
Require authentication for Account Key writes:
Minimize advertisement exposure:
Firmware update mechanism:
This tool is intended for:
WhisperPair-PoC demonstrates that the "seamless" Fast Pair experience comes with security tradeoffs that many manufacturers fail to address. The pairing mode check is a single conditional that separates a secure device from a vulnerable one, yet it's frequently omitted.
Fast Pair's trust model assumes devices will enforce user intent. When they don't, attackers get silent pairing, tracking capabilities, and denial-of-service vectors against victims' devices.
This tool aims to help the ecosystem identify and remediate these issues, making Bluetooth accessories safer for everyone.
WhisperPair-PoC is released for authorized security research only. No responsibility is assumed for misuse of this tool.