From 071ec7a1a73fdd2edc0d068b5d4f269242ba42b6 Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Sat, 22 Aug 2026 06:47:53 +0000 Subject: [PATCH] Add content from: BitChat iOS BLE Mesh Cache Poisoning via RSR and TTL Authent... --- .../ios-pentesting/README.md | 8 ++++ .../pentesting-ble-bluetooth-low-energy.md | 48 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/src/mobile-pentesting/ios-pentesting/README.md b/src/mobile-pentesting/ios-pentesting/README.md index 8d45820c390..c5f13dad964 100644 --- a/src/mobile-pentesting/ios-pentesting/README.md +++ b/src/mobile-pentesting/ios-pentesting/README.md @@ -1145,6 +1145,14 @@ ios-serialisation-and-encoding.md ## Network Communication +### BLE application protocols + +For iOS applications using BLE mesh transports, see the application-protocol cache-poisoning methodology: + +{{#ref}} +../../todo/radio-hacking/pentesting-ble-bluetooth-low-energy.md#ble-mesh-and-gossip-protocol-cache-poisoning +{{#endref}} + It's important to check that no communication is occurring **without encryption** and also that the application is correctly **validating the TLS certificate** of the server.\ To check these kind of issues you can use a proxy like **Burp**: diff --git a/src/todo/radio-hacking/pentesting-ble-bluetooth-low-energy.md b/src/todo/radio-hacking/pentesting-ble-bluetooth-low-energy.md index 5442af222ab..3766dd603c1 100644 --- a/src/todo/radio-hacking/pentesting-ble-bluetooth-low-energy.md +++ b/src/todo/radio-hacking/pentesting-ble-bluetooth-low-energy.md @@ -272,6 +272,51 @@ for off in range(0, len(img_bytes), CHUNK): - **Post-bond microphone abuse:** After bonding, open **HFP** and start **SCO audio** to obtain a live microphone stream for listening/recording (e.g., saving M4A). This chain turns an unsigned KBP acceptance into remote audio capture without user consent. - **Hunt/detect:** Look for Fast Pair GATT traffic followed immediately by classic **bonding attempts to the BR/EDR address returned in KBP**, and for KBP writes lacking a signature. Enforcing signature validation on KBP and prompting for user-confirmed pairing breaks the chain. +## BLE mesh and gossip protocol cache poisoning + +In an application-layer BLE mesh, the important boundary is not only the GATT characteristic: it is the transition from an untrusted packet to **authenticated, replayable mesh state**. A packet admitted to a gossip or synchronization cache can be redistributed after the sender disconnects, so one validation mistake can become persistent state poisoning rather than a one-hop spoof.[[13]](#references) + +### Trust-boundary review + +Review the complete receive path, including messages, attachments, fragments, and legacy compatibility branches, for these composable flaws.[[13]](#references)[[15]](#references) + +- **Packet identity used as transport identity:** A serialized sender ID is attacker-controlled until it is bound to the current BLE central/peripheral connection. Do not use the claimed ID to select replay windows, authorization state, or synchronization state before this binding. +- **Self-declared synchronization responses:** A response flag must not be sufficient to bypass timestamp or replay validation. Bind a response to a pending request, the authenticated transport peer and protocol identity, and a nonce or equivalent unpredictable request context. +- **Network metadata treated as local provenance:** TTL, hop count, route flags, and “local-only” markers remain attacker-controlled when received over GATT. Code equivalent to `if !accepted && packet.ttl == 0 { accepted = true }` is an authentication bypass, not a locality check. +- **Cache insertion before final acceptance:** Call the cache-admission function only after signature, identity, freshness, recipient, authorization, packet-type, structural, and fragment-reassembly checks succeed. Keep forwarding and display decisions downstream from the same final acceptance result. +- **Cross-type inconsistencies:** Repeat the review for file transfers and voice notes; otherwise, a fixed message path can leave an equivalent binary-payload poisoning primitive. + +### Reusable test sequence + +On an authorized test mesh, first establish legitimate protocol state and then mutate only the fields that claim an exception. This distinguishes a state-machine bypass from ordinary unauthenticated GATT access.[[13]](#references)[[14]](#references) + +1. Scan advertisements for the application service UUID, connect to its GATT server, and identify the write/notify characteristics. +2. Create a fresh protocol identity and send a correctly signed discovery or `ANNOUNCE` packet so the target opens its normal synchronization-response window. +3. During that window, send an unsigned broadcast packet combining the response marker with boundary values such as `TTL=0`, a stale timestamp, or a spoofed sender ID. +4. Verify each effect independently: UI acceptance, cache insertion, response-window use under the claimed identity, and survival after the attacking connection closes. +5. Ask another peer to synchronize from the tested node. If it receives, caches, and later serves the injected entry, normal gossip has converted a local injection into autonomous propagation. + +### BitChat packet example + +The disclosed BitChat iOS chain used a signed `ANNOUNCE` to create response state, followed by an unsigned version-1 broadcast `MESSAGE`. Its 14-byte header contained type `0x02`, `TTL=0`, an 8-byte big-endian millisecond timestamp, flags `0x10` (`isRSR` only), and a 2-byte big-endian payload length; an 8-byte peer ID and payload followed, with no recipient or Ed25519 signature.[[13]](#references)[[14]](#references) + +```python +import struct, time + +def craft_rsr_message(peer_id, payload): + frame = bytearray([0x01, 0x02, 0x00]) + frame.extend(struct.pack(">Q", int(time.time() * 1000))) + frame.append(0x10) # isRSR; hasSignature remains clear + frame.extend(struct.pack(">H", len(payload))) + frame.extend(peer_id[:8].ljust(8, b"\x00")) + frame.extend(payload) + return bytes(frame) +``` + +In the vulnerable path, the packet-embedded ID selected an open response window, `isRSR` skipped normal freshness enforcement, `TTL=0` changed failed authentication into acceptance, and caching occurred before the final rejection guard. Sync replay then reproduced the same `TTL=0`/`isRSR` conditions at downstream peers, making every poisoned cache a new source.[[13]](#references) + +For regression testing, assert that a connection cannot change its bound sender ID—even when several frames arrive in one notification batch—and that every unauthenticated packet leaves the UI, cache, forwarding queue, and sync response unchanged. Useful telemetry includes the BLE connection identifier, claimed and bound peer IDs, request/response correlation result, signature result, freshness decision, final acceptance reason, and whether cache insertion was attempted; the BitChat fix added explicit events for sender-ID mismatch, unsolicited RSR packets, and drops from unverified peers.[[15]](#references) + ## Operational notes - On Android test devices, enable **Bluetooth HCI snoop logging** to capture the phone's controller traffic for protocol comparison and troubleshooting.[[11]](#references) @@ -294,5 +339,8 @@ for off in range(0, len(img_bytes), CHUNK): - [10] [Shining Mask BLE protocol notes (BrickCraftDream)](https://github.com/BrickCraftDream/Shining-Mask-stuff/blob/main/ble-protocol.md) - [11] [Android Bluetooth HCI snoop logging](https://source.android.com/docs/core/connect/bluetooth/verifying_debugging) - [12] [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062) +- [13] [BitChat iOS BLE Mesh Cache Poisoning via RSR and TTL Authentication Bypasses](https://barghest.asia/blog/bitchat-cache-poisoning) +- [14] [BARGHEST BitChat iOS BLE cache-poisoning proof of concept](https://github.com/BARGHEST-ngo/PoC_Bitchat1.15.0_iOS-BLEcache-poisoning) +- [15] [BitChat PR #998 — Fix iOS BLE mesh authentication issues in BLEService](https://github.com/permissionlesstech/bitchat/pull/998) {{#include ../../banners/hacktricks-training.md}}