„Im nächsten Teil wird nicht nur erklärt, sondern gemacht. Mit einem nRF52840-Dongle und Logitacker schneide ich den Kopplungsvorgang einer Unifying-Tastatur mit, leite daraus den Schlüssel ab und lese die Eingaben mit.“ Das habe ich am Ende des letzten Beitrags geschrieben. Jetzt löse ich es ein.

Kurz zur Erinnerung, falls du den ersten Teil nicht gelesen hast: Logitech-Funktastaturen mit Unifying-Empfänger verschlüsseln die Übertragung mit AES, aber die Kopplung selbst, also der kurze Moment, in dem sich Tastatur und Empfänger gegenseitig kennenlernen, hat eine nie gepatchte Design-Lücke. Wer genau in diesem Moment mithört, kann daraus den Schlüssel ableiten. Das ist CVE-2019-13052, seit 2019 öffentlich, von Logitech nie gefixt.

Logitech MX Keys mit Unifying-Empfänger und nRF52840-Dongle beim Mitschneiden einer Funk-Kopplung mit LOGITacker

Im ersten Teil stand das als Zeile in einer CVE-Datenbank und als Absatz in einer Offenlegung von Marcus Mengs. Diesmal nicht. Diesmal sitze ich mit einem Dongle für unter 30 Euro vor meiner eigenen MX Keys, schneide die Kopplung mit, und am Ende tippe ich auf der Tastatur, während eine Konsole live mitliest, was ich gerade drücke.

Vorweg die Grenze des Beitrags, damit du nicht am Ende danach suchst: den abgeleiteten AES-Schlüssel zeige ich nirgends. Warum, erkläre ich weiter unten. Die Kurzform: das war zum Zeitpunkt der Aufnahme ein aktives, funktionierendes Credential für meine echte Tastatur, kein historisches Artefakt.

Der Dongle, und welches Werkzeug ich benutze

Im ersten Teil hatte ich einen GeeekPi-Dongle über einen Amazon-Affiliate-Link verlinkt, als Empfehlung für genau dieses Vorhaben. Ehrlich gesagt bin ich am Ende nicht mit dem losgezogen. Zwischen dem Schreiben des ersten Beitrags und diesem Versuch lag noch eine Bestellung woanders, und am Ende kam das Board, das gerade greifbar war, nicht das verlinkte. In der Hand hatte ich stattdessen ein makerdiary nRF52840-MDK USB Dongle, anderer Hersteller, anderes Layout, aber derselbe Chip: ein Nordic nRF52840, auf dem Board direkt aufgedruckt mit N52840 QIAADO 2330FM. Kein Beinbruch, eher ein guter Aufhänger für den Bezugsquellen-Block weiter unten, den ich mir sonst hätte sparen können.

Verpackung des makerdiary nRF52840-MDK USB Dongle mit aufgedrucktem Board-Layout und Pinbelegung.
Die Verpackung des makerdiary nRF52840-MDK USB Dongle, mit aufgedrucktem Board und den Pin-Bezeichnungen.

Werkzeug ist LOGITacker, ursprünglich von mame82, also Marcus Mengs selbst, gebaut und heute unter RoganDawes auf GitHub aktiv gepflegt. Anders als etwa jackit läuft hier keine Angriffslogik auf dem Laptop, LOGITacker ist ein Standalone-Gerät. Die komplette Software steckt auf dem Dongle, bedient wird sie über eine serielle Konsole per USB, der Laptop selbst sieht am Ende nur ein weiteres USB-Gerät mit ein paar zusätzlichen Schnittstellen. Vier Boards werden offiziell unterstützt: der Nordic pca10059, zwei makerdiary-Boards, darunter genau das hier verwendete MDK Dongle, und ein Board von April Brother. Kein Zufallstreffer also. Das Board war von Anfang an ein sinnvoller Kauf für dieses Vorhaben, auch wenn es nicht das verlinkte war, und ich musste vor dem Kauf nicht einmal lange suchen, die Liste der unterstützten Boards steht direkt im Repository.

Geflasht habe ich logitacker_mdk_dongle.hex aus Release v0.2.3-beta, Stand 17. Januar 2020 und bis heute die aktuellste verfügbare Version. Kein Neubau aus dem Quellcode nötig, an der zugrundeliegenden Lücke hat sich seit 2020 nichts geändert. Pfostenstifte liegen dem Board bei, werden für dieses Experiment aber nicht gebraucht. Flashen und Betrieb laufen komplett über USB, kein Lötkolben in Sicht.

