BREAD (BIOS リバース エンジニアリング & アドバンスト デバッガー) は、シリアル ケーブルを介して別の PC から (実際の HW 上の) 任意のリアルモード コードをデバッグできる「注入可能な」リアルモード x86 デバッガーです。
BREAD は、レガシー BIOS をリバース エンジニアリングする多くの失敗した試みから生まれました。すべてではないにしても、大部分の BIOS 解析が逆アセンブラを使用して静的に行われることを考えると、特定のコード部分のレジスタやメモリの値を知る方法がないため、BIOS を理解することは非常に困難になります。
それにもかかわらず、BREAD はブート可能コードや DOS プログラムなどの任意のコードをリアルモードでデバッグすることもできます。
簡単なデモ:
BREADによるCPU文字列名の変更
このデバッガは 2 つの部分に分かれています。デバッガ (完全にアセンブリで記述され、デバッグ対象のハードウェア上で実行されます) と、C で記述され Linux 上で実行されるブリッジです。
デバッガは、16 ビット リアル モードで記述された注入可能なコードであり、BIOS ROM またはその他のリアル モード コード内に配置できます。実行すると、適切な割り込みハンドラが設定され、プロセッサがシングルステップ モードになり、シリアル ポートでコマンドを待ちます。
一方、ブリッジはデバッガーと GDB の間のリンクです。ブリッジは TCP 経由で GDB と通信し、シリアル ポート経由で要求/応答をデバッガに転送します。ブリッジの背後にある考え方は、GDB パケットの複雑さを取り除き、マシンと通信するためのより単純なプロトコルを確立することです。さらに、プロトコルが単純になったことにより、最終的なコード サイズが小さくなり、デバッガーをさまざまな環境に簡単に挿入できるようになります。
次の図に示すように:
+---------+ simple packets +----------+ GDB packets +---------+
| | --------------- > | | --------------- > | |
| dbg | | bridge | | gdb |
| ( real HW ) | <- -------------- | ( Linux ) | <- -------------- | ( Linux ) |
+---------+ serial +----------+ TCP +---------+
GDB スタブを実装することにより、BREAD にはすぐに使える多くの機能が備わっています。次のコマンドがサポートされています。
BIOS などの生のバイナリを GDB でリバース エンジニアリングすると、自動的に元のシンボルが存在しないことになります。ただし、RE プロセスが進むにつれて、ユーザー/プログラマ/ハッカーはコードの特定の部分をより深く理解し、IDA、Cutter、Ghidra などの静的分析ツールを使用して、注釈、コメント、関数定義、などなど。これらの機能強化により、ユーザーの生産性が大幅に向上します。
これを念頭に置いて、プロジェクトには、 symbolify.py
というコンパニオン Python スクリプトがあります。シンボルのリスト (アドレス ラベル) を指定すると、これらのシンボルが追加された最小限の ELF ファイルが生成されます。この ELF は、後で GDB にロードして、デバッグ プロセスを大幅に簡素化するために使用できます。
シンボル ファイルには、空白、空行、コメント (#)、およびアドレス行のコメントを含めることができます。アドレスは 10 進数または 16 進数の形式で、ラベル/シンボル (1 つ以上の空白文字で区切られる) は [a-z0-9_]+ の形式になります (実際の例は、symbols/ami_ipm41d3 にあります)。 TXT):
#
# This is a comment
#
0xdeadbeef my_symbol1
0x123 othersymbol # This function does xyz
# Example with decimal address
456 anotherone
たとえば、symbols/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 のデバッグには、他にも次のような制限があります。BREAD が正しく機能するには最小限のセットアップ (RAM など) が必要であるため、BIOS コードを最初からデバッグすることはできません (ブートブロック)。ただし、 CS:EIP をF000:FFF0
に設定することで、「ウォームリブート」を実行することができます。このシナリオでは、BREAD がすでに適切にロードされているため、BIOS の初期化を再度実行できます。ウォームリブート中の BIOS 初期化の「コードパス」はコールドリブートとは異なる場合があり、実行フローはまったく同じではない場合があることに注意してください。
ビルドに必要なのは、GNU Make、C コンパイラー (GCC、Clang、TCC など)、NASM、および Linux マシンのみです。
デバッガには、ポーリング (デフォルト) と割り込みベースの 2 つの動作モードがあります。
ポーリング モードは最も単純なアプローチであり、さまざまな環境で適切に機能します。ただし、ポーリングの性質により、CPU 使用率が高くなります。
$ git clone https://github.com/Theldus/BREAD.git
$ cd BREAD/
$ make
割り込みベース モードは、新しいデータを常にポーリングするのではなく、UART 割り込みを利用して新しいデータを受信することにより、CPU 使用率を最適化します。これにより、CPU はデバッガからコマンドを受信するまで「停止」状態のままとなり、CPU リソースが 100% 消費されることがなくなります。ただし、割り込みが常に有効であるとは限らないため、このモードはデフォルトのオプションとして設定されていません。
$ 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 内の次の命令がモジュールへの far 呼び出しに置き換えられます。
...
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 内のいくつかの命令を変更した場合、デバッガから戻る前にそれらの命令を復元する必要があることに注意することが重要です。
これら 2 つの命令を置き換える理由は、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
カーネルとターミナル ( KERNEL.SYS
およびCOMMAND.COM
) だけを含むブート可能な FreeDOS (または DOS) フロッピー イメージを作成します。また、このフロッピー イメージに、デバッグするプログラムとDBG.COM
( dbg.bin
) を追加します。
イメージを作成した後は、次の手順を実行する必要があります。
bridge
がすでに開いている状態で起動します (手順については次のセクションを参照してください)。DBG.COM
を実行します。DBG.COM
プロセスが完了するまで続行します。DOS は終了後にプロセス イメージを消去しないことに注意することが重要です。その結果、デバッガを他の DOS プログラムと同様に設定でき、適切なブレークポイントを設定できます。デバッガの先頭は NOP で埋められているため、新しいプロセスがデバッガのメモリを上書きせず、「終了」したように見えた後でも機能を継続できることが期待されます。これにより、BREaD は DOS 自体を含む他のプログラムをデバッグできるようになります。
ブリッジはデバッガーと 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
を起動します。どちらの場合も、GDB が 16 ビットで適切に動作するための補助ファイルがこのフォルダー内にあるため、必ず GDB を BRIDGE ルート フォルダー内で実行してください。
BREAD は常にコミュニティにオープンであり、問題、ドキュメント、テスト、新機能、バグ修正、タイプミスなどの貢献を喜んで受け入れます。ようこそ。
BREAD は MIT ライセンスに基づいてライセンスされています。 Davidson Francis と (できれば) 他の寄稿者によって書かれました。
ブレークポイントはハードウェア ブレークポイントとして実装されるため、使用できるブレークポイントの数は限られています。現在の実装では、アクティブなブレークポイントは一度に 1 つだけです。 ↩
ハードウェア ウォッチポイント (ブレークポイントなど) も、一度に 1 つだけサポートされます。 ↩
デバッグ レジスタは、VM ではデフォルトでは機能しないことに注意してください。 boch の場合は、 --enable-x86-debugger=yes
フラグを指定してコンパイルする必要があります。 Qemu の場合、KVM を有効にして実行する必要があります: --enable-kvm
( make qemu
すでにこれを行っています)。 ↩