NTFS文件系统详解
NTFS (New Technology File System),是 WindowsNT 环境的文件系统。新技术文件系统是Windows NT家族(如,Windows 2000、Windows XP、Windows Vista、Windows 7和 windows 8.1)等的限制级专用的文件系统(操作系统所在的盘符的文件系统必须格式化为NTFS的文件系统,4096簇环境下)。NTFS取代了老式的FAT文件系统。
NTFS对FAT和HPFS作了若干改进,例如,支持元数据,并且使用了高级数据结构,以便于改善性能、可靠性和磁盘空间利用率,并提供了若干附加扩展功能。NTFS文件系统拥有以下特点:
安全性高:NTFS支持基于文件或目录的ACL,并且支持加密文件系统(EFS)。
可恢复性:NTFS支持基于原子事务概念的文件恢复,比较符合服务器文件系统的要求。
文件压缩:NTFS支持基于文件或目录的文件压缩,可以很方便的节省磁盘空间。
磁盘配额:NTFS支持磁盘配额,可针对系统中每个用户分配磁盘资源。
一、分析NTFS文件系统的结构
当用户将硬盘的一个分区格式化为NTFS分区时,就建立了一个NTFS文件系统。NTFS文件系统同FAT32文件系统一样,也是用“簇”为存储单位,一个文件总是占用一个或多个簇。
NTFS文件系统使用逻辑簇号(LCN)和虚拟簇号(VCN)对分区进行管理。
逻辑簇号:既对分区内的第一个簇到最后一个簇进行编号,NTFS使用逻辑簇号对簇进行定位。
虚拟簇号:既将文件所占用的簇从开头到尾进行编号的,虚拟簇号不要求在物理上是连续的。
NTFS文件系统一共由16个“元文件”构成,它们是在分区格式化时写入到硬盘的隐藏文件(以”$”开头),也是NTFS文件系统的系统信息。
NTFS的16个元文件介绍:
二、分析$Boot文件
下面我们分析一下DBR中的各参数
EB 52 90:(跳转指令)本身占2字节它将程序执行流程跳转到引导程序处。
“EB 52 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 04 FF D5 E5:扇区总数83875301
00 00 00 00 00 0C 00 00:MFT的开始簇号7864320000000000000010:MFT的开始簇号7864320000000000000010:MFTmirr的开始簇号16
00 00 00 F6:每个MFT记录的簇数
00 00 00 01:每索引的簇数
B6 FC 23 AA FC 23 63 B9:分区的逻辑序列号
引导程序:DBR的引导程序占用426字节,其负责完成将系统文件NTLDR装入,对于没有安装系统的分区是无效的。
三、分析$MFT元文件
在NTFS文件系统中,磁盘上的所有数据都是以文件的形式存储,其中包括元文件。每个文件都有一个或多个文件记录,每个文件记录占用两个扇区,而MFT元文件就是专门记录每个文件的文件记录。由于NTFS文件系统是通过MFT元文件就是专门记录每个文件的文件记录。由于NTFS文件系统是通过MFT来确定文件在磁盘上的位置以及文件的属性,所以MFT是非常重要的,MFT是非常重要的,MFT的起始位置在DBR中有描述。MFT的文件记录在物理上是连续的,并且从0开始编号。MFT的文件记录在物理上是连续的,并且从0开始编号。MFT的前16个文件记录总是元文件的,并且顺序是固定不变的。
四、分析文件记录
1、文件记录的结构
文件记录由两部分构成,一部分是文件记录头,另一部分是属性列表,最后结尾是四个“FF”。然后我们根据上面BPB中的MFT偏移簇号786432(786432∗4096(偏移)+63∗512(C盘偏移地址)=3221257728=0xC0007E00)找到系统MFT偏移簇号786432(786432∗4096(偏移)+63∗512(C盘偏移地址)=3221257728=0xC0007E00)找到系统MFT所在地址,查看文件记录格式如下是一个完整的文件记录:
在同一系统中,文件记录头的长度和具体偏移位置的数据含义是不变的,而属性列表是可变的,其不同的属性有着不同的含义。后文将对属性进行具体分析,先来看看文件记录头的信息。
在NTFS文件系统中所有与文件相关的数据结构均被认为是属性,包括文件的内容。文件记录是一个与文件相对应的文件属性数据库,它记录了文件的所有属性。每个文件记录中都有多个属性,他们相对独立,有各自的类型和名称。每个属性都由两部分组成,既属性头和属性体。属性头的前四个字节为属性的类型。从文件记录头可以看到第一个属性流的偏移地址,(C0007E00+0038=C0007E38)如下是以10H属性为例的属性结构
另外属性还有常驻与非常驻之分。当一个文件很小时,其所有属性体都可以存放在文件记录中,该属性就称为常驻属性。如果某个文件很大,1KB的文件记录无法记录所有属性时,则文件系统会在MFT元文件之外的区域(也称数据流)存放该文件的其他文件记录属性,这些存放在非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的值为“32 CC 26 00 00 0C”,因为后面是00H,所以知道已经是结尾。如何解析这个Run List呢? 第一个字节是压缩字节,高位和低位相加,3 + 2 = 5,表示这个Data Run信息占用五个字节,其中高位表示起始簇号占用多少个字节,低位表示大小占用的字节数。在这里,起始簇号占用3个字节,值为0C 00 00,大小占用2个字节,值为26 CC。解析后,得到这个数据流起始簇号为C0000,大小为9932簇。
分析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”,找到“MFT”后,在其中寻找根目录的文件记录,一般在5号文件记录。
3、在90H属性中得到B+树索引的根节点文件信息,重点在A0属性上。通过属性中的“Run List”定位到其数据流。
4、从“Run List”定位到起始簇后,再分析索引项可以得到文件名等信息。
5、从索引项中可以获取“MFT”的参考号,然后进入到“MFT”的参考号,然后进入到“MFT”找到对应的文件记录。
6、然后再根据80H属性中的数据流就可以找到文件真正的数据了。
NTFS文件系统详解 之 文件定位
一如既往的叨叨
首先要对硬盘分区(MBR、GPT)和文件系统(NTFS、FAT32等)有一定的认识,要知道MBR扇区以及DBR扇区的基本结构,如果后面遇到不清楚的地方可以参考上一篇文章https://blog.csdn.net/hilavergil/article/details/79270379,如果觉得这个文章不行的话,Emmm...还有Google呢。
接下来的代码目前只适用于MBR格式的分区,如果后面有时间的话再加上GPT的分区定位,实际上GPT的分区表更简洁明了一些。
我尽量少用Windows的Api,从硬盘的MBR扇区(0扇区)开始逐步定位,确定每一个主分区和扩展分区的位置,如果是NTFS分区则定位到MFT,从根目录开始使用DFS进行遍历。
我们的目标是
由于硬盘的分区定位在上面那篇文章已经写过了,因此这篇文章的重点会放在NTFS文件系统的解析上。我们从一个硬盘的MBR扇区开始,定位到一个NTFS分区,然后从NTFS分区的第一个扇区(Boot扇区)开始,逐步分析并定位到该分区中的每个目录和文件。
好了开始写正文
前面那篇文章曾提到过,MBR扇区中的分区表只有四项,当分区数小于等于3的时候,所有的分区都属于主分区,安装操作系统的主分区也叫活动分区,这些在Windows的磁盘管理中都可以看到,MBR扇区中分区表的每一项直接指向该主分区的起始位置,也就是该主分区的DBR扇区。当分区数大于等于4的时候呢怎么办?一般来说这种情况下MBR扇区中分区表的第四项将会指向扩展分区,一个扩展分区可以包含多个逻辑分区,每个逻辑分区都有一个EBR扇区与之对应。而EBR扇区和MBR扇区的结构是一致的,但是在EBR扇区中仅用到了其分区表的前两项,其中第一项指向本逻辑分区的起始偏移(注意逻辑分区是存在于扩展分区内的,见下图),第二项指向下一个逻辑分区的EBR扇区。如此循环往复,你会发现实际上EBR扇区构成了一个逻辑分区的链表,链表的每一个节点都包含两项,第一项指向该EBR对应的逻辑分区,第二项指向下一个逻辑分区的EBR扇区。最后一个节点的第二项为零,表示此后不再有逻辑分区。
图1 名字可能不太对但结构应该没差
当找到一个NTFS文件系统的分区之后,接下来就可以开始着手遍历目录树和文件了。你可能要问为什么不是FAT32或者其他的分区,因为我只看了NTFS……
第一步:读取NTFS的DBR扇区
DBR扇区的结构在之前那片文章写过了,下面给个DBR的截图。
图2 DBR扇区
图中红色方框里面的数据是MFT的偏移(小端字节序),即0X 00 00 00 00 00 0C 00 00,单位是簇,即MFT偏移786432个簇(相对于该分区的起始位置而言),蓝色方框里是每簇的扇区数0X08,黑色方框里是每扇区字节数0X 02 00,即512个字节。有了这些就可以算出MFT偏移的扇区数:786432 乘 8 等于 6291456个扇区,我们转到该分区的第 6291456扇区,就是MFT文件的第一个扇区了。接下来该分析MFT了。
第二步:解析MFT
MFT是什么鬼?百度一下。
图3 ???
不好意思,进错片场了。
图4 MFT主文件表
实际上MFT也是一个文件,在winhex中可以看到它:
图5 WinHex中的MFT文件
也能看到MFT的偏移和我们之前计算的结果时一致的。MFT由一个个表项构成,每一个表项是一个文件记录,大小一般为1KB(两个扇区),记录着卷中每一个目录和文件的信息,每个文件记录的结构都是固定的,由文件记录头和若干属性构成。下面给一个截图:
图6 一个文件记录
其中,文件记录头的结构定义如下:
// 文件记录头
typedef struct _FILE_RECORD_HEADER {
/*+0x00*/ BYTE Type[4];// 固定值'FILE'
/*+0x04*/ UINT16 USNOffset;// 更新序列号偏移, 与操作系统有关
/*+0x06*/ UINT16 USNCount; // 固定列表大小Size in words of Update Sequence Number & Array (S)
/*+0x08*/ UINT64 Lsn; // 日志文件序列号(LSN)
/*+0x10*/ UINT16 SequenceNumber; // 序列号(用于记录文件被反复使用的次数)
/*+0x12*/ UINT16 LinkCount;// 硬连接数
/*+0x14*/ UINT16 AttributeOffset; // 第一个属性偏移
/*+0x16*/ UINT16 Flags;// flags, 00表示删除文件,01表示正常文件,02表示删除目录,03表示正常目录
/*+0x18*/ UINT32 BytesInUse; // 文件记录实时大小(字节) 当前MFT表项长度,到FFFFFF的长度+4
/*+0x1C*/ UINT32 BytesAllocated; // 文件记录分配大小(字节)
/*+0x20*/ UINT64 BaseFileRecord; // = 0 基础文件记录 File reference to the base FILE record
/*+0x28*/ UINT16 NextAttributeNumber; // 下一个自由ID号
/*+0x2A*/ UINT16 Pading; // 边界
/*+0x2C*/ UINT32 MFTRecordNumber; // windows xp中使用,本MFT记录号
/*+0x30*/ UINT16 USN; // 更新序列号
/*+0x32*/ BYTE UpdateArray[0]; // 更新数组 } FILE_RECORD_HEADER, *pFILE_RECORD_HEADER; ————————————————
版权声明:本文为CSDN博主「我是NeroZhang」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Hilavergil/article/details/82622470
文件记录头后面就是属性了,属性由属性头和属性体构成,有如下几种类型:
图7 属性类型
属性有常驻属性和非常驻属性之分,当一个属性的数据能够在1KB的文件记录中保存的时候,该属性为常驻属性;而当属性的数据无法在文件记录中存放,需要存放到MFT外的其他位置时,该属性为非常驻属性。常驻属性和非常驻属性的头部结构定义如下:
//常驻属性和非常驻属性的公用部分
typedef struct _CommonAttributeHeader {
UINT32 ATTR_Type; //属性类型
UINT32 ATTR_Size; //属性头和属性体的总长度
BYTE ATTR_ResFlag; //是否是常驻属性(0常驻 1非常驻)
BYTE ATTR_NamSz; //属性名的长度
UINT16 ATTR_NamOff; //属性名的偏移 相对于属性头
UINT16 ATTR_Flags; //标志(0x0001压缩 0x4000加密 0x8000稀疏)
UINT16 ATTR_Id; //属性唯一ID
}CommonAttributeHeader,*pCommonAttributeHeader;
//常驻属性 属性头
typedef struct _ResidentAttributeHeader {
CommonAttributeHeader ATTR_Common;
UINT32 ATTR_DatSz; //属性数据的长度
UINT16 ATTR_DatOff; //属性数据相对于属性头的偏移
BYTE ATTR_Indx; //索引
BYTE ATTR_Resvd; //保留
BYTE ATTR_AttrNam[0];//属性名,Unicode,结尾无0
}ResidentAttributeHeader, *pResidentAttributeHeader;
//非常驻属性 属性头
typedef struct _NonResidentAttributeHeader {
CommonAttributeHeader ATTR_Common;
UINT64 ATTR_StartVCN; //本属性中数据流起始虚拟簇号
UINT64 ATTR_EndVCN; //本属性中数据流终止虚拟簇号
UINT16 ATTR_DatOff; //簇流列表相对于属性头的偏移
UINT16 ATTR_CmpSz; //压缩单位 2的N次方
UINT32 ATTR_Resvd;
UINT64 ATTR_AllocSz; //属性分配的大小
UINT64 ATTR_ValidSz; //属性的实际大小
UINT64 ATTR_InitedSz; //属性的初始大小
BYTE ATTR_AttrNam[0];
}NonResidentAttributeHeader, *pNonResidentAttributeHeader; ————————————————
版权声明:本文为CSDN博主「我是NeroZhang」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Hilavergil/article/details/82622470
比较重要的几个属性如0X30文件名属性,其中记录着该目录或者文件的文件名;0X80数据属性记录着文件中的数据;0X90索引根属性,存放着该目录下的子目录和子文件的索引项;当某个目录下的内容比较多,从而导致0X90属性无法完全存放时,0XA0属性会指向一个索引区域,这个索引区域包含了该目录下所有剩余内容的索引项。
比较重要的几个属性的结构定义将在后面给出。
下面是一个90属性和A0属性的截图:
图8 90属性和A0属性
从图上大致也能看出来,90属性中包含了2个目录:Program Files (x86) 和 Users,剩下的其他目录就在A0的属性体所指向的索引区了。索引的具体含义将在后面给出。
前面说过,每一个文件记录都会对应一个目录或者文件,因此,如果我们按顺序读取每一个MFT表项,我们就能得到该卷中所有的文件和目录信息,其中还包括了部分被删除的文件和目录(被删除但未覆盖掉)。但我们的目的并不是目录和文件的简单罗列,我们要的是按照目录的树形结构去列出该卷的目录树,并且去定位某一个给定的文件,读出文件的数据。因此到这里还不够,继续向下。
由于所有的目录和文件都在MFT中有对应的记录,因此,一个卷中目录和文件的数量越多,MFT的大小就越大,$MFT文件的大小是动态增长的,NTFS默认给MFT分配了该卷的12.5%的存储空间。MFT的前16项(元数据文件)是固定的(现在第九项$Quota基本上不存在了):
图9 NTFS的元数据文件
有了前面这些基础,接下来我们就能去解析目录树,定位文件位置啦!
大致的思路如下:
首先,MFT的第五项是卷的根目录的文件记录,见上图的$Root项,这是我们遍历的起点。其次,通过根目录的文件记录的90属性和A0属性,可以定位根目录下所有内容的索引项,而这些索引项又指向了它自己的文件记录项(MFT中的一项),该文件记录的90和A0属性又指向了它的子目录的索引项。所以呢。写个递归呀,我们的目录树就出来啦。这个树形结构大致上是这样的:
图10 一棵树
接下来以E盘中的E:\dir1_0\dir2_0\dir3_1\新建文本文档.txt为例,一步步详细的写出遍历的步骤,并读取出这个文本文档中的数据。
第三步:得到根目录的文件记录
前面我们通过计算得到,MFT的偏移为6,291,456个扇区,而根目录的文件记录是MFT的第5项,一个MFT表项占2个扇区,所以根目录的文件记录的偏移为6,291,456 + 2 * 5 = 6,291,466个扇区。转到该扇区,可以看到根目录的文件记录如下:
图11 根目录的文件记录
其中,30属性的属性体结构定义如下(不包含属性头):
//FILE_NAME 0X30属性体
typedef struct _FILE_NAME {
UINT64 FN_ParentFR; /*父目录的MFT记录的记录索引。
注意:该值的低6字节是MFT记录号,高2字节是该MFT记录的序列号*/
FILETIME FN_CreatTime;
FILETIME FN_AlterTime;
FILETIME FN_MFTChg;
FILETIME FN_ReadTime;
UINT64 FN_AllocSz;
UINT64 FN_ValidSz;//文件的真实尺寸
UINT32 FN_DOSAttr;//DOS文件属性
UINT32 FN_EA_Reparse;//扩展属性与链接
BYTE FN_NameSz;//文件名的字符数
BYTE FN_NamSpace;/*命名空间,该值可为以下值中的任意一个
0:POSIX 可以使用除NULL和分隔符“/”之外的所有UNICODE字符,最大可以使用255个字符。注意:“:”是合法字符,但Windows不允许使用。
1:Win32 Win32是POSIX的一个子集,不区分大小写,可以使用除““”、“*”、“?”、“:”、“/”、“<”、“>”、“/”、“|”之外的任意UNICODE字符,但名字不能以“.”或空格结尾。
2:DOS DOS命名空间是Win32的子集,只支持ASCII码大于空格的8BIT大写字符并且不支持以下字符““”、“*”、“?”、“:”、“/”、“<”、“>”、“/”、“|”、“+”、“,”、“;”、“=”;同时名字必须按以下格式命名:1~8个字符,然后是“.”,然后再是1~3个字符。
3:Win32&DOS 这个命名空间意味着Win32和DOS文件名都存放在同一个文件名属性中。*/
BYTE FN_FileName[0];
}FILE_NAME,*pFILE_NAME;
对照上图,可以得到根目录的文件名为 “ . ”。
第四步:计算根目录下索引项的偏移
90属性的属性体由3部分构成:索引根、索引头和索引项。但是有些情况下90属性中是不存在索引项的(上图的90属性不包含索引项,图8中的90属性包含2个索引项),这个时候该目录的索引项由A0属性中的data runs指出。90属性体的结构如下(不包含属性头):
typedef struct _INDEX_HEADER {
UINT32 IH_EntryOff;//第一个目录项的偏移
UINT32 IH_TalSzOfEntries;//目录项的总尺寸(包括索引头和下面的索引项)
UINT32 IH_AllocSize;//目录项分配的尺寸
BYTE IH_Flags;/*标志位,此值可能是以下和值之一:
0x00 小目录(数据存放在根节点的数据区中)
0x01 大目录(需要目录项存储区和索引项位图)*/
BYTE IH_Resvd[3];
}INDEX_HEADER,*pINDEX_HEADER;
//INDEX_ROOT 0X90属性体
typedef struct _INDEX_ROOT {
//索引根
UINT32 IR_AttrType;//属性的类型
UINT32 IR_ColRule;//整理规则
UINT32 IR_EntrySz;//目录项分配尺寸
BYTE IR_ClusPerRec;//每个目录项占用的簇数
BYTE IR_Resvd[3];
//索引头
INDEX_HEADER IH;
//索引项 可能不存在
BYTE IR_IndexEntry[0];
}INDEX_ROOT,*pINDEX_ROOT;
由于这里90属性没有索引项,我们直接看A0属性。
A0属性的属性体即为Data Runs,指向若干个索引区域的簇流(若干个物理上连续的簇称为一个簇流)。以上图的Data Runs为例:【11 01 2C 00 00 00 00 00】第一个字节【11】中的第二个1,表示接下来占用“1”个字节,用来存储该簇流占用簇的个数,即紧随其后的一个字节【01】,表示这个簇流占了0X01个簇的空间。第一个字节【11】中的第一个1,表示接下再占用“1”个字节,用来存储该簇流的偏移,即字节【2C】,表示偏移量为0X2C个簇。再往后一个字节为【00】,是结束标志。也就是说,该Data Runs只有一个簇流,而这个簇流包含一个簇,簇流的起始偏移为0X2C个簇,一个簇为8个扇区,即根目录所指向的索引区的偏移为44 * 8 = 352个扇区。
下面再给一个多簇流的data runs:
图12 Data Runs
十六进制的Data Runs 如下:31 05 F9 FF 0B 21 01 4E FF 11 01 12 31 01 12 CA F4 21 02 31 12 21 02 00 48 21 04 C9 0F 21 04 C9 59 31 04 87 11 01 21 08 57 10 00 02 A0 F8 FF FF。下面是对该Data Runs的解析:
图13 Data Runs解析
从上图可以看出,该Data Runs一共包含了10个簇流,其中第一个簇流占用了5个簇,起始偏移为0X0BFFF9,即786425个簇;第二个簇流占用1个簇,相对于第一个簇流偏移【-178】个簇,即第二个簇流起始偏移为786425 – 178 = 786247个簇。接下来的簇流的计算方法是一样的。
第五步:读取索引项
回到图11,我们计算出data runs指向的索引区的偏移为352个扇区,转到第352扇区,即索引区第一个扇区的位置,我们将看到如下数据(只截取了部分索引项):
图14 索引区域
索引区域占用了若干个簇,每一个簇都包含了两部分:一个标准索引头和若干个标准索引项。这两部分的结构定义如下:
//标准索引头的结构
typedef struct _STD_INDEX_HEADER {
BYTE SIH_Flag[4]; //固定值 "INDX"
UINT16 SIH_USNOffset;//更新序列号偏移
UINT16 SIH_USNSize;//更新序列号和更新数组大小
UINT64 SIH_Lsn; // 日志文件序列号(LSN)
UINT64 SIH_IndexCacheVCN;//本索引缓冲区在索引分配中的VCN
UINT32 SIH_IndexEntryOffset;//索引项的偏移 相对于当前位置
UINT32 SIH_IndexEntrySize;//索引项的大小
UINT32 SIH_IndexEntryAllocSize;//索引项分配的大小
UINT8 SIH_HasLeafNode;//置一 表示有子节点
BYTE SIH_Fill[3];//填充
UINT16 SIH_USN;//更新序列号
BYTE SIH_USNArray[0];//更新序列数组
}STD_INDEX_HEADER,*pSTD_INDEX_HEADER;
//标准索引项的结构
typedef struct _STD_INDEX_ENTRY {
UINT64 SIE_MFTReferNumber;//文件的MFT参考号
UINT16 SIE_IndexEntrySize;//索引项的大小
UINT16 SIE_FileNameAttriBodySize;//文件名属性体的大小
UINT16 SIE_IndexFlag;//索引标志
BYTE SIE_Fill[2];//填充
UINT64 SIE_FatherDirMFTReferNumber;//父目录MFT文件参考号
FILETIME SIE_CreatTime;//文件创建时间
FILETIME SIE_AlterTime;//文件最后修改时间
FILETIME SIE_MFTChgTime;//文件记录最后修改时间
FILETIME SIE_ReadTime;//文件最后访问时间
UINT64 SIE_FileAllocSize;//文件分配大小
UINT64 SIE_FileRealSize;//文件实际大小
UINT64 SIE_FileFlag;//文件标志
UINT8 SIE_FileNameSize;//文件名长度
UINT8 SIE_FileNamespace;//文件命名空间
BYTE SIE_FileNameAndFill[0];//文件名和填充
}STD_INDEX_ENTRY,*pSTD_INDEX_ENTRY;
在标准索引头中,几个比较重要的数据如下:
(1)索引项的偏移:即第一个标准索引项的偏移。在上图中为0X 00 00 00 40,这个偏移是相对于当前位置的偏移,即相对于字节【40】而言,偏移0X40个字节。
(2)索引项的大小:表示这个簇中,有效的索引项和索引头的总字节数。在上图中为0X 00 00 0A 80,即2688个字节,超出该范围的索引项为无效的索引项。
我们顺序读取每一个索引项,找到路径E:\dir1_0\dir2_0\dir3_1\新建文本文档.txt中目录dir1_0的索引项:
图15 目录dir1_0的索引项
标准索引项的结构已给出,其中几个重要的数据:
(1)文件的MFT参考号:低6字节是目录或者文件对应的文件记录的编号,由于MFT是顺序存储的,根据该编号可以定位到该文件记录在MFT中的位置。在上图中,目录dir1_0的MFT参考号为0X 00 00 00 00 00 2A,即dir1_0的文件记录是MFT的第0X2A,即第42项。之前已计算出MFT的偏移为6,291,456扇区,每一项占用2个扇区,因此dir1_0的文件记录的偏移为6,291,456 + 2 * 42 = 6291540扇区。
(2)索引项的长度:即本索引项占用的字节数,定义该索引项的边界。
第六步:现在可以递归啦
我们转到第6291540扇区,即dir1_0的文件记录,按照先前的步骤,去解析90属性和A0属性,就能进入下一层目录,再去定位索引项,越来越深,直到列出该目录的树形结构。OK,还是慢慢来,dir1_0的文件记录如下:
图16 dir1_0的文件记录
可以看到,30属性中的文件名为dir1_0,90属性中无索引项,A0属性的data runs为【11 01 2A 00 00 00 00 00】,计算得出dir1_0的索引区占用0X01个簇,偏移为0X2A个簇,即0X2A * 8 = 336扇区,转到336扇区,会看到如下索引数据:
图17 目录dir1_0下的索引项
找到路径E:\dir1_0\dir2_0\dir3_1\新建文本文档.txt中dir2_0的索引项,即上图的标准索引项1。得到dir2_0的文件记录的MFT编号为0X2D,可以计算出dir2_0的文件记录的偏移:MFT的起始偏移+MFT编号*2,即6,291,456 + 0X2D * 2 = 6291546扇区。读取该文件记录:
图18 dir2_0的文件记录
这个和以前有些不一样了,因为dir2_0只有两个子目录,90属性就够用了,因此没有A0属性。上面说过,90属性体由三部分构成:索引根、索引头和索引项。而90属性中的索引项和标准索引项的结构是一致的。接下来呢,我们找到路径E:\dir1_0\dir2_0\dir3_1\新建文本文档.txt中dir3_1的索引项,即上图中的索引项2,像以前一样计算出它的文件记录在MFT的位置并算出偏移扇区……
后面不再列举啦,一直找到新建文本文档.txt的文件记录,如下:
图19 新建文本文档.txt的文件记录
第七步:读取文件内容
对于文件呢,我们关注的是它的80属性,即数据属性。因为这个txt文件比较大,在MFT中无法存放其所有数据,因此这个文件的80属性为非常驻属性,属性体是一个data runs,指向存放该文件数据的簇。这里Data Runs的计算方法和前面是一致的,从上图可以看到,该data runs指向一个簇流,该簇流一共占用了0X 00 A9 89个簇,起始偏移为0X 4C C8 40个簇,即40256000扇区。我们转到该扇区,就可以读取这个txt中存储的数据了,截图如下:
图20 .txt中的数据
到这里我们已经读到了这个文本文档中的数据了。对于小文件而言,常驻的80属性就能够存放它的数据内容,比如下面这个文件:
图21 小文件的80属性
这个文本文档中只有一个字符串“sadfasdfasdf”,能够存放在常驻的80属性中,因此80属性的属性体并不是Data Runs,而是直接存放了文件内容。
嗯,到这里其实也差不多了,后面附上我写的测试代码吧。代码跳过了活动分区,因为活动分区比较活跃,最好是能够先打一个快照再去读取。
也传到github了:https://github.com/Hilaver/NtfsResolution/
如果后面想到了其他的东西再做补充。
下面是主程序的测试代码:
// ConsoleApplication.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "ntfs.h"
#include "fat32.h"
using namespace std;
//#pragma comment(lib, "Shlwapi.lib")
//#pragma comment(lib, "Kernel32.lib")
//#pragma comment(lib, "version.lib")
#pragma pack(1)
#pragma warning(disable : 4996)
typedef struct _PHY_INFO {
DWORD number;
vectorvols;
}PHY_INFO,*pPHY_INFO;
typedef struct _VCN_LCN_SIZE {
UINT64 VCN;
UINT64 LCN;
UINT64 SIZE;
_VCN_LCN_SIZE() {}
_VCN_LCN_SIZE(UINT64 vcn, UINT64 lcn, UINT64 size) {
this->VCN = vcn;
this->LCN = lcn;
this->SIZE = size;
}
~_VCN_LCN_SIZE() {}
}VCN_LCN_SIZE, *pVCN_LCN_SIZE;
FILE *fp;
//error msg
void GetErrorMessage(DWORD dwErrCode, DWORD dwLanguageId) {//dwLanguageId=0
DWORD dwRet = 0;
LPTSTR szResult = NULL;
setlocale(LC_ALL, "chs");
dwRet = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL,
dwErrCode, dwLanguageId, (LPTSTR)&szResult, 0, NULL);
if (dwRet == 0) { szResult = NULL; _tprintf(_T("No such errorCode\n")); }
else { _tprintf(_T("%s"), szResult); }
szResult = NULL;
return;
}
//char to WCHAR
WCHAR * charToWCHAR(char *s) {
int w_nlen = MultiByteToWideChar(CP_ACP, 0, s, -1, NULL, 0);
WCHAR *ret;
ret = (WCHAR*)malloc(sizeof(WCHAR)*w_nlen);
memset(ret, 0, sizeof(ret));
MultiByteToWideChar(CP_ACP, 0, s, -1, ret, w_nlen);
return ret;
}
//读取物理磁盘
//物理磁盘设备号 起始偏移(Byte) 读取长度(最小一个扇区) 输出缓冲
DWORD ReadDisk(DWORD physicalDriverNumber, UINT64 startOffset, DWORD size, PVOID ret) {
OVERLAPPED over = { 0 };
over.Offset = startOffset & (0xFFFFFFFF);
over.OffsetHigh = (startOffset >> 32)&(0xFFFFFFFF);
CHAR PHYSICALDRIVE[MAX_PATH]; memset(PHYSICALDRIVE, 0, sizeof(PHYSICALDRIVE));
strcpy(PHYSICALDRIVE, "\\\\.\\PHYSICALDRIVE");
PHYSICALDRIVE[strlen(PHYSICALDRIVE)] = '0' + physicalDriverNumber;
LPCWSTR PD = charToWCHAR(PHYSICALDRIVE);
HANDLE handle = CreateFile(PD, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (handle == INVALID_HANDLE_VALUE) return 0;
DWORD readsize;
if (ReadFile(handle, ret, size, &readsize, &over) == 0){
CloseHandle(handle);return 0;
}
CloseHandle(handle);
return readsize;
}
//根据逻辑分区获取其物理磁盘设备号
DWORD GetPhysicalDriveFromPartitionLetter(TCHAR letter)
{
HANDLE hDevice; // handle to the drive to be examined
BOOL result; // results flag
DWORD readed; // discard results
STORAGE_DEVICE_NUMBER number; //use this to get disk numbers
CHAR path[MAX_PATH];
sprintf(path, "\\\\.\\%c:", letter);
//printf("%s\n", path);
hDevice = CreateFile(charToWCHAR(path), // drive to open
GENERIC_READ | GENERIC_WRITE,// access to the drive
FILE_SHARE_READ | FILE_SHARE_WRITE,//share mode
NULL, // default security attributes
OPEN_EXISTING,// disposition
0,// file attributes
NULL);// do not copy file attribute
if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive
{
GetErrorMessage(GetLastError(), 0);
//fprintf(stderr, "CreateFile() Error: %ld\n", GetLastError());
return DWORD(-1);
}
result = DeviceIoControl(
hDevice,// handle to device
IOCTL_STORAGE_GET_DEVICE_NUMBER, // dwIoControlCode
NULL,// lpInBuffer
0, // nInBufferSize
&number, // output buffer
sizeof(number), // size of output buffer
&readed, // number of bytes returned
NULL // OVERLAPPED structure
);
if (!result) // fail
{
fprintf(stderr, "IOCTL_STORAGE_GET_DEVICE_NUMBER Error: %ld\n", GetLastError());
(void)CloseHandle(hDevice);
return (DWORD)-1;
}
//printf("DeviceType(设备类型) is %d, DeviceNumber(物理设备号) is %d, PartitionNumber(分区号) is %d\n", number.DeviceType, number.DeviceNumber, number.PartitionNumber);
(void)CloseHandle(hDevice);
return number.DeviceNumber;
}
//获取逻辑分区的信息如卷标、空间等
void getVolumeInfo(LPCWSTR volumeName) {
DWORD dwTotalClusters;//总的簇
DWORD dwFreeClusters;//可用的簇
DWORD dwSectPerClust;//每个簇有多少个扇区
DWORD dwBytesPerSect;//每个扇区有多少个字节
BOOL bResult = GetDiskFreeSpace((volumeName), &dwSectPerClust, &dwBytesPerSect, &dwFreeClusters, &dwTotalClusters);
printf(("总簇数:%d\n可用簇数:%d\n簇内扇区数:%d\n扇区内字节数:%d\n"), dwTotalClusters, dwFreeClusters, dwSectPerClust, dwBytesPerSect);
//GetErrorMessage(GetLastError(),0);
}
//print buffer in byte
void printBuffer(PVOID buffer, __int64 size) {
BYTE *p = (BYTE*)buffer;
__int64 pos = 0;
while (pos < size) {
fprintf(fp,"%+02X ", *(p++));
//printf("%+02X ", *(p++));
if (++pos % 16 == 0) { fprintf(fp,"\n"); /*printf("\n");*/}
}
}
void printBuffer2(PVOID buffer, __int64 size) {
BYTE *p = (BYTE*)buffer;
__int64 pos = 0;
while (pos < size) {
/*fprintf(fp, "%+02X ", *(p++));*/
printf("%+02X ", *(p++));
if (++pos % 16 == 0) { /*fprintf(fp, "\n");*/ printf("\n"); }
}
}
//不固定字节数的number转为带符号的INT64
INT64 Bytes2Int64(BYTE *num,UINT8 bytesCnt) {
INT64 ret = 0; bool isNegative = false;
memcpy(&ret, num, bytesCnt);
if (ret&(1 << (bytesCnt * 8 - 1))) {
isNegative = true;
INT64 tmp = (INT64(0X01)) << bytesCnt * 8;
for (int i = 0; i < 64 - bytesCnt * 8; i++) {
ret |= (tmp);
tmp <<= 1;
}
}
return isNegative ? -(~(ret)+1) : ret;
}
//获取物理磁盘设备号
vectorgetPhyDriverNumber() {
//这个地方应该有更好的实现方法
vectorphyvols[32];
DWORD dwSize = GetLogicalDriveStrings(0, NULL);
char* drivers = (char*)malloc(dwSize * 2);
DWORD dwRet = GetLogicalDriveStrings(dwSize, (LPWSTR)drivers);
wchar_t* lp = (wchar_t*)drivers;//所有逻辑驱动器的根驱动器路径 用0隔开
DWORD tmpNum = 0;
while (*lp) {
tmpNum = GetPhysicalDriveFromPartitionLetter(lp[0]);
phyvols[tmpNum].push_back(lp[0]);
lp += (wcslen(lp) + 1);//下一个根驱动器路径
}
vectortmpPhyInfo;
for (int i = 0; i < 32; i++) {
if (phyvols[i].size() != 0) {
PHY_INFO tmp;
tmp.number = i;
tmp.vols = phyvols[i];
tmpPhyInfo.push_back(tmp);
}
}
return tmpPhyInfo;
}
void parseMFTEntry(PVOID MFTEntry, DWORD IndexEntrySize,
DWORD phyDriverNumber, UINT64 volByteOffset, UINT8 secPerCluster,
UINT64 mftOffset, WCHAR *filePath);
void dfsIndexEntry(PVOID IndexEntryBuf, DWORD IndexEntrySize,
DWORD phyDriverNumber, UINT64 volByteOffset, UINT8 secPerCluster,
UINT64 mftOffset, WCHAR *filePath);
//DFS 索引项
//索引项 物理磁盘设备号 每个簇的扇区数 卷的物理偏移(字节) MFT的相对偏移(相对于本分区)
void dfsIndexEntry(PVOID IndexEntryBuf, DWORD IndexEntrySize,
DWORD phyDriverNumber, UINT64 volByteOffset, UINT8 secPerCluster, UINT64 mftOffset, WCHAR *filePath) {
printf("dfsIndexEntry\n");
//getchar();
//printf("IndexEntryBuf:\n");
//printBuffer2(IndexEntryBuf, IndexEntrySize);
//printf("\n");
//getchar();
pSTD_INDEX_ENTRY ptrIndexEntry = (pSTD_INDEX_ENTRY)IndexEntryBuf;
//BYTE *ptrOfIndexEntry = (BYTE*)IndexEntryBuf;
//获取MFT编号
UINT64 mftReferNumber = (ptrIndexEntry->SIE_MFTReferNumber) & 0XFFFFFFFFFFFF;//取低六字节
printf("SIE_MFTReferNumber is 0X%X\n", mftReferNumber);
fprintf(fp,"SIE_MFTReferNumber is 0X%X\n", mftReferNumber);
//读取文件名
//ptrOfIndexEntry = ptrIndexEntry->SIE_FileNameAndFill;
//UINT32 fileNameBytes = (ptrIndexEntry->SIE_FileNameSize) * 2;
//WCHAR *fileName = (WCHAR *)malloc(fileNameBytes + 2);
//memset(fileName, 0, fileNameBytes + 2);
//memcpy(fileName, ptrIndexEntry->SIE_FileNameAndFill, fileNameBytes);
//printf("SIE_FileName is %ls\n", fileName);
//读取该索引项对应的MFT
UINT64 mftEntryByteOffset = mftReferNumber * MFTEntrySize + mftOffset + volByteOffset;
printf("mftOffset is %llu\n", mftOffset);
printf("volByteOffset is %llu\n", volByteOffset);
printf("mftEntryByteOffset is %llu\n", mftEntryByteOffset);
PVOID mftEntryBuf = malloc(MFTEntrySize);
ReadDisk(phyDriverNumber, mftEntryByteOffset, MFTEntrySize, mftEntryBuf);
//printf("mftEntryBuf:\n");
//printBuffer2(mftEntryBuf, MFTEntrySize);
//printf("\n");
//getchar();
//解析MFT的0X90 0XA0属性
parseMFTEntry(mftEntryBuf, MFTEntrySize, phyDriverNumber, volByteOffset, secPerCluster, mftOffset, filePath);
}
//解析MFT表项 获取0X90 0XA0属性
//MFT表项 物理设备号 卷物理偏移(字节) 每个簇的扇区数
void parseMFTEntry(PVOID MFTEntry, DWORD IndexEntrySize, DWORD phyDriverNumber,
UINT64 volByteOffset, UINT8 secPerCluster, UINT64 mftOffset, WCHAR *filePath) {
printf("parseMFTEntry\n");
//getchar();
//printf("MFTEntry:\n");
//printBuffer2(MFTEntry, MFTEntrySize);
//printf("\n");
//getchar();
pFILE_RECORD_HEADER MFTEntryHeader = (pFILE_RECORD_HEADER)malloc(sizeof(FILE_RECORD_HEADER));
memcpy(MFTEntryHeader, MFTEntry, sizeof(FILE_RECORD_HEADER));
fprintf(fp, "MFTEntry : BytesAllocated is %u\n", MFTEntryHeader->BytesAllocated);
fprintf(fp, "MFTEntry : BytesInUse is %u\n", MFTEntryHeader->BytesInUse);
fprintf(fp,"MFTEntry : MFTRecordNumber is 0X%0X\n", UINT32((MFTEntryHeader->MFTRecordNumber)));
////
printf("MFTEntry : BytesAllocated is %u\n", MFTEntryHeader->BytesAllocated);
printf("MFTEntry : BytesInUse is %u\n", MFTEntryHeader->BytesInUse);
printf("MFTEntry : MFTRecordNumber is 0X%X\n", UINT32((MFTEntryHeader->MFTRecordNumber)));
////
//UINT32 MFTEntryNumber = MFTEntryHeader->MFTRecordNumber;
//printBuffer2(MFTEntryHeader, sizeof(FILE_RECORD_HEADER));
UINT16 attriOffset = MFTEntryHeader->AttributeOffset;//第一个属性偏移
printf("MFTEntry : AttributeOffset is %hu\n", MFTEntryHeader->AttributeOffset);
UINT8 *pointerInMFTEntry;
pointerInMFTEntry = (UINT8*)MFTEntry;
pointerInMFTEntry += attriOffset;
BYTE *pIndexOfMFTEntry = (BYTE*)MFTEntry;
pIndexOfMFTEntry += attriOffset;//pIndexOfMFTEntry偏移MFT头部大小 指向第一个属性头
UINT32 attriType, attriLen;
//BYTE ATTR_ResFlagAttri;
//get attribute
pCommonAttributeHeader pComAttriHeader = (pCommonAttributeHeader)malloc(sizeof(CommonAttributeHeader));
//存放目录下所有索引项的vector
vectorindexEntryOfDir;
//一个set 删除重复索引项
mapvisMftReferNum;
bool finishFlag = FALSE;
UINT32 attrCnt = 0;
//file path
WCHAR currentFilePath[2048] = { 0 };
//MFT项的类型 00表示删除文件,01表示正常文件,02表示删除目录,03表示正常目录
printf("isDirectoryOrFile[00删除文件,01正常文件,02删除目录,03正常目录]: %hu\n", MFTEntryHeader->Flags);
while (TRUE) {
//pIndexOfMFTEntry 现在指向属性头, 后面会加上这个属性的总长 指向下一个属性头
memcpy(&attriType, pIndexOfMFTEntry, 4);//attri type
if (attriType == 0xFFFFFFFF) { fprintf(fp, "\nATTR_Type is 0xFFFFFFFF, break\n"); break; }
fprintf(fp, "\nattrCnt : %u\n", attrCnt++);
fprintf(fp, "##### AttributeHeader #####\n");
memset(pComAttriHeader, 0, sizeof(CommonAttributeHeader));
//属性头的通用部分
memcpy(pComAttriHeader, pIndexOfMFTEntry, sizeof(CommonAttributeHeader));
fprintf(fp, "ATTR_Type is 0X%XATTR_Size is %d\n", pComAttriHeader->ATTR_Type, pComAttriHeader->ATTR_Size);
//如果属性总长>1024 先break
if (pComAttriHeader->ATTR_Size > 0x400) { fprintf(fp, "\attriLen is more than 1024, break\n"); break; }
fprintf(fp, "ATTR_NamOff is %huATTR_NamSz is %d", pComAttriHeader->ATTR_NamOff, pComAttriHeader->ATTR_NamSz);
//如果当前指针偏移大于MFT已用字节数 break
if ((pIndexOfMFTEntry - MFTEntry) > MFTEntryHeader->BytesInUse) {
fprintf(fp, "\nreach end of BytesInUse, break\n");
break;
}
//resolve attribute header
UINT16 attriHeaderSize = 0;
bool isResidentAttri = false;
switch (pComAttriHeader->ATTR_ResFlag)//是否常驻属性
{
case BYTE(0x00): {
isResidentAttri = true;
//get attribute header
pResidentAttributeHeader residentAttriHeader = (pResidentAttributeHeader)malloc(sizeof(ResidentAttributeHeader));
memcpy(residentAttriHeader, pIndexOfMFTEntry, sizeof(ResidentAttributeHeader));
fprintf(fp, "\n\n常驻属性\n\n");
//fprintf(fp, "ATTR_Size is %u\n", residentAttriHeader->ATTR_Size);
fprintf(fp, "ATTR_DatOff[属性头长度] is %hu\n", residentAttriHeader->ATTR_DatOff);
attriHeaderSize = residentAttriHeader->ATTR_DatOff;
UINT16 ResidentAttributeHeaderSize = residentAttriHeader->ATTR_DatOff;
residentAttriHeader = (pResidentAttributeHeader)realloc(residentAttriHeader, ResidentAttributeHeaderSize);
memcpy(residentAttriHeader, pIndexOfMFTEntry, ResidentAttributeHeaderSize);
//fprintf(fp, "ATTR_AttrNam[属性名] is %ls\n", residentAttriHeader->ATTR_AttrNam);
fprintf(fp, "ATTR_DatSz[属性体长度] is %u\n", residentAttriHeader->ATTR_DatSz);
fprintf(fp, "ATTR_Indx[属性索引] is %u\n", residentAttriHeader->ATTR_Indx);
break;
}
case BYTE(0x01): {
isResidentAttri = false;
//get attribute header
fprintf(fp, "\n\n非常驻属性\n\n");
pNonResidentAttributeHeader nonResidentAttriHeader = (pNonResidentAttributeHeader)malloc(sizeof(NonResidentAttributeHeader));
memcpy(nonResidentAttriHeader, pIndexOfMFTEntry, sizeof(NonResidentAttributeHeader));
//fprintf(fp, "\n\n非常驻属性\nATTR_Type is 0x%X\n", nonResidentAttriHeader->ATTR_Type);
fprintf(fp, "ATTR_DatOff[属性头长度] is %hu\n", nonResidentAttriHeader->ATTR_DatOff);
attriHeaderSize = nonResidentAttriHeader->ATTR_DatOff;
UINT16 NonResidentAttributeHeaderSize = nonResidentAttriHeader->ATTR_DatOff;
nonResidentAttriHeader = (pNonResidentAttributeHeader)realloc(nonResidentAttriHeader, NonResidentAttributeHeaderSize);
memcpy(nonResidentAttriHeader, pIndexOfMFTEntry, NonResidentAttributeHeaderSize);
fprintf(fp, "ATTR_StartVCN[起始VCN] is %llu\n", nonResidentAttriHeader->ATTR_StartVCN);
fprintf(fp, "ATTR_EndVCN[终止VCN] is %llu\n", nonResidentAttriHeader->ATTR_EndVCN);
fprintf(fp, "ATTR_ValidSz[属性实际长度 is %llu\n", nonResidentAttriHeader->ATTR_ValidSz);
fprintf(fp, "ATTR_AllocSz[属性分配长度] is %llu\n", nonResidentAttriHeader->ATTR_AllocSz);
fprintf(fp, "ATTR_InitedSz[属性初始长度] is %llu\n", nonResidentAttriHeader->ATTR_InitedSz);
//fprintf(fp, "ATTR_AttrNam[属性名] is %ls\n", nonResidentAttriHeader->ATTR_AttrNam);
break;
}
default:
break;
}
fprintf(fp, "\n##### END of AttributeHeader #####\n");
//resolve attribute data
BYTE *tmpAttriDataIndex = pIndexOfMFTEntry;
//待修改
//tmpAttriDataIndex += (pComAttriHeader->ATTR_ResFlag == BYTE(0x00) ? sizeof(ResidentAttributeHeader) : sizeof(NonResidentAttributeHeader));
tmpAttriDataIndex += (attriHeaderSize);
//tmpAttriDataIndex指向属性体
switch (pComAttriHeader->ATTR_Type)
{
case 0x00000030: {
//文件名属性 可能多个
pFILE_NAME ptrFileName = (pFILE_NAME)malloc(sizeof(FILE_NAME));
memcpy(ptrFileName, tmpAttriDataIndex, sizeof(FILE_NAME));
if (ptrFileName->FN_NamSpace == 0X02) { break; }
fprintf(fp, "\n##### FILE_NAME #####\n");
printf("\n##### FILE_NAME #####\n");
fprintf(fp, "FN_NameSz is %d\n", ptrFileName->FN_NameSz);
printf("FN_NameSz is %d\n", ptrFileName->FN_NameSz);
fprintf(fp, "FN_NamSpace is %d\n", ptrFileName->FN_NamSpace);
printf("FN_NamSpace is %d\n", ptrFileName->FN_NamSpace);
//get file name
UINT32 fileNameLen = UINT32(0xFFFF & (ptrFileName->FN_NameSz) + 1) << 1;
WCHAR *fileName = (WCHAR*)malloc(fileNameLen);
memset(fileName, 0, fileNameLen);
memcpy(fileName, tmpAttriDataIndex + sizeof(FILE_NAME), fileNameLen - 2);
//printf("FILENAME[0X] is:\n");
//printBuffer2(fileName, fileNameLen);
//printf("\n");
fprintf(fp, "FILENAME is %ls\n", fileName);
printf("FILENAME is %ls\n", fileName);
memset(currentFilePath, 0, sizeof(currentFilePath));
wcscpy(currentFilePath, filePath);
wcscat(currentFilePath,L"\\");
wcscat(currentFilePath, fileName);
printf("\n\n------>\ncurrentFilePath is %ls\n", currentFilePath);
fprintf(fp, "---> currentFilePath is %ls\n", currentFilePath);
fprintf(fp, "\n##### END of FILE_NAME #####\n");
printf("\n##### END of FILE_NAME #####\n");
getchar();
break;
}
case 0x00000090: {//INDEX_ROOT 索引根
pINDEX_ROOT pIndexRoot = (INDEX_ROOT*)malloc(sizeof(INDEX_ROOT));
memcpy(pIndexRoot, tmpAttriDataIndex, sizeof(INDEX_ROOT));
fprintf(fp, "\n##### INDEX_ROOT #####\n");
fprintf(fp, "IR_EntrySz[目录项的大小,一般是一个簇] is %u\n", pIndexRoot->IR_EntrySz);
fprintf(fp, "IR_ClusPerRec[目录项占用的簇数,一般是一个] is %u\n", pIndexRoot->IR_ClusPerRec);
fprintf(fp, "IH_TalSzOfEntries[索引根和紧随其后的索引项的大小] is %u\n", pIndexRoot->IH.IH_TalSzOfEntries);
fprintf(fp, "IH_EntryOff[第一个索引项的偏移] is %u\n", pIndexRoot->IH.IH_EntryOff);
printf("\n##### INDEX_ROOT #####\n");
printf("IR_EntrySz[目录项的大小,一般是一个簇] is %u\n", pIndexRoot->IR_EntrySz);
printf("IR_ClusPerRec[目录项占用的簇数,一般是一个] is %u\n", pIndexRoot->IR_ClusPerRec);
printf("IH_TalSzOfEntries[索引根和紧随其后的索引项的大小] is %u\n", pIndexRoot->IH.IH_TalSzOfEntries);
printf("IH_EntryOff[第一个索引项的偏移] is %u\n", pIndexRoot->IH.IH_EntryOff);
//0X90属性的实际大小
UINT32 attri90Size = sizeof(INDEX_ROOT) - sizeof(INDEX_HEADER) + pIndexRoot->IH.IH_TalSzOfEntries;
pIndexRoot = (INDEX_ROOT*)realloc(pIndexRoot, attri90Size);
memcpy(pIndexRoot, tmpAttriDataIndex, attri90Size);
//获取90属性中的索引头
INDEX_HEADER IR_IH = pIndexRoot->IH;
UINT32 indexTotalSize = pIndexRoot->IH.IH_TalSzOfEntries;//索引头和接下来的索引项的总大小 注意可能没有索引项
//获取90属性中的索引项
BYTE *pIndexOfEntry, *ptrIndexHeaderStart;//索引项的指针 索引头的指针
pIndexOfEntry = pIndexRoot->IR_IndexEntry;
ptrIndexHeaderStart = (BYTE*)(&(pIndexRoot->IH));
UINT32 indexEntryIn90AttriCnt = 0;
while (TRUE) {
UINT64 isIndexEntryFinish = 0;
memcpy(&isIndexEntryFinish, pIndexOfEntry, 8);
if (isIndexEntryFinish == 0X00) {
//MFT号是0 break
break;
}
if (pIndexOfEntry - ptrIndexHeaderStart > indexTotalSize) {
//超出有效长度 break
break;
}
INDEX_ENTRY *pIndexEntry = (INDEX_ENTRY *)pIndexOfEntry;
//printf("IE_FileNameSize is %d\n", pIndexEntry->IE_FileNameSize);
UINT32 fileNameBytes = (pIndexEntry->IE_FileNameSize) * 2;
WCHAR *fileName = (WCHAR *)malloc(fileNameBytes + 2);
memset(fileName, 0, fileNameBytes + 2);
memcpy(fileName, pIndexEntry->IE_FileNameAndFill, fileNameBytes);
//printf("IE_FileName is %ls\n", fileName);
indexEntryIn90AttriCnt++;
//printBuffer2(pIndexEntry, pIndexEntry->IE_Size);
//printf("\n");
//getchar();
/*******************************/
if (visMftReferNum.find((pIndexEntry->IE_MftReferNumber) & 0XFFFFFFFFFFFF) == visMftReferNum.end() && ((((pIndexEntry->IE_MftReferNumber) >> (8 * 6)) & 0XFFFF) != 0X00)) {
// $ObjId的索引项正常 但其指向的MFT的90属性异常 其中该MFT号的高2位为0 90属性中的MFT引用号异常
//这里读到了索引项
printf("find index entry in 90 attribute, cnt is %d\n", indexEntryIn90AttriCnt);
fprintf(fp, "find index entry in 90 attribute, cnt is %d\n", indexEntryIn90AttriCnt);
indexEntryOfDir.push_back(pSTD_INDEX_ENTRY(pIndexEntry));
visMftReferNum.insert(pair((pIndexEntry->IE_MftReferNumber) & 0XFFFFFFFFFFFF, TRUE));
}
//dfsIndexEntry((PVOID)pIndexEntry, pIndexEntry->IE_Size, phyDriverNumber, volByteOffset, secPerCluster, mftOffset);
/*******************************/
pIndexOfEntry += pIndexEntry->IE_Size;
//getchar();
}
printf("\n##### END of INDEX_ROOT #####\n");
break;
}
case 0x000000A0: {//INDEX_ALLOCATION
fprintf(fp, "\n##### INDEX_ALLOCATION #####\n");
printf("\n##### INDEX_ALLOCATION #####\n");
//A0属性体
//存放VCN LCN
vectordataRuns;
//data runs 起始指针
BYTE *dataRunsStartOffset = tmpAttriDataIndex;
//data runs 总字节数
UINT32 attriBodySize = (pIndexOfMFTEntry + pComAttriHeader->ATTR_Size) - tmpAttriDataIndex;
//printf("attriBodySize is %u\n", attriBodySize);
//getchar();
UINT64 vcnCnt = 0;
while ((*dataRunsStartOffset) != 0X00 && dataRunsStartOffset - tmpAttriDataIndex < attriBodySize) {
UINT8 bytesClustersOfStdIndex = (*dataRunsStartOffset) & 0X0F, bytesCluOffsetOfStdIndex = (*dataRunsStartOffset >> 4) & 0X0F;
UINT32 totalBytes = bytesClustersOfStdIndex + bytesCluOffsetOfStdIndex;
//VCN.push_back(vcnCnt++);
UINT64 clustersOfStdIndex = 0;
INT64 cluOffsetOfStdIndex = 0;
//BYTE *ptrInDataRuns = (BYTE *)(&dataRuns);
memcpy(&clustersOfStdIndex, dataRunsStartOffset + 1, bytesClustersOfStdIndex);
//memcpy(&cluOffsetOfStdIndex, dataRunsStartOffset + 1 + bytesClustersOfStdIndex, bytesCluOffsetOfStdIndex);
cluOffsetOfStdIndex = Bytes2Int64(dataRunsStartOffset + 1 + bytesClustersOfStdIndex, bytesCluOffsetOfStdIndex);
//printf("Bytes2Int64 is %lld\n",Bytes2Int64(dataRunsStartOffset + 1 + bytesClustersOfStdIndex, bytesCluOffsetOfStdIndex));
if (vcnCnt == 0) {
dataRuns.push_back(VCN_LCN_SIZE(vcnCnt, cluOffsetOfStdIndex, clustersOfStdIndex));
}
else {
dataRuns.push_back(VCN_LCN_SIZE(vcnCnt, dataRuns[vcnCnt-1].LCN + cluOffsetOfStdIndex, clustersOfStdIndex));
}
vcnCnt++;
dataRunsStartOffset += (totalBytes + 1);
}
printf("-->INDEX_ALLOCATION Cluster Info\n");
for (int i = 0; i < dataRuns.size(); i++) {
printf("VCN, LCN, SIZE: %llu, %llu, %llu\n",dataRuns[i].VCN, dataRuns[i].LCN, dataRuns[i].SIZE);
}
printf("-->End of INDEX_ALLOCATION Cluster Info\n");
getchar();
for (int i = 0; i < dataRuns.size(); i++) {
DWORD stdIndexSize = dataRuns[i].SIZE * secPerCluster*SectorSize;//标准索引占用的总字节数
UINT64 stdIndexByteOffset = volByteOffset + dataRuns[i].LCN * secPerCluster*SectorSize;//标准索引的物理偏移
//读取标准索引区的数据 该data run 的N个簇全部读取
PVOID pStdIndexBuffer = malloc(stdIndexSize);
ReadDisk(phyDriverNumber, stdIndexByteOffset, stdIndexSize, pStdIndexBuffer);
fprintf(fp, "->data run cnt: %d\n", i);
printf("->data run cnt: %d\n", i);
UINT32 indexClusterCnt = 0;
//逐个簇分析
UINT32 indexEntryInIndxArea = 0;
while (indexClusterCnt < dataRuns[i].SIZE) {
//
fprintf(fp, "->cluster cnt: %d\n", indexClusterCnt);
printf("->cluster cnt: %d\n", indexClusterCnt);
//读取标准索引的头部
BYTE *ptrOfStdIndexBuffer = (BYTE*)pStdIndexBuffer + (indexClusterCnt*secPerCluster*SectorSize);//标准索引的指针
indexClusterCnt++;
//ptrIndex指向该索引簇的起始位置
BYTE *ptrIndex = ptrOfStdIndexBuffer;
pSTD_INDEX_HEADER pStdIndexHeader = (pSTD_INDEX_HEADER)malloc(sizeof(STD_INDEX_HEADER));
memcpy(pStdIndexHeader, ptrOfStdIndexBuffer, sizeof(STD_INDEX_HEADER));
UINT32 SIH_Flag;
memcpy(&SIH_Flag, pStdIndexHeader->SIH_Flag, 4);
if (SIH_Flag == 0X00000000) { break; }
//标准索引头的大小=第一个索引项的偏移+24字节
UINT32 stdIndexHeaderSize = pStdIndexHeader->SIH_IndexEntryOffset + 8 * 3;
pStdIndexHeader = (pSTD_INDEX_HEADER)realloc(pStdIndexHeader, stdIndexHeaderSize);
memcpy(pStdIndexHeader, ptrOfStdIndexBuffer, stdIndexHeaderSize);
fprintf(fp, "\n##### STD_INDEX_HEADER #####\n");
fprintf(fp, "SIH_IndexEntryOffset[索引项偏移,从此位置开始] is %u\n", pStdIndexHeader->SIH_IndexEntryOffset);
fprintf(fp, "SIH_IndexEntrySize[索引项总大小] is %u\n", pStdIndexHeader->SIH_IndexEntrySize);
printf("\n##### STD_INDEX_HEADER #####\n");
printf("SIH_IndexEntryOffset[索引项偏移,从此位置开始] is %u\n", pStdIndexHeader->SIH_IndexEntryOffset);
printf("SIH_IndexEntrySize[索引项总大小] is %u\n", pStdIndexHeader->SIH_IndexEntrySize);
UINT32 stdIndexTotalSize = pStdIndexHeader->SIH_IndexEntrySize;
printf("SIH_IndexEntryAllocSize[索引项总分配大小] is %u\n", pStdIndexHeader->SIH_IndexEntryAllocSize);
printf("\n##### END of STD_INDEX_HEADER #####\n");
fprintf(fp, "SIH_IndexEntryAllocSize[索引项总分配大小] is %u\n", pStdIndexHeader->SIH_IndexEntryAllocSize);
fprintf(fp, "\n##### END of STD_INDEX_HEADER #####\n");
//指针移动到索引项
ptrOfStdIndexBuffer += stdIndexHeaderSize;
//BYTE *ptrIndexEntryStartOffset = ptrOfStdIndexBuffer;
while (TRUE) {
//MFT编号为0 break
UINT64 isIndexEntryFinish = 0;
memcpy(&isIndexEntryFinish, ptrOfStdIndexBuffer, 8);
//超出有效长度 break
if (ptrOfStdIndexBuffer - ptrIndex > stdIndexTotalSize) {
fprintf(fp, "超出INDEX有效范围, break\n");
printf("超出INDEX有效范围, break\n");
break;
}
//if (isIndexEntryFinish == UINT64(0)) { break; }
if (isIndexEntryFinish == UINT64(0)) { fprintf(fp, "find mft refer number[00]\n"); }
if (isIndexEntryFinish == UINT64(0)) { printf("find mft refer number[00]\n"); }
//printf("ptrOfStdIndexBuffer - ptrIndexEntryStartOffset is %d\n", ptrOfStdIndexBuffer - ptrIndexEntryStartOffset);
//逐个读取索引项
pSTD_INDEX_ENTRY pStdIndexEntry = (pSTD_INDEX_ENTRY)malloc(sizeof(STD_INDEX_ENTRY));
memcpy(pStdIndexEntry, ptrOfStdIndexBuffer, sizeof(STD_INDEX_ENTRY));
UINT32 stdIndexEntrySize = pStdIndexEntry->SIE_IndexEntrySize;
pStdIndexEntry = (pSTD_INDEX_ENTRY)realloc(pStdIndexEntry, stdIndexEntrySize);
memcpy(pStdIndexEntry, ptrOfStdIndexBuffer, stdIndexEntrySize);
if (pStdIndexEntry->SIE_MFTReferNumber == UINT64(0)) { break; }
//printf("\n##### STD_INDEX_ENTRY #####\n");
//fprintf(fp, "\n");
//printBuffer(pStdIndexEntry, stdIndexEntrySize);
//fprintf(fp, "\n");
//printf("SIE_MFTReferNumber is %d\n", (pStdIndexEntry->SIE_MFTReferNumber) & 0XFFFFFFFFFFFF);
//printf("SIE_IndexEntrySize[索引项大小] is %u\n", (pStdIndexEntry->SIE_IndexEntrySize));
//printf("SIE_FileNameSize is %d\n", (pStdIndexEntry->SIE_FileNameSize));
//printf("SIE_FileAllocSize is %llu\n", (pStdIndexEntry->SIE_FileAllocSize));
//printf("SIE_FileRealSize is %llu\n", (pStdIndexEntry->SIE_FileRealSize));
//UINT8 fileNameBytes = (pStdIndexEntry->SIE_FileNameSize) * 2;
//WCHAR *fileName = (WCHAR *)malloc(fileNameBytes + 2);
//memcpy(fileName, pStdIndexEntry->SIE_FileNameAndFill, fileNameBytes);
//printf("SIE_FileName is %ls\n", fileName);
//printf("\n##### END of STD_INDEX_ENTRY #####\n");
//这里读到了索引项
/********************************/
if (visMftReferNum.find((pStdIndexEntry->SIE_MFTReferNumber) & 0XFFFFFFFFFFFF) == visMftReferNum.end() && ((((pStdIndexEntry->SIE_MFTReferNumber) >> (8 * 6)) & 0XFFFF) != 0X00)) {
printf("find index entry in INDX area, cnt is %d\n", indexEntryInIndxArea);
fprintf(fp, "find index entry in INDX area, cnt is %d\n", indexEntryInIndxArea);
indexEntryInIndxArea++;
indexEntryOfDir.push_back(pSTD_INDEX_ENTRY(pStdIndexEntry));
visMftReferNum.insert(pair((pStdIndexEntry->SIE_MFTReferNumber) & 0XFFFFFFFFFFFF, TRUE));
}
//dfsIndexEntry((PVOID)pStdIndexEntry, stdIndexEntrySize, phyDriverNumber, volByteOffset, secPerCluster, mftOffset);
/********************************/
//ptrOfStdIndexBuffer指向下一个索引项
ptrOfStdIndexBuffer += stdIndexEntrySize;
//getchar();
}
//printBuffer2(pStdIndexBuffer, stdIndexSize);
//getchar();
//fprintf(fp, "\n##### END of INDEX_ALLOCATION #####\n");
//printf("\n##### END of INDEX_ALLOCATION #####\n");
}
}
fprintf(fp,"\n##### END of INDEX_ALLOCATION #####\n");
printf("\n##### END of INDEX_ALLOCATION #####\n");
break;
}
case 0x00000010: {
break;
}
case 0x00000020: {
break;
}
case 0x00000040: {
break;
}
case 0x00000050: {
break;
}
case 0x00000060: {
break;
}
case 0x00000070: {
break;
}
case 0x00000080: {
//文件数据属性
//80属性体长度
UINT32 attriBodySize = (pIndexOfMFTEntry + pComAttriHeader->ATTR_Size) - tmpAttriDataIndex;
//属性体
pDATA ptrData = (pDATA)malloc(attriBodySize);
memcpy(ptrData, tmpAttriDataIndex, attriBodySize);
//如果是常驻属性 80属性体就是文件内容
if (isResidentAttri) {
//80属性体中的文件内容
printf("content in 0X80[resident]:\n");
printBuffer2(ptrData, attriBodySize);
printf("\n");
}
//如果是非常驻属性 80属性体是data runs
else {
printf("content in 0X80[nonresident]:\n");
printBuffer2(ptrData, attriBodySize);
printf("\n");
//存放VCN LCN
vectordataRuns;
//data runs 起始指针
BYTE *dataRunsStartOffset = tmpAttriDataIndex;
//data runs 总字节数
UINT32 attriBodySize = (pIndexOfMFTEntry + pComAttriHeader->ATTR_Size) - tmpAttriDataIndex;
//printf("attriBodySize is %u\n", attriBodySize);
//getchar();
UINT64 vcnCnt = 0;
while ((*dataRunsStartOffset) != 0X00 && dataRunsStartOffset - tmpAttriDataIndex < attriBodySize) {
UINT8 bytesClustersOfStdIndex = (*dataRunsStartOffset) & 0X0F, bytesCluOffsetOfStdIndex = (*dataRunsStartOffset >> 4) & 0X0F;
UINT32 totalBytes = bytesClustersOfStdIndex + bytesCluOffsetOfStdIndex;
//VCN.push_back(vcnCnt++);
UINT64 clustersOfStdIndex = 0;
INT64 cluOffsetOfStdIndex = 0;
//BYTE *ptrInDataRuns = (BYTE *)(&dataRuns);
memcpy(&clustersOfStdIndex, dataRunsStartOffset + 1, bytesClustersOfStdIndex);
//memcpy(&cluOffsetOfStdIndex, dataRunsStartOffset + 1 + bytesClustersOfStdIndex, bytesCluOffsetOfStdIndex);
cluOffsetOfStdIndex = Bytes2Int64(dataRunsStartOffset + 1 + bytesClustersOfStdIndex, bytesCluOffsetOfStdIndex);
//printf("Bytes2Int64 is %lld\n",Bytes2Int64(dataRunsStartOffset + 1 + bytesClustersOfStdIndex, bytesCluOffsetOfStdIndex));
if (vcnCnt == 0) {
dataRuns.push_back(VCN_LCN_SIZE(vcnCnt, cluOffsetOfStdIndex, clustersOfStdIndex));
}
else {
dataRuns.push_back(VCN_LCN_SIZE(vcnCnt, dataRuns[vcnCnt - 1].LCN + cluOffsetOfStdIndex, clustersOfStdIndex));
}
vcnCnt++;
dataRunsStartOffset += (totalBytes + 1);
}
printf("-->DATA Cluster Info\n");
for (int i = 0; i < dataRuns.size(); i++) {
printf("VCN, LCN, SIZE: %llu, %llu, %llu\n", dataRuns[i].VCN, dataRuns[i].LCN, dataRuns[i].SIZE);
}
//拿到了文件每一块的LCN和对应的簇数
//能够直接读取文件内容了
printf("-->End of DATA Cluster Info\n");
getchar();
}
break;
}
case 0x000000B0: {
break;
}
case 0x000000C0: {
break;
}
case 0x000000D0: {
break;
}
case 0x000000E0: {
break;
}
case 0x000000F0: {
break;
}
case 0x00000100: {
break;
}
default: {
finishFlag = TRUE;
break;
}
}
pIndexOfMFTEntry += pComAttriHeader->ATTR_Size;
if (finishFlag) { break; }
}
for (int i = 0; i < indexEntryOfDir.size(); i++) {
//
fprintf(fp,"WATCH :: IndexEntryOfDir -> %d:\n", i);
printBuffer(indexEntryOfDir[i], indexEntryOfDir[i]->SIE_IndexEntrySize);
fprintf(fp,"\n");
//printf("WATCH :: IndexEntryOfDir -> %d:\n", i);
//printBuffer2(indexEntryOfDir[i], indexEntryOfDir[i]->SIE_IndexEntrySize);
//printf("\n");
//getchar();
//dfsIndexEntry((PVOID)(indexEntryOfDir[i]), indexEntryOfDir[i]->SIE_IndexEntrySize, phyDriverNumber, volByteOffset, secPerCluster, mftOffset);
}
for (int i = 0; i < indexEntryOfDir.size(); i++) {
//
//printf("indexEntryOfDir of root:\n");
//printBuffer2(indexEntryOfDir[i], indexEntryOfDir[i]->SIE_IndexEntrySize);
//printf("\n");
//getchar();
fprintf(fp,"begin DFS in Dir\n");
fprintf(fp,"Index Entry Buff:\n");
printBuffer(indexEntryOfDir[i], indexEntryOfDir[i]->SIE_IndexEntrySize);
fprintf(fp,"\n");
printf("begin DFS in Dir\n");
//printf("Index Entry Buff:\n");
//printBuffer2(indexEntryOfDir[i], indexEntryOfDir[i]->SIE_IndexEntrySize);
printf("\n");
if (UINT64((indexEntryOfDir[i]->SIE_MFTReferNumber) & 0XFFFFFFFFFFFF) < 16) {
fprintf(fp,"SIE_MFTReferNumber < 16, continue\n");
//printf("SIE_MFTReferNumber < 16, continue\n");
}
else {
fprintf(fp,"SIE_MFTReferNumber >= 16, begin search\n");
//printf("SIE_MFTReferNumber >= 16, begin search\n");
dfsIndexEntry((PVOID)(indexEntryOfDir[i]), indexEntryOfDir[i]->SIE_IndexEntrySize, phyDriverNumber, volByteOffset, secPerCluster, mftOffset, currentFilePath);
}
//dfsIndexEntry((PVOID)(indexEntryOfDir[i]), indexEntryOfDir[i]->SIE_IndexEntrySize, phyDriverNumber, volByteOffset, secPerCluster, mftOffset);
}
indexEntryOfDir.clear();
visMftReferNum.clear();
//getchar();
}
//解析MFT表项
void resolveMFTEntry(PVOID MFTEntry, DWORD phyDriverNumber, UINT64 volByteOffset, UINT8 secPerCluster, UINT64 mftOffset) {//MFT表项 物理设备号 卷物理偏移(字节) 每个簇的扇区数 mft相对于该分区的偏移
//printBuffer(MFTEntry, MFTEntrySize);
//get MFTEntryHeader
pFILE_RECORD_HEADER MFTEntryHeader = (pFILE_RECORD_HEADER)malloc(sizeof(FILE_RECORD_HEADER));
memcpy(MFTEntryHeader, MFTEntry, sizeof(FILE_RECORD_HEADER));
fprintf(fp, "MFTEntry : BytesAllocated is %u\n", MFTEntryHeader->BytesAllocated);
fprintf(fp, "MFTEntry : BytesInUse is %u\n", MFTEntryHeader->BytesInUse);
fprintf(fp, "MFTEntry : MFTRecordNumber is %u\n", MFTEntryHeader->MFTRecordNumber);
UINT32 MFTEntryNumber = MFTEntryHeader->MFTRecordNumber;
//printBuffer2(MFTEntryHeader, sizeof(FILE_RECORD_HEADER));
UINT16 attriOffset = MFTEntryHeader->AttributeOffset;//第一个属性偏移
UINT8 *pointerInMFTEntry;
pointerInMFTEntry = (UINT8*)MFTEntry;
pointerInMFTEntry += attriOffset;
BYTE *pIndexOfMFTEntry = (BYTE*)MFTEntry;
pIndexOfMFTEntry += attriOffset;//pIndexOfMFTEntry偏移MFT头部大小 指向第一个属性头
UINT32 attriType, attriLen;
//BYTE ATTR_ResFlagAttri;
//get attribute
pCommonAttributeHeader pComAttriHeader = (pCommonAttributeHeader)malloc(sizeof(CommonAttributeHeader));
bool finishFlag = FALSE;
UINT32 attrCnt = 0;
while (TRUE) {
//pIndexOfMFTEntry 现在指向属性头, 后面会加上这个属性的总长 指向下一个属性头
memcpy(&attriType, pIndexOfMFTEntry, 4);//attri type
if (attriType == 0xFFFFFFFF) { fprintf(fp, "\nATTR_Type is 0xFFFFFFFF, break\n"); break; }
fprintf(fp, "\nattrCnt : %u\n", attrCnt++);
fprintf(fp, "##### AttributeHeader #####\n");
memset(pComAttriHeader, 0, sizeof(CommonAttributeHeader));
//属性头的通用部分
memcpy(pComAttriHeader, pIndexOfMFTEntry, sizeof(CommonAttributeHeader));
fprintf(fp, "ATTR_Type is 0X%XATTR_Size is %d\n", pComAttriHeader->ATTR_Type, pComAttriHeader->ATTR_Size);
//如果属性总长>1024 先break
if (pComAttriHeader->ATTR_Size > 0x400) { fprintf(fp, "\attriLen is more than 1024, break\n"); break; }
fprintf(fp, "ATTR_NamOff is %huATTR_NamSz is %d", pComAttriHeader->ATTR_NamOff, pComAttriHeader->ATTR_NamSz);
//如果当前指针偏移大于MFT已用字节数 break
if ((pIndexOfMFTEntry - MFTEntry) > MFTEntryHeader->BytesInUse) {
fprintf(fp, "\nreach end of BytesInUse, break\n");
break;
}
//resolve attribute header
UINT16 attriHeaderSize = 0;
switch (pComAttriHeader->ATTR_ResFlag)//是否常驻属性
{
case BYTE(0x00): {
//get attribute header
pResidentAttributeHeader residentAttriHeader = (pResidentAttributeHeader)malloc(sizeof(ResidentAttributeHeader));
memcpy(residentAttriHeader, pIndexOfMFTEntry, sizeof(ResidentAttributeHeader));
fprintf(fp, "\n\n常驻属性\n\n");
//fprintf(fp, "ATTR_Size is %u\n", residentAttriHeader->ATTR_Size);
fprintf(fp, "ATTR_DatOff[属性头长度] is %hu\n", residentAttriHeader->ATTR_DatOff);
attriHeaderSize = residentAttriHeader->ATTR_DatOff;
UINT16 ResidentAttributeHeaderSize = residentAttriHeader->ATTR_DatOff;
residentAttriHeader = (pResidentAttributeHeader)realloc(residentAttriHeader, ResidentAttributeHeaderSize);
memcpy(residentAttriHeader, pIndexOfMFTEntry, ResidentAttributeHeaderSize);
//fprintf(fp, "ATTR_AttrNam[属性名] is %ls\n", residentAttriHeader->ATTR_AttrNam);
fprintf(fp, "ATTR_DatSz[属性体长度] is %u\n", residentAttriHeader->ATTR_DatSz);
fprintf(fp, "ATTR_Indx[属性索引] is %u\n", residentAttriHeader->ATTR_Indx);
break;
}
case BYTE(0x01): {
//get attribute header
fprintf(fp, "\n\n非常驻属性\n\n");
pNonResidentAttributeHeader nonResidentAttriHeader = (pNonResidentAttributeHeader)malloc(sizeof(NonResidentAttributeHeader));
memcpy(nonResidentAttriHeader, pIndexOfMFTEntry, sizeof(NonResidentAttributeHeader));
//fprintf(fp, "\n\n非常驻属性\nATTR_Type is 0x%X\n", nonResidentAttriHeader->ATTR_Type);
fprintf(fp, "ATTR_DatOff[属性头长度] is %hu\n", nonResidentAttriHeader->ATTR_DatOff);
attriHeaderSize = nonResidentAttriHeader->ATTR_DatOff;
UINT16 NonResidentAttributeHeaderSize = nonResidentAttriHeader->ATTR_DatOff;
nonResidentAttriHeader = (pNonResidentAttributeHeader)realloc(nonResidentAttriHeader, NonResidentAttributeHeaderSize);
memcpy(nonResidentAttriHeader, pIndexOfMFTEntry, NonResidentAttributeHeaderSize);
fprintf(fp, "ATTR_StartVCN[起始VCN] is %llu\n", nonResidentAttriHeader->ATTR_StartVCN);
fprintf(fp, "ATTR_EndVCN[终止VCN] is %llu\n", nonResidentAttriHeader->ATTR_EndVCN);
fprintf(fp, "ATTR_ValidSz[属性实际长度 is %llu\n", nonResidentAttriHeader->ATTR_ValidSz);
fprintf(fp, "ATTR_AllocSz[属性分配长度] is %llu\n", nonResidentAttriHeader->ATTR_AllocSz);
fprintf(fp, "ATTR_InitedSz[属性初始长度] is %llu\n", nonResidentAttriHeader->ATTR_InitedSz);
//fprintf(fp, "ATTR_AttrNam[属性名] is %ls\n", nonResidentAttriHeader->ATTR_AttrNam);
break;
}
default:
break;
}
fprintf(fp, "\n##### END of AttributeHeader #####\n");
//resolve attribute data
BYTE *tmpAttriDataIndex = pIndexOfMFTEntry;
//指向属性体
tmpAttriDataIndex += (attriHeaderSize);
//tmpAttriDataIndex指向属性体
switch (pComAttriHeader->ATTR_Type)
{
case 0x00000010: {//STANDARD_INFORMATION 10属性
pSTANDARD_INFORMATION stdInfo = (pSTANDARD_INFORMATION)malloc(sizeof(STANDARD_INFORMATION));
memcpy(stdInfo, tmpAttriDataIndex, sizeof(STANDARD_INFORMATION));
SYSTEMTIME sysTime;
FileTimeToSystemTime(&(stdInfo->SI_CreatTime), &sysTime);
fprintf(fp, "\n##### STANDARD_INFORMATION #####\n");
//printBuffer(stdInfo, sizeof(STANDARD_INFORMATION));
fprintf(fp, "\n");
fprintf(fp, "SI_CreatTime-wYear is %hu\n", sysTime.wYear);
fprintf(fp, "SI_CreatTime-wMonth is %hu\n", sysTime.wMonth);
fprintf(fp, "SI_CreatTime-wDay is %hu\n", sysTime.wDay);
fprintf(fp, "SI_CreatTime-wHour is %hu\n", sysTime.wHour);
fprintf(fp, "SI_CreatTime-wMinute is %hu\n", sysTime.wMinute);
fprintf(fp, "SI_CreatTime-wSecond is %hu\n", sysTime.wSecond);
fprintf(fp, "SI_AlterTime is %lld\n", stdInfo->SI_AlterTime);
fprintf(fp, "SI_DOSAttr is %d\n", stdInfo->SI_DOSAttr);
fprintf(fp, "SI_MFTChgTime is %lld\n", stdInfo->SI_MFTChgTime);
fprintf(fp, "\n");
fprintf(fp, "\n#####END of STANDARD_INFORMATION#####\n\n");
break;
}
case 0x00000020: {//ATTRIBUTE_LIST 20属性
break;
}
case 0x00000030: {//FILE_NAME 可能不止一个
pFILE_NAME fileNameInfo = (pFILE_NAME)malloc(sizeof(FILE_NAME));
memcpy(fileNameInfo, tmpAttriDataIndex, sizeof(FILE_NAME));
fprintf(fp, "\n##### FILE_NAME #####\n");
//SYSTEMTIME sysTime;
//FileTimeToSystemTime(&(fileNameInfo->FN_AlterTime), &sysTime);
//fprintf(fp, "FN_AlterTime-wYear is %hu\n", sysTime.wYear);
//fprintf(fp, "FN_AlterTime-wMonth is %hu\n", sysTime.wMonth);
//fprintf(fp, "FN_AlterTime-wDay is %hu\n", sysTime.wDay);
//fprintf(fp, "FN_AlterTime-wHour is %hu\n", sysTime.wHour);
//fprintf(fp, "FN_AlterTime-wMinute is %hu\n", sysTime.wMinute);
//fprintf(fp, "FN_NameSz is %hu\n", 0xFFFF & (fileNameInfo->FN_NameSz));
//fprintf(fp, "FN_AllocSz is %llu\n", fileNameInfo->FN_AllocSz);
//fprintf(fp, "FN_ValidSz is %llu\n", fileNameInfo->FN_ValidSz);
//get file name
UINT32 fileNameLen = UINT32(0xFFFF & (fileNameInfo->FN_NameSz) + 1) << 1;
WCHAR *fileName = (WCHAR*)malloc(fileNameLen);
memset(fileName, 0, fileNameLen);
memcpy(fileName, tmpAttriDataIndex + sizeof(FILE_NAME), fileNameLen - 2);
fprintf(fp, "FILENAME is %ls\n", fileName);
fprintf(fp, "FN_NameSz is %d\n", fileNameInfo->FN_NameSz);
fprintf(fp, "FN_NamSpace is %d\n", fileNameInfo->FN_NamSpace);
fprintf(fp, "FILENAME is %ls\n", fileName);
fprintf(fp, "FN_ParentFR[父目录的MFT号] is %llu\n", (fileNameInfo->FN_ParentFR) & 0XFFFFFFFFFFFF);
//getchar();
fprintf(fp, "\n#####END of FILE_NAME#####\n\n");
break;
}
case 0x00000040: {//VOLUME_VERSION OBJECT_ID
break;
}
case 0x00000050: {//SECURITY_DESCRIPTOR
break;
}
case 0x00000060: {//VOLUME_NAME
break;
}
case 0x00000070: {//VOLUME_INFORMATION
break;
}
case 0x00000080: {//DATA
break;
}
case 0x00000090: {//INDEX_ROOT 索引根
//pINDEX_ROOT pIndexRoot = (INDEX_ROOT*)malloc(sizeof(INDEX_ROOT));
//memcpy(pIndexRoot, tmpAttriDataIndex, sizeof(INDEX_ROOT));
//fprintf(fp, "\n##### INDEX_ROOT #####\n");
//fprintf(fp, "IR_EntrySz[目录项的大小,一般是一个簇] is %u\n", pIndexRoot->IR_EntrySz);
//fprintf(fp, "IR_ClusPerRec[目录项占用的簇数,一般是一个] is %u\n", pIndexRoot->IR_ClusPerRec);
//fprintf(fp, "IH_TalSzOfEntries[索引根和紧随其后的索引项的大小] is %u\n", pIndexRoot->IH.IH_TalSzOfEntries);
//fprintf(fp, "IH_EntryOff[第一个索引项的偏移] is %u\n", pIndexRoot->IH.IH_EntryOff);
////0X90属性的实际大小
//UINT32 attri90Size = sizeof(INDEX_ROOT) - sizeof(INDEX_HEADER) + pIndexRoot->IH.IH_TalSzOfEntries;
//fprintf(fp, "\n##### END of INDEX_ROOT #####\n");
break;
}
case 0x000000A0: {//INDEX_ALLOCATION
//fprintf(fp, "\n##### INDEX_ALLOCATION #####\n");
////A0属性体
//pINDEX_ALLOCATION pIndexAlloc = (pINDEX_ALLOCATION)malloc(sizeof(INDEX_ALLOCATION));
//memcpy(pIndexAlloc, tmpAttriDataIndex, sizeof(INDEX_ALLOCATION));
//fprintf(fp, "\n##### END of INDEX_ALLOCATION #####\n");
break;
}
case 0x000000B0: {//BITMAP
break;
}
case 0x000000C0: {//SYMBOL_LINK REPARSE_POINT
break;
}
case 0x000000D0: {//EA_INFORMATION
break;
}
case 0x000000E0: {//EA
break;
}
case 0x000000F0: {//PROPERTY_SET
break;
}
case 0x00000100: {//LOGGED_UNTILITY_STREAM
break;
}
default: {
finishFlag = TRUE;
break;
}
}
pIndexOfMFTEntry += pComAttriHeader->ATTR_Size;
if (finishFlag) { break; }
}
}
//解析ntfs DBR扇区
void resolveNTFSDBRSector(DWORD phyDriverNumber, UINT64 startSecOffset, pNTFSDBR DBRBuf) {//物理磁盘设备号,DBR物理偏移(Byte),DBR扇区数据
fprintf(fp, "bytePerSector is %hu\n", DBRBuf->bytePerSector);
fprintf(fp, "secPerCluster is %hu\n", DBRBuf->secPerCluster);
fprintf(fp, "totalSectors is %llu\n", DBRBuf->totalSectors);
fprintf(fp, "MFT offset(logical cluster number) is %llu\n", DBRBuf->MFT);
printf("MFT offset(logical cluster number) is %llu\n", DBRBuf->MFT);
//ntfs mft offset(byte)
UINT64 MFToffset = UINT64((DBRBuf->MFT)*(DBRBuf->secPerCluster)*(DBRBuf->bytePerSector)) + startSecOffset;
fprintf(fp, "MFTMirror offset(logical cluster number) is %llu\n", DBRBuf->MFTMirror);
fprintf(fp, "\n");
//直接读取MFT第五项 根目录项
UINT64 MFTEntryOffset = UINT64(5 * 1024 + MFToffset);
fprintf(fp, "MFTEntry[5] Offset is %llu\n", MFTEntryOffset);
PVOID tmpMFTEntryBuf = malloc((UINT)MFTEntrySize);
memset(tmpMFTEntryBuf, 0, (UINT)MFTEntrySize);
ReadDisk(phyDriverNumber, MFTEntryOffset, MFTEntrySize, tmpMFTEntryBuf);
//路径
WCHAR filePath[2048] = { 0 };
//这里开始深搜目录树
parseMFTEntry(tmpMFTEntryBuf, MFTEntrySize, phyDriverNumber, startSecOffset,
DBRBuf->secPerCluster, UINT64((DBRBuf->MFT)*(DBRBuf->secPerCluster)*(DBRBuf->bytePerSector)), filePath);
return;
//这里是顺序读取MFT 能读到被删的文件信息
//读取MFT表项
UINT64 MFTEntryCnt = 0;
UINT32 mftEntryFlag;
printf("Analyzing MFT......\n");
printf("MFTEntryCnt");
while (TRUE) {
//while (MFTEntryCnt<128) {
fprintf(fp, "\n\n########### MFT ENTRY ##########\n");
fprintf(fp, "MFTEntryCnt is %llu\n", MFTEntryCnt);
printf("%llu\t", MFTEntryCnt);
//PVOID tmpMFTEntryBuf = malloc((UINT)MFTEntrySize);
//get MFTEntry
UINT64 MFTEntryOffset = UINT64((MFTEntryCnt++) * 1024 + MFToffset);
fprintf(fp, "MFTEntryOffset is %llu\n", MFTEntryOffset);
PVOID tmpMFTEntryBuf = malloc((UINT)MFTEntrySize);
memset(tmpMFTEntryBuf, 0, (UINT)MFTEntrySize);
ReadDisk(phyDriverNumber, MFTEntryOffset, MFTEntrySize, tmpMFTEntryBuf);
//解析MFT表项
mftEntryFlag = 0X00;
memcpy(&mftEntryFlag, tmpMFTEntryBuf, (UINT)4);
if (mftEntryFlag != MFTEntryFlag) { fprintf(fp, "\nMiss MFTEntryFlag, break\n"); break; }
//printBuffer(tmpMFTEntryBuf, MFTEntrySize);
resolveMFTEntry(tmpMFTEntryBuf, phyDriverNumber, startSecOffset, DBRBuf->secPerCluster, UINT64((DBRBuf->MFT)*(DBRBuf->secPerCluster)*(DBRBuf->bytePerSector)));
fprintf(fp, "\n########### END MFT ENTRY ##########\n");
}
printf("\n");
}
void run() {
fp = fopen("output.txt", "w");
vectordriverInfos = getPhyDriverNumber();
pMBRSector MBRs[64];
short int MBRcnt = 0;
for (int i = 0; i < driverInfos.size(); i++) {
DWORD phyVolCnt = 0;
//phyVolCnt = 0;
fprintf(fp, "### NOW IN PHYDRIVER - %d ###\n", driverInfos[i].number);
printf("### NOW IN PHYDRIVER - %d ###\n", driverInfos[i].number);
//MBRs[MBRcnt] = (pMBRSector)malloc(sizeof(MBRSector));
//read MBR sector
PVOID tmpMBRBuf = malloc(UINT32(MBRSectorSize));
ReadDisk(driverInfos[i].number, 0, MBRSectorSize, tmpMBRBuf);
MBRs[MBRcnt] = (pMBRSector)tmpMBRBuf;
//for test
fprintf(fp, "MBR in physicalDriveNumber%lu:\n", driverInfos[i].number);
printf("MBR in physicalDriveNumber%lu:\n", driverInfos[i].number);
printBuffer(tmpMBRBuf, MBRSectorSize);
printBuffer2(tmpMBRBuf, MBRSectorSize);
fprintf(fp, "\n");
int PTEntryCnt = 0;
while (PTEntryCnt < 4) {
fprintf(fp, "PartitionTableEntry%d:\n", PTEntryCnt);
fprintf(fp, "bootSignature(引导标志) is %+02X\n", MBRs[MBRcnt]->ptEntrys[PTEntryCnt].bootSignature);
fprintf(fp, "systemSignature(分区类型标志) is %+02X\n", MBRs[MBRcnt]->ptEntrys[PTEntryCnt].systemSignature);
fprintf(fp, "startSectorNo is %+08X\n", MBRs[MBRcnt]->ptEntrys[PTEntryCnt].startSectorNo);
//printf("startSectorNo is %+08X\n", MBRs[MBRcnt - 1]->ptEntrys[PTEntryCnt].startSectorNo);
fprintf(fp, "totalSectorsNum is %+08X\n", MBRs[MBRcnt]->ptEntrys[PTEntryCnt].totalSectorsNum);
fprintf(fp, "\n");
//PTEntryCnt不足四个
if (MBRs[MBRcnt]->ptEntrys[PTEntryCnt].startSectorNo == 0x00) {
break;
}
//先跳过活动分区
if (MBRs[MBRcnt]->ptEntrys[PTEntryCnt].bootSignature == 0X80) {
printf("\n\n\nVolume letter is %c\n\n\n", driverInfos[i].vols[phyVolCnt]);
printf("\n\n活动分区, leap it\n");
fprintf(fp, "\n\n活动分区, leap it\n");
PTEntryCnt++;
phyVolCnt++;
getchar();
continue;
}
//read DBR or EBR sector
PVOID readBuf = malloc(UINT32(SectorSize));
UINT64 startSecOffset = UINT64(MBRs[MBRcnt]->ptEntrys[PTEntryCnt].startSectorNo);
startSecOffset *= UINT64(SectorSize);
fprintf(fp, "startSecOffset is %lld\n", startSecOffset);
ReadDisk(driverInfos[i].number, startSecOffset, SectorSize, readBuf);
//判断分区类型
switch (MBRs[MBRcnt]->ptEntrys[PTEntryCnt].systemSignature)
{
case BYTE(0x0C): {//FAT32
printf("\n\n\nVolume letter is %c\n\n\n", driverInfos[i].vols[phyVolCnt++]);
getchar();
fprintf(fp, "FAT32 DBR sector is\n");
//printf("FAT32 DBR sector, continue\n");
printBuffer(readBuf, SectorSize);
fprintf(fp, "\n");
pFAT32_DBR DBRBuf = (pFAT32_DBR)readBuf;
//sizeof(FAT32_DBR);
fprintf(fp, "Sectors_per_Cluster is %hu\n", DBRBuf->BPB.Sectors_per_Cluster);
fprintf(fp, "FATs is %hu\n", DBRBuf->BPB.FATs);
fprintf(fp, "FATs is %u\n", DBRBuf->BPB.Large_Sector);
fprintf(fp, "System_ID is %llu\n", DBRBuf->Extend_BPB.System_ID);
fprintf(fp, "\n");
printf("resolve fat32 volume\n");
//每扇区字节数
UINT16 bytesPerSector = DBRBuf->BPB.Bytes_per_Sector;
//每簇扇区数
UINT8 sectorsPerCluster = DBRBuf->BPB.Sectors_per_Cluster;
//保留扇区数
UINT16 reservedSectorNum = DBRBuf->BPB.Reserved_Sector;
//fat表占用的扇区数
UINT32 fat32Sectors = DBRBuf->BPB.Fat32_Sector.Sectors_per_FAT_FAT32;
//根目录簇号
UINT32 rootCluster = DBRBuf->BPB.Fat32_Sector.Root_Cluster_Number;
//定位根目录 保留扇区数+每个FAT表占用扇区数*2+(根目录簇号-2)*每簇扇区数
UINT64 rootSectorOffset = reservedSectorNum + fat32Sectors * 2 + (rootCluster - 2)*sectorsPerCluster;
printf("rootSectorOffset is %llu\n", rootSectorOffset);
getchar();
UINT64 rootSectorByteOffset = rootSectorOffset * bytesPerSector + startSecOffset;
PVOID tmpBuff = malloc(SectorSize);
ReadDisk(driverInfos[i].number, rootSectorByteOffset, SectorSize, tmpBuff);
printBuffer2(tmpBuff, SectorSize);
getchar();
break;
}
case BYTE(0x07): {//ntfs
printf("\n\n\nVolume letter is %c\n\n\n", driverInfos[i].vols[phyVolCnt++]);
getchar();
fprintf(fp, "ntfs DBR sector is\n");
printf("find ntfs DBR sector\n");
printBuffer(readBuf, SectorSize);
fprintf(fp, "\n");
pNTFSDBR DBRBuf = (pNTFSDBR)readBuf;
//解析ntfs dbr
resolveNTFSDBRSector(driverInfos[i].number, startSecOffset, DBRBuf);
break;
}
case BYTE(0x0F): {//Extended
fprintf(fp, "EBR sector is\n");
printBuffer(readBuf, SectorSize);
fprintf(fp, "\n");
pMBRSector EBRBuf = (pMBRSector)readBuf;
bool isEbrFinish = FALSE;
while (TRUE) {
if (!(EBRBuf->ptEntrys[1].totalSectorsNum)) { isEbrFinish = TRUE; }
printf("find EBR sector, continue\n");
fprintf(fp, "this volume : logical startSectorNo is %+08X\n", EBRBuf->ptEntrys[0].startSectorNo);
fprintf(fp, "this volume : totalSectorsNum is %+08X\n", EBRBuf->ptEntrys[0].totalSectorsNum);
fprintf(fp, "next volume : logical startSectorNo is %+08X\n", EBRBuf->ptEntrys[1].startSectorNo);
//本分区DBR的物理偏移
UINT64 thisVolPhyStartSecOffset = UINT64(MBRs[MBRcnt]->ptEntrys[PTEntryCnt].startSectorNo)
+ UINT64(EBRBuf->ptEntrys[0].startSectorNo);
thisVolPhyStartSecOffset *= UINT64(SectorSize);
//下一个EBR的物理偏移
UINT64 nextVolPhyStartSecOffset = UINT64(MBRs[MBRcnt]->ptEntrys[PTEntryCnt].startSectorNo)
+ UINT64(EBRBuf->ptEntrys[1].startSectorNo);
nextVolPhyStartSecOffset *= UINT64(SectorSize);
printf("nextVolPhyStartSecOffset is %llu\n", nextVolPhyStartSecOffset);
getchar();
//读取本分区的DBR
PVOID tempDBRBuf = malloc(int(DBRSectorSize));
ReadDisk(driverInfos[i].number, thisVolPhyStartSecOffset, DBRSectorSize, tempDBRBuf);
fprintf(fp, "\nDBR after EBR:\n");
printBuffer(tempDBRBuf, DBRSectorSize);
fprintf(fp, "\n");
switch (EBRBuf->ptEntrys[0].systemSignature)
{
case BYTE(0x07): {//ntfs
printf("\n\n\nVolume letter is %c\n\n\n", driverInfos[i].vols[phyVolCnt++]);
getchar();
printf("find ntfs DBR sector\n");
pNTFSDBR DBRBuf = (pNTFSDBR)tempDBRBuf;
//解析ntfs dbr
resolveNTFSDBRSector(driverInfos[i].number, thisVolPhyStartSecOffset, DBRBuf);
break;
}
case BYTE(0x0C): {//fat32
printf("\n\n\nVolume letter is %c\n\n\n", driverInfos[i].vols[phyVolCnt++]);
getchar();
printf("FAT32 DBR sector, continue\n");
break;
}
default:
break;
}
if (isEbrFinish) { break; }
//读取下一个EBR
PVOID tempEBRBuf = malloc(int(EBRSectorSize));
ReadDisk(driverInfos[i].number, nextVolPhyStartSecOffset, EBRSectorSize, tempEBRBuf);
EBRBuf = pMBRSector(tempEBRBuf);
fprintf(fp, "\nnext EBR sector is\n");
printBuffer(tempEBRBuf, EBRSectorSize);
fprintf(fp, "\n");
}
break;
}
default:
break;
}
PTEntryCnt++;
fprintf(fp, "\n");
}
MBRcnt++;
fprintf(fp, "\n");
}
fclose(fp);
}
//判断卷是否属于USB
bool isUsbDev(TCHAR volumePath[]) {
HANDLE deviceHandle = CreateFile(volumePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
STORAGE_PROPERTY_QUERY query;
memset(&query, 0, sizeof(query));
DWORD bytes;
STORAGE_DEVICE_DESCRIPTOR devd;
//STORAGE_BUS_TYPE用于记录结构,类型要初始化
STORAGE_BUS_TYPE busType = BusTypeUnknown;
if (DeviceIoControl(deviceHandle, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query), &devd, sizeof(devd), &bytes, NULL)) {
busType = devd.BusType;
}
CloseHandle(deviceHandle);
return busType == BusTypeUsb;
}
//发现USB设备
bool findUsbDev() {
bool ret = false;
DWORD dwSize = GetLogicalDriveStrings(0, NULL);
char* drivers = (char*)malloc(dwSize * 2);
DWORD dwRet = GetLogicalDriveStrings(dwSize, (LPWSTR)drivers);
wchar_t* lp = (wchar_t*)drivers;//所有逻辑驱动器的根驱动器路径 用0隔开
DWORD tmpNum = 0;
while (*lp) {
CHAR path[MAX_PATH];
sprintf(path, "\\\\.\\%c:", lp[0]);
if (isUsbDev(charToWCHAR(path))) {
//printf("find usb\n");
ret = true;
break;
}
lp += (wcslen(lp) + 1);//下一个根驱动器路径
}
return ret;
}
void test() {
}
int _tmain(int argc, TCHAR *argv[], TCHAR *env[]) {
setlocale(LC_ALL, "chs");
//test();
run();
system("pause");
return 0;
}
接下来是结构定义:
//ntfs.h
#pragma once
#pragma pack(1)
#pragma warning(disable : 4996)
#include "stdafx.h"
#define MBRlen 446
#define minReadSize 512
#define MFTEntrySize 1024
#define SectorSize 512
#define DBRSectorSize 512
#define EBRSectorSize 512
#define MBRSectorSize 512
#define MFTEntryFlag 0x454C4946 //FILE
//DPT表项
typedef struct _PartTableEntry {
BYTE bootSignature;//引导标志
BYTE startHead;//CHS寻址方式,起始磁头
BYTE startSector;//起始扇区,本字节低六位
BYTE startCylinder;//起始磁道(柱面),startSector高二位和本字节
BYTE systemSignature;//分区类型标志
BYTE endHead;//终止磁头
BYTE endSector;//终止扇区
BYTE endCylinder;//终止磁道
unsigned int startSectorNo;//LBA寻址,起始扇区号
unsigned int totalSectorsNum;//该分区扇区总数
}PartTableEntry, *pPartTableEntry;
//MBR扇区
typedef struct _MBRSector {
BYTE MBR[MBRlen];
PartTableEntry ptEntrys[4];
BYTE endSignature[2];
}MBRSector, *pMBRSector;
//NTFS DBR扇区
typedef struct _NTFSDBR {
BYTE JMP[3];//跳转指令
BYTE FsID[8];//文件系统ID
unsigned short int bytePerSector;//每扇区字节数
BYTE secPerCluster;//每簇扇区数
BYTE reservedBytes[2];//2个保留字节
BYTE zeroBytes[3];//三个0字节
BYTE unusedBytes1[2];//2个未用字节
BYTE mediaType;//媒体类型
BYTE unusedBytes2[2];//2个未用字节
unsigned short int secPerTrack;//每磁道扇区数
unsigned short int Heads;//磁头数
unsigned int hideSectors;//隐藏扇区数
BYTE unusedBytes3[4];//4个未用字节
BYTE usedBytes[4];//4个固定字节
unsigned __int64 totalSectors;//总扇区数
unsigned __int64 MFT;//MFT起始簇号
unsigned __int64 MFTMirror;//MFTMirror文件起始簇号
char fileRecord;//文件记录
BYTE unusedBytes4[3];//3个未用字节
char indexSize;//索引缓冲区大小
BYTE unusedBytes5[3];//未用字节
BYTE volumeSerialID64[8];//卷序列号
unsigned int checkSum;//校验和
BYTE bootCode[426];//引导代码
BYTE endSignature[2];//结束标志
}NTFSDBR, *pNTFSDBR;
//MFT表项的结构
// 文件记录头
typedef struct _FILE_RECORD_HEADER
{
/*+0x00*/BYTE Type[4];// 固定值'FILE'
/*+0x04*/UINT16 USNOffset;// 更新序列号偏移, 与操作系统有关
/*+0x06*/UINT16 USNCount; // 固定列表大小Size in words of Update Sequence Number & Array (S)
/*+0x08*/ UINT64 Lsn; // 日志文件序列号(LSN)
/*+0x10*/ UINT16 SequenceNumber; // 序列号(用于记录文件被反复使用的次数)
/*+0x12*/ UINT16 LinkCount;// 硬连接数
/*+0x14*/ UINT16 AttributeOffset; // 第一个属性偏移
/*+0x16*/ UINT16 Flags;// flags, 00表示删除文件,01表示正常文件,02表示删除目录,03表示正常目录
/*+0x18*/ UINT32 BytesInUse; // 文件记录实时大小(字节) 当前MFT表项长度,到FFFFFF的长度+4
/*+0x1C*/ UINT32 BytesAllocated; // 文件记录分配大小(字节)
/*+0x20*/ UINT64 BaseFileRecord; // = 0 基础文件记录 File reference to the base FILE record
/*+0x28*/ UINT16 NextAttributeNumber; // 下一个自由ID号
/*+0x2A*/ UINT16 Pading; // 边界
/*+0x2C*/ UINT32 MFTRecordNumber; // windows xp中使用,本MFT记录号
/*+0x30*/ UINT16 USN; // 更新序列号
/*+0x32*/ BYTE UpdateArray[0]; // 更新数组
} FILE_RECORD_HEADER, *pFILE_RECORD_HEADER;
//常驻属性和非常驻属性的公用部分
typedef struct _CommonAttributeHeader {
UINT32 ATTR_Type; //属性类型
UINT32 ATTR_Size; //属性头和属性体的总长度
BYTE ATTR_ResFlag; //是否是常驻属性(0常驻 1非常驻)
BYTE ATTR_NamSz; //属性名的长度
UINT16 ATTR_NamOff; //属性名的偏移 相对于属性头
UINT16 ATTR_Flags; //标志(0x0001压缩 0x4000加密 0x8000稀疏)
UINT16 ATTR_Id; //属性唯一ID
}CommonAttributeHeader,*pCommonAttributeHeader;
//常驻属性 属性头
typedef struct _ResidentAttributeHeader {
CommonAttributeHeader ATTR_Common;
UINT32 ATTR_DatSz; //属性数据的长度
UINT16 ATTR_DatOff; //属性数据相对于属性头的偏移
BYTE ATTR_Indx; //索引
BYTE ATTR_Resvd; //保留
BYTE ATTR_AttrNam[0];//属性名,Unicode,结尾无0
}ResidentAttributeHeader, *pResidentAttributeHeader;
//非常驻属性 属性头
typedef struct _NonResidentAttributeHeader {
CommonAttributeHeader ATTR_Common;
UINT64 ATTR_StartVCN; //本属性中数据流起始虚拟簇号
UINT64 ATTR_EndVCN; //本属性中数据流终止虚拟簇号
UINT16 ATTR_DatOff; //簇流列表相对于属性头的偏移
UINT16 ATTR_CmpSz; //压缩单位 2的N次方
UINT32 ATTR_Resvd;
UINT64 ATTR_AllocSz; //属性分配的大小
UINT64 ATTR_ValidSz; //属性的实际大小
UINT64 ATTR_InitedSz; //属性的初始大小
BYTE ATTR_AttrNam[0];
}NonResidentAttributeHeader, *pNonResidentAttributeHeader;
/*下面是索引结构的定义*/
//标准索引头的结构
typedef struct _STD_INDEX_HEADER {
BYTE SIH_Flag[4]; //固定值 "INDX"
UINT16 SIH_USNOffset;//更新序列号偏移
UINT16 SIH_USNSize;//更新序列号和更新数组大小
UINT64 SIH_Lsn; // 日志文件序列号(LSN)
UINT64 SIH_IndexCacheVCN;//本索引缓冲区在索引分配中的VCN
UINT32 SIH_IndexEntryOffset;//索引项的偏移 相对于当前位置
UINT32 SIH_IndexEntrySize;//索引项的大小
UINT32 SIH_IndexEntryAllocSize;//索引项分配的大小
UINT8 SIH_HasLeafNode;//置一 表示有子节点
BYTE SIH_Fill[3];//填充
UINT16 SIH_USN;//更新序列号
BYTE SIH_USNArray[0];//更新序列数组
}STD_INDEX_HEADER,*pSTD_INDEX_HEADER;
//标准索引项的结构
typedef struct _STD_INDEX_ENTRY {
UINT64 SIE_MFTReferNumber;//文件的MFT参考号
UINT16 SIE_IndexEntrySize;//索引项的大小
UINT16 SIE_FileNameAttriBodySize;//文件名属性体的大小
UINT16 SIE_IndexFlag;//索引标志
BYTE SIE_Fill[2];//填充
UINT64 SIE_FatherDirMFTReferNumber;//父目录MFT文件参考号
FILETIME SIE_CreatTime;//文件创建时间
FILETIME SIE_AlterTime;//文件最后修改时间
FILETIME SIE_MFTChgTime;//文件记录最后修改时间
FILETIME SIE_ReadTime;//文件最后访问时间
UINT64 SIE_FileAllocSize;//文件分配大小
UINT64 SIE_FileRealSize;//文件实际大小
UINT64 SIE_FileFlag;//文件标志
UINT8 SIE_FileNameSize;//文件名长度
UINT8 SIE_FileNamespace;//文件命名空间
BYTE SIE_FileNameAndFill[0];//文件名和填充
}STD_INDEX_ENTRY,*pSTD_INDEX_ENTRY;
/****下面定义的均是属性体的结构 不包括属性头****/
//STANDARD_INFORMATION 0X10属性体
/*
SI_DOSAttr取值:
0x0001只读
0x0002隐藏
0x0004系统
0x0020归档
0x0040设备
0x0080常规
0x0100临时文件
0x0200稀疏文件
0x0400重解析点
0x0800压缩
0x1000离线
0x2000无内容索引
0x4000加密
*/
typedef struct _STANDARD_INFORMATION {
FILETIME SI_CreatTime;//创建时间
FILETIME SI_AlterTime;//最后修改时间
FILETIME SI_MFTChgTime;//文件的MFT修改的时间
FILETIME SI_ReadTime;//最后访问时间
UINT32 SI_DOSAttr;//DOS文件属性
UINT32 SI_MaxVer;//文件可用的最大版本号 0表示禁用
UINT32 SI_Ver;//文件版本号 若最大版本号为0 则值为0
UINT32 SI_ClassId;//??
//UINT64 SI_OwnerId;//文件拥有者ID
//UINT64 SI_SecurityId;//安全ID
//UINT64 SI_QuotaCharged;//文件最大可使用的空间配额 0表示无限制
//UINT64 SI_USN;//文件最后一次更新的记录号
#if 0
uint32 QuotaId;
uint32 SecurityId;
uint64 QuotaCharge;
USN Usn;
#endif
}STANDARD_INFORMATION,*pSTANDARD_INFORMATION;
//ATTRIBUTE_LIST 0X20属性体
typedef struct _ATTRIBUTE_LIST {
UINT32 AL_RD_Type;
UINT16 AL_RD_Len;
BYTE AL_RD_NamLen;
BYTE AL_RD_NamOff;
UINT64 AL_RD_StartVCN;//本属性中数据流开始的簇号
UINT64 AL_RD_BaseFRS;/*本属性记录所属的MFT记录的记录号
注意:该值的低6字节是MFT记录号,高2字节是该MFT记录的序列号*/
UINT16 AL_RD_AttrId;
//BYTE AL_RD_Name[0];
UINT16 AlignmentOrReserved[3];
}ATTRIBUTE_LIST,*pATTRIBUTE_LIST;
//FILE_NAME 0X30属性体
typedef struct _FILE_NAME {
UINT64 FN_ParentFR; /*父目录的MFT记录的记录索引。
注意:该值的低6字节是MFT记录号,高2字节是该MFT记录的序列号*/
FILETIME FN_CreatTime;
FILETIME FN_AlterTime;
FILETIME FN_MFTChg;
FILETIME FN_ReadTime;
UINT64 FN_AllocSz;
UINT64 FN_ValidSz;//文件的真实尺寸
UINT32 FN_DOSAttr;//DOS文件属性
UINT32 FN_EA_Reparse;//扩展属性与链接
BYTE FN_NameSz;//文件名的字符数
BYTE FN_NamSpace;/*命名空间,该值可为以下值中的任意一个
0:POSIX 可以使用除NULL和分隔符“/”之外的所有UNICODE字符,最大可以使用255个字符。注意:“:”是合法字符,但Windows不允许使用。
1:Win32 Win32是POSIX的一个子集,不区分大小写,可以使用除““”、“*”、“?”、“:”、“/”、“<”、“>”、“/”、“|”之外的任意UNICODE字符,但名字不能以“.”或空格结尾。
2:DOS DOS命名空间是Win32的子集,只支持ASCII码大于空格的8BIT大写字符并且不支持以下字符““”、“*”、“?”、“:”、“/”、“<”、“>”、“/”、“|”、“+”、“,”、“;”、“=”;同时名字必须按以下格式命名:1~8个字符,然后是“.”,然后再是1~3个字符。
3:Win32&DOS 这个命名空间意味着Win32和DOS文件名都存放在同一个文件名属性中。*/
BYTE FN_FileName[0];
}FILE_NAME,*pFILE_NAME;
//VOLUME_VERSION
typedef struct _VOLUME_VERSION {
//??
}VOLUME_VERSION,*pVOLUME_VERSION;
//OBJECT_ID 0X40属性体
typedef struct _OBJECT_ID {
BYTE OID_ObjID[16];//文件的GUID
BYTE OID_BirthVolID[16];//文件建立时所在卷的ID
BYTE OID_BirthID[16];//文件的原始ID
BYTE OID_DomainID[16];//对象所创建时所在域的ID
}OBJECT_ID, *pOBJECT_ID;
//SECRUITY_DESCRIPTOR 0X50属性体
typedef struct _SECRUITY_DESCRIPTOR {
//??
}SECRUITY_DESCRIPTOR,*pSECRUITY_DESCRIPTOR;
//VOLUME_NAME 0X60属性体
typedef struct _VOLUME_NAME {
BYTE VN_Name[0];
}VOLUME_NAME,*pVOLUME_NAME;
//VOLUME_INFORMATION 0X70属性体
typedef struct _VOLUME_INFORMATION{
UINT64 VI_Resvd;
BYTE VI_MajVer;//卷主版本号
BYTE VI_MinVer;//卷子版本号
UINT16 VI_Flags;/*标志位,可以是以下各值组合
0x0001脏位,当该值被设置时Windows将会在下次启动时运行chkdsk/F命令。
0x0002日志文件改变尺寸
0x0004卷挂接时升级
0x0008由Windows NT 4挂接
0x0010启动时删除USN
0x0020修复过的ID
0x8000被chkdsk修改过*/
}VOLUME_INFORMATION,*pVOLUME_INFORMATION;
//DATA 0X80属性体
typedef struct _DATA {
//??
///*+0x10*/ UINT64 StartVcn; // LowVcn 起始VCN 起始簇号
///*+0x18*/ UINT64 LastVcn; // HighVcn 结束VCN 结束簇号
///*+0x20*/ UINT16 RunArrayOffset;// 数据运行的偏移
///*+0x22*/ UINT16 CompressionUnit; // 压缩引擎
///*+0x24*/ UINT32 Padding0; // 填充
///*+0x28*/ UINT32 IndexedFlag;// 为属性值分配大小(按分配的簇的字节数计算)
///*+0x30*/ UINT64 AllocatedSize; // 属性值实际大小
///*+0x38*/ UINT64 DataSize; // 属性值压缩大小
///*+0x40*/ UINT64 InitializedSize; // 实际数据大小
///*+0x48*/ UINT64 CompressedSize;// 压缩后大小
BYTE D_data[0];
}DATA,*pDATA;
typedef struct _INDEX_ENTRY {
UINT64 IE_MftReferNumber;/*该文件的MFT参考号。注意:该值的低6字节是MFT记录号,高2字节是该MFT记录的序列号*/
UINT16 IE_Size;//索引项的大小 相对于索引项开始的偏移量
UINT16 IE_FileNAmeAttriBodySize;//文件名属性体的大小
UINT16 IE_Flags;/*标志。该值可能是以下值之一:
0x00 普通文件项
0x01 有子项
0x02 当前项是最后一个目录项
在读取索引项数据时应该首先检查该成员的值以确定当前项的类型*/
UINT16 IE_Fill;//填充 无意义
UINT64 IE_FatherDirMftReferNumber;//父目录的MFT文件参考号
FILETIME IE_CreatTime;//文件创建时间
FILETIME IE_AlterTime;//文件最后修改时间
FILETIME IE_MFTChgTime;//文件记录最后修改时间
FILETIME IE_ReadTime;//文件最后访问时间
UINT64 IE_FileAllocSize;//文件分配大小
UINT64 IE_FileRealSize;//文件实际大小
UINT64 IE_FileFlag;//文件标志
UINT8 IE_FileNameSize;//文件名长度
UINT8 IE_FileNamespace;//文件命名空间
BYTE IE_FileNameAndFill[0];//文件名和填充
//BYTE IE_Stream[0];//目录项数据,结构与文件名属性的数据相同
//UINT64 IE_SubNodeFR;//子项的记录索引。该值的低6字节是MFT记录号,高2字节是该MFT记录的序列号
}INDEX_ENTRY,*pINDEX_ENTRY;
typedef struct _INDEX_HEADER {
UINT32 IH_EntryOff;//第一个目录项的偏移
UINT32 IH_TalSzOfEntries;//目录项的总尺寸(包括索引头和下面的索引项)
UINT32 IH_AllocSize;//目录项分配的尺寸
BYTE IH_Flags;/*标志位,此值可能是以下和值之一:
0x00 小目录(数据存放在根节点的数据区中)
0x01 大目录(需要目录项存储区和索引项位图)*/
BYTE IH_Resvd[3];
}INDEX_HEADER,*pINDEX_HEADER;
//INDEX_ROOT 0X90属性体
typedef struct _INDEX_ROOT {
//索引根
UINT32 IR_AttrType;//属性的类型
UINT32 IR_ColRule;//整理规则
UINT32 IR_EntrySz;//目录项分配尺寸
BYTE IR_ClusPerRec;//每个目录项占用的簇数
BYTE IR_Resvd[3];
//索引头
INDEX_HEADER IH;
//索引项 可能不存在
BYTE IR_IndexEntry[0];
}INDEX_ROOT,*pINDEX_ROOT;
//INDEX_ALLOCATION 0XA0属性体
typedef struct _INDEX_ALLOCATION {
//UINT64 IA_DataRuns;
BYTE IA_DataRuns[0];
}INDEX_ALLOCATION,*pINDEX_ALLOCATION;
//BITMAP
typedef struct _MFT_ATTR_BITMAP {
//??
}MFT_ATTR_BITMAP,*pMFT_ATTR_BITMAP;
//SYMBOL_LINK
typedef struct _SYMBOL_LINK {
//??
}SYMBOL_LINK,*pSYMBOL_LINK;
//REPARSE_POINT
typedef struct _REPARSE_POINT{
UINT32 RP_Type;/*重解析数据类型,该值可以是以下值之一
0x20000000别名
0x40000000最高等待时间
0x80000000微软使用
0x68000005NSS
0x68000006NSS恢复
0x68000007SIS
0x68000008DFS
0x88000003卷挂接点
0xA8000004 HSM
0xE8000000 硬连接*/
UINT16 RP_DatSz;//重解析数据尺寸
UINT16 RP_Resvd;//
BYTE RP_Data[0];//重解析数据
}REPARSE_POINT,*pREPARSE_POINT;
//EA_INFORMATION
typedef struct _EA_INFORMATION {
UINT16 EI_PackedSz;//压缩扩展属性尺寸
UINT16 EI_NumOfEA;//拥有NEED_EA记录的扩展属性个数
UINT32 EI_UnpackedSz;//未压缩扩展属性尺寸
}EA_INFORMATION,*pEA_INFORMATION;
//EA
typedef struct _EA {
UINT32 EA_Next;//下一个扩展属性的偏移(本记录的尺寸)
BYTE EA_Flags;//标志位,值取0x80表示需要EA
BYTE EA_NamLen;//名字数据的长度(M)
UINT16 EA_ValLen;//值数据的长度
BYTE EA_NameVal[0];//名字数据和值数据
}EA,*pEA;
//PROPERTY_SET
typedef struct _PROPERTY_SET {
//??
}PROPERTY_SET,*pPROPERTY_SET;
//LOGGED_UNTILITY_STREAM
typedef struct _LOGGED_UNTILITY_STREAM {
//??
}LOGGED_UNTILITY_STREAM,*pLOGGED_UNTILITY_STREAM;
//fat32.h
#pragma once
#pragma pack(1)
#pragma warning(disable : 4996)
#include "stdafx.h"
/*
【FAT32分区的结构】
【[保留扇区,其中第一个扇区为DBR]||||[FAT1]||||[FAT2]||||[数据区]】
*/
//fat32相关
typedef struct _FAT32_Sector
{
UINT32 Sectors_per_FAT_FAT32;//FAT32中FAT表占用总扇区数
UINT16 Extend_Flag;//0-3位表示活动FAT数,7位:0表示在运行时FAT映射到所有FAT
//1表示只有一个FAT是活动的,其他位保留
UINT16 FS_Version;//文件系统版本,高字节表示主要修订号,低直接表示次要修订号
UINT32 Root_Cluster_Number;//根目录簇号,一般取值为2
UINT16 FS_Info_Sector;//文件系统扇区号,一般取1
UINT16 Backup_Sector;//备份引导扇区,一般取值为6
BYTE Reserved_Sector[12];//保留扇区
}FAT32_Sector, *pFAT32_Sector;
typedef struct _Basic_BPB
{
UINT16Bytes_per_Sector;//每个扇区字节数,可取minReadSize,1024,2048,4096,通常取minReadSize
BYTESectors_per_Cluster;//每簇扇区数,可取1,2,4,8,16,32,64,128,FAT32最多跟踪
//268,435,445个簇
UINT16Reserved_Sector;//保留扇区,表示第一个FAT前的扇区数 【也就是FAT1的偏移】
BYTEFATs;//FAT表个数,通常取2
UINT16RootEntry;//根目录项数,对FAT32,取值必为0
UINT16SmallSector;//小扇区数,对FAT32,取值必为0
BYTEMedia;//存储介质描述,F8表示硬盘,F0表示3.5软盘
UINT16Sector_per_FAT_FAT16;//针对FAT12/16的每个FAT扇区数,对FAT32,取值为0
UINT16Sector_per_Track;//每道扇区数,描述磁盘物理结构
UINT16Heads;//磁头数
UINT32Hidden_Sector;//该块硬盘前用于存放引导代码及分区表的扇区数 【隐藏扇区】
UINT32Large_Sector;//总扇区数,若SmallSector为0,此处表示分区上扇区总数
//可用扇区数 = 总扇区数-保留扇区-FAT表占用扇区
_FAT32_SectorFat32_Sector;//FAT32文件系统扇区信息
}Basic_BPB, *pBasic_BPB;
typedefstruct _FAT32_Extend_BPB
{
BYTE Physical_Drive;//物理驱动器号,0x80表示物理硬盘,0x00表示软盘驱动器
BYTE Reserved;//保留
BYTE Extend_Singure;//0x28或0x29以供Windows NT识别
UINT32 Vol_Serial;//卷序列号,由格式化时随机获得
BYTE Vol_Label[11];//卷标示
BYTE System_ID[8];//系统ID,根据格式化的格式为FAT32,FAT16等
}FAT32_Extend_BPB, *pFAT32_Extend_BPB;
//FAT32 DBR扇区
typedef struct _FAT32_DBR
{
BYTEJumpInstrction[3];//0x00,跳转指令,通常为EB 58 90,其中58指示了,跳转位置,在X86中,58+2就代表跳转到5A处
BYTEOEMID[8];//0x03,厂商标示和OS版本信息
_Basic_BPBBPB;//0x0B
_FAT32_Extend_BPBExtend_BPB;//0x40
BYTE Boot_Strap[420];//文件系统引导代码//0x5A,引导区代码
BYTE endSignature[2];//0x01FE,结束标示
}FAT32_DBR, *pFAT32_DBR;
//stdafx.h
// stdafx.h : 标准系统包含文件的包含文件,
// 或是经常使用但不常更改的
// 特定于项目的包含文件
//
#pragma once
#include "targetver.h"
#include
#include
#include
#include
#include
#include
#include
//#include
//#include
//#include
//#include
//#include
#include
//#include
//#include
//#include
//#include
//#include
//#include
//#include
//#include
//#include
//#include
//#include "md5.h"
//#include
// TODO: 在此处引用程序需要的其他头文件
————————————————
版权声明:本文为CSDN博主「我是NeroZhang」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/Hilavergil/article/details/82622470
NTFS文件系统结构--从零开始追踪一个文件的位置
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA/a>版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yiqiaoxihui/article/details/70171159
前言:最近由于项目需要,研究了一下NTFS文件系统,NTFS文件系统是windows使用的文件系统,包括NT,2000,xp系列。无奈万恶资本主义的windows将自家的东西全部藏在阴暗的角落,NTFS理所当然地也不开源,尽管没有源代码,还是有足够丰富的资料将NTFS文件系统曝光在自由的阳光下。下面通过从NTFS文件系统的根源出发,展示如何通过一层层的解析,最终读取到其中的某个文件。
环境:LINUX Ubuntu16.04,待解析的文件系统:NTFS,追踪其中的文件:C:\WINDOWS\DtcInstall.log(随机找的,仅仅以此为例)
说明:本人找了一块完整的NTFS文件系统镜像,它里面是一个完整的Windows XP系统,是从Windows XP虚拟机镜像切下来的(因为虚拟机镜像不止包括完整的文件系统,例如我的这块虚拟机镜像从第0x7E000字节以后才是完整的NTFS文件系统),这些都不重要,重要的是现在你只要有一块完整的NTFS文件系统,当然必须是Window XP系统的,因为我们还有追踪C:\WINDOWS\DtcInstall.log这个文件,所以其实在Windows XP中使用Disk Genius打开你C盘分区就是一块完整的NTFS文件系统,如果以16进制查看应该是这样的:
NTFS文件系统的布局:
BOOT存放一些文件系统基本信息,如一个扇区512bytes,一簇(cluster)有8个扇区,共4K,比较重要的一项是MFT的偏移簇号。
MFT是NTFS中最重要的部分,它是一个个以“FILE”开始的文件记录数据结构。
struct 文件记录{文件记录头;属性1;属性2;属性3;……}
而属性包括属性头和属性体两部分,如下属性的数据结构。
struct 属性{属性头;属性体;}
所以,下面的文件记录结构举例请参考文件记录结构、文件记录头结构、属性头结构、不同属性体结构来看!
这个数据结构就是文件的全部信息,文件的各种属性都放在这个数据结构中,在NTFS中文件内容也属于文件的属性,所以在NTFS中文件的一切都是属性,比如在这个数据结构中有30H属性记录文件名,10H属性记录文件的一些元信息如创建、访问时间等,而80H属性则是文件的内容,因为一个文件记录数据结构仅仅4K,所以如果文件很大,80H属性存放的不再是文件内容,而是存放文件内容的簇索引号run list.对于run list 解析以后介绍,这里知道它是文件内容的簇偏移索引即可。举例说明,如DctInstall.log文件的文件记录如图:
注意这个文件内容是160字节,80H属性能够包括全部内容,所以这个80H属性是常驻属性,如果是非常驻属性,则文件内容在run list表示的簇偏移中。注意常驻属性和非常驻属性的结构是不一样的。
NTFS系统的其他部分比如FreeSpace等相关性不大,不做解释,可参考其他资料。
第一步,定位根目录:
通过BOOT块中的信息获取MFT偏移量为第0x40000簇,如图,而根目录在MFT的第6个文件记录。
MFT偏移簇数0x4000
MFT0到11个固定的文件记录,第6个为根目录的文件记录
找到根目录的文件记录的偏移位置,主要关注他的A0H属性,因为这个属性记录根目录中所有文件的文件名以及文件记录的位置。如图(再次强调,务必参考文件记录的结构理解图片内容)
根目录的文件记录
第二步:找到根目录的索引节点和WINDOWS目录索引项
由根目录的文件记录的A0H属性的run list :0x31 01 52f909,根据run list的特殊计算方式(具体可参考文末链接)即第一个字节的3代表后三字节为簇偏移量,第一个字节的1代表共一簇。所以记录根文件中所有文件的文件名以及文件记录号的簇偏移位置为0x09f952(ntfs采用小端方式存储数据),共一簇。所以现在偏移到0x09f952簇查看具体内容。如图所示(请务必参考索引节点的数据结构阅读图片):
其中索引节点的数据结构:
struct 索引节点{索引头;索引项1;索引项2;索引项3;……}
由图片可以看出,根目录的索引节点有索引头和一个个索引项构成,其中这一个个索引项和根目录中的每个文件或目录一一对应,这些目录项的文件名就是根目录中文件或目录的名称。我们现在偏移到根目录下目录名为WINDOWS的索引项,如图:
WINDOWS目录的索引项
第三步:定位WINDOWS目录的文件记录
由WINDOWS索引项的MFT号,即起始的8个字节,可以计算出WINDOWS目录的文件记录号,即ox0000 0000 001c=28,所以WINDOWS目录的文件记录号为28,相对于MFT起始位置偏移28项,0x28*0x400=0x7000。和根目录一样,因为WINDOWS是个目录,所以仍然关注A0H属性,因为此属性记录WINDOWS目录中所有文件或目录的名称和MFT号(文件记录号),WINDOWS目录的文件记录如图:
WINDOWS目录的文件记录,重点关注A0H属性
第四步:获取WINDOWS目录的索引节点以及WINDOWS目录下DtcInstall.log的索引项
如上图run list 不止一项,先取第一项0x31 0a a1a606,即WINDOWS目录中一部分文件或目录的索引项信息(包括文件或目录名以及文件或目录的MFT号)在簇偏移为ox06a6a1处,共有0x01簇。
现在偏移到435873簇(0x06a6a1),内容如图,与根目录下的索引项内容类似,可以发现我们要找到DtcInstall.log文件名已经出现。
DtcInstall.log的索引项
第五步:获取DtcInstall.log文件的MFT号,偏移到指定位置
从DtcInstall.log索引项中我们可以发现该文件的MFT号为ox15f9,所以该文件的文件记录在MFT的偏移位置为ox15f9*0x400=0x57e400,找到该偏移位置,如图(这个图上面贴过):
在该文件记录存储着DtcInstall.log的全部信息,包括文件内容,由于文件内容小,所以80H属性可以容纳全部,所以是常驻属性,否则应该是非常驻属性,而且非常驻属性的最后是run list 指向文件内容的真正簇偏移。
结束
至此,对NTFS文件系统中某个文件的追踪过程全部结束,本文着重解释文件在NTFS文件系统的逻辑关系,对于NTFS文件系统的基础知识不做过多介绍,所以阅读本文前,可以参考文末链接了解NTFS的基础知识。另外,人非圣贤,过程中难免有错误,欢迎批评指正!
参考资料:
http://dubeyko.com/development/FileSystems/NTFS/ntfsdoc.pdf
详解NTFS文件系统
NTFS文件系统取证分析
利用winhex在NTFS文件系统下定位文件,找到其目录项和簇号等等
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/lily960427/article/details/66479071
软件安全的实验,记录一下,首先需要对NTFS文件系统有了解,有时间的推荐先看这篇博客
一、NTFS需要的基础
1、MFT:磁盘上的所有数据都是以文件的形式存储,其中包括元文件。 每个文件都有一个或多个文件记录,每个文件记录占用两个扇区 $MFT元文件就是专门记录每个文件的文件记录。 其中第五个目录是根目录的文件记录。 第一个目录是MFT本身的文件记录。
2、簇号: NTFS文件系统使用逻辑簇号(LCN)和虚拟簇号(VCN)对分区进行管理。 逻辑簇号:既对分区内的第一个簇到最后一个簇进行编号,NTFS使用逻辑簇号对簇进行定位。 虚拟簇号:既将文件所占用的簇从开头到尾进行编号的,虚拟簇号不要求在物理上是连续的。
3、索引项:每个文件在目录中有其索引项。结构如图:其中文件的MFT索引号(3个字节)是我们需要的,是虚拟索引号,是相对的。
4、属性(具体可以看博客):
文件记录由两部分构成,一部分是文件记录头,另一部分是属性列表
10H类型属性它包含文件的一些基本信息
20H类型属性既属性列表,当一个文件需要好几个文件记录时,才会用到20H属性。
30H类型属,该属性用于存储文件名 ,它总是常驻属性。
80H属性 80H属性是文件数据属性,该属性容纳着文件的内容
5、runlist(在80或者A0属性里面可以找到):
当属性不能存放完数据,系统就会在NTFS数据区域开辟一个空间存放,这个区域是以簇为单位的。
Run List就是记录这个数据区域的起始簇号和大小
举个栗子 31 0C 2C 22 45 其中3表示有3个字节表示这个数据流的开始簇号即 2C 22 45 1表示有1个字节描述这个数据流的大小即0C
当有多个Run List 的时候,都需要计算,并且相加,得到多个数据流。
二、具体实现(为了计算方便(懒),举例本机E盘上的一个140M左右的文件)
(1)选取的是文件12345aa.pdf,大小约140M:
(2)首先借助winhex工具打开可以直接得到其簇号,并且知道它直接位于E盘根目录之下(作为我们定位的验证)如下图:
(3)大概的流程:
(MFT的结构一般第五个目录就是根目录,并且其一个目录一般都是2个扇区即一个字节,MFT逻辑簇号不是连续的,根目录也是,需要runlist来计算出,它到底有几个数据流,数据流的起始位置和大小分别是多少!)
算出MFT中第五个目录即根目录的扇区位置,转到该扇区得到根目录的runlist
计算出根目录所在的扇区,打开根目录,即可以找到我们的文件pdf
根据它在根目录中的数据找到它在MTF中的索引号,得到MTF中的记录
在MTF中找到它真正的数据流,即可以看到真正的文件内容。(因为大于100M,肯定是非常驻)
(4)用winhex打开我们的磁盘E盘(每个偏移的具体含义参见更加详细的博客)
分析boot文件可以在0DH中得到一个簇有8个扇区的大小。
030-037H是MFT的开始簇号0x00 00 00 00 00 0C 00 00
040-043H是每个MFT记录的大小为0x00 00 00 F6个簇
1C-1FH是隐藏扇区(MBR到DBR) 0x 00 00 10 00簇
(5)计算MFT本身的数据流情况
跳转到MFT的第一个扇区即6291456扇区,在80h属性处得到rundate
33 80 D1 00 00 00 0C 起始簇号786432 大小为53632簇 包括214528个目录项
33 0B C8 00 F5 7E 6F 起始簇号786432+(6F 7E F5)即7306997=8093429 大小为51211簇 包括204844个目录项
33 35 92 00 15 62 38 起始簇号 8093429+(38 62 15)即3695125=11788554 大小为 37429簇 包括149716个目录项
(一个簇8个扇区,2个扇区一个目录项,即一个簇4个目录项)
(6)可以计算得到MFT第五个记录(根目录)所在扇区为6291456+1*5*2=6291466 跳到这个扇区 找到其中的索引属性A0x,得到两个run data 41 05 62 C1 81 01 和 41 01
计算的第一个起始簇号为25280866,第二个起始簇号为1
(7)转到扇区25280866*8扇区得到根目录的扇区:利用winhex验证正确
(8)在根目录下找到我们的文件12345aa.pdf 如下(查找文件名)
(9)根据下图,计算出文件的MFT索引号为085EC4,为548548项。
(10)下面有两种方式找到MFT中的目录项:
(1)使用虚拟簇号,打开MFT,计算偏移为217B1000,跳转得到12345aa.pdf的目录
(2)使用逻辑簇号,根据上面分析的,可知MFT分为了3块,
因为548548>214528+204844 个目录项 ,所以我们的文件在第三块MFT表中
其簇号=(548548-214528-204844)/4+11788554(第三块MFT起始簇号)=11820848号
跳转到其簇号得到文件的目录项
(11)读其80H属性得到其run date为43 20 87 00 3C 7A C0 00 数据流起始位置为12614204簇,与我们直接用工具在右侧看到的簇号相同,转到该簇得到文件内容
(12)定位成功,有子文件夹的参考上门步骤重复计算到最后一个子文件夹就可以。
手把手教你用WinHex在NTFS分区中恢复被删除的文件(上)
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/fjb2080/article/details/5617111
原创文章,转载请注明出处,谢谢!
作者:清林,博客名:飞空静渡
在这篇教程中,我想以一个最简单的例子来说明如何使用winhex来恢复NTFS分区中被删除的文件的,为了使这上篇的文章的知识尽量的简单,我这里所恢复的条件有这么几个:
1、我格式化了一个分区,所以这个分区中是没有任何文件的。
2、我使用的文件大小小于1K,所以这个文件在NTFS分区的文件记录中的数据属性中是个常驻属性。所以这里不涉及到运行计算的问题,应该是最简单的文件恢复了,以这个恢复例子的过程,可以建立一个数据恢复的大体原理了。
希望大家喜欢 :)
下面开始。
第一:建立一个文本文件
我格式化了我的分区G,分区类型是NTFS,使用快速格式化,正常格式化只在检查磁盘坏道时才有用。。。
之后,我在这个分区建了一个文本文件,如下图所示:
这个文件很简单,内容也很少,我保存这个文件后,然后然后我们来看看这个文件大小信息,如下图所示:
我们看到,这个文件大小是224个字节,记住了哦,等下恢复这个文件的时候就可以对比一下了咯 嘻嘻 。。。
之后我就删除了这个文件,现在,我们看看怎么恢复这个文件!
第二:用winhex打开分区
我们运行winhex,然后点击菜单:工具-->打开磁盘,来打开G盘。如下图所示:
我们可以找到$MFT这个文件,然后右击它,然后点击打开,之后就会打开这个文件MFT。如下面两个图所示:
这里为什么要这么做呢?
因为,我们只需要在MFT找到我们丢失的文件,如果我们在整个分区里搜索并且这个分区很大的话,会要很多时间的,而MFT文件就小得多了,最大也就1G左右,这是我人为弄出来的,一般系统是很难见到这么大的MFT文件的,除非你是做服务器,有很多磁盘碎片和小文件。。。
之后,我们可以点击菜单中的:搜索-->查找文本。如下图所示:
我们输入我们想要恢复的文件名HelloWorld,而且注意,要选择Unicode。因为MFT的文件名是Unicode形式的,之后就是查找了!
一会我们就找到了这个文件,如下图所示:
为了能使用WinHex的颜色,我们转到分区去看这个文件记录!首先,我们看到这个文件记录在MFT的偏移地址是7450H,然后我们在winhex中转到我们分区的视图,然后点击MFT文件,这样就偏移到了我们MFT文件的位置,然后选择菜单中的:位置-->转到偏移位置。
然后输出7450,位置是从当前位置开始!如下图所示:
之后,我们就找到了这个文件记录,如下图所示:
我们看图的左边,文件记录号是29,我们看上图的蓝色框,那个是文件记录的标识:FILE。那么我们怎么知道这是个被删除的文件呢,一种办法是直接在文件记录头上看,如上图的红色框,那2字节为文件记录偏移16H处,0表示文件已被删除,1表示这个文件在使用,2表示是个目录等等。
好,现在我们去查找文件记录中的数据属性,这个属性是80H开头,好在winhex有这个颜色分类,很容易找到,如上图的橙色框所示。
下面我们集中抽取这个数据属性来分析,如下图所示:
我们一个一个来分析框中的内容表示的意思,第一个红色的框表示这是个数据属性,值是80H。橙色框:0表示这是个常驻属性,即这个文件的内容就在这个文件记录中,如果是1就表示这是个非常驻属性,那么我们要获取数据就得从运行中一个一个的计算获得,这个在下期讲解!
蓝色框的4个字节表示这个文件的数据大小:E0H,折合十进制就是224,即这个文件大小是224个自己,记得我在前面讲过吗,上面还有文件大小的截图呢!绿色框的4个字节表示这个文件的数据的偏移位置18H,这个偏移从这个数据属性的开始位置计算,就是上图中的80H位置,这个位置这样计算:75b0H + 18H = 75c8H。
然后,我们就可以由这个数据的偏移位置和大小得到这个文件的数据,如紫色框表示。
最后,我们用鼠标来选择这些数据(选择的数据的颜色会变的),然后点击右键,选择:编辑-->复制选项块-->植入新文件,如下图所示:
在这里,我们最好就选择另外一个分区,不要在要恢复的分区中保存文件,已保证我们的数据不会被覆盖掉!
这里,我把文件保存在D盘中,也命名为HelloWorld.txt。
最后,我们来看下我们恢复的文件:
文件被恢复了!
这节讲到这里,以后讲解更加复杂的恢复!
手工WINHEX数据恢复NTFS卷中误删除的文件
对于数据恢复来说,虽然文件删除后所有的数据运行都能够在残留的MFT中找到,但是数据运行的个数越少即文件碎片越少或者没有碎片,文件被覆盖的可能性就越小,数据恢复的概率也就越高。以下是手工恢复NTFS卷中误删除文件的过程。
1.需要恢复的文件
NTFS卷中一个文件被删除时,其MFT并没有被删除,前面已经介绍过,这里以恢复一个NTFS卷中一删除的文件为例,假设在用户的NTFS卷D盘中有一个名为photo的目录,该目录下有一个名为“penquan.jpg”的文件,如图5-1所示。假设用户不小心将此文件删除。
图 5-1 NTFS 卷中将被删除的文件
2. 找到要恢复文件的 MFT
首先通过 WinHex 选择文件所在的逻辑磁盘将其打开,如图 5-2 所示。
图 5-2 选择磁盘分区
打开磁盘的分区后找到该分区的 MFT ,如图 5-3 所示。
图 5-3 转到 MFT 的起始位置
3. 恢复数据
找到分区的 $MFT 后,通过文件名查找文件的 MFT ,如图 5-4 所示。
图 5-4 查找文件的 MFT
查找到的结果如图 5-5 所示。
图 5-5 已删除文件的 MFT
先来看看MFT头,偏移15.16H为0表示该文件已经被删除了,系统根据这个标志来决定建立新文件时是否能够覆盖这个MFT而创建自己的MFT。10H 属性就不分析了,除非希望恢复的文件所有的时间属性和以前一样,用户对此要求一般没有那么高,所以跳过10H属性不分析。30H属性这里也不分析。关键是要分析80H属性,即数据属性,在该属性所有的描述中,对恢复数据最有用的信息有两个,一个是偏移00C12DD160H开始的8个字节的属性是该文件的实际大小506E,单位是字节。还有一个地方是偏移00C12DD170H开始的数据运行位置描述,这里为十六进制数41H 06H 83H 0BH 90H 00H。其中41H定义了其后面有1个字节表示该文件的数据运行所占的簇的个数,4个字节表示该数据运行的起始逻辑簇号,这里定义了其运行占用了06个簇,其起始逻辑簇号为900B83H。知道了起始簇号和数据运行的真实大小,甚至知道运行所占的簇的个数,要恢复文件数据就很容易了。
在WinHex中选择“位置”|“转换到扇区”命令,打开对话框,在“簇”文本框中输入9440131(900B83H转换后的十进制数),然后单击确定,即可找到数据的起始位置,其中FFH DBH是.jpg照片的文件头标志。在找到的数据起始位置右击,选择“选块开始”命令。如图5-6所示。
图 5-6 文件的起始位置
继续在 WinHex 中选择 “ 位置 ”|“ 转换偏移量 ” 命令,打开 “ 转到偏移量 ” 对话框输入 20590 ( 506EH 转换成十进制数)如图 5-7 所示。
图 5-7 转换偏移量
单击确定后会跳到文件的结尾位置,单击右键选择 “ 选块结尾 ” 命令,如图 5-8 所示,即可完全的选择到用户要恢复的文件数据。
图 5-8 找到文件的结束位置
选中所有要恢复的数据内容后,在选中的任意块上右击,选择 “ 编辑 ”|“ 复制选块 ”|“ 进入新文件 ” 命令如图 5-9 所示。
图 5-9 复制所有数据
然后将文件命名并保存到指定的路径,如图 5-10 所示。
图 5-10 保存文件
保存成功后,关闭 WinHex ,按照刚才保存的路径打开文件,数据恢复成功,图 5-11 是恢复后的文件。
转载于:https://www.cnblogs.com/top5/archive/2010/06/13/1757790.html
用winhex恢复删除的文件(NTFS文件系统)
用winhex回复删除的文件(NTFS文件系统)
个人认为用winhex恢复刚删除的一个或是几个文件winhex的效率比数据恢复软件还是比较高的,用winhex对底层数据进行分析并恢复数据。以我的电脑D盘的下载文件下的文件为例。
接下来删除这个文件。shift+delete
现在用winhex打开D盘
首先从DBR找到文件记录开始的位置,我这里是C0000(十六进制)转换过来时786432号簇,每簇是8个扇区(786432*8=6291456扇区)
跳转到$MFT第一个文件记录的位置,搜索drivethelife6_setup.exe文件名(注意文件名是以unicode字符存储的)。在6310826扇区找到目录项。
通过对目录项分析下一个文件记录就是要找的文件drivethelife6_setup.exe的位置。
接下来计算文件的位置了。
第一个 32 8B 00 74 8A 20 开始位置(十六进制 20 8A 74)转换为十进制是2132596簇,占用(十六进制00 8B) 139个簇。转到2132596号簇,139*8=1112 向下偏移1112个扇区,然后复制到新文件。
第二个 32 1E 06 CF D6 FB 开始位置(十六进制 FB D6 CF 这个是负数 转换成二进制111110111101011011001111)然后逐位取反末尾家1转换为十进制是-272689用2132596+(-272689)=1859907簇,占用(十六进制06 1E) 1566个簇。到相应位置复制到新文件
第三个 31 61 28 64 01 开始位置(十六进制 01 64 28)转换为十进制是91176簇,占用(十六进制61) 97个簇。用2132596+(-272689)+91176=1951083号簇,97*8=776 向下偏移776个扇区,然后复制到新文件。
第四个 32 E2 04 E5 CE E3 开始位置(十六进制 E3 CE E5 可以看出这里又是一个负数,转换成二进制 111000111100111011100101)然后逐位取反末尾家1转换为十进制是-1847579簇,占用(十六进制04 E2) 1250个簇。用2132596+(-272689)+91176+(-1847579)=103504号簇,1250*8=10000 向下偏移10000个扇区,然后复制到新文件。
下图是恢复出来的四个碎片文件,要合并后才可以用。
下面合并文件。
合并完成的文件。
看文件是否可以运行。可以运行。
NTFS的目录和文件
本文介绍NTFS的文件和目录。我将从目录在NTFS卷里的存储方式开始,然后介绍用户数据文件的细节,包括文件是怎样存储和命名的,以及文件的最大字节限制。接着我将描述文件的标准属性,最后是重解析点(reparse points),它是Windows 2000里NTFS 5.0引入的新特性。
NTFS的目录
从外部结构看,NTFS组织目录的方式和FAT一样(其他许多文件系统也是如此), 即所谓的多级(hierarchical)模型或目录树(directory tree)模型。目录树的“基(base)”是根(root)目录,NTFS系统的重要元数据文件之一。在根目录里,存储了指向其他文件或目录的引用,每个子目录里又可以有任意的文件和目录,这样就形成了一个树状结构。
注意:目录(Directories)又可以称为文件夹(folders)
虽然NTFS里目录树的结构与FAT的类似,它们内部的管理却不同。其中一点是,FAT里目录包含了其下文件的所有额外信息,文件本身只包含数据。而在NTFS里,文件就是属性的集合,会自己包含需要的描述信息和数据。目录同样只包含自己的信息,而不用管其下的文件。
NTFS里的所有对象都是文件,这也适用于目录。每个目录在MFT里都有一个记录,它是目录信息的主要存储地。MFT里的记录存储了目录的如下属性:
Header (H):这是NTFS用来管理文件或目录的数据,包括NTFS内部使用的标识序列号,指向文件或目录属性的指针,和记录空闲空间的指针。注意Header不是一个属性,而是MFT记录的头信息。
Standard Information Attribute (SI):这个属性是文件或目录的“标准”信息,例如创建、修改、访问时间戳,以及文件的“标准”属性(只读,隐藏等)。
File Name Attribute (FN):这个属性存储目录的名字。注意一个目录可以有多个名字属性,例如“常规”名字,MS-DOS兼容的短名字,或者类似POSIX的硬链接名字。
Index Root Attribute:这个属性包含了目录下所有文件的“标准”信息。如果文件数太多,那么就只包含部分文件的信息,其余的文件信息存储于外部的index buffer attribute里,后面会介绍。
Index Allocation Attribute:如果目录下的文件过多,上面的Index Root Attribute放不下,就会使用这个属性包含指向index buffer入口的指针。
Security Descriptor (SD) Attribute:包含目录及其内容的访问控制信息,或叫安全信息(security information)。目录的访问控制列表(ACLs:Access Control Lists)和相关数据就存储于此。
简单的说,小目录可能整个存储于MFT记录里,就跟小文件一样。大目录会分成多个数据记录,由目录的Index Root Attribute来引用。NTFS使用一种特殊的方式来存储目录下的文件索引。在FAT文件系统里,这些索引是一个链表,前几个索引存储于第一个簇里,接着存储于第二个簇,等等。这种方案实现容易,但需要扫描整个链表才能找到一个文件,导致定位单个文件非常耗时,特别是很大的目录。
为了提高性能,NTFS使用了B树结构。B树是一个平衡树存储结构,源自于关系型数据库, 对它的详细介绍可能花费很多篇幅,而且网络上已有不少资源,那么我就不罗嗦了。从实践效果看,B树能使目录的索引“自动排序”,这在加入新文件的时候会带来一些花费。但是文件的搜索现在性能大大提升了,特别是对于大目录。
NTFS的文件和数据存储
从用户的眼光看,文件系统的基础是文件。文件是一些数据的集合,能包含任何东西:程序,文本,音像,记录,等等其他信息。操作系统不需辨识文件的种类,文件的使用方式依赖于解释它的应用程序。
在NTFS里,文件也存储成和上面类似的方式:属性的集合。文件的数据也是属性之一,术语叫做“data”属性。注意,要理解文件是如何存储的,必须对NTFS的体系结构有所了解,最好是知道MFT的结构和原理,以及NTFS下的属性类型:驻留的和非驻留的。
NTFS里文件的存储方式依赖于文件的大小。所有文件的核心结构都基于下面的信息和属性:
Header (H):同上面目录的Header。
Standard Information Attribute (SI):同上面目录的Standard Information Attribute。
Data (Data) Attribute:这个属性存储了文件的实际内容。
Security Descriptor (SD) Attribute:同上面目录的Security Descriptor Attribute。
除了这些基本属性,一个文件可以有很多其他属性。如果一个文件的所有属性能放进一个MFT记录,那么这个文件就全部在MFT记录里。如果文件太大,NTFS把一部分属性移出MFT记录,转化成非驻留的。具体的步骤是这样的:
1.首先NTFS试图把整个文件放进MFT记录里。只有少数非常小的文件可能成功。
2.如果失败,data属性转化成非驻留的。MFT里的data属性只包含指向这些数据范围(extents,又叫runs)的指针。数据范围(extents)是指存储数据若干个连续的块, 位于MFT的外面。
3.如果文件太大,导致指向数据范围的指针也不能存储在MFT记录里,那么这些指针会变成非驻留的。这样的文件在主MFT记录(main MFT record)里没有data属性,而是有一个指向下一个MFT记录的指针,data属性在这个记录里,并存储指向数据范围的指针。
4.如果文件继续增大,NTFS会重复这种扩展,为超大的文件创建出无限个非驻留的MFT记录,当然,只要磁盘能够容纳。由此可见,文件越大,它的存储结构也会越复杂。
数据范围(extents)是NTFS里大部分文件数据存储的地方,它由若干连续的簇组成。文件的data属性里包含了数据范围的起始簇标识,和簇的个数。起始簇标识是一个虚拟簇编号(VCN:virtual cluster number)。而“起始 + 长度“的方式意味着NTFS不用读遍每个簇才知道后面的簇在哪里,它还能减少文件碎片的产生。
NTFS的文件大小
商用程序和数据库使用Windows操作系统和FAT文件系统的最大问题之一就是文件的大小限制,有时是4GB,有时是2GB。起初它们看起来足够了,但是到现在谁都知道这远远不够。即使是我自己的机器,有时也会备份比这大得多的数据文件。
前面介绍了NTFS试图把小文件存进MFT记录里,大些的文件把数据存到扩展属性和数据范围里。这种设计允许文件的大小没有限制。实际上,NTFS下文件的大小的确没有限制,单个文件可以占用整个卷,只留下很小的MFT和其他必要的内部结构。
NTFS还应用了一些特性来优化存储非常大的文件。其中之一是文件压缩(file-based compression),降低大文件占用的磁盘空间。另一个是稀疏文件(sparse files),非常适用于那些大部分内容都是0的文件。
NTFS文件命名
微软早期的操作系统在文件命名上非常不方便,比如DOS风格的8字符文件名加3字符后缀方式——所谓的8.3标准文件名。与其他竞争对手例如Unix和Apple Macintosh比起来,这种方式简直不可接受!为了解决这个问题,微软决定给NTFS的文件名以充分的扩展性。
长度:普通文件的文件名长度最多为255个Unicode字符(2字节);
大小写:文件名可以大小写混用,并且会被NTFS保存起来。但是访问文件的时候文件名是大小写无关的,举个例子:你命名了文件“4Q Results.doc”,当你列出这个文件时,会正确的显示“4Q Results.doc”(大小写被NTFS保存起来了)。但是你可以使用下面的任何名字引用到刚才的文件:“"4q results.doc”,“ReSulTS.dOc”,等等。
字符:文件名能包含任意Unicode字符,包括空格,除了这几个:? " / / < > * | :,原因是这几个字符被操作系统作为文件名的分隔符或命令操作符。
Unicode格式:所有的NTFS文件名都存储成Unicode格式。相比之下,传统的计算机使用ASCII码(1字节),只能辨识“最常见”的一百多个英语字符。这对于其他很多语言都是不够的,特别是亚洲国家。Unicode是一种国际性的,16-bit的字符表示方式,支持当今几乎所有的语言和符号。
有的读者也许会想起,当初Window 95的VFAT文件系统引入了一种长文件名,作为附加属性。这个文件系统自动为所有长文件名生成一个8.3形式的短文件名,兼容以前的软件。NTFS也是这样做的。但是表面相似并不代表内部也是一样,VFAT只是在短文件名系统上打了一个补丁,NTFS却是从设计上就支持长文件名。
文件名存储在MFT记录的file name属性里。实际上,NTFS支持同一个文件的MFT记录里有多个file name属性,第一个是文件的“常规”文件名,第二个是MS-DOS兼容的短文件名,甚至第三个作为兼容POSIX标准的硬链接(hard links)文件名。硬链接是指同一个文件有多个名字,并位于不同的目录之下。这些链接的名字可以作为不同的file name属性。这是对UNIX灵活的命名系统的一个模仿。
NTFS文件属性
在NTFS文件系统里,文件是属性的集合,属性则是各种形式的信息和数据,属性的内容由软件来解释。目录也是这样,它们只是了包含一些普通文件没有的属性,并由文件系统来解释和使用。
所有的属性都有2种不同的存储方式,依赖于属性自身的大小,即:
驻留属性:如果属性只需很少的存储空间,那么可以放在文件的主MFT记录里。事实上,NTFS要求某些属性必须在MFT记录里,比如文件名,创建、修改、访问时间戳等。
非驻留属性:如果属性需要更多的空间,那么可以存储在MFT记录外面。MFT记录里会保留指向属性数据的指针。
由于MFT记录大小有限,只有少数属性可以放在MFT记录里。许多其他属性都是非驻留的,特别是文件的data属性。非驻留的属性又有2种形式。如果指向数据的指针能放进MFT记录,那么把数据放在数据范围里(称为run或extent),指向这些范围的指针放在MFT记录里。一个属性可以占用多个数据范围,每个对应一个MFT记录里的指针。如果一个属性占用的数据范围太多,导致这些指针已不能放进文件的MFT记录,那么整个data属性可以移出去,变成一个外部属性(external attribute),存储于一个或多个MFT记录里。
NTFS预定义了一些属性,称为系统属性,如下表所示:
Attribute List:这是一个“元属性(meta-attribute)”,即属性的属性。如果某个属性变成非驻留的,那么主MFT记录会使用Attribute List存储指向这个属性的指针;
Bitmap:保存簇分配信息的位图,由$Bitmap元数据文件使用;
Data:包含文件数据。默认情况下,文件的所有数据存储在单个data属性里,数据量再大也是一个属性。不过文件可以有多个其他数据属性,供特殊的应用程序使用;
Extended Attribute (EA)和Extended Attribute Information:据我所知,这些属性是为了兼容OS / 2下的NTFS分区,Windows NT / 2000没有使用它们;
File Name (FN):文件或目录的名字。前面说过,一个文件可以有多个名字,包括“常规”名字,MS-DOS兼容的短名字,或POSIX硬链接名,等;
Index Root Attribute:包含一个目录下的文件索引信息。如果目录下文件很少,所有的文件索引都可以放在MFT记录里;如果文件数太多,那么MFT里只存放部分文件的索引,其余的放在外面的index buffer attributes里;
Index Allocation Attribute:如果一个目录下的文件太多,MFT记录会创建这个属性,包含指向外部index buffer attributes属性的指针;
Security Descriptor (SD)::包含文件及其内容的访问控制信息,或叫安全信息(security information)。文件的访问控制列表(ACLs :Access Control Lists)和相关数据就存储于此。文件所有者和认证信息也存储于此;
Standard Information (SI):这个属性是文件或目录的“标准”信息,例如创建、修改、访问时间戳,以及文件的“标准”属性(只读,隐藏等)。
Volume Name,Volume Information和Volume Version:NTFS卷的标签,版本和其他信息,由$Volume元数据文件使用。
NTFS还支持用户为文件创建自定义的属性。在这里“用户”的意义有些微妙,因为这是从微软的角度来说的——应用程序开发者。应用程序可以给文件创建自己的属性,但是NTFS文件系统的使用者不能。
NTFS重解析点(Reparse Points)
随Windows 2000发布的NTFS版本5里最有趣的一个属性是引入了一些特殊的文件系统功能,并应用于特定的文件或目录上。这些特殊功能使NTFS文件系统更加强大和有扩展性。这个特性的实现基础叫做重解析点(reparse points)。
重解析点的使用源于一些应用程序想把一些特殊数据存储到特殊的地方——重解析点,然后由应用程序做上特殊的标记,只允许它使用。为此文件系统引入了一个应用程序相关的特殊过滤器(application-specific filter),并与重解析点的标记关联起来。多个应用程序可以把不同的数据存储到同一个重解析点文件里,只要使用不同的标记。微软保留了几个标记为自己使用。
现在我们假设用户打算访问一个有标记的重解析点文件。当文件系统打开文件时,发现有重解析点关联到这个文件,于是“重解析”这个打开文件请求,发现与应用程序相关联的可用过滤器,并与这个重解析点进行匹配,通过后就可以把重解析点的数据传送给这个过滤器了。过滤器于是可以把这些数据用于任何途径,依赖于应用程序最初的定义。这是一个非常灵活的系统:应用程序不需关心重解析点是如何工作的,重解析点的实现细节对于用户是完全透明的。你只需简单的放入和拿出数据,其余的事情都是自动完成。这使文件系统的功能大大增强了。
微软使用重解析点在Windows 2000里实现了如下的功能:
符号链接(Symbolic Links):符号链接允许你创建一个指向其他地方某个文件的指针。NTFS并没有像UNIX文件系统那样实现“真正”的文件符号链接,但是从功能上重解析点完全可以模拟得一模一样。本质上,NTFS的符号链接就是一个重解析点,把对一个文件的访问转移到另一个文件身上。
交叉点(Junction Points):交叉点和符号链接类似,只不过对象是目录而不是文件。
卷装载点(Volume Mount Points):卷装载点和前2者类似,只是更进一层:它能创建对整个卷的链接。比如,你可以为可移动硬盘或其他存储介质创建卷装载点,或者让本地的不同分区(C:,D:,E:等等)看起来就像在一个卷里一样。这对于那些大型的CD-ROM服务器非常有用,如果没有卷装载点,它们就只能为每张磁盘人工维护一个分区字母。
远程存储服务器(RSS:Remote Storage Server):Windows 2000的这个特性能利用一些规则来移除NTFS卷上不常用的文件,放到存档介质里(比如CD-RW或磁带)。当它把文件移出到“下线”或“半下线”的存储介质上时,RSS自动创建指向这个存档文件的重解析点,以备日后使用。
上面只是重解析点使用例子的一少部分。这个功能非常灵活,是NTFS的亮点之一:它让文件系统的功能大大增强,又不需对文件系统进行任何更改。
联系客服