3 pontos por GN⁺ 2025-01-06 | 1 comentários | Compartilhar no WhatsApp
  • Com engenharia reversa do chip SWL01U de um sintetizador Yamaha PSR-E433 antigo, foi possível gravar código na RAM e exibir o vídeo de Bad Apple no LCD usando apenas mensagens USB-MIDI SysEx
  • Experimentos com o JTAG IDCODE 0x3f0f0f0f e OpenOCD/GDB confirmaram que o chip se comporta como um núcleo ARM7TDMI, e foram extraídos a ROM interna de 64 KiB e o firmware em flash externo de 16 MiB
  • Dentro do firmware havia um shell oculto que roda sobre MIDI SysEx, permitindo usar comandos de leitura/escrita de memória após login e a senha #0000
  • Injetando código ARM na RAM com um comando de escrita arbitrária de memória e sobrescrevendo o endereço de retorno da pilha, tornou-se possível a execução de código apenas reproduzindo um arquivo MIDI, sem JTAG nem UART
  • A saída no LCD foi aprimorada por meio do controle da CGRAM, cópia da tabela de tarefas, desativação da tarefa de display e substituição do callback do shell, reduzindo a transferência por quadro de 6732 bytes para 92 bytes

Investigando o interior do Yamaha PSR-E433

  • O equipamento analisado era um sintetizador Yamaha PSR-E433 usado há muito tempo, e na placa principal havia dois chips de flash, um chip de RAM e um chip YAMAHA SWL01U, junto da marcação DMLCD
  • Quase não havia informação pública sobre o SWL01U, e apenas um texto encontrado online mencionava que ele talvez fosse baseado em um núcleo de CPU SuperH
  • O manual de serviço de um modelo parecido, o E443, trazia o pinout do SWL01U, com TESTN, PROTN, duas UARTs bidirecionais e pontos de teste JTAG indicados
  • A abordagem inicial seguiu quatro frentes
    • manipular os pinos TESTN e PROTN para verificar mudanças no modo de boot
    • soldar no pino UART Tx para observar saída
    • ler o código de identificação do chip via JTAG
    • remover os chips de flash para extrair o firmware
  • Ao ativar TESTN, o sintetizador não inicializava, e PROTN não alterava o funcionamento
  • Foi feita solda direta em um pino UART Tx não utilizado, mas não houve saída em nenhuma das quatro combinações de TESTN/PROTN

Comportamento ARM7TDMI revelado via JTAG

  • Como o JTAG exige detalhes de circuito que variam conforme a implementação do fabricante, a primeira tentativa com OpenOCD foi ler o IDCODE, suportado por quase todos os dispositivos
  • O OpenOCD reportou o IDCODE 0x3f0f0f0f, e esse valor parecia compatível com microcontroladores ARM7 como as famílias STMicroelectronics STR7xxx ou Atmel SAM7xxx
  • Ao configurar o SWL01U como alvo arm7tdmi no OpenOCD, a conexão funcionou normalmente, com indicação de 2 unidades de breakpoint/watchpoint no hardware
  • Ao pausar e retomar a execução com o GDB, a corrente da placa mudava de forma previsível
    • cerca de 115 mA em execução
    • cerca de 98 mA em pausa
  • Essa variação de corrente foi um forte indício de que o núcleo ARM7TDMI estava realmente sendo parado e retomado

