TBS Crossfire Analyse / Reverse Engineering von g3gg0

deadcat

aim for the bushes
Mitarbeiter
#1
Ich habe einen ganz interessanten Blogeintrag zu TBS Crossfire gefunden, der meiner Meinung nach sehr lesenswert ist. Zumindest, wenn man sich für derartigen Kram interessiert.
Alles Nachfolgende (inkl. Bilder) ist eine gekürzte und ins Deutsche übersetzte Version eines Artikels von g3gg0, namens [FPV] Analysis of TBS Crossfire, reverse engineering the air link.


Ich bin auf den Crossfire-Zug aufgesprungen und habe mir eine TBS Tango 2, sowie ein paar Nano RXs gekauft. Mir gefiel das kompakte Design der Fernsteuerung und die Idee, ein flexibles Telemetrieprotokoll zu haben. Natürlich habe ich mich auch mit dem seriellen Crossfire-Protokoll beschäftigt (das zwischen Quad und Empfänger), aber das ist eine andere Geschichte...
Ich weiß, dass es jetzt Tracer und sogar die Open-Source-Lösung ExpressLRS gibt, die auch ein ESP auf der RX-Seite bietet, was ziemlich cool ist. Zusätzlich WiFi auf der RX-Seite ist ein bisschen übertrieben, aber nice-to-have.

Wie auch immer. Ich habe festgestellt, dass der Crossfire Nano RX im Grunde ein PIC32 mit einem SX1272 LoRa-Modem ist. Ich fand es daher sehr praktisch, dass es ein ESP32-basiertes, einfach zu bedienendes LoRa-Gerät mit Display gibt: Das Heltec WiFi LoRa 32 (V2).
SAM_0748_800X800.png

Was können wir also damit machen? Können wir Crossfire sniffen? Können wir es hijacken? Oder können wir einige DoS-Angriffe durchführen? Lasst uns versuchen, das herauszufinden.

Zunächst müssen wir herausfinden, welche Funktionen des LoRa-Modems die Crossfire-Geräte verwenden und welche Frequenzen genutzt werden. Eine Möglichkeit wäre, die Firmware zu dumpen und sie zu rekonstruieren, wie ich es normalerweise am liebsten tue. Nach einigem Suchen gelang es mir, eine Liste der Firmware-Versionen und auch einige Links zu Firmware-Dateien zu finden, die ohne Authentifizierung verfügbar sind. Bei näherer Betrachtung dieser Dateien scheinen sie verschlüsselt zu sein.

grafik-9.png

Wenn man die Binärdateien mit einem von mir entwickelten Tool vergleicht (stellt es euch wie einen fortgeschrittenen Hex-Editor vor), kann man sehen, dass die Verschlüsselung wahrscheinlich eine Blockchiffre mit 8-Byte-Blöcken ist, oder vielleicht eine einfache XOR-basierte Verschlüsselung. Es sieht auch so aus, als gäbe es einen 10-Byte-Header, der die Hardware-ID und die Firmware-Version enthält.

2021-08-28_20-39-39.gif
Unterschied zwischen zwei Firmware-Versionen. Rote Bereiche unterscheiden sich zwischen zwei Versionen.

Ich habe diese Art der Verschlüsselung vor ein paar Jahren geknackt, aber das hat einige Zeit gedauert und ich wollte nicht zu viel Zeit hiermit verbringen. Spoiler: Ich habe trotzdem eine ganze Menge Zeit damit verbracht...

Die Verschlüsselung verdirbt also den Spaß und bringt keinen schnellen Erfolg.
Sollen wir nun die Firmware des PIC32MX170F256D mit einigen ChipWhisperer-Angriffen auslesen?
Nee, mein geliebtes IDA Pro Advanced unterstützt diesen Chip nicht einmal, also wäre es wahrscheinlich ein bisschen zu viel Arbeit, die Register usw. herauszufinden.
Also beschloss ich, die SPI-Kommunikation zwischen dem PIC32 und dem SX1272 zu sniffen. Da ich gerade keinen Logic Analyzer hier habe, aber ein paar FPGA-Boards, waren die nächsten Schritte klar.

Nach ein bisschen Löten hatte ich die nötigen Leitungen (MOSI, MISO, SCLK, SS) an ein CYC1000-Board von trenz electronic angeschlossen, das ich vor ein paar Jahren von ARROW bei einem Besuch der embedded world erhalten habe. Tolle Hardware. Nur ein paar Euro und ihr habt ein leistungsstarkes FPGA-Board auf eurem Schreibtisch.

