- En rétroconcevant la puce SWL01U d’un ancien synthétiseur Yamaha PSR-E433, il a été possible d’écrire du code en RAM et d’afficher une vidéo de Bad Apple sur l’écran LCD uniquement à l’aide de messages USB-MIDI SysEx
- Des essais avec l’IDCODE JTAG
0x3f0f0f0fet OpenOCD/GDB ont confirmé que la puce se comporte comme un cœur ARM7TDMI, et ont permis de dumper la ROM interne de 64 Kio ainsi que le firmware stocké dans une mémoire flash externe de 16 Mio - Le firmware contenait un shell caché fonctionnant au-dessus de MIDI SysEx, permettant après
loginet le mot de passe#0000d’utiliser des commandes de lecture/écriture mémoire - En injectant du code ARM en RAM via une commande d’écriture mémoire arbitraire, puis en écrasant l’adresse de retour de la pile, il devient possible d’obtenir une exécution de code simplement en lisant un fichier MIDI, sans JTAG ni UART
- L’affichage sur l’écran LCD a ensuite été amélioré via le contrôle de la CGRAM, la copie de la table des tâches, la désactivation de la tâche d’affichage et le remplacement du callback du shell, réduisant le volume transmis de 6 732 octets à 92 octets par image
Exploration interne du Yamaha PSR-E433
- L’appareil visé était un synthétiseur Yamaha PSR-E433 utilisé depuis longtemps ; sur la carte mère se trouvaient deux puces flash, une puce RAM et une puce
YAMAHA SWL01U, avec la mentionDMLCD - Il existait très peu d’informations publiques sur la SWL01U, et un seul billet trouvé en ligne mentionnait qu’elle pourrait être basée sur un cœur CPU SuperH
- Le manuel de maintenance d’un modèle proche, le E443, donnait le brochage de la SWL01U, avec
TESTN,PROTN, deux UART bidirectionnels et des points de test JTAG - L’approche initiale suivait quatre pistes
- manipuler les broches
TESTNetPROTNpour vérifier un éventuel changement de mode de démarrage - souder sur la broche UART Tx pour voir s’il y avait une sortie
- lire le code d’identification de la puce via JTAG
- retirer la puce flash pour dumper le firmware
- manipuler les broches
- Lorsque
TESTNétait activée, le synthétiseur ne démarrait pas, etPROTNne changeait rien au fonctionnement - Une soudure directe sur une broche UART Tx inutilisée n’a donné aucune sortie, dans les quatre combinaisons possibles de
TESTNetPROTN
Le comportement ARM7TDMI révélé par JTAG
- Comme JTAG nécessite souvent des détails de câblage propres à chaque constructeur, la première tentative avec OpenOCD a consisté à lire l’IDCODE, pris en charge par presque tous les appareils
- OpenOCD a remonté l’IDCODE
0x3f0f0f0f, une valeur qui semblait compatible avec des microcontrôleurs ARM7 comme les familles STR7xxx de STMicroelectronics ou SAM7xxx d’Atmel - En déclarant la SWL01U comme cible
arm7tdmidans OpenOCD, la connexion s’est faite correctement, avec détection de 2 unités breakpoint/watchpoint - En arrêtant puis relançant l’exécution avec GDB, le courant consommé par la carte variait de façon prévisible
- environ 115 mA en cours d’exécution
- environ 98 mA à l’arrêt
- Cette variation de courant était un indice fort que le cœur ARM7TDMI était réellement stoppé puis relancé
Dump de la ROM et du firmware en flash
- D’après la documentation ARM7TDMI, le vecteur de reset se trouve à l’adresse
0; en lisant l’adresse0avec GDB, une instruction de saut de typeldr pc, [pc, #24]est apparue - Un dump de 16 Mio à partir de l’adresse
0, ouvert dans Cutter, montrait des chaînes répétées tous les 64 Kio- par exemple :
SWL01U Internalse répétait à0x0000bfd0,0x0001bfd0,0x0002bfd0, etc.
- par exemple :
- En raison de ce motif répétitif et des chaînes présentes, ce dump a été identifié comme une mémoire interne à la puce plutôt que la flash externe ; il a donc été conclu que la SWL01U embarque une ROM de 64 Kio
- La cible du saut du vecteur de reset était
0x02000000; un nouveau dump de 16 Mio depuis cette adresse, cette fois sans répétition, a alors été réalisé - Le dump de la flash externe contenait des chaînes visibles lors de l’usage normal du synthétiseur
GrandPnoTr1 will be OverWritten!BogiWogi
- L’agencement mémoire identifié était le suivant
- ROM interne :
0x00000000, 64 Kio - flash externe :
0x02000000, 16 Mio - au démarrage, la ROM passe immédiatement le contrôle à la flash externe
- ROM interne :
Un shell caché découvert avec Ghidra
- Cutter seul ne suffisait pas pour l’analyse, d’où le passage à Ghidra pour reconstituer la structure du firmware à partir des chaînes et de leurs xref
- Des chaînes comme
help,?,info,verétaient regroupées à proximité, chacune liée à un tableau ressemblant à une suite de paires nom-de-commande / pointeur de fonction - La fonction de traitement des commandes avait la forme d’une machine à états, et les chaînes
loginetPasswd Erroront permis d’identifier un shell avec procédure d’authentification - Le traitement d’entrée du shell parcourait un tampon circulaire de 256 octets, caractère par caractère, et exécutait la commande à la rencontre du caractère
\r - Le flux de connexion était le suivant
- après saisie de
login, affichage depasswd? - après saisie du mot de passe
#0000, affichage delogin OK - les commandes deviennent alors utilisables
- après saisie de
- Les commandes identifiées du shell comprenaient notamment
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
- La commande
inforenvoyait les informations suivantesDevelopName PSR-E433DevelopNumber #3341Main DevelopNumber #3341Make data & time MAY 16 2012 19:00:57J/E Select English
Un shell fonctionnant au-dessus de l’USB-MIDI SysEx
- La fonction de sortie du shell séparait chaque octet en deux nibbles de 4 bits haut/bas, les plaçait dans des octets distincts, puis ajoutait un en-tête fixe et un footer
- La structure du paquet correspondant à l’invite
>était la suivante- en-tête :
F0 43 73 01 52 19 00 00 - payload :
03 0E 02 00 - footer :
F7
- en-tête :
- Un message MIDI SysEx commence par
0xF0, traverse l’ID constructeur et le payload, puis se termine par0xF7; le payload ne peut contenir que des octets dont le MSB vaut 0 - Le
0x43de l’en-tête correspondait à l’identifiant constructeur Yamaha, et la structure des paquets du shell correspondait bien au format des messages Yamaha SysEx - Le descripteur USB du synthétiseur ne présentait qu’une interface MIDI, sans port série distinct
- En écrivant un script Python convertissant entre un terminal et le protocole du shell, il a été possible de dialoguer avec le shell via l’USB-MIDI
Exécution de code via shellcode MIDI
- La commande du shell
m/l AAAAAAAA DDDDDDDD\reffectue une écriture mémoire 32 bits, avec adresse et données transmises en ASCII hexadécimal - Même pour écrire un payload de 4 octets, le volume réellement transmis augmentait fortement
- chaque octet de commande est converti en deux octets de nibbles de 4 bits
- 9 octets s’ajoutent pour le message SysEx
- chaque groupe de 3 octets est encapsulé dans un paquet USB-MIDI de 4 octets
- écrire 4 octets impose d’envoyer 72 octets au synthétiseur
- en comptant l’écho et l’invite, un total de 396 octets est échangé
- Une zone RAM apparemment inutilisée a été repérée pour y placer du code assembleur ARM, puis l’adresse de retour de la pile a été écrasée afin d’exécuter ce code
- Le premier payload appelait une fonction interne du firmware chargée d’afficher une chaîne, ce qui a permis d’écrire
HeloWrlddans la zone texte de 8 caractères de l’écran LCD - Cette technique fonctionne sans JTAG ni UART, et peut être déclenchée simplement en lisant un fichier MIDI contenant les messages adéquats
- Un fichier MIDI pour le firmware 1.02 du PSR-E433 a également été fourni, avec l’avertissement qu’une lecture sur d’autres appareils Yamaha ou sur d’autres versions du firmware du PSR-E433 pouvait produire des effets imprévisibles
Afficher Bad Apple sur l’écran LCD
- Le contrôleur LCD du Yamaha PSR-E433 est un ML9040A, conçu à l’origine pour des caractères texte matriciels
- L’écran comportait, en plus de la zone matricielle, une zone de notation musicale, une zone à 7 segments, une zone d’accords et une zone clavier en bas
- Le ML9040A disposait de trois mémoires
- DDRAM : le contenu texte à afficher, écrit par l’hôte
- CGROM : conversion des codes caractères en motifs graphiques
- CGRAM : définition par l’hôte d’un maximum de 8 caractères personnalisés
- Le firmware manipulait la CGRAM pour contrôler les éléments d’affichage non textuels situés sous la zone matricielle, ce qui offrait une voie pour afficher des graphismes personnalisés
- Une fonction du firmware permettant d’envoyer des données arbitraires au contrôleur LCD a été trouvée et utilisée pour charger un motif en damier dans la CGRAM, mais le firmware continuait ensuite à la rafraîchir et écrasait rapidement le contenu
Contrôler le rafraîchissement de l’affichage via des manipulations RAM
- Réécrire directement la flash risquant de briquer l’appareil, toutes les expérimentations ont été limitées à des manipulations en RAM, de façon à pouvoir revenir en arrière par un simple redémarrage
- Le firmware contenait une structure rappelant un RTOS rudimentaire, avec dans la flash une table globale décrivant, pour 64 tâches, leurs fonctions de callback, leurs piles et leurs attributs
- Au démarrage, le firmware en flash indiquait à la ROM l’emplacement de cette table de tâches, et la ROM enregistrait cet emplacement dans une variable globale de la SRAM intégrée
- En copiant la table des tâches en RAM puis en redirigeant la ROM vers cette nouvelle table, il devenait possible de remplacer les callbacks de tâches sans modifier la flash
- Le callback de la tâche de rafraîchissement de l’affichage a été remplacé par le callback idle par défaut, empêchant ainsi le firmware de continuer à écraser la CGRAM
Amélioration de l’efficacité de transmission et des artefacts visuels
- La première implémentation de Bad Apple fonctionnait, mais l’efficacité de transmission était faible, avec une fréquence d’image très basse et des artefacts à l’écran
- À chaque image, même en n’envoyant que 64 octets de CGRAM et un écrasement d’adresse de retour 32 bits, le volume réel transmis atteignait 6 732 octets pour 70 octets de payload
- Cette faible efficacité avait principalement deux causes
- les données devaient être encapsulées sous forme de commandes du shell
- le synthétiseur renvoyait chaque caractère en écho dans de gros paquets SysEx
- En remplaçant le callback de la tâche du shell par un callback maison recevant des données brutes et ne répondant pas, l’encapsulation des commandes et le surcoût d’écho ont pu être supprimés
- Après des optimisations supplémentaires de packing, le volume transmis par image est tombé de 6 732 octets à 92 octets, soit une réduction par 73
- Les artefacts restants provenaient du fait que la communication LCD et le scan des boutons/LED du panneau partageaient les mêmes 8 lignes GPIO
- L’implémentation finale n’écrivait plus directement sur le LCD ; elle demandait à la tâche de multiplexage LCD/panneau d’envoyer les données voulues après la fin du scan du panneau, ce qui évitait les corruptions visuelles
Procédure finale et pistes d’analyse restantes
- La procédure finale pour afficher une vidéo LCD via MIDI était la suivante
- se connecter au shell
- écrire en RAM le code d’exécution via la commande d’écriture mémoire du shell
- écraser l’adresse de retour de pile pour exécuter le code en RAM
- copier la table des tâches en RAM
- modifier les nouvelles tables de tâches pour qu’elles se pointent mutuellement
- reconfigurer la ROM pour qu’elle utilise la nouvelle table des tâches
- remplacer le callback de la tâche d’affichage par le callback idle par défaut
- remplacer le callback de la tâche du shell par un callback personnalisé
- dans ce callback personnalisé, décompacter les données MIDI puis les transmettre à la tâche de multiplexage LCD/panneau
- alimenter ensuite les images vidéo via MIDI
- La compréhension de la zone MMIO de la SWL01U reste encore limitée, et le DSP séparé du cœur ARM principal constitue aussi une cible d’analyse future
- Ressources associées
1 commentaires
Avis sur Hacker News
SuperH était aussi présent dans la Sega 32X, la Sega Saturn et la Sega Dreamcast, ainsi que dans certains premiers Pocket PC comme le HP Jornada.
La plupart des Pocket PC étaient toutefois basés sur ARM.
Le postulat est absurdement tiré par les cheveux, et le fait qu’ils aient réellement réussi est étonnant.
Il mentionne « encore une optimisation de packing », et je me demande comment les frames sont transmises.
Si la matrice de points correspond à 8 caractères de 7x5, cela fait 280 bits au total, soit 40 groupes de 7 bits par frame, mais la transmission semble utiliser deux fois cet espace.
Je me demande si c’est gaspillé à cause des données de contrôle, ou si la méthode de transmission est juste un peu sous-optimale.
À cela s’ajoutent 9 octets d’en-tête et de pied de paquet.
Il me semble que l’article indique 92, mais le calcul paraît incorrect.
Trouver une manière d’utiliser l’ensemble des 7 bits était trop difficile, donc, par rapport à l’approche originale, j’ai choisi une solution à peine moins bonne que l’optimum.
Si l’algorithme exact t’intéresse, le code n’est pas encore bien documenté, mais tu peux regarder ces fichiers : https://github.com/portasynthinca3/swl01u/blob/master/fun/bi..., https://github.com/portasynthinca3/swl01u/blob/master/fun/ba...
Si « je n’ai pas beaucoup d’expérience en rétro-ingénierie » donne ça, je me demande bien où se situe le reste d’entre nous.
Le niveau 1, c’est être nouveau et enthousiaste tout en sachant qu’on ne sait pas encore ; le niveau 2, c’est « je suis Dieu » ; le niveau 3, c’est « je suis idiot ».
Il parle du « premier shellcode MIDI au monde », mais sur la plupart des grandes plateformes, les shellcodes MIDI existent depuis plus de 20 ans : https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=midi
Évidemment, c’était du SysEx.
Dans le MIDI standard, SysEx est un peu l’équivalent d’un assembleur inline en Python.
Presque tous les appareils MIDI cachent des éléments propriétaires non documentés.
Brancher un clavier MIDI, jouer Am6,9/G#, et voir s’ouvrir une fenêtre de terminal avec les droits root, ce serait vraiment classe.
Récemment, j’ai cherché ce qu’il faudrait pour faire du fuzzing MIDI, et j’ai trouvé des ressources pour générer des fichiers .mid, mais ce n’était pas exactement ce que je voulais.
Cette piste semble plutôt valoir la peine d’être explorée.
Je trouve assez dommage que les synthétiseurs récents l’utilisent de moins en moins, surtout Roland.
Behringer le prend toutefois encore assez bien en charge.
Par exemple, le Deepmind dispose déjà d’une bonne plage de MIDI CC, mais il est presque entièrement programmable via SysEx.
Recherche impressionnante.
Cela me rappelle un peu l’étude de 2017 qui avait synthétisé du shellcode dans de véritables molécules d’ADN/ARN et démontré une exécution de code à distance sur une machine de séquençage ADN : https://www.usenix.org/conference/usenixsecurity17/technical...
J’allais dire « et ensuite, OSC ? », mais pour l’instant, MIDI semble encore dominer.
Je recommande de lire l’article en entier, mais, à mon avis, les phrases clés sont celles-ci :
« Ces dingues chez [le fabricant du clavier] ont créé un shell qui tourne au-dessus de messages MIDI SysEx eux-mêmes au-dessus d’USB »
« La commande la plus intéressante est celle de lecture/écriture arbitraire de mémoire. Si on le souhaite, on peut inspecter et modifier la mémoire du synthétiseur via MIDI »
« Si on le souhaite, on peut écrire ces messages dans un fichier MIDI et les faire lire par le synthétiseur comme n’importe quel autre fichier MIDI. Tiens, ça me donne une bonne idée… »
« Après d’innombrables nuits blanches passées à fouiller le firmware, j’ai trouvé une fonction qui envoie des données arbitraires au contrôleur LCD »
D’une certaine manière, c’est un petit aperçu d’un cauchemar de l’Internet des objets.
Presque n’importe quel appareil pourrait avoir une backdoor, voire une backdoor aussi stupide que #0000.
La perte de paquets est parfaitement normale.
Je me demande s’il serait possible d’insérer de la musique MIDI entre les commandes Bad Apple pour que l’audio se lise aussi tout seul.
Le README du dépôt indique qu’il contient un dump d’image, mais en réalité il n’y en a pas.
Je me demande si c’est l’état attendu.
Au dernier moment, j’ai ajouté
*.binà.gitignorepour exclure le morceau de code assemblé, et on dirait que le dump a été exclu avec.Je compte l’uploader d’ici quelques heures.
Ces dumps sont probablement protégés par le copyright de Yamaha, donc c’est peut-être finalement une bonne décision.
Si des lecteurs de HN se trouvent en Arménie, Porta donnera une présentation sur ce sujet le 10 janvier à la Hacker Embassy.
Ce serait super d’y aller : https://t.me/hackerembassy/17