前面解析MBR,得到每个分区的起始扇区号,以及每个分区的大小;至于该分区的详细信息,则取决于该分区卷加载的文件系统。比如:簇大小,扇区大小,文件系统类型,该分区使用情况,介质描述,隐含扇区,扇区总数……
解析完MBR,跳转到引导分区的第一个扇区,即DBR;然后从DBR中的BPB中得到改卷的基本参数信息,定位MFT起始簇,进而定位到文件系统元数据文件位置,对该分区上的所有文件和目录进行遍历解析。
下面分步说明:
DBR的作用
简单的来说,计算机启动时执行完BOIS的启动代码,检查各硬件设备正常后,JMP到MBR的引导代码进行执行;然后由MBR引导至活动分区的DBR,再由DBR引导操作系统。如:DBR调用NTLDR,再由NTLDR调用系统内核。
一.FAT32DBR和NTFS$BOOT的统一
每个分区第0号扇区,也就是系统引导扇区DBR(DOSBOOTRECORDER)。不管是FAT32文件系统,还是NTFS文件系统,第一个扇区内容都是DBR。很多做文件系统过滤驱动的开发人员强调,如果是FAT32系统,那么分区开始的第一个扇区是DBR;如果是NTFS系统,那么分区开始的第一个扇区是$BOOT文件。其实,这个说法是基于文件系统特征的,虽然没有错,但也混淆了视听。事实上,在NTFS系统上,$BOOT位于分区开始的2个簇上,也就是16个扇区,8K大小(假设每簇大小占8扇区)。在这16个扇区上,第一个扇区仍然是DBR。所以,从分区卷扇区级上来说,NTFS分区的第一个扇区仍然是DBR。这样就统一了说法,也便于理解了~
二.读取DBR,进行解析:
为什么要读DBR呢,因为DBR存放着关于文件系统的重要参数信息以及系统引导代码。读DBR扇区的数据,并进行解析;由前面的分析,至少有两种方法可以定位到DBR的位置。
关于解析DBR,声明两个重载的函数:
voidAnalyseDBR(ULONGulSector);//参数为扇区号
voidAnalyseDBR(wchar_t*szVolumeLink);//参数为分区符号链接
A.解析MBR,根据分区表的信息,定位到指定分区的起始扇区,读取该扇区,按照DBR格式进行解析,上一节已经解析了MBR,分区信息存储在链表结构里,直接定位到第一个分区,读取DBR,伪代码如下:
//
//定位到第一个卷,即C盘
//
{
NextEntry=RemoveHeadList(&NtfsData.Partitions);
PtItem=(PPARTITION_ITEM)CONTAINING_RECORD(NextEntry,PARTITION_ITEM,ForPtChain.ListEntry);
cout<<"\nthetestvolume'sstartsector:"<<PtItem->StartSector<<endl;
ulDbrSector=PtItem->StartSector;
}
//利用解析MBR的分区表信息,定位到分区开始扇区,解析DBR
AnalyseDBR(ulDbrSector);
详细代码见toysNtfs工程。
B.根据分区符号链接,如:_T("\\\\.\\C:"),直接打开分区,进行读写;相比解析MBR的方式,这种方法明显直接而简单,打开分区以后,第一个扇区就是DBR。解析MBR虽然麻烦,但是灵活;将整个磁盘与分区都联系起来,更容易理解磁盘结构;甚至经过深入学习,自己开发一个小型的磁盘分区工具,划分一小块空间,用来存储自己的秘密文件,该卷的解析方式只有你自己知道(有意不让windows识别),岂不快哉~
伪代码如下:(假设打开C:盘)
//利用分区符号链接,打开分区,进行解析
AnalyseDBR(_T("\\\\.\\C:"));
前后两种方法解析的结果如下:
(相同的结果)
C.DBR在磁盘上的格式和各字段的含义,在FAT文件系统上的DBR和NTFS上的DBR有所不同,下面以NTFS为例:
大概分以下几个部分:
跳转指令;
文件系统标志,Oem;
BPB,25字节;
其后类似于扩展BPB的一些字段;
大小为[0x200-0x044]的引导代码;
结束标志:55AA
用代码结构表示如下:同样,只记录需要关心的若干字段,便于解析NTFS其他部分。
typedefstruct_PACKED_BOOT_SECTOR{
UCHARJump[3];
UCHAROem[8];
BIOS_PARAMETER_BLOCKPackedBpb;//BPB
UCHARUnused[4];
LONGLONGNumberSectors;//扇区总数
LCNMftStartLcn;//MFT开始簇
LCNMft2StartLcn;
CHARClustersPerFileRecordSegment;
UCHARReserved0[3];
CHARDefaultClustersPerIndexAllocationBuffer;
UCHARReserved1[3];
LONGLONGSerialNumber;
ULONGChecksum;
UCHARBootStrap[0x200-0x044];//引导代码
}PACKED_BOOT_SECTOR;
typedefPACKED_BOOT_SECTOR*PPACKED_BOOT_SECTOR;
DBR中的BPB结构如下:
typedefstructBIOS_PARAMETER_BLOCK{
USHORTBytesPerSector;
UCHARSectorsPerCluster;
USHORTReservedSectors;
UCHARFats;
USHORTRootEntries;
USHORTSectors;
UCHARMedia;
USHORTSectorsPerFat;
USHORTSectorsPerTrack;
USHORTHeads;
ULONGHiddenSectors;
ULONGLargeSectors;
}BIOS_PARAMETER_BLOCK;
typedefBIOS_PARAMETER_BLOCK*PBIOS_PARAMETER_BLOCK;
大概能见名知意。BPB(boisparameterblock)是一段描述能够使可执行引导代码找到相关参数的信息。主要描述磁盘结构的细节,包括每扇区的字节数BytesPerSector,每簇的扇区数SectorsPerCluster;磁盘介质;每磁道扇区数,磁头数等,均为后续引导代码提供参数。
关于文件系统的信息,其实在扩展BPB中,也就是BPB之后的若干字段。这些字段中的数据使得NTLDR能够在启动时找到主文件表$MFT,进而定位到其他数据文件。
至此,DBR解析完毕,得到该卷上的MFT起始簇,备份MFT起始簇,每簇扇区数,每扇区字节数,介质等信息。经过简单的计算就能知道起始MFT的扇区号。
三.解析MFT
由于用扇区来描述磁盘空间粒度比较小,处理量比较大,所以引进簇的概念。簇一般是2的倍数,一般NTFS中一个簇占8个连续扇区。基本上所有文件系统用簇来描述分区。
LCN(logicalclusternumber)逻辑簇号,对卷的第1个簇到最后一个簇进行编号。只要知道LCN号和簇的大小以及NTFS卷在物理磁盘中的绝对扇区就可以对簇进行扇区定位。
VCN(virtualclusternumber)虚拟簇号,VCN对特定文件的簇从头到尾进行编号,表示文件内部的相对位置,方便系统对文件中的数据进行引用。假如一个文件占用m个簇,那么这些簇的VCN就是从0到(m-1)。
NTFS文件系统用文件来记录所有信息;文件由属性组成;所以对NTFS文件的读写,实际上是对NTFS属性的读写。一个文件对应一条MFT记录(即文件记录块),一个文件记录块固定大小为1K,占两个扇区;所有文件的MFT记录都有一个文件来管理,就是$MFT,也就是0号MFT记录所指向的文件。
由前面解析DBR知道MFT起始扇区,那么读取该扇区内容,就是$MFT文件的文件记录了。
解析之前,先说说元文件,对文件系统有一个全局的认识。
NTFS文件系统由DBR(本身也是$BOOT的内容)和元文件组成,其他文件和目录通过文件系统管理。
元数据文件记录了一些非常重要的文件系统数据,包括用于文件定位和恢复的数据结构、引导程序数据以及整个卷的分配位图等信息。这些数据被NTFS文件系统理解为文件,并以文件的方式进行管理,统一了思想。
元文件对于用户是不能直接访问的,MFT将开头的16个文件记录块保留用于这些元数据文件,除此之外的文件记录块才用于普通的用户文件和目录。
文件系统16个元文件列表:
0$MFT//mft本身
1$MftMirr//mft元数据文件的镜像,用于备份恢复
2$LogFile//文件操作历史记录文件
3$Volume//文件卷信息文件
4$AttrDef//属性定义文件
5$Root(\)//根目录文件
其它的元文件参照其他资料,不例举了。
MFT结构
每个文件都有一条MFT记录,通过读取该文件的MFT记录,就可以知道该文件有哪些属性,各属性的内容是多少,是否有加密,是否有隐藏存档属性,是否有命名的数据流等。
MFT文件记录由记录头和属性部分组成;一个文件可以有多个属性,这些属性又分为属性头和属性内容。这些属性的标准信息由$AttrDef元文件定义。
属性又分为常驻属性(ResidentAttr)和非常驻属性(NonResidentAttr)。当文件的属性内容大于大约700字节时,就要使用非常驻属性。将属性内容外挂到磁盘的其他位置,这些位置由runs片来描述。
不同属性相同的地方在于属性头,即要么是常驻属性头,要么是非常驻属性头;不同属性的属性内容各不相同,在磁盘上的分布结构也不同,作用也不同;特别说明,这些属性是可以扩充的。对各个属性的解析,则比较繁琐。
那么解析MFT文件记录块的基本框架如下:
ReadSector(MFT索引号,长度)
{
解析MFT记录头;(大小0x38字节)
解析属性部分
{
Switch(属性类型)
{
Case:对不同的属性做处处理,读取或修改,break;
Default:
}
}
}
指定一个MFT,解析属性列表:
关于MFT的详细结构,请参考文献资料,解析过程见代码。
无法说的太详细,因为NTFS内容很多,结构却一点也不复杂;展开全部细节,却又不是只言片语就能说完的,亦属力所不能及;说的太概念化,又恐读之无物。那,权当抛砖引玉吧。
程序代码写的只是一个框架,在这个框架上就可以填充各个属性的详细分析过程。这样的解析未免不太科学,但是作为学习了解文件系统在磁盘上的底层结构,却是很简单有效的。如果要理解NTFS文件系统工作的原理,须跟踪文件系统IO处理流程,了解IO管理器,磁盘驱动,缓存cache管理等相关知识。
顺着这个路径,熟悉文件的各个属性,属性之间的相互作用;用tfs代码,参考部分解析过程。
PS:程序代码只是为了学习方便而写的简单测试框架;本节主要是分析DBR,获取MFT的起始簇,获取分区卷的必要参数信息;对指定的一条MFT文件记录进行定位和解析;如此就可以从MFT中读取文件的所有内容,包括:文件只读、隐藏、存档属性,创建时间,修改时间,对象ID,数据内容,命名数据流,重解析点等信息。
那么,遗留的问题是,如何获得MFT索引号?MFT号其实是文件引用号(64位)的低48位,那么如何获取文件引用号?这里方法就不一了,比如:递归解析$Root跟目录文件,获取每个索引项的文件引用号,取低48位作为MFT索引号去MFT表中读取文件数据;第二种方法是,遍历MFT在磁盘上分布的区域大小,计算出总的MFT记录数量,然后发送控制码FSCTL_GET_NTFS_FILE_RECORD来获取文件引用号。
关于解析:首先要明确目的,才能在解析过程中对关心的文件属性信息作特殊处理;如果作为学习,那么选择一个清晰明了的框架还是比较合理的。等熟悉了基本结构,在去深入学习NTFS驱动源码,调试,在实际应用中开发项目,合理应用,使产品更高效;举个例子,NTFS文件系统中,遍历磁盘上的所有文件,并获取文件在磁盘上的数据存储信息。如果是普通的递归遍历法,即FindFirstFile,,FindNextFile,那么遍历速度将比直接遍历MFT慢25倍左右。
下节具体解析文件的若干属性(数据属性,$IndexRoot根目录属性);给定文件名通过$ROOT文件查找文件引用号。
参考资料:
《数据恢复技术》戴士剑、涂彦晖;
《数据重现,文件系统原理精解》马林
《windowsinternal》,4th,MarkE.Russinovich
《windows内核情景分析》毛德操
相关代码:
ToysNtfs2.rar