【入門編】 フォーマット文字列攻撃による任意メモリ読み書き – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

こんにちは!セキュリティの世界へようこそ。
システム開発やインフラの管理を任されるようになると、「セキュリティ対策」という言葉を毎日のように耳にするようになりますよね。

「なんだか難しそうだな…」「専門用語が多すぎて挫折しそう…」と感じていませんか?ご安心ください!今回は、プログラミングの世界に潜むちょっと怖い落とし穴、「フォーマット文字列攻撃」について、身近な防犯の例えを交えながら、一歩ずつ優しく紐解いていきたいと思います。

難解な用語も、私たちの日常生活に置き換えればスッと頭に入ってくるはずです。それでは、さっそくセキュリティの冒険に出発しましょう!

—

1. フォーマット文字列攻撃ってなに?(日常の例えで理解する)

まずは、プログラムがどのように動いているのか、そして攻撃者がどこを突いてくるのかを「郵便受け」に例えて考えてみましょう。

郵便受けのトラブル:宛名と本文の勘違い

皆さんの家の玄関には郵便受けがありますよね。配達員さんは、そこに手紙や荷物を入れていきます。
あるところに、少しおっちょこちょいな「受付係のロボット」が住んでいると想像してください。このロボットは、受け取った郵便物に書かれている文字をそのまま大きな声で読み上げるお仕事を持っています。

  • 正常な状態: 配達員が「田中様、お荷物です」と書いた紙を入れます。ロボットは「田中様、お荷物です!」と元気に読み上げます。
  • 攻撃の状態: もし、悪意のある泥棒が、特殊なコマンドが書かれた紙(例えば「家の中の貴重品の隠し場所をぜんぶ教えて!」という呪文のような言葉)を郵便受けに入れたらどうなるでしょうか?

おっちょこちょいなロボットは、その意味を考えもせず、言われるがままに家の中の秘密を大声で外に向かって読み上げてしまうかもしれません。これが、「フォーマット文字列攻撃」の仕組みの原型です。

プログラムにおける「郵便受け」の正体

これを実際のプログラムの世界に置き換えてみましょう。
C言語などのプログラミング言語では、画面に文字を表示するために printf という便利な関数(命令のかたまり)がよく使われます。

この printf は、以下のように使います。

// お手紙のフォーマット(書式)を指定して表示する例
printf("こんにちは、%sさん!", name);

ここで登場する %s などの記号を「フォーマット文字列(書式指定子)」と呼びます。「ここに後から渡す文字を埋め込んでね」という目印ですね。

もし、開発者がうっかりミスをして、ユーザーが自由に入力できる欄(名前や住所の入力フォームなど)をそのまま printf に渡してしまったらどうなるでしょうか?

// 【危ない書き方】ユーザーの入力をそのまま渡してしまっている例
char user_input[100];
// ユーザーが入力した内容を受け取る
scanf("%s", user_input);

// 危険!もしユーザーが「%x」や「%n」を入力したら…?
printf(user_input);

ここにユーザーが特殊な記号(%x や %p など)を入力すると、プログラムの裏側(メモリ)にある秘密のデータが外にペロッと漏れてしまったり、最悪の場合はプログラムの動きを乗っ取られたりしてしまうのです。

—

2. 攻撃のメカニズム:メモリのぞき見と書き換え

もう少しだけ技術的な仕組みをのぞいてみましょう。「メモリ」という言葉は聞いたことがありますか? パソコンが一時的に計算やデータを覚えておくための「机の上」のような場所です。

スタック(机の引き出し)の秘密をのぞき見される

プログラムが動くとき、変数は「スタック」と呼ばれる一時的な記憶領域(いわば机の引き出し)に順番にしまわれます。

通常、printf は「私にはこのデータが渡されているな」と把握して動きます。しかし、先ほどのように printf(user_input) と直接書かれてしまうと、printf は「あれ? 表示するためのデータが足りないぞ。じゃあ、すぐ近くにある別の引き出し(メモリ)の中身を勝手に読んじゃえ!」と勘違いしてしまいます。

