Engenharia reversa do firmware do dispositivo usando o exemplo de um "rinoceronte" intermitente. Parte 2


Apresentamos a sua atenção a segunda parte do artigo sobre engenharia reversa do firmware do dispositivo Flashing Rhino, com base na classe principal da conferência SMARTRHINO-2018 .

Na primeira parte do artigo, o firmware do dispositivo foi carregado no desmontador IDA e uma análise inicial dos comandos do protocolo do dispositivo foi realizada. Comandos individuais foram testados em um dispositivo em funcionamento.

Na segunda parte, uma análise das tarefas restantes do firmware será realizada.

Deixe-me lembrá-lo, depois de analisar a tarefa Bluetooth em termos de controle de LEDs, foi decidido mudar para a tarefa de LED, pois a tarefa inicial é criar um aplicativo para controlar LEDs e, para isso, é necessário um entendimento detalhado da operação do firmware.

O arquivo de firmware está disponível para estudo independente.

Todas as informações são fornecidas apenas para fins educacionais.

Sob o gato, há muitos rinocerontes piscando.

Tarefa de LED


Resumidamente: uma análise completa da tarefa responsável pela troca de LEDs. Análise de tipos de dados e variáveis ​​globais.

A tarefa do LED é representada pela função x_leds_task , localizada em 0x08005A08 .

Além das linhas estranhas "Eu tenho uma super potência ..." na função principal da tarefa de LED, você pode prestar atenção à linha "matiz> max: mudar o brilho \ r \ n" .



Ao mesmo tempo, vemos uma situação familiar - (PALAVRA *) (v26 + 4). No menu de contexto da variável v26, selecione o item "Converter em struct *" e indique a estrutura criada anteriormente:



Dado que v5 = v26 , repetimos a operação “Convert to struct *” para a variável v5.

Continuamos a estruturar o código e os dados. Defina a representação hexadecimal em qualquer lugar. Renomear:

  • v5 - led ;
  • v6 - idx ;
  • v8 - matiz_1 ;
  • v9 - matiz_2 ;
  • v26 - _led ;

O código está melhorando. Mas algumas variáveis ​​ainda machucam os olhos, por exemplo, a variável v23:





Aparentemente, a v23 é uma matriz de 4 bytes.
idx é o índice do LED; esse índice é adicionado ao endereço base; dessa maneira, o acesso é feito a elementos com os mesmos deslocamentos - é assim que as matrizes se comportam.

Nós atribuímos o tipo char v23[4] e o renomeamos para leds_smth , o código se torna mais bonito:



Você também pode observar que o resultado da função x_queue_recv é retornado para a variável v25:

 x_queue_recv(&v25, leds_queue, 1000); 

Mas pode não estar claro como os dados necessários estão na estrutura _led . O fato é que as variáveis ​​v25 e _led estão localizadas uma na outra na pilha - isso pode ser entendido pelo fato de que na descompilação elas são escritas em linhas adjacentes. A localização das variáveis ​​na pilha pode ser vista em uma janela separada se você clicar duas vezes na variável:



Eles provavelmente são uma estrutura ou o compilador fez alguma otimização. Assim, pode-se argumentar que os dados da tarefa Bluetooth são transmitidos para a tarefa LED. Para saber com mais precisão, vou verificar o dispositivo - para o LED zero via Bluetooth, enviarei os valores 0x208 , 0x2D0 , 0x398 , 0x3E9 , que podem ser observados no código:



Os resultados da verificação do valor da matiz no dispositivo:

  • 0x208 - os LEDs pararam de alternar suavemente e foram configurados nas cores: vermelho, verde, azul, roxo;
  • 0x2D0 - os LEDs começaram a mudar novamente;
  • 0x398 - nada mudou;
  • 0x3E9 - nada mudou.

