C# NTFS $MFT文件解析 C#如何直接读取NTFS主文件表来获取文件元数据

5次阅读

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

C# NTFS $MFT文件解析 C#如何直接读取NTFS主文件表来获取文件元数据

直接读取 $MFT 需要管理员权限和原始磁盘访问,普通 FileStream 会失败

windows 默认禁止用户态程序直接读取物理磁盘或卷的底层结构,哪怕你用 File.Open(@".C:", ...),打开成功也不代表能跳到 $MFT 偏移处读——多数情况会触发 UnauthorizedAccessException 或读出全零。这不是代码写错了,是系统策略卡死的。

  • 必须以 Administrator 身份运行进程(UAC 提权不能省)
  • 要用 CreateFile Win32 API 打开卷句柄,且 flag 必须含 FILE_SHARE_READ | FILE_SHARE_WRITEOPEN_EXISTING
  • $MFT 不在固定偏移,得先解析卷引导扇区(BPB)里的 MftStartLcn 字段,再乘以簇大小换算成字节偏移
  • .NET 的 FileStream 对原始设备支持弱,建议用 SafeFileHandle + NativeMethods.ReadFile 控制读行为

$MFT 记录不是纯结构体,每个记录含可变长属性,不能 Marshal.PtrToStructure 一把梭

NTFS 中一个 $MFT 记录(通常 1024 字节)由固定头 + 多个属性($STANDARD_INFORMATION$FILE_NAME$DATA 等)拼接而成,属性长度、顺序、是否存在都动态变化。硬套 C# struct 会错位、越界、把属性头当数据读。

  • 必须逐字节解析:先读 48 字节记录头,拿到 AttributesOffsetFlags(是否已删除)
  • 每个属性以 4 字节签名开头(如 0x30000000 表示 $STANDARD_INFORMATION),后跟长度字段,需循环跳读
  • $FILE_NAME 属性里存的是 UTF-16 名称,但可能有多个(含 DOS 8.3 名),要检查 NameLengthNameOffset
  • 别依赖 sizeof(MyMftRecord) —— 实际记录长度由头中 BytesInUse 决定,常小于分配单元

读出来的 $STANDARD_INFORMATION 时间戳是 UTC,但 Windows 资源管理器显示的是本地时区

NTFS 所有时间字段(CreationTimeChangeTime 等)都是 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;否则就是给自己上锁链

真正难的不是读字节,是判断哪块是有效记录、哪个属性可信、怎么处理碎片化或跨簇的属性。没解析过几百条真实记录,很难信自己写的偏移计算是对的。

text=ZqhQzanResources