
Debugger x86 in modalità reale iniettabile per reverse engineering di BIOS e debugging di codice arbitrario in modalità reale tramite cavo seriale, con integrazione GDB e supporto per breakpoint/watchpoint hardware.
BREAD (BIOS Reverse Engineering & Advanced Debugger) è un debugger x86 in modalità reale 'iniettabile' in grado di eseguire il debug di codice arbitrario in modalità reale (su hardware reale) da un altro PC tramite cavo seriale.
BREAD è nato da molti tentativi falliti di fare reverse engineering di BIOS legacy. Dato che la stragrande maggioranza – se non tutta – l'analisi dei BIOS viene eseguita staticamente usando disassemblatori, comprendere il BIOS diventa estremamente difficile, poiché non c'è modo di conoscere il valore dei registri o della memoria in un dato pezzo di codice.
Nonostante ciò, BREAD può anche eseguire il debug di codice arbitrario in modalità reale, come codice avviabile o programmi DOS.
Demo rapida:
https://user-images.githubusercontent.com/8294550/217709970-9007a1e3-7352-470d-a22f-cbb5219d5547.mp4
Cambiare il nome della stringa della CPU tramite BREAD
Questo debugger è diviso in due parti: il debugger (scritto interamente in assembly e in esecuzione sull'hardware sottoposto a debug) e il bridge, scritto in C e in esecuzione su Linux.
Il debugger è il codice iniettabile, scritto in modalità reale a 16 bit, e può essere posizionato all'interno della ROM del BIOS o in qualsiasi altro codice in modalità reale. Quando viene eseguito, imposta i gestori di interrupt appropriati, mette il processore in modalità a passo singolo e attende comandi sulla porta seriale.
Il bridge, d'altra parte, è il collegamento tra il debugger e GDB. Il bridge comunica con GDB tramite TCP e inoltra le richieste/risposte al debugger attraverso la porta seriale. L'idea alla base del bridge è quella di rimuovere la complessità dei pacchetti GDB e stabilire un protocollo più semplice per comunicare con la macchina. Inoltre, il protocollo più semplice consente di ridurre le dimensioni finali del codice, rendendo più facile l'iniettabilità del debugger in vari ambienti diversi.
Come mostrato nel seguente diagramma:
+---------+ pacchetti semplici +----------+ Pacchetti GDB +---------+
| |------------------->| |------------------>| |
| dbg | | bridge | | gdb |
|(HW reale)|<-------------------| (Linux) |<------------------| (Linux) |
+---------+ seriale +----------+ TCP +---------+
Implementando lo stub GDB, BREAD ha molte funzionalità integrate. Sono supportati i seguenti comandi:
Fare reverse engineering di un binario grezzo, come un BIOS, in GDB implica automaticamente non avere i suoi simboli originali. Tuttavia, con l'avanzare del processo di RE, l'utente/programmatore/hacker acquisisce una migliore comprensione di alcune parti del codice, e strumenti di analisi statica come IDA, Cutter, Ghidra e altri consentono di aggiungere annotazioni, commenti, definizioni di funzioni e altro. Questi miglioramenti aumentano notevolmente la produttività dell'utente.
Con questo in mente, nel progetto è presente uno script Python complementare chiamato symbolify.py. Data una lista di simboli (etichette di indirizzo), genera un file ELF minimale con questi simboli aggiunti. Questo ELF può poi essere caricato in GDB successivamente e utilizzato per semplificare notevolmente il processo di debug.
Il file dei simboli può includere spazi bianchi, righe vuote, commenti (#) e commenti sulla riga dell'indirizzo. Gli indirizzi possono essere in formato decimale o esadecimale, e le etichette/simboli (separati da uno o più caratteri di spazio bianco) possono essere della forma [a-z0-9_]+, come in (un esempio reale si trova in symbols/ami_ipm41d3.txt):
#
# Questo è un commento
#
0xdeadbeef mio_simbolo1
0x123 altro_simbolo # Questa funzione fa xyz
# Esempio con indirizzo decimale
456 ancora_uno
Ad esempio, considerando il file dei simboli disponibile in symbols/ami_ipm41d3.txt, l'utente può fare qualcosa come:
$ ./symbolify.py symbols/ami_ipm41d3.txt ip41symbols.elf
Quindi, caricarlo in GDB come in:
(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_
Notate che anche il completamento automatico di GDB funziona come previsto, sorprendente?
Quante? Sì. Poiché il codice sottoposto a debug non è consapevole di essere sottoposto a debug, può interferire con il debugger in diversi modi, per citarne alcuni:
Salto in modalità protetta: Se il codice sottoposto a debug passa alla modalità protetta, le strutture per i gestori di interrupt, ecc. vengono alterate e il debugger non verrà più invocato a quel punto del codice. Tuttavia, è possibile che un salto di ritorno alla modalità reale (ripristinando lo stato precedente completo) permetta al debugger di funzionare di nuovo.
Modifiche IDT: Se per qualsiasi motivo il codice sottoposto a debug modifica la IDT o il suo indirizzo base, i gestori del debugger non verranno invocati correttamente.
Stack: BREAD utilizza uno stack e presuppone che esista! Non dovrebbe essere inserito in posizioni in cui lo stack non è ancora stato configurato.
Per il debug del BIOS, ci sono altre limitazioni come: non è possibile eseguire il debug del codice del BIOS dall'inizio (bootblock), poiché è necessaria una configurazione minima (come la RAM) affinché BREAD funzioni correttamente. Tuttavia, è possibile eseguire un "warm-reboot" impostando CS:EIP su F000:FFF0. In questo scenario, l'inizializzazione del BIOS può essere seguita di nuovo, poiché BREAD è già stato caricato correttamente. Si noti che il "percorso del codice" dell'inizializzazione del BIOS durante un warm-reboot potrebbe essere diverso da un cold-reboot e il flusso di esecuzione potrebbe non essere esattamente lo stesso.
Per compilare sono necessari solo GNU Make, un compilatore C (come GCC, Clang o TCC), NASM e una macchina Linux.
I punti di interruzione sono implementati come breakpoint hardware e pertanto hanno un numero limitato di punti di interruzione disponibili. Nell'implementazione attuale, solo 1 punto di interruzione attivo alla volta! ↩
Anche i watchpoint hardware (come i punti di interruzione) sono supportati solo uno alla volta. ↩