计算机毕业论文:探索NTFS
时间:2022-11-17 10:21:00
导语:计算机毕业论文:探索NTFS一文来源于网友上传,不代表本站观点,若需要原创文章可咨询客服老师,欢迎参考。
NTFS是WindowsNT引入的新型文件系统,它具有许多新特性。本文旨在探索NTFS的底层结构,所叙述的也仅是文件在NTFS卷上的分布。NTFS中,卷中所有存放的数据均在一个叫$MFT的文件中,叫主文件表(MasterFileTable)。而$MFT则由文件记录(FileRecord)数组构成。FileRecord的大小一般是固定的,通常情况下均为1KB,这个概念相当于Linux中的inode。FileRecord在$MFT文件中物理上是连续的,且从0开始编号。$MFT仅供FileSystem本身组织、架构文件系统使用,这在NTFS中称为元数据(Metadata)。以下列出Windows2000Release出的NTFS的元数据文件(我将要给出的示例代码的部分输出结果)。
FileRecord(inode)FileName
--------------------------
0$MFT
1$MFTMirr
2$LogFile
3$Volume
4$AttrDef
5.
6$Bitmap
7$Boot
8$BadClus
9$Secure
10$UpCase
11$Extend
Windows2000中不能使用dir命令(甚至加上/ah参数)像普通文件一样列出这些元数据文件。实际上FileSystemDriver(ntfs.sys)维护了一个系统变量NtfsProtectSystemFiles用于隐藏这些元数据。默认情况下,这个变量被设为TRUE,所以使用dir/ah将得不到任何文件。知道这个行为后使用i386kd修改NtfsProtectSystemFiles后即可以列出元数据文件:
kd>xntfs!NtfsProtect*
fe213498Ntfs!NtfsProtectSystemFiles
fe21349cNtfs!NtfsProtectSystemAttributes
kd>ddntfs!NtfsProtectSystemFilesl2
fe2134980000000100000001
kd>edntfs!NtfsProtectSystemFiles0
kd>ddntfs!NtfsProtectSystemFilesl2
fe2134980000000000000001
kd>
D:\>ver
MicrosoftWindows2000[Version5.00.2195]
D:\>dir/ah$*
驱动器D中的卷是W2KNTFS
卷的序列号是E831-9D04
D:\的目录
2000-04-2719:3136,000$AttrDef
2000-04-2719:310$BadClus
2000-04-2719:3167,336$Bitmap
2000-04-2719:318,192$Boot
2000-04-2719:31<DIR>$Extend
2000-04-2719:3113,139,968$LogFile
2000-04-2719:3127,575,296$MFT
2000-04-2719:314,096$MFTMirr
2000-04-2719:31131,072$UpCase
2000-04-2719:310$Volume
9个文件40,961,960字节
1个目录51,863,552可用字节
需要指出的是ntfs.sys将元数据文件以一种特殊的方式打开,所以在打开NtfsProtectSystemFiles后,如果使用ReadFile等产生IRP_MJ_READ等IRP包时将会导致PageFault(详见GaryNebbett的《WindowsNT/2000NativeAPIReference》)。
以上的讨论均是基于$MFT文件而讨论的,即基于$MFT中的FileRecord(inode)讨论的。为更好的继续以下的讨论,这儿我列出FileRecordHeader的结构:
typedefstruct{
ULONGType;
USHORTUsaOffset;
USHORTUsaCount;
USNUsn;
}NTFS_RECORD_HEADER,*PNTFS_RECORD_HEADER;
typedefstruct{
NTFS_RECORD_HEADERNtfs;
USHORTSequenceNumber;
USHORTLinkCount;
USHORTAttributesOffset;
USHORTFlags;//0x0001=InUse,0x0002=Directory
ULONGBytesInUse;
ULONGBytesAllocated;
ULONGLONGBaseFileRecord;
USHORTNextAttributeNumber;
}FILE_RECORD_HEADER,*PFILE_RECORD_HEADER;
下面我将讨论如何定位$MFT。稍微有点操作系统知识的人都会知道引导扇区(BootSector),其物理位置为卷中的第一个扇区。以下由dskprobe.exe(Windows2000ResourceKit中的一个小工具)分析的第一个扇区(当然也可以使用WinHex等其他应用程序):
file:d:\Sector00.bin
Size:0x00000200(512)
Address|00010203-04050607:08090A0B-0C0D0E0F|0123456789ABCDEF
---------|-------------------------:-------------------------|-----------------
00000000|EB52904E-54465320:20202000-02080000|?R?NTFS.....
00000010|00000000-00F80000:3F00F000-3F000000|.....?..?.e.?...
00000020|00000000-80008000:90C04100-00000000|....€.€.惱A.....
00000030|04000000-00000000:091C0400-00000000|................
00000040|F6000000-01000000:049D31E8-BB31E894|?.......?杌1钄
..
..
..
000001F0|00000000-00000000:83A0B3C9-000055AA|........儬成..U?
这512字节为如下的格式:(摘自GaryNebbett书中,本文许多代码均来自或参考此书。)
#pragmapack(push,1)
typedefstruct{
UCHARJump[3];
UCHARFormat[8];
USHORTBytesPerSector;
UCHARSectorsPerCluster;
USHORTBootSectors;
UCHARMbz1;
USHORTMbz2;
USHORTReserved1;
UCHARMediaType;
USHORTMbz3;
USHORTSectorsPerTrack;
USHORTNumberOfHeads;
ULONGPartitionOffset;
ULONGReserved2[2];
ULONGLONGTotalSectors;
ULONGLONGMftStartLcn;
ULONGLONGMft2StartLcn;
ULONGClustersPerFileRecord;
ULONGClustersPerIndexBlock;
ULONGLONGVolumeSerialNumber;
UCHARCode[0x1AE];
USHORTBootSignature;
}BOOT_BLOCK,*PBOOT_BLOCK;
#pragmapack(pop)
各个字段的详细意义从字段名中即可大致清楚。在linux-ntfs的GNU工程(/projects/linux-ntfs)中也有详细的文档,限于篇幅我不将其列出。可以使用如下代码读出卷中的第一个扇区:
hVolume=CreateFile(drive,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,0,
OPEN_EXISTING,0,0);
ReadFile(hVolume,&bootb,sizeof(bootb),&n,0);
bootb是一个BOOT_BLOCK结构,在我的卷中如下格式(请对应Sector00.bin分析):
DumpBootBlockatbelow:
BytesPerSector:200
SectorsPerCluster:8
BootSectors:0
SectorsPerTrack:3F
NumberOfHeads:F0
PartitionOffset:3F
TotalSectors:41C090
MftStartLcn:4
Mft2StartLcn:41C09
ClustersPerFileRecord:F6
ClustersPerIndexBlock:1
VolumeSerialNumber:E8319D04
BootSignature:AA55
以上的MftStartLcn其实是$MFT在卷中的簇(Cluster)号。簇是NTFS的基本单位,最小单位。一个只有1Byte的文件也要占用一簇的空间。NTFS使用LCN(LogicalClusterNumber)来代表NTFS卷中的物理位置,其简单的从0到卷中的总簇数减一进行编号。对于一个特定的文件NTFS则使用VCN(VirtualClusterNumber)来映射LCN实现文件的组织。从MftStartLcn的值4可以知道$MFT的LCN为4与SectorsPerCluster、BytesPerSector的大小即可定位$MFT的位置。得到$MFT的位置后,如果遍历$MFT中所有的FileRecord即可以得到卷中所有的文件列表(前面已经提到FileRecord只是简单的从0开始编号)。也就是说到目前为止已经可以对文件组织有最简单的认识,但如何得到文件的信息呢,如文件名等等。NTFS中所有文件包括普通的用户文件、元数据文件均用同样的方式组织数据、属性等。我将nfi.exe(来自WindowsNT/2000OEMSupportTools)的输出结果列出,作为我叙述的开始:
D:\>copyconfile
testforntfs^Z
已复制1个文件。
D:\>nfid:\file
NTFSFileSectorInformationUtility.
Copyright(C)MicrosoftCorporation1999.Allrightsreserved.
\file
$STANDARD_INFORMATION(resident)
$FILE_NAME(resident)
$DATA(resident)
D:\>echotestforattr>file:ATTR
D:\>nfid:\file
NTFSFileSectorInformationUtility.
Copyright(C)MicrosoftCorporation1999.Allrightsreserved.
\file
$STANDARD_INFORMATION(resident)
$FILE_NAME(resident)
$DATA(resident)
$DATAATTR(resident)
nfi的输出结果$STANDARD_INFORMATION、$FILE_NAME、$DATA等在NTFS中称为属性(Attribute)。属性分为常驻属性(ResidentAttribute)与非常驻属性(NonresidentAttribute)。文件的数据也包含在属性中,似乎与属性这个名称有点混谣。不过这又让NTFS有了更加统一的组织文件的形式。这也同时让NTFS有MultiStreams的特性(上面也演示了这个特性)。通过指定的FileRecord定位给定的Attribute的实现代码如下:
template<classT1,classT2>inline
T1*Padd(T1*p,T2n){return(T1*)((char*)p+n);}
PATTRIBUTEFindAttribute(PFILE_RECORD_HEADERfile,
ATTRIBUTE_TYPEtype,PWSTRname)
{
for(PATTRIBUTEattr=PATTRIBUTE(Padd(file,file->AttributesOffset));
attr->AttributeType!=-1;
attr=Padd(attr,attr->Length)){
if(attr->AttributeType==type){
if(name==0&&attr->NameLength==0)returnattr;
if(name!=0&&wcslen(name)==attr->NameLength
&&_wcsicmp(name,PWSTR(Padd(attr,attr->NameOffset)))==0)returnattr;
}
}
return0;
}
GaryNebbett提供的这个FindAttribute函数在Attributename(即第三个参数)不为空串时可能会出现bug,主要原因是_wcsicmp对UNICODE字符串比较时应该是以\0结束的标准的C字符串。我在提供的代码中已经纠正了这个错误。
下面我将通过使用SoftICE来分析这段代码得到$MFT的$FILE_NAME属性来得到$MFT的filename。这个示例同样适用于得到其它文件的$FILE_NAME(如上面的file)、还有其它的属性如$DATA等等。
:bpxFindAttribute
BreakduetoBPXFindAttribute(ET=6.89seconds)
:locals
[EBP-4]+structATTRIBUTE*attr=0x00344D68<{...}>
[EBP+8]+structFILE_RECORD_HEADER*file=0x00344D38<{...}>
[EBP+C]enumATTRIBUTE_TYPEtype=AttributeFileName(30)
[EBP+10]+unsignedshort*name=0x004041BC<"$MFT">
:?file
structFILE_RECORD_HEADER*=0x00344D38<{...}>
structNTFS_RECORD_HEADERNtfs={...}
unsignedshortSequenceNumber=0x1,"\0\x01"
unsignedshortLinkCount=0x1,"\0\x01"
unsignedshortAttributesOffset=0x30,"\00"
unsignedshortFlags=0x1,"\0\x01"
unsignedlongBytesInUse=0x2D8,"\0\0\x02\xD8"
unsignedlongBytesAllocated=0x400,"\0\0\x04\0"
unsignedquadBaseFileRecord=0x0,"\0\0\0\0\0\0\0\0"
unsignedshortNextAttributeNumber=0x6,"\0\x06"
file参数我传入的是$MFT,从$MFT的LCN=4可以得到其在卷中的物理地址,这在上面已说明。你也可以使用dskprobe(我机子中为第LCN*SectorsPerCluster=4*8扇区)得到底下SoftICE的输出结果:
:dd@file//以下的注释可对照文中开头列出的FILE_RECORD_HEADER定义。
0023:00344D38454C49460003002A6D4AC04D00000000FILE*...M.Jm....
0023:00344D480001000100010030000002D800000400....0...........
----
|__AttributeOffset
0023:00344D580000000000000000043400060000FA0D..........4.....
0023:00344D6800000010000000600018000000000000....`...........
----------------
||_指出这个Attribute的长度。定义如下。
|_根据AttributeOffset得到的Attribute头,定义如下。00000010指出这个Attribute为StandardInformation
0023:00344D7800000048000000182C1761D001BFB03CH........a.,<...
Attribute头如下定义:
typedefstruct{
ATTRIBUTE_TYPEAttributeType;
ULONGLength;
BOOLEANNonresident;
UCHARNameLength;
USHORTNameOffset;
USHORTFlags;//0x0001=Compressed
USHORTAttributeNumber;
}ATTRIBUTE,*PATTRIBUTE;
typedefstruct{
ATTRIBUTEAttribute;
ULONGValueLength;
USHORTValueOffset;
USHORTFlags;//0x0001=Indexed
}RESIDENT_ATTRIBUTE,*PRESIDENT_ATTRIBUTE;
typedefstruct{
ULONGLONGDirectoryFileReferenceNumber;
ULONGLONGCreationTime;//Savedwhenfilenamelastchanged
ULONGLONGChangeTime;//ditto
ULONGLONGLastWriteTime;//ditto
ULONGLONGLastAccessTime;//ditto
ULONGLONGAllocatedSize;//ditto
ULONGLONGDataSize;//ditto
ULONGFileAttributes;//ditto
ULONGAlignmentOrReserved;
UCHARNameLength;
UCHARNameType;//0x01=Long,0x02=Short
WCHARName[1];
}FILENAME_ATTRIBUTE,*PFILENAME_ATTRIBUTE;
ATTRIBUTE_TYPE是一个Enum型定义。其中00000010为StandardInformation。30为FileName。因为FileNameAttribute总是一个常驻Attribute,所以我将RESIDENT_ATTRIBUTE定义也给出。OK,现在可以继续Dump下一个Attribute:
//dd@file+file->AttributeOffset+length(StandardInformationAttribute)
:dd@file+30+60
0023:00344DC8000000300000006800180000000300000...h...........
--------------
||___这里的NameLength与NameOffset指FileNameAttribute名。不要与$MFTFileName混谣。
|_指出这是一个FileNameAttribute。
0023:00344DD80000004A000100180000000500050000J...............
--------------------
|||_根据ValueOffset的值,得到FILENAME_ATTRIBUTE的具体位置。
||_ValueOffset值
|_ValueLength值
0023:00344DE82C1761D001BFB03C2C1761D001BFB03C.a.,<....a.,<...
0023:00344DF82C1761D001BFB03C2C1761D001BFB03C.a.,<....a.,<...
0023:00344E0800004000000000000000400000000000.@.......@......
0023:00344E180000000600000000002403040046004D..........$.M.F.
----------
||___找到$MFT的FileName了吧。
|_NameLength
0023:00344E2800000054000000000000008000000190T...............
0023:00344E3800400001000100000000000000000000..@.............
这儿给出了DumpAttribute的一个具体方法。最后我将给出遍历FileRecord的代码,在给出代码前应该说明一下$MFT中$BITMAP属性。NTFS的这个Attribute相当于LINUXEXT2的s_inode_bitmap数组(Linux2.0版本)。所以很容易明白$BITMAP的作用,即每bit指出相应FileRecord的在用情况。以下是DumpAllFileRecord的代码:
BOOLbitset(PUCHARbitmap,ULONGi)
{
return(bitmap[i>>3]&(1<<(i&7)))!=0;
}
VOIDDumpAllFileRecord()
{
PATTRIBUTEattr=FindAttribute(MFT,AttributeBitmap,0);
PUCHARbitmap=newUCHAR[AttributeLengthAllocated(attr)];
ReadAttribute(attr,bitmap);
ULONGn=AttributeLength(FindAttribute(MFT,AttributeData,0))/BytesPerFileRecord;
PFILE_RECORD_HEADERfile=PFILE_RECORD_HEADER(newUCHAR[BytesPerFileRecord]);
for(ULONGi=0;i<n;i++){
if(!bitset(bitmap,i))continue;
ReadFileRecord(i,file);
if(file->Ntfs.Type==''''ELIF''''&&(file->Flags&3)){
attr=FindAttribute(file,AttributeFileName,0);
if(attr==0)continue;
PFILENAME_ATTRIBUTEname
=PFILENAME_ATTRIBUTE(Padd(attr,PRESIDENT_ATTRIBUTE(attr)->ValueOffset));
printf("%8lu%.*ws\n",i,int(name->NameLength),name->Name)
}
}
}
本文引用GaryNebbett的些定义可能对Windows2000版本有些很小的出入,不过Internet有其神奇的地方,虽然Microsoft不提供这些信息,但诸如linux-ntfsGNU工程等均是学习NTFS的一个很好的资料,本文也参考了很多它提供的文档。另外MarkRussinovich的《InsideWin2KNTFS》、《InsideNTFS》、《ExploringNTFSOn-diskStructures》等也是很好的NTFS资料。本文仍未涉及NTFS中目录的组织(B+树)等等,可能的话我会另行介绍。文中介绍的完整代码可到下载。出现的错误也欢迎来信指教(tsu00@)!
最后感谢AntonAltaparmakov,感谢我的同事在出差时抽空给我买到GaryNebbett的书。感谢我看到的所有资料的原作者们。感谢他们!
参考资料:
1.GaryNebbett《WindowsNT/2000NativeAPIReference》
2.Linux-NTFSProjectNTFSDocumentationVersion0.4
3.MarkRussinovich相关文档
4.DavidSolomom《InsideWindowsNT,2ndEdition》
- 上一篇:计算机毕业论文:消息钩子函数入门篇
- 下一篇:计算机毕业论文:监视、控制计算机的使用