简短回答: 当你能装得上的时候,用 analyzeMFT 做纯 Python 解析;想要一个带类型的对象模型时用 libmft;在乎速度时则调用 omerbenamram/mft。纯 Python 解析比 Rust crate 慢约 10–50 倍,但对一次性脚本来说够用了。
你将要做的事
NTFS Master File Table 是一段由固定大小、各 1024 字节的记录组成的序列。要用 Python 解析它,你只需要做四件事:
- 打开
$MFT文件(或从磁盘镜像中读取)。 - 每次 1024 字节地往下走。
- 对每条记录应用 fixup 数组。字节级布局见记录结构剖析。
- 走完每条记录里的属性流。
下面列出的库都能替你处理这四步。大多数分析师只在某个库没暴露所需字段时,才会退回到原始 struct.unpack。
方案一:analyzeMFT
analyzeMFT 是经典的纯 Python MFT 解析器,最初由 David Kovar 编写,至今仍在维护。以 CLI 为主,但也可被导入使用。
# pip install analyzeMFT
from analyzeMFT.mft_analyzer import MFTAnalyzer
analyzer = MFTAnalyzer(mft_file="path/to/$MFT", output_file="out.csv")
analyzer.analyze()
它生成的 CSV 每行对应一条记录,同时包含来自 $STANDARD_INFORMATION 和 $FILE_NAME 的时间戳。对于以电子表格驱动的排查工作而言已经够用。
何时使用: 小型 $MFT 文件、临时脚本、不允许引入原生依赖的环境。
局限: 在多 GB 输入上速度慢(单线程纯 Python),并且对象模型偏向 CSV 输出,而非程序化遍历。
方案二: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 层对用户隐藏掉的事情。
何时使用: 你想编写遍历记录、按属性过滤并输出自定义形态的逻辑。
方案三:调用 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 —— 每行一条记录 —— 能干净地流式喂给 Python,而不必把全部输出加载到内存里。同样的输入下,与 analyzeMFT 相比,Rust 解析器通常快 10–50 倍,内存占用只有约十分之一。
何时使用: 生产流水线、大型输入,或任何在意解析耗时的场合。
直接从磁盘镜像中读取 $MFT
如果你手上是原始的 .dd 或 .E01 镜像,而不是已经提取出来的 $MFT 文件,可以用 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 或写到磁盘上
当卷被在分区级别加密、但通过某个解密器以原始镜像形式挂载出来时,这是最干净的做法。
常见陷阱
- 忘记 fixup 数组。 直接读取 1024 字节而不应用 USA,会让你在每条记录的偏移 510 和 1022 处得到乱码。上面列出的每个库都会替你做这件事 —— 只有当你理解 fixup 机制时,才该自己写解析器(参见记录结构剖析)。
- 把记录号当作身份。 记录号会被重用。由记录号加序列号组成的 64 位 file reference 才是不会冲突的标识符。
- 混淆两套时间戳。 每条记录都在
$STANDARD_INFORMATION(更新频繁)和$FILE_NAME(基本稳定)中各保存一组时间戳。做时间戳篡改检测时两者都需要 —— 见 MFT 的四个时间戳。
何时完全不必用 Python
对于无需任何安装的一次性交互式分析,把 $MFT 直接拖到本站的浏览器解析器。它运行的是编译为 WebAssembly 的同一份 omerbenamram/mft crate,在客户端完成过滤与搜索,还能导出 CSV —— 完全不需要 Python。