ntfs文件系统服务器版,详解NTFS文件系统+NTFS 文件系统解析

NTFS、用过Windows系统的人都知道,它是一个很强大的文件系统,支持的功能很多,存储的原理也很复杂。目前绝大多数Windows用户都是使用NTFS文件系统,它主要以安全性和稳定性而闻名,下面是它的一些主要特点。

安全性高:NTFS支持基于文件或目录的ACL,并且支持加密文件系统(EFS)。

可恢复性:NTFS支持基于原子事务概念的文件恢复,比较符合服务器文件系统的要求。

文件压缩:NTFS支持基于文件或目录的文件压缩,可以很方便的节省磁盘空间。

磁盘配额:NTFS支持磁盘配额,可针对系统中每个用户分配磁盘资源。

一、分析NTFS文件系统的结构

当用户将硬盘的一个分区格式化为NTFS分区时,就建立了一个NTFS文件系统。NTFS文件系统同FAT32文件系统一样,也是用“簇”为存储单位,一个文件总是占用一个或多个簇。

NTFS文件系统使用逻辑簇号(LCN)和虚拟簇号(VCN)对分区进行管理。

逻辑簇号:既对分区内的第一个簇到最后一个簇进行编号,NTFS使用逻辑簇号对簇进行定位。

虚拟簇号:既将文件所占用的簇从开头到尾进行编号的,虚拟簇号不要求在物理上是连续的。

NTFS文件系统一共由16个“元文件”构成,它们是在分区格式化时写入到硬盘的隐藏文件(以”$”开头),也是NTFS文件系统的系统信息。

NTFS的16个元文件介绍:

首先找到该分区的起始扇区,具体可以参考MBR分区结构、DPT分区表、EBR扩展引导这篇文章。

二、分析$Boot文件

$Boot元文件由分区的第一个扇区(既DBR)和后面的15个扇区(既NTLDR区域)组成,其中DBR由“跳转指令”、“OEM代号”、“BPB”、“引导程序”和“结束标志”组成,这里和FAT32文件系统的DBR一样。下图是一个NTFS文件系统完整的DBR。

下面我们分析一下DBR中的各参数

EB 58 90:(跳转指令)本身占2字节它将程序执行流程跳转到引导程序处。

“EB 58 90″清楚地指明了OS引导代码的偏移位置。jump 52H加上跳转指令所需的位移量,即开始于0×55。

4E 54 46 53 20 20 20 20:(OEM代号)这部分占8字节,其内容由创建该文件系统的OEM厂商具体安排。为“NTFS”。

BPB:NTFS文件系统的BPB从DBR的第12个字节开始,占用73字节,记录了有关该文件系统的重要信息,下表中的内容包含了“跳转指令”、“OEM代号”以及“BPB”的参数。

对照上面的BPB分析如下:

02 00:每个扇区512个字节

08:每个簇8个扇区

00 00:保留扇区为0

00 00 00:为0

00:不使用

F8:为硬盘

00 00:为0

00 3F:每磁道63个扇区

00 FF:每柱面255个磁头

00 00 00 3F:隐藏扇区数(MBR到DBR)

00 00 00 00:不使用

80 00 80 00:不使用

00 00 00 00 0C 80 33 FF:扇区总数209728511

00 00 00 00 00 00 00 03:$MFT的开始簇号

00 00 00 00 00 85 57  80:$MFTmirr的开始簇号

00 00 00 F6:每个MFT记录的簇数

00 00 00 01:每索引的簇数

B8 11 2A 0C B8 11 2A 0C:分区的逻辑序列号

引导程序:DBR的引导程序占用426字节,其负责完成将系统文件NTLDR装入,对于没有安装系统的分区是无效的。

结束标志:DBR的结束标志与MBR,EBR的结束标志相同,为“55 AA”。

三、分析$MFT元文件

在NTFS文件系统中,磁盘上的所有数据都是以文件的形式存储,其中包括元文件。每个文件都有一个或多个文件记录,每个文件记录占用两个扇区,而$MFT 元文件就是专门记录每个文件的文件记录。由于NTFS文件系统是通过$MFT来确定文件在磁盘上的位置以及文件的属性,所以$MFT是非常重要 的,$MFT的起始位置在DBR中有描述。$MFT的文件记录在物理上是连续的,并且从0开始编号。$MFT的前16个文件记录总是元文件的,并且顺序是 固定不变的。

