Como parte da reunião 0x0A DC7831
Neste artigo descreveremos como executar o firmware do dispositivo no emulador, demonstrar a interação com o depurador e realizar uma pequena análise dinâmica do firmware.
Pré-história
Há muito tempo atrás em uma galáxia muito distante
Há alguns anos, em nosso laboratório, houve a necessidade de investigar o firmware de um dispositivo. O firmware foi compactado e descompactado com um bootloader. Ele fez isso de uma forma muito complicada, mudando os dados da memória várias vezes. E o próprio firmware interagiu ativamente com os periféricos. E tudo isso no núcleo do MIPS.
Por razões objetivas, os emuladores disponíveis não nos agradavam, mas ainda queríamos executar o código. Decidimos então fazer nosso próprio emulador, que faria o mínimo e nos permitiria descompactar o firmware principal. Nós tentamos e funcionou. Pensamos, e se adicionarmos periféricos para executar também o firmware principal. Não doeu muito - e deu certo também. Pensamos novamente e decidimos fazer um emulador completo.
O resultado foi um emulador de sistemas de computador
Por que Kopycat?
Há um jogo de palavras.
- copycat (Inglês, substantivo [ˈkɒpɪkæt]) - imitador, imitador
- gato (Inglês, substantivo [ˈkæt]) - gato, gato - o animal preferido de um dos criadores do projeto
- A letra “K” vem da linguagem de programação Kotlin
Imitador
Ao criar o emulador, foram definidos objetivos bem específicos:
- a capacidade de criar rapidamente novos periféricos, módulos, núcleos de processador;
- a capacidade de montar um dispositivo virtual a partir de vários módulos;
- a capacidade de carregar quaisquer dados binários (firmware) na memória de um dispositivo virtual;
- capacidade de trabalhar com instantâneos (instantâneos do estado do sistema);
- a capacidade de interagir com o emulador por meio do depurador integrado;
- linguagem moderna e agradável para desenvolvimento.
Como resultado, foi escolhido Kotlin para implementação, uma arquitetura de barramento (é quando os módulos se comunicam entre si por meio de barramentos de dados virtuais), JSON como formato de descrição de dispositivo e GDB RSP como protocolo para interação com o depurador.
O desenvolvimento vem acontecendo há pouco mais de dois anos e está em andamento ativo. Durante esse tempo, os núcleos de processador MIPS, x86, V850ES, ARM e PowerPC foram implementados.
O projeto está crescendo e é hora de apresentá-lo ao público em geral. Faremos uma descrição detalhada do projeto posteriormente, mas por enquanto vamos nos concentrar no uso do Kopycat.
Para os mais impacientes, uma versão promocional do emulador pode ser baixada em
Rinoceronte no emulador
Lembremos que anteriormente para a conferência SMARTRHINO-2018, foi criado um dispositivo de teste “Rhinoceros” para ensinar habilidades de engenharia reversa. O processo de análise estática de firmware foi descrito em
Agora vamos tentar adicionar “alto-falantes” e executar o firmware no emulador.
Precisamos de:
1)Java 1.8
2) Python e módulo
Para Windows:
1)
2)
Para Linux:
1) socat
Você pode usar Eclipse, IDA Pro ou radare2 como cliente GDB.
Como isso funciona?
Para realizar o firmware no emulador, é necessário “montar” um dispositivo virtual, que seja análogo a um dispositivo real.
O dispositivo real (“rinoceronte”) pode ser mostrado no diagrama de blocos:
O emulador possui estrutura modular e o dispositivo virtual final pode ser descrito em um arquivo JSON.
JSON 105 linhas
{
"top": true,
// Plugin name should be the same as file name (or full path from library start)
"plugin": "rhino",
// Directory where plugin places
"library": "user",
// Plugin parameters (constructor parameters if jar-plugin version)
"params": [
{ "name": "tty_dbg", "type": "String"},
{ "name": "tty_bt", "type": "String"},
{ "name": "firmware", "type": "String", "default": "NUL"}
],
// Plugin outer ports
"ports": [ ],
// Plugin internal buses
"buses": [
{ "name": "mem", "size": "BUS30" },
{ "name": "nand", "size": "4" },
{ "name": "gpio", "size": "BUS32" }
],
// Plugin internal components
"modules": [
{
"name": "u1_stm32",
"plugin": "STM32F042",
"library": "mcu",
"params": {
"firmware:String": "params.firmware"
}
},
{
"name": "usart_debug",
"plugin": "UartSerialTerminal",
"library": "terminals",
"params": {
"tty": "params.tty_dbg"
}
},
{
"name": "term_bt",
"plugin": "UartSerialTerminal",
"library": "terminals",
"params": {
"tty": "params.tty_bt"
}
},
{
"name": "bluetooth",
"plugin": "BT",
"library": "mcu"
},
{ "name": "led_0", "plugin": "LED", "library": "mcu" },
{ "name": "led_1", "plugin": "LED", "library": "mcu" },
{ "name": "led_2", "plugin": "LED", "library": "mcu" },
{ "name": "led_3", "plugin": "LED", "library": "mcu" },
{ "name": "led_4", "plugin": "LED", "library": "mcu" },
{ "name": "led_5", "plugin": "LED", "library": "mcu" },
{ "name": "led_6", "plugin": "LED", "library": "mcu" },
{ "name": "led_7", "plugin": "LED", "library": "mcu" },
{ "name": "led_8", "plugin": "LED", "library": "mcu" },
{ "name": "led_9", "plugin": "LED", "library": "mcu" },
{ "name": "led_10", "plugin": "LED", "library": "mcu" },
{ "name": "led_11", "plugin": "LED", "library": "mcu" },
{ "name": "led_12", "plugin": "LED", "library": "mcu" },
{ "name": "led_13", "plugin": "LED", "library": "mcu" },
{ "name": "led_14", "plugin": "LED", "library": "mcu" },
{ "name": "led_15", "plugin": "LED", "library": "mcu" }
],
// Plugin connection between components
"connections": [
[ "u1_stm32.ports.usart1_m", "usart_debug.ports.term_s"],
[ "u1_stm32.ports.usart1_s", "usart_debug.ports.term_m"],
[ "u1_stm32.ports.usart2_m", "bluetooth.ports.usart_m"],
[ "u1_stm32.ports.usart2_s", "bluetooth.ports.usart_s"],
[ "bluetooth.ports.bt_s", "term_bt.ports.term_m"],
[ "bluetooth.ports.bt_m", "term_bt.ports.term_s"],
[ "led_0.ports.pin", "u1_stm32.buses.pin_output_a", "0x00"],
[ "led_1.ports.pin", "u1_stm32.buses.pin_output_a", "0x01"],
[ "led_2.ports.pin", "u1_stm32.buses.pin_output_a", "0x02"],
[ "led_3.ports.pin", "u1_stm32.buses.pin_output_a", "0x03"],
[ "led_4.ports.pin", "u1_stm32.buses.pin_output_a", "0x04"],
[ "led_5.ports.pin", "u1_stm32.buses.pin_output_a", "0x05"],
[ "led_6.ports.pin", "u1_stm32.buses.pin_output_a", "0x06"],
[ "led_7.ports.pin", "u1_stm32.buses.pin_output_a", "0x07"],
[ "led_8.ports.pin", "u1_stm32.buses.pin_output_a", "0x08"],
[ "led_9.ports.pin", "u1_stm32.buses.pin_output_a", "0x09"],
[ "led_10.ports.pin", "u1_stm32.buses.pin_output_a", "0x0A"],
[ "led_11.ports.pin", "u1_stm32.buses.pin_output_a", "0x0B"],
[ "led_12.ports.pin", "u1_stm32.buses.pin_output_a", "0x0C"],
[ "led_13.ports.pin", "u1_stm32.buses.pin_output_a", "0x0D"],
[ "led_14.ports.pin", "u1_stm32.buses.pin_output_a", "0x0E"],
[ "led_15.ports.pin", "u1_stm32.buses.pin_output_a", "0x0F"]
]
}
Preste atenção no parâmetro firmware seção params é o nome de um arquivo que pode ser carregado em um dispositivo virtual como firmware.
O dispositivo virtual e sua interação com o sistema operacional principal podem ser representados pelo seguinte diagrama:
A instância de teste atual do emulador envolve interação com as portas COM do sistema operacional principal (depuração UART e UART para o módulo Bluetooth). Podem ser portas reais às quais os dispositivos estão conectados ou portas COM virtuais (para isso você só precisa com0com/socat).
Para interagir com o emulador de fora, existem atualmente duas formas principais:
- Protocolo GDB RSP (portanto, as ferramentas que suportam este protocolo são Eclipse/IDA/radare2);
- linha de comando do emulador interno (Argparse ou Python).
Portas COM virtuais
Para interagir com o UART de um dispositivo virtual na máquina local através de um terminal, você precisa criar um par de portas COM virtuais associadas. No nosso caso, uma porta é usada pelo emulador e a segunda é usada por um programa de terminal (PuTTY ou tela):
Usando com0com
As portas COM virtuais são configuradas usando o utilitário de configuração do kit com0com (versão do console - C:Arquivos de programas (x86)com0comsetupс.exe, ou versão GUI - C:Arquivos de programas (x86)com0comsetupg.exe):
Marque as caixas ativar saturação de buffer para todas as portas virtuais criadas, caso contrário o emulador aguardará uma resposta da porta COM.
Usando socat
Em sistemas UNIX, as portas COM virtuais são criadas automaticamente pelo emulador usando o utilitário socat; para fazer isso, basta especificar o prefixo no nome da porta ao iniciar o emulador socat:
.
Interface de linha de comando interna (Argparse ou Python)
Como Kopycat é um aplicativo de console, o emulador fornece duas opções de interface de linha de comando para interagir com seus objetos e variáveis: Argparse e Python.
Argparse é uma CLI integrada ao Kopycat e está sempre disponível para todos.
Uma CLI alternativa é o interpretador Python. Para utilizá-lo, é necessário instalar o módulo Jep Python e configurar o emulador para funcionar com Python (será utilizado o interpretador Python instalado no sistema principal do usuário).
Instalando o módulo Python Jep
No Linux o Jep pode ser instalado via pip:
pip install jep
Para instalar o Jep no Windows, você deve primeiro instalar o Windows SDK e o Microsoft Visual Studio correspondente. Tornamos tudo um pouco mais fácil para você e
pip install jep-3.8.2-cp27-cp27m-win_amd64.whl
Para verificar a instalação do Jep, você precisa executar na linha de comando:
python -c "import jep"
A seguinte mensagem deve ser recebida em resposta:
ImportError: Jep is not supported in standalone Python, it must be embedded in Java.
No arquivo em lote do emulador para o seu sistema (copycat.bat - para Windows, imitador - para Linux) para a lista de parâmetros DEFAULT_JVM_OPTS
adicione um parâmetro adicional Djava.library.path
— deve conter o caminho para o módulo Jep instalado.
O resultado para Windows deve ser uma linha como esta:
set DEFAULT_JVM_OPTS="-XX:MaxMetaspaceSize=256m" "-XX:+UseParallelGC" "-XX:SurvivorRatio=6" "-XX:-UseGCOverheadLimit" "-Djava.library.path=C:/Python27/Lib/site-packages/jep"
Lançando o Kopycat
O emulador é um aplicativo JVM de console. O lançamento é realizado através do script de linha de comando do sistema operacional (sh/cmd).
Comando para executar no Windows:
binkopycat -g 23946 -n rhino -l user -y library -p firmware=firmwarerhino_pass.bin,tty_dbg=COM26,tty_bt=COM28
Comando para executar no Linux usando o utilitário socat:
./bin/kopycat -g 23946 -n rhino -l user -y library -p firmware=./firmware/rhino_pass.bin, tty_dbg=socat:./COM26,tty_bt=socat:./COM28
-g 23646
— Porta TCP que ficará aberta para acesso ao servidor GDB;-n rhino
— nome do módulo principal do sistema (dispositivo montado);-l user
— nome da biblioteca para busca do módulo principal;-y library
— caminho para busca dos módulos incluídos no dispositivo;firmwarerhino_pass.bin
— caminho para o arquivo de firmware;- COM26 e COM28 são portas COM virtuais.
Como resultado, um prompt será exibido Python >
(ou Argparse >
):
18:07:59 INFO [eFactoryBuilder.create ]: Module top successfully created as top
18:07:59 INFO [ Module.initializeAndRes]: Setup core to top.u1_stm32.cortexm0.arm for top
18:07:59 INFO [ Module.initializeAndRes]: Setup debugger to top.u1_stm32.dbg for top
18:07:59 WARN [ Module.initializeAndRes]: Tracer wasn't found in top...
18:07:59 INFO [ Module.initializeAndRes]: Initializing ports and buses...
18:07:59 WARN [ Module.initializePortsA]: ATTENTION: Some ports has warning use printModulesPortsWarnings to see it...
18:07:59 FINE [ ARMv6CPU.reset ]: Set entry point address to 08006A75
18:07:59 INFO [ Module.initializeAndRes]: Module top is successfully initialized and reset as a top cell!
18:07:59 INFO [ Kopycat.open ]: Starting virtualization of board top[rhino] with arm[ARMv6Core]
18:07:59 INFO [ GDBServer.debuggerModule ]: Set new debugger module top.u1_stm32.dbg for GDB_SERVER(port=23946,alive=true)
Python >
Interação com IDA Pro
Para simplificar os testes, usamos o firmware Rhino como arquivo fonte para análise no IDA no formato
Você também pode usar o firmware principal sem meta informações.
Após iniciar o Kopycat no IDA Pro, no menu Debugger vá até o item “Alternar depurador…" e selecione "Depurador GDB remoto". A seguir, configure a conexão: menu Depurador - Opções de processo…
Defina os valores:
- Aplicação - qualquer valor
- Nome do host: 127.0.0.1 (ou o endereço IP da máquina remota onde o Kopycat está sendo executado)
- Porto: 23946
Agora o botão de depuração fica disponível (tecla F9):
Clique nele para conectar-se ao módulo depurador no emulador. O IDA entra no modo de depuração, janelas adicionais ficam disponíveis: informações sobre registros, sobre a pilha.
Agora podemos usar todos os recursos padrão do depurador:
- execução passo a passo de instruções (Entre em и Passar por cima — teclas F7 e F8, respectivamente);
- iniciar e pausar a execução;
- criando pontos de interrupção para código e dados (tecla F2).
Conectar-se a um depurador não significa executar o código do firmware. A posição de execução atual deve ser o endereço 0x08006A74
- início da função Redefinir_Handler. Se você rolar a listagem para baixo, poderá ver a chamada de função principal. Você pode colocar o cursor nesta linha (endereço 0x08006ABE
) e execute a operação Execute até o cursor (tecla F4).
Em seguida, você pode pressionar F7 para entrar na função principal.
Se você executar o comando Continuar processo (tecla F9), então a janela “Aguarde” aparecerá com um único botão Suspender:
Quando você pressiona Suspender a execução do código do firmware é suspensa e pode continuar a partir do mesmo endereço do código onde foi interrompida.
Se continuar executando o código, você verá as seguintes linhas nos terminais conectados às portas COM virtuais:
A presença da linha “state bypass” indica que o módulo Bluetooth virtual mudou para o modo de recepção de dados da porta COM do usuário.
Agora no terminal Bluetooth (COM29 na imagem) você pode inserir comandos de acordo com o protocolo Rhino. Por exemplo, o comando “MEOW” retornará a string “mur-mur” para o terminal Bluetooth:
Imite-me não completamente
Ao construir um emulador, você pode escolher o nível de detalhe/emulação de um dispositivo específico. Por exemplo, o módulo Bluetooth pode ser emulado de diferentes maneiras:
- o dispositivo é totalmente emulado com um conjunto completo de comandos;
- Os comandos AT são emulados e o fluxo de dados é recebido da porta COM do sistema principal;
- o dispositivo virtual fornece redirecionamento completo de dados para o dispositivo real;
- como um esboço simples que sempre retorna "OK".
A versão atual do emulador usa a segunda abordagem - o módulo Bluetooth virtual realiza a configuração, após a qual muda para o modo de “proxy” de dados da porta COM do sistema principal para a porta UART do emulador.
Vamos considerar a possibilidade de instrumentação simples do código caso alguma parte da periferia não seja implementada. Por exemplo, se não foi criado um timer responsável por controlar a transferência de dados para DMA (a verificação é realizada na função ws2812b_waitlocalizado em 0x08006840
), então o firmware sempre esperará que o sinalizador seja redefinido ocupadolocalizado em 0x200004C4
que mostra a ocupação da linha de dados DMA:
Podemos contornar esta situação redefinindo manualmente o sinalizador ocupado imediatamente após instalá-lo. No IDA Pro, você pode criar uma função Python e chamá-la em um ponto de interrupção, e colocar o próprio ponto de interrupção no código após escrever o valor 1 no sinalizador ocupado.
Manipulador de ponto de interrupção
Primeiro, vamos criar uma função Python no IDA. Cardápio Arquivo - Comando de script...
Adicione um novo snippet na lista à esquerda e dê um nome a ele (por exemplo, BPT),
No campo de texto à direita, insira o código da função:
def skip_dma():
print "Skipping wait ws2812..."
value = Byte(0x200004C4)
if value == 1:
PatchDbgByte(0x200004C4, 0)
return False
Depois disso, pressionamos Execute e feche a janela do script.
Agora vamos para o código em 0x0800688A
, defina um ponto de interrupção (tecla F2), edite-o (menu de contexto Editar ponto de interrupção...), não se esqueça de definir o tipo de script como Python:
Se o valor do sinalizador atual ocupado é igual a 1, então você deve executar a função skip_dma na linha do script:
Se você executar o firmware para execução, o acionamento do código do manipulador de ponto de interrupção poderá ser visto na janela IDA saída por linha Skipping wait ws2812...
. Agora o firmware não esperará que o sinalizador seja redefinido ocupado.
Interação com o emulador
É improvável que a emulação pela emulação cause deleite e alegria. É muito mais interessante se o emulador ajudar o pesquisador a ver os dados na memória ou a estabelecer a interação das threads.
Mostraremos como estabelecer interação dinamicamente entre tarefas RTOS. Você deve primeiro pausar a execução do código, se ele estiver em execução. Se você for para a função bluetooth_task_entry para o ramo de processamento do comando “LED” (endereço 0x080057B8
), então você pode ver o que é criado primeiro e depois enviado para a fila do sistema ledControlQueueHandle alguma mensagem.
Você deve definir um ponto de interrupção para acessar a variável ledControlQueueHandlelocalizado em 0x20000624
e continue executando o código:
Como resultado, a parada ocorrerá primeiro no endereço 0x080057CA
antes de chamar a função osMailAlloc, então no endereço 0x08005806
antes de chamar a função osMailPut, depois de um tempo - para o endereço 0x08005BD4
(antes de chamar a função osMailGet), que pertence à função leds_task_entry (tarefa LED), ou seja, as tarefas foram trocadas e agora a tarefa LED recebeu o controle.
Desta forma simples você pode estabelecer como as tarefas RTOS interagem entre si.
É claro que na realidade a interação das tarefas pode ser mais complicada, mas utilizando um emulador, rastrear essa interação se torna menos trabalhoso.
Lançar com Radare2
Você não pode ignorar uma ferramenta tão universal como o Radare2.
Para conectar-se ao emulador usando r2, o comando ficaria assim:
radare2 -A -a arm -b 16 -d gdb://localhost:23946 rhino_fw42k6.elf
Lançamento disponível agora (dc
) e pausar a execução (Ctrl+C).
Infelizmente, no momento, r2 tem problemas ao trabalhar com o hardware do servidor gdb e layout de memória; por causa disso, pontos de interrupção e etapas não funcionam (comando ds
). Esperamos que isso seja corrigido em breve.
Executando com Eclipse
Uma das opções de utilização do emulador é depurar o firmware do dispositivo que está sendo desenvolvido. Para maior clareza, também usaremos o firmware Rhino. Você pode baixar as fontes de firmware
Usaremos o Eclipse do conjunto como um IDE
Para que o emulador carregue o firmware compilado diretamente no Eclipse, você precisa adicionar o parâmetro firmware=null
ao comando de inicialização do emulador:
binkopycat -g 23946 -n rhino -l user -y modules -p firmware=null,tty_dbg=COM26,tty_bt=COM28
Configurando a configuração de depuração
No Eclipse, selecione o menu Executar - Configurações de depuração... Na janela que se abre, na seção Depuração de hardware GDB você precisa adicionar uma nova configuração e, na guia “Principal”, especifique o projeto atual e o aplicativo para depuração:
Na aba “Debugger” você precisa especificar o comando GDB:
${openstm32_compiler_path}arm-none-eabi-gdb
E insira também os parâmetros para conexão ao servidor GDB (host e porta):
Na aba “Inicialização”, você deve especificar os seguintes parâmetros:
- ativar caixa de seleção Carregar imagem (para que a imagem do firmware montada seja carregada no emulador);
- ativar caixa de seleção Carregar símbolos;
- adicione o comando de inicialização:
set $pc = *0x08000004
(defina o registro do PC para o valor da memória no endereço0x08000004
- o endereço está armazenado lá ResetHandler).
Nota, se você não quiser baixar o arquivo de firmware do Eclipse, então as opções Carregar imagem и Executar comandos não há necessidade de indicar.
Depois de clicar em Debug, você pode trabalhar no modo depurador:
- execução de código passo a passo
- interagindo com pontos de interrupção
Nota. Eclipse tem, hmm... algumas peculiaridades... e você tem que conviver com elas. Por exemplo, se ao iniciar o depurador aparecer a mensagem “Nenhuma fonte disponível para “0x0″”, execute o comando Step (F5)
Em vez de uma conclusão
Emular código nativo é algo muito interessante. Torna-se possível para um desenvolvedor de dispositivos depurar o firmware sem um dispositivo real. Para um pesquisador, é uma oportunidade de realizar análises dinâmicas de código, o que nem sempre é possível mesmo com um dispositivo.
Queremos fornecer aos especialistas uma ferramenta que seja conveniente, moderadamente simples e que não exija muito esforço e tempo para ser configurada e executada.
Escreva nos comentários sobre sua experiência com emuladores de hardware. Convidamos você a discutir e teremos prazer em responder perguntas.
Apenas usuários registrados podem participar da pesquisa.
Para que você está usando o emulador?
-
Eu desenvolvo (depuro) firmware
-
Estou pesquisando firmware
-
Eu lanço jogos (Dendi, Sega, PSP)
-
outra coisa (escreva nos comentários)
7 usuários votaram. 2 usuários se abstiveram.
Qual software você usa para emular código nativo?
-
QEMU
-
Motor unicórnio
-
Proteu
-
outra coisa (escreva nos comentários)
6 usuários votaram. 2 usuários se abstiveram.
O que você gostaria de melhorar no emulador que está usando?
-
eu quero velocidade
-
Quero facilidade de configuração/inicialização
-
Quero mais opções de interação com o emulador (API, ganchos)
-
Estou feliz com tudo
-
outra coisa (escreva nos comentários)
8 usuários votaram. 1 usuário se absteve.
Fonte: habr.com