BREAD(BIOS 逆向工程和高级调试器)是一种“可注入”实模式 x86 调试器,可以通过串行电缆从另一台 PC 调试任意实模式代码(在真实硬件上)。
BREAD 是在对传统 BIOS 进行逆向工程的多次失败尝试中诞生的。鉴于绝大多数(如果不是全部)BIOS 分析是使用反汇编程序静态完成的,理解 BIOS 变得极其困难,因为无法知道给定代码段中寄存器或内存的值。
尽管如此,BREAD 还可以在实模式下调试任意代码,例如可启动代码或 DOS 程序。
快速演示:
通过 BREAD 更改 CPU 字符串名称
该调试器分为两部分:调试器(完全用汇编语言编写并在被调试的硬件上运行)和桥接器(用 C 语言编写并在 Linux 上运行)。
调试器是可注入代码,以 16 位实模式编写,可以放置在 BIOS ROM 或任何其他实模式代码中。执行时,它会设置适当的中断处理程序,将处理器置于单步模式,并等待串行端口上的命令。
另一方面,桥是调试器和 GDB 之间的链接。桥接器通过 TCP 与 GDB 通信,并通过串行端口将请求/响应转发到调试器。桥接器背后的想法是消除 GDB 数据包的复杂性并建立一个更简单的协议来与机器通信。此外,更简单的协议使最终代码大小更小,使调试器更容易注入到各种不同的环境中。
如下图所示:
+---------+ simple packets +----------+ GDB packets +---------+
| | --------------- > | | --------------- > | |
| dbg | | bridge | | gdb |
| ( real HW ) | <- -------------- | ( Linux ) | <- -------------- | ( Linux ) |
+---------+ serial +----------+ TCP +---------+
通过实现 GDB 存根,BREAD 具有许多开箱即用的功能。支持以下命令:
在 GDB 中对原始二进制文件(例如 BIOS)进行逆向工程自动意味着没有其原始符号。然而,随着 RE 过程的进展,用户/程序员/黑客可以更好地理解代码的某些部分,并且 IDA、Cutter、Ghidra 等静态分析工具允许添加注释、注释、函数定义、等等。这些增强功能显着提高了用户的工作效率。
考虑到这一点,项目中有一个名为symbolify.py
的配套 Python 脚本。给定一个符号列表(地址标签),它会生成一个添加了这些符号的最小 ELF 文件。然后可以将该 ELF 加载到 GDB 中并用于大大简化调试过程。
符号文件可以包含空格、空行、注释 (#) 以及地址行上的注释。地址可以采用十进制或十六进制格式,标签/符号(由一个或多个空格字符分隔)可以采用 [a-z0-9_]+ 的形式,如(真实示例可以在symbols/ami_ipm41d3.txt 中找到)。 TXT):
#
# This is a comment
#
0xdeadbeef my_symbol1
0x123 othersymbol # This function does xyz
# Example with decimal address
456 anotherone
例如,考虑到符号/ami_ipm41d3.txt 中提供的符号文件,用户可以执行以下操作:
$ ./simbolify.py symbols/ami_ipm41d3.txt ip41symbols.elf
然后,将其加载到 GDB 中,如下所示:
(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_
请注意,甚至 GDB 自动完成功能也按预期工作,令人惊奇吗?
多少?是的。由于正在调试的代码不知道自己正在被调试,因此它可能会以多种方式干扰调试器,仅举几例:
保护模式跳转:如果被调试的代码切换到保护模式,中断处理程序等的结构将被更改,并且在代码中的该点将不再调用调试器。但是,跳回到实模式(恢复之前的完整状态)可能会让调试器再次工作。
IDT 更改:如果由于任何原因调试代码更改了 IDT 或其基地址,则将无法正确调用调试器处理程序。
堆栈:BREAD 使用堆栈并假设它存在!不应将其插入到尚未配置堆栈的位置。
对于 BIOS 调试,还有其他限制,例如:不可能从一开始(引导块)调试 BIOS 代码,因为 BREAD 需要最低限度的设置(例如 RAM)才能正常运行。但是,可以通过将 CS:EIP 设置为F000:FFF0
来执行“热重启”。在这种情况下,可以再次进行 BIOS 初始化,因为 BREAD 已经正确加载。请注意,热重启期间 BIOS 初始化的“代码路径”可能与冷重启期间不同,并且执行流程可能不完全相同。
构建只需要 GNU Make、C 编译器(例如 GCC、Clang 或 TCC)、NASM 和 Linux 机器。
调试器有两种操作模式:轮询(默认)和基于中断:
轮询模式是最简单的方法,应该适用于各种环境。然而,由于轮询的性质,CPU 使用率很高:
$ git clone https://github.com/Theldus/BREAD.git
$ cd BREAD/
$ make
基于中断的模式通过利用 UART 中断接收新数据而不是不断轮询来优化 CPU 利用率。这会导致 CPU 保持“暂停”状态,直到收到来自调试器的命令,从而防止它消耗 100% 的 CPU 资源。然而,由于中断并不总是启用,因此该模式未设置为默认选项:
$ git clone https://github.com/Theldus/BREAD.git
$ cd BREAD/
$ make UART_POLLING=no
使用BREAD只需要一根串行电缆(是的,你的主板有一个COM头,请检查手册)并在适当的位置注入代码。
要注入,必须在 dbg.asm(调试器的 src)中进行最小的更改。必须更改代码的“ORG”以及代码应如何返回(在代码中查找“ >> CHANGE_HERE <<
”以查找需要更改的位置)。
以 AMI 旧版为例,其中调试器模块将放置在 BIOS 徽标的位置( 0x108200
或FFFF:8210
),并且 ROM 中的以下指令已被替换为对该模块的远程调用:
...
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
...
以下补丁就足够了:
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
需要注意的是,如果您更改了 ROM 中的一些指令来调用调试器代码,则必须在从调试器返回之前恢复它们。
替换这两条指令的原因是它们是在 BIOS 在屏幕上显示徽标(现在是调试器)之前执行的,确保了几个关键点:
找到一个调用调试器的好位置(BIOS 已经充分初始化,但还不算太晚)可能具有挑战性,但这是可能的。
此后, dbg.bin
就可以插入到 ROM 中的正确位置了。
使用 BREAD 调试 DOS 程序有点棘手,但也是可能的:
dbg.asm
以便 DOS 将其理解为有效的 DOS 程序:times
)int 0x20
)以下补丁解决了这个问题:
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
创建仅包含内核和终端的可引导 FreeDOS(或 DOS)软盘映像: KERNEL.SYS
和COMMAND.COM
。还要将要调试的程序和DBG.COM
( dbg.bin
) 添加到该软盘映像中。
创建镜像后应执行以下步骤:
bridge
已经打开的情况下启动它(请参阅下一节的说明)。DBG.COM
。DBG.COM
进程继续运行直至完成。需要注意的是,DOS 退出后不会删除进程映像。因此,调试器可以像任何其他 DOS 程序一样进行配置,并且可以设置适当的断点。调试器的开头充满了 NOP,因此预计新进程不会覆盖调试器的内存,从而使其即使在看起来“完成”后也能继续运行。这允许 BREAD 调试其他程序,包括 DOS 本身。
Bridge 是调试器和 GDB 之间的粘合剂,可以以不同的方式使用,无论是在真实硬件还是虚拟机上。
其参数为:
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)
要在真实硬件上使用它,只需不带参数调用它即可。或者,您可以使用-d
参数更改设备路径:
./bridge
或./bridge -d /path/to/device
)Single-stepped, you can now connect GDB!
然后启动 GDB: gdb
。 对于在虚拟机中使用,执行顺序略有变化:
./bridge
或./bridge -d /path/to/device
)make bochs
或make qemu
)Single-stepped, you can now connect GDB!
然后启动 GDB: gdb
。在这两种情况下,请务必在 BRIDGE 根文件夹中运行 GDB,因为该文件夹中有辅助文件,使 GDB 在 16 位中正常工作。
BREAD 始终向社区开放,并愿意接受贡献,无论是问题、文档、测试、新功能、错误修复、拼写错误等。欢迎加入。
BREAD 根据 MIT 许可证获得许可。由戴维森·弗朗西斯和(希望如此)其他贡献者撰写。
断点作为硬件断点实现,因此可用断点的数量有限。在当前的实现中,一次只有 1 个活动断点! ↩
硬件观察点(如断点)也一次仅支持一个。 ↩
请注意,调试寄存器默认在虚拟机上不起作用。对于 bochs,需要使用--enable-x86-debugger=yes
标志进行编译。对于 Qemu,它需要在启用 KVM 的情况下运行: --enable-kvm
( make qemu
已经这样做了)。 ↩