计算机毕业论文:探索NTFS

时间:2022-11-17 10:21:00

计算机毕业论文:探索NTFS

探索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》