
Eu tenho um amigo que está envolvido na reparação de ferro automotivo. De alguma forma, ele me trouxe um microcontrolador soldado de uma unidade de controle de aquecedor autônomo. Ele disse que seu programador não aceita e gostaria de poder transferir o firmware para frente e para trás, porque existem muitos blocos; no ferro, eles geralmente são os mesmos, mas as unidades que controlam são diferentes. E parece que existe um bloco em vez de um com defeito, mas o software é diferente e você simplesmente não pode substituí-lo. Como a tarefa era interessante, decidi vasculhar. Se o tópico é interessante para você, por favor, sob o gato ...
O sujeito era M306N5FCTFP. Este é um microcontrolador do grupo M16C / 6N5. O núcleo M16C / 60 foi desenvolvido pela Mitsubishi e, como Desde 2003, o sucessor desta empresa em termos de MK é Renesas, agora esses microcontroladores são conhecidos sob esta marca.
Um pouco sobre o próprio microcontrolador
O seixo é um microcontrolador de 16 bits em um pacote QFP de 100 pinos. O kernel possui 1 MB de espaço de endereço, uma frequência de clock de 20 MHz para desempenho automotivo. O conjunto de periféricos também é muito extenso: dois temporizadores de 16 bits e a possibilidade de gerar um PWM trifásico para controle do motor, todos os tipos de UART, SPI, I2C naturalmente, 2 canais DMA, existe um controlador CAN2.0B integrado e um PLL. Na minha opinião, é muito bom para o velho. Aqui está um gráfico de visão geral da documentação:

Como minha tarefa é extrair o software, ele também está muito interessado em memória. Este MK foi produzido em duas versões: mascarado e Flash. Consegui, como mencionado acima, o M306N5FCTFP. Sobre ele, a descrição diz o seguinte:
- Versão em memória flash
- 128 KBytes + 4K (4K adicional - o chamado bloco A como um presente para o usuário armazenar dados, mas também pode armazenar o programa)
- V-ver. (versão automotiva com faixa de + 125 ° C)
Como retirar do dispositivo o que os desenvolvedores arrastaram
É natural que você comece a tentar obter algo do microcontrolador estudando os mecanismos integrados pelo desenvolvedor do chip para tarefas de programação de memória. O manual declara que o fabricante gentilmente colocou um gerenciador de inicialização na memória para as necessidades de programação em circuito do dispositivo.

Como você pode ver na figura acima, a memória é dividida em 2 partes: a área do usuário e a área do carregador de inicialização. No segundo, um carregador de inicialização padrão é carregado de fábrica, que pode gravar, ler, apagar a memória do usuário e se comunicar por meio de uma interface assíncrona, síncrona ou CAN. É indicado que ele pode ser reescrito por conta própria ou não pode ser reescrito. No final, isso é facilmente verificado tentando bater no carregador de inicialização padrão pelo menos através do UART ... Olhando para o futuro: o fabricante do aquecedor não se incomodou com o carregador de inicialização, para que você possa ir mais longe nessa direção. Faça imediatamente a reserva de que ainda existe um método de programação paralelo, mas desde Eu não tinha um programador para isso, não considerei essa opção.
A entrada no modo de operação do carregador de inicialização é fornecida por uma certa combinação nas entradas CNVSS, P5_0, P5_5 durante uma redefinição de hardware. Em seguida, escreva seu próprio utilitário para copiar o conteúdo da memória ou use o finalizado. A Renesas fornece seu próprio utilitário, chamado "M16C Flash Starter", mas possui uma função de leitura reduzida. Ele não salva o que lê no disco, mas o compara com um arquivo do disco. I.e. de fato, isso não é leitura, mas verificação. No entanto, existe um utilitário gratuito alemão chamado M16C-Flasher, que pode ler o firmware. Em geral, o kit de ferramentas inicial foi escolhido.
Sobre a proteção de leitura

