この記事は学習用です。ChatGPT と GitHub Copilot を使っています。
1. 概要 — バッファオーバーフローとは何か
バッファオーバーフロー(Buffer Overflow)は、プログラムが用意したメモリ領域(バッファ)に対して想定より多くのデータを書き込むことでメモリを上書きし、意図しない動作を引き起こす脆弱性です。
古典的かつ代表的な脆弱性の一つであり、現在でも C 言語や C++ など、手動でメモリ管理を行う言語で問題になります。
発生例
たとえば、次のような C コードを考えます。
void vulnerable(char *input) {
char buf[8];
strcpy(buf, input);
}
ここで input
に 8 バイトを超える文字列が与えられると、buf
の領域をはみ出して他のメモリを上書きしてしまいます。
この動作が攻撃に悪用されると、プログラムの制御を奪われる可能性があります。
2. 仕組みと影響範囲
スタック構造とリターンアドレス
C 言語の関数呼び出しでは、一般にスタック(stack)上にリターンアドレスやローカル変数が配置されます。
攻撃者はオーバーフローを利用してスタック上のリターンアドレスを書き換え、関数終了時に攻撃者が用意した任意のコードへ制御を移すことを狙います。
スタック上のメモリ構造(概念図)
多くの環境(x86/x86-64 など)では、スタックは「高アドレス → 低アドレス」に向かって伸長する。
関数呼び出し時には呼び出し元の復帰先アドレス(リターンアドレス)が保存され、続いてフレームポインタ(旧 FP/EBP/RBP の保存値)やローカル変数領域が配置される。
コンパイラや ABI により詳細は変わる。
層 | 内容 |
---|---|
高アドレス | リターンアドレス(呼び出し元の復帰先) |
保存されたフレームポインタ(旧 FP/RBP) | |
カナリア値(有効な場合) | |
ローカル変数(buf などの配列・変数) | |
低アドレス | 引数の一部や一時領域(呼出規約に依存) |
典型的なイメージ(高→低アドレス):
[ リターンアドレス ] ← 関数終了時にここへ戻る
[ 保存FP (EBP/RBP) ]
[ Stack Canary ] ← -fstack-protector 等で挿入(有効な場合)
[ 他のローカル変数 ]
[ buf ... ] ← 長すぎる入力でここから上方向に上書きが進む
^^^^^^^^^^^^^^^^^^
(低アドレス側)
- オーバーフローの伝播: 小さなバッファ(例: buf)を超えるデータは、他のローカル変数 →(カナリアが無効または回避された場合)保存 FP → リターンアドレスの順に破壊し得る。リターンアドレスが書き換わると、関数復帰時の制御フローが変化する。
- カナリアの役割: カナリアが有効であれば、関数復帰前の検査で値の破壊が検出され、プロセスは異常終了することで不正な復帰を防ぐ。
- 配置のばらつき: 変数の並び順やパディング、アライメントはコンパイラ最適化や ABI に依存するため、実際の配置はここで示す概念図と必ずしも一致しない。
- アーキテクチャ差分: ARM や RISC-V では「リンクレジスタ(LR)」に復帰先を保持するが、関数の入れ子や例外処理等でスタックへ退避されることがある。スタック成長方向や引数の受け渡し方法も ABI に依存する。
影響
- 任意コード実行(シェルの起動など)
- 脆弱なプロセスの権限範囲で任意の命令が実行され、バックドア設置や設定改変、横展開の足掛かりになり得る。
- 権限の奪取・昇格
- 高権限プロセス(例:root/SYSTEM)で発生した場合は即時に特権獲得につながる。低権限でも他の欠陥と組み合わせて権限昇格が起こり得る。
- サービス拒否(DoS)
- 異常終了(クラッシュ)や無限ループを誘発し、サービス停止・再起動、リソース枯渇、SLA 低下を引き起こす。
- データ改ざん・情報漏えい
- スタック/ヒープ上の機密(パスワード、トークン、鍵、個人情報)の読み出しや、業務データの書き換え・破損のリスク。
- セキュリティ機構の回避
- ROP などの手法と併用されると、DEP/NX や ASLR といった防御を迂回される可能性がある。
- 横展開(ラテラルムーブメント)
- 侵害ホストを踏み台として同一ネットワーク内の他システムへ侵入が拡大する。
- コンプライアンス・事業影響
- 情報漏えいに伴う法令・規約違反、罰則や信用失墜、インシデント対応コストの増大につながる。
3. 実際の攻撃手法
3.1 シェルコードの挿入
攻撃者は、バッファ内に任意コード(シェルコード)を埋め込み、そのアドレスをリターンアドレスに書き換えることで実行を狙います。
具体的なコードは本記事では取り扱いません。
3.2 NOP sled の利用
攻撃者はシェルコードの前に大量の NOP 命令(何もしない命令)を挿入し、リターンアドレスが多少ずれても NOP 領域を「滑って」シェルコードに到達するようにします。
これを NOP sled(ノップスレッド)と呼びます。
4. 防御技術と対策
4.1 安全な関数の利用
strcpy
や sprintf
など、長さチェックを行わない関数の使用を避け、長さを指定できる関数を使用します。
char buf[8];
strncpy(buf, input, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0'; // ヌル終端を保証
用途に応じて snprintf
や fgets
の利用も検討します(strncpy
は用途によっては扱いが難しい点にも留意)。
4.2 スタック保護機構(Stack Canary)
コンパイラオプションを有効にすると、関数実行時にスタック上に「カナリア値」が挿入され、改ざんを検知できます。
gcc -fstack-protector-strong -o safe safe.c
4.3 メモリ実行保護(DEP/NX ビット)
データ領域(スタックやヒープ)を実行不可に設定し、注入されたシェルコードの実行を防ぎます。
4.4 アドレス空間配置のランダム化(ASLR)
プロセスごとにメモリ空間の配置をランダム化し、攻撃者がアドレスを特定しにくくします。
# 例:Linux で ASLR の設定確認
cat /proc/sys/kernel/randomize_va_space
5. 現代の状況と限界
現代の OS では DEP・ASLR・Stack Canary が標準的に搭載され、単純なオーバーフロー攻撃は困難になっています。
しかし、攻撃者は次のような手法で回避を試みます。
- Return-Oriented Programming(ROP):既存の命令断片(ガジェット)を連結して悪用
- ヒープオーバーフローや use-after-free:スタック以外のメモリ領域を標的に
- 不十分な入力検証を突くロジックバグ攻撃
そのため、安全なコーディングと継続的なテスト・脆弱性検査が依然として重要です。
6. まとめ
バッファオーバーフローは単純な入力ミスから生じる古典的な脆弱性ですが、今なお多くの攻撃の基盤となり得ます。
システムを守るため、以下を徹底します。
- 入力サイズの検証・境界チェックを常に行う
- 危険な関数の使用を避ける
- コンパイラの保護機能を有効化する
- 定期的な脆弱性検査と更新を実施する
A. 参考リンク
IPA: 安全なウェブサイトの作り方
OWASP Buffer Overflow
MITRE CWE-120
Linux man strcpy(3)
コメント