Das makerdiary nRF52840-MDK USB Dongle neben den beiliegenden, losen Pfostenstiftleisten. Auf dem Chip steht der Aufdruck N52840 2330FM.
Das Board selbst, mit lesbarem Chip-Aufdruck N52840 2330FM. Die Pfostenstifte liegen bei, werden für dieses Experiment aber nicht gebraucht.

Flashen, und wie ich den Bootloader falsch eingeschätzt habe

Ich bin mit der Annahme reingegangen dass so ein Nordic-Board ein serielles DFU spricht, das man mit nrfutil bedient. Falsch gedacht. Hält man beim Einstecken den Knopf auf dem Board, meldet es sich stattdessen als schnödes USB-Massenspeichergerät:

$ lsblk -o NAME,SIZE,LABEL,FSTYPE,MOUNTPOINT
sda   32,1M UF2BOOT vfat /media/kernel/UF2BOOT

Ein UF2-Bootloader, keine Nordic-eigene serielle Schnittstelle. LOGITackers Release liefert für dieses Board aber nur eine .hex-Datei, keine fertige .uf2. Also musste ich die Datei erst mit Microsofts eigenem uf2conv.py umwandeln, und zwar mit der richtigen Familien-ID, die ich mir nicht ausgedacht, sondern gegen die mitgelieferte uf2families.json geprüft habe:

$ grep -A2 NRF52840 uf2families.json
    "id": "0xada52840",
    "short_name": "NRF52840",

$ python3 uf2conv.py -f 0xADA52840 -c -o logitacker_mdk_dongle.uf2 logitacker_mdk_dongle.hex
Converted to uf2, output size: 470528, start address: 0x1000
Wrote 470528 bytes to logitacker_mdk_dongle.uf2

$ cp logitacker_mdk_dongle.uf2 /media/kernel/UF2BOOT/

Der Bootloader hat die Datei angenommen und das Laufwerk danach getrennt, das ist normales Verhalten beim Flashen. Was nicht normal lief: das Board hat sich anschließend nicht von selbst mit der neuen Firmware gemeldet. Erst ein einmaliges Aus- und wieder Einstecken hat es zurückgebracht, und dann korrekt:

$ lsusb
Bus 001 Device 021: ID 1915:520c Nordic Semiconductor ASA Logitacker by MaMe82

Vier neue Schnittstellen kamen dazu: seriell, Maus, Tastatur und HID-Rohdaten. Ein nettes Detail am Rand, die USB-Seriennummer trägt die Firmware-Version gleich mit, v0.2.3-beta.

Das nRF52840-MDK-Dongle im USB-Port eines Notebooks, mit durchgehend grün leuchtenden LEDs im Bootloader-Modus.
Im UF2-Bootloader-Modus leuchten die LEDs des Dongles durchgehend grün.

Erstkontakt, und die Adresse stand quasi auf dem Karton

Serielle Konsole auf, 115200 Baud, fertig. Der Standardmodus von Logitacker heißt discover, eine Art passiver Dauerscan, und der bringt schon etwas, bevor ich überhaupt ein Kommando eingetippt habe:

<info> LOGITACKER_PROCESSOR_DISCOVER: DISCOVERY: received valid ESB frame (addr C3:1F:D0:D1:07, len: 15, ch idx 0, raw ch 5, rssi 47)
<info> LOGITACKER_PROCESSOR_DISCOVER: discovered device is Logitech

C3:1F:D0:D1:07. Das ist keine zufällige Adresse. Die USB-Seriennummer meines Tastatur-Empfängers, die sowohl solaar show meldet als auch auf dem Gehäuse aufgedruckt steht, lautet C31FD0D1. Exakt dieselbe Bytefolge, als Präfix der Funkadresse. Ohne jeden Angriff, nur weil das Gerät eingeschaltet ist, lässt sich die Funkadresse eines Unifying-Empfängers aus der Seriennummer ablesen, die außen auf dem Gehäuse steht.

Ich habe kurz gebraucht, um das wirklich zu glauben, und den Empfänger deshalb aus dem Rechner gezogen und die Seriennummer noch einmal mit der Lupe der Handykamera abfotografiert, im Vergleich zur Konsolenausgabe daneben. Kein Tippfehler, keine Verwechslung, dieselben acht Zeichen. Für einen Angriff braucht es das ohnehin nicht, discover läuft passiv und komplett ohne dass ich irgendetwas an Tastatur oder Empfänger anfassen müsste.

