3 puntos por GN⁺ 2025-01-06 | 1 comentarios | Compartir por WhatsApp
  • 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 0x3f0f0f0f y 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 login y la contraseña #0000 se 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 marca DMLCD
  • 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 TESTN y PROTN para 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
  • Al activar TESTN, el sintetizador no arrancaba, y PROTN no 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 arm7tdmi en 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ón 0 con GDB apareció una instrucción de salto del tipo ldr pc, [pc, #24]
  • Se volcó 16 MiB desde la dirección 0 y se abrió en Cutter, pero las cadenas se repetían cada 64 KiB
    • Ejemplo: SWL01U Internal se repetía en 0x0000bfd0, 0x0001bfd0, 0x0002bfd0, etc.
  • 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
    • GrandPno
    • Tr1 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

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, ?, info y ver estaban 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 login y Passwd Error confirmaban 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 mostraba passwd?
    • Al ingresar la contraseña #0000, aparecía login OK
    • Después de eso, ya se podían ejecutar comandos
  • Entre los comandos identificados en la shell estaban
    • 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
  • El comando info devolvía esta información
    • DevelopName PSR-E433
    • DevelopNumber #3341
    • Main DevelopNumber #3341
    • Make data & time MAY 16 2012 19:00:57
    • J/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
  • Un mensaje MIDI SysEx comienza con 0xF0, pasa por un ID de fabricante y un payload, y termina con 0xF7; además, el payload solo puede contener bytes con el MSB en 0
  • El 0x43 del 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\r de 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 HeloWrld en 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

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

    • También se usó mucho en aplicaciones industriales, y Mitsubishi utilizó este chip en las ECU de algunos vehículos, incluido el Lancer Evolution.
  • 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.

    • La matriz de puntos en realidad es de 8 caracteres de 5x8, así que son 320 bits en total, y esos 320 bits se empaquetan usando 4 bits por byte disponibles en el protocolo de shell.
      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.

    • Saber mucho pero ser consciente de que no sabes nada parece como el nivel 4 de experiencia.
      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”.
    • En especial, los ingenieros no profesionales realmente destacados suelen decir cosas así.
  • 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

    • Hay muchos desbordamientos de búfer, pero otra cosa es si alguien escribió realmente shellcode para esas vulnerabilidades.
  • 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.

    • Estaría bueno poder provocar ejecución remota de código tocando una canción.
      Sería genial conectar un teclado MIDI, tocar Am6,9/G# y que se abra una terminal con permisos de root.
    • Espero con ganas ver qué hackeo de SysEx va a meter Google en el soporte MIDI de Chrome que viene desarrollando desde hace unos años.
    • No tenía idea de que existía este mundo.
      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.
    • SysEx es excelente.
      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”.

    • Ahora la verdadera pregunta es si se puede modificar el código que se ejecuta en el teclado para que, cuando otro teclado del mismo modelo reciba estos datos MIDI, intente infectarse.
      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.
    • Si reproduces esto como un archivo MIDI, sospecho que el resultado será dubstep.
    • Decir que puedes leer y escribir la memoria del sintetizador por MIDI suena fácil, pero SysEx no garantiza la entrega y no tiene concepto de conexión ni de sesión, así que puede ser frustrante.
      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.

    • Fue un error.
      En el último momento agregué *.bin a .gitignore para excluir algunos fragmentos de código ensamblado, y parece que los volcados también quedaron excluidos.
      Los voy a subir en unas horas.
    • Eso parece.
      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