新闻详情

新闻详情

首页 / 资讯中心 / 详情

NTFS元文件解析:$MFT、运行列表与核心元文件拆解

发布时间:2026/9/30 5:28:21来源:尧图网络
NTFS元文件解析:$MFT、运行列表与核心元文件拆解
$MFT 这个名字干过数据恢复或者写过文件系统工具的人应该都不陌生。很多人第一次打开十六进制编辑器看 NTFS 卷的时候会以为分区开头那块全是用户数据翻到后面才发现有个奇怪的记录里躺着文件夹名、时间戳、簇号范围——那些东西就是元文件metadata file。NTFS 和 FAT 最大的设计差异之一就是把管理数据本身也当成文件来存储文件名、权限描述符、簇分配位图、日志、大小写映射表全部以文件记录的形式存在于 $MFT 里都有自己的记录号、自己的数据流。这意味着解析 NTFS 前缀的元文件本质上就是在解析普通文件只是这些文件的编号被固定保留了。这篇内容是 NTFS 系列的第三篇前两篇讲了引导扇区和 $MFT 记录结构这次专门把元文件NTFS元文件解析拿出来做一次逐字节的拆解。我会把十六个固定编号元文件的职责、记录头与属性的解析路径、数据运行列表run list的解码逻辑以及 $Bitmap、$Secure、$UpCase、$LogFile、$Extend 这几个高频元文件的内部结构讲透最后给出一份可以跑起来的最小 MFT 解析器代码。适合正在写文件系统解析工具、做取证类项目、或者单纯想把 NTFS 弄明白的读者零基础也能跟着看下去因为每一步我都会说明为什么是这样。1. 元文件在 NTFS 里到底是什么先把清单和编号理清NTFS 卷上一切皆文件这句话不是修辞。卷上第一个能被系统识别的结构是引导扇区它告诉操作系统 $MFT 从哪个簇开始、每条记录多大从 $MFT 的第一条记录开始前十六个位置被系统预留给了元文件。普通用户文件从 16 号记录往后排元文件的编号在任何 NTFS 卷上都是一样的不随卷大小、簇大小变化。1.1 十六个固定编号的元文件与各自的职责先把清单摆出来后面所有解析工作都围绕这张表展开记录号名称作用0$MFT主文件表自身每条记录描述一个文件或目录1$MFTMirr$MFT 前几条记录的镜像2$LogFile事务日志记录元数据变更以保证崩溃一致性3$Volume卷的名称、版本、脏标志等卷级信息4$AttrDef属性定义表描述每种属性的类型与排序规则5.根目录6$Bitmap簇分配位图一个 bit 对应一个簇7$Boot引导扇区及其备份卷的首尾各一份8$BadClus坏簇记录用一个稀疏流圈出坏簇位置9$Secure安全描述符仓库实现权限信息的去重共享10$UpCase大小写映射表65536 个双字节条目11$Extend扩展元文件目录内部再挂四个子元文件12-15保留3.0 版本之前曾用于存放扩展元文件$Extend 里面挂的四个是 $ObjId对象 ID、$Quota磁盘配额、$Reparse重解析点、$UsnJrnl更新序列号日志。在早期的 NTFS 3.0 之前这四个是各自占用 12 到 15 号记录的3.0 之后统一挪进了 $Extend 目录12 到 15 号就空了出来。你在老镜像上做解析的时候如果发现 12 号记录里是 $ObjId不要惊讶那就是历史遗留结构。1.2 为什么 0 到 15 号记录不能被当成普通文件处理一个很容易踩的误区是既然元文件也是文件那我遍历 $MFT 的时候把它们和普通文件一视同仁不就行了实际操作中不行原因有三个。第一元文件的记录里有相当一部分字段语义和普通文件不同。比如 $Bitmap 的 $DATA 流里装的是位图而不是文件内容$Boot 的 $DATA 流只有 8KB 左右但它的位置和引导扇区是绑定的你按文件大小去读会读到一堆补零。第二$LogFile 和 $Extend 下的 $UsnJrnl 的内容是循环写入的日志区存在大量无效尾部直接当数据处理会解析出垃圾。第三也是最关键的$MFT 自己有可能会碎片化它的 $DATA 流是通过运行列表描述的你在读第 17 条记录之前必须先用 0 号记录里的运行列表把 $MFT 的真实簇分布算出来否则一旦 $MFT 不连续从第二个簇开始读到的全是错的数据。我个人的做法是在解析器里对记录号小于 16 的记录单独打标只走元文件专用的解析分支同时对 $MFT 自身的 $DATA 流做一次显式的运行列表展开把它当作读取所有记录的索引表而不是普通文件。2. 一条 MFT 记录的字节级解剖元文件解析的原子单位是 MFT 记录。典型情况下一条记录 1024 字节但如果卷的簇大小是 4KB 或更大clusters_per_mft_record可能为正数记录尺寸就等于簇大小乘以该值。所以第一步永远是算记录大小不能硬编码 1024。2.1 记录头的字段与更新序列数组的修补逻辑记录头固定 48 字节左右字段布局如下偏移长度字段0x004魔数FILE 正常BAAD 表示损坏0x042更新序列数组偏移0x062更新序列数组条目数0x088$LogFile 序列号LSN0x102序列号用于判断引用是否过期0x122硬链接计数0x142第一个属性的偏移0x162标志位0x01 使用中0x02 目录0x04 扩展记录0x184记录已用字节数0x1C4记录分配字节数0x208基础记录引用非 0 表示本记录是扩展记录0x282下一个可用的属性 ID0x2C4本记录自身的记录号更新序列数组USA也叫 fixup 数组是 NTFS 保证崩溃时不写入撕裂的技术。原理是写盘时把每个扇区最后两个字节替换成一个不断递增的更新序列号USN原始的两个字节被挪到 USA 数组里保存读盘时先校验每个扇区末两字节是否等于 USA 数组的第一个条目相等才认为这条记录写完整然后再把原始字节还原回去。需要注意的细节USA 数组的条目数包括了那个 USN 本身所以 1024 字节记录、512 字节扇区的情况下条目数是 31 个 USN 2 个扇区的原始字节而每个扇区末两字节的位置分别是1 × 512 - 2和2 × 512 - 2。我见过不止一个人在循环里把所有usa_cnt个扇区都校验一遍结果最后一个位置越界或者误报原因就是把 USN 本身也算成了扇区。注意如果你是在做只读分析可以先跳过 fixup 校验直接解析但一旦遇到BAAD或者属性长度明显异常第一件事就是回头检查 fixup 是否匹配多数记录损坏其实是补丁没还原。2.2 属性区的遍历规则类型码、长度与结束标记从记录头给出的第一个属性偏移开始属性是一条接一条紧凑排列的每条属性的前 16 字节是公共头类型码4 字节、本属性总长度4 字节、是否非常驻1 字节、属性名长度1 字节、属性名偏移2 字节、属性标志2 字节、属性 ID2 字节。遍历的逻辑非常朴素读类型码如果是0xFFFFFFFF说明到了结束标记停止否则读长度偏移加上长度跳到下一条。这里有两个必须记住的换算属性的名字长度单位是 UTF-16 字符数换算成字节要乘 2名字偏移和值偏移都是相对于该属性起始位置的偏移不是相对于记录头。常见的属性类型码如下类型码名称说明0x10$STANDARD_INFORMATION四个时间戳、DOS 属性、属主与安全 ID0x20$ATTRIBUTE_LIST属性列表指向被拆到其他记录的属性0x30$FILE_NAME父目录引用、四个时间戳、文件名0x40$OBJECT_ID128 位对象 ID0x50$SECURITY_DESCRIPTOR老式实现里的安全描述符0x60$VOLUME_NAME卷标0x70$VOLUME_INFORMATION版本号与脏标志0x80$DATA文件内容可匿名也可命名0x90$INDEX_ROOT索引的根节点0xA0$INDEX_ALLOCATION索引的溢出节点0xB0$BITMAP索引或簇的位图0xC0$REPARSE_POINT重解析点数据$STANDARD_INFORMATION 里的四个时间戳是 FILETIME 格式从 1601-01-01 UTC 起算的 100 纳秒数换算成 Unix 时间要减去 11644473600 秒再除以 10000000。2.3 常驻与非常驻的分野属性头两套字段布局公共头之后常驻属性和非常驻属性的字段完全不一样。常驻属性内容能塞进记录里比如几百字节的 $FILE_NAME、小文本文件的 $DATA在 0x10 处放的是值长度和值偏移非常驻属性的 0x10 处开始是起始 VCN、结束 VCN0x20 处是运行列表偏移0x28 起是三个 8 字节尺寸。偏移常驻字段非常驻字段0x10值长度4起始 VCN80x14值偏移2结束 VCN80x16索引标志1运行列表偏移20x20-运行列表偏移20x28-已分配大小80x30-实际大小80x38-初始化大小8这三个尺寸经常被搞混。已分配大小是按簇对齐后的空间实际大小是文件真实长度初始化大小表示已写入零值或者有效数据的长度实际大小与初始化大小之间的区域在读取时应当返回零。做稀疏文件解析或者压缩文件解析的时候初始化大小是判断尾部是否要补零的依据。提示属性名的偏移和长度字段在两种布局里位置相同解析公共头之后先判断non_resident标志再决定读哪套字段是最不容易出错的分支写法。3. 数据运行列表NTFS 用它描述簇的分布非常驻属性的内容不在记录里而是散落在卷的若干簇上运行列表就是用来描述第几段连续簇从哪开始、有多长的编码。NTFS 用变长编码压缩这些信息代价是手工解码时容易读错字节。3.1 头字节的高低位半字节与增量编码每个运行项由三部分组成一个头字节、一个变长的长度字段、一个变长的偏移字段。头字节的低四位表示长度字段占几个字节高四位表示偏移字段占几个字节。长度为 0 表示偏移字段省略稀疏段头字节本身为 0 表示运行列表结束。偏移字段存的是相对值是相对于前一个运行起始 LCN 的差值而且是带符号的小端整数。这个设计的意义在于大多数文件在磁盘上是顺序分配的相邻段的 LCN 差很小用 1 个字节就能表示整条运行列表可以压得很短。第一条运行的基准是 0所以它的偏移就是绝对 LCN。假设一段运行列表字节是11 05 20 11 03 30 00解码过程是这样的第一项头字节 0x11低位 1、高位 1长度字段 1 字节、偏移字段 1 字节长度 5、偏移 0x20得到 LCN 0x20 开始的 5 个簇第二项头字节 0x11长度 3、偏移 0x30累加到 LCN 0x50得到 3 个簇最后一个 0x00 表示结束。如果第二项的偏移是FE按有符号解释就是 -2LCN 变成 0x1E说明文件在磁盘上出现了一次回跳。3.2 手工解码与几种典型误读我在看别人写的解析器时见到最多的三个错误是这样的。第一个是把偏移字段当无符号数读。一旦文件出现先往后写、再回填前面空间的分配模式偏移就是负数按无符号读会得到一个天文数字的 LCN接着就是越界读或者算出离谱的簇数。第二个是把长度字段和偏移字段的大小搞反。头字节的低位是长度、高位是偏移这个顺序在线上的资料里说法不完全一致最可靠的验证方式是用 $MFT 自己来验$MFT 通常从卷的某个中等偏后的簇开始你算出来的第一条运行 LCN 应该和引导扇区里0x30处的 $MFT 起始簇号完全一致不一致就是解错了。第三个是忘了处理稀疏运行。稀疏运行的头字节高四位是 0意味着没有偏移字段解码时不要更新当前 LCN只在逻辑上占用簇数量。3.3 稀疏与压缩在两套运行列表里的表现压缩属性和稀疏属性都依赖运行列表表达空洞。稀疏文件只有一个运行列表其中偏移为 0 的段就是未分配区域读取时返回零。压缩文件有两套运行列表压缩运行列表和普通运行列表属性头 0x22 处的压缩单元大小字段通常是 4表示 16 个簇一个压缩单元决定解压粒度压缩运行列表里出现的稀疏段表示该压缩单元内的数据被压缩后变短了读取时需要先解压再补齐到压缩单元大小。$BadClus 就是稀疏机制最典型的应用它的 $Bad 流名义大小等于整个卷的容量但绝大部分是稀疏的只有真正标记为坏簇的位置才有实际分配。所以你看到 $BadClus 的 $DATA 大小等于卷容量时不要慌那不是一个装满坏簇的巨型文件。4. 逐个拆解$Bitmap、$Secure、$UpCase、$LogFile、$Extend知道结构之后真正有价值的是理解几个关键元文件的内部布局因为绝大多数工具需求最后都会落到这几个上面算剩余空间要看 $Bitmap做权限分析要看 $Secure做文件名比较要看 $UpCase做崩溃恢复要看 $LogFile做变更追踪要看 $UsnJrnl。4.1 $Bitmap一个 bit 管一个簇$Bitmap 的 $DATA 流是非驻留的每一位对应卷上的一个簇bit 为 1 表示已分配为 0 表示空闲。位序是每字节内从低位到高位第 n 个字节的第 i 位对应簇号n × 8 i。整个位图的字节数等于总簇数除以 8 向上取整。解析它的价值在于一是可以直接算出卷的真实使用率比操作系统报告的数字更底层二是可以反向校验你的运行列表解码是否正确——把 $MFT 里所有文件的 $DATA 运行列表展开把占用的簇在自建位图里置位最后和 $Bitmap 做异或如果差异极小剩下的应该只是元文件自身占用的簇说明你的解析链路是通的。我做过几次这个比对差异控制在一两百个簇以内基本可以确认逻辑没错。提示$Bitmap 自身的 $DATA 也是簇也是被标记为已分配的做异或比对时别把自己算漏了。4.2 $Secure安全描述符去重靠两个索引权限信息如果每个文件存一份会有大量重复NTFS 的做法是集中存放在 $Secure 里。$Secure 有三个关键属性名为$SDS的非驻留数据流、名为$SII的索引、名为$SDH的索引。$SDS是一串拼接在一起的安全描述符每个条目按 16 字节对齐填充。$SII索引的键是 4 字节的安全 ID数据是 4 字节的$SDS内偏移。$SDH索引的键是 8 字节4 字节哈希加 4 字节安全 ID数据也是 8 字节4 字节哈希加 4 字节偏移排序规则按哈希。文件记录的 $STANDARD_INFORMATION 里那个 4 字节安全 ID 字段就是用来到$SII里查出偏移、再到$SDS里取出描述符的。安全描述符本身的结构是修订号1 字节、控制位2 字节、属主 SID 偏移4 字节、组 SID 偏移4 字节、SACL 偏移4 字节、DACL 偏移4 字节之后是 SID 和 ACL 的实际内容。SID 的结构是修订号1 字节、子机构数量1 字节、6 字节标识机构、然后是若干 4 字节子机构值。想判断这个文件属于哪个用户就是沿着这条链路走一遍。4.3 $UpCase 与 $LogFile大小写映射表和日志重启页$UpCase 是一个非驻留的 $DATA 流固定 128KB包含 65536 个 16 位条目把每个 UTF-16 码位映射成它的大写形式。它的第一个条目是 0。索引排序规则为文件名0x01的比较操作就是先用这张表把字符统一成大写再比较这也是 NTFS 文件名大小写不敏感但保留原始大小写的实现方式。如果你想在非 Windows 环境做和系统一致的文件名查找直接读这张表比调用语言内置的大小写转换更可靠因为不同版本的系统在个别码位上的映射是有差异的。$LogFile 的头部是两个重启页NTFS 3.1 里每页 4KB偏移 0 和 4096 各一份。重启页有魔数、校验和、日志页大小、当前 LSN 等字段页面最后同样有更新序列数组保护。$LogFile 的主要内容是重做和撤销记录解析起来比 $MFT 复杂得多一般做崩溃恢复或者写日志回放工具时才需要深入。日常分析里更有用的是 $LogFile 的头部信息比如判断日志是否干净、最后一个 LSN 是多少。4.4 $Extend 下的四个子元文件$Extend 本身是一个目录记录它的 $INDEX_ROOT 指向的子节点里列出四个元文件。$ObjId 存放 128 位对象 ID用于分布式链接跟踪内部有一个匿名数据流和名为$O的索引。$Quota 存放磁盘配额信息结构上比较特殊有一个匿名数据流以及$O、$Q两个索引$O按属主 SID 索引$Q存放配额限制和使用量。$Reparse 存放所有重解析点符号链接、挂载点等的反向索引名为$R键里包含重解析点标签。$UsnJrnl 有两个流常驻的$Max记录当前最大的 USN 和日志 ID非驻留的$J是一串变长的 USN 记录。USN 记录版本 2的字段顺序是记录长度、主版本、次版本、文件引用号、父目录引用号、USN、时间戳、原因掩码、来源信息、安全 ID、文件属性、文件名长度、文件名偏移、文件名。原因掩码里常见的有文件创建、文件删除、数据覆盖、数据扩展、重命名旧名、重命名新名、安全变更等等把原因和时间戳组合起来就能还原出一段时间内卷上发生的变更序列这也是变更审计类工具的底层数据来源。$Max 流里除了最大 USN还包含一个日志 ID用于判断$J流是否被重置过忽略这个 ID 会导致跨越日志重置的两段数据被错误地拼接在一起。5. 动手写一个最小 MFT 解析器理论说完了接下来是最能说明问题的部分几十行 Python 就能把 $MFT 和 $Bitmap 解析出来。这段代码我实际在 Windows 生成的 NTFS 镜像和真实分区上都跑过只做只读操作不会写盘。5.1 从引导扇区拿到簇大小、记录大小与 MFT 起点引导扇区的关键字段是每扇区字节数0x0B、每簇扇区数0x0D、总扇区数0x28、$MFT 起始簇号0x30、每 MFT 记录簇数0x40有符号单字节、每索引缓冲区簇数0x44。记录大小的算法是如果 0x40 处是正数记录大小等于该值乘以簇大小如果是负数记录大小等于 2 的该值绝对值次方。多数现代卷因为簇是 4KB0x40 处是 -10记录大小就是 1024。import struct def parse_boot(f): f.seek(0) bs f.read(512) if bs[3:11] ! bNTFS : raise ValueError(不是 NTFS 卷) bps struct.unpack_from(H, bs, 0x0B)[0] spc bs[0x0D] cluster_size bps * spc total_sectors struct.unpack_from(Q, bs, 0x28)[0] mft_lcn struct.unpack_from(Q, bs, 0x30)[0] mftmirr_lcn struct.unpack_from(Q, bs, 0x38)[0] cpr struct.unpack_from(b, bs, 0x40)[0] record_size (2 ** -cpr) if cpr 0 else cpr * cluster_size return { bytes_per_sector: bps, cluster_size: cluster_size, total_sectors: total_sectors, mft_lcn: mft_lcn, mftmirr_lcn: mftmirr_lcn, record_size: record_size, volume_bytes: total_sectors * bps, }5.2 记录读取与 fixup 校验读记录时先做 fixup 还原。注意校验的是每个扇区最后两字节是否等于 USA 数组的第一个条目然后把这些字节替换回数组里保存的原始值。def read_record(f, offset, record_size, bytes_per_sector): f.seek(offset) buf bytearray(f.read(record_size)) if buf[:4] not in (bFILE, bBAAD): return None usa_off, usa_cnt struct.unpack_from(HH, buf, 0x04) usn bytes(buf[usa_off:usa_off 2]) for i in range(1, usa_cnt): tail i * bytes_per_sector - 2 if bytes(buf[tail:tail 2]) ! usn: raise ValueError(fixup 校验失败记录可能被截断) buf[tail:tail 2] buf[usa_off 2 * i: usa_off 2 * i 2] return buf5.3 解析属性与运行列表属性遍历和运行列表解码是整个解析器的核心实现上比想象中短。ATTR_NAMES {0x10:STANDARD_INFORMATION, 0x20:ATTRIBUTE_LIST, 0x30:FILE_NAME, 0x40:OBJECT_ID, 0x50:SECURITY_DESCRIPTOR, 0x60:VOLUME_NAME, 0x70:VOLUME_INFORMATION, 0x80:DATA, 0x90:INDEX_ROOT, 0xA0:INDEX_ALLOCATION, 0xB0:BITMAP, 0xC0:REPARSE_POINT} def decode_runs(data): runs, lcn, i [], 0, 0 while i len(data): hdr data[i]; i 1 if hdr 0: break lsz, osz hdr 0x0F, (hdr 4) 0x0F length int.from_bytes(data[i:i lsz], little); i lsz raw data[i:i osz]; i osz if osz 0: runs.append((None, length)) # 稀疏段 else: lcn int.from_bytes(raw, little, signedTrue) runs.append((lcn, length)) return runs def iter_attrs(buf): off struct.unpack_from(H, buf, 0x14)[0] while True: atype struct.unpack_from(I, buf, off)[0] if atype 0xFFFFFFFF: return alen struct.unpack_from(I, buf, off 4)[0] nonres buf[off 8] name_len, name_off buf[off 9], struct.unpack_from(H, buf, off 0x0A)[0] name buf[off name_off: off name_off name_len * 2].decode(utf-16-le) if name_len else attr {type: atype, name: ATTR_NAMES.get(atype, hex(atype)), attr_name: name, offset: off, length: alen} if nonres: run_off struct.unpack_from(H, buf, off 0x20)[0] attr[runs] decode_runs(buf[off run_off: off alen]) attr[alloc], attr[real], attr[init] struct.unpack_from(QQQ, buf, off 0x28) else: vlen struct.unpack_from(I, buf, off 0x10)[0] voff struct.unpack_from(H, buf, off 0x14)[0] attr[value] buf[off voff: off voff vlen] yield attr off alen5.4 从 $MFT 自身展开把 $Bitmap 读出来$MFT 的第 0 条记录里的 $DATA 运行列表描述了整个 $MFT 的簇分布必须先把它解出来之后所有记录都可以通过运行列表换算成物理偏移。def mft_physical_offset(runs, cluster_size, record_index, record_size): # 把记录索引换算成 $MFT 数据流内的逻辑偏移再映射到物理偏移 byte_off record_index * record_size vcn byte_off // cluster_size inner byte_off % cluster_size acc 0 for lcn, length in runs: if vcn acc length: if lcn is None: raise ValueError($MFT 记录落在稀疏段数据不完整) return (lcn (vcn - acc)) * cluster_size inner acc length raise ValueError(记录号超出 $MFT 范围)拿到 $Bitmap6 号记录的 $DATA 运行列表后按段顺序读出全部字节统计置位数量乘以簇大小就是你算出来的已用空间。把它和系统工具报告的数字对比如果偏差在元文件自身占用范围内整条解析链路就算打通了。6. 解析路上容易翻车的几个点代码跑通只是第一步真实镜像上的情况比教科书复杂得多。下面几条都是我在实际项目中反复遇到的。6.1 记录大小、$MFT 碎片与扩展记录记录大小不要假设是 1024。有些卷格式化时选择了更大的簇记录大小跟着变成 4096有些老卷只有 512 字节扇区且簇是 512 字节记录大小还是 1024 但 fixup 条目数只有 2。第一个字节读错后面全盘皆错。$MFT 碎片化在长期使用的卷上非常普遍。判断方法很简单如果第一条运行的 LCN 加上长度小于总簇数说明 $MFT 至少在逻辑上不止一段。此时读第 N 条记录必须走运行列表映射不能简单地mft_lcn * cluster_size N * 1024。扩展记录记录头标志 0x04且基础记录引用非 0也要处理。当一条记录塞不下所有属性时NTFS 会把一部分属性挪到空闲记录里通过 $ATTRIBUTE_LIST 串起来。解析时应该把扩展记录里的属性合并回基础记录而不是当成独立文件。我见过工具把扩展记录单独列成一个不存在于任何目录里的幽灵文件根源就在这里。6.2 序列号、时间戳与脏标志带来的误判MFT 引用号是 8 字节低 48 位是记录号、高 16 位是序列号。序列号在记录被复用时会递增所以一个目录项指向的记录如果序列号对不上说明它指向的是已经被回收重用的记录那个文件名其实已经不存在了。做删除文件恢复时这一步判断必不可少否则会把别人的新文件当成你要找的旧文件。时间戳也有坑。$STANDARD_INFORMATION 和 $FILE_NAME 里各有一份时间戳正常写入时两者接近但不完全相等。如果两者差异很大通常说明有人在文件创建后修改过其中之一或者文件被复制到新卷时只更新了一部分。做时间线分析时把两份时间戳都列出来比对比只看一处可靠得多。$Volume 记录里的 $VOLUME_INFORMATION 有一个脏标志0x0001。卷被非正常卸载会把这个标志置位下次挂载时系统会先跑一致性检查。在 Linux 上用只读方式挂载这类卷一般没问题但如果你在容器或者用户态挂载方案里操作卷被标记为脏或者挂载进程异常退出后挂载点会处于失效状态访问时会报端点未连接之类的错误这时需要先卸载再重新挂载而不是怀疑文件系统本身坏了。注意任何分析操作都应该在镜像副本上进行。直接对原盘做挂载和读写可能在你还没拿到数据之前就把恢复线索覆盖掉了。6.3 用别的工具交叉验证别自己骗自己自己写解析器最大的风险是自洽错误代码有 bug但解析出来的结果看起来合理于是你就信了。解决办法是交叉验证。系统自带工具可以直接读出每扇区字节数、每簇字节数、每 MFT 记录字节数、$MFT 的有效数据长度等参数和自己的计算结果对一遍对不上就先怀疑代码。取证方向的工具能直接 dump 单条 MFT 记录命令行工具可以按 inode 号输出记录详情用来验证你的属性遍历顺序是否正确。十六进制编辑器配 NTFS 模板可以逐个字段对照适合排查个别字段解析错位的问题。我的习惯是每次改动解析器之后固定跑三个检查$MFT 第一条运行的 LCN 是否等于引导扇区里的值所有记录遍历完之后属性总数和结束标记是否都命中$Bitmap 统计出的已用簇数与自己展开的运行列表占用簇数的差异是否在一个合理范围内。这三个检查都能过基本可以放心用。最后再补一句经验解析 $UsnJrnl 的$J流时先读常驻的$Max拿到日志 ID 和最大 USN再顺着$J的记录长度字段一条条往前走遇到长度异常或者 USN 超出最大值就停不要试图把整段流都解释完。日志流在覆盖写之后尾部留着大量旧数据把它们当有效记录读进去会凭空多出几百条删除事件这种假阳性在审计场景里比漏报还麻烦。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Cold Email 规模化个性化实战指南:marketing-skills 仓库的四级个性化系统与 3 分钟研究信号体系 2026/9/30 7:21:27

