
Injectable Real-Mode x86-Debugger für BIOS-Reverse-Engineering und Debugging von beliebigem Real-Mode-Code über serielles Kabel, mit GDB-Integration und Hardware-Breakpoint-/Watchpoint-Unterstützung.
BREAD (BIOS Reverse Engineering & Advanced Debugger) ist ein 'injizierbarer' x86-Real-Mode-Debugger, der beliebigen Real-Mode-Code (auf echter Hardware) von einem anderen PC über ein serielles Kabel debuggen kann.
BREAD entstand aus vielen fehlgeschlagenen Versuchen, Legacy-BIOS zu reverse-engineeren. Da die überwiegende Mehrheit – wenn nicht alle – BIOS-Analysen statisch mit Disassemblern durchgeführt werden, wird das Verständnis des BIOS extrem schwierig, da es keine Möglichkeit gibt, den Wert von Registern oder Speicher in einem bestimmten Codeabschnitt zu kennen.
Trotzdem kann BREAD auch beliebigen Code im Real-Modus debuggen, wie bootfähigen Code oder DOS-Programme.
Kurze Demo:
https://user-images.githubusercontent.com/8294550/217709970-9007a1e3-7352-470d-a22f-cbb5219d5547.mp4
CPU-String-Namen mit BREAD ändern
Dieser Debugger ist in zwei Teile aufgeteilt: den Debugger (vollständig in Assembler geschrieben und auf der zu debuggenden Hardware laufend) und die Brücke, in C geschrieben und unter Linux laufend.
Der Debugger ist der injizierbare Code, der in 16-Bit-Real-Mode geschrieben ist und im BIOS-ROM oder in jedem anderen Real-Mode-Code platziert werden kann. Bei Ausführung richtet er die entsprechenden Interrupt-Handler ein, versetzt den Prozessor in den Single-Step-Modus und wartet auf Befehle auf der seriellen Schnittstelle.
Die Brücke hingegen ist die Verbindung zwischen dem Debugger und GDB. Die Brücke kommuniziert mit GDB über TCP und leitet die Anfragen/Antworten über die serielle Schnittstelle an den Debugger weiter. Die Idee hinter der Brücke ist es, die Komplexität der GDB-Pakete zu vermeiden und ein einfacheres Protokoll für die Kommunikation mit der Maschine zu etablieren. Darüber hinaus ermöglicht das einfachere Protokoll eine kleinere endgültige Codegröße, was es dem Debugger erleichtert, in verschiedene Umgebungen injiziert zu werden.
Wie im folgenden Diagramm gezeigt:
+---------+ einfache Pakete +----------+ GDB-Pakete +---------+
| |--------------->| |--------------->| |
| dbg | | bridge | | gdb |
|(echte HW)|<---------------| (Linux) |<---------------| (Linux) |
+---------+ seriell +----------+ TCP +---------+
Durch die Implementierung des GDB-Stubs bietet BREAD viele Funktionen out-of-the-box. Die folgenden Befehle werden unterstützt:
Das Reverse-Engineering eines rohen Binärs, wie eines BIOS, in GDB bedeutet automatisch, dass man keine ursprünglichen Symbole hat. Jedoch gewinnt der Benutzer/Programmierer/Hacker im Laufe des RE-Prozesses ein besseres Verständnis bestimmter Codeteile, und statische Analysetools wie IDA, Cutter, Ghidra und andere ermöglichen das Hinzufügen von Annotationen, Kommentaren, Funktionsdefinitionen und mehr. Diese Verbesserungen steigern die Produktivität des Benutzers erheblich.
Vor diesem Hintergrund gibt es im Projekt ein begleitendes Python-Skript namens symbolify.py. Ausgehend von einer Liste von Symbolen (Adresse Label) generiert es eine minimale ELF-Datei mit diesen Symbolen. Diese ELF kann dann später in GDB geladen werden, um den Debugging-Prozess erheblich zu vereinfachen.
Die Symboldatei kann Leerzeichen, Leerzeilen, Kommentare (#) und Kommentare in der Adresszeile enthalten. Adressen können im Dezimal- oder Hexadezimalformat sein, und Labels/Symbole (durch ein oder mehrere Leerzeichen getrennt) können die Form [a-z0-9_]+ haben, wie in einem echten Beispiel in symbols/ami_ipm41d3.txt:
#
# Dies ist ein Kommentar
#
0xdeadbeef my_symbol1
0x123 othersymbol # Diese Funktion macht xyz
# Beispiel mit Dezimaladresse
456 anotherone
Zum Beispiel, wenn die Symboldatei unter symbols/ami_ipm41d3.txt verfügbar ist, kann der Benutzer Folgendes tun:
$ ./simbolify.py symbols/ami_ipm41d3.txt ip41symbols.elf
Dann in GDB laden wie folgt:
(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_
Beachten Sie, dass sogar die GDB-Autovervollständigung wie erwartet funktioniert, erstaunlich?
Wie viele? Ja. Da der zu debuggende Code nicht weiß, dass er g debuggt wird, kann er den Debugger auf verschiedene Weise stören, um nur einige zu nennen:
Protected-Mode-Sprung: Wenn der debugged Code in den Protected-Mode wechselt, werden die Strukturen für Interrupt-Handler usw. geändert, und der Debugger wird an dieser Stelle im Code nicht mehr aufgerufen. Es ist jedoch möglich, dass ein Rücksprung in den Real-Mode (unter Wiederherstellung des vollständigen vorherigen Zustands) den Debugger wieder funktionsfähig macht.
IDT-Änderungen: Wenn aus irgendeinem Grund der debugged Code die IDT oder ihre Basisadresse ändert, werden die Debugger-Handler nicht ordnungsgemäß aufgerufen.
Stack: BREAD verwendet einen Stack und geht davon aus, dass er existiert! Er sollte nicht an Stellen eingefügt werden, an denen der Stack noch nicht konfiguriert ist.
Für BIOS-Debugging gibt es weitere Einschränkungen, wie: Es ist nicht möglich, den BIOS-Code von Anfang an (Bootblock) zu debuggen, da für die korrekte Funktion von BREAD eine minimale Einrichtung (wie RAM) erforderlich ist. Es ist jedoch möglich, einen "Warm-Neustart" durchzuführen, indem CS:EIP auf F000:FFF0 gesetzt wird. In diesem Szenario kann die BIOS-Initialisierung erneut verfolgt werden, da BREAD bereits ordnungsgemäß geladen ist. Bitte beachten Sie, dass der "Codepfad" der BIOS-Initialisierung während eines Warm-Neustarts anders sein kann als bei einem Kalt-Neustart und der Ausführungsablauf möglicherweise nicht genau gleich ist.
Zum Erstellen wird nur GNU Make, ein C-Compiler (wie GCC, Clang oder TCC), NASM und eine Linux-Maschine benötigt.
Der Debugger hat zwei Betriebsmodi: Polling (Standard) und Interrupt-basiert: