- Durch Reverse Engineering des SWL01U-Chips in einem alten Yamaha-PSR-E433-Synthesizer wurde Code allein über USB-MIDI-SysEx-Nachrichten in den RAM geschrieben und das Bad-Apple-Video auf dem LCD angezeigt
- Mit JTAG-IDCODE
0x3f0f0f0fund Experimenten mit OpenOCD/GDB wurde bestätigt, dass sich der Chip wie ein ARM7TDMI-Kern verhält; außerdem wurden das interne 64-KiB-ROM und die externe 16-MiB-Flash-Firmware ausgelesen - In der Firmware befand sich eine versteckte Shell, die über MIDI SysEx läuft; nach
loginund dem Passwort#0000konnten Befehle zum Lesen/Schreiben von Speicher verwendet werden - Über einen Befehl zum Schreiben beliebiger Speicherbereiche wurde ARM-Code in den RAM injiziert und anschließend die Rücksprungadresse auf dem Stack überschrieben, wodurch Codeausführung allein durch das Abspielen einer MIDI-Datei möglich wurde – ohne JTAG oder UART
- Die LCD-Ausgabe wurde über CGRAM-Steuerung, Kopieren der Task-Tabelle, Deaktivieren des Display-Tasks und Ersetzen des Shell-Callbacks verbessert; dabei sank die übertragene Datenmenge pro Frame von 6732 Byte auf 92 Byte
Untersuchung des Yamaha PSR-E433
- Das Zielgerät war ein lange genutzter Yamaha PSR-E433-Synthesizer; auf dem Mainboard befanden sich neben der Beschriftung
DMLCDzwei Flash-Chips, ein RAM-Chip und einYAMAHA SWL01U-Chip - Öffentliche Informationen zum SWL01U gab es kaum; nur ein online gefundener Beitrag erwähnte, dass er möglicherweise auf einem SuperH-CPU-Kern basiere
- Im Service-Handbuch des ähnlichen Modells E443 war das SWL01U-Pinout enthalten; dort waren
TESTN,PROTN, zwei bidirektionale UARTs und JTAG-Testpunkte ausgewiesen - Der erste Ansatz verfolgte vier Wege
- Manipulation der Pins
TESTNundPROTN, um Änderungen im Bootmodus zu prüfen - Anlöten an den UART-Tx-Pin, um Ausgaben zu beobachten
- Auslesen des Chip-Identifikationscodes über JTAG
- Entfernen der Flash-Chips zum Dumpen der Firmware
- Manipulation der Pins
- Bei Aktivierung von
TESTNbootete der Synthesizer nicht;PROTNveränderte das Verhalten nicht - Obwohl direkt an einen ungenutzten UART-Tx-Pin gelötet wurde, gab es in allen vier Kombinationen von
TESTN/PROTNkeinerlei Ausgabe
ARM7TDMI-Verhalten über JTAG sichtbar
- JTAG erfordert je nach Herstellerimplementierung detaillierte Schaltungsinformationen, aber zunächst wurde mit OpenOCD das von fast allen Geräten unterstützte Auslesen des IDCODE versucht
- OpenOCD meldete den IDCODE
0x3f0f0f0f; dieser Wert schien mit ARM7-basierten Mikrocontrollern wie den STMicroelectronics-STR7xxx- oder Atmel-SAM7xxx-Familien in Verbindung stehen zu können - Nachdem SWL01U in OpenOCD als
arm7tdmi-Target angegeben wurde, gelang die Verbindung; es wurden zwei Hardware-Breakpoint/Watchpoint-Einheiten angezeigt - Wenn die Ausführung mit GDB angehalten und fortgesetzt wurde, änderte sich der Stromverbrauch des Boards vorhersehbar
- während der Ausführung etwa 115 mA
- im angehaltenen Zustand etwa 98 mA
- Diese Stromänderung war ein starkes Signal dafür, dass tatsächlich ein ARM7TDMI-Kern angehalten und fortgesetzt wurde
Dump von ROM und Flash-Firmware
- Laut ARM7TDMI-Dokumentation liegt der Reset Vector an Adresse
0; beim Lesen von Adresse0mit GDB war ein Sprungbefehl der Formldr pc, [pc, #24]zu sehen - Ein 16-MiB-Dump ab Adresse
0wurde in Cutter geöffnet, doch Zeichenketten wiederholten sich alle 64 KiB- Beispiel:
SWL01U Internalwiederholte sich bei0x0000bfd0,0x0001bfd0,0x0002bfd0usw.
- Beispiel:
- Aufgrund des Wiederholungsmusters und der Zeichenketten wurde dieser Dump nicht als externer Flash, sondern als interner Speicher des Chips eingeordnet; für den SWL01U wurde ein 64-KiB-ROM festgehalten
- Das Sprungziel des Reset Vectors war
0x02000000; beim erneuten Dump von 16 MiB ab dieser Adresse gab es keine Wiederholungen - Der externe Flash-Dump enthielt Zeichenketten, die während der Nutzung des Synthesizers sichtbar sind
GrandPnoTr1 will be OverWritten!BogiWogi
- Die bestätigte Speicheraufteilung lautet
- internes ROM:
0x00000000, 64 KiB - externer Flash:
0x02000000, 16 MiB - Beim Booten übergibt das ROM die Kontrolle sofort an den externen Flash
- internes ROM:
Die in Ghidra gefundene versteckte Shell
- Da Cutter allein für die Analyse nicht ausreichte, wurde zu Ghidra gewechselt; über Zeichenketten und xrefs wurde die Firmware-Struktur nachvollzogen
- Zeichenketten wie
help,?,infoundverlagen nahe beieinander, und jede war mit einem Array verknüpft, das wie Paare aus Befehlsname und Funktionszeiger aussah - Die Befehlsverarbeitungsfunktion hatte die Form einer State Machine; anhand der Zeichenketten
loginundPasswd Errorwurde bestätigt, dass es sich um eine Shell mit Login-Prozedur handelte - Die Shell-Eingabeverarbeitung durchläuft einen 256-Byte-Ringpuffer zeichenweise und führt einen Befehl aus, sobald ein
\r-Zeichen erkannt wird - Der Login-Ablauf war wie folgt
- Eingabe von
logingibtpasswd?aus - Eingabe des Passworts
#0000gibtlogin OKaus - Danach können Befehle ausgeführt werden
- Eingabe von
- Zu den identifizierten Shell-Befehlen gehören
logout,help,?,info,verstack,perf-on,perf-off,perf-dispd,dp,d xxxxx,d/s xxxxxm ADDRESS DATA,m/b ADDRESS DATA,m/w ADDRESS DATA,m/l ADDRESS DATA
- Der Befehl
infolieferte folgende Informationen zurückDevelopName PSR-E433DevelopNumber #3341Main DevelopNumber #3341Make data & time MAY 16 2012 19:00:57J/E Select English
Shell über USB-MIDI SysEx
- Die Shell-Ausgabefunktion teilte jedes Byte in ein oberes und ein unteres 4-Bit-Nibble auf, legte sie in separate Bytes und fügte davor und dahinter einen festen Header und Footer an
- Die Paketstruktur für den Prompt
>sah wie folgt aus- Header:
F0 43 73 01 52 19 00 00 - Payload:
03 0E 02 00 - Footer:
F7
- Header:
- MIDI-SysEx-Nachrichten beginnen mit
0xF0, enthalten eine Hersteller-ID und Payload und enden mit0xF7; im Payload sind nur Bytes mit MSB 0 erlaubt 0x43im Header war die Yamaha-Hersteller-ID, und die Shell-Paketstruktur passte zum Format von Yamaha-SysEx-Nachrichten- Der USB-Descriptor des Synthesizers enthielt nur ein MIDI-Interface und keinen separaten seriellen Port
- Mit einem Python-Skript, das zwischen Terminal und Shell-Protokoll übersetzte, konnte über USB-MIDI mit der Shell kommuniziert werden
Codeausführung per MIDI-Shellcode
- Der Shell-Befehl
m/l AAAAAAAA DDDDDDDD\rführt einen 32-Bit-Speicherschreibvorgang aus; Adresse und Daten werden als hexadezimales ASCII übergeben - Selbst beim Schreiben einer 4-Byte-Payload wuchs die tatsächlich übertragene Datenmenge stark an
- Befehlsbytes werden jeweils in zwei 4-Bit-Nibble-Bytes umgewandelt
- Der SysEx-Nachricht werden 9 Byte hinzugefügt
- Alle 3 Byte werden in ein 4-Byte-USB-MIDI-Paket gekapselt
- Für einen 4-Byte-Schreibvorgang müssen 72 Byte an den Synthesizer gesendet werden
- Einschließlich Echo und Prompt laufen insgesamt 396 Byte hin und her
- In einem RAM-Bereich, der offenbar nicht genutzt wurde, wurde ARM-Assembly-Code abgelegt; anschließend wurde eine Stack-Rücksprungadresse überschrieben, um diesen Code auszuführen
- Die erste Payload rief eine interne Zeichenketten-Ausgabefunktion der Firmware auf und zeigte
HeloWrldim 8-Zeichen-Textbereich des LCDs an - Diese Methode funktioniert auch ohne JTAG oder UART; es genügt, die Nachrichten in eine MIDI-Datei einzubetten und diese abzuspielen
- Für die PSR-E433-Firmware 1.02 wurde auch eine MIDI-Datei bereitgestellt; es wurde jedoch gewarnt, dass das Abspielen auf anderen Yamaha-Geräten oder anderen Firmware-Versionen des PSR-E433 unvorhersehbares Verhalten auslösen kann
Bad Apple auf dem LCD anzeigen
- Der LCD-Controller des Yamaha PSR-E433 ist ein ML9040A und grundsätzlich für die Verarbeitung von Punktmatrix-Textzeichen ausgelegt
- Das LCD enthält neben dem Punktmatrixbereich auch Notenanzeigen, einen 7-Segment-Bereich, Akkordanzeigen und einen unteren Bereich zur Darstellung der Tastatur
- Der ML9040A besitzt drei Speicherarten
- DDRAM: vom Host geschriebene Zeichendaten, die angezeigt werden sollen
- CGROM: wandelt Zeichencodes in Grafikmuster um
- CGRAM: erlaubt dem Host, bis zu 8 benutzerdefinierte Zeichen zu definieren
- Die Firmware steuerte Nicht-Text-Anzeigeelemente unterhalb der Punktmatrix durch Manipulation des CGRAM; über diesen Pfad ließen sich benutzerdefinierte Grafiken anzeigen
- Eine Firmware-Funktion zum Senden beliebiger Daten an den LCD-Controller wurde gefunden und ein Schachbrettmuster in das CGRAM geladen, doch die Firmware aktualisierte das CGRAM weiterhin und überschrieben es kurz darauf
Steuerung der Display-Aktualisierung durch RAM-Manipulation
- Da ein direktes Überschreiben des Flashs das Gerät bricken könnte, wurden alle Experimente auf RAM-Manipulation beschränkt, damit sie sich durch Neustarten der Stromversorgung rückgängig machen lassen
- In der Firmware gab es eine Struktur, die wie ein primitives RTOS aussah; im Flash befand sich eine globale Tabelle, die Callback-Funktionen, Stacks und Attribute von 64 Tasks definierte
- Beim Booten teilt die Flash-Firmware dem ROM die Position der Task-Tabelle mit, und das ROM speichert diese Position in einer globalen Variable im internen SRAM
- Kopiert man die Task-Tabelle in den RAM und ändert, dass das ROM die neue Tabelle nutzt, lassen sich Task-Callbacks ersetzen, ohne den Flash zu verändern
- Der Callback des Display-Aktualisierungs-Tasks wurde durch den Standard-Idle-Callback ersetzt, sodass die Firmware das CGRAM nicht weiter überschreiben konnte
Verbesserungen bei Übertragungseffizienz und Bildfehlern
- Die erste Bad-Apple-Implementierung funktionierte, hatte aber wegen niedriger Übertragungseffizienz eine sehr geringe Framerate und Bildartefakte
- Selbst wenn pro Frame nur 64 Byte CGRAM und das Überschreiben einer 32-Bit-Rücksprungadresse gesendet wurden, betrug die tatsächlich übertragene Menge 6732 Byte pro 70 Byte Payload
- Die Hauptgründe für die geringe Effizienz waren zwei Punkte
- Daten mussten in Form von Shell-Befehlen verpackt werden
- Der Synthesizer echote jeden Befehl zeichenweise als großes SysEx-Paket
- Durch Ersetzen des Shell-Task-Callbacks durch einen eigenen Callback, der Rohdaten entgegennimmt und nicht antwortet, ließen sich Command-Wrapping und Echo-Overhead entfernen
- Nach zusätzlichen Packing-Optimierungen sank die übertragene Datenmenge pro Frame von 6732 Byte auf 92 Byte, eine Reduktion um den Faktor 73
- Die verbleibenden Artefakte entstanden, weil LCD-Kommunikation und das Scannen der Panel-Tasten/LEDs dieselben 8 GPIO-Leitungen gemeinsam nutzen
- Die finale Implementierung schrieb nicht direkt auf das LCD, sondern forderte den LCD/Panel-Multiplexing-Task auf, die gewünschten Daten zu senden, nachdem er den Panel-Scan abgeschlossen hatte; so wurden Bildfehler vermieden
Finale Ablaufprozedur und verbleibende Analyseziele
- Der finale Ablauf zur Anzeige eines LCD-Videos per MIDI sieht folgendermaßen aus
- In die Shell einloggen
- Ausführungscode mit dem Speicherschreibbefehl der Shell in den RAM schreiben
- Die Stack-Rücksprungadresse überschreiben, um den RAM-Code auszuführen
- Die Task-Tabelle in den RAM kopieren
- Die neuen Task-Tabellen so ändern, dass sie aufeinander zeigen
- Das ROM so ändern, dass es die neue Task-Tabelle verwendet
- Den Callback des Display-Tasks durch den Standard-Idle-Callback ersetzen
- Den Shell-Task-Callback durch einen eigenen Callback ersetzen
- Im eigenen Callback MIDI-Daten entpacken und an den LCD/Panel-Multiplexing-Task weitergeben
- Videoframes per MIDI zuführen
- Das Verständnis der MMIO-Bereiche des SWL01U ist noch begrenzt; auch der vom Haupt-ARM-Kern getrennte DSP bleibt ein Analyseziel
- Verwandte Materialien
1 Kommentare
Meinungen auf Hacker News
SuperH steckte auch im Sega 32X, Sega Saturn und Sega Dreamcast und wurde auch in einigen frühen Pocket PCs wie dem HP Jornada verwendet.
Die meisten Pocket PCs basierten allerdings auf ARM.
Die Prämisse ist geradezu absurd, und umso erstaunlicher ist, dass es tatsächlich gelungen ist.
Es wurde „eine weitere Packing-Optimierung“ erwähnt; mich würde interessieren, wie die Frames übertragen werden.
Wenn die Dot-Matrix aus acht 7x5-Zeichen besteht, wären das insgesamt 280 Bit, also 40 Gruppen à 7 Bit pro Frame, aber es sieht so aus, als würde für die Übertragung der doppelte Platz verwendet.
Ich frage mich, ob das wegen der Steuerdaten verschwendet wird oder ob die Übertragungsmethode einfach etwas suboptimal ist.
Dazu kommen 9 Byte für Paket-Header und -Footer.
Im Artikel steht wohl 92, aber das scheint falsch berechnet zu sein.
Einen Weg zu finden, alle 7 Bit zu nutzen, war zu schwierig, daher wurde im Vergleich zur ursprünglichen Methode eine Lösung gewählt, die nur minimal schlechter als das Optimum ist.
Wer den genauen Algorithmus wissen möchte, kann sich diese noch nicht aufgeräumten Code-Dateien ansehen: https://github.com/portasynthinca3/swl01u/blob/master/fun/bi..., https://github.com/portasynthinca3/swl01u/blob/master/fun/ba...
Wenn „ich habe nicht viel Erfahrung mit Reverse Engineering“ so aussieht, frage ich mich, wo der Rest von uns steht.
Stufe 1 ist neu und enthusiastisch, weiß aber, dass man noch nichts weiß; Stufe 2 ist „ich bin Gott“, Stufe 3 ist die „ich bin ein Idiot“-Phase.
Es heißt zwar „weltweit erster MIDI-Shellcode“, aber auf den meisten großen Plattformen gibt es MIDI-Shellcode schon seit über 20 Jahren: https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=midi
Natürlich war es SysEx.
In Standard-MIDI ist SysEx so etwas wie ein Inline-Assembler in Python.
In fast jedem MIDI-Gerät verbergen sich undokumentierte proprietäre Dinge.
Ein MIDI-Keyboard anschließen, Am6,9/G# spielen, und ein Terminalfenster mit Root-Rechten öffnet sich – das wäre wirklich cool.
Ich habe kürzlich nachgesehen, was man braucht, um MIDI zu fuzzing, und Materialien gefunden, um dafür .mid-Dateien zu erzeugen, aber das war nicht ganz das, wonach ich gesucht hatte.
Stattdessen scheint es sich zu lohnen, diese Richtung zu erkunden.
Es ist ziemlich schade, dass heutige Synthesizer es scheinbar immer weniger nutzen, insbesondere Roland.
Behringer unterstützt es aber immer noch ziemlich gut.
Der Deepmind zum Beispiel hat ohnehin schon einen guten MIDI-CC-Bereich, ist über SysEx aber fast zu 100 % programmierbar.
Erstaunliche Forschung.
Erinnert ein wenig an die Arbeit von 2017, in der Shellcode in echte DNA/RNA-Moleküle synthetisiert wurde, um Remote Code Execution auf DNA-Sequencing-Geräten zu demonstrieren: https://www.usenix.org/conference/usenixsecurity17/technical...
Ich wollte schon sagen: „Als Nächstes OSC?“, aber offenbar dominiert MIDI noch immer.
Ich empfehle, den ganzen Artikel zu lesen, aber die Kernsätze sind für mich diese:
„Diese Verrückten von [Keyboard-Hersteller] haben eine Shell gebaut, die über MIDI-SysEx-Nachrichten auf USB läuft.“
„Der interessanteste Befehl ist der zum Lesen/Schreiben beliebiger Speicherbereiche. Wenn man will, kann man per MIDI im Speicher des Synthesizers herumsehen und herumstochern.“
„Wenn man möchte, kann man diese Nachrichten in eine MIDI-Datei schreiben und sie wie jede andere MIDI-Datei auf dem Synthesizer abspielen. Hm, da kommt mir eine gute Idee …“
„Nach unzähligen schlaflosen Nächten im Firmware-Dschungel fand ich eine Funktion, die beliebige Daten an den LCD-Controller sendet.“
In gewisser Weise ist das ein kleiner Blick in den Internet-of-Things-Albtraum.
Fast jedes Gerät könnte eine Backdoor haben, sogar eine dumme wie #0000.
Paketverlust ist dabei völlig normal.
Ich frage mich, ob man zwischen die Bad-Apple-Befehle MIDI-Musik einfügen kann, damit auch Audio selbst abgespielt wird.
Im README des Repositories steht, dass ein Image-Dump enthalten sei, tatsächlich ist aber keiner da.
Ich frage mich, ob das so beabsichtigt ist.
In letzter Minute wurde
*.binzu.gitignorehinzugefügt, um die assemblierten Code-Snippets auszuschließen, und offenbar wurden die Dumps dabei ebenfalls ausgeschlossen.Ich werde sie in ein paar Stunden hochladen.
Diese Dumps könnten unter Yamaha-Copyright fallen, also war das vielleicht sogar eine gute Entscheidung.
Falls unter den HN-Lesern jemand in Armenien ist: Porta hält am 10. Januar in der Hacker Embassy einen Vortrag zu diesem Thema.
Kommt unbedingt vorbei: https://t.me/hackerembassy/17