Drei Versuche, die nichts brachten, und der eine, der klappte

Diagramm: zwei Kopplungsversuche über die Easy-Switch-Taste blieben ohne Funkanfrage beim Dongle, der dritte Versuch per Aus- und Wiedereinschalten der Tastatur war beim ersten Mal erfolgreich.
Zwei Versuche über die Easy-Switch-Taste brachten nichts, der dritte Versuch nach der dokumentierten Methode klappte sofort.

Um einen echten Kopplungsvorgang mitzuschneiden, muss erst einer stattfinden. Also den Mitschnitt-Modus starten und danach die schon gekoppelte MX Keys trennen, damit sie sich während laufendem Mitschnitt neu koppeln kann:

LOGITacker (discover) $ pair sniff run
<info> LOGITACKER_RADIO: Channel hopping stopped
<info> LOGITACKER_PROCESSOR_PAIR_SNIFF: Sniff pairing on address BB:0A:DC:A5:75
<info> ESB_ILLEGALMOD: Using channel table 'Unifying pairing'
<info> ESB_ILLEGALMOD: New channel table with length 11
$ solaar unpair 1DA452CF
Unpaired 1: MX Keys Keyboard (MX Keys) [408A:1DA452CF]

$ solaar pair C31FD0D1
Pairing: turn your new device on (timing out in 30 seconds).

Erster Versuch: Easy-Switch-Taste „1“ gehalten, laut solaar show genau der Kanal, der für diesen Rechner reserviert ist. Die LED blinkte brav weiter. Beim Dongle kam in den vollen 30 Sekunden, die solaar pair öffnet, nichts an:

<info> LOGITACKER_PROCESSOR_PAIR_SNIFF: dongle on channel 44
... (many more channel hops, receiver beacon tracked continuously) ...
<info> LOGITACKER_PROCESSOR_PAIR_SNIFF: Lost dongle in pairing mode, restart channel hopping

Zweiter Versuch, gleiche Methode, gleiches Ergebnis. solaar pair lief diesmal direkt in einen Timeout:

$ solaar pair C31FD0D1
solaar: error: Exception: pairing failed: device timeout

An der Stelle dachte ich kurz, das Board oder die Firmware hätten ein Problem, und habe testweise sogar den Kanal manuell auf den Standardwert zurückgesetzt und noch einmal von vorne begonnen. Half nichts. Meine Deutung dazu, ausdrücklich eine Deutung und kein gemessener Fakt: das Dauerblinken der Easy-Switch-LED zeigt vermutlich nur an, dass dieser Kanal aktuell keinen Empfänger kennt, keine aktive Funksuche. Analysiert habe ich das interne Verhalten der Tastatur nicht, dafür fehlt mir der Einblick, und ich will hier nichts behaupten, was ich nicht geprüft habe.

Dritter Versuch, diesmal nach der Methode, die die Logitacker-Dokumentation für Unifying-Geräte tatsächlich vorschreibt und die mit der Easy-Switch-Taste nichts zu tun hat: Tastatur ausschalten, Kopplungsfenster am Empfänger öffnen, Tastatur wieder einschalten. Erster Versuch nach dieser Methode, sofort erfolgreich:

<info> LOGITACKER_PROCESSOR_PAIR_SNIFF: PAIR SNIFF data received on channel 5
<info> LOGITACKER_PROCESSOR_PAIR_SNIFF: PAIR SNIFF assigned C3:1F:D0:D1:08 as new sniffing address
<info> LOGITACKER_PAIRING_PARSER: Device name: MX Keys
<info> LOGITACKER_PAIRING_PARSER: Device RF address: C3:1F:D0:D1:08
<info> LOGITACKER_PAIRING_PARSER: Device serial: 1D:A4:52:CF
<info> LOGITACKER_PAIRING_PARSER: Device WPID: 0x408A
<info> LOGITACKER_PROCESSOR_PAIR_SNIFF: device automatically stored to flash
<info> LOGITACKER_PROCESSOR_PAIR_SNIFF: Sniffed full pairing, moving on with passive enumeration for C3:1F:D0:D1:08

solaar show bestätigt unabhängig dieselbe Seriennummer 1DA452CF und denselben WPID 408A für dieselbe Tastatur, wobei die neu ersniffte Funkadresse C3:1F:D0:D1:08 denselben Präfix trägt wie die passive Sichtung von vorhin, nur eine Adresse höher.

