3 Punkte von GN⁺ 2025-01-06 | 1 Kommentare | Auf WhatsApp teilen
  • 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 0x3f0f0f0f und 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 login und dem Passwort #0000 konnten 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 DMLCD zwei Flash-Chips, ein RAM-Chip und ein YAMAHA 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 TESTN und PROTN, 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
  • Bei Aktivierung von TESTN bootete der Synthesizer nicht; PROTN veränderte das Verhalten nicht
  • Obwohl direkt an einen ungenutzten UART-Tx-Pin gelötet wurde, gab es in allen vier Kombinationen von TESTN/PROTN keinerlei 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 Adresse 0 mit GDB war ein Sprungbefehl der Form ldr pc, [pc, #24] zu sehen
  • Ein 16-MiB-Dump ab Adresse 0 wurde in Cutter geöffnet, doch Zeichenketten wiederholten sich alle 64 KiB
    • Beispiel: SWL01U Internal wiederholte sich bei 0x0000bfd0, 0x0001bfd0, 0x0002bfd0 usw.
  • 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
    • GrandPno
    • Tr1 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

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, ?, info und ver lagen 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 login und Passwd Error wurde 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 login gibt passwd? aus
    • Eingabe des Passworts #0000 gibt login OK aus
    • Danach können Befehle ausgeführt werden
  • Zu den identifizierten Shell-Befehlen gehören
    • logout, help, ?, info, ver
    • stack, perf-on, perf-off, perf-disp
    • d, dp, d xxxxx, d/s xxxxx
    • m ADDRESS DATA, m/b ADDRESS DATA, m/w ADDRESS DATA, m/l ADDRESS DATA
  • Der Befehl info lieferte folgende Informationen zurück
    • DevelopName PSR-E433
    • DevelopNumber #3341
    • Main DevelopNumber #3341
    • Make data & time MAY 16 2012 19:00:57
    • J/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
  • MIDI-SysEx-Nachrichten beginnen mit 0xF0, enthalten eine Hersteller-ID und Payload und enden mit 0xF7; im Payload sind nur Bytes mit MSB 0 erlaubt
  • 0x43 im 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\r fü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 HeloWrld im 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

 
GN⁺ 2025-01-06
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.

    • Auch in der Industrie wurde er häufig eingesetzt; Mitsubishi verwendete diesen Chip in den ECUs einiger Fahrzeuge, darunter der Lancer Evolution.
  • 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.

    • Die Dot-Matrix besteht tatsächlich aus acht 5x8-Zeichen, also insgesamt 320 Bit, und diese 320 Bit werden in 4 Bit pro Byte gepackt, die im Shell-Protokoll nutzbar sind.
      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.

    • Viel zu wissen und zugleich zu erkennen, dass man nichts weiß, fühlt sich ungefähr wie Erfahrungsstufe 4 an.
      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.
    • Besonders herausragende nicht professionelle Engineers sagen so etwas häufig.
  • 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

    • Es gibt viele Buffer Overflows, aber ob tatsächlich jemand für diese Schwachstellen Shellcode geschrieben hat, ist eine andere Frage.
  • 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.

    • Es wäre schön, wenn man durch das Spielen eines Musikstücks Remote Code Execution auslösen könnte.
      Ein MIDI-Keyboard anschließen, Am6,9/G# spielen, und ein Terminalfenster mit Root-Rechten öffnet sich – das wäre wirklich cool.
    • Ich bin gespannt, welche SysEx-Hacks Google in die MIDI-Unterstützung von Chrome quetschen wird, an der sie seit einigen Jahren arbeiten.
    • Ich hatte keine Ahnung, dass es diese Welt gibt.
      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.
    • SysEx ist großartig.
      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.“

    • Die eigentliche Frage ist jetzt, ob man den auf dem Keyboard laufenden Code so ändern kann, dass er versucht, andere Keyboards desselben Modells zu infizieren, wenn sie diese MIDI-Daten empfangen.
      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.
    • Wenn man das wie eine MIDI-Datei abspielt, klingt das Ergebnis vermutlich nach Dubstep.
    • Zu sagen, man könne per MIDI den Speicher des Synthesizers lesen und schreiben, klingt einfach, aber SysEx bietet weder Zustellgarantie noch ein Konzept von Verbindung oder Session, was frustrierend sein kann.
      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.

    • Das war ein Versehen.
      In letzter Minute wurde *.bin zu .gitignore hinzugefügt, um die assemblierten Code-Snippets auszuschließen, und offenbar wurden die Dumps dabei ebenfalls ausgeschlossen.
      Ich werde sie in ein paar Stunden hochladen.
    • Scheint so.
      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