blatann
BLE-Exploits mit Python und nRF52840 entwickeln
Lernziel
Du lernst, wie blatann über die Connectivity-Firmware mit dem nRF52840-Dongle kommuniziert. Du installierst die Bibliothek, flashst die Firmware, und setzt einen vollständigen BLE-Exploit in Python um — vom Verbindungsaufbau bis zum gezielten Write auf eine Charakteristik.
Was ist blatann?
blatann ist eine Python-Bibliothek für BLE-Kommunikation, die direkt mit dem nRF52840-Dongle von Nordic Semiconductor kommuniziert. Anders als Bleak oder ähnliche Bibliotheken, die den BLE-Stack des Betriebssystems verwenden, spricht blatann über eine serielle Verbindung mit der sogenannten Connectivity-Firmware, die auf dem Dongle läuft.
Das gibt dir eine Abstraktionsschicht, die deutlich tiefer liegt als bei systemeigenen BLE-APIs. Du kannst Connection-Parameter direkt setzen, Pairing explizit steuern und denselben Hardware-Dongle nutzen, den du in Phase 2 als Sniffer betrieben hast — nur mit anderer Firmware.
blatann vs. Bleak: die wichtigste Unterscheidung
Bleak ist einfacher zu installieren und läuft auf dem eingebauten Bluetooth-Adapter. Es eignet sich gut für Standard-BLE-Kommunikation. blatann erfordert einen nRF52840-Dongle, bietet dafür aber direkten Chip-Level-Zugriff: Du kannst Verbindungen ohne Verschlüsselung aufbauen, beliebige Handles schreiben, und Connection Intervals und PHY manuell kontrollieren. Für Sicherheitsanalysen und PoC-Exploits ist blatann die richtige Wahl.
Installation
Voraussetzungen
- Python 3.9 oder neuer
- nRF52840-Dongle (Nordic Semiconductor PCA10059)
- nRF Connect for Desktop (für Firmware-Flashing)
# Virtuelle Umgebung erstellen (empfohlen)
python3 -m venv ble-env
source ble-env/bin/activate # Windows: ble-env\Scripts\activate
# blatann installieren
pip install blatann==0.6.0
# Installation prüfen
python3 -c "import blatann; print(blatann.__version__)"
# Ausgabe: 0.6.0Connectivity-Firmware flashen
Der nRF52840-Dongle muss mit der Connectivity-Firmware bespielt werden, bevor blatann ihn ansprechen kann. Das ist eine einmalige Einrichtung pro Dongle.
# Pfad zur mitgelieferten Firmware finden
python3 -c "import blatann, os; print(os.path.dirname(blatann.__file__))"
# Firmware liegt unter: .../blatann/sdk/connectivity/
# Dateiname: connectivity_4.1.4_usb_with_s132_5.1.0.hex- nRF Connect for Desktop öffnen
- Programmer-App starten
- nRF52840-Dongle anschließen (im Bootloader-Modus: Mode-Taste beim Einstecken halten)
.hex-Datei aus dem blatann-Paket auswählen- Flash-Vorgang starten — dauert ca. 60 Sekunden
Serielle Port-Berechtigungen (Linux)
blatann kommuniziert mit dem Dongle über /dev/ttyACM0. Ohne Gruppenrechte schlägt die Verbindung mit einem Permission denied-Fehler fehl. Prüfe mit ls -la /dev/ttyACM* und füge deinen Nutzer zur dialout-Gruppe hinzu:
sudo usermod -a -G dialout $USER
# Danach neu anmelden oder: newgrp dialoutGrundlegendes Beispiel: LED-Strip ansprechen
Der LED-Strip aus dem Fallbeispiel dieser Arbeit verwendet keinerlei Verschlüsselung. Steuerbefehle werden im Format 7e [len] [opcode] [params...] ef auf eine einzige Charakteristik geschrieben. Das macht ihn zum idealen Einstiegsbeispiel für blatann.
from blatann import BleDevice
# Konstanten — aus Phase 1 (JADX) und Phase 2 (Wireshark) bekannt
SERIAL_PORT = "/dev/ttyACM0" # Linux; Windows: "COM3"
TARGET_MAC = "AA:BB:CC:DD:EE:FF" # MAC aus nRF Connect oder Wireshark
WRITE_UUID = "0000ffd9-0000-1000-8000-00805f9b34fb"
def build_color_cmd(r: int, g: int, b: int) -> bytes:
"""Farbbefehl nach LED-Strip-Protokoll (Phase 4 rekonstruiert)."""
return bytes([0x7e, 0x07, 0x05, 0x03, r, g, b, 0x10, 0xef])
def set_color(port: str, mac: str, r: int, g: int, b: int) -> None:
ble_device = BleDevice(port)
ble_device.open()
print(f"[*] Verbinde mit {mac}...")
peer = ble_device.connect(mac).wait(timeout=15)
if peer is None:
print("[!] Verbindung fehlgeschlagen — Gerät in Reichweite?")
ble_device.close()
return
print("[*] Entdecke GATT-Dienste...")
peer.discover_services().wait(timeout=10)
char = peer.database.find_characteristic(WRITE_UUID)
if char is None:
print(f"[!] Charakteristik {WRITE_UUID} nicht gefunden")
peer.disconnect().wait()
ble_device.close()
return
payload = build_color_cmd(r, g, b)
char.write(payload).wait(timeout=5)
print(f"[+] Farbe gesetzt: RGB({r}, {g}, {b})")
print(f" Payload: {payload.hex()}") # 7e070503ff000010ef für Rot
peer.disconnect().wait()
ble_device.close()
if __name__ == "__main__":
set_color(SERIAL_PORT, TARGET_MAC, 255, 0, 0) # RotFunktionen im Überblick
| Funktion | Beschreibung |
|---|---|
BleDevice(port) | Dongle-Verbindung initialisieren |
ble_device.open() | Serielle Verbindung aufbauen |
ble_device.connect(mac).wait() | BLE-Verbindung zum Zielgerät |
peer.discover_services().wait() | GATT-Baum vollständig einlesen |
peer.database.find_characteristic(uuid) | Charakteristik nach UUID suchen |
char.read().wait() | Charakteristik auslesen |
char.write(data).wait() | Bytes auf Charakteristik schreiben |
char.subscribe(callback).wait() | Notifications aktivieren |
ble_device.scanner.start() | BLE-Geräte scannen |
peer.disconnect().wait() | Verbindung sauber trennen |
Erweiterte Nutzung
Notifications empfangen
Viele Geräte senden Messdaten oder Bestätigungen über Notifications. blatann erlaubt es, Callback-Funktionen zu registrieren, die bei jeder eingehenden Notification aufgerufen werden.
def on_notification(characteristic, event_args):
data = event_args.value
print(f"[<] Notification: {data.hex()}")
def read_with_notifications(port: str, mac: str, notify_uuid: str) -> None:
ble_device = BleDevice(port)
ble_device.open()
peer = ble_device.connect(mac).wait(timeout=15)
peer.discover_services().wait()
notify_char = peer.database.find_characteristic(notify_uuid)
notify_char.subscribe(on_notification).wait()
print("[*] Warte auf Notifications (10 Sekunden)...")
import time
time.sleep(10)
notify_char.unsubscribe().wait()
peer.disconnect().wait()
ble_device.close()Connection-Parameter einstellen
blatann erlaubt es, Connection-Intervall und Slave-Latency direkt zu kontrollieren. Das ist nützlich, wenn ein Gerät auf bestimmte Timing-Parameter angewiesen ist oder du Timeouts simulieren möchtest.
from blatann.gap.gap_types import ConnectionParameters
# Verbindung mit angepassten Parametern aufbauen
conn_params = ConnectionParameters(
min_conn_interval_ms=15,
max_conn_interval_ms=30,
timeout_ms=4000,
slave_latency=0,
)
peer = ble_device.connect(mac, connection_params=conn_params).wait(timeout=15)
# Alternativ: Parameter einer bestehenden Verbindung ändern
peer.set_connection_parameters(100, 120, 6000)Pairing und Bonding
Für Geräte, die Pairing erfordern, bietet blatann ein explizites Pairing-Interface. Du setzt die Sicherheitsparameter direkt nach dem Verbindungsaufbau und löst das Pairing dann separat aus.
from blatann.gap.smp_types import IoCapabilities
# Just Works: kein Passcode, keine I/O-Fähigkeiten → automatisches Pairing
peer.security.set_security_params(
passcode_pairing=False,
io_capabilities=IoCapabilities.NO_INPUT_NO_OUTPUT,
bond=False,
)
# Pairing anfordern (nutzt die zuvor gesetzten Parameter)
peer.security.pair().wait(timeout=30)
print("[+] Pairing abgeschlossen: Level", peer.security.security_level)Just Works: Kein Schutz gegen MITM
Der Pairing-Modus "Just Works" (kein PIN, kein ECDH-Key-Vergleich) schützt nicht gegen Man-in-the-Middle-Angriffe. Er entspricht Security Mode 1, Level 2 (unauthentifizierte Verschlüsselung) im BLE-Sicherheitsmodell. Die Verbindung wird zwar verschlüsselt, aber der Temporary Key ist 0 — ein passiver Angreifer, der das Pairing mitschneidet, kann alles entschlüsseln. Echter MITM-Schutz erfordert mindestens Passkey Entry (SM1 Level 3).
Mehrere Geräte gleichzeitig ansprechen
blatann unterstützt mehrere gleichzeitige Verbindungen. Das erlaubt koordinierte Multi-Device-Skripte, zum Beispiel um Race Conditions oder Broadcast-Schwachstellen zu demonstrieren.
def connect_all(port: str, mac_list: list[str]) -> list:
ble_device = BleDevice(port)
ble_device.open()
peers = []
for mac in mac_list:
peer = ble_device.connect(mac).wait(timeout=15)
if peer is not None:
peer.discover_services().wait()
peers.append(peer)
print(f"[+] Verbunden: {mac}")
return peersBekannte Probleme
| Problem | Ursache | Lösung |
|---|---|---|
Permission denied: /dev/ttyACM0 | Fehlende Dialout-Gruppe | sudo usermod -a -G dialout $USER |
Failed to open device | Falsche Firmware oder Dongle nicht erkannt | Connectivity-Firmware erneut flashen |
Connection timeout | Gerät nicht in Reichweite oder im Schlafmodus | Gerät aufwecken, Dongle näher heranhalten |
Characteristic not found | Falsche UUID oder Service Discovery zu früh | discover_services().wait() vollständig abwarten |
Firmware version mismatch | blatann-Version passt nicht zur Firmware-Version | Firmware aus dem installierten blatann-Paket nehmen (blatann/sdk/connectivity/) |
Debugging aktivieren
Wenn eine Verbindung unerwartet abbricht oder ein Write fehlschlägt, hilft das blatann-Debug-Log weiter. Es zeigt jeden ATT-Befehl auf der Drahtebene.
# Vor dem Skript ausführen:
export BLATANN_LOG_LEVEL=DEBUG
# Oder im Skript:
import logging
logging.basicConfig(level=logging.DEBUG)Zusammenfassung
- blatann kommuniziert über die Connectivity-Firmware direkt mit dem nRF52840-Dongle — das erlaubt Chip-Level-Kontrolle ohne OS-Bluetooth-Stack.
- Installation:
pip install blatann==0.6.0+ Connectivity-Firmware flashen + Dialout-Gruppe setzen. - Das Grundmuster jedes Scripts:
BleDevice öffnen → verbinden → discover_services → Charakteristik suchen → lesen/schreiben/subscriben → trennen. - Notifications, Connection-Parameter und Pairing sind vollständig programmierbar.
- Der häufigste Stolperstein: fehlende Serielle-Port-Berechtigungen auf Linux.