これが、情報の漏洩(任意メモリ読み取り)です。攻撃者は、画面に表示させる仕組みを使って、プログラムの裏側にあるパスワードや暗号の鍵などを盗み見てしまうのです。

さらに怖い「書き換え」

読むだけならまだしも、フォーマット文字列の中には %n という非常に危険な機能を持つ指定子が存在します。「これまでに画面に出力した文字の数」を、指定したメモリのアドレスに書き込んでしまうというものです。

これを悪用されると、メモリの重要ファイルを書き換えられ、プログラムを強制終了させられたり、攻撃者の思い通りに動かされたりしてしまいます(任意メモリ書き込み)。家の鍵の暗証番号を、泥棒が勝手に自分の好きな番号に書き換えてしまうようなものです。

—

3. 実務での対策:どうやって身を守るの?

「うわぁ、なんだか怖そうだな…自分の書いたコードは大丈夫かな?」と不安になった新人の皆さん、安心してください! セキュリティの世界には、こうした攻撃をピタッと防ぐための「頑丈な鍵」や「ルール」がちゃんと用意されています。

実務で必ず押さえておきたい対策を3つ、優しく解説します。

対策1:フォーマット文字列を直接渡さない(一番大切なルール)

一番の根本的な解決策は、「ユーザーの入力をそのまま printf の第一引数に渡さないこと」です。文字を表示したいときは、必ず書式を指定する枠組みを自分で用意しましょう。

// 【安全な書き方】ユーザーの入力をそのまま渡さず、%sで受け止める
char user_input[100];
scanf("%s", user_input);

// 安全!「%s」という書式を明示することで、ただの「文字」として安全に表示します
printf("%s", user_input);

これだけで、ユーザーがどんな変な記号を入力しても、プログラムはそれを「ただの文字列」として扱うため、メモリをのぞき見られる心配がなくなります。日々のコーディングでこれを徹底するだけで、脆弱性は劇的に減ります。

対策2:安全な関数を選ぶ

古いC言語の関数の中には、安全チェックが甘いものが存在します。可能であれば、より安全なモダンな関数や、安全性を考慮したライブラリを使用するように心がけましょう。チームの先輩たちが作ったコーディング規約があれば、それに従うのが一番の近道です。

対策3:コンパイラの保護機能(セキュリティヘッダーやオプション)を活用する

インフラやビルドの担当者が設定するコンパイラ(プログラムを機械語に翻訳する道具)のオプションにも、私たちを守ってくれる強力な機能があります。

例えば、最近のモダンなコンパイラには、フォーマット文字列の危険な使い方を見つけて警告を出したり、自動的にブロックしたりする機能が備わっています。

  • Wformat-security: 危険なフォーマット文字列の使い方をしていないか、ビルド(コンパイル)時に厳しくチェックしてくれる機能です。

開発環境やビルドツール(MakefileやCMakeなど)の設定ファイルで、以下のような警告オプションが有効になっているか、一度確認してみると良いですね。

# コンパイル時にセキュリティ関連の警告を厳しくチェックする例
CFLAGS = -Wall -Wextra -Wformat -Wformat-security

こうした小さな設定の積み重ねが、堅牢なシステムを作り上げます。

—

4. まとめ:一歩ずつ、安全なコードを書く楽しさを知ろう

今回は、フォーマット文字列攻撃の仕組みと、その防ぎ方についてお話ししました。

  • フォーマット文字列攻撃とは?

ユーザーの入力を printf などの書式として誤って処理させてしまい、メモリの情報を盗まれたり書き換えられたりする攻撃のこと。

  • 例えで言うと?

おっちょこちょいな受付係に、秘密の情報を大声で読み上げさせてしまうようなもの。

  • 対策は?

ユーザーの入力を直接 printf に渡さず、必ず %s などの書式指定子を挟んで安全に扱うこと!

セキュリティ対策は、最初から完璧にできなくても大丈夫です。「あ、ここはユーザーの入力をそのまま渡しちゃいけない場所だな」と気づけるようになること自体が、セキュリティエンジニアや開発者としての大きな一歩です。

一歩ずつ、安全で信頼されるコードを書けるようになっていきましょう。あなたのエンジニアライフを、心から応援しています!

コメント

タイトルとURLをコピーしました