BREAD (BIOS Reverse Engineering & Advanced Debugger) est un débogueur x86 en mode réel « injectable » qui peut déboguer du code arbitraire en mode réel (sur du matériel réel) à partir d'un autre PC via un câble série.
BREAD est né de nombreuses tentatives infructueuses de rétro-ingénierie du BIOS existant. Étant donné que la grande majorité, sinon la totalité, de l'analyse du BIOS est effectuée de manière statique à l'aide de désassembleurs, la compréhension du BIOS devient extrêmement difficile, car il n'existe aucun moyen de connaître la valeur des registres ou de la mémoire dans un morceau de code donné.
Malgré cela, BREAD peut également déboguer du code arbitraire en mode réel, tel que du code amorçable ou des programmes DOS.
Démo rapide :
Changer le nom de la chaîne du processeur via BREAD
Ce débogueur est divisé en deux parties : le débogueur (écrit entièrement en assembleur et fonctionnant sur le matériel en cours de débogage) et le pont, écrit en C et fonctionnant sous Linux.
Le débogueur est le code injectable, écrit en mode réel 16 bits, et peut être placé dans la ROM du BIOS ou dans tout autre code en mode réel. Une fois exécuté, il configure les gestionnaires d'interruptions appropriés, met le processeur en mode étape unique et attend les commandes sur le port série.
Le pont, quant à lui, constitue le lien entre le débogueur et GDB. Le pont communique avec GDB via TCP et transmet les demandes/réponses au débogueur via le port série. L'idée derrière le pont est de supprimer la complexité des paquets GDB et d'établir un protocole plus simple pour communiquer avec la machine. De plus, le protocole plus simple permet de réduire la taille du code final, ce qui facilite l'injection du débogueur dans différents environnements.
Comme le montre le schéma suivant :
+---------+ simple packets +----------+ GDB packets +---------+
| | --------------- > | | --------------- > | |
| dbg | | bridge | | gdb |
| ( real HW ) | <- -------------- | ( Linux ) | <- -------------- | ( Linux ) |
+---------+ serial +----------+ TCP +---------+
En implémentant le stub GDB, BREAD possède de nombreuses fonctionnalités prêtes à l'emploi. Les commandes suivantes sont prises en charge :
L'ingénierie inverse d'un binaire brut, tel qu'un BIOS, dans GDB implique automatiquement de ne pas avoir ses symboles d'origine. Cependant, à mesure que le processus RE progresse, l'utilisateur/programmeur/hacker acquiert une meilleure compréhension de certaines parties du code, et des outils d'analyse statique comme IDA, Cutter, Ghidra et d'autres permettent l'ajout d'annotations, de commentaires, de définitions de fonctions, et plus encore. Ces améliorations augmentent considérablement la productivité de l'utilisateur.
Dans cet esprit, il existe un script Python compagnon dans le projet appelé symbolify.py
. Étant donné une liste de symboles (étiquette d'adresse), il génère un fichier ELF minimal avec ces symboles ajoutés. Cet ELF peut ensuite être chargé ultérieurement dans GDB et utilisé pour simplifier considérablement le processus de débogage.
Le fichier de symboles peut inclure des espaces, des lignes vides, des commentaires (#) et des commentaires sur la ligne d'adresse. Les adresses peuvent être au format décimal ou hexadécimal, et les étiquettes/symboles (séparés par un ou plusieurs caractères d'espacement) peuvent être de la forme [a-z0-9_]+, comme dans (un exemple réel peut être trouvé dans symboles/ami_ipm41d3. SMS):
#
# This is a comment
#
0xdeadbeef my_symbol1
0x123 othersymbol # This function does xyz
# Example with decimal address
456 anotherone
Par exemple, en considérant le fichier de symboles disponible dans symboles/ami_ipm41d3.txt, l'utilisateur peut faire quelque chose comme :
$ ./simbolify.py symbols/ami_ipm41d3.txt ip41symbols.elf
Ensuite, chargez-le dans GDB comme dans :
(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_
Notez que même la saisie semi-automatique GDB fonctionne comme prévu, étonnant ?
Combien? Oui. Étant donné que le code en cours de débogage ignore qu'il est en cours de débogage, il peut interférer avec le débogueur de plusieurs manières, pour n'en nommer que quelques-unes :
Saut en mode protégé : si le code débogué passe en mode protégé, les structures des gestionnaires d'interruptions, etc. sont modifiées et le débogueur ne sera plus invoqué à ce stade du code. Cependant, il est possible qu'un retour en mode réel (restauration de l'état précédent complet) permette au débogueur de fonctionner à nouveau.
Modifications de l'IDT : si pour une raison quelconque le code débogué modifie l'IDT ou son adresse de base, les gestionnaires du débogueur ne seront pas correctement invoqués.
Pile : BREAD utilise une pile et suppose qu'elle existe ! Il ne doit pas être inséré dans des emplacements où la pile n'a pas encore été configurée.
Pour le débogage du BIOS, il existe d'autres limitations telles que : il n'est pas possible de déboguer le code du BIOS dès le début (bootblock), car une configuration minimale (telle que la RAM) est requise pour que BREAD fonctionne correctement. Cependant, il est possible d'effectuer un « redémarrage à chaud » en définissant CS:EIP sur F000:FFF0
. Dans ce scénario, l'initialisation du BIOS peut être à nouveau suivie, car BREAD est déjà correctement chargé. Veuillez noter que le « chemin de code » d'initialisation du BIOS lors d'un redémarrage à chaud peut être différent d'un redémarrage à froid et que le flux d'exécution peut ne pas être exactement le même.
La construction nécessite uniquement GNU Make, un compilateur C (tel que GCC, Clang ou TCC), NASM et une machine Linux.
Le débogueur a deux modes de fonctionnement : interrogation (par défaut) et basé sur les interruptions :
Le mode d'interrogation est l'approche la plus simple et devrait bien fonctionner dans divers environnements. Cependant, en raison de la nature de l'interrogation, l'utilisation du processeur est élevée :
$ git clone https://github.com/Theldus/BREAD.git
$ cd BREAD/
$ make
Le mode basé sur les interruptions optimise l'utilisation du processeur en utilisant les interruptions UART pour recevoir de nouvelles données, au lieu de les interroger constamment. Cela a pour conséquence que le processeur reste dans un état « arrêt » jusqu'à ce qu'il reçoive des commandes du débogueur, ce qui l'empêche ainsi de consommer 100 % des ressources du processeur. Cependant, comme les interruptions ne sont pas toujours activées, ce mode n'est pas défini comme option par défaut :
$ git clone https://github.com/Theldus/BREAD.git
$ cd BREAD/
$ make UART_POLLING=no
L'utilisation de BREAD nécessite uniquement un câble série (et oui, votre carte mère a un en-tête COM, consultez le manuel) et l'injection du code à l'emplacement approprié.
Pour injecter, des modifications minimes doivent être apportées dans dbg.asm (le src du débogueur). L'ORG du code doit être modifié ainsi que la manière dont le code doit être renvoyé (recherchez " >> CHANGE_HERE <<
" dans le code pour les endroits qui doivent être modifiés).
En utilisant un héritage AMI comme exemple, où le module de débogueur sera placé à la place du logo du BIOS ( 0x108200
ou FFFF:8210
) et les instructions suivantes dans la ROM ont été remplacées par un appel distant au module :
...
00017EF2 06 push es
00017EF3 1E push ds
00017EF4 07 pop es
00017EF5 8BD8 mov bx , ax - ┐ replaced by: call 0xFFFF : 0x8210 (dbg.bin)
00017EF7 B8024F mov ax , 0x4f02 - ┘
00017EFA CD10 int 0x10
00017EFC 07 pop es
00017EFD C3 ret
...
le patch suivant suffit :
diff --git a/dbg.asm b/dbg.asm
index caedb70..88024d3 100644
--- a/dbg.asm
+++ b/dbg.asm
@@ -21,7 +21,7 @@
; SOFTWARE.
[BITS 16]
- [ORG 0x0000] ; >> CHANGE_HERE <<
+ [ORG 0x8210] ; >> CHANGE_HERE <<
%include "constants.inc"
@@ -140,8 +140,8 @@ _start:
; >> CHANGE_HERE <<
; Overwritten BIOS instructions below (if any)
- nop
- nop
+ mov ax, 0x4F02
+ int 0x10
nop
nop
Il est important de noter que si vous avez modifié quelques instructions dans votre ROM pour appeler le code du débogueur, elles doivent être restaurées avant de revenir du débogueur.
La raison du remplacement de ces deux instructions est qu'elles sont exécutées juste avant que le BIOS n'affiche le logo à l'écran, qui est désormais le débogueur, garantissant quelques points clés :
Trouver un bon emplacement pour appeler le débogueur (là où le BIOS est déjà suffisamment initialisé, mais pas trop tard) peut être difficile, mais c'est possible.
Après cela, dbg.bin
est prêt à être inséré à la bonne position dans la ROM.
Le débogage des programmes DOS avec BREAD est un peu délicat, mais possible :
dbg.asm
pour que DOS le comprenne comme un programme DOS valide :times
)int 0x20
)Le correctif suivant résout ce problème :
diff --git a/dbg.asm b/dbg.asm
index caedb70..b042d35 100644
--- a/dbg.asm
+++ b/dbg.asm
@@ -21,7 +21,10 @@
; SOFTWARE.
[BITS 16]
- [ORG 0x0000] ; >> CHANGE_HERE <<
+ [ORG 0x100]
+
+ times 40*1024 db 0x90 ; keep some distance,
+ ; 40kB should be enough
%include "constants.inc"
@@ -140,7 +143,7 @@ _start:
; >> CHANGE_HERE <<
; Overwritten BIOS instructions below (if any)
- nop
+ int 0x20 ; DOS interrupt to exit process
nop
Créez une image de disquette amorçable FreeDOS (ou DOS) contenant uniquement le noyau et le terminal : KERNEL.SYS
et COMMAND.COM
. Ajoutez également à cette image disquette le programme à déboguer et le DBG.COM
( dbg.bin
).
Les étapes suivantes doivent être suivies après la création de l'image :
bridge
déjà ouvert (reportez-vous à la section suivante pour les instructions).DBG.COM
.DBG.COM
se poursuivre jusqu'à ce qu'il se termine.Il est important de noter que le DOS n'efface pas l'image du processus après sa sortie. En conséquence, le débogueur peut être configuré comme n'importe quel autre programme DOS et les points d'arrêt appropriés peuvent être définis. Le début du débogueur est rempli de NOP, il est donc prévu que le nouveau processus n'écrasera pas la mémoire du débogueur, lui permettant de continuer à fonctionner même après qu'il semble « terminé ». Cela permet à BREaD de déboguer d'autres programmes, y compris le DOS lui-même.
Bridge est le ciment entre le débogueur et GDB et peut être utilisé de différentes manières, que ce soit sur du matériel réel ou sur une machine virtuelle.
Ses paramètres sont :
Usage: ./bridge [options]
Options:
-s Enable serial through socket, instead of device
-d <path> Replaces the default device path (/dev/ttyUSB0)
(does not work if -s is enabled)
-p <port> Serial port (as socket), default: 2345
-g <port> GDB port, default: 1234
-h This help
If no options are passed the default behavior is:
./bridge -d /dev/ttyUSB0 -g 1234
Minimal recommended usages:
./bridge -s (socket mode, serial on 2345 and GDB on 1234)
./bridge (device mode, serial on /dev/ttyUSB0 and GDB on 1234)
Pour l'utiliser sur du matériel réel, invoquez-le simplement sans paramètres. Vous pouvez éventuellement modifier le chemin du périphérique avec le paramètre -d
:
./bridge
ou ./bridge -d /path/to/device
)Single-stepped, you can now connect GDB!
puis lancez GDB : gdb
. Pour une utilisation dans une machine virtuelle, l'ordre d'exécution change légèrement :
./bridge
ou ./bridge -d /path/to/device
)make bochs
ou make qemu
)Single-stepped, you can now connect GDB!
puis lancez GDB : gdb
.Dans les deux cas, assurez-vous d'exécuter GDB dans le dossier racine de BRIDGE, car ce dossier contient des fichiers auxiliaires pour que GDB fonctionne correctement en 16 bits.
BREAD est toujours ouvert à la communauté et disposé à accepter des contributions, qu'il s'agisse de problèmes, de documentation, de tests, de nouvelles fonctionnalités, de corrections de bugs, de fautes de frappe, etc. Bienvenue à bord.
BREAD est sous licence MIT. Écrit par Davidson Francis et (espérons-le) d'autres contributeurs.
Les points d'arrêt sont implémentés en tant que points d'arrêt matériels et ont donc un nombre limité de points d'arrêt disponibles. Dans l'implémentation actuelle, un seul point d'arrêt actif à la fois ! ↩
Les points de surveillance matériels (comme les points d'arrêt) ne sont également pris en charge qu'un par un. ↩
Veuillez noter que les registres de débogage ne fonctionnent pas par défaut sur les VM. Pour bochs, il doit être compilé avec l'indicateur --enable-x86-debugger=yes
. Pour Qemu, il doit fonctionner avec KVM activé : --enable-kvm
( make qemu
le fait déjà). ↩