【Pwn総まとめ編】実戦シナリオとチートシートで完走する|CTF思考フレームワーク #66
こんにちは、アンペンです!
今回はPwn章の最終回、実戦シナリオとチートシートでこれまでの全技術を統合します。#58のアセンブリ基礎から、#59 BoF、#60 ROP、#61 FSB/GOT、#62-#64 Heap三部作、#65 保護機構と続いた一連の知識を、『CTF本番で問題を開いた瞬間にどう動くか』の定型ルーチンに落とし込みます。
Pwn問題は『時間勝負』。バイナリの種類・保護状況・配布ファイル(libc.so.6の同梱有無)から、最初の3分で攻略方針が決まれば後は実装作業に集中できます。チートシートをそばに置けば、本番でも手が止まりません。
総まとめ回でいちばん伝えたいのは、『Pwnは才能ではなく“型”だ』ということです。問題を開いて固まってしまうのは、知識が足りないからではなく、“最初に何を見るか”の手順が決まっていないから。逆に、見る順番さえ体に入れば、初見の問題でも淡々と前に進めます。今日はその“型”を、チートシートとテンプレという形で持ち帰ってください。

Pwn問題を開いたら、まず何を見ればいい?

(1)checksec、(2)file/strings、(3)Ghidra/IDAでmain読み、(4)libcバージョン特定、(5)脆弱性候補リストアップ。この5ステップで作戦が決まる。あとは脆弱性タイプ別の決め技を当てはめるだけ。
Pwn問題攻略の定型ルーチン5ステップ:(1)checksecで保護状況、(2)file/strings/Ghidraで全体把握、(3)libc-databaseでlibc特定、(4)脆弱性タイプ分類(BoF/UAF/FSB/Race/整数オーバーフロー等)、(5)タイプ別の決め技で突破口決定。脆弱性タイプ別『決め技マップ』を覚えれば、本番でも手が止まりません。pwntoolsテンプレを『リーク→計算→送信』の3段で用意しておけば、毎回ゼロから書かずに済みます。
この記事で分かること
- Pwn問題攻略の定型5ステップ
- 脆弱性タイプ別の決め技マップ(BoF/UAF/FSB/Race/整数)
- pwntools テンプレートの3段構造
- 本番で時間短縮のコツ
- Pwn全章の振り返り(#58〜#66)
📖 はじめてのWebセキュリティ #66|Pwn総まとめ編
実戦シナリオとチートシートで完走する。 シリーズ一覧を見る →
⚠️ 大事なお約束
本番システムへのexploitは違法。CTF・自作バイナリのみで確認してください。
Pwn問題の定型ルーチン5ステップ
- ①checksec:4項目(Canary/NX/PIE/RELRO)の状態 → 必要なリーク経路と防御の壁が見える(#65参照)
- ②file/strings/Ghidra:動的リンクか静的か、strippedか、main関数の所在、補助関数(
win等)の存在、文字列リソースから機能推定 - ③libc特定:配布されたlibc.so.6があればそれを直接使う。なければリーク値1〜2個から
libc-databaseで照合(./find puts 0xXXXXXX) - ④脆弱性分類:main関数を読みながらBoF/UAF/Double Free/FSB/Off-by-one/Race/整数オーバーフローのどれかを当てる。複数共存もある
- ⑤突破口決定:分類に応じてリーク経路・計算式・ペイロード構造を決定。あとは実装するだけ
この5ステップは、料理でいう“下ごしらえ”のようなものです。いきなり炒め始める(exploitを書き始める)前に、まず食材を確認し(checksec)、レシピを読み(Ghidra)、調味料をそろえる(libc特定)。下ごしらえが済んでいれば、あとの調理は迷いません。逆にここを飛ばすと、途中で『あれが無い、これが違う』と手が止まる。急がば回れ、の典型ですね。

対戦ゲームでは『相手キャラ(=脆弱性タイプ)』が決まれば、こちらが使う『必殺技(=攻撃手法)』はほぼ固定。BoFなら『ret2win or ret2libc』、UAFなら『vtable hijack』、FSBなら『%nでGOT書換 or libcリーク』、ヒープなら『tcache poisoning → __free_hook』。チートシートを手元に置いておけば、CTF本番でも手が止まらず、残り時間を実装と微調整に集中できます。
ここで覚える用語:pwntoolsテンプレート
pwntools(from pwn import *)を使う典型コード骨格。elf = ELF('./vuln')でバイナリ読込、libc = ELF('./libc.so.6')でlibc読込、p = process('./vuln') if args.LOCAL else remote(host, port)でローカル/リモート切替、p.recvuntil()/p.sendline()で対話、p.interactive()でシェル獲得後の手動操作。1回作って使い回せば、問題ごとにペイロード組立部分だけ書き換えれば済みます。
脆弱性タイプ別 決め技チートシート
- BoF (canary off): ret2win(win関数あれば)→ret2libc(なければ)。x64はret-gadgetでmovaps対策
- BoF (canary on): canary leak経路(FSB/部分書込/fork brute-force)を探す→canaryを再現してret書換
- UAF / Double Free: 同サイズ malloc/free操作で再利用誘導→vtable hijack or tcache poisoning→
__free_hook(glibc < 2.34) or_IO_FILE系 - FSB:
%p列挙でstack位置特定→libc/canaryリーク→fmtstr_payload(off, {got: target})でGOT書換 - Off-by-one: 上位/下位1バイト改竄→隣接チャンクのsize書換→overlapping chunksを作成→好きな場所にmalloc誘導
- Race condition: マルチスレッド/シグナルで競合→TOCTOU(check-time-of-use-time)で前提を覆す
- 整数オーバーフロー: 巨大なsize計算で結果が小さくなる→小さいbufに大量書込が成立→BoFやheap-bofに連鎖
- Type confusion(C++/V8等): 型キャストエラーで構造体メンバが別解釈→任意R/Wプリミティブへ
このチートシートは、丸暗記するより“引ける状態”にしておくのが正解です。本番でやることは、①で見た保護状況と④で分けた脆弱性タイプを、この表に突き合わせるだけ。『canary無しBoF=ret2win/ret2libc』『UAF=vtable乗っ取り』と、対応が一目で引ければ、悩む時間がゼロになります。最初は表を見ながらでOK。何度も引くうちに、自然と手が覚えていきます。

pwntoolsテンプレート(3段構造)
- 初期化部:
context.binary = elf = ELF('./vuln')/libc = ELF('./libc.so.6')/p = process(...) if args.LOCAL else remote(host, port) - リーク部:
p.recvuntil(b'> ')→p.sendline(payload_leak)→leak = u64(p.recv(8)) - 計算部:
libc.address = leak - libc.symbols['puts']→log.info(f'libc base: {hex(libc.address)}') - 送信部: 2回目のpayload送信 →
p.interactive()
テンプレ運用のコツは、『毎回ゼロから書かない』こと。リーク→計算→送信の3段は、どの問題でもほぼ同じ骨格です。だから一度きれいに書いたものを template.py として保存し、問題ごとに“ペイロードを組む部分だけ”差し替える。これだけで、つまらないタイプミスや初期化忘れが激減し、頭を本質(脆弱性をどう突くか)に集中させられます。

CTFでやってみよう:テンプレで2問連続攻略
異なる脆弱性タイプを連続で解いてテンプレを習熟する
目的は『1問目で時間使っても、2問目以降は型化されて早くなる』を体感することです。テンプレをtemplate.pyとして保存しておけば、毎回コピーから始められます。
- pwntoolsテンプレを
template.pyに貼り付け、ベース確保 - 1問目: 単純なBoFバイナリ(canary無し / PIE無し) → ret2libcで攻略。リーク→計算→送信の3段を実装
- 2問目: menu型UAF + tcache poisoning → vtable hijackで攻略
- 解いたら『解き方の流れ』を1行メモ(『libcリーク→ROP』『UAF→tcache→hook』等)
- 同じテンプレで別ジャンルもいくつか(FSBバイナリ、Off-by-one等)
- 気付いた小ネタ(x64アライメント、libc文字列の探し方等)を
cheatsheet.mdに追記 - 余裕があれば pwn.college / picoCTF / overthewire / HackTheBox Starting Pointで実戦練習
- 定期的にCTF(CTFtime.orgでスケジュール確認)に参加し、本番で時間内に解く経験を積む
守る側:『すべてON+メモリ安全言語+監視』の総まとめ
- 新規プロジェクトはRust/Go等のメモリ安全言語を選ぶ ── これだけで多くの脆弱性が構造的に排除される
- C/C++を続けるなら全保護機構ON(
-fstack-protector-strong -fPIE -pie -Wl,-z,relro -Wl,-z,now -D_FORTIFY_SOURCE=2 -fcf-protection=full) - 境界チェックなし関数(
gets/strcpy/sprintf/strcat)を禁止。コードレビューと静的解析(cppcheck/clang-tidy)で検出 - 開発・CIにASan+UBSan+libFuzzer/AFL++を組込、未知バグを継続的に発見
- CIでchecksecを成果物に対して回し、退化を即検出。1項目でも欠ければビルド失敗
- 本番にはWAF/EDR+クラッシュ集約監視(同一プロセスの繰り返しクラッシュは自動exploitの兆候)
- 定期的なレッドチーム演習で実戦的に弱点を洗う、脅威モデリングを年1回更新
- サプライチェーン(依存ライブラリ)も常に最新化、CVEは即時パッチ適用

『定型ルーチン+チートシート』でPwnが楽しめるようになるね!

これでPwn章完結。次回からは新章OSINT編。公開情報だけで世界を読み解く思考を扱うよ。
まとめ:『定型5ステップ+チートシート+テンプレ』で完走
- 定型ルーチン:checksec→静的解析→libc特定→分類→決め技の5ステップ
- 脆弱性タイプ別の決め技マップを頭に入れる
- pwntoolsテンプレで『リーク→計算→送信』を高速化
- 守りは全保護ON+メモリ安全言語+Fuzz+監視+レッドチームの多層
- 本番経験はpicoCTF/pwn.college/HackTheBoxで継続的に
ここまで来たあなたは、もうアセンブリの読み方からヒープの最新攻撃、そして守りの全保護機構までを一周しました。#58から始まったPwnの旅は、これでひと区切りです。大切なのは“完璧に覚える”ことではなく、“型と地図を持って、迷わず手を動かせる”こと。あとは実戦で場数を踏むほど、この型は鋭くなっていきます。お疲れさまでした——Pwn章、完走です!
次回からはOSINT章。公開情報だけで世界を読み解く思考を扱います。Pwnとは違うベクトルですが、攻撃者の発想を理解するためには必須です。
次に読みたい記事
参考資料
次に読みたい記事
未経験からセキュリティ・インフラエンジニアを目指すロードマップ
CTFの先のキャリア。インフラ基礎固めから就職までの4ステップ。
