bin_parser_to_be_tested.py 技術ドキュメント
プログラムの動作
bin_parser_to_be_tested.py は、未知のバイナリファイルの探索を支援するためのPythonスクリプトです。このプログラムの主な目的は、バイナリデータの中から特定の数値(整数や浮動小数点数)や文字列のパターンを検出し、その周辺のデータを調査することによって、ファイルの構造を解明する手助けをすることです。
主な機能は以下の通りです。
値の検索 (
search): 指定されたデータ型、値、エンディアン、および検索ステップ(アライメント)に基づいて、バイナリファイル内で数値または文字列を検索します。浮動小数点数の比較には許容誤差を設定できます。文字列のリストアップ (
strings): バイナリデータの中から表示可能なASCII文字列を抽出し、指定された最小長以上のものをリストアップします。特定のサブ文字列を含むものでフィルタリングすることも可能です。オフセットの検査 (
inspect): 特定のオフセットに存在するバイナリデータを、複数の異なるデータ型(例:float32,int16,strなど)として解釈し、それぞれの値を表示します。これにより、同じバイト列がどのように異なるデータとして読み取られるかを確認できます。配列の読み込み (
array): 指定されたオフセットから、連続する数値型のデータを配列として読み込み、各要素のオフセットと値を表示します。要素間のステップ(ストライド)も指定可能です。データのダンプ (
dump): 指定されたオフセットから指定された長さのバイナリデータを、16進数とASCII文字の両方で表示するヘックスダンプを行います。
このプログラムは、ファイルフォーマットが不明なデータセットや、逆アセンブルされたバイナリコードのスライスを解析する際に特に有用です。
原理
bin_parser_to_be_tested.py は、Pythonの標準ライブラリである struct モジュールと正規表現 (re) を核として、バイナリデータの読み取り、解析、および検索を行います。
バイナリデータの読み込み:
load_bin_file関数は、指定されたファイルパスからバイナリデータをバイト列 (bytesオブジェクト) としてメモリに読み込みます。これはopen(file_path, "rb")を使用して行われます。エンディアンの指定:
endian_prefix関数は、ユーザーがlittle(リトルエンディアン) またはbig(ビッグエンディアン) のどちらを指定したかに応じて、structモジュールで使用するフォーマット文字列のプレフィックスを決定します。リトルエンディアンの場合は<、ビッグエンディアンの場合は>が使われます。データ型のマッピング:
TYPE_MAPディクショナリは、サポートされる各データ型(例:float32,int16,uint8など)に対して、structモジュールが認識するフォーマット文字と、その型のバイトサイズをマッピングします。例えば、"float32"は("f", 4)に対応し、これは単精度浮動小数点数を意味します。バイナリデータの解釈 (
get_data,read_array):get_data関数は、指定されたオフセットから、指定されたサイズとデータ型、エンディアンに従ってバイナリデータを読み込み、Pythonの対応する型(int,float,str)に変換します。これはstruct.unpack_from(prefix + fmt, bin_data, offset)[0]を使用して実現されます。 文字列の読み込みに関しては、ascii_prefix(表示可能なASCII文字のみを読み込む)、c_string(NULL文字で終端される文字列を読み込む)、fixed(固定長で文字列を読み込む) の3つのモードがあります。read_array関数は、get_dataを繰り返し呼び出すことで、特定の型の配列データを連続して読み取ります。step引数により、要素間のバイトオフセット(ストライド)を制御できます。値の検索 (
search_data):数値の検索:
search_data関数は、指定されたオフセット範囲内で、stepバイトごとにデータを読み込み、ターゲット値と比較します。浮動小数点数の比較には、numeric_match関数が絶対許容誤差 (atol) および相対許容誤差 (rtol) を用いて行われます。数式は以下の通りです。 $\(|val - target| \le (atol + rtol \cdot |target|)\)\( ここで \)val\( はバイナリから読み取った値、\)target$ は検索対象の値です。文字列の検索:
search_raw_string関数は、Pythonのバイト列オブジェクトのfind()メソッドを使用して、エンコードされたターゲット文字列(バイト列)を効率的に検索します。
ASCII文字列の抽出 (
find_ascii_strings):find_ascii_strings関数は、正規表現rb"[\x20-\x7E]{%d,}" % min_lengthを使用して、ASCIIの表示可能文字(スペース0x20からチルダ0x7Eまで)がmin_length以上連続するパターンを検索します。re.finditerを使用してすべてのマッチを見つけ、そのオフセットとデコードされた文字列を返します。ヘックスダンプ (
hex_dump):hex_dump関数は、バイナリデータを読みやすくするために、指定されたオフセットと長さのデータを、16進数表記とASCII文字表記の両方で整形して出力します。これは一般的なバイナリエディタやデバッガの機能と同様です。
必要な非標準ライブラリとインストール方法
bin_parser_to_be_tested.py は、Pythonの標準ライブラリのみを使用しており、argparse, math, os, re, struct, sys, typing 以外の追加の非標準ライブラリは必要ありません。
したがって、特別なインストール手順は不要です。Pythonがインストールされていれば、そのまま実行できます。
必要な入力ファイル
プログラムは、バイナリファイルを読み込むことを想定しています。 期待されるファイル形式やデータ構造は特に指定されません。プログラムは、任意のバイナリデータを生のバイト列として読み込み、ユーザーの指定に基づいてその内容を解析しようとします。
入力ファイルのパスは、コマンドライン引数として常に必要となります。
例: test.raw のような任意のバイナリファイル。
生成される出力ファイル
bin_parser_to_be_tested.py は、ファイルシステム上に新しいファイルを生成することはありません。すべての処理結果は標準出力 (stdout) にテキスト形式で表示されます。
ユーザーは、シェルのリダイレクト機能(例: > output.txt)を使用して、標準出力をファイルに保存することができます。
コマンドラインでの使用例 (Usage)
bin_parser_to_be_tested.py は、サブコマンド形式のコマンドラインインターフェースを提供します。基本的な実行方法は以下の通りです。
python bin_parser_to_be_tested.py <command> [arguments]
利用可能な主なコマンドと引数は以下の通りです。
search コマンド
バイナリファイル内で数値または文字列を検索します。
python bin_parser_to_be_tested.py search <infile> --type <type> --value <value> [options]
<infile>: 入力バイナリファイルのパス。--type <type>: 検索するデータの型 (例:float32,int32,strなど)。--value <value>: 検索する値。整数は0xプレフィックスで16進数も指定可能。--endian <little|big>: エンディアンの指定 (デフォルト:little)。--step <bytes>: 検索ステップバイト数 (数値型はデフォルトで型サイズ、文字列は1)。--tolerance <float>: 浮動小数点数比較の絶対許容誤差 (デフォルト:1e-6)。--rtol <float>: 浮動小数点数比較の相対許容誤差 (デフォルト:0.0)。--dump <bytes>: 各ヒットの後にダンプするバイト数 (デフォルト:64)。--dump-before <bytes>: 各ヒットの前に表示するバイト数 (デフォルト:16)。--max-hits <int>: 表示する最大ヒット数 (デフォルト:20)。
strings コマンド
ファイルから表示可能なASCII文字列をリストアップします。
python bin_parser_to_be_tested.py strings <infile> [options]
<infile>: 入力バイナリファイルのパス。--min-length <int>: 検索する文字列の最小長 (デフォルト:4)。--contains <substring>: 特定のサブ文字列を含む文字列のみをフィルタリングします。--max-hits <int>: 表示する最大ヒット数 (デフォルト:200)。
inspect コマンド
特定のオフセットのデータを複数の型で解釈します。
python bin_parser_to_be_tested.py inspect <infile> --offset <offset> [options]
<infile>: 入力バイナリファイルのパス。--offset <offset>: データを読み込む開始オフセット (16進数は0xプレフィックスで指定可能)。--types <type1,type2,...>: 解釈するデータ型のカンマ区切りリスト (デフォルト:float32,int16,uint16,str)。--sizes <size1,size2,...>:--typesに対応するサイズのカンマ区切りリスト。指定しない場合、文字列型は--str-sizeを、その他の型はデフォルトサイズを使用。--str-size <bytes>: 文字列型解釈時のデフォルトサイズ (デフォルト:32)。--str-mode <ascii_prefix|c_string|fixed>: 文字列の解釈モード (デフォルト:ascii_prefix)。--endian <little|big>: エンディアンの指定 (デフォルト:little)。
array コマンド
特定のオフセットから数値型の配列を読み込みます。
python bin_parser_to_be_tested.py array <infile> --offset <offset> --type <type> --count <count> [options]
<infile>: 入力バイナリファイルのパス。--offset <offset>: 配列の開始オフセット。--type <type>: 配列要素のデータ型。--count <int>: 読み込む要素の数。--step <bytes>: 要素間のバイトオフセット(ストライド) (デフォルト: 型サイズ)。--endian <little|big>: エンディアンの指定 (デフォルト:little)。
dump コマンド
特定のオフセットからデータをヘックスダンプします。
python bin_parser_to_be_tested.py dump <infile> --offset <offset> [options]
<infile>: 入力バイナリファイルのパス。--offset <offset>: ダンプを開始するオフセット。--length <bytes>: ダンプするバイト数 (デフォルト:128)。--width <int>: 各行に表示するバイト数 (デフォルト:16)。
コマンドラインでの具体的な使用例
ここでは、test.raw という架空のバイナリファイルを前提とした実行例とその説明を示します。
例1: 浮動小数点数 120.0 をリトルエンディアンで4バイトアライメント検索
python bin_parser_to_be_tested.py search test.raw --type float32 --value 120.0 --endian little --step 4 --tolerance 1e-3
説明:
test.raw ファイル内で、単精度浮動小数点数 (float32) の 120.0 をリトルエンディアンで検索します。検索は4バイトステップ(典型的な float32 のアライメント)で行われ、許容誤差は \(10^{-3}\) です。ヒットしたオフセットと、その周辺64バイト(ヒット地点の16バイト前からの)のヘックスダンプが表示されます。
実行結果の例:
Found 1 match(es).
offset 0x00000100 (256)
000000F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000100 00 00 F0 42 00 00 00 00 00 00 00 00 00 00 00 00 |...B............|
00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
(0x00000100 のオフセットに float32 の 120.0 (リトルエンディアンで 00 00 F0 42) が見つかったことを示します。)
例2: 同じ値をフルブルートフォーススキャン
python bin_parser_to_be_tested.py search test.raw --type float32 --value 120.0 --step 1
説明:
test.raw ファイル内で、単精度浮動小数点数 120.0 をリトルエンディアンで検索します。今度は1バイトステップ(フルブルートフォーススキャン)で行われます。これにより、アライメントされていない可能性のあるデータも検出できますが、処理時間は長くなり、誤検出も増える可能性があります。ヒットしたオフセットと周辺のダンプが表示されます。
実行結果の例:
Found 2 match(es).
offset 0x00000100 (256)
000000F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000100 00 00 F0 42 00 00 00 00 00 00 00 00 00 00 00 00 |...B............|
00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
offset 0x00000201 (513)
000001F1 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000201 00 00 F0 42 00 00 00 00 00 00 00 00 00 00 00 00 |...B............|
00000211 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000221 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
(0x00000100 と 0x00000201 の2つのオフセットでヒットが見つかり、2つ目のオフセットは4バイトアライメントではないことを示します。)
例3: テキストフラグメント DEVICE を直接検索
python bin_parser_to_be_tested.py search test.raw --type str --value DEVICE
説明:
test.raw ファイル内で、文字列 "DEVICE" を検索します。文字列検索はデフォルトで1バイトステップで行われます。ヒットしたオフセットと、その周辺のヘックスダンプが表示されます。
実行結果の例:
Found 1 match(es).
offset 0x00000040 (64)
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000040 44 45 56 49 43 45 00 00 00 00 00 00 00 00 00 00 |DEVICE..........|
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
(0x00000040 のオフセットに "DEVICE" という文字列が見つかったことを示します。)
例4: 表示可能なASCII文字列をリストアップ
python bin_parser_to_be_tested.py strings test.raw --min-length 4
説明:
test.raw ファイルから、長さが4文字以上の表示可能なASCII文字列をすべて抽出してリストアップします。各文字列はそのオフセットとともに表示されます。
実行結果の例:
0x00000040 (64): DEVICE
0x00000080 (128): FIRMWARE_V1.2
0x000003A0 (928): LOG_DATA_START
(ファイル内の複数のASCII文字列とそれらのオフセットが表示されます。)
例5: 特定のオフセット 0x120 のデータを複数型で検査
python bin_parser_to_be_tested.py inspect test.raw --offset 0x120 --types float32,int16,uint16,str --sizes 4,2,2,32
説明:
test.raw ファイルのオフセット 0x120 にあるバイナリデータを、float32 (4バイト)、int16 (2バイト)、uint16 (2バイト)、str (32バイト) の各型として解釈し、その結果を表示します。--sizes 引数で各型に適用する明示的なサイズを指定しています。解釈結果と、そのオフセット周辺の64バイトのヘックスダンプが表示されます。
実行結果の例:
File: test.raw
Offset: 0x00000120 (288)
- float32 size=4 value=3.14159265359
- int16 size=2 value=-1024
- uint16 size=2 value=64512
- str size=32 value=Sample String at Offset 0x120
00000100 00 00 F0 42 00 00 00 00 00 00 00 00 00 00 00 00 |...B............|
00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000120 DB 0F 49 40 00 FC 00 FC 53 61 6D 70 6C 65 20 53 |..I@.. ..Sample S|
00000130 74 72 69 6E 67 20 61 74 20 4F 66 66 73 65 74 20 |tring at Offset |
00000140 30 78 31 32 30 00 00 00 00 00 00 00 00 00 00 00 |0x120...........|
00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
(指定されたオフセット 0x120 のバイト列が、各型でどのように解釈されるかが表示されます。例では DB 0F 49 40 が float32 として 3.14159265359 に、00 FC が int16 として -1024 に、00 FC が uint16 として 64512 に、53 61 6D ... が文字列に解釈されています。)
例6: オフセット 0x200 から float32 の配列を16要素読み込み
python bin_parser_to_be_tested.py array test.raw --offset 0x200 --type float32 --count 16 --step 4
説明:
test.raw ファイルのオフセット 0x200 から、float32 型のデータを16要素分読み込みます。各要素は4バイトステップで配置されていると仮定されます。読み込まれた各要素のインデックス、オフセット、および値が表示されます。
実行結果の例:
[ 0] offset=0x00000200 ( 512) value=1.0
[ 1] offset=0x00000204 ( 516) value=2.0
[ 2] offset=0x00000208 ( 520) value=3.0
[ 3] offset=0x0000020C ( 524) value=4.0
[ 4] offset=0x00000210 ( 528) value=5.0
[ 5] offset=0x00000214 ( 532) value=6.0
[ 6] offset=0x00000218 ( 536) value=7.0
[ 7] offset=0x0000021C ( 540) value=8.0
[ 8] offset=0x00000220 ( 544) value=9.0
[ 9] offset=0x00000224 ( 548) value=10.0
[ 10] offset=0x00000228 ( 552) value=11.0
[ 11] offset=0x0000022C ( 556) value=12.0
[ 12] offset=0x00000230 ( 560) value=13.0
[ 13] offset=0x00000234 ( 564) value=14.0
[ 14] offset=0x00000238 ( 568) value=15.0
[ 15] offset=0x0000023C ( 572) value=16.0
(インデックス 0 から 15 までの float32 値が、それぞれのオフセットとともに表示されます。)