このページの目次

ASLR とセキュリティ保護

カバー範囲: ASLR (stack/mmap/exec) → PIE → RELRO → stack canary → NX → CET (IBT/SHSTK) → CFI → セーフティチェックを防御として 適用対象: Linux ユーザーランド, GCC/Clang + glibc + kernel

概要

ASLR は Linux ユーザーランドにおけるセキュリティの最初の防衛線です。アドレス空間のレイアウトをランダム化することで、攻撃者がコード、データ、スタックの位置を予測できなくします。しかし、ASLR だけでは不十分です。PIE、RELRO、stack canary、NX はそれぞれ異なる攻撃面を解決し、組み合わせることで階層型防御を形成します。

ASLR の3層

# /proc/sys/kernel/randomize_va_space
#   0: 無効
#   1: mmap base + stack + vDSO のみをランダム化 (部分的)
#   2: 全ランダム化 + brk (完全, デフォルト)

# ランダム化される対象:
#   - stack:      各 exec ごとにランダムオフセット (最大約8MBのランダム範囲)
#   - mmap base:  共有ライブラリ .so および匿名 mmap の開始アドレス
#   - executable: PIE (Position-Independent Executable) のみ
#   - brk (heap): full モードでランダム化

# ASLR の影響を受けないもの:
#   - 非 PIE 実行ファイル: 固定アドレス (0x400000)
#   - vsyscall: 固定アドレス (0xffffffffff600000)

PIE: Position-Independent Executable

# 非 PIE (従来型, GCC デフォルトの -no-pie, 非推奨):
#   .text が固定アドレス 0x400000 にロードされる
#   → 攻撃者はすべての gadget のアドレスを知っている → ROP が容易
file old_binary  # "ELF 64-bit LSB executable"

# PIE (現代のデフォルト, GCC -pie):
#   全体として .so のようにランダムロード可能 → ASLR が有効
#   ロードベースアドレスは各 exec ごとにランダム
file /bin/ls     # "ELF 64-bit LSB pie executable"

# ロードベースアドレスの確認:
cat /proc/self/maps | head -1  # 毎回異なる

# PIE のパフォーマンスコスト: 約2-3% (GOT の追加間接参照)
#   -fno-plt オプションにより一部解消可能 (PLT をスキップし、GOT への直接アクセス)

RELRO: リロケーションの読み取り専用化

# リンカオプション:
# -Wl,-z,norelro: 保護なし
# -Wl,-z,relro:   部分的保護 (デフォルト)
# -Wl,-z,now:     完全保護 (BIND_NOW + RELRO)

# 効果:
#   Partial RELRO:
#     .got および .dynamic は起動後に読み取り専用になる
#     .got.plt は依然として書き込み可能 (遅延バインディングが必要)
#
#   Full RELRO (BIND_NOW):
#     すべての GOT は起動後に読み取り専用になる
#     コスト: すべてのシンボルが起動時に即時解決される → 起動が若干遅くなる
#     利点: GOT が書き込み不可 → GOT ポインターの上書きが不可能 → GOT overwrite 攻撃を防止

# 確認:
readelf -l /bin/ls | grep GNU_RELRO  # 表示されれば → RELRO 有効
readelf -d /bin/ls | grep BIND_NOW   # 表示されれば → Full RELRO

Stack Canary

// GCC -fstack-protector (自動有効)
// 各関数のスタックフレームの末尾に 8バイトの canary (ランダム値) を配置
// 関数終了時に canary をチェック → 変更されていれば → スタックオーバーフロー → abort

// Canary のソース:
//   /proc/sys/kernel/randomize_va_space → AT_RANDOM (16バイト)
//   または: __stack_chk_guard (glibc TLS, 起動時に AT_RANDOM から初期化)

// 確認:
//   __stack_chk_fail() → __fortify_fail() → abort + ログ出力

// コンパイラレベル:
//   -fstack-protector:      char buf[8+] を持つ関数を保護
//   -fstack-protector-strong: 配列やアドレス取得操作のある関数を保護 (推奨)
//   -fstack-protector-all:   すべての関数を保護 (パフォーマンスコスト約5%)

NX (No-Execute)

ハードウェア (x86: XD bit / NX bit, ARM: XN):
  ELF セグメント PF_X → executable としてマッピング
  スタックとヒープ: PF_X なし → 実行不可

  → 攻撃者はヒープ/スタックに shellcode を注入して実行にジャンプできない
  → ただし ROP は依然として有効 (既存の .text 断片を再利用)

確認:
  cat /proc/cpuinfo | grep nx   # x86: フラグに 'nx' が含まれる
  readelf -l /bin/ls | grep STACK  # PT_GNU_STACK: RW (X なし)

CET: コントロールフロー強制 (Intel 第11世代以降)

IBT (Indirect Branch Tracking):
  ハードウェア: 間接ジャンプ(jmp/call [reg])は endbr32/endbr64 命令のみ許可
    → ROP/JOP が gadget の途中にジャンプ → CPU が検出 → #CP fault

SHSTK (Shadow Stack):
  ハードウェア: 独立したシャドウスタックを維持 (通常の store では変更不可)
    call: ハードウェアが戻りアドレスをシャドウスタックに push
    ret:  ハードウェアがシャドウスタックと通常スタックを比較 → 不一致 → #CP fault
    → ROP でスタックが変更 → ret 時に検出 → 拒否

コンパイラ: GCC 8+ / Clang 7+ -fcf-protection=full
カーネル: CET サポート (5.18以降)

複合防御

攻撃ベクトル             緩和策
────────────────────────────────────
スタックオーバーフロー →  canary + NX + SHSTK
ヒープオーバーフロー →   FORTIFY_SOURCE, ASAN (デバッグ用)
GOT overwrite →          Full RELRO
ROP/JOP →                ASLR + PIE + IBT
情報漏洩 →               kptr_restrict, dmesg_restrict
UAF →                    ASAN (デバッグ用), SLAB_FREELIST_RANDOM

確認ツール

# バイナリのセキュリティ強化状態を確認
checksec --file=/bin/ls
# または: hardening-check /bin/ls  (Debian/Ubuntu devscripts)

# 出力例:
#   Position Independent Executable: yes
#   Stack protected: yes
#   Fortify Source functions: yes
#   Read-only relocations: yes
#   Immediate binding: yes

参考文献

  • カーネルドキュメント⁠: Documentation/admin-guide/sysctl/kernel.rst (randomize_va_space)
  • LWN: "CET on x86", "Full RELRO and BIND_NOW"
  • ツール⁠: checksec, hardening-check, pwntools

キーワード: ASLR, PIE, RELRO, stack canary, NX, CET, IBT, SHSTK, FORTIFY_SOURCE, BIND_NOW