【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も候補

%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書換の標的はprintfやexit。次に呼ばれる関数を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値を %p で表示させ、後続のBoFをcanary込みで成立させる。
printf@gotをsystemのアドレスに書き換え、次のprintf("/bin/sh")がsystem(“/bin/sh”)に化ける。
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ユーザ入力検出
常にprintf("%s", input)のようにformat固定で書く。これだけでFSBは死ぬ。
リンク時に-Wl,-z,relro,-z,nowでGOTを読み取り専用化。実行時の書換不能。
gcc -Wformat-securityやCodeQL/Semgrepでprintf(input)パターンを検出。

「フォーマット文字列にユーザー入力を入れない」──これだけが教訓💪
🧪 pwn検証用のLinuxラボ
Pwnやエクスプロイトの練習は、必ず自分の検証環境で。💻 ConoHa VPSならLinuxをワンクリックで立てて、gdb-pedaやpwntoolsを仕込んでそのままトレーニングできます。
※ 上記は他社サービスへのリンクです。購入は各自でご判断ください。
⚠️ よくある落とし穴
printf(user_input)を「ログ出力だけだから安全」と思う- %nが効くケースで Full RELRO 適用を忘れる
- pwntools fmtstr_payload のoffsetを誤指定
- %hn / %hhn / %n の書き込みサイズ違いを混同
- PIE時にleakを忘れて固定アドレスで書く
- ループ外のprintfでも何度もattackできる前提を忘れる
🧰 ツール早見表
| ツール | 用途 | 備考 |
|---|---|---|
| pwntools fmtstr_payload | FSB自動生成 | オフセット指定で完成 |
| ROPgadget | gadget収集 | FSBと併用 |
| gdb + pwndbg | デバッグ | 書き込み結果の確認 |
| radare2 / Ghidra | 逆コンパイル | printf呼び出しの確認 |
| CodeQL / Semgrep | 静的解析 | CIで検出 |
🎓 本気で学びたい人へ
ローレベルやOSレベルの許可を、仕事として深く学びたい方へ。🎓 ササエルはインフラとセキュリティの両方を学べるスクールです。
📚 もっと深く学びたい人へ
実際に手を動かして攻撃手法を体で覚えたいなら『7日間でハッキングをはじめる本 TryHackMe』がおすすめ📚
📚 次に読みたい
- ROP入門編|CTF思考フレームワーク #60
- Heap入門編|CTF思考フレームワーク #62
- スタックBoF基礎編|CTF思考フレームワーク #59
- Heap応用編(UAF/DF)|CTF思考フレームワーク #63
✍️ 学んだことを発信する
検証ノートをブログで公開したい方は、高速で安価なConoHa WINGが使いやすいです。
⚖️ 大事なお約束
この記事の手法は、必ず自分の環境か、許可されたCTF・脆弱性報奨金プログラム(HackerOne、Bugcrowd等)で試してください。他人のサービスに無断で攻撃を仕掛けるのは不正アクセス禁止法違反、立派な犯罪です。学んだ知識は守る側で活かしましょう🤝
この記事は合法な学習・防御目的での解説です。許可のないシステムへの攻撃は犯罪になります(不正アクセス禁止法ほか)。検証は必ず自分が管理する環境・CTF・公式ハンズオンで行ってください🙏



