
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.
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. ↩