PR

バッファオーバーフロー攻撃の仕組みと防御

この記事は学習用です。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 安全な関数の利用

strcpysprintf など、長さチェックを行わない関数の使用を避け、長さを指定できる関数を使用します。

char buf[8];
strncpy(buf, input, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';  // ヌル終端を保証

用途に応じて snprintffgets の利用も検討します(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)

B. 関連書籍

コメント