- Al hacer ingeniería inversa del chip SWL01U de un sintetizador Yamaha PSR-E433 antiguo, se logró escribir código en la RAM y mostrar video de Bad Apple en la LCD usando solo mensajes USB-MIDI SysEx
- Con el IDCODE JTAG
0x3f0f0f0fy experimentos con OpenOCD/GDB, se confirmó que el chip se comporta como un núcleo ARM7TDMI, y se volcaron su ROM interna de 64 KiB y el firmware en flash externa de 16 MiB - Dentro del firmware había una shell oculta que funcionaba sobre MIDI SysEx, y después de
loginy la contraseña#0000se podían usar comandos de lectura/escritura de memoria - Inyectando código ARM en la RAM con un comando de escritura arbitraria de memoria y luego sobrescribiendo la dirección de retorno de la pila, fue posible ejecutar código solo reproduciendo un archivo MIDI, sin JTAG ni UART
- La salida a la LCD mejoró mediante control de CGRAM, copia de la tabla de tareas, desactivación de la tarea de pantalla y reemplazo del callback de la shell, reduciendo la transferencia por cuadro de 6732 bytes a 92 bytes
Investigación interna del Yamaha PSR-E433
- El equipo objetivo era un sintetizador Yamaha PSR-E433 usado desde hace mucho tiempo, y en la tarjeta principal había dos chips flash, un chip RAM y un chip
YAMAHA SWL01U, junto con la marcaDMLCD - Casi no había información pública sobre el SWL01U, y solo una publicación encontrada en línea mencionaba que podría estar basado en un núcleo de CPU SuperH
- En el manual de servicio del modelo similar E443 había un pinout del SWL01U, donde aparecían
TESTN,PROTN, dos UART bidireccionales y puntos de prueba JTAG - El enfoque inicial tuvo cuatro frentes
- Manipular los pines
TESTNyPROTNpara verificar cambios en el modo de arranque - Soldar al pin UART Tx para revisar si había salida
- Leer el código de identificación del chip por JTAG
- Retirar el chip flash para volcar el firmware
- Manipular los pines
- Al activar
TESTN, el sintetizador no arrancaba, yPROTNno cambiaba el comportamiento - Se soldó directamente al pin UART Tx sin uso, pero no hubo salida en ninguna de las cuatro combinaciones de
TESTN/PROTN
Comportamiento ARM7TDMI revelado por JTAG
- Como JTAG requiere detalles de circuito según la implementación de cada fabricante, primero se intentó con OpenOCD la lectura de IDCODE, compatible con casi todos los dispositivos
- OpenOCD reportó el IDCODE
0x3f0f0f0f, y ese valor parecía poder relacionarse con microcontroladores ARM7 como las familias STR7xxx de STMicroelectronics o SAM7xxx de Atmel - Al configurar el SWL01U como objetivo
arm7tdmien OpenOCD, la conexión funcionó correctamente y se indicó que el hardware tenía 2 unidades de breakpoint/watchpoint - Al detener y reanudar la ejecución con GDB, la corriente de la placa cambiaba de forma predecible
- En ejecución: aproximadamente 115 mA
- En pausa: aproximadamente 98 mA
- Este cambio de corriente fue una señal fuerte de que realmente se estaba deteniendo y reanudando un núcleo ARM7TDMI
Volcado de la ROM y del firmware flash
- Según la documentación de ARM7TDMI, el vector de reset está en la dirección
0, y al leer la dirección0con GDB apareció una instrucción de salto del tipoldr pc, [pc, #24] - Se volcó 16 MiB desde la dirección
0y se abrió en Cutter, pero las cadenas se repetían cada 64 KiB- Ejemplo:
SWL01U Internalse repetía en0x0000bfd0,0x0001bfd0,0x0002bfd0, etc.
- Ejemplo:
- Por el patrón repetitivo y las cadenas, se concluyó que ese volcado no era la flash externa sino la memoria interna del chip, y que el SWL01U tenía una ROM de 64 KiB
- El destino del salto del vector de reset era
0x02000000, y al volver a volcar 16 MiB desde esa dirección ya no hubo repeticiones - El volcado de la flash externa contenía cadenas visibles durante el uso del sintetizador
GrandPnoTr1 will be OverWritten!BogiWogi
- El mapa de memoria identificado fue el siguiente
- ROM interna:
0x00000000, 64 KiB - Flash externa:
0x02000000, 16 MiB - Durante el arranque, la ROM cede el control de inmediato a la flash externa
- ROM interna:
Una shell oculta encontrada con Ghidra
- Como Cutter no bastaba para el análisis, se cambió a Ghidra y se fue reconstruyendo la estructura del firmware siguiendo cadenas y xrefs
- Cadenas como
help,?,infoyverestaban agrupadas en direcciones cercanas, y cada una apuntaba a un arreglo que parecía contener pares de nombre de comando y puntero a función - La función que procesaba comandos tenía forma de máquina de estados, y las cadenas
loginyPasswd Errorconfirmaban que era una shell con procedimiento de inicio de sesión - La entrada de la shell recorría un buffer circular de 256 bytes y procesaba carácter por carácter, ejecutando el comando al encontrar el carácter
\r - El flujo de inicio de sesión era el siguiente
- Al ingresar
login, se mostrabapasswd? - Al ingresar la contraseña
#0000, aparecíalogin OK - Después de eso, ya se podían ejecutar comandos
- Al ingresar
- Entre los comandos identificados en la shell estaban
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
- El comando
infodevolvía esta informaciónDevelopName PSR-E433DevelopNumber #3341Main DevelopNumber #3341Make data & time MAY 16 2012 19:00:57J/E Select English
Una shell que funciona sobre USB-MIDI SysEx
- La función de salida de la shell dividía cada byte en nibbles de 4 bits alto y bajo, los guardaba en bytes separados y añadía un header y footer fijos
- La estructura del paquete correspondiente al prompt
>era la siguiente- Header:
F0 43 73 01 52 19 00 00 - Payload:
03 0E 02 00 - Footer:
F7
- Header:
- Un mensaje MIDI SysEx comienza con
0xF0, pasa por un ID de fabricante y un payload, y termina con0xF7; además, el payload solo puede contener bytes con el MSB en 0 - El
0x43del header era el ID de fabricante de Yamaha, y la estructura de los paquetes de la shell coincidía con el formato de mensajes Yamaha SysEx - En el descriptor USB del sintetizador solo había una interfaz MIDI; no existía un puerto serial aparte
- Al convertir entre una terminal y el protocolo de la shell con un script en Python, se pudo interactuar con la shell por USB-MIDI
Ejecución de código con shellcode MIDI
- El comando
m/l AAAAAAAA DDDDDDDD\rde la shell realiza escrituras de memoria de 32 bits y transmite dirección y datos como ASCII hexadecimal - Incluso para escribir un payload de 4 bytes, el volumen real de transferencia crecía mucho
- Cada byte del comando se convertía en dos bytes de nibble de 4 bits
- Se añadían 9 bytes por el mensaje SysEx
- Cada 3 bytes se envolvían en un paquete USB-MIDI de 4 bytes
- Para una escritura de 4 bytes había que enviar 72 bytes al sintetizador
- Sumando eco y prompt, el total intercambiado llegaba a 396 bytes
- Se encontró una región de RAM que parecía no estar en uso, se colocó allí código ARM en ensamblador y luego se sobrescribió una dirección de retorno de la pila para ejecutarlo
- El primer payload llamaba a una función interna del firmware para imprimir cadenas y mostraba
HeloWrlden el área de texto de 8 caracteres de la LCD - Este método también funcionaba sin JTAG ni UART, y podía ejecutarse simplemente incluyendo los mensajes en un archivo MIDI y reproduciéndolo
- También se proporcionó un archivo MIDI para el firmware 1.02 del PSR-E433, pero se advirtió que reproducirlo en otros dispositivos Yamaha o en otras versiones de firmware del PSR-E433 podía producir comportamientos impredecibles
Mostrar Bad Apple en la LCD
- El controlador LCD del Yamaha PSR-E433 es el ML9040A, y por defecto estaba pensado para manejar caracteres de texto en matriz de puntos
- La LCD no solo tenía un área de matriz de puntos, sino también notación musical, zona de 7 segmentos, indicación de acordes y un área inferior de visualización del teclado
- El ML9040A tenía tres memorias
- DDRAM: el host escribe los datos de caracteres a mostrar
- CGROM: convierte códigos de caracteres en patrones gráficos
- CGRAM: el host puede definir hasta 8 caracteres personalizados
- El firmware manipulaba la CGRAM para controlar elementos no textuales debajo de la matriz de puntos, y esa ruta permitía mostrar gráficos definidos por el usuario
- Se encontró una función del firmware que enviaba datos arbitrarios al controlador LCD y se cargó un patrón de prueba en la CGRAM, pero el firmware seguía actualizando la CGRAM y lo sobrescribía poco después
Control de la actualización de pantalla mediante manipulación de RAM
- Como sobrescribir directamente la flash podía dejar el dispositivo inservible, todos los experimentos se limitaron a manipulación de RAM para que un reinicio de energía pudiera revertirlos
- El firmware tenía una estructura parecida a un RTOS primitivo, y en la flash había una tabla global que definía callbacks de 64 tareas, sus pilas y atributos
- Durante el arranque, el firmware en flash le informaba a la ROM la ubicación de la tabla de tareas, y la ROM guardaba esa ubicación en una variable global dentro de la SRAM integrada
- Si se copiaba la tabla de tareas a la RAM y luego se cambiaba la ROM para que usara la nueva tabla, era posible reemplazar callbacks de tareas sin modificar la flash
- Se cambió el callback de la tarea de actualización de pantalla por el callback idle predeterminado, evitando que el firmware siguiera sobrescribiendo la CGRAM
Mejora de eficiencia de transferencia y de artefactos visuales
- La primera implementación de Bad Apple funcionó, pero la eficiencia de transferencia era baja, la tasa de cuadros muy reducida y había artefactos visuales
- Aun enviando por cuadro solo 64 bytes de CGRAM y la sobreescritura de una dirección de retorno de 32 bits, el tráfico real era de 6732 bytes por 70 bytes de payload
- Las dos causas principales de esa baja eficiencia eran
- Los datos tenían que envolverse en comandos de la shell
- El sintetizador hacía eco de los comandos carácter por carácter en paquetes SysEx grandes
- Al reemplazar directamente el callback de la tarea de la shell por uno propio que recibía datos crudos y no respondía, se pudieron eliminar el encapsulado en comandos y el overhead del eco
- Tras optimizaciones adicionales de empaquetado, la transferencia por cuadro se redujo de 6732 bytes a 92 bytes, una disminución de 73 veces
- Los artefactos restantes se debían a que la comunicación con la LCD y el escaneo de botones/LED del panel compartían las mismas 8 líneas GPIO
- La implementación final no escribía directamente en la LCD; en su lugar, solicitaba a la tarea de multiplexado LCD/panel que enviara los datos deseados después de terminar el escaneo del panel, evitando así la corrupción visual
Procedimiento final de funcionamiento y objetivos de análisis pendientes
- El procedimiento final para mostrar video en la LCD mediante MIDI fue el siguiente
- Iniciar sesión en la shell
- Escribir código de ejecución en la RAM usando el comando de escritura de memoria de la shell
- Sobrescribir la dirección de retorno de la pila para ejecutar el código en RAM
- Copiar la tabla de tareas a la RAM
- Modificar las nuevas tablas de tareas para que se apunten entre sí
- Cambiar la ROM para que use la nueva tabla de tareas
- Reemplazar el callback de la tarea de pantalla por el callback idle predeterminado
- Reemplazar el callback de la tarea de la shell por un callback propio
- En el callback propio, desempaquetar los datos MIDI y entregarlos a la tarea de multiplexado LCD/panel
- Alimentar los cuadros de video por MIDI
- La comprensión de la región MMIO del SWL01U sigue siendo limitada, y el DSP separado del núcleo ARM principal también queda como objetivo de análisis futuro
- Material relacionado
1 comentarios
Opiniones en Hacker News
SuperH también estuvo en Sega 32X, Sega Saturn y Sega Dreamcast, y se usó en algunas de las primeras Pocket PC, como la HP Jornada.
Aunque la mayoría de las Pocket PC estaban basadas en ARM.
La premisa es casi absurdamente disparatada, así que sorprende que realmente lo hayan logrado.
Mencionaron “otra optimización de empaquetado”, y me da curiosidad cómo transmiten los cuadros.
Si la matriz de puntos son 8 caracteres de 7x5, serían 280 bits en total, es decir, 40 grupos de 7 bits por cuadro, pero parece que usan el doble de espacio para la transmisión.
Me pregunto si se desperdicia por los datos de control o si el método de transmisión es un poco subóptimo.
A eso se le agregan 9 bytes de encabezado y pie de paquete.
En el artículo parece que escribí 92, pero creo que calculé mal.
Encontrar una forma de usar los 7 bits completos era demasiado difícil, así que elegí una solución solo un poquito peor que la óptima en comparación con el método original.
Si te interesa el algoritmo exacto, el código todavía no está ordenado, pero puedes ver estos archivos: https://github.com/portasynthinca3/swl01u/blob/master/fun/bi..., https://github.com/portasynthinca3/swl01u/blob/master/fun/ba...
Si esto es lo que significa “no tengo mucha experiencia en ingeniería inversa”, no sé en qué lugar queda el resto de nosotros.
El nivel 1 es cuando eres nuevo y entusiasta, pero sabes que todavía no sabes; el nivel 2 es “soy un dios”; el nivel 3 es la etapa de “soy un idiota”.
Dice “el primer shellcode MIDI del mundo”, pero en la mayoría de las plataformas principales el shellcode MIDI existe desde hace más de 20 años: https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=midi
Por supuesto que era SysEx.
En MIDI estándar, SysEx es como un ensamblador en línea en Python.
Dentro de casi todos los dispositivos MIDI hay cosas propietarias no documentadas escondidas.
Sería genial conectar un teclado MIDI, tocar Am6,9/G# y que se abra una terminal con permisos de root.
Hace poco busqué qué hacía falta para hacer fuzzing de MIDI y encontré material para generar archivos .mid, pero no era exactamente lo que quería.
En cambio, esto parece un camino que vale la pena explorar.
Me da bastante pena que los sintetizadores modernos parezcan usarlo cada vez menos, especialmente Roland.
Aun así, Behringer todavía lo soporta bastante bien.
Por ejemplo, el Deepmind ya tiene un buen rango de MIDI CC, pero con SysEx es programable casi al 100%.
Investigación increíble.
Me recuerda un poco al trabajo de 2017 en el que sintetizaron shellcode en moléculas reales de DNA/RNA y demostraron ejecución remota de código en equipos de secuenciación de DNA: https://www.usenix.org/conference/usenixsecurity17/technical...
Iba a decir “¿lo próximo será OSC?”, pero parece que MIDI todavía manda.
Recomiendo leer el artículo completo, pero creo que las frases clave son estas:
“Estos locos de [fabricante de teclados] hicieron un shell que corre sobre mensajes MIDI SysEx sobre USB”.
“El comando más interesante es el de lectura/escritura de memoria arbitraria. Si quieres, puedes mirar y toquetear la memoria del sintetizador mediante MIDI”.
“Si quieres, puedes escribir estos mensajes en un archivo MIDI y reproducirlo en el sintetizador como cualquier otro archivo MIDI. Vaya, se me ocurre una buena idea…”
“Después de incontables noches sin dormir hurgando en el firmware, encontré una función que envía datos arbitrarios al controlador LCD”.
En cierto modo, es como asomarse un poco a una pesadilla del Internet de las cosas.
Casi cualquier dispositivo podría tener una puerta trasera, incluso una tan tonta como #0000.
Es completamente normal que haya pérdida de paquetes.
Me pregunto si se podría insertar música MIDI entre los comandos de Bad Apple para que también reproduzca audio por sí mismo.
El README del repositorio dice que incluye volcados de imagen, pero en realidad no están.
Me pregunto si ese es el estado correcto.
En el último momento agregué
*.bina.gitignorepara excluir algunos fragmentos de código ensamblado, y parece que los volcados también quedaron excluidos.Los voy a subir en unas horas.
Esos volcados probablemente estén protegidos por copyright de Yamaha, así que quizá haya sido una buena decisión después de todo.
Si entre los lectores de HN hay alguien en Armenia, Porta dará una charla sobre este tema el 10 de enero en Hacker Embassy.
Estaría bueno que vayan: https://t.me/hackerembassy/17