四、分析文件记录

1、文件记录的结构

文件记录由两部分构成,一部分是文件记录头,另一部分是属性列表,最后结尾是四个“FF”。如下是一个完整的文件记录:

在同一系统中,文件记录头的长度和具体偏移位置的数据含义是不变的,而属性列表是可变的,其不同的属性有着不同的含义。后文将对属性进行具体分析,先来看看文件记录头的信息。

在NTFS文件系统中所有与文件相关的数据结构均被认为是属性,包括文件的内容。文件记录是一个与文件相对应的文件属性数据库,它记录了文 件的所有属性。每个文件记录中都有多个属性,他们相对独立,有各自的类型和名称。每个属性都由两部分组成,既属性头和属性体。属性头的前四个字节为属性的 类型。如下是以10H属性为例的属性结构。

另外属性还有常驻与非常驻之分。当一个文件很小时,其所有属性体都可以存放在文件记录中,该属性就称为常驻属性。如果某个文件很大,1KB的文件记 录无法记录所有属性时,则文件系统会在$MFT元文件之外的区域(也称数据流)存放该文件的其他文件记录属性,这些存放在非$MFT元文件内的记录就称为 非常驻属性。

分析属性的属性头

每个属性都有一个属性头,这个属性头包含了一些该属性的重要信息,如属性类型,属性大小,名字(并非都有)及是否为常驻属性等。

常驻属性的属性头分析表:

如下是非常驻属性的属性头分析表:

前面说过了,属性的种类有很多,因此各属性体的含义也不同。下表是NTFS文件系统中的所有属性体的简介。

接下来来看几个重要的属性:

分析10H属性:

10H类型属性它包含文件的一些基本信息,如文件的传统属性,文件的创建时间和最后修改时间和日期,文件的硬链接数等等。如下:是一个10H类型的属性。

其中偏移0×20处的文件属性解释如下:

分析20H属性

20H类型属性既属性列表,当一个文件需要好几个文件记录时,才会用到20H属性。20H属性记录了一个文件的下一个文件记录的位置。如下:是20H属性的解释。

分析30H属性

30H类型属,该属性用于存储文件名 ,它总是常驻属性。最少68字节,最大578字节,可容纳最大Unicode字符的文件名长度。

分析80H属性

80H属性是文件数据属性,该属性容纳着文件的内容,文件的大小一般指的就是未命名数据流的大小。该属性没有最大最小限制,最小情况是该属性为常驻属性。常驻属性就不做多的解释了,上面我标记的是一个非常驻的80H属性。

其中,Run List是最难理解,也是最重要的。当属性不能存放完数据,系统就会在NTFS数据区域开辟一个空间存放,这个区域是以簇为单位的。Run List就是记录这个数据区域的起始簇号和大小,一个Run List例子上所示。这个示例中,Run List的值为“12 41 47 03”,因为后面是00H,所以知道已经是结尾。如何解析这个Run List呢? 第一个字节是压缩字节,高位和低位相加,1+2=3,表示这个Data Run信息占用三个字节,其中高位表示起始簇号占用多少个字节,低位表示大小占用的字节数。在这里,起始簇号占用1个字节,值为03,大小占用2个字节, 值为47 41。解析后,得到这个数据流起始簇号为3,大小为18241簇。

虽然数据不一样,但是表达的意思是一样的。

分析90H属性

90H属性是索引根属性,该属性是实现NTFS的B+树索引的根节点,它总是常驻属性。该属性的结构如下图:

索引根的结构如表:

索引头的结构如表:

索引项结构如表:

分析A0H属性

A0属性是索引分配属性,也是一个索引的基本结构,存储着组成索引的B+树目录索引子节点的定位信息。它总是常驻属性。如下:是一个A0H属性的实例。

根据上图A0H属性的“Run List”可以找到索引区域,偏移到索引区域所在的簇,如下图:

起始簇:18265

簇大小:3

起始扇区号 = 该分区的其实扇区 + 簇号 * 每个簇的扇区数  也就是

64 + 18265 * 8 = 146124

对了,上面的偏移0×28还要加上0×18 = 0×40.

标准索引头的解释如下:

索引项的解释如下:

到此一些常用属性基本介绍的差不多了。