Se você olhar o código novamente, poderá ver que o valor 0x398 pode ser associado logicamente a um valor menor que 0x167 (valores diferentes são definidos para o elemento da matriz leds_smth ). Portanto, executarei esta verificação: primeiro, LED 010078FF20 o primeiro LED para verde (matiz = LED 010078FF20 ), enquanto os outros três LEDs continuarão mudando de cor.


Agora, LED 010398FFFF protocolo Bluetooth - depois disso, o primeiro LED passou para o modo geral de troca de cores.

Portanto, o valor de matiz 0x398 redefine o valor estático da cor, o que significa que a matriz leds_smth contém sinalizadores (0 ou 1) para os LEDs serem ocupados:

  • 0 - o LED não está ocupado, participa da alternância suave de cores ( matiz = 0x398 );
  • 1 - o LED está ocupado, o usuário define uma cor estática ( matiz <= 0x167 ).

Renomeie leds_smth para leds_busy .

Agora, o objetivo do seguinte bloco de código deve ficar claro:



O ciclo nas linhas 83-101 executa um mosaico de cores suave com uma etapa de troca de cores de 5: v12 += 5 . Se o LED estiver com uma cor estática acesa, esse LED não participará do mosaico. Após o ciclo, existem linhas de inclusão a curto prazo de todos os LEDs.

Renomear:

  • sub_800678A - x_led_set_hsv ;
  • v12 - hue_step ;
  • v13, v17, v18, v19 - led0_busy , led1_busy , led2_busy , led3_busy ;
  • v11, v20, v21, v22 - matiz0 , matiz1 , matiz2 , matiz3 ;
  • dword_200004C4 - led_control .

A função sub_80039FE presumivelmente executa um tempo limite (caso contrário, os LEDs não mudaram suavemente, mas instantaneamente), vamos chamá-lo de x_sleep e a variável v16 é led_timeout .

O objetivo da função sub_8006934 ainda não é óbvio, mas é usado em todos os lugares após a configuração da cor nos LEDs - você pode chamá-lo de x_led_fix_color .

Após a renomeação, é fácil entender a função sub_8006944 (chamada na tonalidade <= 0x167 branch):



Simplesmente realiza uma verificação adicional para determinar a cor do LED. Renomeie a função sub_8006944 para x_led_set_hsv_wrap (sufixo _wrap - uma explicação de que este é um "invólucro" sobre outra função) e defina o seguinte protótipo para ela:

 signed int __fastcall x_led_set_hsv_wrap(int led_control, signed int idx, int hue, char sat, char val) 

Vamos voltar um nível para a função x_leds_task. Mais uma vez olhando para o código, você pode descobrir que o ramo "matiz> 0x3E8" começou a ficar assim:



Ou seja, um valor de matiz maior que 0x3E8 deve alterar o tempo limite do mosaico colorido. Vou verificar enviando alguns valores para o dispositivo:

  • matiz = 0x3E9 - os LEDs começaram a mudar rapidamente:


  • matiz = 0xFFFF - os LEDs começaram a mudar muito lentamente:



Ao sair do ciclo principal da tarefa de LED, a função sub_8003C44 é usada , que também é usada na função sub_8005070:



Renomear:

  • sub_8005070 - x_freeMsg ;
  • sub_8003C44 - x_free_queue .

Além disso, na tarefa de LED, o seguinte ramo não pode deixar de atrair atenção:



Você pode tentar executar o comando de LED B816D8D90000FFFF . Mas se você se lembrar de que apenas 2 caracteres são usados ​​como índice de LED, uma tentativa de alcançar esse código será obviamente malsucedida. Deixe este tópico para mais tarde. Renomeie a função sub_8004AE8 para x_mad_blinking e é hora de corrigir a assinatura da função x_printf (da última vez que escrevi a assinatura errada):

 void x_printf(const char *format, ...) 

O ciclo principal da tarefa de LED foi desmontado, mas ainda há um código no início da tarefa.

Vamos dar uma olhada no código:



