
Depurador x86 en modo real inyectable para ingeniería inversa de BIOS y depuración de código arbitrario en modo real a través de cable serie, con integración GDB y soporte de puntos de interrupción y observación por hardware.
BREAD (BIOS Reverse Engineering & Advanced Debugger) es un depurador x86 en modo real 'inyectable' que puede depurar código arbitrario en modo real (en hardware real) desde otro PC mediante un cable serie.
BREAD surgió de muchos intentos fallidos de realizar ingeniería inversa a BIOS heredadas. Dado que la gran mayoría —si no todas— de los análisis de BIOS se realizan de forma estática usando desensambladores, entender la BIOS se vuelve extremadamente difícil, ya que no hay forma de conocer el valor de los registros o la memoria en un fragmento de código determinado.
A pesar de esto, BREAD también puede depurar código arbitrario en modo real, como código de arranque o programas DOS.
Demostración rápida:
https://user-images.githubusercontent.com/8294550/217709970-9007a1e3-7352-470d-a22f-cbb5219d5547.mp4
Cambiando el nombre de la cadena de la CPU mediante BREAD
Este depurador se divide en dos partes: el depurador (escrito completamente en ensamblador y ejecutándose en el hardware que se está depurando) y el puente (bridge), escrito en C y ejecutándose en Linux.
El depurador es el código inyectable, escrito en modo real de 16 bits, y puede colocarse dentro de la ROM de la BIOS o en cualquier otro código en modo real. Cuando se ejecuta, configura los manejadores de interrupción apropiados, pone el procesador en modo de paso a paso (single-step) y espera comandos en el puerto serie.
El puente, por otro lado, es el enlace entre el depurador y GDB. El puente se comunica con GDB a través de TCP y reenvía las solicitudes/respuestas al depurador mediante el puerto serie. La idea detrás del puente es eliminar la complejidad de los paquetes GDB y establecer un protocolo más simple para comunicarse con la máquina. Además, el protocolo más simple permite que el tamaño final del código sea más pequeño, facilitando que el depurador pueda inyectarse en varios entornos diferentes.
Como se muestra en el siguiente diagrama:
+---------+ simple packets +----------+ GDB packets +---------+
| |--------------->| |--------------->| |
| dbg | | bridge | | gdb |
|(real HW)|<---------------| (Linux) |<---------------| (Linux) |
+---------+ serial +----------+ TCP +---------+
Al implementar el stub de GDB, BREAD tiene muchas características listas para usar. Se admiten los siguientes comandos:
Realizar ingeniería inversa de un binario sin procesar, como una BIOS, en GDB implica automáticamente no tener sus símbolos originales. Sin embargo, a medida que avanza el proceso de RE, el usuario/programador/hacker obtiene una mejor comprensión de ciertas partes del código, y herramientas de análisis estático como IDA, Cutter, Ghidra y otras permiten agregar anotaciones, comentarios, definiciones de funciones y más. Estas mejoras aumentan significativamente la productividad del usuario.
Con esto en mente, hay un script Python complementario en el proyecto llamado symbolify.py. Dada una lista de símbolos (dirección etiqueta), genera un archivo ELF mínimo con estos símbolos agregados. Este ELF luego puede cargarse en GDB y usarse para simplificar enormemente el proceso de depuración.
El archivo de símbolos puede incluir espacios en blanco, líneas en blanco, comentarios (#) y comentarios en la línea de dirección. Las direcciones pueden estar en formato decimal o hexadecimal, y las etiquetas/símbolos (separados por uno o más caracteres de espacio en blanco) pueden tener la forma [a-z0-9_]+, como en (un ejemplo real se puede encontrar en symbols/ami_ipm41d3.txt):
#
# This is a comment
#
0xdeadbeef my_symbol1
0x123 othersymbol # This function does xyz
# Example with decimal address
456 anotherone
Por ejemplo, considerando el archivo de símbolos disponible en symbols/ami_ipm41d3.txt, el usuario puede hacer algo como:
$ ./simbolify.py symbols/ami_ipm41d3.txt ip41symbols.elf
Luego, cargarlo en GDB de la siguiente manera:
(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_
Observa que incluso el autocompletado de GDB funciona como se espera, ¿increíble?
¿Cuántas? Sí. Dado que el código que se está depurando no sabe que está siendo depurado, puede interferir con el depurador de varias maneras, por nombrar algunas:
Salto a modo protegido: Si el código depurado cambia a modo protegido, las estructuras para los manejadores de interrupción, etc. se alteran y el depurador ya no será invocado en ese punto del código. Sin embargo, es posible que un salto de vuelta al modo real (restaurando el estado anterior completo) permita que el depurador vuelva a funcionar.
Cambios en la IDT: Si por alguna razón el código depurado cambia la IDT o su dirección base, los manejadores del depurador no se invocarán correctamente.
Pila: ¡BREAD usa una pila y asume que existe! No debe insertarse en lugares donde la pila aún no se ha configurado.
Para la depuración de BIOS, existen otras limitaciones como: no es posible depurar el código de la BIOS desde el principio (bootblock), ya que se requiere una configuración mínima (como RAM) para que BREAD funcione correctamente. Sin embargo, es posible realizar un 'reinicio en caliente' estableciendo CS:EIP en F000:FFF0. En este escenario, se puede seguir la inicialización de la BIOS nuevamente, ya que BREAD ya está correctamente cargado. Ten en cuenta que la 'ruta de código' de la inicialización de la BIOS durante un reinicio en caliente puede ser diferente de un reinicio en frío y el flujo de ejecución puede no ser exactamente el mismo.
Compilar solo requiere GNU Make, un compilador de C (como GCC, Clang o TCC), NASM y una máquina Linux.
El depurador tiene dos modos de funcionamiento: por sondeo (polling, por defecto) y basado en interrupciones:
Los puntos de interrupción se implementan como puntos de interrupción hardware y por lo tanto tienen un número limitado de puntos disponibles. ¡En la implementación actual, solo 1 punto de interrupción activo a la vez! ↩
Los puntos de vigilancia hardware (como los puntos de interrupción) también solo se admiten uno a la vez. ↩