結論: インストール可能な環境では純 Python の analyzeMFT、型付きオブジェクト モデルが欲しいなら libmft、速度を重視するなら omerbenamram/mft にシェル アウト、というのが基本方針です。純 Python による解析は Rust クレートの約 10〜50 倍遅いですが、1 回限りのスクリプトには十分です。
読むもの
NTFS の Master File Table は 1,024 バイトの固定サイズ レコードの連続です。Python から解析するには、次の手順だけで足ります。
$MFTファイル(あるいはディスク イメージから読み出したもの)を開きます。- 1,024 バイトずつ進めて辿ります。
- 各レコードにフィックスアップ配列を適用します。バイト レベルのレイアウトは レコードの解剖 を参照してください。
- 各レコード内の属性ストリームを走査します。
下記のライブラリはこの 4 ステップすべてを処理します。たいていのアナリストは、ライブラリが必要なフィールドを公開していないときにのみ生の struct.unpack にフォールバックします。
選択肢 1: analyzeMFT
analyzeMFT は古典的な純 Python の MFT パーサーで、もともと David Kovar 作、現在もメンテナンスされています。CLI 先行ですが、import 可能です。
# pip install analyzeMFT
from analyzeMFT.mft_analyzer import MFTAnalyzer
analyzer = MFTAnalyzer(mft_file="path/to/$MFT", output_file="out.csv")
analyzer.analyze()
生成される CSV はレコードごとに 1 行で、$STANDARD_INFORMATION と $FILE_NAME 両方のタイムスタンプを含みます。スプレッドシート ベースのトリアージには十分です。
使うべき時: 小さな $MFT ファイル、その場限りのスクリプト、ネイティブ依存が不可。
制約: 数 GB の入力では遅く(シングルスレッドの純 Python)、オブジェクト モデルはプログラム的な走査よりも CSV 出力向きです。
選択肢 2: libmft(型付きオブジェクト モデル)
レコードを Python オブジェクトとしてクエリしたい場合、libmft はディスク構造に近い型付きモデルを公開します。
# pip install libmft
from libmft.api import MFT
with open("path/to/$MFT", "rb") as f:
mft = MFT(f)
for entry in mft:
if not entry.is_deleted():
continue
name = entry.get_full_path()
si = entry.get_attributes(0x10)[0] # $STANDARD_INFORMATION
print(name, si.created, si.modified)
libmft は親参照を解決するため、各エントリにそのフル パスを尋ねれば、自分で走査ロジックを書く必要はありません。$ATTRIBUTE_LIST 拡張レコードも透過的に処理してくれます — analyzeMFT の CSV 層では隠れてしまうものです。
使うべき時: レコードを走査し、属性で絞り込み、独自の形式を出力するロジックを書きたい場合。
選択肢 3: Rust パーサーにシェル アウト
$MFT が大きい(約 1 GB 以上)、または多数のディスクをバッチ処理する場合、現実的に最速なのは Python からネイティブ パーサーにシェル アウトし、その JSON を読むことです。
import json
import subprocess
# omerbenamram/mft — `cargo install mft` またはリリース バイナリをダウンロード
proc = subprocess.run(
["mft_dump", "-o", "json", "path/to/$MFT"],
capture_output=True, check=True,
)
for line in proc.stdout.splitlines():
record = json.loads(line)
if record["header"]["flags"] & 0x1 == 0: # IN_USE がクリア → 削除済み
print(record["entry"], record["file_name"]["name"])
mft_dump は JSON Lines — 1 レコード 1 行 — を出力するので、出力全体をメモリにロードせず Python にきれいにストリームできます。同じ入力に対して analyzeMFT と比較すると、Rust パーサーは通常 10〜50 倍速く、メモリは 1/10 程度です。
使うべき時: 本番パイプライン、大きな入力、または解析時間が問題になる場面。
ディスク イメージから直接 $MFT を読む
抽出済み $MFT ファイルではなく生の .dd や .E01 イメージを持っている場合、pytsk3(The Sleuth Kit の Python バインディング)でボリューム上の $MFT までシークして、そのバイトをストリームできます。
import pytsk3
img = pytsk3.Img_Info("disk.dd")
fs = pytsk3.FS_Info(img, offset=0) # NTFS パーティションのオフセットを使う
mft_file = fs.open_meta(inode=0) # $MFT は常に inode 0
size = mft_file.info.meta.size
data = mft_file.read_random(0, size)
# data に $MFT が入った状態。libmft に渡すか、ディスクに書き出す
ボリュームがパーティション レベルで暗号化されているが、生イメージを返す復号器でマウントされている場合に、これが最もきれいなアプローチです。
よくある落とし穴
- フィックスアップ配列を忘れる。 USA を適用せずに 1,024 バイトの生のチャンクを読むと、各レコードのオフセット 510 と 1022 がゴミになります。上記のどのライブラリもこれを代わりにやってくれます — フィックスアップ機構を理解している場合だけ自前のパーサーを書いてください(レコード解剖の記事 を参照)。
- レコード番号を ID として扱う。 レコード番号は再利用されます。衝突しない識別子は 64 ビットの ファイル参照(レコード番号 + シーケンス番号)です。
- 2 組のタイムスタンプを混同する。 すべてのレコードは
$STANDARD_INFORMATION(頻繁に更新)と$FILE_NAME(ほぼ安定)の両方にタイムスタンプを持ちます。タイムストンピング検出には両方が必要です — MFT の 4 つのタイムスタンプ を参照してください。
Python を使わない選択肢
何もインストールせず 1 回限りの対話的解析がしたいなら、本サイトの ブラウザ パーサー に $MFT をドロップしてください。WebAssembly にコンパイルされた同じ omerbenamram/mft クレートが動き、クライアント側でフィルタと検索を行い、CSV をエクスポートします — Python は不要です。