BREAD (BIOS Reverse Engineering & Advanced Debugger) es un depurador x86 en modo real 'inyectable' que puede depurar código arbitrario en modo real (en HW real) desde otra PC mediante un cable serie.
BREAD surgió de muchos intentos fallidos de aplicar ingeniería inversa al BIOS heredado. Dado que la gran mayoría, si no todos, los análisis de BIOS se realizan estáticamente utilizando desensambladores, comprender el BIOS se vuelve extremadamente difícil, ya que no hay forma de saber 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:
Cambiar el nombre de la cadena de la CPU a través de PAN
Este depurador se divide en dos partes: el depurador (escrito enteramente en ensamblador y ejecutándose en el hardware que se está depurando) y el puente, escrito en C y ejecutándose en Linux.
El depurador es el código inyectable, escrito en modo real de 16 bits, y se puede colocar dentro de la ROM del BIOS o cualquier otro código en modo real. Cuando se ejecuta, configura los manejadores de interrupciones apropiados, pone el procesador en modo de un solo paso y espera comandos en el puerto serie.
El puente, por otro lado, es el vínculo entre el depurador y GDB. El puente se comunica con GDB a través de TCP y reenvía las solicitudes/respuestas al depurador a través del 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 del código final sea más pequeño, lo que facilita que el depurador se pueda inyectar 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 código auxiliar de GDB, BREAD tiene muchas características listas para usar. Se admiten los siguientes comandos:
La ingeniería inversa de un binario sin formato, como un 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 las 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.
Teniendo esto en cuenta, hay un script Python complementario en el proyecto llamado symbolify.py
. Dada una lista de símbolos (etiqueta de dirección), genera un archivo ELF mínimo con estos símbolos agregados. Este ELF se puede cargar en GDB más tarde y utilizar 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 espacios en blanco) pueden tener la forma [a-z0-9_]+, como en (se puede encontrar un ejemplo real en símbolos/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 símbolos/ami_ipm41d3.txt, el usuario puede hacer algo como:
$ ./simbolify.py symbols/ami_ipm41d3.txt ip41symbols.elf
Luego, cárguelo en GDB como en:
(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_
Tenga en cuenta que incluso el autocompletado de GDB funciona como se esperaba, ¿sorprendente?
¿Cuántos? Sí. Dado que el código que se está depurando no sabe que se está depurando, puede interferir con el depurador de varias maneras, por nombrar algunas:
Salto en modo protegido: si el código depurado cambia al modo protegido, las estructuras para los manejadores de interrupciones, etc., se modifican y el depurador ya no se invocará en ese punto del código. Sin embargo, es posible que un salto al modo real (restauración del estado anterior completo) permita que el depurador vuelva a funcionar.
Cambios de IDT: si por algún motivo el código depurado cambia el IDT o su dirección base, los controladores del depurador no se invocarán correctamente.
Pila: BREAD usa una pila y asume que existe. No debe insertarse en ubicaciones donde la pila aún no se haya configurado.
Para la depuración del BIOS, existen otras limitaciones como: no es posible depurar el código del BIOS desde el principio (bloque de arranque), 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" configurando CS:EIP en F000:FFF0
. En este escenario, se puede seguir la inicialización del BIOS nuevamente, ya que BREAD ya está cargado correctamente. Tenga en cuenta que la "ruta del código" de la inicialización del 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.
La compilación 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: sondeo (predeterminado) y basado en interrupciones:
El modo de sondeo es el enfoque más simple y debería funcionar bien en una variedad de entornos. Sin embargo, debido a la naturaleza del sondeo, hay un alto uso de CPU:
$ git clone https://github.com/Theldus/BREAD.git
$ cd BREAD/
$ make
El modo basado en interrupciones optimiza la utilización de la CPU mediante el uso de interrupciones UART para recibir nuevos datos, en lugar de sondearlos constantemente. Esto da como resultado que la CPU permanezca en un estado "detenido" hasta que reciba comandos del depurador y, por lo tanto, evita que consuma el 100% de los recursos de la CPU. Sin embargo, como las interrupciones no siempre están habilitadas, este modo no está configurado como opción predeterminada:
$ git clone https://github.com/Theldus/BREAD.git
$ cd BREAD/
$ make UART_POLLING=no
Usar BREAD solo requiere un cable serie (y sí, su placa base tiene un encabezado COM, consulte el manual) e inyectar el código en la ubicación adecuada.
Para inyectar, se deben realizar cambios mínimos en dbg.asm (el src del depurador). Se debe cambiar el 'ORG' del código y también cómo debe regresar el código (busque " >> CHANGE_HERE <<
" en el código para los lugares que deben cambiarse).
Usando una AMI heredada como ejemplo, donde el módulo depurador se colocará en el lugar del logotipo del BIOS ( 0x108200
o FFFF:8210
) y las siguientes instrucciones en la ROM se reemplazarán con una llamada remota al módulo:
...
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
...
el siguiente parche es suficiente:
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
Es importante tener en cuenta que si ha modificado algunas instrucciones dentro de su ROM para invocar el código del depurador, deben restaurarse antes de regresar del depurador.
La razón para reemplazar estas dos instrucciones es que se ejecutan justo antes de que el BIOS muestre el logotipo en la pantalla, que ahora es el depurador, lo que garantiza algunos puntos clave:
Encontrar una buena ubicación para llamar al depurador (donde el BIOS ya se haya inicializado lo suficiente, pero no demasiado tarde) puede ser un desafío, pero es posible.
Después de esto, dbg.bin
está listo para insertarse en la posición correcta en la ROM.
Depurar programas de DOS con BREAD es un poco complicado, pero posible:
dbg.asm
para que DOS lo entienda como un programa de DOS válido:times
)int 0x20
)El siguiente parche soluciona esto:
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
Cree una imagen de disquete de arranque de FreeDOS (o DOS) que contenga solo el núcleo y el terminal: KERNEL.SYS
y COMMAND.COM
. Agregue también a esta imagen del disquete el programa a depurar y el DBG.COM
( dbg.bin
).
Se deben seguir los siguientes pasos después de crear la imagen:
bridge
ya abierto (consulte la siguiente sección para obtener instrucciones).DBG.COM
.DBG.COM
continúe hasta que finalice.Es importante tener en cuenta que DOS no borra la imagen del proceso una vez que sale. Como resultado, el depurador se puede configurar como cualquier otro programa de DOS y se pueden establecer los puntos de interrupción adecuados. El comienzo del depurador está lleno de NOP, por lo que se anticipa que el nuevo proceso no sobrescribirá la memoria del depurador, lo que le permitirá continuar funcionando incluso después de que parezca "terminado". Esto permite a BREaD depurar otros programas, incluido el propio DOS.
Bridge es el pegamento entre el depurador y GDB y se puede utilizar de diferentes maneras, ya sea en hardware real o en una máquina virtual.
Sus parámetros son:
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)
Para usarlo en hardware real, simplemente invoquelo sin parámetros. Opcionalmente, puede cambiar la ruta del dispositivo con el parámetro -d
:
./bridge
o ./bridge -d /path/to/device
)Single-stepped, you can now connect GDB!
y luego inicie GDB: gdb
. Para su uso en una máquina virtual, el orden de ejecución cambia ligeramente:
./bridge
o ./bridge -d /path/to/device
)make bochs
o make qemu
)Single-stepped, you can now connect GDB!
y luego inicie GDB: gdb
.En ambos casos, asegúrese de ejecutar GDB dentro de la carpeta raíz de BRIDGE, ya que hay archivos auxiliares en esta carpeta para que GDB funcione correctamente en 16 bits.
BREAD siempre está abierto a la comunidad y dispuesto a aceptar contribuciones, ya sea con problemas, documentación, pruebas, nuevas funciones, correcciones de errores, errores tipográficos, etc. Bienvenido a bordo.
BREAD tiene licencia MIT. Escrito por Davidson Francis y (con suerte) otros contribuyentes.
Los puntos de interrupción se implementan como puntos de interrupción de hardware y, por lo tanto, tienen un número limitado de puntos de interrupción disponibles. En la implementación actual, ¡solo 1 punto de interrupción activo a la vez! ↩
Los puntos de vigilancia de hardware (como los puntos de interrupción) también solo se admiten uno a la vez. ↩
Tenga en cuenta que los registros de depuración no funcionan de forma predeterminada en las máquinas virtuales. Para bochs, debe compilarse con el indicador --enable-x86-debugger=yes
. Para Qemu, debe ejecutarse con KVM habilitado: --enable-kvm
( make qemu
ya hace esto). ↩