
Depurador x86 em modo real injetável para engenharia reversa de BIOS e depuração de código arbitrário em modo real via cabo serial, com integração GDB e suporte a breakpoints/watchpoints de hardware.
BREAD (BIOS Reverse Engineering & Advanced Debugger) é um depurador x86 em modo real 'injetável' que pode depurar código arbitrário em modo real (em hardware real) de outro PC via cabo serial.
O BREAD surgiu de muitas tentativas frustradas de fazer engenharia reversa de BIOS legadas. Dado que a grande maioria — senão todas — das análises de BIOS é feita estaticamente usando desmontadores, entender a BIOS se torna extremamente difícil, já que não há como saber o valor dos registradores ou da memória em um determinado trecho de código.
Apesar disso, o BREAD também pode depurar código arbitrário em modo real, como código inicializável ou programas DOS também.
Demonstração rápida:
https://user-images.githubusercontent.com/8294550/217709970-9007a1e3-7352-470d-a22f-cbb5219d5547.mp4
Alterando o nome da string da CPU via BREAD
Este depurador é dividido em duas partes: o depurador (escrito inteiramente em assembly e executando no hardware que está sendo depurado) e a ponte (bridge), escrita em C e executando no Linux.
O depurador é o código injetável, escrito em modo real de 16 bits, e pode ser colocado dentro da ROM da BIOS ou em qualquer outro código em modo real. Quando executado, ele configura os manipuladores de interrupção apropriados, coloca o processador em modo de passo único e aguarda comandos na porta serial.
A ponte, por outro lado, é o elo entre o depurador e o GDB. A ponte se comunica com o GDB via TCP e encaminha as requisições/respostas para o depurador através da porta serial. A ideia por trás da ponte é remover a complexidade dos pacotes GDB e estabelecer um protocolo mais simples para se comunicar com a máquina. Além disso, o protocolo mais simples permite que o tamanho final do código seja menor, facilitando a injeção do depurador em diversos ambientes diferentes.
Conforme mostrado no diagrama a seguir:
+---------+ pacotes simples +----------+ Pacotes GDB +---------+
| |---------------->| |---------------->| |
| dbg | | bridge | | gdb |
|(HW real)|<----------------| (Linux) |<----------------| (Linux) |
+---------+ serial +----------+ TCP +---------+
Ao implementar o stub GDB, o BREAD possui muitos recursos prontos para uso. Os seguintes comandos são suportados:
Fazer engenharia reversa de um binário bruto, como uma BIOS, no GDB automaticamente implica em não ter seus símbolos originais. No entanto, à medida que o processo de RE avança, o usuário/programador/hacker adquire uma melhor compreensão de certas partes do código, e ferramentas de análise estática como IDA, Cutter, Ghidra e outras permitem a adição de anotações, comentários, definições de funções e muito mais. Esses aprimoramentos aumentam significativamente a produtividade do usuário.
Pensando nisso, existe um script Python complementar no projeto chamado symbolify.py. Dada uma lista de símbolos (endereço rótulo), ele gera um arquivo ELF mínimo com esses símbolos adicionados. Este ELF pode então ser carregado posteriormente no GDB e usado para simplificar bastante o processo de depuração.
O arquivo de símbolos pode incluir espaços em branco, linhas em branco, comentários (#) e comentários na linha de endereço. Endereços podem estar em formato decimal ou hexadecimal, e rótulos/símbolos (separados por um ou mais caracteres de espaço em branco) podem ter a forma [a-z0-9_]+, como em (um exemplo real pode ser encontrado em symbols/ami_ipm41d3.txt):
#
# Isto é um comentário
#
0xdeadbeef meu_simbolo1
0x123 outrosimbolo # Esta função faz xyz
# Exemplo com endereço decimal
456 maisum
Por exemplo, considerando o arquivo de símbolos disponível em symbols/ami_ipm41d3.txt, o usuário pode fazer algo como:
$ ./simbolify.py symbols/ami_ipm41d3.txt ip41symbols.elf
Em seguida, carregue-o no GDB conforme:
(gdb) add-symbol-file ip41symbols.elf 0
add symbol table from file "ip41symbols.elf" at
.text_addr = 0x0
(y or n) y
Reading symbols from ip41symbols.elf...
(No debugging symbols found in ip41symbols.elf)
(gdb) p cseg_
cseg_change_video_mode_logo cseg_get_cpuname
(gdb) p cseg_
Note que até o auto-completar do GDB funciona como esperado, incrível?
Quantos? Sim. Como o código que está sendo depurado não sabe que está sendo depurado, ele pode interferir no depurador de várias maneiras, para citar algumas:
Salto para modo protegido: Se o código depurado mudar para modo protegido, as estruturas para manipuladores de interrupção, etc., são alteradas e o depurador não será mais invocado naquele ponto do código. No entanto, é possível que um salto de volta ao modo real (restaurando o estado anterior completo) permita que o depurador funcione novamente.
Mudanças na IDT: Se por qualquer motivo o código depurado alterar a IDT ou seu endereço base, os manipuladores do depurador não serão invocados adequadamente.
Pilha: O BREAD usa uma pilha e assume que ela existe! Ele não deve ser inserido em locais onde a pilha ainda não foi configurada.
Para depuração de BIOS, existem outras limitações, como: não é possível depurar o código da BIOS desde o início (bootblock), pois uma configuração mínima (como RAM) é necessária para que o BREAD funcione corretamente. No entanto, é possível realizar um "warm-reboot" definindo CS:EIP para F000:FFF0. Neste cenário, a inicialização da BIOS pode ser acompanhada novamente, pois o BREAD já está devidamente carregado. Observe que o "caminho-código" da inicialização da BIOS durante um warm-reboot pode ser diferente de um cold-reboot e o fluxo de execução pode não ser exatamente o mesmo.
A compilação requer apenas GNU Make, um compilador C (como GCC, Clang ou TCC), NASM e uma máquina Linux.
O depurador tem dois modos de operação: polling (padrão) e baseado em interrupção:
O modo polling é a abordagem mais simples e deve funcionar bem em diversos ambientes. No entanto, devido à natureza de polling, há um alto uso da CPU:
Pontos de parada são implementados como pontos de parada de hardware e, portanto, têm um número limitado de pontos de parada disponíveis. Na implementação atual, apenas 1 ponto de parada ativo por vez! ↩
Pontos de observação de hardware (assim como pontos de parada) também são suportados apenas um de cada vez. ↩