
Débogueur injectable en mode réel x86 pour l'ingénierie inverse du BIOS et le débogage de code arbitraire en mode réel via un câble série, avec intégration GDB et support des points d'arrêt et de surveillance matériels.
BREAD (BIOS Reverse Engineering & Advanced Debugger) est un débogueur x86 en mode réel « injectable » qui permet de déboguer du code en mode réel arbitraire (sur du vrai matériel) depuis un autre PC via un câble série.
BREAD est né de nombreuses tentatives infructueuses de rétro-ingénierie de BIOS hérités. Étant donné que la grande majorité – pour ne pas dire la totalité – des analyses de BIOS sont effectuées statiquement à l’aide de désassembleurs, comprendre le BIOS devient extrêmement difficile, car il n’existe aucun moyen de connaître la valeur des registres ou de la mémoire à un endroit donné du code.
Malgré cela, BREAD peut également déboguer du code arbitraire en mode réel, comme du code amorçable ou des programmes DOS.
Démo rapide :
https://user-images.githubusercontent.com/8294550/217709970-9007a1e3-7352-470d-a22f-cbb5219d5547.mp4
Changement du nom de la chaîne CPU via BREAD
Ce débogueur est divisé en deux parties : le débogueur (écrit entièrement en assembleur et s’exécutant sur le matériel débogué) et le pont (bridge), écrit en C et s’exécutant sur 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. Lorsqu’il est exécuté, il met en place les gestionnaires d’interruptions appropriés, place le processeur en mode pas à pas (single-step) et attend les commandes sur le port série.
Le pont, quant à lui, est le lien entre le débogueur et GDB. Le pont communique avec GDB via TCP et transmet les requêtes/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 finale du code, ce qui facilite l’injection du débogueur dans divers environnements.
Comme illustré dans le diagramme suivant :
+---------+ paquets simples +----------+ paquets GDB +---------+
| |--------------->| |--------------->| |
| dbg | | bridge | | gdb |
|(matériel réel)|<---------------| (Linux) |<---------------| (Linux) |
+---------+ série +----------+ TCP +---------+
En implémentant le stub GDB, BREAD dispose de nombreuses fonctionnalités prêtes à l’emploi. Les commandes suivantes sont prises en charge :
Le reverse engineering d’un binaire brut, comme un BIOS, dans GDB implique automatiquement de ne pas avoir ses symboles originaux. Cependant, au fur et à mesure que le processus de RE progresse, l’utilisateur/programmeur/pirate acquiert une meilleure compréhension de certaines parties du code, et des outils d’analyse statique comme IDA, Cutter, Ghidra, etc., permettent d’ajouter des annotations, des commentaires, des définitions de fonctions, etc. Ces améliorations augmentent considérablement la productivité de l’utilisateur.
Dans cette optique, il existe un script Python compagnon dans le projet appelé symbolify.py. À partir d’une liste de symboles (adresse étiquette), il génère un fichier ELF minimal avec ces symboles ajoutés. Ce ELF peut ensuite être chargé dans GDB et utilisé pour simplifier grandement 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 en format décimal ou hexadécimal, et les étiquettes/symboles (séparés par un ou plusieurs espaces) peuvent être de la forme [a-z0-9_]+, comme dans (un exemple réel se trouve dans symbols/ami_ipm41d3.txt) :
#
# Ceci est un commentaire
#
0xdeadbeef mon_symbole1
0x123 autresymbole # Cette fonction fait xyz
# Exemple avec adresse décimale
456 encoreun
Par exemple, en considérant le fichier de symboles disponible dans symbols/ami_ipm41d3.txt, l’utilisateur peut faire :
$ ./simbolify.py symbols/ami_ipm41d3.txt ip41symbols.elf
Ensuite, le charger dans GDB comme suit :
(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 l’auto-complétion de GDB fonctionne comme prévu, incroyable ?
Combien ? Oui. Étant donné que le code en cours de débogage n’est pas conscient qu’il est débogué, il peut interférer avec le débogueur de plusieurs manières, pour n’en citer que quelques-unes :
Saut en mode protégé : Si le code débogué bascule en mode protégé, les structures des gestionnaires d’interruptions, etc. sont modifiées et le débogueur ne sera plus invoqué à ce point du code. Cependant, il est possible qu’un retour en mode réel (restaurant 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 endroits où la pile n’a pas encore été configurée.
Pour le débogage de BIOS, il existe d’autres limitations comme : il n’est pas possible de déboguer le code du BIOS depuis le tout début (bootblock), car une configuration minimale (telle que la RAM) est nécessaire pour que BREAD fonctionne correctement. Cependant, il est possible d’effectuer un « redémarrage à chaud » en réglant CS:EIP sur F000:FFF0. Dans ce scénario, l’initialisation du BIOS peut être suivie à nouveau, car BREAD est déjà correctement chargé. Veuillez noter que le « chemin du code » de l’initialisation du BIOS lors d’un redémarrage à chaud peut être différent de celui d’un redémarrage à froid et que le flux d’exécution peut ne pas être exactement le même.
La compilation nécessite uniquement GNU Make, un compilateur C (comme GCC, Clang ou TCC), NASM et une machine Linux.
Le débogueur a deux modes de fonctionnement : par 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 d’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. Ainsi, le processeur reste dans un état « halt » jusqu’à réception de commandes du débogueur, ce qui évite une consommation à 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
Utiliser BREAD nécessite uniquement un câble série (et oui, votre carte mère a un en-tête COM, vérifiez le manuel) et d’injecter le code à l’emplacement approprié.
Pour injecter, des modifications minimales doivent être apportées dans dbg.asm (la source du débogueur). L’« ORG » du code doit être modifié ainsi que la manière dont le code doit revenir (cherchez « >> CHANGE_HERE << » dans le code pour les endroits à modifier).
En prenant un AMI legacy comme exemple, où le module débogueur sera placé à l’emplacement du logo du BIOS (0x108200 ou FFFF:8210) et où les instructions suivantes dans la ROM ont été remplacées par un appel lointain au module :
...
00017EF2 06 push es
00017EF3 1E push ds
00017EF4 07 pop es
00017EF5 8BD8 mov bx,ax -┐ remplacé par : call 0xFFFF:0x8210 (dbg.bin)
00017EF7 B8024F mov ax,0x4f02 -┘
00017EFA CD10 int 0x10
00017EFC 07 pop es
00017EFD C3 ret
...
le correctif suivant est suffisant :
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 <<
; Instructions BIOS écrasées ci-dessous (le cas échéant)
- 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 invoquer 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 maintenant le débogueur, ce qui garantit quelques points clés :
Trouver un bon emplacement pour appeler le débogueur (là où le BIOS a 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.
Déboguer 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épond à cela :
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 ; garder une certaine distance,
+ ; 40 kB devraient suffire
%include "constants.inc"
@@ -140,7 +143,7 @@ _start:
; >> CHANGE_HERE <<
; Instructions BIOS écrasées ci-dessous (le cas échéant)
- nop
+ int 0x20 ; Interruption DOS pour quitter le processus
nop
Créez une image disquette FreeDOS (ou DOS) amorçable 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 avoir créé l’image :
bridge déjà ouvert (reportez-vous à la section suivante pour les instructions).DBG.COM.DBG.COM continuer jusqu’à sa fin.Il est important de noter que DOS n’efface pas l’image du processus après sa sortie. Par conséquent, 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, donc on anticipe que le nouveau processus n’écrasera pas la mémoire du débogueur, lui permettant de continuer à fonctionner même après avoir semblé « terminé ». Cela permet à BREAD de déboguer d’autres programmes, y compris DOS lui-même.
Le pont est le lien 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 une machine virtuelle.
Ses paramètres sont :
Usage: ./bridge [options]
Options:
-s Active le série via socket, au lieu du périphérique
-d <chemin> Remplace le chemin du périphérique par défaut (/dev/ttyUSB0)
(ne fonctionne pas si -s est activé)
-p <port> Port série (en tant que socket), défaut : 2345
-g <port> Port GDB, défaut : 1234
-h Cette aide
Si aucune option n'est passée, le comportement par défaut est :
./bridge -d /dev/ttyUSB0 -g 1234
Utilisations minimales recommandées :
./bridge -s (mode socket, série sur 2345 et GDB sur 1234)
./bridge (mode périphérique, série sur /dev/ttyUSB0 et GDB sur 1234)
Pour l’utiliser sur du matériel réel, il suffit de l’invoquer sans paramètres. Vous pouvez éventuellement changer le chemin du périphérique avec le paramètre -d :
./bridge ou ./bridge -d /chemin/vers/periph)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 /chemin/vers/periph)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 des fichiers auxiliaires s’y trouvent pour que GDB fonctionne correctement en 16 bits.
BREAD est toujours ouvert à la communauté et prêt à accepter les contributions, que ce soit sous forme de signalements de problèmes, de documentation, de tests, de nouvelles fonctionnalités, de corrections de bogues, de corrections orthographiques, 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 à la fois. ↩
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’option --enable-x86-debugger=yes. Pour Qemu, il doit être exécuté avec KVM activé : --enable-kvm (make qemu le fait déjà). ↩