Zuerst versuchte ich es mit einem dieser leicht erhältlichen Logic Analyzer, die mit sigrok PulseView arbeiten, aber das hat nicht wirklich funktioniert. Ich empfing nur ein paar Bits und dann Stille. Also beschloss ich, meinen eigenen SPI-Sniffer zu entwickeln.

Ich wollte keinen hohen Zeitaufwand, erinnert ihr euch?

Nachdem ich das Blockschaltbild in Quartus modelliert und den SystemVerilog-Entwurf der SPI-Log-Engine geschrieben hatte, war der Hardwareteil einsatzbereit. Ich hasse clock domain crossing. Ganz im Ernst. Vor allem, wenn ein doppelt getakteter FIFO aus der Intels IP-Bibliothek die Störungen nicht behebt.

grafik-10.png

Nach ein paar weiteren Stunden C#-Hacking entstand ein SPI-Sniffer-Tool, das die Daten vom FPGA über den seriellen USB-Port empfängt und die Lese- und Schreibvorgänge der SX1272-Register analysiert, um ein Log aller vorgenommenen Konfigurationen anzuzeigen. Es ermöglicht das Speichern der protokollierten Binärdaten und das Abspielen des Logs, falls ich später eine neue Parsing- oder Analysefunktion hinzufügen möchte.


2021-08-28_21-18-27.gif
Ein einfaches SPI-Sniffer-Frontend, das die Lese- und Schreibvorgänge der SX1272-Register parst.

Es protokolliert auch die Frequenzen und loggt, in welcher Reihenfolge die Kanäle gewechselt werden. Diese Kanal-Hopping-Reihenfolge gilt höchstwahrscheinlich nur für meine Konfiguration und meinen Bind Key - falls so etwas für Crossfire gilt. Für andere TX/RX-Paare erwarte ich eine andere Kanalreihenfolge.

Lasst uns kurz untersprechend und ein paar Begriffe klären:
"Kanal" - Die verwendeten Frequenzen sind alle gleich weit voneinander entfernt, daher nehme ich an, dass es eine logische Kanalnummerierung gibt, wobei 0 der Kanal mit der niedrigsten Frequenz ist, die ich gesehen habe
"Uplink" - Daten, die vom TX / der Fernsteuerung an den Copter gesendet werden
"Downlink" - Daten, die vom Quad empfangen werden

In den meisten Modi verwendet das Crossfire-Protokoll die Kanäle 0-49 zum Senden von Daten an den Empfänger, schaltet dann die Frequenz um genau 50 Kanäle nach oben und der Empfänger antwortet auf den Kanälen 50-99. Dann schaltet das Senden auf den nächsten Kanal gemäß der Hopping-Sequenz. Dieser Vorgang wird 150 Mal wiederholt, und dann wird die gesamte Hopping-Sequenz wiederholt.

grafik-11.png
Beispiel für Hopping-Sequenz.​


Im obigen Beispiel sendet der Sender also seine Daten auf Kanal 0, schaltet auf Kanal 50 um und wartet, bis der Empfänger die Daten bestätigt und Telemetrie sendet. Dann schaltet der Sender auf Kanal 18 um, sendet seine Uplink-Daten und wartet auf Kanal 68 (18+50) auf die Antwort des Empfängers. Nachdem alle Kanäle in dieser Sequenz durchlaufen wurden, beginnt er wieder bei Kanal 0.
Die RACE-Modi hingegen bleiben für Uplink- und Downlink-Daten auf demselben Kanal, verwenden aber die Kanäle 0-99 für das Hopping.
Die genauen Frequenzen könnten auch Teil des ursprünglichen Bindings sein, sodass meine Theorie falsch sein könnte. Ich werde das auch mit anderen Konfigurationen testen. Die gesamte Kommunikation wurde im FSK-Modus des LoRa-Modems durchgeführt, mit einer Frequenzverschiebung von 42,48 kHz und einer Bitrate von 85,1 kBaud.

Mein Setup nutzt folgende Frequenzen:

frequenzen.png
Die Frequenzen unterscheiden sich zwischen zwei getesteten Setups um ~15 kHz.​


Zu diesem Zeitpunkt begann ich, einen Arduino-Sketch für das Heltec-Board zu programmieren. Ich wollte Daten sehen, die aus der Luft gegriffen werden statt nur aus einem lahmen SPI-Log. Auch wenn es in diesem Post so aussieht, als hätte ich von Anfang an beide Richtungen der SPI-Kommunikation gesnifft, habe ich in Wirklichkeit nur die Schreibvorgänge an die LoRa-Chips gesnifft, die vom PIC32 übertragen wurden. Das reichte aus, um die Einstellungen zu erhalten, aber ich konnte nicht sehen, was der Tango 2 TX gesendet hat. Erst später wurde die gesamte SPI-Kommunikation geloggt.