下面来说下遍历一个分区下面文件列表的思路:

1、定位DBR,通过DBR可以得知“$MFT”的起始簇号及簇的大小。

2、定位“$MFT”,找到“$MFT”后,在其中寻找根目录的文件记录,一般在5号文件记录。

3、在90H属性中得到B+树索引的根节点文件信息,重点在A0属性上。通过属性中的“Run List”定位到其数据流。

4、从“Run List”定位到起始簇后,再分析索引项可以得到文件名等信息。

5、从索引项中可以获取“$MFT”的参考号,然后进入到“$MFT”找到对应的文件记录。

6、然后再根据80H属性中的数据流就可以找到文件真正的数据了。

原著:alone monkey的博客

NTFS 文件系统解析

1.windows 下磁盘文件读写

下面是读取D:\磁盘上的第0扇区 512 Bytes

CreateFile()打开磁盘,获取文件句柄;

SetFilePointer()设置读写的位置;

ReadFile()读取磁盘扇区数据。

HANDLE hFile;char drive[] = "\\\\.\\D:";    //------- \\.\D: -----//------- 创建文件句柄 ------

hFile = CreateFile(drive,    // 还可以为硬盘"\\\\.\\physicalDrive0"绝对读写, 或类似"D:\\abc\\fileName.txt"文件形式,

GENERIC_READ,      // 打开方式

FILE_SHARE_WRITE|FILE_SHARE_READ, // 前者表示之后打开该文件的程序 only write, 后者为only read

NULL,                // 安全属性 LPSECURITY_ATTRIBUTES

OPEN_EXISTING,           // how to Create

0, NULL);              //

if(hFile == INVALID_HANDLE_VALUE) return;//

中间两个参数分别是偏移字节数的低32Bit数值,和高32Bit的地址,高32Bit不用则为零;此处表示D:\从首扇区起,偏移字节数为0SetFilePointer(hFile, 0, NULL, FILE_BEGIN);DWORD lenRead;ReadFile(hFile, buf, 512, &lenRead, NULL); // 在设定的位置起,读取512Byte到缓存buf; 这里读取的是分区引导扇区

2.获取磁盘所有驱动器

DWORD allDrive = GetLogicalDrive().// 返回值共有 32 Bits,从低位到高位分别表示A,B,C,D,E,.....// 比如0x0000 007C, 即 01111100, 表示有C,D,E,F,G 共5个驱动器;

3.NTFS 文件系统

一个NTFS系统是由引导扇区,主文件表MFT,和数据区组成;另外MFT有一部分重要备份在数据区。

(1) 引导扇区

若果D:\盘是NTFS文件系统,那么上面得到的第0个扇区数据 buf 偏移0x03开始的8个Bytes就是“NTFS”,表示这个扇区就是NTFS的引导记录。这第0个扇区也就是$Boot扇区,这个扇区包含了该卷的 BPB 和扩展BPB参数,可以得到该卷的卷大小,磁头数,扇区大小,簇大小等等参数;要解析一个NTFS卷的文件结构也是从这里的BPB参数开始的。

解析时有用到的是这个扇区的前 88 (0x58) 个Bytes,剩余的是引导代码和结束标志“55 AA”,前88个字节具体结构如下:

typedef struct NTFS_BPB{  // 在cmd 输入 fsutil fsinfo ntfsinfo d: 查询 NTFS 信息

UCHAR jmpCmd[3];

UCHAR s_ntfs[8];//“NTFS”标志

// 0x0B

UCHAR bytesPerSec[2];// 0x0200  扇区大小,512B

UCHAR SecsPerClu;// 0x08   每簇扇区数,4KB

UCHAR rsvSecs[2];//       保留扇区

UCHAR noUse01[5];//

// 0x15

UCHAR driveDscrp;// 0xF8 磁盘介质 -- 硬盘

UCHAR noUse02[2];//

// 0x18

UCHAR SecsPerTrack[2]; //  0x003F  每道扇区数 63

UCHAR Headers[2]; //  0x00FF 磁头数

UCHAR secsHide[4]; //  0x3F  隐藏扇区

UCHAR noUse03[8]; //

// 0x28

UCHAR allSecsNum[8];// 卷总扇区数, 高位在前, 低位在后

// 0x30

UCHAR MFT_startClu[8]; // MFT 起始簇

UCHAR MFTMirr_startClu[8]; // MTF 备份 MFTMirr 位置

//0x40

UCHAR cluPerMFT[4];     // 每记录簇数 0xF6

UCHAR cluPerIdx[4];     // 每索引簇数

//0x48

UCHAR SerialNum[8];    // 卷序列号

UCHAR checkSum[8];     // 校验和

}Ntfs_Bpb,*pNtfs_Bpb;