Was ich da wirklich in der Hand hatte, und was ich bewusst nicht zeige

Ein erfolgreich mitgeschnittener Kopplungsvorgang liefert bei Logitacker fünf Dinge: Gerätename, Funkadresse, Seriennummer, WPID, und einen 16 Byte langen AES-Schlüssel. Die ersten vier stehen oben, alle unabhängig gegen Solaar geprüft. Der Schlüssel steht hier nicht. Kein einziges Byte davon, auch keine erfundenen Beispielwerte, die nur wie ein Schlüssel aussehen würden.

Der Unterschied zur EK-Zertifikat-Entscheidung im TPM-Beitrag lässt sich in einem Satz sagen: dort ging es um eine dauerhafte Geräte-Kennung, eindeutig, aber für sich genommen nicht ausnutzbar. Hier ging es um ein Credential, das im Moment der Aufnahme aktiv gültig war und echten Zugriff auf meine echte, gerade auf meinem Schreibtisch liegende Tastatur bedeutet hätte. Direkt im Anschluss an den Mitschnitt aus dem nächsten Abschnitt habe ich die Kopplung deshalb noch einmal neu gemacht, um den mitgeschnittenen Schlüssel ungültig zu machen:

$ solaar unpair 1DA452CF
Unpaired 1: MX Keys Keyboard (MX Keys) [408A:1DA452CF]

$ solaar pair C31FD0D1
Pairing: turn your new device on (timing out in 30 seconds).
Paired device 1: MX Keys Keyboard (MX Keys) [408A:1DA452CF]

Wieder die Aus-und-Einschalt-Methode, wieder auf Anhieb erfolgreich. Der Schlüssel aus dem Mitschnitt ist damit nicht mehr der Schlüssel, der heute im Einsatz ist. Falls du dich fragst, ob das den Beweis im nächsten Abschnitt irgendwie entwertet: nein, der wurde vorher aufgezeichnet, die Rotation kam erst danach.

Der Beweis: Tastenanschläge in Echtzeit entschlüsselt

Diagramm: vom Pairing-Handshake über den mitgeschnittenen und abgeleiteten AES-Schlüssel bis zur Live-Entschlüsselung der Tastenanschläge, wobei der Schlüssel selbst nicht gezeigt wird.
Der Ablauf vom mitgeschnittenen Handshake bis zum entschlüsselten Klartext. Der Schlüssel selbst taucht in diesem Beitrag nirgends auf.

Nach dem erfolgreichen Mitschnitt wechselt Logitacker automatisch in einen passiven Mithör-Modus. Ich habe angefangen, auf der frisch wieder gekoppelten MX Keys zu tippen, und die Konsole hat live mitgeschrieben. Vier Tastendrücke aus derselben Sitzung, jeder mit eigenem Rohframe und eigenem entschlüsseltem Ergebnis:

ZählerVerschlüsselter RohframeEntschlüsselter Klartext-ReportErkannte Taste
4693281200 D3 45 B3 46 90 36 E6 B0 93 46 93 28 12 00 00 00 00 00 00 00 ED00 13 00 00 00 00 00 C9P
4693281400 D3 82 5E 0C 2F 89 D7 B2 86 46 93 28 14 00 00 00 00 00 00 00 6500 17 00 00 00 00 00 C9T
4693281600 D3 C7 B6 18 D3 34 82 1E 3D 46 93 28 16 00 00 00 00 00 00 00 9D00 37 00 00 00 00 00 C9.
4693281800 D3 68 E2 F1 7D 3C 6B D6 3D 46 93 28 18 00 00 00 00 00 00 00 A200 28 00 00 00 00 00 C9ENTER

Nacheinander gelesen: P, T, ., ENTER. Kein vollständiges Wort, sondern das Ende eines Satzes, den ich in dem Moment tatsächlich getippt habe, plus Enter. Genau das steht hier auch so, nicht schöngeredet zu mehr, als es ist.

Die Rohframes in der Tabelle sind Chiffretext, unbedenklich zum Zeigen, reines Rauschen ohne den Schlüssel. Was daraus die Klartext-Reports macht, ist der Schlüssel aus dem vorigen Abschnitt, und der taucht hier nicht auf. Ich habe dabei ziemlich bewusst langsam getippt, fast schon ein bisschen albern vor dem eigenen Bildschirm, nur um in der Konsole klar zuordnen zu können, welche Zeile zu welchem Tastendruck gehört. Es hat trotzdem gedauert, bis ich wirklich geglaubt habe, was da steht.