Tudo seria bem simples se o gerenciador de inicialização não fornecesse proteção contra acesso não autorizado. Vou apenas dar uma tradução muito livre do manual.
Função de verificação de ID
Usado nos modos de troca serial e CAN. O identificador transmitido pelo programador é comparado com o identificador gravado na memória flash. Se os identificadores não corresponderem, os comandos enviados pelo programador não serão aceitos. No entanto, se 4 bytes do vetor de redefinição forem FFFFFFFFh, os identificadores não serão comparados, permitindo que todos os comandos sejam executados. O identificador é de 7 bytes armazenados sequencialmente, iniciando no primeiro byte, nos endereços 0FFFDFh, 0FFFE3h, 0FFFEBh, 0FFFEFh, 0FFFF3h, 0FFFF7h e 0FFFFBh.Assim, para acessar o programa, você precisa conhecer os 7 bytes estimados. Mais uma vez, olhando para o futuro, conectei ao MK usando o mesmo “M16C Flash Starter” e certifiquei-me de que combinações de zeros e FF não funcionassem, e esse problema teria que ser resolvido de alguma forma. Uma ideia com um ataque através de canais de terceiros veio à tona imediatamente aqui. Já comecei a fingir ser um lenço na cabeça, o que me permite medir a corrente no circuito elétrico, mas decidi que a Internet é grande e a maioria das bicicletas já foi inventada. Após algumas pesquisas, rapidamente encontrei no hackaday.io o projeto Serge 'q3k' Bazanski, intitulado “Engenharia reversa Toshiba R100 BIOS”. E dentro da estrutura deste projeto, o autor resolveu essencialmente exatamente o mesmo problema: extração de firmware do MK M306K9FCLR. Além disso - naquela época a tarefa já havia sido resolvida com sucesso por ele. Por um lado, fiquei um pouco chateado - um enigma interessante não foi resolvido por mim. Por outro lado, a tarefa passou de uma busca por vulnerabilidade para sua exploração, que prometeu uma solução muito mais rápida.
Em poucas palavras, q3k, exatamente pela mesma lógica, iniciou o estudo com uma análise do consumo atual; nesse sentido, estava em condições muito mais favoráveis, porque ele tinha ChipWhisperer, eu ainda não tenho essa coisa. Mas desde sua primeira sonda para remover a corrente de consumo se mostrou inadequada e ele não conseguiu isolar algo útil do barulho, decidiu tentar um ataque simples ao tempo de resposta. O fato é que o carregador de inicialização extrai a saída OCUPADA durante a execução do comando para informar ao host que está ocupado ou está pronto para executar o próximo comando. De acordo com a suposição de q3k, medir o tempo entre a transmissão do último bit do identificador e a remoção do sinalizador ocupado pode servir como fonte de informações durante a enumeração. Ao verificar essa suposição enumerando o primeiro byte da chave, um desvio de tempo foi realmente encontrado em apenas um caso - quando o primeiro byte era igual a FFh. Para a conveniência do tempo de medição, o autor até diminuiu a velocidade do MK, desligando o ressonador de quartzo e aplicando uma onda quadrada de 666 kHz à entrada do relógio para simplificar o procedimento de medição. Depois disso, o identificador foi selecionado com sucesso e o software foi recuperado.
A primeira panqueca - um ancinho
Ha! Eu pensei ... Agora, rapidamente rebitei o programa para o meu STM32VLDiscovery c STM32F100 a bordo, que enviará o código e medirá o tempo de resposta, e cuspir os resultados da medição no terminal. Porque A placa de ensaio com o controlador de destino foi conectada anteriormente ao PC através do adaptador USB-UART. Para não alterar nada na placa de ensaio, trabalharemos no modo assíncrono.

Quando, no início do carregador de inicialização, a entrada CLK1 é puxada para o chão, ele percebe que eles querem comunicação assíncrona dele. Foi por isso que o usei - o suspender já estava soldado e eu apenas conectei as duas placas com os fios: Discovery e a placa de ensaio com o alvo M306.
Nota sobre a harmonização de níveis:
Porque Como o M16 possui níveis de TTL nos terminais e o STM32 possui LVTTL (simplificado, consulte a folha de dados para obter detalhes), a correspondência de níveis é necessária. Porque este não é um dispositivo que, como uma bateria conhecida, funcione, funcione e funcione, mas, na verdade, ele se conecta uma vez na mesa, então eu não me incomodei com tradutores de nível: os níveis de saída digeridos de cinco volts do STM32, no sentido de 3 volts, percebidos como "1" , as saídas do M16 são alimentadas nas entradas tolerantes a 5V do STM32, para que não pareçam ruins, e não esquecemos de colocar a perna que puxa o RESET M16 no modo de dreno aberto. Eu esqueci, e isso é + 2 horas para o cofrinho do tempo perdido.
Esse mínimo é suficiente para entender as glândulas um do outro.A lógica do software atacante é a seguinte:
- Estabelecemos uma conexão com o controlador. Para fazer isso, você deve esperar até que a redefinição seja concluída e transmitir 16 caracteres zero com um intervalo de mais de 20 ms. Isso é para elaborar o algoritmo para determinar automaticamente a taxa de câmbio, porque a interface é assíncrona e o MK não sabe nada sobre sua frequência. A velocidade inicial do transmissor deve ser 9600 baud, é nessa velocidade que o carregador calcula. Depois disso, se desejar, você pode solicitar uma taxa de câmbio diferente de cinco disponível no intervalo 9600-115200 (embora, no meu caso, o carregador tenha se recusado a trabalhar no 115200). Como não preciso alterar a velocidade, solicitei apenas a versão do gerenciador de inicialização para controlar a sincronização. Passamos FBh, o carregador responde com uma linha como "VER.1.01".
- Enviamos o comando “unlock”, que contém a iteração atual da chave, e medimos o tempo até que o sinalizador de ocupado seja apagado.