(2)关于簇

在一个分区中引导记录扇区所在的簇编号为0,往后的簇编号1,2,3等等一直到卷尾,这就是一个分区的逻辑簇号(LCN);计算逻辑扇区号:LCN * 簇大小,簇的大小在BPB参数中找到,一般为8个扇区4KB;以此可以由 MFT 起始簇 MFT_startClu 计算出第一个 MFT 项(记录)的位置。

VCN,虚拟簇号,给一个文件从它的首簇开始编号,为0,依次递增一直到文件的尾簇,在物理上不一定连续。

(3)主文件表(Master File Table, MFT)

MFT 是由一条条 MFT 项(记录)所组成的,而且每项大小是固定的(一般为1KB),MFT保留了前16项用于特殊文件记录,称为元数据元数据在磁盘上是物理连续的,编号为0~15;如果$MFT的偏移为0x0C0000000, 那么下一项的偏移就是0x0C0000400,在下一项就是0x0C0000800等等;

MFT记录了整个卷的所有文件 (包括MFT本身、数据文件、文件夹等等) 信息,包括空间占用,文件基本属性,文件位置索引,创建时间用户权限,加密信息等等,每一个文件在 MFT 中都有一个或多个 MFT 项记录文件属性信息,这里的属性包括数据,如果这个文件很小,在 MFT 项中就可以放下,那么这条属性就定义为常驻属性,常驻标志位记为1,如果是非常驻,则有一个索引指向另一条记录(称为一个运行)。

(3)第一条 MFT 项:$MFT

MFT 的第一项记录$MFT描述的是主分区表MFT本身,它的编号为0,MFT项的头部都是如下结构:

typedef struct MFT_HEADER{

UCHARmark[4]; // "FILE" 标志

UCHARUsnOffset[2];// 更新序列号偏移     30 00

UCHARusnSize[2]; // 更新序列数组大小+1   03 00

UCHARLSN[8]; // 日志文件序列号(每次记录修改后改变) 58 8E 0F 34 00 00 00 00

// 0x10

UCHARSN[2]; // 序列号 随主文件表记录重用次数而增加

UCHARlinkNum[2]; // 硬连接数 (多少目录指向该文件) 01 00

UCHARfirstAttr[2];// 第一个属性的偏移  38 00

UCHARflags[2];// 0已删除 1正常文件 2已删除目录 3目录正使用

// 0x18

UCHARMftUseLen[4];// 记录有效长度  A8 01 00 00

UCHARmaxLen[4];// 记录占用长度   00 04 00 00

// 0x20

UCHARbaseRecordNum[8]; // 索引基本记录, 如果是基本记录则为0

UCHARnextAttrId[2];// 下一属性Id  07 00

UCHARborder[2];//

UCHARxpRecordNum[4]; // 用于xp, 记录号

// 0x30

UCHARUSN[8];      // 更新序列号(2B) 和 更新序列数组

}Mft_Header, *pMft_Header;

上面的头部结构体在扇区的数据偏移 0x00 ~0x38;

在0x38之后的4大块颜色数据是4条属性,描述名称,时间,索引等等信息,最后以“FF FF FF FF”结束。它们分别以0x10,0x30,0x80, 0xB0作为标志;这里的四种属性所描述的的信息类型可以由下表查得,对照数据和结构体可以把这4条属性解析出来。

//------------------ 属性头通用结构 ----

typedef struct NTFSAttribute //所有偏移量均为相对于属性类型 Type 的偏移量

