なぜエクスプロイト開発にGDBが必要なのか(第1回/全8回)
GDBを少しかじった方は「GDBは難しい」「黒い画面が怖い」と言われます。
でも理由はシンプルで、何を見ているツールかが見えないからなんですね。
この記事のゴールは2つだけです。
・GDBの役割(なぜ必要か)を、図で理解します。
・Kali Linuxで、最小の操作(止める→進める→見る)を体験します。
初心者の方にもわかりやすく説明したいと思います。
※イメージです。
GDBは「CPUとメモリに質問する道具」
エクスプロイト開発や、脆弱性解析で一番難しいのは・・・
「プログラムの中で何が起きているかが分からない」と言うことなのですね。
GDBが必要な理由は、
①プログラムが止められる。
②メモリ(レジスタ)の内部が見られる。
③プログラムが再現できる。なんです。
プログラムの実行の流れとして
・通常実行は
入力 → 実行 → 結果(成功/クラッシュ) ← 中身が見えない
・GDB実行は
入力 → 実行 → ①停止 → ②中身を見る(ここで観測できる) → 再開 → 結果
GDBが見ている箇所として
・CPU(レジスタ)・・・次に何をする?(RIP)
・スタック・・・どこで作業してる?(RSP)
・コード位置・・・いま何行目?(list)
となります。
GDBは単なるデバッカーツールではなく、
上記より、仮説 → 観測 → 修正ができるツールなのです。
Kali Linuxでの準備
1.Kali Linuxに「GDB」が入っているか確認しましょう。
gdb –version
と表示されればOKです。
2.超シンプルプログラムを用意します。
ターミナルを起動して「nanoエディタ」で作成します。
nano gdb01-01.c
※実行後、nanoエディタが立ち上がります。
3.以下のプログラム(C言語)を入力します。
—–
#include <stdio.h>
int main() {
int a = 5;
int b = 7;
int c = a + b;
printf(“Result: %d\n", c);
return 0;
}
—–
4.プログラム入力完了後
「Ctrl」+ o で、プログラムの書き込み
「Ctrl」+ x で、nanoエディター終了です。
5.デバッグ情報つきでコンパイルします。(超重要)
gcc -g gdb01-01.c -o gdb01-01
※"-g" の意味は
・"-g"なし:GDBが「住所(アドレス)」しか追えない。
→ 初心者には難しいです。
・"-g"あり:行番号・変数名が見える。
→ 理解が一気に楽になります。
6.lsコマンドでオブジェクトの確認をします。
作成した超シンプルプログラムを利用してのミニ演習
止める→進める→見る(最小コマンドだけ)
1.GDBを起動
gdb ./gdb01-01
gdbが起動されました。"(gdb)"が出たらOKです。
2.まず普通に実行します。(比較用)
run
“Result: 12" が出て終了します。
3.次に、gdbの機能を使い"main"で止めます。
break main
run
※"int main()"の次の行の先頭で止まっています。
4.いまどこにいるかコードを表示します。
list
5.1行ずつ命令を進めます。(ここが理解の核心になります。)
next
※"next"は「次の行へ進む」です。
※"int b = 7;"の行の先頭で止まっています。
next
※"int c = a + b;"の行の先頭で止まっています。
next
※"printf(“Result: %d\n", c);"の行の先頭で止まっています。
6.変数の内容を見ます。
print a
print b
print c
7.CPUの状態を見ます。
info registers
※レジスタの情報は沢山あります。
まず最初に確認してもらいたい項目は
RIP:次に実行する命令の位置。(プログラムの現在地)
RSP:スタックの先頭。(作業机の一番上)
RBP:関数の基準点。(机の目印)
の3つになります。
表示されているレジスタと意味(x86-64)
※参考までに
1. 汎用レジスタ (General Purpose Registers)
1. 汎用レジスタ (General Purpose Registers)
データ演算、ポインタ、関数引数などに使用されます。
rax (Register A): 演算結果、関数の戻り値を格納する主レジスタ。
rbx (Register B): ベースポインタ、汎用データ。
rcx (Register C): カウンタレジスタ、ループ処理や文字列操作で使用。
rdx (Register D): データレジスタ、I/O処理や、raxと組み合わせて64ビット以上の乗除算に使用。
rsi (Source Index): 文字列処理のソースアドレス、関数呼び出しの第2引数。
rdi (Destination Index): 文字列処理のデスティネーションアドレス、関数呼び出しの第1引数。
rbp (Base Pointer): 現在のスタックフレームのベースアドレス(スタックの底)。
rsp (Stack Pointer): 現在のスタックトップアドレス。
r8 – r15: 汎用レジスタ。追加のデータや関数引数(第3?第6引数)に使用。
2. 命令ポインタ (Instruction Pointer)
rip (Instruction Pointer): 次に実行される命令のアドレスを指す。
3. フラグレジスタ (Flag Register)
eflags: CPUのステータス情報(算術演算の結果や割り込みの許可/禁止など)をビット単位で保持。
例: [ IF ZF ] (Interrupt Flag: 割り込み可能, Zero Flag: 結果が0)
4. セグメントレジスタ (Segment Registers)
cs, ss, ds, es, fs, gs: メモリセグメントのベースアドレスなどを格納。64ビットモードでは主にfs, gsがスレッドローカルストレージ(TLS)に使用されます。
5.fs_base (Thread Local Storage: TLS)
用途: スレッドごとに固有の変数(スレッド局所記憶)を管理するために使用されます。
仕組み: __thread 変数などにアクセスする際、CPUは fs レジスタをベースとした相対アドレスを参照します。
6.gs_base (Per-CPU Data / OS特定用途)
用途: 主にカーネル内で、CPUごとのデータ(Per-CPUデータ)を管理するために使用されます。
ユーザー空間: ライブラリや特定のランタイム環境が独自の目的(スタックガードなど)で使用することがあります。
8.再開します。
continue
9.gdbを終了します。
quit
GDB操作が「論理」に変わる瞬間
あなたが今やったことの確認です。(観測フロー)
(1) break main → どこで止めるか決める(観測点)
(2) run → そこまで実行
(3) list → いまの場所を確認
(4) next → 1行ずつ進め、変化を見る
(5) print / info reg→ 変数・CPU状態を確認
(6) continue → 再開して結果を見る
関数呼び出しがあると…
スタック上に
・ローカル変数
・戻り先情報(戻る場所)
などが積まれます。
GDBは RSP/RBP を見て
「いまこの積み上げがどうなっているか」を確認できます。
ここまでできれば、次回以降の「スタック」「レジスタ」「メモリ」が一気に理解しやすくなります。
なぜエクスプロイト開発にGDBが必要なのかのまとめ
GDBがエクスプロイト開発に必要な理由は、攻撃手順を覚えるためではなく、プログラム内部を「観測して理解する」ためです。
通常実行では入力と結果しか見えず、クラッシュしても原因が推測止まりになります。GDBならブレークポイントで実行を止め、現在地(list)・変数(print)・CPU状態(info registers)・スタックやメモリを確認できます。
これにより「仮説→観測→検証→修正」のループが回り、偶然ではなく再現性のある理解が得られます。つまりGDBは、失敗を情報に変え、論理的にエクスプロイトを組み立てるための“顕微鏡”です。