Smart-LED-Strip
Klartext-GATT-Kommunikation und XOR-Obfuskation mit globalem Schlüssel
Lernziel
Nach dieser Seite verstehst du, wie ein weit verbreitetes Smart-Home-Gerät auf allen drei analysierten Ebenen versagt: keine Verschlüsselung auf dem Steuerkanal, symbolische XOR-Obfuskation auf dem Werbekanal und keinerlei Schutz gegen Replay-Angriffe. Du kannst die vier gefundenen Schwachstellen benennen, ihre CVSS-Scores nachvollziehen und erklären, warum ein "verschlüsselnder" Code tatsächlich nur eine einzige Zahl verbirgt.
Das Gerät und seine App
Der analysierte Smart-LED-Strip wird über die App wl.smartled (Version 6.4.11, Build 156) gesteuert. Mit über 10 Millionen Downloads im Google Play Store ist dies kein Nischenprodukt — es steht stellvertretend für eine ganze Klasse von Smart-Home-Leuchtmitteln, die auf denselben Protokollstandards aufbauen.
Stell dir das Gerät wie eine Fernsteuerung für Weihnachtslichter vor, nur über Bluetooth: Die App sendet kurze Befehle, der Strip führt sie aus — Farbe ändern, Helligkeit anpassen, Effekte starten. Klingt harmlos. Was dabei über Funk geht, ist es leider nicht.
Einordnung in den Guide
Diese Fallstudie setzt die Methoden aus dem 5-Phasen-Assessment-Guide voraus. Die hier beschriebenen Befunde wurden mit Wireshark, JADX und einem BLE-Sniffer (nRF52840 Dongle) gewonnen. Wenn du die Werkzeuge noch nicht kennst, lies zuerst Phase 2: Traffic Capture und Phase 1: APK-Analyse.
Phase 1: Der GATT-Kanal — vollständig im Klartext
Aufzeichnung und Statistik
In einem 45-sekündigen BLE-Mitschnitt wurden 6.221 Pakete erfasst. Davon konnten 69 GATT Write Commands isoliert und analysiert werden — die direkten Steuerbefehle der App an den Strip.
Das Ergebnis war eindeutig: Alle 69 Befehle lagen im Klartext vor. Kein einziger war verschlüsselt, obfuskiert oder anderweitig geschützt.
GATT-Struktur
Der Strip exponiert seine Steuerung über eine einzige GATT-Charakteristik:
| Attribut | Wert |
|---|---|
| Service UUID | 0000fff0-0000-1000-8000-00805f9b34fb |
| Charakteristik UUID | 0000fff3-0000-1000-8000-00805f9b34fb |
| Berechtigungen | Write, Write Without Response |
| Authentifizierung | keine |
| Pairing erforderlich | nein |
Das bedeutet: Jedes BLE-fähige Gerät in Reichweite kann Befehle senden, ohne sich vorher gegenüber dem Strip identifizieren zu müssen.
Erkunde das GATT-Profil des Geräts interaktiv:
GATT Profile
Das Befehlsformat
Alle Steuerbefehle folgen einem einheitlichen Schema:
7E [len] [opcode] [param1] [param2] [param3] [param4] 10 EF
7E= Startbyte (immer gleich)[len]= Länge der Nutzdaten[opcode]= Befehlstyp (welche Funktion)[param1–4]= Parameter (abhängig vom Opcode)10 EF= Endbytes (immer gleich)
Ein konkretes Beispiel aus dem Mitschnitt — der Befehl für die Farbe Hellgrün (R=128, G=255, B=0):
7e 07 05 03 80 ff 00 10 ef
Dekodiert:
| Byte | Hex | Bedeutung |
|---|---|---|
| 1 | 7E | Startbyte |
| 2 | 07 | Länge (7 Datenbytes) |
| 3 | 05 | Opcode: Farbe (RGB) |
| 4 | 03 | Subparameter |
| 5 | 80 | R = 128 |
| 6 | FF | G = 255 |
| 7 | 00 | B = 0 |
| 8 | 10 | Param (fest) |
| 9 | EF | Endbyte |
Probiere verschiedene Befehle im interaktiven Packet Dissector aus — bewege die Maus über die einzelnen Felder:
LED-Strip Befehlsformat
Paketbytes (9):
Farbvorschau
rgb(128, 255, 0)
Befehlsverteilung im Mitschnitt
Die 69 aufgezeichneten Befehle verteilten sich auf sechs Kategorien:
| Typ | Anzahl | Anteil | Opcode |
|---|---|---|---|
| Farbsteuerung (RGB) | 30 | 43,5 % | 0x05 |
| Effektmodi | 25 | 36,2 % | 0x03 |
| Power (An/Aus) | 8 | 11,6 % | 0x04 |
| Helligkeit | 3 | 4,3 % | 0x01 |
| Geschwindigkeit | 2 | 2,9 % | 0x02 |
| Initialisierung | 1 | 1,5 % | 0x83 |
Klartext bedeutet: kein Reverse Engineering nötig
Wenn alle Befehle im Klartext übertragen werden, braucht ein Angreifer die App gar nicht zu analysieren. Es genügt, einen einzigen Mitschnitt zu machen und die Befehle direkt zu replizieren. Das Format ist so einfach, dass man mit Wireshark, einem Nachmittag und einem BLE-Adapter alles herausfindet, was man braucht.
Welche UUID identifiziert die Steuer-Charakteristik des LED-Strips?
Phase 2: Der Advertisement-Kanal — XOR-Obfuskation
Während der GATT-Kanal völlig offen ist, schien der Advertisement-Kanal auf den ersten Blick verschlüsselt: Die Nutzdaten der BLE-Advertisementpakete enthielten keine lesbaren Strings und wirkten auf den ersten Blick zufällig.
Spoiler: Sie waren es nicht.
Analyse der APK mit JADX
In der dekompilierten App fand sich die Klasse EncryptUtil mit folgendem XOR-Schlüssel:
byte[] key = {89, 76, 90, 75, 53, 41, 33, 41, 62, 72, 64,
118, 100, 98, 81, 68, 94, 68, 63, data_num};
// ASCII: "YLZK51!)>H@vdbQD^D?" + Counter-Byte (0–255)Der Schlüssel besteht aus 19 festen Bytes und einem variablen Counter-Byte (data_num), das von 0 bis 255 läuft. Auf den ersten Blick klingt das nach einem 20-Byte-Schlüssel — das wäre immerhin 160 Bit.
Der Kollaps auf ein einziges Byte
Das "iterative XOR" in EncryptUtil verknüpft das Ergebnis eines Bytes stets mit dem nächsten Schlüsselbyte:
result[i] ^= key[0]
result[i] ^= key[1]
...
result[i] ^= key[18]
Da XOR assoziativ und selbstinvers ist, kollabiert das zu:
result[i] ^= key[0] ⊕ key[1] ⊕ ... ⊕ key[18]
Das XOR aller 19 statischen Bytes ergibt einen einzigen effektiven Schlüssel:
key_bytes = [89, 76, 90, 75, 53, 41, 33, 41, 62, 72, 64, 118, 100, 98, 81, 68, 94, 68, 63]
effective_key = 0
for b in key_bytes:
effective_key ^= b
print(hex(effective_key)) # -> 0x66 (dezimal: 102)Effektiver Schlüsselraum: 256 Möglichkeiten (ein einziges Byte). Das ist triviales Brute-Force-Gebiet — oder direkte Berechnung, wenn man die APK hat.
Probiere es selbst aus — klicke auf "Start", um den schrittweisen Kollaps des 19-Byte-Schlüssels zu beobachten:
XOR-Schlüssel-Kollaps des LED-Strips
19 Schlüsselbytes (iterativ XOR-verknüpft):
Laufendes XOR-Ergebnis:
Drücke Start, um zu beginnenEin Schlüssel für alle Geräte weltweit
Der XOR-Schlüssel ist in der APK fest codiert — als ASCII-String, der in JADX sofort lesbar ist. Wer die App installiert hat (10+ Millionen Menschen), kann den Schlüssel aus der APK extrahieren. Damit lassen sich die Advertisement-Daten aller LED-Strips dieser Produktlinie weltweit entschlüsseln. Ein Schlüssel, ein Angriff, zehn Millionen Geräte.
Validierung: 138 Pakete, 100 % Entschlüsselungsrate
Im Mitschnitt wurden 138 Advertisement-Pakete erfasst. Mit dem extrahierten Schlüssel (0x66) konnten alle 138 vollständig entschlüsselt werden — 100 % Erfolgsrate.
Das Advertisement-Payload hat eine feste Struktur:
- Länge: 25 Byte
- Manufacturer ID:
0xBEE8 - Geräte-Status und Farbinformationen im Klartext (nach XOR-Dekodierung)
Warum kollabiert der scheinbar 19-Byte-XOR-Schlüssel auf einen effektiven 1-Byte-Schlüssel?
Phase 3: Replay-Angriff — empirisch bestätigt
Ein Replay-Angriff ist wie das Abfilmen einer Türöffnung und das spätere Abspielen des Videos vor der Kamera: Man muss gar nicht wissen, wie die Tür funktioniert — man wiederholt einfach, was einmal geklappt hat.
Nachweis im Mitschnitt
Im PCAP-Mitschnitt erscheint derselbe RGB-Befehl (Farbe Hellgrün: #80FF00) zweimal innerhalb von 180 Millisekunden — als exakt identische Pakete:
Frame @17.38s: 7e 07 05 03 80 ff 00 10 ef
Frame @17.56s: 7e 07 05 03 80 ff 00 10 ef
Kein Nonce. Kein Zeitstempel. Keine Sequenznummer. Der Strip akzeptiert beide Pakete ohne zu zögern.
Das bedeutet: Ein aufgezeichneter Befehl kann beliebig oft wiederholt werden. Ein Angreifer, der einmal einen Mitschnitt gemacht hat, kann den Strip danach unbegrenzt steuern — ohne die App, ohne Authentifizierung, ohne dass der Besitzer es bemerkt.
Replay-Angriffe sind einfach durchzuführen
Ein Replay-Angriff erfordert keine besonderen Kenntnisse. Es genügt ein handelsüblicher BLE-USB-Adapter (z. B. nRF52840 Dongle, ca. 10 Euro) und frei verfügbare Software wie btlejack oder GATTacker. Die Pakete müssen nicht einmal verstanden werden — einfach aufnehmen und abspielen.
Phase 4: Proof-of-Concept-Code
Der folgende Python-Code demonstriert einen vollständigen GATT-Exploit. Er verbindet sich ohne Pairing, sendet einen beliebigen RGB-Befehl und trennt die Verbindung wieder:
CHAR_UUID = "0000fff3-0000-1000-8000-00805f9b34fb"
def build_color_cmd(r: int, g: int, b: int) -> bytes:
"""Klartext-RGB-Befehl — keine Verschlüsselung auf GATT-Ebene."""
return bytes([0x7E, 0x07, 0x05, 0x03, r, g, b, 0x10, 0xEF])
def exploit(ble_device, target_addr: str, r: int, g: int, b: int) -> None:
# Verbindung ohne Pairing — der Strip verlangt es nicht
conn = ble_device.connect(target_addr).wait()
conn.discover_services().wait()
for svc in conn.database.services:
for char in svc.characteristics:
if str(char.uuid) == CHAR_UUID:
char.write(build_color_cmd(r, g, b)).wait()
conn.disconnect().wait()Aufruf:
# Setzt den Strip auf Rot — ohne irgendeine Autorisierung
exploit(ble_device, "AA:BB:CC:DD:EE:FF", r=255, g=0, b=0)Ethischer Rahmen
Dieser Code dient ausschließlich dem Verständnis der Schwachstelle. Das Ausführen gegen fremde Geräte ohne Genehmigung ist illegal. Wenn du eigene Geräte testen willst, lies zuerst den rechtlichen Rahmen.
Gefundene Schwachstellen
Überblick
| ID | Bezeichnung | CVSS-Score | Schweregrad |
|---|---|---|---|
| VULN-2026-006 | Vollständig unverschlüsselter GATT-Kanal | 8.3 | HIGH |
| VULN-2026-007 | XOR-Obfuskation im Advertisement-Kanal | 8.3 (Umgebung: bis 8.7) | HIGH |
| VULN-2026-008 | Keine Authentifizierung | 7.1 | HIGH |
| VULN-2026-009 | Kein Replay-Schutz | 6.5 | MEDIUM |
VULN-2026-006: Vollständig unverschlüsselter GATT-Kanal
CVSS-Vektor: AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L — Score: 8.3 HIGH
Alle 69 GATT Write Commands im Mitschnitt lagen im Klartext vor. Das bedeutet:
- Ein passiver Beobachter kann alle Befehle lesen, ohne etwas zu entschlüsseln.
- Ein aktiver Angreifer kann beliebige Befehle senden, ohne sich zu identifizieren.
- Reverse Engineering der App ist nicht erforderlich — das Protokoll ist direkt aus dem Mitschnitt lesbar.
OWASP IoT Top 10: I7 (Unsichere Datenübertragung), I9 (Unsichere Standard-Einstellungen)
VULN-2026-007: XOR-Obfuskation im Advertisement-Kanal
CVSS-Vektor: AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L — Score: 8.3 HIGH (Umgebungsanpassung: bis 8.7)
Der XOR-Schlüssel ist in der APK als ASCII-Klartext gespeichert und in JADX sofort sichtbar. Da er global für alle Geräte dieser Produktlinie gilt:
- Einmalige Extraktion aus einer APK kompromittiert alle Geräte weltweit.
- Angreifer müssen keinen eigenen Strip besitzen — nur die App aus dem Play Store.
- Die "Verschlüsselung" bietet null Vertraulichkeit und null Integrität.
OWASP IoT Top 10: I1 (Schwache, vorhersagbare Credentials), I7 (Unsichere Datenübertragung)
VULN-2026-008: Keine Authentifizierung
CVSS-Vektor: AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L — Score: 7.1 HIGH
Im gesamten 45-sekündigen Mitschnitt wurde keine einzige SMP-Sequenz (Security Manager Protocol) beobachtet. Es findet kein Pairing statt, keine PIN-Eingabe, keine kryptografische Identifizierung.
Das Gerät akzeptiert Befehle von jedem BLE-Gerät in Reichweite ohne jede Prüfung.
OWASP IoT Top 10: I2 (Unsichere Netzwerkdienste)
VULN-2026-009: Kein Replay-Schutz
CVSS-Vektor: AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L — Score: 6.5 MEDIUM
Empirisch bestätigt: Ein identisches Paket wird zweimal innerhalb von 180 ms gesendet und vom Gerät beide Male akzeptiert. Keine Sequenznummern, keine Nonces, keine Zeitstempel.
OWASP IoT Top 10: I7 (Unsichere Datenübertragung)
Gesamtbewertung
| Kriterium | Wert |
|---|---|
| Gesamtrisiko | HIGH |
| Höchster CVSS-Score | 8.3 |
| Anzahl gefundener Schwachstellen | 4 (3 HIGH, 1 MEDIUM) |
| Angreifer-Aufwand | Minimal — kein RE erforderlich |
| Exploit-Methode | Direkte GATT-Writes ohne Dekodierung |
| PCAP-Validierung | 69/69 Befehle dekodiert, 138/138 Advertisements entschlüsselt |
Warum dieser Fund besonders schwer wiegt
Die meisten der hier gefundenen Schwachstellen erfordern überhaupt kein Fachwissen. Das Protokoll ist im Klartext lesbar, das XOR ist in JADX sofort sichtbar, und ein Replay-Angriff braucht nur einen billigen BLE-Adapter. Bei 10+ Millionen Downloads bedeutet das ein massives, leicht ausnutzbares Angriffspotential für eine enorme Nutzerbasis.
Warum wiegt VULN-2026-007 (XOR-Obfuskation) trotz des 'verschlüsselten' Aussehens schwer?
Vergleich mit den anderen Fallstudien
Der LED-Strip steht stellvertretend für einen bestimmten Typ von BLE-Schwachstellen: Fehlende Sicherheit durch falsches Vertrauen in die Komplexität des Protokolls. Die Entwickler haben vermutlich angenommen, dass das proprietäre Befehlsformat und die XOR-Obfuskation ausreichen, um Angreifer abzuschrecken.
Im Vergleich:
- LED-Brille (Fallstudie 1): Setzt AES ein — aber im ECB-Modus mit hartkodiertem Schlüssel in der nativen Bibliothek. Stärker obfuskiert, aber ebenfalls kompromittiert.
- Körperwaage (Fallstudie 3): Sendet Gewichtsdaten ebenfalls im Klartext, kompromittiert aber primär die Privatsphäre der Nutzer statt die Gerätekontrolle.
Alle drei Geräte teilen dasselbe Grundproblem: Sicherheit wurde als nachträglicher Gedanke behandelt, nicht als Designanforderung.
Zusammenfassung
- Der GATT-Kanal des Smart-LED-Strips überträgt alle 69 Steuerbefehle vollständig im Klartext — kein Pairing, keine Verschlüsselung, keine Authentifizierung.
- Das Befehlsformat (
7E [len] [opcode] [params] 10 EF) ist trivial aus einem Wireshark-Mitschnitt rekonstruierbar. - Der Advertisement-Kanal ist scheinbar "verschlüsselt", aber das iterative XOR kollabiert mathematisch auf einen effektiven 1-Byte-Schlüssel (
0x66), der direkt aus der APK extrahierbar ist. - Da der Schlüssel global ist, reicht eine einzige APK-Extraktion, um die Advertisementdaten aller Geräte weltweit zu entschlüsseln.
- Kein Replay-Schutz: Aufgezeichnete Pakete werden beliebig oft akzeptiert — empirisch mit zwei identischen Paketen im 180ms-Abstand bestätigt.
- Vier Schwachstellen: 3 × HIGH (CVSS 7.1–8.3), 1 × MEDIUM (CVSS 6.5) — Gesamtrisiko HIGH.
Weiter zu: Körperwaage-Fallstudie | Zurück zur Übersicht