Obwohl ich die Hopping-Sequenz habe, habe ich einen Arduino-Sketch gestartet, der einfach auf einem einzelnen Kanal auf Daten wartet. Hopping ist etwas, das später hinzugefügt werden kann. Zuerst muss der SX1276 des Heltec-Boards so konfiguriert werden, dass er Daten vom TX empfängt. Vielleicht haben Sie es schon bemerkt - SX1272 und SX1276. Einer davon befindet sich auf dem Nano RX und der andere auf dem Heltec-Board.
Glücklicherweise sind sie recht kompatibel, nur ein paar Register sind unterschiedlich. Also kein einfaches Abspielen der geloggten Daten, sondern alles manuell codieren. Das hätte ich sowieso tun müssen, also ist das schon okay.

Nach ein paar Stunden habe ich die ersten Daten von der Fernsteuerung erhalten. Die Payload (23 Byte Uplink, 13 Byte Downlink) war von unbekanntem Format, aber ganz offensichtlich kodiert. Interessant ist die Tatsache, dass bei der Übertragung über Funk nur 10 Bit Auflösung verwendet werden, während das serielle Protokoll 11 Bit verwendet. Ich gehe nicht davon aus, dass dies ein großes Qualitätsproblem für Crossfire darstellt, aber es war unerwartet und hat mich zumindest etwas verwirrt.

Ein weiterer interessanter Punkt:
Wenn man den 8-Kanal-Modus verwendet, sendet der Sender in jedem Paket die Sticks 0-7.
Wenn man stattdessen den 12-Kanal-Modus nutzt, sendet der Sender die Sticks 0-3 in jedem Paket und wechselt die Sticks 4-7 und 8-11 ab, wobei die Aktualisierungsrate halbiert wird.
Das alternierende Paket wird im ersten Byte der Uplink-Daten markiert (Bit 5 gesetzt).

Was das Timing betrifft, so ist ein RX/TX-Paar alle 6,666 ms aufgrund der 150 Hz-Rate durchaus zu erwarten. Das Downlink-Paket wird spätestens 2,6 ms, nachdem das Uplink-Paket gesendet wurde, empfangen. Dieser Zeitraum ist kürzer, da das Downlink-Paket ein paar Bytes kürzer ist. Ich werde ein wenig rechnen und eine Zeitplantabelle erstellen, wenn ich alle Zahlen richtig habe.

grafik-13.png
Aus einem späteren SPI-Log - die Sicht des Empfängers auf die Uplink- (RX) und Downlink- (TX) Kanäle.​


grafik.png
Einige Notizen zum Paketformat. RX bedeutet, dass der Empfänger dies empfangen hat. TX bedeutet, dass der Empfänger dies gesendet hat.​




Auch der CRC wurde offensichtlich am Ende der Pakete platziert, aber der Sender verwendete 16-Bit-CRC in den Uplink-Paketen und der Empfänger antwortete mit 8-Bit-CRC im Downlink-Paket.
Leider stimmten die CRCs nie überein und mir wurde schnell klar, dass es einen Seed geben muss, der zur Initialisierung der CRC verwendet wird. Sogar Pakete mit identischer Payload hatten, abhängig von der Hop-Nummer, unterschiedliche CRCs. Nach einer Sekunde, als sich die Hopping-Sequenz wiederholte, wiederholten sich auch die Init-Werte und damit auch die CRCs. Das bedeutet, dass jeder dieser 300 Sprünge pro Sekunde (150 Up-/Downlink-Pakete pro Sekunde) seinen eigenen Initialisierungswert für den CRC haben muss.

Nachdem ich das bemerkt hatte, war es nur noch eine Frage von ein paar Stunden Programmierarbeit, den exakten CRC-Typ zu bruteforcen.
CRC8: poly 0x07, refin=false refout=false xorout=0x00
CRC16: poly 0x1021, refin=true refout=true xorout=0x0000
Der Init-Wert ist jedoch unbekannt und hängt von der Hop-Nummer ab.

Wenn man ein Firmware-Upgrade durchführt oder den Empfänger bindet, geschieht dies im LoRa-Modus und verwendet den CRC8 mit dem Initialwert Null. Natürlich sind die Firmware-Daten OTA immer noch verschlüsselt und nur die Daten, die wir oben gesehen haben, direkt nach dem 10-Byte-Header.
Diese CRC-Initialisierung mit der Hop-Nummer und einem Seed-Wert ist eindeutig ein Zeichen dafür, dass TBS sicherstellen wollte, dass mehrere TXs in einem Gebiet sich nicht gegenseitig stören können, sodass falsche Stick-Werte von einem RX interpretiert werden, der die Daten nicht empfangen sollte.