Was das nicht war

Eine kurze, ehrliche Einordnung. Dieser Versuch endet beim Mitlesen. Ich habe keine Einschleusung versucht, obwohl derselbe bekannte Schlüssel das technisch auch für gefälschte, eingeschleuste Tastenanschläge ermöglicht hätte, also für aktiven Angriff statt reinem Zuhören. Das ist die andere Hälfte von CVE-2019-13052, und die habe ich bewusst nicht angefasst. Nicht aus technischer Unfähigkeit, Logitacker kann das nachweislich, sondern weil eine aktive Einschleusung gegen die eigene Tastatur eine andere Kategorie Experiment ist als reines Zuhören, und weil das für den Beleg der Lücke in diesem Beitrag nicht nötig war.

Was das für die Empfehlung aus Beitrag 1 bedeutet

Im ersten Teil stand am Ende eine Rangfolge: Kabel für wirklich Geheimes, Logi Bolt oder sauberes Bluetooth fürs Büro, vorhandenes Unifying behalten und pflegen, No-Name-Funktastaturen ersetzen. An dieser Rangfolge ändert dieser Beitrag nichts. Was sich ändert, ist der Status des Arguments dahinter. Die Kopplungs-Lücke ist jetzt kein Datenbankeintrag mehr, sondern ein Ablauf, den ich mit handelsüblicher Hardware für unter 30 Euro selbst gegen mein eigenes Gerät durchgeführt habe, an einem gewöhnlichen Nachmittag, ohne Speziallabor und ohne Vorwissen, das ich mir nicht selbst aus der Dokumentation hätte holen können. Das praktische Risiko bleibt so klein wie im ersten Teil beschrieben, gebunden an den kurzen, seltenen Moment der Kopplung. Aber „das ist doch nur graue Theorie“ trägt als Einwand jetzt nicht mehr, und genau das war für mich der eigentliche Grund, diesen zweiten Teil überhaupt zu schreiben.

Was offen bleibt

  • Warum die Easy-Switch-Taste keine Kopplungsanfrage auslöst, bleibt meine Deutung des Dauerblinkens, keine bestätigte Analyse des Tastatur-internen Verhaltens. Sicher war ich einfach nur zu dumm die Tasten in der richtigen Reihenfolge zu drücken. gefettfingert
  • Die aktive Einschleusungshälfte von CVE-2019-13052 habe ich mit dem bekannten Schlüssel nicht ausprobiert. Ob und wann daraus noch ein dritter Teil wird, ist offen. Das hängt nun also an euch. Wenn ich sehen wollte, wie ich einfach Text oder Kommandos einschleuse, sagt es mir 🙂
  • Ob sich derselbe Ablauf gegen andere Unifying-Tastaturen als meine MX Keys genauso zuverlässig reproduzieren lässt, habe ich nicht geprüft. Aber hey, wir sind uns nun wohl alle sicher, dass es klappen wird, oder?
  • Die genaue Ursache der beiden ersten Kopplungsversuche, ob Timing, Tastatur-Firmware-Logik oder etwas Drittes, habe ich nicht bis auf die Funkebene nachverfolgt. gefettfingert

Bezugsquellen

Der folgende Link ist ein Affiliate-Link (Werbung). Kaufst du darüber, bekomme ich eine kleine Provision, für dich ändert sich am Preis nichts.

Der Dongle in diesem Beitrag war tatsächlich ein makerdiary nRF52840-MDK USB Dongle, über einen anderen Kanal bezogen. Der verlinkte Dongle trägt denselben Nordic-nRF52840-Chip, ist aber ein anderes Board von einem anderen Hersteller. Ob Logitacker dieses konkrete Modell offiziell unterstützt, habe ich nicht geprüft. Offiziell gelistet sind der Nordic pca10059, zwei makerdiary-Boards und ein Dongle von April Brother.

Siehe auch

Wenn ich irgendwo danebenliege, korrigiert mich gerne, dann lerne ich selbst etwas. Und die Frage an euch zum Schluss: Würdet ihr für ein bisschen mehr Sicherheit auf Logi Bolt oder Kabel umsteigen, oder ist euch das Restrisiko bei einer gepflegten Unifying-Tastatur egal? Wenn ihr mögt, dürft ihr mich dazu sehr gerne fragen.

Fediverse-Reaktionen