Cold Email 规模化个性化实战指南:marketing-skills 仓库的四级个性化系统与 3 分钟研究信号体系

AI 技能人工智能 【免费下载链接】marketingskills Marketing skills for Claude Code and AI agents. CRO, copywriting, SEO, analytics, and growth engineering. 项目地址: https://gitcode.com/GitHub_Trending/mar/marketingskills 点击查看 免费下载 本文以…

阅读更多 →
大模型 API 价格怎么看?除了 Token 单价还要算哪些成本 2026/9/30 7:21:20

大模型 API 价格怎么看?除了 Token 单价还要算哪些成本

大模型 API 价格怎么看?除了 Token 单价还要算哪些成本 很多人第一次比较大模型 API 价格,最容易看的就是:每百万 Token 多少钱。这个数字当然重要,但真正把 API 接进 AI Coding、Agent、知识库或者企业内部系统后,很快…

阅读更多 →
碳排放核算软件如何支撑工业能碳大数据与节能优化 2026/9/30 7:21:20

碳排放核算软件如何支撑工业能碳大数据与节能优化

碳排放核算软件在工业能碳大数据场景下,设计核心不是孤立碳因子库,而是与能源台账、月结锁账、申报导出同源。GB/T 36132—2025与工信厅节〔2025〕13号12项业务功能,要求组织碳与能源数据可勾稽、可回放;2025年度新培育约2038家、…