Na linha 49, é mais provável que os LEDs sejam verificados quanto à disponibilidade e, no caso de um erro, seja feita uma chamada para a função sub_8004BBC, que desliga as interrupções e inicia um loop infinito no qual a linha "../Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_gpio.c" é usada. Provavelmente, é uma função de asserção ou similar.

Renomear:

  • sub_8004BBC - x_gpio_assert ;
  • sub_800698C - x_check_gpio .

O objetivo da função sub_8006968 ficará claro se você observar atentamente o dispositivo quando ele estiver ligado:


Todos os quatro LEDs juntos acendem primeiro vermelho, depois verde e depois azul. Depois disso, eles são definidos por cor: 0-vermelho, 1-verde, 2-azul, 3-violeta. E só então eles começam a trocar os mosaicos.

Como o mosaico inicia no ciclo principal de tarefas, é lógico que as linhas 58-61 antes do ciclo principal sejam responsáveis ​​pela inclusão de curto prazo de cores diferentes nos LEDs, e as linhas 52-56 sejam responsáveis ​​por definir vermelho-verde-azul em todos os LEDs de uma só vez. Renomeie a função sub_8006968 para x_led_all_set_rgb (RGB - apenas em um palpite, de acordo com os argumentos passados).

Extravagâncias na tarefa de LED


Resumidamente: definindo a funcionalidade do código que contém linhas estranhas. Gerando uma senha para o dispositivo.

Agora vamos para o início da função x_leds_task:



"Eraze" , "gen" , "flash" , "redefinir" - por que tudo isso ???

Vamos tentar descobrir.

Seja sub_80066BC x_leds_task_init .

Vejamos sub_8006B38:



Memset de água pura, concorda?



Voltar para x_leds_task. Algo está errado com o tipo de variável v24:



A IDA cometeu um pequeno erro com o tipo, mas um comentário com uma marca de pilha nos ajuda. Entre as variáveis ​​v24 e v25 até 12 bytes (0x44 - 0x38). Portanto, renomeamos a v24 para buf e atribuímos o tipo unsigned __int8 buf[12] (Ida unsigned __int8 buf[12] que o novo tipo de dados é maior que o antigo - concordamos).

Próximo. Função sub_8004CE4:



Renomeie a1 para buf , v1 para _buf .

Função sub_8006B26:



Você a reconheceu?

E se sem maquiagem?

Claro memcpy . Renomeie.

O objetivo da função sub_8004CE4 é obter alguns dados no endereço 0x08007C00 . A propósito, esse endereço está no intervalo de endereços da memória flash do microcontrolador (e firmware, em particular). Renomeie sub_8004CE4 para x_read_data_0x08007C00 .

Linha de Função X_leds_task 36:

 if ( (unsigned int)buf[0] - 65 > 0x19 ) 

Altere a exibição dos dados (tecla R no número 65, tecla H no número 0x19):

 if ( (unsigned int)buf[0] - 'A' > 25 ) 

Depois de um pouco de reflexão, você pode entender que esse é um teste do alcance do alfabeto latino AZ.

Em seguida, usando os prompts na forma de cadeias de formato, renomeie:

  • sub_8004C10 - x_erase ;
  • sub_80059C8 - x_gen ;
  • sub_8004C84 - x_flash .

A função sub_8003C66 não faz nada notável - apenas aumenta alguma variável global - renomeie sub_8003C66 para x_smth_inc .

A função x_erase não aceita nenhum argumento - isso pode ser verificado no desmontador:



Dentro do x_erase, o endereço familiar 0x08007C00 é usado e três funções desconhecidas são acessadas:



Uma rápida olhada nessas três funções, vemos que elas estão acessando endereços no intervalo 0x40022000 - 0x400223FF . A documentação para o microcontrolador afirma claramente que essa é a faixa da "Interface FLASH" . Ou seja, a função x_erase apaga um pouco da memória flash - ótimo!