{

UCHAR Type[4]; // 属性类型 0x10, 0x20, 0x30, 0x40,...,0xF0,0x100

UCHAR Length[4]; // 属性的长度

UCHAR NonResidentFiag; // 是否是非常驻属性,l 为非常驻属性,0 为常驻属性 00

UCHAR NameLength;// 属性名称长度,如果无属性名称,该值为 00

UCHAR ContentOffset[2]; // 属性内容的偏移量 18 00

UCHAR CompressedFiag[2]; // 该文件记录表示的文件数据是否被压缩过 00 00

UCHAR Identify[2]; // 识别标志 00 00

//--- 0ffset: 0x10 ---

//-------- 常驻属性和非常驻属性的公共部分 ----

union CCommon14 {

//---- 如果该属性为 常驻 属性时使用该结构 ----

struct CResident

{

UCHAR StreamLength[4];// 属性值的长度, 即属性具体内容的长度。“48 00 00 00”

UCHAR StreamOffset[2];// 属性值起始偏移量“18 00”

UCHAR IndexFiag[2]; // 属性是否被索引项所索引,索引项是一个索引(如目录)的基本组成 00 0021 };

//------- 如果该属性为 非常驻 属性时使用该结构 ----

struct CNonResident

{

UCHAR StartVCN[8];// 起始的 VCN 值(虚拟簇号:在一个文件中的内部簇编号,0起)

UCHAR LastVCN[8]; // 最后的 VCN 值

UCHAR RunListOffset[2]; // 运行列表的偏移量

UCHAR CompressEngineIndex[2]; // 压缩引擎的索引值,指压缩时使用的具体引擎。

UCHAR Reserved[4];

UCHAR StreamAiiocSize[8]; // 为属性值分配的空间,单位为B,压缩文件分配值小于实际值

UCHAR StreamRealSize[8]; // 属性值实际使用的空间,单位为B

UCHAR StreamCompressedSize[8]; // 属性值经过压缩后的大小, 如未压缩, 其值为实际值

};

};

};

由这个结构体可以知道,属性头的长度取决于是否有属性名,属性名长度是多少;是否常驻,如果常驻,属性内容长度是多少,如果非常驻,运行列表有多长。

(0x08)日志文件序列号,它又叫文件参考号、文件引用号,一共 8Byte,前6个字节是文件称为文件号;后2个字节是文件顺序号,文件顺序号随重用而增加。

10H类型:10H属性$STANDART_INFORMATION,描述的是文件的创建、访问、修改时间,传统属性,以及版本信息等等。

struct Value0x10

{

UCHAR fileCreateTime[8];// 文件创建时间

UCHAR fileChangeTime[8];// 文件修改时间

UCHAR MFTChangeTime[8]; // MFT修改时间

UCHAR fileLatVisited[8];// 文件最后访问时间

UCHAR tranAtrribute[4]; // 文件传统属性

UCHAR otherInfo[28];// 版本,所有者,配额,安全等等信息(详细略)

UCHAR updataNum[8]; // 文件更新序列号

};

下面的偏移都是相对于属性首字节,其值加上0x38 就是实际偏移(图中的offset)。

0x00 4B: (0x10) 类型标志

0x04 4B: (0x60) 整条10H属性的长度

0x08 1B: (0x00) 非常驻

0x09 1B: (0x00) 无属性名称

0x0A 2B: (0x18) 属性内容偏移位置

0x18 8B: (ED 46 39 6B 6B 93 CF 01) 8个字节是文件创建时间,紧随其后的3x8个字节分别是文件最后修改时间,MFT修改时间,文件最后访问时间。64位数值是相对于1601-01-01零点整的千万分之一秒数。可以用FileTimeToSystemTime()转换成我们通常见到的形式。

0x38 8B: (06 00 00 00 00 00 00 00)传统属性,这里是系统隐含文件,位描述:

后面还有0x28个字节是版本和管理信息等等。

20H类型  $ATTRIBUTE_LIST

当一个文件需要多个MFT项来记录,20H是用来描述属性的属性列表;当非常驻属性依然不够空间,则需要属性列表。20H类属性也有可能为常驻或非常驻,可以应用上面的通用属性头。以此结构体得到属性值的偏移地址,进而得到属性内容。

//------- 这个结构只是数据内容部分,不包括属性头 NTFSAttribute ----

//------- 由属性头的属性值偏移量确定属性值的起始位置 ---

struct Value0x20{

UCHAR type[4]; // 类型

UCHAR recordType[2]; // 记录类型

UCHAR nameLen[2];// 属性名长度

UCHAR nameOffset;// 属性名偏移

UCHAR startVCN[8]; // 起始 VCN

UCHAR baseRecordNum[8]; // 基本文件记录索引号

UCHAR attributeId[2];// 属性 id

//---- 属性名(Unicode) 长度取决于 nameLen 的值 ---

};

30H类型  $FILE_NAME

30H 类型属性描述的是文件或文件夹的名字和创建基本信息,属性头不在赘述,属性值的结构如下:

struct Value0x30

{

UCHAR parentFileNum[8]; // 父目录的文件参考号,前6B

UCHAR createTime[8];// 文件创建时间

UCHAR changeTime[8];// 文件修改时间

UCHAR MFTchangeTime[8]; // MFT 修改时间

UCHAR lastVisitTime[8] // 最后一次访问时间

UCHAR AllocSize[8]; // 文件分配大小

UCHAR realSize[8]; // 实际大小

UCHAR fileFlag[4]; // 文件标志,系统 隐藏 压缩等等

UCHAR EAflags[4]// EA扩展属性和重解析点

UCHAR nameLength; // 文件名长

UCHAR nameSpace;// 文件命名空间

//----- Name (Unicode编码) 长度为 2 * nameLength ----

}

NTFS通过为一个文件创建多个30H属性实现POSIX (Portable Operating System Interface, 可移植操作系统接口) 式硬连接,每个30H属性都有自己的详细资料和父目录;一个硬连接删除时,就从MFT中删除这个文件名,最后一个硬连接被删除时,这个文件就算是真正被删除了。

文件参考号:包括前 6B 的文件记录号,后 2B 的文件引用计数;当文件记录号为0x05时,是根目录。

命名空间:0 --- POSIX, 1 -- Win32,  2 --- DOS,  3 --- Win32 & DOS

40H属性  $OBJECT_ID

MTFS统一给所有 MFT 记录分配的一个标识 --- 对象ID,即结构体第一个16B,可能只有一个全局对象ID,后面的3个ID可能没有。

struct Value0x40

{

UCHAR GObjectId[16];// 全局对象ID 给文件的唯一ID号

UCHAR GOriginalVolumeId[16];// 全局原始卷ID 永不改变

UCHAR GOriginalObjectId[16];// 全局原始对象ID 派给本MFT记录的第一个对象ID

UCHAR GDomain[16]; // 全局域ID (未使用)

};

50H属性  $SECURITY_DESCRIPTOR ( 安全描述符) 略。

60H属性  $VOLUME_NAME 卷名属性

struct Value0x60

{

//---- 通用属性头 --

UCHAR VolumeName[N];//(Unicode) N 最大为 127 外加一个结束符'\0'

};

70H属性  $VOLUME_INFORMATION  卷版本、状态

struct Value0x70

{//----- 通用属性头 ---

UCHAR noUsed1[8];// 00

UCHAR mainVersion; // 主版本号 1--winNT, 3--Win2000/XP

UCHAR SecVersion;// 次版本号 当主为3, 0--win2000, 1--WinXP/7

UCHAR flag[2]; // 标志

UCHAR noUsed2[4];// 00

};

/* flag:

* 0x0001 坏区标志 下次重启时chkdsk/f进行修复

* 0x0002 调整日志文件大小

* 0x0004 更新装载

* 0x0008 装载到 NT4

* 0x0010 删除进行中的USN

* 0x0020 修复对象 Ids

* 0x8000 用 chkdsk 修正卷

*/

80H属性  $DATA  容纳文件数据(未命名数据流),文件的大小一般指是未命名数据流的大小,没有长度限制,当它为常驻时,数据长度最小。它的结构为属性头加上数据流,如果数据流太大,则标记为非常驻,以运行的方式索引到外部。例如找一个MP3文件,从它的MFT项中0x80属性中可以看到它一定是非常驻,它的运行所指向的一系列簇就是音乐文件数据流;

一个80H属性实例:

0x00~0x37  是属性头;运行列表偏移是紫色和橙色区域,0x40开始,可以得到运行列表 32 40 34 00 00 0C 32 80 31 07 54 16 ;分析如下:

首先0x32,低4位是2,表示紧随其后的2Byte 0x3440作为运行簇大小,高4位是3,表示簇大小之后的3个Byte 0x0C0000 是起始簇,这是一个运行结束;接下来的0x32同理得簇起始号0x165407,运行大小为0x3180簇;一个运行的结束后跟0x00 为列表结束,之后填充无效字符。

90H属性  $INDEX_ROOT  索引根。实现NTFS的B+树索引的根节点,总是常驻。索引根属性由属性头、索引根和索引项组成。属性头是通用属性头的常驻部分。结构体如下(可能有些偏差):

struct indexHeader

{

UCHAR type[4]; // 属性类型

UCHAR checkRule[4];// 校对规则

UCHAR allocSize[4];// 索引项分配大小

UCHAR CluPerIdx; // 每索引记录的簇数

UCHAR noUse01[3]; // 填充

UCHAR firstIdxOffset[4];// 第一个索引项偏移

UCHAR idxTotalSize[4]; // 索引项总大小

UCHAR indxAllocSize[4]; // 索引项分配大小

UCHAR flag; // 标志

UCHAR noUse02[3];

};

// 一般小目录在90属性中有常驻目录项,目录项的结构与INDX缓存中的目录项一样

struct indexItem

{

UCHAR MFTnum[8] // 文件的 MFT 记录号,前6B是MFT编号,用于定位此文件记录

UCHAR ItemLen:[2]; // 索引项大小

UCHAR IndexIdentifier:[2]; // 索引标志 R

UCHAR isNode[2];// 1---此索引包含一个子节点,0---此为最后一项

UCHAR noUse03[2]; // 填充

UCHAR FMFTnum[8]; // 父目录 MFT文件参考号 0x05表示根目录

UCHAR CreateTime[8];//文件创建时间

UCHAR fileModifyTime[8];//文件修改时间

UCHAR MFTModifyTime[8]; //文件记录最后修改时间

UCHAR LastVisitTime[8]; //文件最后访问时间

UCHAR FileAllocSize[8]; //文件分配大小 (B)

UCHAR FileRealSize[8]; //文件实际大小 (B)

UCHAR FileIdentifier[8];//文件标志

UCHAR FileNameLen; //文件名长度

UCHAR FileNameSpace; //文件名命名空间

//---- 0x52 ---

//FileName; // 文件名 (末尾填满 8 字节)

UCHAR nextItemVCN[8]; // 下一项的 VCN (有子节点才有)

};

A0H属性  $INDEX_ALLOCATION  索引分配属性,也是索引,由属性头和运行列表组成,一般指向一个或多个目录文件(INDX文件,即4K缓存);

A0H属性和90H属性共同描述了磁盘文件和目录的 MFT 记录的位置。第5项MFT的A0H属性记录根目录的位置。

B0H属性  $BITMAP 位图属性,虚拟簇使用(占用)情况,这条属性用在$MFT和索引中;在Bitmap文件中,每一个 Bit 代表分区的一个簇,置1代表其已使用;第0个字节的第0位表示分区第0簇,之后依次递增。

C0H属性  $REPARSE_POINT 重解析点。使应用程序为文件或目录关联一个应用程序数据块,详细略。

D0H$EA_INFORMATION  扩充信息属性。为在NTFS下实现HPFS的OS/2子系统信息,及WinNT服务器的OS/2客户端应用而设置的,一般为非常驻;

E0H$EA  扩充属性  也是为了实现NTFS下的 HPFS,一般为非常驻;

100H $LOGGED_UTILITY_STREAM,EFS加密属性,存储用于实现EFS加密有关的信息,合法用户列表,解密密钥等等

(4) 解析一个磁盘分区的文件目录的顺序:

引导扇区($Boot) ----> 第0项记录($MFT) ----> 根目录记录(第5项,90H,A0H) ----> 根目录(INDX)

struct indxHeader    // A0H外部缓存文件结构,最大长度一般为4K

{

UCHAR mark[4];// 标志 "INDX"

UCHAR usnOffset[2]; // 更新序列偏移

UCHAR usnSize[2]; // 更新序列数组大小S

UCHAR LSN[8]; // 日志文件序列号

UCHAR indxVCN[8]; // 本索引缓存在分配索引中的VCN

UCHAR itemOffset[4]; // 第一项索引的偏移(从这里开始计算)

UCAHR itemSize[4];// 索引项实际大小(B)

UCHAR itemAlloc[4]; // 索引项分配大小(B)(不包括头部) 略小于4K

UCHAR isNode; // 是结点置1,表示有子节点

UCHAR noUse[3];

//UCHAR USN[2S]; // 更新序列号和数组

};

在文件头之后就是目录项了,项的结构就是在上面90H的介绍里定义的indexItem,每一个项代表一个文件或目录的MFT项,通过项的 MFT 记录号可以计算出MFT项的磁盘地址,它等于$MFT 的偏移地址 + 编号*0x400,以此可以找到该索引项对应的文件或子目录的MFT项。

(5)搜索一个已删除的文件或目录的MFT项

上面说了,一个文件的MFT项的地址等于$MFT的地址+MFT编号*0x400,如果目录中的对应项删除了,那么可以从MFT的首部开始检索,因为MFT一般是连续的,而一个MFT项的大小又是固定的,一项项读取,找到各自的0x30属性,解析出文件名,进行比较(MFT中有一些空白区域需要跳过)。

(6) 关于文件名

一般在文件名的前一个字节是文件名的命名空间,不管是INDX文件中,还是0x30属性中。

0x00 ---- POSI ,最大的命名空间,大小写敏感,支持除“\0&”和“/”所有Unicode字符,最大程度255个字符;

0x01 ---- Win32,是POSI的子集,不支持字符:* / < > | \  : ?  ,不能用句点或空格结束;

0x02 ---- DOS , 是Win32的子集,字符必须比空格0x20大,文件名1~8个字符,然后句点分割接后缀扩展名1~3个字符;

0x03 --- DOS&Win32,必须兼容Win32和DOS命名方式

在INDX文件中,经常可以看到含有0x02和0x03或者0x01的两个不同命名空间、相同MFT编号的项,也就是说这两个目录项指向同一个记录,同样的在这个文件的MFT项中也有两个0X30属性,其中一个是0x01或0x03,表示的是完整的文件名;另一个是0x02,DOS命名方式,它是一个短文件名,它在我们命名的基础上,截断“.”之前的超出6个字符的所有字符,只剩前6个,之后接上“~1”,这样正好8个字符,当然后面的句点和扩展名保留。另外,它必须满足DOS命名规则,必须大写,删除禁止使用的字符等等。如果文件名重复了,在“~1”基础上递增,“~2”,“~3”等等。检索比对时,我们自然要使用前者。

(7)关于字符集

字符集是字符在计算机上的编码方式,可以看成一种协议,一种约定规则,我们处理一串二进制数所代表的字符时,必须清楚它用的是哪一种编码方式;

在windows系统中文件的命名是固定用两个字节表示一个字符,在MFT中可以发现英文文件名字符之间都填充一个“\0”,这是宽字符集与变长字符集兼容,在宽字符集中,小于128的字符数值上是等于ASCII码;我们的文件数据一般用的是变长字符集(GB2312等等);

为了比较输入的文件名和NTFS中的文件名,我们必须要先转换;

两个WinAPI 函数,用于宽字符和变长字符转换

// 函数原型int WideCharToMultiByte(

UINT CodePage, // code page

DWORD dwFlags, // performance and mapping flags

LPCWSTR lpWideCharStr, // address of wide-character string

int cchWideChar, // number of characters in string

LPSTR lpMultiByteStr, // address of buffer for new string

int cchMultiByte, // size of buffer

LPCSTR lpDefaultChar, // address of default for unmappable

// characters

LPBOOL lpUsedDefaultChar // address of flag set when default

// char. used

);

int MultiByteToWideChar(

UINT CodePage, // code page

DWORD dwFlags, // character-type options

LPCSTR lpMultiByteStr, // address of string to map

int cchMultiByte, // number of bytes in string

LPWSTR lpWideCharStr, // address of wide-character buffer

int cchWideChar // size of buffer

);

//--- WCHAR 定义在tchar.h中 ----

void charTest()

{

TCHAR tc1[16] ;  //= _T("后来");

WCHAR tc2[8] = {0x540E, 0x6765, 0, 0, 0, 0, 0, 0};

// memset(tc2, 0, 20);

// MultiByteToWideChar(CP_ACP, 0, tc1, 4, (LPWSTR)tc2, 4);

WideCharToMultiByte(CP_ACP, 0 ,(WCHAR*)tc2, 2, tc1, sizeof(tc1), 0, 0);

cout<

PrintHex(tc1);

cout<

cout<

PrintHex(tc2);

cout<

}

你可能感兴趣的:(ntfs文件系统服务器版)