PR 本記事には広告(Amazonアソシエイト・もしもアフィリエイト・A8.net等)が含まれます。掲載情報の正確性には努めていますが、商品の詳細は必ずリンク先で最新情報をご確認ください。

【GOT Overwrite・Format String編】任意書込プリミティブで実行を奪う|CTF思考フレームワーク #61

【GOT Overwrite・Format String編】任意書込プリミティブで実行を奪う|CTF思考フレームワーク #61 アイキャッチ画像
安全に生きたい編集部

広告・PRを含みます。この記事にはアフィリエイトリンクが含まれます。掲載内容は編集方針に基づいて作成していますが、価格・在庫・キャンペーン内容はリンク先で最新情報を確認してください。

Format String 攻撃って何?printf に関係あるの?📐

そうです。printf(user_input) のような『書式指定子をユーザーに任せた』実装があると、%n でメモリへの任意書込、%p でメモリ読み出しができます。GOT(Global Offset Table)を書き換えれば任意関数呼び出しに発展。

Format String Vulnerability は、printf(format, args) で format を信頼できないデータに渡すと発生。%x %p %n を駆使することで、メモリ読み出し・任意アドレス書込が可能。GOT(共有ライブラリ関数のアドレス表)を書き換えれば、puts() を system() に変えるなど任意関数呼び出しへ。

printf にユーザー入力渡すだけでそんな事できるんだ…

この記事は、CTF思考フレームワーク第61回。Format String 攻撃の仕組みと、任意書込プリミティブの構築、GOT Overwrite による実行奪取、開発側の防御(コンパイラ警告・FORTIFY_SOURCE)を整理します。

📖 この記事はシリーズの一部です
CTF思考フレームワーク#61 / 全86記事 → シリーズ一覧を見る →

🎯 printf(user_input) という1行のミスから、深刻なRCE(任意コード実行)が起こり得ます。Format String脆弱性は「任意の場所を読み書きできる」強力なプリミティブで、CTFのクラシック題材です。

Format Stringの真価は %n(書き込み)。任意のアドレスに任意の値を書ける=GOT書き換えで関数フックがそのまま「shell起動関数を呼ばせる」攻撃に直結します。

難易度:★★★(上級)

GOT Overwrite・Format Stringで任意書込プリミティブを獲得します✍️

Format Stringは「書式文字列にユーザー入力」するだけで読み書きの両方が手に入る恐ろしい脆弱性✍️

この記事で出てくる言葉

先に意味を押さえておくと読みやすい用語です。

  • CTF: セキュリティの練習問題を解く競技。必ず許可された環境だけで試します。
  • 情報漏洩: 本来出てはいけない個人情報や機密情報が外部に出ることです。
  • Pwn: バイナリの不備を突いてプログラムの制御を奪うCTF分野です。
  • スタック: 関数呼び出しや一時データを積み上げて管理するメモリ領域です。

👀 観察フェーズ:まず何を見る?

printf(user_input)のようなコードがあれば即FSB(Format String Bug)確定🔍

