- 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
0x3f0f0f0fe 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
logine 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çãoDMLCD - 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
TESTNePROTNpara 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
- manipular os pinos
- Ao ativar
TESTN, o sintetizador não inicializava, ePROTNnã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
arm7tdmino 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ço0no GDB apareceu uma instrução de salto no formatoldr pc, [pc, #24] - Foram extraídos 16 MiB a partir do endereço
0e abertos no Cutter, mas as strings se repetiam a cada 64 KiB- Ex.:
SWL01U Internalse repetia em0x0000bfd0,0x0001bfd0,0x0002bfd0etc.
- Ex.:
- 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
GrandPnoTr1 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
- ROM interna:
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,?,infoeverestavam 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
loginePasswd Errorconfirmaram 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íapasswd? - ao digitar a senha
#0000, saíalogin OK - depois disso, os comandos podiam ser executados
- ao digitar
- Entre os comandos identificados no shell estavam
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
- O comando
inforetornava as seguintes informaçõesDevelopName PSR-E433DevelopNumber #3341Main DevelopNumber #3341Make data & time MAY 16 2012 19:00:57J/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
- header:
- Uma mensagem MIDI SysEx começa com
0xF0, passa pelo ID do fabricante e pelo payload e termina com0xF7, e o payload só pode conter bytes com MSB igual a 0 - O
0x43do 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\rdo 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
HeloWrldna á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
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
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
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
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”
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
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 muito legal plugar um teclado MIDI, tocar Am6,9/G# e ver uma janela de terminal com privilégios de root abrir
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
É 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”
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
É 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
No último momento, adicionei
*.binao.gitignorepara excluir trechos de código montado, e parece que o dump foi excluído juntoPretendo fazer o upload em algumas horas
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