Aparentemente, a função x_flash grava na memória flash, depois de verificar o comprimento da linha para escrever (a propósito, os argumentos a2 e a3 são supérfluos aqui - ajudaremos o Idea):



E tudo isso acontece no "dispositivo de iluminação" ???

Bem, e a função x_gen ? Após uma rápida olhada e renomeação de variáveis, ficará assim:



A função sub_8006CB4 tem a seguinte aparência:



E sub_8006D10 - assim:



Não retenha o desejo de pesquisar na Internet essas constantes indecentemente bonitas: 0xABCD , 0x1234 , 0xE66D , 0xDEEC , 0x4C957F2D e 0x5851F42D . Se a Internet ainda não estiver completamente proibida, você provavelmente encontrará essas constantes na fonte para funções aleatórias . Não é de admirar que a função pai seja chamada x_gen.

Essa também é uma situação muito típica: chame srand () antes do loop e chame random () no loop, então renomeie-o:

  • sub_8006D10 - x_rand;
  • sub_8006CB4 - x_srand.

Um leitor curioso, examinando a função sub_8005098 , pode descobrir de onde vem a semente da função srand.

Assim, a função x_gen gera uma sequência aleatória do tamanho especificado .

Depois que a linha gerada é gravada na memória flash, o dispositivo é reinicializado:



Parece algum tipo de reinicialização estranha. Mas se olharmos para a lista de tarefas deste dispositivo, encontraremos "watchdogTask" entre elas. Obviamente, se houver uma "tarefa emperrada", o watchdog será reiniciado.

A tarefa de LED, exceto o modo MadBlinking, pode ser considerada analisada.

Vamos analisar através das linhas que outras tarefas existem no sistema:



Depois de restaurar os links para as strings no código, você pode ver esta figura:



Primeiro, há um link para uma string com o nome task, depois um link para a função principal da tarefa. E eles são usados ​​na função principal , onde essas tarefas são iniciadas:



Vamos executar as renomeações ausentes:

  • sub_80050FC - x_sensor_task ;
  • sub_8004AAC - x_watchdogTask ;
  • sub_8005440 - x_uartRxTask .

Tarefa Watchdog


O watchdog de tarefas não faz nada particularmente interessante:



Renomear:

  • dword_200003F8 - wd_variable ;
  • sub_8001050 - x_update_wd_var .

Tarefa UART


Resumidamente: pesquise dados e funções que possuem links de diferentes funções. Determinação da sua finalidade.

Uma rápida olhada na tarefa UART permite detectar o envio de dados para uma fila desconhecida definida pela variável unk_200003EC :



Após restaurar os links para essa variável por meio de pesquisa binária, veremos que, além do x_uartRxTask, ele é usado no principal (lá, a fila é criada, aparentemente) e na função desconhecida até o momento sub_80051EC :



Renomear:

  • sub_80051EC - x_recvMsg_uart_queue ;
  • unk_200003EC - uart_queue .

Consulte as referências cruzadas para x_recvMsg_uart_queue:

  • sub_8005250;
  • x_bluetooth_task.

Primeiro, consulte a função sub_8005250 :



Depois de pensar, renomeie:

  • unk_2000034C - cmd_count ;
  • a1 - cmd ;
  • v4 - _cmd ;
  • v6 é rsp ;
  • sub_8005250 - x_bluetooth_cmd .

Vamos ver agora onde o x_bluetooth_cmd ainda é usado. Todos os links adicionais somente da tarefa Bluetooth, é hora de retornar a ela.

Voltar à tarefa bluetooth


Resumidamente: a análise final da tarefa Bluetooth. Procure autorização sem uma senha.



Se você observar os locais em que a função sub_8006A84 é usada e não for muito preguiçoso e observar as entranhas, não há dúvida de que isso é calórico . É lógico - para receber dados no buffer, você deve primeiro criar esse buffer.

