NTFS 结构说明
关键字:Windows NT NTFS 文件系统 MFT
在Windows NT?中,Microsoft使用了一种新型的文件系统NTFS,它针对FAT/FAT32文件系统安全性差、容易产生碎片、难以恢复等缺点作了重大改进,使得系统总能保持较好的性能。不过使用NTFS的分区只能被Windows NT?系统识别和操作,而且它的结构是Microsoft的内部机密,没有任何官方文档。因此给各位需要在非Windows NT?环境读写NTFS分区的编程人员带来了巨大不便。不过好在还有很多人在研究分析它,现在结合网上搞到的一些资料和我自己的分析对NTFS作一个说明(可惜还是有很多东西没能搞明白)。
一、 概述
在NTFS中一改FAT/FAT32中将文件分配表等系统数据放在分区最前面,然后接着才是数据区的做法,把所有的信息都组织起来以文件的形式存放,包括扇区分配表,引导记录等数据都是作为文件存在于磁盘中。其中只有$Boot(分区引导记录)文件的位置是固定存放在分区首部,其他的文件都可以随意存在于分区中的任何一个位置。这样使得对所有数据的读写都有一个较为统一的方法,而且使得改变分区的尺寸和碎片整理变得非常容易。
在NTFS分区中,存储分区信息的文件被称为系统文件。在每个NTFS分区中都有十个这样的系统文件,它们分别是:
$MFT
$MFTMirr
$LogFile
$Volume
$AttrDef
. (分区根目录)
$Bitmap
$Boot
$BadClus
$Secure
$UpCase
$Extend
下面分别对每个系统文件的作用及所保存的信息作一个说明:
$MFT(Master File Table)文件。这个文件是NTFS分区中最重要的文件,它记录了分区中所有文件(包括$MFT自身)的基本信息。通过$MFT就可以访问分区中的所有文件和系统数据。$MFT由多个MFT记录单元组成,每一个文件的描述占用一到多个(一个不够的情况下)$MFT记录单元。其中前十二个记录单元中记录着上面的十二个系统文件的信息。每个记录单元记录着文件的建立时间、在分区中的位置、长度、属性、文件名等信息。信息在MFT单元中用各种属性来表示,不同的属性代表着不同的信息,而文件或目录中的内容则表现为属性中的外部或内部(取决于数据的长度)附加数据。其记录格式见下面的MFT格式说明。
$MFTMirr(Mirror of Master File Table) 文件。这个文件是$MFT文件的一个备份,它保存着$MFT文件前4个记录单元的信息。格式同$MFT。
$LogFile文件。记录中对分区的操作的日志信息。(日志记录具体格式不明)
$Volume文件。保存着分区建立时间,分区版本等信息。
$AttrDef文件。记录的MFT单元中的属性的基本信息,包括属性的名字,最大最小尺寸等
. (分区根目录)文件。作用同FAT/FAT32中的根目录,但记录格式有所变化。关于记录格式的描述请参考目录项存储区格式。
$Bitmap文件。记录着分区中存储单元(簇)使用状况。类似于FAT/FAT32中的FAT表,但不以索引的形式记录,而是每一个Bit表示一个簇。其值为1表示该簇已被使用,为0则表示空闲。
$Boot文件。NTFS分区的引导记录。在NTFS分区引导系统时负责将NTLDR装入内存并将控制权交给它。其中记录着分区的总扇区数,分区中每个存储单元(簇)包含的扇区数,$MFT所在的簇的簇号,$MFTMirr所在的簇的簇号和分区引导例程等重要信息。其格式见后面的$Boot文件格式说明。注意:在NTFS分区中$Boot文件的位置固定在分区开始处。
$BadClus文件。似乎是记录着分区中有效(可以正常使用)的簇的个数和分区有效容量(以字节为单位)。因为我手里没有有坏道的硬盘,所以不敢肯定。
$Secure文件。就其名而言似乎是系统安全描述,不过没有研究过。
$UpCase文件。Unicode编码下的大写字母表。
$Extend文件。用于和HPFS兼容的信息。
二、 读写操作流程
在非NT的系统中对NTFS分区中的文件进行自由的读写操作才是我们研究NTFS分区格式的最终目标。下面我就对整个读操作的流程来一个说明:
首先从硬盘的分区表中计算出分区开始的位置,再读取分区数据区中的第一个扇区($Boot文件的第一个扇区)从中取得隐藏扇区数目,$MFT开始的簇号,每簇扇区数,每个MFT记录的尺寸。通过这些数据计算出$MFT所在的位置,读取第一个MFT记录,然后按照记录中描述的$MFT文件的存放位置读出文件名为“.”的MFT记录(根目录文件)。然后在该记录中搜索要求的目录项。找到以后,获得它的描述在$MFT中的索引,再在其中寻找下级目录的索引……直到找到为止。
三、 改变分区尺寸操作流程
NTFS分区的尺寸只与四个记录有关:
1、$Boot文件的第一个扇区中的分区扇区数。
2、分区最后一个扇区中的$Boot文件第一扇区的备份。
3、系统文件$Bitmap的尺寸。
4、系统文件$BadClus中的有效簇描述。
因此修改NTFS分区的尺寸就比修改FAT/FAT32分区的尺寸要容易得多。只需要改变前两处的分区扇区数,再调整$Bitmap文件的尺寸即可。因为前两处的修改十分容易,这里就只讲解一下如何修改后两处。
首先在$MFT文件中找到分区根目录的位置,读取根目录记录,从中找到$Bitmap文件所在的项,修改其占用的存储器空间和实际尺寸并保存。然后按照该项的描述读出$MFT中描述$Bitmap文件的记录,修改其文件属性中的VCN结束段的索引号、占用空间数、数据尺寸、有效数据尺寸并适当地增/减$Bitmap文件的数据流描述,如需要的话还要调整它的$Ddata属性的尺寸。再修改$Bitmap文件的内容以标志出文件中新添/缩减的簇。
其次是修改$BadClus文件中第二个$Data属性中的VCN结束段索引、占用空间数、数据尺寸、有效数据尺寸及数据流描述。但要注意!在$BadClus文件中记录的是整个分区中没有问题的簇数及其描述,与$Bitmap文件中修改的值不同。
然后……天下太平了。
记录格式说明
一、MFT记录头
偏移地址 |
占用字节数 |
描述 |
0H |
4 |
MFT 记录标志,ASCII字符串:“FILE” |
4H |
2 |
修正值的偏移地址(关于修正值请参考修正值技术) |
6H |
2 |
修正值个数+1 S |
8H |
2 |
日志文件序列号LSN |
10H |
2 |
序列号 |
12H |
2 |
链接计数 |
14H |
2 |
属性数据的偏移地址 |
16H |
2 |
数据标志,Bit0为1表示有外部数据,Bit1为1表示当前记录是一个目录项 |
18H |
4 |
本MFT记录用到的存储空间(包括记录头和数据) |
1CH |
4 |
本MFT记录占用的存储空间 |
20H |
8 |
如果本MFT记录是从MFT记录,则存放主MFT记录在$MFT中的位置,否则为0 |
28H |
2 |
一个自由属性的ID |
2AH |
S*2 |
修正值表 |
二、属性记录头
偏移地址 |
占用字节数 |
描述 |
0H |
4 |
属性类型,关于属性类型请参考属性类型描述 |
4H |
2 |
本记录尺寸 |
8H |
1 |
外部数据标志,为0则表示数据是内部数据,就根在记录头后面,为1则表示属性数据是外部数据,需要另外读取 |
9H |
1 |
记录名(如果有的话)或内部数据尺寸 |
0AH |
2 |
记录名(如果有的话)或内部数据相对于记录开始位置的偏移地址 |
0CH |
2 |
压缩标志,为1表示文件被压缩 |
0EH |
2 |
属性ID对于内部数据或记录名 |
10H |
4 |
数据尺寸 |
14H |
2 |
数据相对于记录开始位置的偏移地址 |
16H |
2 |
属性索引标志对于外部数据 |
10H |
8 |
本段数据开始簇号(注:这里的簇不同于分区存储单元的簇) |
18H |
8 |
本段数据结束簇号 |
20H |
2 |
数据存储位置描述区相对于记录开始位置的偏移 |
22H |
2 |
压缩引擎序号 |
28H |
8 |
本段数据占用的存储空间 |
30H |
8 |
本段数据的尺寸 |
38H |
8 |
已初始化的数据尺寸 |
40H |
8 |
数据压缩后的尺寸 |
记录格式:首先是记录名(如果有记录名),紧接着就是内部数据或外部数据索引。关于外部数据索引请参考:外部数据索引格式
三、属性类型
0四、属性数据格式
1、$Standard_Information
偏移地址 |
占用字节数 |
描述 |
0H |
8 |
文件创建时间 |
8H |
8 |
文件最后一次修改的时间 |
10H |
8 |
文件记录最后一次修改的时间 |
18H |
8 |
最后一次操作文件的时间 |
20H |
4 |
DOS文件属性 |
24H |
0CH+ |
不详,似乎总为0 |
其中DOS文件属性字各Bit的意义描述如下:
Bit |
描述 |
0 |
只读 |
1 |
隐藏 |
2 |
系统 |
5 |
归档 |
18 |
符号链接 |
19 |
压缩 |
2、$Attribute_List
3、$File_Name
偏移地址 |
占用字节数 |
描述 |
0H |
4 |
包含该文件的文件记录在$MFT中的索引 |
6H |
2 |
不详 |
8H |
8 |
(?)时间 |
10H |
8 |
(?)时间 |
18H |
8 |
(?)时间 |
20H |
8 |
(?)时间 |
28H |
8 |
文件占用的存储器空间 |
30H |
8 |
文件的实际尺寸 |
38H |
8 |
文件属性,其具体描述请参考$Standard_Information |
40H |
1 |
文件名字符串长度(Unicode) |
41H |
1 |
文件名类型 |
42H |
1 |
Unicode码文件名 |
其中文件名类型的说明如下:
值 |
描述 |
0 |
POSIX |
1 |
Unicode |
2 |
DOS(8.3) |
3 |
Unicode、DOS通用 |
4、$Object_ID
5、$Security_Descriptor
6、$Volume_Name
$Volume_Name属性的数据就是Unicode编码的分区卷标。
7、$Volume_Information
$Volume_Information属性中记录了分区的版本号等信息。
偏移地址 |
占用字节数 |
描述 |
8H |
1 |
主版本号 |
9H |
1 |
子版本号 |
0AH |
1 |
磁盘查错标志,为1表示需要在启动时运行磁盘查错程序 |
8、$Data
$Data属性的数据由用户自己定义。关于系统常用的VCN和LCN的格式请参考数据流描述。
9、$Index_Root
$Index_Root属性中记录着目录中的文件或子目录的目录项信息,
偏移地址 |
占用字节数 |
描述 |
0H |
4 |
|
4H |
4 |
|
8H |
4 |
|
0CH |
4 |
目录项存储区尺寸 |
10H |
4 |
|
14H |
4 |
|
18H |
4 |
|
1CH |
2 |
是否使用目录项存储区标志 |
1EH |
2 |
10、$Index_Allocation
其数据是描述目录项数据位置的数据流描述。
11、$Bitmap
其数据是以一Bit代表一个分配单元的存储状态位图。为1则表示该簇已经被使用,为0则表示该簇空闲。
12、$Reparse_Point
作用不明
13、$EA_Information
作用不明
14、$EA
作用不明
15、$Logged_Untility_Stream
作用不明
五、目录项存储区格式
偏移地址 |
占用字节数 |
描述 |
0H |
4 |
目录项存储区标识:“INDX” |
4H |
2 |
修正值表相对于存储区开始处的偏移 |
6H |
2 |
修正项个数+1 |
10H |
8 |
|
18H |
2 |
记录尺寸 |
1CH |
4 |
存储区中有效数据的尺寸 |
20H |
4 |
存储区最大可用尺寸(除去记录头18H字节) |
24H |
4 |
|
28H |
A+ |
修正值表 |
18H+ |
目录项记录 |
其中目录项记录的格式如下:
偏移地址 |
占用字节数 |
描述 |
0H |
4 |
包含该文件的文件记录在$MFT中的索引 |
6H |
2 |
不详 |
8H |
2 |
本记录尺寸 |
0AH |
2 |
该文件的文件名属性记录尺寸 |
10H |
8 |
存放此记录的目录在MFT中的索引(父目录索引) |
18H |
8 |
|
20H |
8 |
|
28H |
8 |
|
30H |
8 |
修改时间 |
38H |
8 |
文件占用的存储空间 |
40H |
8 |
文件的实际尺寸 |
48H |
8 |
|
50H |
1 |
文件名字符串长度(Unicode) |
51H |
1 |
文件名类型,关于文件名类型的描述请参考前面的$File_Name属性的说明 |
52H |
[50H*2] |
Unicode编码的文件名 |
修正值技术:
使用修正值技术的目的是用来确定记录是否被完全正确地写入存储设备。所有的重要数据都使用的这项技术。这些重要数据包括MFT记录,目录项记录和日志记录。
修正值技术是通过往每个记录的最末两个字节写入一个特定的校验值来实现写操作检验的,但一个记录通常会多于一个扇区,而且在进行写操作时我们并不知道扇区写入时的顺序,因此只好在每个扇区的最后两个字节都写入校验值。
它的操作流程如下:
1、 写操作
首先在内存中将修正值加1,然后用它替换缓冲区中每个扇区最后两个字节处的数据,把被替换的数据存放到修正值表中;再把所有数据写入存储设备。
2、 读操作
首先把整个记录读入内存中,检查记录标志(如FILE或INDX等)是否正确;然后检查每个扇区的最后两字节是否与修正值相同,如不同则表明在上次写操作时发生了错误;如果修正值检查通过,就将修正值表中的原始数据还原到相应的位置,然后就可以进行其他操作了。
数据流描述
在NTFS分区中最基本的存储单元是数据流,每个数据流由它的起始簇号和尺寸来描述。一个数据流的起始簇号是相对于上一簇的开始位置的偏移而不是绝对簇号,而且描述流起始簇号和尺寸的域都是可变长度的。流描述的第一个字节指明了它们两者的尺寸,其中高4Bit指明描述起始簇的域的尺寸,低4Bit则指明了流尺寸域的尺寸。当一个流描述的第一个字节为0里就表示该流已经结束。
现举例如下。
设有一段数据流描述:21 20 ED 05 22 48 07 48 22 21 28 C8 DB
第一个数据流:开始于簇5EDH的20H个簇(5EDH-60CH)
第二个数据流:开始于簇2835H的748H个簇(2835H=5EDH+2248H)
第三个数据流:开始于簇3FDH的28H个簇(3FDH=2835H+0DBC8H)
注意:这里的数据流偏移值是一个有符号数!
$Boot文件结构
偏移地址 |
占用字节数 |
描述 |
0H |
3 |
跳转到启动例程的指令 |
3H |
4 |
NTFS分区标志:“NTFS” |
BH |
2 |
每扇区字节数 |
DH |
1 |
每簇扇区数 |
EH |
2 |
保留扇区数 |
10H |
1 |
FAT个数(为了与FAT/FAT32的BPB兼容,值为0) |
11H |
2 |
根目录项数,值为0 |
13H |
2 |
分区扇区数,值为0 |
15H |
1 |
存储介质(扇区为F8) |
16H |
2 |
每个FAT占用扇区数,值为0 |
18H |
2 |
每道扇区数 |
1AH |
2 |
磁头数 |
1CH |
4 |
隐藏扇区数 |
20H |
4 |
分区扇区数,值为0 |
24H |
1 |
物理设备号(每一个硬盘为80H) |
25H |
1 |
未用 |
26H |
2 |
保留,值为80H |
28H |
8 |
分区扇区数-1 |
30H |
8 |
$MFT第一簇簇号 |
38H |
8 |
$MFT Mirr第一簇簇号 |
40H |
1 |
每个文件记录占用的簇数,具体描述请见表后注释 |
44H |
4 |
每个目录项存储区占用的簇数,具体描述同上 |
48H |
8 |
分区序列号 |
作为分区引导时被执行的第一段代码,$Boot有着为整个系统开路的重要作用,它是NTFS分区中唯一一个位置固定不变的文件;在它里面又存储着$MFT的位置,分区尺寸等重要数据。文件系统驱动程序必须读取其中的数据才能定位$MFT,才能对整个分区进行操作。因此在这里也需要将它的结构作一个说明。
注:每个文件记录占用的簇数如果为否值则表明记录尺寸小于簇尺寸,此时计算文件记录尺寸的方法应该是:记录占用的字节数=1(~每文件记录占用簇数)。