Extração da ROM e do firmware em flash

  • Segundo a documentação do ARM7TDMI, o vetor de reset fica no endereço 0, e ao ler o endereço 0 no GDB apareceu uma instrução de salto no formato ldr pc, [pc, #24]
  • Foram extraídos 16 MiB a partir do endereço 0 e abertos no Cutter, mas as strings se repetiam a cada 64 KiB
    • Ex.: SWL01U Internal se repetia em 0x0000bfd0, 0x0001bfd0, 0x0002bfd0 etc.
  • Pelo padrão de repetição e pelas strings, concluiu-se que esse dump não era o flash externo, mas sim memória interna do chip, indicando que o SWL01U possui uma ROM de 64 KiB
  • O destino do salto do vetor de reset era 0x02000000, e ao extrair novamente 16 MiB a partir desse endereço, não havia repetição
  • O dump do flash externo continha strings visíveis durante o uso do sintetizador
    • GrandPno
    • Tr1 will be OverWritten!
    • BogiWogi
  • O mapa de memória identificado foi o seguinte
    • ROM interna: 0x00000000, 64 KiB
    • flash externo: 0x02000000, 16 MiB
    • no boot, a ROM transfere imediatamente o controle para o flash externo

O shell oculto encontrado com Ghidra

  • Como o Cutter não bastava para a análise, a investigação migrou para o Ghidra, seguindo strings e xrefs para entender a estrutura do firmware
  • Strings como help, ?, info e ver estavam agrupadas em endereços próximos, e cada uma se ligava a um array que parecia conter pares de nome de comando e ponteiro de função
  • A função de tratamento de comandos tinha forma de máquina de estados, e as strings login e Passwd Error confirmaram que se tratava de um shell com procedimento de login
  • O tratamento de entrada do shell percorria um buffer circular de 256 bytes, processando caractere por caractere, e executava o comando ao encontrar \r
  • O fluxo de login era o seguinte
    • ao digitar login, saía passwd?
    • ao digitar a senha #0000, saía login OK
    • depois disso, os comandos podiam ser executados
  • Entre os comandos identificados no shell estavam
    • 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
  • O comando info retornava as seguintes informações
    • DevelopName PSR-E433
    • DevelopNumber #3341
    • Main DevelopNumber #3341
    • Make data & time MAY 16 2012 19:00:57
    • J/E Select English

O shell rodando sobre USB-MIDI SysEx

  • A função de saída do shell dividia cada byte em nibbles alto/baixo de 4 bits, colocava cada um em um byte separado e adicionava header e footer fixos
  • A estrutura do pacote correspondente ao prompt > era a seguinte
    • header: F0 43 73 01 52 19 00 00
    • payload: 03 0E 02 00
    • footer: F7
  • Uma mensagem MIDI SysEx começa com 0xF0, passa pelo ID do fabricante e pelo payload e termina com 0xF7, e o payload só pode conter bytes com MSB igual a 0
  • O 0x43 do header era o ID de fabricante da Yamaha, e a estrutura do pacote do shell batia com o formato de mensagens Yamaha SysEx
  • O descritor USB do sintetizador incluía apenas uma interface MIDI, sem porta serial separada
  • Ao usar um script em Python para converter entre o terminal e o protocolo do shell, foi possível conversar com o shell via USB-MIDI

Execução de código com shellcode via MIDI

  • O comando m/l AAAAAAAA DDDDDDDD\r do shell realiza escrita de memória de 32 bits, passando endereço e dados em ASCII hexadecimal
  • Mesmo para gravar um payload de 4 bytes, o volume de transmissão real aumentava muito
    • cada byte do comando era convertido em dois bytes de nibble de 4 bits
    • 9 bytes eram adicionados à mensagem SysEx
    • a cada 3 bytes, havia encapsulamento em um pacote USB-MIDI de 4 bytes
    • para uma gravação de 4 bytes, era preciso enviar 72 bytes ao sintetizador
    • com eco e prompt incluídos, o total chegava a 396 bytes trafegados
  • Foi encontrada uma região de RAM aparentemente não usada, onde se colocou código em assembly ARM, e então o endereço de retorno da pilha foi sobrescrito para executar esse código
  • O primeiro payload chamava uma função interna do firmware para imprimir strings e exibia HeloWrld na área de texto de 8 caracteres do LCD
  • Esse método funciona sem JTAG nem UART e pode ser executado apenas reproduzindo uma mensagem embutida em um arquivo MIDI
  • Também foi fornecido um arquivo MIDI para o firmware 1.02 do PSR-E433, com o aviso de que reproduzi-lo em outros dispositivos Yamaha ou em outras versões de firmware do PSR-E433 pode causar comportamento imprevisível

Exibindo Bad Apple no LCD

  • O controlador de LCD do Yamaha PSR-E433 é o ML9040A, originalmente voltado para caracteres de texto em matriz de pontos
  • Além da área em matriz de pontos, o LCD também tinha notação musical, área de 7 segmentos, indicação de acordes e uma área inferior de teclado
  • O ML9040A tinha três tipos de memória
    • DDRAM: o host grava os dados de caracteres a serem exibidos
    • CGROM: converte códigos de caracteres em padrões gráficos
    • CGRAM: o host pode definir até 8 caracteres personalizados
  • O firmware controlava elementos visuais não textuais abaixo da matriz de pontos manipulando a CGRAM, e esse caminho podia ser aproveitado para exibir gráficos personalizados
  • Foi encontrada uma função do firmware que enviava dados arbitrários ao controlador LCD e foi gravado um padrão de teste na CGRAM, mas o firmware continuava atualizando a CGRAM e logo sobrescrevia tudo

Controle da atualização do display via manipulação de RAM

  • Como sobrescrever diretamente o flash poderia inutilizar o aparelho, todos os experimentos foram limitados à manipulação de RAM, para que um reinício de energia pudesse reverter tudo
  • O firmware tinha uma estrutura que lembrava um RTOS primitivo, e no flash havia uma tabela global definindo callbacks, pilhas e atributos de 64 tarefas
  • No boot, o firmware em flash informava à ROM a localização da tabela de tarefas, e a ROM guardava esse endereço em uma variável global na SRAM interna
  • Copiando a tabela de tarefas para a RAM e alterando a ROM para usar a nova tabela, tornou-se possível trocar callbacks de tarefas sem modificar o flash
  • O callback da tarefa de atualização do display foi trocado pelo callback idle padrão, impedindo que o firmware continuasse sobrescrevendo a CGRAM

Melhorias de eficiência de transmissão e artefatos na tela

  • A primeira implementação de Bad Apple funcionava, mas a eficiência de transmissão era baixa, com taxa de quadros muito pequena e artefatos na imagem
  • Mesmo enviando apenas 64 bytes de CGRAM e a sobrescrita de um endereço de retorno de 32 bits por quadro, o volume real era de 6732 bytes para 70 bytes de payload
  • Havia dois motivos principais para a baixa eficiência
    • os dados precisavam ser encapsulados na forma de comandos do shell
    • o sintetizador fazia eco de cada caractere em grandes pacotes SysEx
  • Ao substituir o callback da tarefa do shell por um callback próprio que recebia dados brutos e não respondia, foi possível eliminar o encapsulamento dos comandos e o overhead do eco
  • Após otimizações adicionais de empacotamento, a transferência por quadro caiu de 6732 bytes para 92 bytes, uma redução de 73 vezes
  • Os artefatos restantes aconteciam porque a comunicação com o LCD e a varredura de botões/LEDs do painel compartilhavam as mesmas 8 linhas GPIO
  • A implementação final não escrevia diretamente no LCD; em vez disso, solicitava que a tarefa de multiplexação LCD/painel enviasse os dados desejados após concluir a varredura do painel, evitando corrupção visual

Procedimento final de funcionamento e alvos de análise restantes

  • O procedimento final para exibir vídeo no LCD via MIDI foi o seguinte
    • fazer login no shell
    • gravar o código de execução na RAM com o comando de escrita de memória do shell
    • sobrescrever o endereço de retorno da pilha para executar o código na RAM
    • copiar a tabela de tarefas para a RAM
    • modificar as novas tabelas de tarefas para que apontem umas para as outras
    • alterar a ROM para usar a nova tabela de tarefas
    • trocar o callback da tarefa de display pelo callback idle padrão
    • trocar o callback da tarefa do shell pelo callback próprio
    • no callback próprio, desempacotar os dados MIDI e repassá-los à tarefa de multiplexação LCD/painel
    • fornecer os quadros de vídeo via MIDI
  • O entendimento da região MMIO do SWL01U ainda é limitado, e o DSP separado do núcleo ARM principal também permanece como alvo de análise
  • Materiais relacionados

1 comentários

 
GN⁺ 2025-01-06
Comentários do Hacker News
  • O SuperH também equipou o Sega 32X, o Sega Saturn e o Sega Dreamcast, e foi usado em alguns dos primeiros Pocket PCs, como o HP Jornada
    Porém, a maioria dos Pocket PCs era baseada em ARM

    • Ele também foi muito usado em aplicações industriais, e a Mitsubishi usou esse chip nas ECUs de alguns veículos, incluindo o Lancer Evolution
  • A premissa é absurda a ponto de parecer ridícula, então é surpreendente que tenham realmente conseguido
    Foi mencionada “outra otimização de empacotamento”, e fiquei curioso sobre como os frames são transmitidos
    Se a matriz de pontos tiver 8 caracteres 7x5, são 280 bits no total, ou seja, 40 grupos de 7 bits por frame, mas parece que a transmissão usa o dobro desse espaço
    Fico me perguntando se isso é desperdício por causa dos dados de controle, ou se o método de transmissão é um pouco subótimo

    • Na verdade, a matriz de pontos tem 8 caracteres 5x8, então são 320 bits no total, e esses 320 bits são empacotados em 4 bits por byte disponíveis no protocolo do shell
      A isso se somam 9 bytes de cabeçalho e rodapé do pacote
      Acho que no texto foi escrito 92, mas parece que a conta saiu errada
      Como encontrar uma forma de usar todos os 7 bits era difícil demais, escolhi uma solução só um pouquinho pior que a ótima em comparação com o método original
      Se quiser saber o algoritmo exato, o código ainda não está organizado, mas dá para olhar estes arquivos: https://github.com/portasynthinca3/swl01u/blob/master/fun/bi..., https://github.com/portasynthinca3/swl01u/blob/master/fun/ba...
  • Se isso é o que significa “não tenho muita experiência em engenharia reversa”, nem sei onde o restante de nós fica

    • Saber muito e, ainda assim, ter consciência de que não sabe nada parece algo por volta do nível 4 de experiência
      O nível 1 é o estado de ser novo e entusiasmado, mas saber que ainda não sabe; o nível 2 é “eu sou deus”; e o nível 3 é “eu sou um idiota”
    • Engenheiros não profissionais especialmente brilhantes costumam dizer esse tipo de coisa
  • Foi chamado de “primeiro shellcode MIDI do mundo”, mas, na maioria das principais plataformas, shellcode MIDI existe há mais de 20 anos: https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=midi

    • Há muitos buffer overflows, mas é outra questão saber se alguém chegou a escrever shellcode de fato para essas vulnerabilidades
  • Claro que era SysEx
    No MIDI padrão, SysEx é como um assembler inline em Python
    Dentro de quase todo dispositivo MIDI há coisas proprietárias não documentadas escondidas

    • Seria ótimo conseguir causar execução remota de código tocando uma música
      Seria muito legal plugar um teclado MIDI, tocar Am6,9/G# e ver uma janela de terminal com privilégios de root abrir
    • Estou ansioso para ver que tipo de hack de SysEx vão enfiar no suporte a MIDI do Chrome que o Google vem construindo nos últimos anos
    • Eu não fazia a menor ideia de que esse mundo existia
      Recentemente procurei o que seria necessário para fazer fuzzing de MIDI, e encontrei materiais para gerar arquivos .mid, mas não era bem o que eu queria
      Em vez disso, parece valer a pena explorar esse lado
    • SysEx é excelente
      É uma pena que os sintetizadores atuais pareçam usá-lo cada vez menos, especialmente os da Roland
      Ainda assim, a Behringer continua dando um suporte razoavelmente bom
      Por exemplo, o Deepmind já tem uma boa faixa de MIDI CC, mas via SysEx é programável quase 100%
  • Pesquisa impressionante
    Lembra um pouco o estudo de 2017 que sintetizou shellcode em moléculas reais de DNA/RNA e demonstrou execução remota de código em equipamentos de sequenciamento de DNA: https://www.usenix.org/conference/usenixsecurity17/technical...
    Eu ia dizer “o próximo é OSC?”, mas por enquanto parece que o MIDI ainda domina

  • Recomendo ler o artigo inteiro, mas acho que as frases centrais são estas
    “Esses malucos [do fabricante do teclado] criaram um shell que roda em cima de mensagens MIDI SysEx sobre USB”
    “O comando mais interessante é o de leitura/escrita arbitrária de memória. Se quiser, você pode espiar e cutucar a memória do sintetizador via MIDI”
    “Se quiser, você pode escrever essas mensagens em um arquivo MIDI e reproduzi-las no sintetizador como qualquer outro arquivo MIDI. Opa, estou tendo uma boa ideia…”
    “Depois de muitas noites sem dormir mergulhando no firmware, encontrei uma função que envia dados arbitrários para o controlador LCD”

    • Agora a verdadeira pergunta é se dá para alterar o código em execução no teclado para que, quando outro teclado do mesmo modelo receba esses dados MIDI, ele tente infectá-lo
      De certa forma, é um pequeno vislumbre do pesadelo da Internet das Coisas
      Quase qualquer dispositivo pode ter um backdoor, e pode até ser um backdoor idiota como #0000
    • Se você reproduzir isso como um arquivo MIDI, acho que o resultado vai soar como dubstep
    • Dizer que é possível ler e escrever a memória do sintetizador via MIDI parece simples, mas o SysEx não oferece garantia de entrega nem conceito de conexão ou sessão, então pode ser frustrante
      É completamente normal ocorrer perda de pacotes
  • Fico curioso se seria possível inserir música MIDI entre os comandos de Bad Apple para que o áudio também se reproduza por conta própria

  • O README do repositório diz que há um dump de imagem, mas na prática ele não está lá
    Fico me perguntando se esse é o estado correto

    • Foi um erro
      No último momento, adicionei *.bin ao .gitignore para excluir trechos de código montado, e parece que o dump foi excluído junto
      Pretendo fazer o upload em algumas horas
    • Parece que sim
      Como esses dumps podem estar protegidos por direitos autorais da Yamaha, talvez até tenha sido uma boa decisão
  • Se houver algum leitor do HN na Armênia, Porta vai apresentar esse tema em 10 de janeiro na Hacker Embassy
    Seria ótimo se você pudesse aparecer: https://t.me/hackerembassy/17