まずprintf系に第一引数として未検証のユーザ入力が渡っていないか確認。%pを投げてスタックの中身が漏れたら確定。

  • printf / fprintf / sprintf / snprintf に user_input が直接
  • %p %p %p %p %pでリーク確認
  • スタック上のオフセット番号(%N$p
  • GOT書き換え対象(puts / exit / printf自身など)
  • PIE有効ならまずleakで.text base取得
  • libc使用=one_gadgetも候補

%pでスタックリーク、%sで任意アドレス読込、%n任意アドレスに書込が可能になります👀

%nって「ここまで何文字書いたか」を書き込む機能なんだね…超危険😱

🤔 仮説フェーズ:攻撃者は何を考える?

FSB/GOT攻撃の典型は4方向。

🕶️ 攻撃者は「%nでGOTを書き換え、次にその関数が呼ばれた瞬間に乗っ取り」と発想。1回の入力でリークと書き込みを両方やる、もしくはループ内printfなら何度も叩いて多段書き込み。

👀 仮説①:%pで情報漏洩

スタック内容を片っ端からアドレスとして表示。stack canaryやlibcベースを抜く。

✍️ 仮説②:%n で任意書込

%200x%7$nのような形で指定アドレスに任意の数を書き込み。

🎯 仮説③:GOT Overwrite

動的リンクのGOTテーブルを書き換え、関数呼出を任意アドレスへリダイレクト。

🐦 仮説④:Tcache/Heap書込との連携

%nでheap内のメタデータを書き換えてheap攻撃へ橋渡し

Format Stringは「読み書き両方できるプリミティブ」として最強なんだね💡

🔬 検証フェーズ:どうやって確かめる?

pwntoolsのfmtstr_payload()がペイロード生成を完全自動化してくれる🧪

%p 連打でスタック内容を漏らし、自分が送った文字列が何番目のオフセットに来るかを特定(”AAAA” + %p×N)。そこを足がかりに %hn(2バイト書き込み)を組み立てる。

# 自分の入力位置を特定
echo "AAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p" | ./chall
# 出力に 0x41414141 が出た位置がオフセット

# pwntoolsの fmtstr_payload で自動生成
from pwn import *
offset = 6
writer = {elf.got['exit']: elf.symbols['win']}
payload = fmtstr_payload(offset, writer)
p.sendline(payload)

GOT書換の標的はprintfexit。次に呼ばれる関数をsystemに置き換える💡

⚔️ 攻撃フェーズ:実際の手口

FSB/GOT攻撃の代表3パターン。

攻撃の典型:①%pでleak(PIE/libc base)、②%nでGOT書き換え、③write_what_where(任意アドレスに任意値)。pwntoolsのfmtstr_payloadは2バイトずつ書く最適化を自動でやってくれる。

# 手書きで例
# GOT[exit]=0x404020 を 0x401234 に書き換え
# %1234x%6$hn でオフセット6に2バイト書き込む
# 上位2バイト用にもう一発用意して2回に分ける

# 安全な使い方が pwntools fmtstr_payload
from pwn import *
payload = fmtstr_payload(6, {0x404020: 0x401234})
print(payload)

近年のFORTIFY_SOURCEは %n を引数固定の場合のみ許可するなど、Format Stringへの締め付けが進んでいます。CTFでは依然頻出ジャンル📜

① canary leak via %p

スタック上のcanary値を %p で表示させ、後続のBoFをcanary込みで成立させる。

② GOT書換でsystemへ

printf@gotsystemのアドレスに書き換え、次のprintf("/bin/sh")system(“/bin/sh”)に化ける。

③ Full RELRO突破

GOT書換できない場合はheapの関数ポインタやvtableを狙う。

🛡️ 防御フェーズ:どう守る?

FSB/GOT対策の3点。

実装側は「printfにユーザ入力を直接渡さない」が絶対ルール。lint・SAST・コンパイラ警告(-Wformat-security)で検出可能。

  • printf(“%s”, user_input) 形式で固定フォーマット
  • -Wformat-securityを必ず有効
  • FORTIFY_SOURCE=2
  • Full RELROでGOT書き換え封じ
  • CFIで関数ポインタ呼び出しを保護
  • 静的解析(CodeQL / Semgrep)でprintfユーザ入力検出
🚫 ユーザー入力をformat引数にしない

常にprintf("%s", input)のようにformat固定で書く。これだけでFSBは死ぬ。

🛡️ Full RELRO

リンク時に-Wl,-z,relro,-z,nowGOTを読み取り専用化。実行時の書換不能。

🔍 静的解析でFSB検出

gcc -Wformat-securityCodeQL/Semgrepprintf(input)パターンを検出。

「フォーマット文字列にユーザー入力を入れない」──これだけが教訓💪

🧪 pwn検証用のLinuxラボ

Pwnやエクスプロイトの練習は、必ず自分の検証環境で。💻 ConoHa VPSならLinuxをワンクリックで立てて、gdb-pedaやpwntoolsを仕込んでそのままトレーニングできます。

PR / 広告

ConoHa VPS

※ 上記は他社サービスへのリンクです。購入は各自でご判断ください。

⚠️ よくある落とし穴

  1. printf(user_input) を「ログ出力だけだから安全」と思う
  2. %nが効くケースで Full RELRO 適用を忘れる
  3. pwntools fmtstr_payload のoffsetを誤指定
  4. %hn / %hhn / %n の書き込みサイズ違いを混同
  5. PIE時にleakを忘れて固定アドレスで書く
  6. ループ外のprintfでも何度もattackできる前提を忘れる

🧰 ツール早見表

ツール用途備考
pwntools fmtstr_payloadFSB自動生成オフセット指定で完成
ROPgadgetgadget収集FSBと併用
gdb + pwndbgデバッグ書き込み結果の確認
radare2 / Ghidra逆コンパイルprintf呼び出しの確認
CodeQL / Semgrep静的解析CIで検出

🎓 本気で学びたい人へ

ローレベルやOSレベルの許可を、仕事として深く学びたい方へ。🎓 ササエルはインフラとセキュリティの両方を学べるスクールです。

PR / 広告

ササエル

📚 もっと深く学びたい人へ

実際に手を動かして攻撃手法を体で覚えたいなら『7日間でハッキングをはじめる本 TryHackMe』がおすすめ📚

📚 次に読みたい

✍️ 学んだことを発信する

検証ノートをブログで公開したい方は、高速で安価なConoHa WINGが使いやすいです。

PR / 広告

ConoHa WING

⚖️ 大事なお約束

必ず守ってね

この記事の手法は、必ず自分の環境か、許可されたCTF・脆弱性報奨金プログラム(HackerOne、Bugcrowd等)で試してください。他人のサービスに無断で攻撃を仕掛けるのは不正アクセス禁止法違反、立派な犯罪です。学んだ知識は守る側で活かしましょう🤝

この記事は合法な学習・防御目的での解説です。許可のないシステムへの攻撃は犯罪になります(不正アクセス禁止法ほか)。検証は必ず自分が管理する環境・CTF・公式ハンズオンで行ってください🙏

記事URLをコピーしました