Aber ist dies eine Art Sicherheitsfunktion?

Definitiv nicht. Ich kann die Hopping-Sequenz bereits durch Abhör- und Zeitmessungen erkennen und auch die CRC-Init-Werte aus der Luft abfangen, ohne dass es jemand merkt. Dann könnte ich diese Werte verwenden, um einen Sendevorgang vorzutäuschen und die Kontrolle über den Copter eines Opfers zu erlangen. Noch nicht fertig, aber mit dem, was ich im Moment weiß, leicht™ möglich.

Nachdem ich mit Timer-Problemen in der arduino-esp32-Bibliothek zu kämpfen hatte, die ein Fehler im SDK zu sein scheinen, war es möglich, das exakte Timing für das Channel-Hopping zu bestimmen.



Wenn man die Hopping-Sequenz korrekt befolgt, werden alle Stickwerte und Telemetrie-Metadaten korrekt aufgezeichnet. Crossfire hat eine Menge davon - sogar das Konfigurationsmenü des RX selbst wird über langsamere Telemetriekanäle übertragen. Hier ein dekodiertes Konfigurationsmenü meines v6.06 Nano RX mit allen Menüeinträgen und deren möglichen Optionen:

2021-07-28_00-34-50.gif

Um einige verlorene Pakete zu finden, habe ich Plots hinzugefügt, die Informationen wie RSSI (up/down), Frequenzkorrekturwerte und sogar Paket-Timings anzeigen.



Was jetzt?

Das hier hat mit meinem Setup funktioniert. Für mein gebundenes TX/RX-Paar. Bei einem anderen Paar, das ein freundlicher Mensch für mich getestet hat, hat es nicht funktioniert. Das war irgendwie zu erwarten. Ich bin sicher, dass es mit den genauen Frequenzen zu tun hat. Vom Protokoll her erwarte ich keine Überraschungen, da es keine Authentifizierung gibt, nur einen gesalzenen CRC und etwas Hopping, die beide zurückgerechnet werden können.
Derjenige, der es für mich getestet hat, erhält in den nächsten Tagen das SPI-Sniffing-Setup und dann werden wir seine FSK-Konfiguration (Frequenzen, Rate, Modulationsparameter) herausfinden und den Sniffer auch für sein Setup zum Laufen bringen. Ich denke, das ist der letzte Schritt zu einer generischen Lösung.
Er hat das Gerät erhalten, und nach einer kurzen Protokollierungssession war der Grund dafür, dass meine Vermutung der ersten verwendeten Frequenz ein wenig daneben lag. Die Basisfrequenz um 15 kHz nach unten zu verschieben, löste das Problem und funktionierte auch bei seinem Gerät.

Bedenkt außerdem, dass ich nur den 150-Hz-Modus analysiert habe, der FSK-Modulation verwendet. Es gibt auch andere Modi, mit denen das Crossfire-System arbeiten kann. Es gibt einen 50-Hz-Modus und einen noch langsameren LoRa-Modulationsmodus für wirklich schlechte Empfangssituationen. Ich bin mir nicht sicher, ob ich diese Modi supporten werde.
Auch Tracer könnte interessant sein, aber da sie die gleiche Basis haben, erwarte ich, dass es sich gleich oder zumindest ähnlich verhält.


Mein persönliches Fazit:
Stabilität - GUT
Sicherheit - SCHLECHT
Datenschutz - KEINER

Dies entspricht jedoch meinen Erwartungen, sodass ich damit zufrieden bin.

ESP32 Quellcode: GitHub - g3gg0/ESP32_CRSFSniffer
SPI-Log Quartus Dateien für MAX1000: GitHub - g3gg0/CRSF_RevEng_SPILog
SPI-Log C# Client und Parser: GitHub - g3gg0/CRSF_RevEng
 

Dr.Coolgood

Well-known member
#2
Wow! Das ist ja SENSATIONELL ! ! !

Seit wie vielen Jahren gibt sich die Industrie Mühe, ihr Treiben zu „verschlüsseln“ ? Und stümpert dabei so herum, dass ein Engagierter es einfach knackt?
Warum nicht gleich Open Source?
Was für eine sinnlose Vergeudung von Energie!
 
FPV1

Banggood

Oben Unten