Agora sub_8006DBC . Vamos dar uma olhada (as variáveis ​​já foram renomeadas):



Recordando as funções da biblioteca C padrão para trabalhar com seqüências de caracteres, veremos strstr (procure uma substring) aqui e renomeá-laemos com ousadia.

Vamos analisar o código da função x_bluetooth_task - talvez algo tenha mudado aqui desde a última visita . No processo, nomeamos as variáveis:

  • v2 - estado ;
  • v3 - data_len .

Há uma função sub_80052E2 ao lado dela. Por analogia com funções que extraem números de um comando Bluetooth, ele extrai uma sequência de um comprimento especificado - vamos chamá-lo de x_get_str .

Continuamos:

  • v26 - isEcho ;
  • v6 - meow_str ;
  • v10 - uart_cmd_byte ;
  • v11 - uart_cmd_str ;
  • v12 - str_0 ;
  • v13 - str_1 ;
  • v14 - format_str ;
  • sub_8000F5C - x_blink_small_led .

Termine com uma renomeação rápida:

  • v19 - senha ; (como existem linhas sobre autorização e senha ao lado)
  • sub_8004CC0 - x_check_password ;
  • sub_8006AF4 - x_free (como senha, cmd e bt_args são ponteiros para objetos dinâmicos (marque isso!), a memória deve ser liberada após usá-los);
  • sub_8006DAC - x_strcpy (confira!).

Agora explore os ramos READ , WRIT , AUTP , SETP .

Como um teste em um dispositivo em execução mostrou, é necessária autorização para os comandos READ, WRIT, SETP. Uma tentativa de autorização com o comando AUTP nos leva à função x_check_password para verificar a senha:



Acontece que o comprimento da senha deve ter 8 caracteres e a senha é comparada (na função sub_8006B08) com bytes no endereço 0x08007C00 - onde a cadeia gerada de caracteres aleatórios AZ é armazenada.

Acontece que, sem saber a senha, não podemos fazer login no dispositivo. Bem, ou quase não pode ...

Preste atenção em onde a variável auth_flag é usada :



Acontece que ele é usado não apenas na tarefa Bluetooth. E aqui apenas não analisamos a tarefa Sensor ainda. Nós vamos lá.

Tarefa do sensor


Resumidamente: o que o botão de toque faz?

De acordo com as melhores práticas de programação, toda a tarefa do sensor se encaixa em uma tela da IDA. E isso não pode deixar de se alegrar:



Linha a linha ...

  • “TSC% d \ r \ n” - esta linha deve fazer você pensar no controlador de sensor de toque para microcontroladores STM32;
  • "AUTH BTN \ r \ n" - botão de autorização ???
  • "SET AUTH% d \ r \ n" - define o sinalizador de autorização?

Vamos ver como o dispositivo se comportará se você pressionar o botão de toque (você percebeu que o rinoceronte na perna tem um botão de toque?):



Quando pressionado brevemente, o pequeno LED vermelho acende. Com uma pressão longa, esse LED acende por um longo tempo.

Se correlacionarmos isso com o código, podemos assumir que a função sub_8000708 é uma função para obter a hora atual. Então, se a diferença entre a hora atual e o início do toque no sensor for superior a 1000 (1 segundo), o LED acenderá por 0xEA60 milissegundos (1 minuto). Mas a variável auth_flag é de grande interesse, que é definida como 1 com um toque longo no botão de toque, dando ao invasor o acesso ao administrador do “dispositivo de iluminação” e acesso a funções privilegiadas.

Assim, após a autorização “por botão”, você pode ler a senha armazenada no dispositivo (comando READ), gravar na RAM (função WRIT) ou definir uma nova senha (SETP).

Louco piscando


Resumidamente: pode ser executado um ramo estranho do código Mad Blinking?

