NTFS中的$MFT详解 ntfs权限详解

NTFS是Windows NT引入的新型文档系统,他具备许多新特性。本文旨在探索NTFS的底层结构,所叙述的也仅是文档在NTFS卷上的分布。NTFS中,卷中任何存放的数据均在一个叫$MFT的文档中,叫主文档表(Master File Table)。而$MFT则由文档记录(File Record)数组构成。File Record的大小一般是固定的,通常情况下均为1KB,这个概念相当于Linux中的inode。File Record在$MFT文档中物理上是连续的,且从0开始编号。$MFT仅供File System本身组织、架构文档系统使用,这在NTFS中称为元数据(Metadata)。以下列出Windows 2000 Release出的NTFS的元数据文档(我将要给出的示例代码的部分输出结果)。

File Record(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

Windows 2000中不能使用dir命令(甚至加上/ah参数)像普通文档相同列出这些元数据文档。实际上File System Driver(ntfs.sys)维护了一个系统变量NtfsProtectSystemFiles用于隐藏这些元数据。默认情况下,这个变量被设为TRUE,所以使用dir /ah将得不到任何文档。知道这个行为后使用i386kd修改NtfsProtectSystemFiles后即能够列出元数据文档:

kd> x ntfs!NtfsProtect*

fe213498 Ntfs!NtfsProtectSystemFiles

fe21349c Ntfs!NtfsProtectSystemAttributes

kd> dd ntfs!NtfsProtectSystemFiles l 2

fe213498 00000001 00000001

kd> ed ntfs!NtfsProtectSystemFiles 0

kd> dd ntfs!NtfsProtectSystemFiles l 2

fe213498 00000000 00000001

kd>

D:>ver

Microsoft Windows 2000 [Version 5.00.2195]

D:>dir /ah $*

驱动器 D 中的卷是 W2KNTFS

卷的序列号是 E831-9D04

D: 的目录

2000-04-27 19:31 36,000 $AttrDef

2000-04-27 19:31 0 $BadClus

2000-04-27 19:31 67,336 $Bitmap

2000-04-27 19:31 8,192 $Boot

2000-04-27 19:31 <DIR> $Extend

2000-04-27 19:31 13,139,968 $LogFile

2000-04-27 19:31 27,575,296 $MFT

2000-04-27 19:31 4,096 $MFTMirr

2000-04-27 19:31 131,072 $UpCase

2000-04-27 19:31 0 $Volume

9 个文档 40,961,960 字节

1 个目录 51,863,552 可用字节

需要指出的是ntfs.sys将元数据文档以一种特别的方式打开,所以在打开NtfsProtectSystemFiles后,假如使用ReadFile等产生IRP_MJ_READ等IRP包时将会导致Page Fault(详见Gary Nebbett的《Windows NT/2000 Native API Reference》)。

以上的讨论均是基于$MFT文档而讨论的,即基于$MFT中的File Record(inode)讨论的。为更好的继续以下的讨论,这儿我列出File Record Header的结构:

typedef struct {

NTFS中的$MFT详解 ntfs权限详解
ULONG Type;

USHORT UsaOffset;

USHORT UsaCount;

USN Usn;

} NTFS_RECORD_HEADER, *PNTFS_RECORD_HEADER;

typedef struct {

NTFS_RECORD_HEADER Ntfs;

USHORT SequenceNumber;

USHORT LinkCount;

USHORT AttributesOffset;

USHORT Flags; // 0x0001 = InUse, 0x0002 = Directory

ULONG BytesInUse;

ULONG BytesAllocated;

ULONGLONG BaseFileRecord;

USHORT NextAttributeNumber;

} FILE_RECORD_HEADER, *PFILE_RECORD_HEADER;

下面我将讨论如何定位$MFT。稍微有点操作系统知识的人都会知道引导扇区(Boot Sector),其物理位置为卷中的第一个扇区。以下由dskprobe.exe(Windows 2000 Resource Kit中的一个小工具)分析的第一个扇区(当然也能够使用WinHex等其他应用程式):

file: d:Sector00.bin

Size: 0x00000200 (512)

Address | 00 01 02 03-04 05 06 07 : 08 09 0A 0B-0C 0D 0E 0F | 0123456789ABCDEF

---------|-------------------------:-------------------------|-----------------

00000000 | EB 52 90 4E-54 46 53 20 : 20 20 20 00-02 08 00 00 | ?R?NTFS .....

00000010 | 00 00 00 00-00 F8 00 00 : 3F 00 F0 00-3F 00 00 00 | .....?..?.e.?...

00000020 | 00 00 00 00-80 00 80 00 : 90 C0 41 00-00 00 00 00 | ......惱A.....

00000030 | 04 00 00 00-00 00 00 00 : 09 1C 04 00-00 00 00 00 | ................

00000040 | F6 00 00 00-01 00 00 00 : 04 9D 31 E8-BB 31 E8 94 | ?.......?杌1钄

. .

. .

. .

000001F0 | 00 00 00 00-00 00 00 00 : 83 A0 B3 C9-00 00 55 AA | ........儬成..U?

这512字节为如下的格式:(摘自Gary Nebbett书中,本文许多代码均来自或参考此书。)

#pragma pack(push, 1)

typedef struct {

UCHAR Jump[3];

UCHAR Format[8];

USHORT BytesPerSector;

UCHAR SectorsPerCluster;

USHORT BootSectors;

UCHAR Mbz1;

USHORT Mbz2;

USHORT Reserved1;

UCHAR MediaType;

USHORT Mbz3;

USHORT SectorsPerTrack;

USHORT NumberOfHeads;

ULONG PartitionOffset;

ULONG Reserved2[2];

ULONGLONG TotalSectors;

ULONGLONG MftStartLcn;

ULONGLONG Mft2StartLcn;

ULONG ClustersPerFileRecord;

ULONG ClustersPerIndexBlock;

ULONGLONG VolumeSerialNumber;

UCHAR Code[0x1AE];

USHORT BootSignature;

} BOOT_BLOCK, *PBOOT_BLOCK;

#pragma pack(pop)

各个字段的周详意义从字段名中即可大致清楚。在linux-ntfs的GNU工程(http://sf.net/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分析):

Dump BootBlock at below:

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(Logical Cluster Number)来代表NTFS卷中的物理位置,其简单的从0到卷中的总簇数减一进行编号。对于一个特定的文档NTFS则使用VCN(Virtual Cluster Number)来映射LCN实现文档的组织。从MftStartLcn的值4能够知道$MFT的LCN为4和SectorsPerCluster、BytesPerSector的大小即可定位$MFT的位置。得到$MFT的位置后,假如遍历$MFT中任何的File Record即能够得到卷中任何的文档列表(前面已提到File Record只是简单的从0开始编号)。也就是说到现在为止已能够对文档组织有最简单的认识,但如何得到文档的信息呢,如文档名等等。NTFS中任何文档包括普通的用户文档、元数据文档均用同样的方式组织数据、属性等。我将nfi.exe(来自Windows NT/2000 OEM Support Tools)的输出结果列出,作为我叙述的开始:

D:>copy con file

testforntfs^Z

已复制 1 个文档。

D:>nfi d:file

NTFS File Sector Information Utility.

Copyright (C) Microsoft Corporation 1999. All rights reserved.

file

$STANDARD_INFORMATION (resident)

$FILE_NAME (resident)

$DATA (resident)

D:>echo testforattr>file:ATTR

D:>nfi d:file

NTFS File Sector Information Utility.

Copyright (C) Microsoft Corporation 1999. All rights reserved.

file

$STANDARD_INFORMATION (resident)

$FILE_NAME (resident)

$DATA (resident)

$DATA ATTR (resident)

nfi的输出结果$STANDARD_INFORMATION、$FILE_NAME、$DATA等在NTFS中称为属性(Attribute)。属性分为常驻属性(Resident Attribute)和很驻属性(Nonresident Attribute)。文档的数据也包含在属性中,似乎和属性这个名称有点混谣。但是这又让NTFS有了更加统一的组织文档的形式。这也同时让NTFS有MultiStreams的特性(上面也演示了这个特性)。通过指定的File Record定位给定的Attribute的实现代码如下:

template <class T1, class T2> inline

T1* Padd(T1* p, T2 n) { return (T1*)((char *)p n); }

PATTRIBUTE FindAttribute(PFILE_RECORD_HEADER file,

ATTRIBUTE_TYPE type, PWSTR name)

{

for (PATTRIBUTE attr = PATTRIBUTE(Padd(file, file->AttributesOffset));

attr->AttributeType != -1;

attr = Padd(attr, attr->Length)) {

if (attr->AttributeType == type) {

if (name == 0 && attr->NameLength == 0) return attr;

if (name != 0 && wcslen(name) == attr->NameLength

&& _wcsicmp(name, PWSTR(Padd(attr, attr->NameOffset))) == 0) return attr;

}

}

return 0;

}

Gary Nebbett提供的这个FindAttribute函数在Attribute name(即第三个参数)不为空串时可能会出现bug,主要原因是_wcsicmp对UNICODE字符串比较时应该是以结束的标准的C字符串。我在提供的代码中已纠正了这个错误。

下面我将通过使用SoftICE来分析这段代码得到$MFT的$FILE_NAME属性来得到$MFT的file name。这个示例同样适用于得到其他文档的$FILE_NAME(如上面的file)、更有其他的属性如$DATA等等。

:bpx FindAttribute

Break due to BPX FindAttribute (ET=6.89 seconds)

:locals

[EBP-4] struct ATTRIBUTE * attr = 0x00344D68 <{...}>

[EBP 8] struct FILE_RECORD_HEADER * file = 0x00344D38 <{...}>

[EBP C] enum ATTRIBUTE_TYPE type = AttributeFileName (30)

[EBP 10] unsigned short * name = 0x004041BC <"$MFT">

:?file

struct FILE_RECORD_HEADER * = 0x00344D38 <{...}>

struct NTFS_RECORD_HEADER Ntfs = {...}

unsigned short SequenceNumber = 0x1, "x01"

unsigned short LinkCount = 0x1, "x01"

unsigned short AttributesOffset = 0x30, "0"

unsigned short Flags = 0x1, "x01"

unsigned long BytesInUse = 0x2D8, "x02xD8"

unsigned long BytesAllocated = 0x400, "x04"

unsigned quad BaseFileRecord = 0x0, ""

unsigned short NextAttributeNumber = 0x6, "x06"

file参数我传入的是$MFT,从$MFT的LCN=4能够得到其在卷中的物理地址,这在上面已说明。您也能够使用dskprobe(我机子中为第LCN*SectorsPerCluster=4*8扇区)得到底下SoftICE的输出结果:

:dd @file //以下的注释可对照文中开头列出的FILE_RECORD_HEADER定义。

0023:00344D38 454C4946 0003002A 6D4AC04D 00000000 FILE*...M.Jm....

0023:00344D48 00010001 00010030 000002D8 00000400 ....0...........

----

|__AttributeOffset

0023:00344D58 00000000 00000000 04340006 0000FA0D ..........4.....

0023:00344D68 00000010 00000060 00180000 00000000 ....`...........

-------- --------

| |_指出这个Attribute的长度。定义如下。

|_根据AttributeOffset得到的Attribute头,定义如下。00000010指出这个Attribute为StandardInformation

0023:00344D78 00000048 00000018 2C1761D0 01BFB03C H........a.,<...

Attribute头如下定义:

typedef struct {

ATTRIBUTE_TYPE AttributeType;

ULONG Length;

BOOLEAN Nonresident;

UCHAR NameLength;

USHORT NameOffset;

USHORT Flags; // 0x0001 = Compressed

USHORT AttributeNumber;

} ATTRIBUTE, *PATTRIBUTE;

typedef struct {

ATTRIBUTE Attribute;

ULONG ValueLength;

USHORT ValueOffset;

USHORT Flags; // 0x0001 = Indexed

} RESIDENT_ATTRIBUTE, *PRESIDENT_ATTRIBUTE;

typedef struct {

ULONGLONG DirectoryFileReferenceNumber;

ULONGLONG CreationTime; // Saved when filename last changed

ULONGLONG ChangeTime; // ditto

ULONGLONG LastWriteTime; // ditto

ULONGLONG LastAccessTime; // ditto

ULONGLONG AllocatedSize; // ditto

ULONGLONG DataSize; // ditto

ULONG FileAttributes; // ditto

ULONG AlignmentOrReserved;

UCHAR NameLength;

UCHAR NameType; // 0x01 = Long, 0x02 = Short

WCHAR Name[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:00344DC8 00000030 00000068 00180000 00030000 0...h...........

-------- ------

| |___这里的NameLength和NameOffset指FileNameAttribute名。不要和$MFT FileName混谣。

|_指出这是个FileNameAttribute。

0023:00344DD8 0000004A 00010018 00000005 00050000 J...............

-------- ---- --------

| | |_根据ValueOffset的值,得到FILENAME_ATTRIBUTE的具体位置。

| |_ValueOffset值

|_ValueLength值

0023:00344DE8 2C1761D0 01BFB03C 2C1761D0 01BFB03C .a.,<....a.,<...

0023:00344DF8 2C1761D0 01BFB03C 2C1761D0 01BFB03C .a.,<....a.,<...

0023:00344E08 00004000 00000000 00004000 00000000 .@.......@......

0023:00344E18 00000006 00000000 00240304 0046004D ..........$.M.F.

-- --------

| |___找到$MFT的FileName了吧。

|_NameLength

0023:00344E28 00000054 00000000 00000080 00000190 T...............

0023:00344E38 00400001 00010000 00000000 00000000 ..@.............

这儿给出了Dump Attribute的一个具体方法。最后我将给出遍历File Record的代码,在给出代码前应该说明一下$MFT中$BITMAP属性。NTFS的这个Attribute相当于LINUX EXT2的s_inode_bitmap数组(Linux 2.0版本)。所以很容易明白$BITMAP的作用,即每bit指出相应File Record的在用情况。以下是DumpAllFileRecord的代码:

BOOL bitset(PUCHAR bitmap, ULONG i)

{

return (bitmap[i >> 3] & (1 << (i & 7))) != 0;

}

VOID DumpAllFileRecord()

{

PATTRIBUTE attr = FindAttribute(MFT, AttributeBitmap, 0);

PUCHAR bitmap = new UCHAR[AttributeLengthAllocated(attr)];

ReadAttribute(attr, bitmap);

ULONG n = AttributeLength(FindAttribute(MFT, AttributeData, 0)) / BytesPerFileRecord;

PFILE_RECORD_HEADER file = PFILE_RECORD_HEADER(new UCHAR[BytesPerFileRecord]);

for (ULONG i = 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_ATTRIBUTE name

= PFILENAME_ATTRIBUTE(Padd(attr, PRESIDENT_ATTRIBUTE(attr)->ValueOffset));

printf("%8lu %.*wsn", i, int(name->NameLength),name->Name)

}

}

}

本文引用Gary Nebbett的些定义可能对Windows 2000版本有些很小的出入,但是Internet有其神奇的地方,虽然Microsoft不提供这些信息,但诸如linux-ntfs GNU工程等均是学习NTFS的一个很好的资料,本文也参考了很多他提供的文档。另外Mark Russinovich的《Inside Win2K NTFS》、《Inside NTFS》、《Exploring NTFS On-disk Structures》等也是很好的NTFS资料。本文仍未涉及NTFS中目录的组织(B 树)等等,可能的话我会另行介绍。文中介绍的完整代码可到http://webcrazy.yeah.net下载。出现的错误也欢迎来信指教(tsu00@263.net)!

最后感谢Anton Altaparmakov,感谢我的同事在出差时抽空给我买到Gary Nebbett的书。感谢我看到的任何资料的原作者们。感谢他们!

参考资料:

1.Gary Nebbett《Windows NT/2000 Native API Reference》

2.Linux-NTFS Project NTFS Documentation Version 0.4

3.Mark Russinovich相关文档

4.David Solomom《Inside Windows NT,2nd Edition》

  

爱华网本文地址 » http://www.413yy.cn/a/25101014/190436.html

更多阅读

命令提示符cmd 中的tracert命令详解 精 tracert 返回值详解

命令提示符(cmd)中的tracert命令详解 精——简介tracert也被称为Windows路由跟踪实用程序,在命令提示符(cmd)中使用tracert命令可以用于确定IP数据包访问目标时所选择的路径。本文主要探讨了tracert命令的各个功能。命令提示符(cmd)中的tr

沈卫国:康托对角线法中的逻辑问题详析4

康托对角线法中的逻辑问题详析(4)沈卫国八、康托定理与康托对角线法的同构性分析康托定理在集合论中的地位毋庸讳言。其与对角线法的关系,早被论及。但笔者一直似未见具体分析。笔者早年的著作中,对此曾有分析。笔者甚至怀疑,有人也许

股票中的KDJ线应该如何看? 股票kdj指标详解

问大家,股票中的KDJ线应该如何看?最佳答案下面是我从百度知道copy而来,其实你也可以查查的.---------随机指标KDJ使用入门KDJ指标的中文名称是随机指数,最早起源于期货市场。KDJ指标的应用法则KDJ指标是三条曲线,

声明:《NTFS中的$MFT详解 ntfs权限详解》为网友冷月寒光分享!如侵犯到您的合法权益请联系我们删除