O comando consiste no código F5h, três bytes do endereço onde a área do identificador começa (no meu caso, para o kernel M16C, é 0FFFDFh), comprimento (07h) e o próprio identificador.
- Medimos o tempo entre a transmissão do último bit do identificador e a remoção do sinalizador de ocupado.
- Aumentamos o byte de chave que está sendo classificado (KEY1 no estágio inicial), retornamos à etapa 2 até classificarmos todos os 255 valores do byte atual.
- Redefinimos as estatísticas para o terminal (bem, ou realizamos a análise "a bordo").
Para se comunicar com o MK de destino, usei o USART no STM32, para medir o tempo - um temporizador no modo de captura de entrada. A única coisa, por simplicidade, não medi o tempo entre o último bit da chave e a remoção da bandeira, mas entre o início da transmissão e a bandeira. O motivo foi que o último bit pôde mudar e, no modo assíncrono, não havia nada a ser anexado à entrada de captura. Ao mesmo tempo, o UART é hardware e o tempo de transmissão é basicamente idêntico e não deve haver erros tangíveis.
Como resultado, para todos os valores, os resultados foram idênticos. Completamente idêntico. A frequência do relógio do temporizador foi de 24 MHz, respectivamente, a resolução de tempo é de 41,6 ns. Bem, ok, tentei desacelerar o alvo MK. Nada mudou. Aqui surgiu a pergunta na minha cabeça: o que estou fazendo de errado, como o q3k fez? Após a comparação, a diferença foi encontrada: ele usa uma interface de troca síncrona (SPI) e eu sou assíncrono (UART). E em algum lugar aqui, chamei a atenção para o momento que perdi no início. Mesmo nos diagramas de fiação para modos de carregador de inicialização síncrono e assíncrono, a saída pronta é nomeada de forma diferente:

Em síncrono é "OCUPADO", em assíncrono é "Monitor". Examinamos a tabela "Funções de saída no modo de E / S serial padrão":
"Semyon Semenych ..."A ninharia, que foi esquecida a princípio, trouxe o lugar errado. Na verdade, se no modo síncrono, esse é exatamente o sinalizador ocupado do carregador de inicialização, no modo assíncrono (aquele que o modo de E / S serial 2) é apenas um "pisca-pisca" para indicar a operação. Talvez, em geral, o sinal de hardware esteja pronto para o transceptor e, portanto, a incrível precisão de sua elevação.
Em geral, soldamos o resistor no pino SCLK do chão ao VCC, soldamos o fio, conectamos tudo ao SPI e começamos novamente ...
Sucesso!

No modo síncrono, tudo é quase o mesmo, apenas nenhum procedimento preliminar para estabelecer uma conexão é necessário, a sincronização é simplificada e a captura de tempo pode ser realizada com mais precisão. Se eu escolhesse imediatamente esse modo, economizaria tempo ... Mais uma vez, não compliquei e medi o tempo desde o último bit, mas iniciei o timer antes de iniciar a transferência do último byte da chave, ou seja, ligamos o timer e enviamos para o transmissor KEY7 (na captura de tela acima, no analisador lógico, é possível ver a distância entre os cursores. Esse é o intervalo de tempo medido).
Isso foi mais do que suficiente para uma identificação bem-sucedida. Aqui está a enumeração de um byte:

No eixo x, temos o número de contagens discretas, no eixo y, respectivamente, o valor da chave transmitida. A relação sinal / ruído é tal que mesmo nenhum filtro é necessário, assim como na escola em uma aula de informática: encontramos o máximo na matriz e vamos para a seleção do próximo byte. Os primeiros 6 bytes são selecionados com facilidade e rapidez, um pouco mais difícil com o último: aí é apenas um descaramento descarado que não funciona, você precisa redefinir a "vítima" antes de cada tentativa. Como resultado, são necessários cerca de 400 ms para cada tentativa e a pesquisa é, na pior das hipóteses, na região de um minuto e meio. Mas isso é o pior. Após cada tentativa, solicitamos um status e, assim que adivinhamos, paramos. No começo, geralmente eu rapidamente examinava o identificador com canetas, inserindo a saída do console no Excel e plotando o gráfico, tanto mais que era uma tarefa única, mas no artigo eu decidi adicionar iteração automática, em prol de um console bonito ...

Obviamente, se o desenvolvedor apagasse o gerenciador de inicialização (substituído pelo próprio), não seria tão fácil sair, mas na eletrônica automotiva, os MKs geralmente não são fechados. Em particular, na unidade de controle de outro aquecedor, no qual o V850 da mesma Renesas foi instalado, tudo foi decidido soldando um par de fios e copiando o firmware com um utilitário padrão. Este é todo o mecanismo de criptomoeda no mundo da ECU. Aparentemente, os fabricantes não gostam do fenômeno de ajuste de chips e outros tipos de interferência ... Embora isso seja como uma corrida de armaduras e projéteis - as glândulas são mais íngremes, mais caras, mas não há vencedor ...
Referências:
- https://www.dataman.com/media/datasheet/Renesas/M16C6N5Group.pdf
- https://hackaday.io/project/723-reverse-engineering-toshiba-r100-bios/log/51302-ec-firmware-dumped
- https://q3k.org/slides-recon-2018.pdf