必须以管理员权限调用createfile获取卷句柄,解析bpb得mft起始簇号并换算字节偏移,再逐属性解析可变长结构,时间戳需用fromfiletimeutc转换且校验有效性。

直接读取 $MFT 需要管理员权限和原始磁盘访问,普通 FileStream 会失败
Windows 默认禁止用户态程序直接读取物理磁盘或卷的底层结构,哪怕你用 File.Open(@"\.C:", ...),打开成功也不代表能跳到 $MFT 偏移处读——多数情况会触发 UnauthorizedAccessException 或读出全零。这不是代码写错了,是系统策略卡死的。
- 必须以
Administrator身份运行进程(UAC 提权不能省) - 要用
CreateFileWin32 API 打开卷句柄,且 flag 必须含FILE_SHARE_READ | FILE_SHARE_WRITE和OPEN_EXISTING -
$MFT不在固定偏移,得先解析卷引导扇区(BPB)里的MftStartLcn字段,再乘以簇大小换算成字节偏移 - .NET 的
FileStream对原始设备支持弱,建议用SafeFileHandle+NativeMethods.ReadFile控制读行为
$MFT 记录不是纯结构体,每个记录含可变长属性,不能 Marshal.PtrToStructure 一把梭
NTFS 中一个 $MFT 记录(通常 1024 字节)由固定头 + 多个属性($STANDARD_INFORMATION、$FILE_NAME、$DATA 等)拼接而成,属性长度、顺序、是否存在都动态变化。硬套 C# struct 会错位、越界、把属性头当数据读。
- 必须逐字节解析:先读 48 字节记录头,拿到
AttributesOffset和Flags(是否已删除) - 每个属性以 4 字节签名开头(如
0x30000000表示$STANDARD_INFORMATION),后跟长度字段,需循环跳读 -
$FILE_NAME属性里存的是 UTF-16 名称,但可能有多个(含 DOS 8.3 名),要检查NameLength和NameOffset - 别依赖
sizeof(MyMftRecord)—— 实际记录长度由头中BytesInUse决定,常小于分配单元
读出来的 $STANDARD_INFORMATION 时间戳是 UTC,但 Windows 资源管理器显示的是本地时区
NTFS 所有时间字段(CreationTime、ChangeTime 等)都是 64 位 FILETIME(100ns 自 1601-01-01 UTC),C# 的 DateTime.FromFileTimeUtc() 可正确转换,但若误用 FromFileTime() 就会多加本地偏移,导致时间快/慢几小时。
-
$STANDARD_INFORMATION偏移 0x08 开始是 4 个连续的 64 位 FILETIME,顺序为创建、修改、MFT 修改、访问时间 - 注意:NTFS 默认禁用最后访问时间更新(
fsutil behavior query disablelastaccess返回 1),所以该字段常为 0 - 某些旧工具或低版本 Windows 可能写入不规范时间值(如全零或极大值),解析时应加范围校验,避免
DateTime构造异常
绕过 $MFT 直接拿元数据?用 GetFileInformationByHandle 更稳但信息少
如果目标只是获取文件大小、创建时间、属性标志等基础元数据,完全没必要碰 $MFT。Win32 的 GetFileInformationByHandle(对应 .NET 的 File.GetAttributes + FileInfo)走的是内核缓存路径,快、安全、兼容所有文件系统。
-
FileInfo无法告诉你文件在磁盘上的物理位置、是否被硬链接、或历史删除状态 - 它读不到
$OBJECT_ID、$REPARSE_POINT这类扩展属性,也看不到目录项级的ParentReferenceNumber - 真要分析取证、恢复已删除文件、查硬链接关系,才值得啃
$MFT;否则就是给自己上锁链
真正难的不是读字节,是判断哪块是有效记录、哪个属性可信、怎么处理碎片化或跨簇的属性。没解析过几百条真实记录,很难信自己写的偏移计算是对的。










