/*
Skogkatt 开始翻译于2015-01-24,仅作为学习研究之用,谢绝转载。
2015-01-31更新MFT entry 属性概念。
2015-02-01翻译完成。
译注:我翻译这本书的这三章虽然蓄谋已久,但并不是一个计划好的工作。因为之前和vczh、mili、darkfall曾讨论过everything这个软件,也曾想过要写一个开源的everything,于是就出来一个坑。everything这个软件其实是从底层直接parse了NTFS MFT,然后parse类每一个FILE entry,从里面拆出来了每一个文件的信息,这个操作速度远快于Win32 FindFirstFile和FindNextFile。道理虽然简单,但是实现起来代码不会很少。
又,我从2013年起因工作原因开始研究和分析NTFS文件系统,并且看过数遍《File System Forensic Analysis》这本书的NTFS三章。这三章的信息已经略显过时并且存在一些技术细节谬误,翻译出来仅仅是给英语不好的朋友们做为拓展知识所用。如果想认真研究NTFS实现细节,建议看看泄露的Windows源代码、开源的NTFS3g库并使用磁盘编辑工具实际看看磁盘的布局。
另外,NTFS3g目前公开的代码坑很多,在高负荷压力测试中会出现严重的数据丢失损坏甚至文件系统挂掉,,不建议作为一个严谨的NTFS实现来使用。
*/
新技术文件系统(NTFS)是由Microsoft设计的并作为Micrtosoft Windows NT、Windows 2000、 Windows XP和Windows Server的缺省文件系统。在写作本书的时候,Microsoft已经停止了Windows 98 和 ME 产品线的销售,Windows XP家庭版成为新的消费系统。FAT仍将会存在于移动和小型存储设备之中,但NTFS将会成为Windows研究中最为常见的文件系统。NTFS是比FAT更为复杂的文件系统,这是由于它具有众多的功能特性和伸缩性。由于NTFS的复杂性,我们需要三章来讨论它。本章将会讨论NTFS的核心概念,覆盖到我们模型中的五个分类。第12章,“NTFS分析”讨论了NTFS分析和使用五分类模型来展示我们从哪里获取证据。第13章,“NTFS数据结构”介绍了NTFS相关的数据结构。
简介
NTFS被设计为具有可靠性、安全性和支持大型存储设备。伸缩性是由通用的数据结构所封装的具有特定内容的数据来实现的。这之所以是一个可伸缩的设计,是由于内部的数据结构可以根据文件系统新增的需求而变化,二外部的封装可以保持不变。一个通用封装的例子是,NTFS文件系统的每一个字节都被分配到文件之中。我们稍后在本章中将会讨论NTFS文件的概念。
NTFS是一个复杂的文件系统,不幸的是,并没有一个从Microsoft公开的规范来描述其在磁盘上的布局。文件系统高层组件的描述已经公开了,但是底层细节信息仍然非常匮乏。幸运的是,其他组织已经公开了他们所认为的磁盘布局结构信息,这些信息包含在本书之中,并且我们将会用它们来手工深入到磁盘之中。尽管这听起来很有难度,然而,我们仍然无法确保这里所描述的数据结构和磁盘上面的完全一致。
NTFS是大量Windows系统的标准,并且在免费的Unix发行版中也越发常见(译注:Linux)。没有官方规范和单一支配应用来创建文件系统两个因素合并在一起,使得难以区分应用指定的功能和文件系统通用的功能。例如,存在着Microsoft所不使用的初始化文件系统的方法,然而这种方法的结果是否被认为是一个“合法的NTFS”文件系统还难以定论。Microsoft在每一次新发布Windows系统的时候都会更改文件系统内部,我将会在这里指出这些改变。
一切都是文件
为了了解NTFS的设计,一个最重要的概念是一切重要的数据都被分配为文件。这包括其他文件系统通常隐藏起来的基本的文件系统管理数据。事实上,包含管理数据的文件可以存在放卷的任何位置,和普通文件一样。也就是说,NTFS文件系统并不像其他文件系统那样有一个特定的布局。整个文件系统被认为是一个数据区,每一个扇区都可以分配给一个文件。唯一不变的布局是每一个卷开始的几个扇区,包含有引导扇区和引导代码。
MFT概念
主文件表(MFT)是NTFS的心脏,这是因为它包含所有文件和目录的信息。每一个文件和目录在表中至少有一个入口(Entry),entry本身很简单。每一个entry都是1KB大,但是只有前42字节有定义的用途。剩余的空间用来存储属性(Attributes),属性是很小的具有特性用途的数据结构。例如,有一个属性用来保存文件的名称,另一个属性用来保存文件的内容。图11.1一个MFT entry的基本布局,包括头信息和三个属性。
图11.1 一个MFT entry,有一个小头部,剩余的部分用来存储不同的属性。这个entry有三个属性。
Microsoft称每一个表中的entry为一个文件记录,但是我想把每一个entry简称为MFT entry,这样更便于记忆。每一个entry都基于其在表中的位置有一个地址,从0开始。所有的entry的大小都是1024字节,不过实际的大小定义在引导扇区中。
如NTFS中的所有一切一样,MFT也是一个文件。导致这个令人困惑的是MFT有一个它自己的entry。表中第一个entry名字叫做$MFT,它描述了MFT在磁盘上的位置。实际上,这是唯一描述MFT在磁盘上的位置的地方;也就是说,你需要处理这个entry才能得知MFT的布局和大小。MFT的起始位置在引导扇区给出,引导扇区始终是文件系统的第一个扇区。我们可以在图11.2看到这个,如何使用引导扇区找到第一个MFT entry,它显示出MFT被分为碎片,有32到34和56到58的簇组成。类似于FAT,NTFS使用簇,簇由连续的扇区组成。
图11.2 引导扇区和$MFT的关系,用来确定MFT的布局。
在Microsoft实现的NTFS中,MFT开始保持尽量小的尺寸,当需要更多的entry时扩展MFT。理论上说,操作系统可以在创建文件系统的时候创建固定数量的entry,但是Microsoft实现的动态特性允许很方便的通过分卷扩展文件系统容量。Microsoft在MFT entry创建后不删除它们。
MFT entry内容
每一个MFT entry的大小定义在引导扇区之中,但是Microsoft使用的所有版本都使用1024字节大小。数据结构开始的42个字节包含12个域,剩余的982字节没有特定的结构,可以用属性填充。你可以把一个MFT entry想想为一个用来存放你物品的大盒子。盒子外面是你的基本信息,比如你的名字和地址。基本信息等价于MFT entry的固定域。盒子里面一开始是空的,但是它可以用来保存任何比它小的容器。这非常像MFT entry没有内部结构并且它含有一些包含特定信息的属性。
每一个MFT entry的第一个域是签名,一个标准的entry是ASCII字符串“FILE”,如果一个entry里面发现了错误,它可能含有字符串“BAAD”,还有一个标志域用来标识这个entry是否使用、这个entry是否是一个目录。一个MFT entry的分配状态也可以通过$MFT文件的$BITMAP属性来检查,详见第13章。
如果一个文件无法把它的所有属性放进一个entry,它可以使用多个entry。当这种情况发生的时候买第一个entry被称为基本文件记录,或者基本MFT entry,后续的每一个entry都在它的固定域保存有基本entry的地址。
第13章显示了一个MFT entry的数据结构,并分解了我们的示例文件系统镜像。
MFT entry 地址
每一个MFT entry都使用一个48位的顺序地址,第一个entry地址是0。MFT最大地址随着MFT的增长而增大,它的值由$MFT的大小除以每一个entry的大小来计算。Microsoft称这个顺序地址为文件编号(File number)。
每一个MFT entry还有一个16位的顺序号,当entry分配的时候这个顺序号增长。例如,想象一下MFT entry313的顺序号是1.entry313的文件删除了,然后这个entry被分配给一个新的文件。当这个entry被重新分配,它得到了一个新的顺序号2。MFT entry和顺序号组合起来,顺序号放在高16位,组成了一个64位的文件参考地址,见图11.3。
图11.3 MFT地址和顺序号组成一个文件参考地址的例子。
NTFS使用文件参考地址来引用MFT entry,这是由于顺序号使得检查文件系统是否处于一种损坏状态变得容易。例如,如果系统在一个文件的数据结构被分配的时候崩溃了,顺序号可以用来判断一个数据结构是否包含MFT entry,是因为上一个文件使用了它还是它是新文件的一部分。我们也可以用它来恢复被删除的内容。例如,如果我们有一个未分配的数据结构,里面有文件参考号,我们可以判断这个数据结构被使用以来MFT entry是否被重新分配过。顺序号在信息挖掘中有很大的作用,但是在本章为简单起见,我将主要讨论文件号,或者MFT entry地址。
文件系统元文件
由于卷的每一个字节都被分配给了文件,必定有文件保存了文件系统的管理数据。Microsoft称它们为元文件,但这样会导致困惑,因为我们还要讨论文件元数据。我将会称这些特殊文件为文件系统元文件。
Microsoft保留了前16个MFT entry作为文件系统元文件(Microsoft文档声称只保留了前16个entry,但是实际上第一个用户文件或者目录从entry24开始。entry17到23作为缓冲entry防止预留的不够用导致溢出),这些保留并且未用的entry被置为分配状态并且只有基本的信息。每个文件系统元文件都列在根目录,尽管通常情况下普通用户是看不到它们的。每一个文件系统元文件的名字都由“$”开始,并且第一个字母大写。我们将在第12章讨论每一个文件系统元文件,但是我们现在把它们列在表11.1作为一个方便参考。
表11.1标准的NTFS文件系统元文件
Entry |
File Name |
Description |
0 | $MFT | MFT自身的entry |
1 | $MFTMirr | 含有MFT起始的几个entry的备份 |
2 | $LogFile | 含有元数据事务的日志 |
3 | $Volume | 含有卷信息 |
4 | $AttrDef | 含有属性信息 |
5 | . | 文件系统根目录 |
6 | $Bitmap | 文件系统簇的分配状态 |
7 | $Boot | 引导扇区和文件系统引导代码 |
8 | $BadClus | 含有坏扇区的簇 |
9 | $Secure | 还有安全和访问控制相关信息 |
10 | $Upcase | 含有每一个Unicode字符的大写形式 |
11 | $Extend | 含有可选扩展文件的目录。 |
MFT entry属性概念
一个MFT entry只有很少的内部结构,它的大部分空间都被用来存储属性,属性是用来存储特定类型数据的数据结构。有许多类型的属性,每种都有其独特的内部结构。例如有用于文件名、日期和时间乃至其内容的属性。这是NTFS相较于其它文件系统的不同之处之一。大部分文件系统仅用来读写文件内容,而NTFS读写属性,属性的一种包含有文件内容。
想象一下我们之前把MFT entry比喻成一个开始是空的大盒子,而属性则是在这个大盒子之中的小盒子,这些小盒子有任意的形状以最有效的存放物品。例如,一顶帽子可以存储在一个短圆盒中,一张海报可以存储在一个长圆筒中。
尽管每种属性都保存着不同类型的数据,所有的属性都有两个部分:头部和内容。图11.4显示了一个MFTentry有四个头部和内容对(译注:应该是三个,怀疑作者笔误)。头部是所有属性通用的。内容和属性类型相关,并且可以是任意大小。如果我们从盒子来类比,每一个小盒子外面都有相同的基本信息,但是每个盒子形状都不一样。
图11.4 示例MFT entry,标记了头部和内容的位置。
属性头部
属性头部标识了属性的类型、大小和名称。头部还有是否压缩和加密的标识。属性类型是一个数值的标识用来标识数据的类型,我们将会在“标准属性类型”一节讨论缺省的属性类型。一个MFT entry可以有多个相同类型的属性。
有些属性可以被赋予一个名字,以UTF-16的Unicode字符串的形式存储在属性头部。属性同时还有一个在此MFT entry中唯一的标识值。如果一个entry有多个同一类型的属性,这个标识值可以用来区分它们。属性头数据结构将会在第13章“属性头”中给出。
属性内容
属性的内容可以是任意格式任意大小。例如,一种属性被用来存储文件的内容,所以其大小可能是几个MB乃至数个GB。将这么多数据存储在仅有1024字节大小的MFT entry中是不切实际的。
为了解决这个问题,NTFS提供了两种存储属性内容的位置。常驻属性将它的内容和头部一同存储在MFT entry之内。这仅适用于小属性。非常驻属性将其内容存储在文件系统的簇中。属性的头部中标识了这个属性是常驻的还是非常驻的。如果一个属性是常驻的,则其内容紧跟头部。如果一个属性是非常驻的,头部将会给出簇地址。图11.5我们看到之前给出的MFT entry例子,不过这次它的第三个属性太大了无法放入MFT,所以被放到了829号簇中。
图11.5 MFT entry例子,第三个属性变得太大所以变成了非常驻。
非常驻属保存在cluster runs(译注:run在这里很难找到一个恰当的中文词汇,姑且不翻译了)中,由连续的簇构成,而每一个run由起始簇地址和run长度来描述。例如,如果一个属性分配了48、49、50、51和52这几个簇,则它有一个run,这个run开始于簇48,长度为5.如果这个属性还分配了簇80和81,则它有第二个run,起始于80长度为2。第三个run可能起始于簇56长度为4.可以看图11.6。
图11.6 示例runlist,有三个描述分配簇的run。
在本书中,我们区分了不同类型的地址。例如,我们定义了文件系统逻辑地址是文件系统数据单元的地址,二文件逻辑地址是相对于文件起始的地址。NTFS对这些地址使用了不同的术语。逻辑簇编号(Logical Cluster Number,LCN)等价于文件系统逻辑地址,虚拟簇编号(Virtual Cluster Number,VCN)等价于文件逻辑地址。
NTFS使用VCN到LCN的映射来描述非常驻属性的run。回到刚才的例子,这个属性的run显示了VCN地址0到4映射到了LCN地址48到52,VCN地址5到6映射到了LCN地址80到81,VCN地址7到10映射到了LCN地址56到59。runlist的数据结构将会在第13章“属性头”中给出。
标准属性类型
到目前为止我们已经讨论了属性类型的一般术语。现在我们要看看一些标准属性的基础知识。它们之中的大部分将会在第12章中详细讨论。
之前我们已经提及,每一种属性类型都被定义了一个编号,微软使用这个编号对一个entry中的属性进行排序。标准属性被赋予了缺省的类型值,但我们稍后将会看到这可以在$AttrDef文件系统元文件中重定义。除了编号之外,每一个属性类型有一个名字,都是大写并且以“$”开头。部分缺省属性类型和它们的标识在表11.2中给出。不是所有的属性类型和标识都会出现在每一个文件之中。更详细的描述请看第12章,数据结构详见第13章。
表11.2 缺省的MFT entry属性类型。
(译注:Live Writer太操蛋,粘贴表格蛋都碎了,暂且略过)
几乎每一个被分配的MFT entry都有$FILE_NAME和$STANDARD_INFORMATION类型的属性。一个例外是非基础MFT entry(译注:base MFT entry),稍后将会讨论。$FILE_NAME属性包含有文件的名字、大小和时间信息。$STANDARD_INFORMATION属性含有时间、所有关系和安全信息。后者(译注:$STANDARD_INFORMATION)存在于每一个文件和目录的原因是由于其含有用于确保数据安全和配额的数据。在抽象的意义上,在此属性中并无必要的数据,但文件系统的应用程序层功能要求这些数据。这两个属性都是常驻的。
每一个文件都有一个$DATA 属性,其包含有文件的内容。如果内容尺寸超过大约700字节,它就会变成一个非常驻属性,保存到外面的簇中。当一个文件含有多于一个$DATA 属性时,这些多出的属性有时被称为附加数据流(alternate data streams,ADS)。当创建文件时创建的缺省$DATA并没有名字,但是多出的$DATA属性必须有。注意属性名字和类型名字是不同的。例如,$DATA是属性类型名,属性的名字可能是“fred”。有些工具,包括The Sleuth Kit (TSK),会给缺省的$DATA属性指定一个叫做“$DATA”的名字。
每一个目录都有一个$INDEX_ROOT属性,这个属性含有其包含的文件和目录的信息。如果这个目录很大,$INDEX_ALLOCATION和$BITMAP属性也用于存储信息。令事情更复杂的是,一个目录也可以在$INDEX_ROOT之外还含有额外的$DATA属性。也就是说,目录可以同时存储文件内容和其所包含的文件和子目录的列表。$DATA属性可以存储应用程序或者用户想存储的任何数据。一个目录的$INDEX_ROOT和$INDEX_ALLOCATION属性通常其名字是“$I30”。
图11.7 显示了我们之前演示的一个MFT entry,它的属性被赋予了类型和名字。它有三个标准文件属性。在这个例子中,所有的属性都是常驻的。
图11.7 我们的MFT entry例子,属性被加上了名字和标识。
其他属性概念
上一节我们讨论了适用于所有NTFS属性的基本概念。不过不是所有的属性都是基本的,本节我们将会看到更高级的概念。特别的,我们将会看到当一个文件有太多的属性时将会发生什么,我们也会看到文件的属性内容被压缩和加密地方法。
基础 MFT entry(base MFT entry)
一个文件最多可以有65536个属性(由于标识符是16位的),所以其可能需要多于一个MFT entry来保存它的所有的属性的头部(即便是非常驻属性也需要它们的头部保存在MFT entry内部)。当附加的MFT entry被分配给一个文件的时候,原来的MFT entry被称为基础MFT entry。非基础entry会在它们的MFT entry域中保存有基础MFT entry的地址。
基础MFT entry有一个$ATTRIBUTE_LIST类型的属性,这个属性含有文件的每一个属性和MFT地址,以便于查找到它们。非基础MFT entry没有$FILE_NAME和$STANDARD_INFORMATION属性。我们将会在第12章“元数据分类”中详细讨论$ATTRIBUTE_LIST属性。
稀疏属性
NTFS可以降低文件的空间需求,这是通过将某些非常驻$DATA属性的数据保存为稀疏来实现的。稀疏属性是那些全为零的簇没有写入磁盘的属性。作为替代,一个特殊的run用来保存零簇(译注:含有全零数据的簇)。一般来说,一个run含有起始簇位置和长度,但是一个稀疏run只含有长度没有起始位置。有一个标志指出一个属性是稀疏的。
例如,假设一个文件占用了12个簇。起始的五个簇非零,接下来的三个簇都是零,最后的四个簇非零。当它保存为一个普通的属性,一个长度为12的run将会被创建出来以保存这个文件,如图11.8A所示。当保存为稀疏属性,将会有三个run被创建出来,并且只有9个簇被实际分配,如图11.8B所示。
图11.8 一个12簇长的文件被保存为(A)普通布局或,(B)稀疏布局,有三个簇在稀疏run中。
压缩属性
NTFS允许属性被保存为压缩格式,尽管实际的算法并没有公开给出。注意这是一个文件系统级别的压缩,并不是一个通过诸如zip或者gzip等外部应用程序实现的应用级压缩。微软表示只有非常驻的$DATA属性会被压缩。NTFS通过稀疏run和压缩数据来减少磁盘空间需求。属性头有一个标识来确定其是否压缩,$STANDARD_INFORMATION和$FILE_NAME属性内的标识也显示了这个文件是否含有压缩属性。
属性内容压缩之前,数据被划分成等大的块,称为压缩单元。压缩单元的大小在属性头中给出。有三种情形会发生在每个压缩单元中:
让我们以一个简单的例子来检验每一种场景。假设压缩单元大小事16个簇,并且我们有一个长度为64个簇的$DATA属性,见图11.9。我们把数据切分为四个压缩单元并检查每一个。第一个单元压缩为16个簇,所以它没有压缩。第二个单元全为零,所以创建了一个16簇的稀疏run,没有分配实际的簇。,第三个单元压缩成了10个簇,所以压缩后的数据以10个簇的run写入到磁盘,并且添加了一个6个簇的稀疏run。最后一个单元压缩为16个簇,所以他没有压缩,创建了一个16簇的run来保存它。
图11.9 一个属性有两个压缩单元没有被压缩,一个单元稀疏,一个单元压缩为10个簇。
当操作系统,或者取证工具读取这个属性,它们会发现压缩标志,并且run被组织成压缩单元大小的块,第一个run的大小和压缩单元一样,所以我们知道它没有被压缩。第二个run的大小和压缩单元一样,并且是稀疏的,所以我们知道它们是16个簇的零。第三个和第四个run组成了一个压缩单元,我们发现它只需要10个簇也就是需要解压缩,最后一个run大小和压缩单元一样,也就是没有压缩。
最后一个例子太简单了,所以我将会演示一个更具挑战性的文件,见图11.10,之所以更复杂是由于初始的布局并不是按照压缩单元来分配的。为了处理这个文件,我们首先需要重新组织这6个run中的数据并将数据分配为16个簇的压缩单元中。在合并了碎片化的run之后,我们看到有一个含有内容的run,一个稀疏run,一个内容run,另一个稀疏run。合并的数据被组织为压缩单元,我们将会看到,前两个单元没有稀疏run,也没有被压缩。第三个和第五个单元有稀疏run,被压缩了。第四个单元是稀疏的,相应的数据全为零。
图11.10 一个将要被压缩的属性,它的run碎片化并且没有符合压缩单元边界。
加密属性
NTFS提供属性内容被加密的能力。这一节将给出一个其实现和保存磁盘上形式的概要。理论上说,任意属性都可以加密,但是Windows仅允许$DATA属性被加密。当一个属性被加密,只有其内容被加密,属性头没有加密。会创建一个$LOGGED_UTILITY_STREAM属性给文件,其含有解密数据所需要的密钥。
在Windows中,用户可以选择加密特定的文件或者目录。一个加密的目录没有任何加密数据,但是在这个目录中创建的文件或者目录将会被加密。一个被加密的文件或者目录将会在$STANDARD_INFORMATION属性中设置一个特殊的标志,每一个被加密的属性都会在其属性头设置一个特殊的标志位。
密码学常识
在我们讨论NTFS的加密实现之前,我将会简单介绍一下密码学的基本概念。加密过程是使用加密算法和密钥将明文变换为密文。解密过程是使用解密算法和密钥将密文变换为明文。如果有人拿出密文,那么在没有密钥的情况下应该是无法获知明文内容的。
有两类加密算法:对称和非对称。对称算法使用相同的密钥来加密和解密数据。例如,密钥“spot”可以被用来将明文加密成密文,然后通用的密钥可以被用来将密文解密成明文。对称加密非常快,但是当共享密文数据的时候就会很麻烦。如果我们使用对称加密来加密一个文件并且想让很多人访问这个文件,我们要么需要使用一个谁都知道的密钥来加密这个文件,要么把这个文件复制多份并使用每个人唯一的密钥来分别加密这个文件。如果我们为所有人只使用一个密钥,那么为某一个人撤销访问许可而不更换密钥就会变得非常困难。如果我们为每一个人单独加密,就会浪费大量的磁盘空间。
非对称加密手机用一个密钥来加密并且使用另一个密钥进行解密。例如,密钥“spot”被用来将明文加密成密文,密钥“felix”被用来将密文解密。非对称加密的最常见用途是将一个密钥公开,例如“spot”,而另一个密钥不公开,例如“felix”。这样大家就可以用公钥对数据进行加密,但是仅能用私钥进行解密。显然,在真实世界里密钥会比“spot”和“felix”长的多。事实上,一般至少有1024位那么长。
NTFS实现
当一个NTFS的$DATA属性被加密时,它的内容被一个称为DESX的对称加密算法所加密。为每一个MFT entry的加密数据都随机生成了一个密钥,这个密钥被称为“file encryption key,FEK”。如果一个MFT entry有多个$DATA属性,它们使用同一个FEK进行加密。
FEK以加密的形式储存在$LOGGED_UTILITY_STREAM属性中。这个属性包含一个数据加密域“data decryption fields,DDF”和数据恢复域“data recovery fields,DRF”的列表。DDF被创建用来存放每一个有权访问这个文件的用户的安全ID(SID),加密信息,和使用用户公钥加密的FEK。数据恢复域被创建用于每一个数据恢复方法,它含有当管理员或者其他授权用户需要访问数据的时候所使用的用数据恢复公钥加密的FEK。我们可以在图11.11看到这个过程。
图11.11 加密过程起始于文件内容和公钥,结束于已加密内容和已加密密钥。
要想解密一个$DATA属性,$LOGGED_UTILITY_STREAM属性需要被处理来获取用户的DDF入口。用户的私钥被用来解密FEK,FEK被用来解密$DATA属性。当一个用户的访问权限被撤销时,他的密钥被从管理表中移除。用户的私钥保存在Windows的注册表中,并使用用户的登录口令作为密钥进行对称加密。这也就是说,在取证分析过程中需要用户的口令和注册表来解密任何已被加密的文件。我们可以在图11.12看到这个过程。
图11.12 解密过程起始于已加密内容,密钥和用户口令,结束于已解密内容。
有些安全工具可以用于暴力攻击用户的登录口令,这也可以被用于解密数据。如果只有部分目录和文件被加密,未加密的文件内容副本也可能存在于未分配的磁盘空间中。事实上,NTFS的设计上存在小小的瑕疵,它创建一个名叫EFS0.TMP的临时文件,这个文件用来保存被加密文件的明文。在操作系统对原始文件完成加密后,它删除了临时文件,但是文件的内容并没有被清除。也就是说,一个明文版的文件依然存在,数据恢复工具可以在MFT entry没有被再分配的情况下恢复出这个文件。交换空间或页面文件也有可能提供未加密数据的副本。有报告称,如果管理员、域控制器或者其他账号被配置为具有恢复代理权限,则任意文件可以被解密,因为这个账号有权访问任意文件。
索引
NTFS在很多情形下使用索引数据结构,本节描述它们。NTFS中的索引是一个属性的集合,它们有序存放。索引最常见的用途是目录,这是因为目录含有$FILE_NAME属性。
在NTFS版本3.0(Win2000引入)以前,只有$FILE_NAME属性保存在索引中,但是现在其它含有属性的地方也使用索引。例如,安全信息保存在索引中,配额也是。本节展现了什么是索引和它是怎么是现实的。
B树
NTFS索引将属性排序到树中,特指B树。树是一组称为节点的数据结构彼此连接在一起,有一个头节点以及该节点的分支链接到其他节点。在图11.13(A)中,我们看到节点A,并且它链接到节点B和C。节点B链接到节点D和E。父节点是链接到其他节点的节点,子节点是被链接的节点,例如,A是B和C的父节点,B和C是A的子节点。叶节点是没有子节点的节点,节点C、D和E是叶节点。这个例子是二叉树,因为每个节点最多只有两个字节点。
图11.13 (A)5个节点的树,(B)同一个树,按照节点值排序。
树之所以有用是因为它们很容易排序和查找。图11.13(B)显示了同左边一样的树,但是现在每个节点都被赋予了一个值。如果我们试图查找一个值,我们将它和根节点比较,如果根节点较大,我们接下来查找左子节点。如果根节点较小,我们查找右子节点。例如,如果我们想要查找6,我们和根节点7比较。节点较大,所以我们去左边的子节点比较它的值,它是5。这个节点较小,所以我们继续从右子节点继续查找并比较它的值,这次是6。仅通过三次比较我们就找到了。我们可以仅用两次查找就找到9,如果是在列表中就需要搜索5次。
NTFS使用B树,恨我们刚才看见的二叉树很相似,但是每个节点有多于两个子节点。一般来说,一个节点有多少个子节点取决于一个节点能够存储多少个值。例如,在二叉树中一个节点能存储一个值和两个字节点。如果我们能在一个节点中存储五个值,我们可以有六个字节点。有很多B树的变种,相对于我在这里讨论的而言它们有更多的规则,这是由于本节是用来讨论它们的基本概念,而不是你如何创建一个B树。
图11.14显示了一棵B树,包含名字而不是数值。节点A含有三个值和四个子节点。如果我们查找文件ggg.txt,我们在根节点中查找并且发现名字在字母排序的eee.txt和lll.txt之间。也就是说,我们接下来去节点C来查找,我们将在这个节点中找到我们所要的。
图11.14 一个以文件名作为值的B树。
现在我们让情况更复杂些,我们看看如何增加和删除值。这是一个很重要的概念,它解释了为什么在NTFS中删除的文件名很难找回。让我们假设一个节点仅能存储三个文件名,然后文件jjj.txt被添加进来。听起来很容易,但是我们将看到这会导致两个节点被删除,五个新节点被创建。当我们查找jjj.txt放在哪里合适时,我们发现它应该在节点C的末尾,紧跟iii.txt后面。图11.15的上面显示了这种情况,但是不幸的是,现在有四个名字在这个节点了,而它只能放下三个。也就是说,我们从中间把节点C断开,将ggg.txt移动到上一层,并创建节点F和G来存放节点C的内容。这显示在图11.15的下面。
图11.15 上面的树显示了“jjj.txt”添加到节点C,下面的树是删除节点C的结果,因为每个节点只能有三个文件名。
不幸的是,现在节点A有了四个值。所以我们拆分它并将ggg.txt移动到顶层节点。最终的结果可以在图11.16中看到。添加一个文件导致了删除节点A和C,增加了节点F,G,H,I和J。节点A和C中先前删除的文件信息现在不复存在。
图11.16 增加了文件“jjj.txt”之后的最终状态。
现在删除文件zzz.txt,这个操作从节点E删除了文件名,并不需要其它改变。和实现有关,文件zzz.txt的细节可能依然存在于节点中并且可以被恢复。
为了使事情变得困难些,想象一下fff.txt被删除。节点F变为空节点并且需要填充。我们将eee.txt从节点I移动到节点F并将bbb.txt从节点B移动到节点I。这创建了一棵仍然平衡的树,所有的叶节点到节点H距离相同。最终的状态见图11.17.
图11.17 删除文件“zzz.txt”和文件“fff.txt”之后的树。
节点B可能在其未分配空间里含有bbb.txt,因为bbb.txt移动到了节点I。我们的分析工具可能会显示文件bbb.txt已经删除,但实际上并非如此。它仅仅因为fff.txt的删除而被移动了。
从树中添加和删除值的过程表明这个过程可能会是多么复杂。其他文件系统,例如FAT,其目录使用名字列表,很容易描述为什么一个已删除的名称存在或者不存在,但是在使用树的情况下很难预测最终结果。
NTFS索引属性
现在我们描述B树的一般概念,我们需要描述NTFS中它们是如何实现以创建索引的。树中的每一个数据项使用一个被称为index entry的数据结构来在节点中保存数据。有很多种index entry,但是它们都有相同的标准头域,详见第13章。例如,一个目录index entry包含一些头数据和一个$FILE_NAME属性。index entry被组织到树的节点中,并被保存到列表中。一个空的enrry表示列表结束。图11.18显示了一个示例目录节点,有四个$FILE_NAME index entry。
图11.18 NTFS目录中的一个节点,有四个index entry。
索引节点可以保存在两种MFT enrty属性中。$INDEX_ROOT属性是常驻的,并且仅可以用来保存一个含有少量index entry的节点。$INDEX_ROOT属性始终是索引树的根节点。
更大的索引需要分配一个非常驻的$INDEX_ALLOCATION属性,它含有所需的所有节点。这个属性的内容是一个大缓冲区,含有一个或多个索引记录。索引记录是固定大小的,一般是4096字节,它含有一个index entry的列表。每一个索引记录有一个从0开始的地址。我们可以在图11.19中看到我们有一个含有三个index entry的$INDEX_ROOT属性,和一个非常驻的$INDEX_ALLOCATION属性,它含有一个分配的簇713,使用了三个索引记录。
图11.19 这个目录在它的常驻$INDEX_ROOT属性中有三个index entry,同时在它的非常驻$INDEX_ALLOCATION属性中有三个索引记录。
$INDEX_ALLOCATION属性可以有还未被索引记录使用的已分配空间。$BITMAP属性用来管理索引记录的分配状态。如果一个新的节点需要为树分配,$BITMAP被用来查找一个可用的索引记录;否则的话,更多的空间被加入进来(以完成分配)。每一个索引都被赋予一个名字,与之相关的$INDEX_ROOT、$INDEX_ALLOCATION和$BITMAP 属性也在属性头中被赋予相同的名字。
每一个index entry都有一个标志来显示其是否具有子节点。如果有子节点,它们的索引记录地址将会在index entry中给出。每一个节点中的index entry是排序的,如果你正在查找的值比index entry小并且index entry有子节点,那么在子节点中查找。如果你到达了列表末尾的空entry,那么在它的子节点中查找。
然我们用一些例子说明。想象一下一个索引有三个entry正好放进$INDEX_ROOT。在这种情况下,仅有一个$INDEX_ROOT被分配出来,它含有这三个index entry数据结构和一个空的entry在列表的末尾。图11.20(A)中可以看到这种情况。现在想想一下一个索引有15个entry的情况,这样$INDEX_ROOT就放不下了,但是可以放进$INDEX_ALLOCATION属性的一个索引记录中。可以在图11.20(B)中看到这种情况。当我们填充了索引记录的index entry后,我们需要增加一个新的层次,我们创建了一个三结点树。这种情形显示在图11.20(C)中。有一个值在根节点和两个字节点。每一个子节点保存在同一个$INDEX_ALLOCATION属性的不同索引记录中,并且$INDEX_ROOT节点中的entry指向它们。
图11.20 三种NTFS索引情形:(A)小的索引有三个entry,(B)大索引有两个节点和15个entry,(C)三结点树含有25个entry。
分析工具
第1章提及的所有工具都支持NTFS镜像,他们可以访问不同数量的属性。如果你对查看你的Windows系统上的不同属性有兴趣,可以使用微软的nfi.exe。它显示了活动系统中的MFT内容,包括属性名和簇地址。这对于取证分析并没有太大的用处,因为系统必须运行,但是有助于学习NTFS。Mark Russinovich的NTFSInfo提供了类似的活动系统上的信息。
TSK允许你查看任意属性的内容,为了使后续两章的例子更清晰,我将会描述查看不同属性的语法。回想一下,每一个属性都有类型值,并且MFT entry的每一个属性都有一个唯一的标识符。通过这两个值,我们可以显示任意属性。
取代指定元数据地址的是,我们可以给icat指定地址和属性类型。如果有多于一个的指定类型的属性,我们给出唯一的标识符。例如,如果我们检查MFT entry 34,我们可以通过指定“34-48”来查看$FILE_NAME属性(类型48)。如果我们想看它的$DATA属性(类型128),我们指定“34-128”,如果有多个$DATA属性,我们还需要指定唯一的属性标识符。如果我们想要查看的有ID3,指定“34-128-3”。
TSK中的istat工具可以列出一个文件的所有属性。下面是一个MFT entry的属性输出:
[REMOVED]
Type: $STANDARD_INFORMATION (16-0) Name: N/A Resident size: 72
Type: $FILE_NAME (48-2) Name: N/A Resident size: 84
Type: $OBJECT_ID (64-8) Name: N/A Resident size: 16
Type: $DATA (128-3) Name: $Data Non-Resident, Encrypted size: 4294
94843 94844 94845 94846 94847 94848 102873 102874
102875
Type: $DATA (128-5) Name: ADS Non-Resident, Encrypted size: 4294
102879 102880 102881 102882 102883 102884 102885 102886
102887
Type: $LOGGED_UTILITY_STREAM (256-7) Name: $EFS Non-Resident size: 552
102892 102893
总结
NTFS之中的每一个关键元素都被关联给了一个文件或者一个索引。本章之中,我们讨论了NTFS的核心概念:MFT入口、属性和索引。通过使用这些基本的概念,我们现在就可以检查特定的属性和分析第12章中的分类项目。
参考资料
略,请看原著。