课件站
课件站  

  小学 一年级上册 一年级下册 二年级上册 二年级下册 三年级上册 三年级下册 四年级上册 四年级下册 五年级上册 五年级下册 六年级上册 六年级下册

  初中 七年级上册 七年级下册 八年级上册 八年级下册 九年级上册 九年级下册  高中 语文 数学 英语 物理 化学 生物 地理 历史 政治   学前 幼儿园

 
当前位置: > 论文 > 高中信息技术论文 >
高中信息技术论文:探索NTFS    发布时间:2019-03-27 11:09:16
高中信息技术论文:探索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 {
        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.
探索NTFS


    \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字符串比较时应该是以\0结束的标准的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, "\0\x01"
     unsigned short LinkCount = 0x1, "\0\x01"
     unsigned short AttributesOffset = 0x30, "\00"
     unsigned short Flags = 0x1, "\0\x01"
     unsigned long BytesInUse = 0x2D8, "\0\0\x02\xD8"
     unsigned long BytesAllocated = 0x400, "\0\0\x04\0"
     unsigned quad BaseFileRecord = 0x0, "\0\0\0\0\0\0\0\0"
     unsigned short NextAttributeNumber = 0x6, "\0\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 %.*ws\n", 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 NebbettWindows NT/2000 Native API Reference
    2.Linux-NTFS Project NTFS Documentation Version 0.4
    3.Mark Russinovich相关文档
    4.David SolomomInside Windows NT,2nd Edition
 


【推荐浏览其它相关的优秀教育论文】

上一篇:随机型存储模型应用研究
下一篇:问题教学法在高中信息技术教学中的应用