Vamos voltar à tarefa Bluetooth e renomear um pouco mais.

  • v21 - vip_smth (ainda não está claro o que existe);
  • v22 - vip_str (sequência de tamanho desconhecido, extraída dos argumentos);
  • v23 - mad_led - atribua "Convert to struct *" e especifique struct_LED .

E aqui vemos o número 0xB816D8D9 (encontrado na primeira parte do artigo na tarefa Bluetooth) como o índice do LED. Este código será executado se a verificação for realizada:

 if ( sub_8005520(vip_str, vip_smth) == 0x46F70674 ) 

Renomeie sub_8005520 para x_vip_check e dê uma olhada nele:



Dado que o primeiro argumento é uma string (pelo menos a string é passada para essa função), esse código mostra que o segundo argumento é o comprimento dessa string (ou o comprimento que deve ser processado). Renomear:

  • a1 - str ;
  • a2 - len .

Vamos dar uma olhada na função sub_8000254 :



Agora veja sub_8000148 . Aqui está o seu começo:



Este é apenas um terço da função ... Mmmm ... Gostoso! Um escavador experiente verá facilmente aqui ...

O que?
operação de divisão inteira.

Como pode ser desenterrado?
Se você fizer um esforço, a partir da função sub_8000254, você poderá acessar x_printf (através de várias outras funções). Um ponto importante a ser destacado neste momento é que geralmente todas as funções padrão são razoavelmente padrão . Isso significa que você pode tentar encontrar no domínio público pelo menos algum código-fonte da função que está sendo investigada, para que o estudo seja mais produtivo.

Então, pegamos a fonte do printf, depois olhamos para o vfprintf , comparando-o com o código do firmware estudado. Usando o código fonte, saímos para a função itoa e concluímos que a função sub_8000254 é o operador operador% (considerando o restante da divisão), e essa terrível função longa nada mais é do que pegar a parte inteira da divisão (operação div).

Uma pergunta legítima pode surgir - por que? O fato é que não pode haver operações DIV, MOD em um microcontrolador específico; portanto, o compilador substitui a chamada de funções individuais em vez desses operadores. A propósito, aqui estão algumas outras funções matemáticas .

Não se esqueça de renomear durante a escavação.

Assim, a função x_vip_check calcula ... E essa será sua lição de casa .

A propósito, se você executar o comando VIP correto, obteremos um "rinoceronte na discoteca":



Breve relatório sobre firmware


O firmware do dispositivo é baseado no sistema operacional FreeRTOS em tempo real. O sistema possui as seguintes tarefas:

  1. Tarefa Bluetooth . Processa comandos que vêm em forma de texto via Bluetooth.
  2. Tarefa de LED . Controla os LEDs coloridos de acordo com os comandos do Bluetooth.
  3. Tarefa do sensor . Acende o LED vermelho, permite uma autorização de curto prazo sem uma senha no dispositivo.
  4. Tarefa UART . Permite que você interaja com o módulo Bluetooth através da porta UART interna (usada para inicializar o Bluetooth).
  5. Tarefa Watchdog . Mantém o controle de congelamentos.

O estudo não levou em consideração a capacidade de ler dados da porta UART (contatos Tx / GND).

Sumário


Durante a aula principal na conferência, apenas a principal funcionalidade de controle de LED foi desmontada. Os participantes mais ativos foram apresentados com seus “rinocerontes” experimentais.

Na minha opinião, o “rinoceronte” produziu um layout decente para um curso de treinamento em engenharia reversa e pesquisa de vulnerabilidades. Um recurso do layout pode ser a capacidade de alterar o firmware quantas vezes você quiser, cada curso possui seu próprio firmware. Ao contrário de analisar um arquivo executável, o firmware reverso permite que você entenda melhor:

  • como trabalhar com a IDA;
  • princípios de interação entre o firmware e o dispositivo;
  • Princípios operacionais do RTOS.

Muito obrigado a todos que leram até o fim!

Source: https://habr.com/ru/post/pt412561/


All Articles