阅读更多 →
消防联网监测:设施状态和火情识别双轮驱动快速联动 2026/9/30 7:21:14

消防联网监测:设施状态和火情识别双轮驱动快速联动

工厂的消防管理涉及多个独立子系统:火灾报警控制器、消防水系统(泵房、稳压泵、水压监测)、防火门和阀门状态、巡检维护记录。这些系统之间相互独立,火灾报警控制器响了但不知道消防水泵是不是在运行状态、消火栓阀门是不是开着。…

阅读更多 →
预算紧张时怎么起步?SaaS 与源码的入门门槛对比 2026/9/30 7:21:14

预算紧张时怎么起步?SaaS 与源码的入门门槛对比

刚起步的商家,现金流往往是头等大事。SaaS 手机号注册即自动分配商城空间,不用买服务器、不用请技术,几百块就能把店开起来,试错成本很低,跑不通损失也有限。 源码部署看似入门零成本。但真正跑起来,要付服…

阅读更多 →
一文教会昇腾ATC模型转换工具:把开源模型编译成NPU能跑的格式 2026/9/30 7:21:14

一文教会昇腾ATC模型转换工具:把开源模型编译成NPU能跑的格式

昇腾 ATC 模型转换入门指南:把训练模型变成 NPU 能跑的 OM 一句话让Agent变成昇腾专家,昇腾任务轻松搞定。评测入口:请按这个开源仓接入昇腾图谱 https://gitcode.com/agent0/kg-tools 你花几天训练出一个深度学习模型,满怀期待地…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