65.9K
CodeProject 正在变化。 阅读更多。
Home

NTFS 解析器库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (42投票s)

2010年5月15日

BSD

16分钟阅读

viewsIcon

219039

downloadIcon

12706

一个C++库,用于帮助解析NTFS卷、文件记录和属性。

引言

这是一个用于帮助解析NTFS卷、文件记录和属性的库。读者应具备NTFS和C++编程的深入知识。

我不会在这里介绍NTFS概念,因为介绍要么内容庞杂,要么空洞无物。请在此搜索关于NTFS的最佳文档

作为一个操作系统爱好者,我对自己对文件系统的了解甚少感到羞愧。每次阅读与操作系统相关的书籍时,我总是在“文件系统”章节感到困惑。内容要么过于简洁,不足以深入理解,要么过于枯燥,令人难以继续阅读。于是我决定编写一些短小的代码来了解我的硬盘上发生了什么。我选择了NTFS,因为它是我电脑上的文件系统,而且几乎每个人都说它是一个好的设计,至少不是一个坏的设计。

起初,这非常痛苦,因为可用的文档非常少。微软并没有公开其所谓的“新一代文件系统”。只能在网上找到零散的信息。经过几天的研究收集到的文档,我头脑中的迷雾逐渐散去。经过一些成功的测试,我认为可以编写一个库来方便NTFS解析,同时也加深我的理解。

Windows NT试图构建一个面向对象的操作系统。起初,我在选择使用C++类还是传统的C过程来完成任务时犹豫不决。作为操作系统的重要组成部分,它应该高效、紧凑,并具有可伸缩性和可管理性。操作系统内核必须用C编写。但我正在编写一个用户模式库,在仔细研究了NTFS数据结构后,我决定使用C++类来封装它们。

NTFS是一个先进的日志文件系统,能够满足从家用PC到数据服务器的需求。我尚未实现其所有功能。以下部分尚未支持:

  1. 日志记录
  2. 安全
  3. 加密和压缩
  4. 其他一些高级功能

演示项目

1. ntfsundel

其目的是搜索和恢复已删除的文件。

这似乎是一项艰巨的任务,但通过使用这个库,我花不到一个小时就实现了它,大部分时间都花在了调整对话框界面上。当然,这更像一个简单的测试程序,而不是商业产品。我没有检查已释放的簇是否已被其他文件修改(这也是商业工具分析大卷时花费大量时间的原因之一)。

NTFSParseLib/ntfsundel.JPG

2. ntfsdump

转储文件的前16K。由于该库直接从磁盘扇区读取数据,我们可以绕过操作系统保护,查看通常无法访问的文件,例如位于“Windows\System32\config”目录下的文件。

NTFSParseLib/ntfsdump.JPG

3. ntfsdir

列出子文件和目录。

NTFSParseLib/ntfsdir.JPG

4. ntfsattr

列出文件或目录的属性。

NTFSParseLib/ntfsattr.JPG

源代码

1. 源文件

源代码包含五个.h文件。在编写C++代码时,我更倾向于直接在头文件中编写,因为这大大简化了部署,而且看起来也很酷。只需包含.h文件即可完成所有工作,无需将.cpp文件添加到项目中。该库是您自己源代码的一部分,未引用的库源代码会被编译器默默丢弃。当然,用这种方式实现一个大型系统会很困难,当类之间相互引用时。我不知道微软ATL是如何实现这一目标的。

1. NTFS.h

在您的源代码中包含此文件。不需要其他包含。

2. NTFS_DataType.h

NTFS通用数据结构和数据类型定义。没有类,只有结构。

3. NTFS_Common.h

特定于此库的NTFS数据结构和数据类型定义。以及一个单一的列表实现CSList,用于帮助管理相同类型的对象。

4. NTFS_FileRecord.h

NTFS卷和文件记录类定义和实现。

5. NTFS_Attribute.h

NTFS属性类和辅助类定义和实现。

2. 编码

我作为嵌入式系统设计师已经有大约十年的经验,习惯于有限的系统资源并挖掘硬件的全部潜力(试想一下在8位CPU上以2MIPS运行、只有256字节RAM的情况下实现一个IP堆栈)。如今的PC,RAM和CPU速度已不再是问题,但我仍然保持编写紧凑、尽可能高效和快速运行的代码的习惯。

为了实现这个目标,该库中的许多数据缓冲区在不同对象之间共享。为了完成不同的任务,玩弄指针是必须的,尽管危险。C++通过引入构造函数、析构函数以及复制构造函数来帮助我们管理内存,但这还不够。否则,就不会有所谓的“智能指针”,它只是C++风格的指针技巧(当然,如果你不够“聪明”,它会导致难以发现的“智能”错误)。

我正试图使这个库比简单的测试更有用。源代码和演示项目是在VC6.0 SP6中开发的,也可以在VC10.0中编译。二进制文件已在Windows XP SP3和Windows7中测试。我添加了许多跟踪消息,将在Visual Studio的输出窗口中显示,以帮助调试。该库兼容Unicode,可以编译成ANSI或Unicode二进制文件。定义_UNICODE以进行Unicode构建。就像NT内核一样,NTFS使用Unicode存储文件名。因此,Unicode构建的运行速度将比ANSI构建快。所有已通过或返回的指针和引用,如果不应被目标修改,都被标记为“const”。如果我们试图修改这些缓冲区或对象,编译器会发出警告(但我经常通过类型转换为非const指针来违反自己的规则)。我还添加了验证代码,以防止坏参数和错误数据。处理磁盘卷时,你越小心越好。

该库频繁读取磁盘扇区。因此,我将维护一些缓冲区来加快数据访问。尽管操作系统已经通过磁盘缓存帮助了我们,用户模式缓冲区将是额外的优势。

由于它直接访问磁盘扇区,您必须拥有管理员权限才能运行演示项目。在Windows7中,仅获得管理员权限还不够;需要提升的特权。您必须是“Administrator”用户或获得提升的特权才能成功打开卷。此库以只读模式访问磁盘;它**应该**是安全的,并且不会损坏您的磁盘卷。请自行承担使用风险。

NTFS卷和文件记录类

1. CNTFSVolume

此类封装了一个单独的NTFS卷。

volume是卷名;例如:'C'、'D'。这是唯一的构造函数。它执行以下操作:

用户应在构造函数后立即调用此函数来验证一切是否正常。如果此函数返回FALSE,则不应进行任何其他处理。

返回此卷中的文件记录数。它不是所有当前文件和目录的总和,因为已删除的文件可能仍占用记录槽。

磁盘物理扇区的大小(字节)。通常为512。从BPB获取。

单个文件记录的大小(字节)。通常为1024。从BPB获取。

索引块的大小(字节)。通常为4096。从BPB获取。

$MFT元文件的相对起始地址。从BPB获取。

返回值:成功时为TRUE。当attrType不是有效属性类型时为FALSE

安装一个卷范围回调函数,以便在找到特定属性时被调用。可用于在属性流被处理之前查看其原始流。

移除所有卷范围回调函数。

  1. CNTFSVolume(_TCHAR volume)
    1. 以只读模式打开卷,并获取一个句柄以直接访问磁盘的物理扇区。
    2. 读取BPB,进行一些验证,并存储所需信息。
    3. 解析NTFS元文件$Volume,读取并验证NTFS版本。
    4. 解析NTFS元文件$MFT,获取其$DATA属性以定位$MFT中其他文件记录。NTFS试图通过保留$MFT后的某些缓冲区来保持文件记录的连续性。但在我的八年旧笔记本电脑上,$MFT在系统卷中被分割成三个部分。
  2. BOOL IsVolumeOK() const
  3. ULONGLONG GetRecordsCount() const
  4. DWORD GetSectorSize() const
  5. DWORD GetFileRecordSize() const
  6. DWORD GetIndexBlockSize() const
  7. ULONGLONG GetMFTAddr() const
  8. BOOL InstallAttrRawCB(DWORD attrType, ATTR_RAW_CALLBACK cb)
    • attrType:属性类型。
    • cb:回调函数。
  9. void ClearAttrRawCB()

2. CFileRecord

解析单个文件记录。这是最重要的类。NTFS将几乎所有东西都视为文件,甚至包括引导扇区。

volume表示此文件记录所属的卷。

fileRef是要解析的文件的文件引用。

返回值:成功时为TRUE。否则为FALSE。当此函数失败时,不应进行任何进一步的处理。

此函数从磁盘读取文件记录,然后验证并修补更新序列号。用户可以逐个解析任意数量的文件。先前解析的数据将被释放。

解析文件记录的选定属性(由SetAttrMask()例程选择)。这是库中最大且最耗时的例程。所有选定的属性都将被解析为相应的C++对象,并按类型插入单独的列表中。

返回值:成功时为TRUE。当attrType无效时为FALSE

安装一个文件记录范围回调函数,以便在找到特定属性时被调用。可用于在属性流被处理之前查看其原始流。

ParseAttrs()找到一个属性时,它会首先在CFileRecord中查找已安装的回调函数并调用它。如果找不到,它将继续查找此文件记录所属的CNTFSVolume对象中安装的回调函数。

移除所有文件记录范围回调函数。

mask包含要解析的属性。在NTFS_Common.h中定义为MASK_???

用户可以选择要解析的属性,并丢弃不需要的属性以节省时间和RAM。例如,如果您只想获取文件大小和时间戳,则无需花费时间解析$DATA属性。$STANDARD_INFORMATION$ATTRIBUTE_LIST将始终被解析,无论它们是否被选中,但$ATTRIBUTE_LIST中不需要的属性将被丢弃。

此函数应在ParseAttrs()之前调用。

此例程遍历文件记录的所有已解析属性,并同步调用用户定义的函数,向用户提供属性的已解析C++对象。

此例程应在ParseAttrs()之后调用。

查找包含类型“attrType”的第一个属性。如果找不到类型为“attrType”的属性,则返回NULL。调用后,内部索引将移至第一个元素。

此例程应在ParseAttrs()之后调用。

查找包含类型“attrType”的下一个属性。如果找不到更多类型为“attrType”的属性,则返回NULL。调用后,内部索引将移至下一个。

此例程应在FindFirstAttr()之后调用。

CAttrBase *ab = FindFirstAttr(ATTR_TYPE_FILENAME)
while (ab)
{
    // process ab here
    ab = FindNextAttr(ATTR_TYPE_FILENAME);
}

MFC的CFileFind类设计得非常糟糕且容易出错,所以我没有遵循它的风格。

返回值

一个文件记录可能具有多个文件名($FILE_NAME属性)。将返回第一个Win32名称。

以字节为单位获取文件大小。从$FILE_NAME属性获取。

获取文件的最后修改时间、创建时间和最后访问时间。时间已转换为系统中设置的时区。从$STANDARD_INFORMATION属性获取。

遍历文件记录(目录文件)中所有子条目,并同步调用用户定义的函数,向用户提供由CIndexEntry类封装的所有子条目。在枚举子文件和目录时很有用。必须已解析$INDEX_ROOT$INEX_ALLOCATION属性(请参阅SetAttrMask())。

返回值:找到时为TRUE,否则为FALSE

它用于查找子文件或目录。必须已解析$INDEX_ROOT$INEX_ALLOCATION属性(请参阅SetAttrMask())。

name是文件数据流的名称。未命名流时为NULL

通过名称查找特定的数据流。NTFS文件可能具有多个数据流($DATA属性)。文件内容始终位于未命名流中。必须已解析$DATA属性(请参阅SetAttrMask())。

检查此文件记录是否已被删除。

检查此文件记录是否为目录。

检查它是只读文件。从$STANDARD_INFORMATION属性获取。

检查它是隐藏文件。从$STANDARD_INFORMATION属性获取。

检查它是系统文件。从$STANDARD_INFORMATION属性获取。

检查它是压缩文件。从$STANDARD_INFORMATION属性获取。

检查它是加密文件。从$STANDARD_INFORMATION属性获取。

检查它是稀疏文件。从$STANDARD_INFORMATION属性获取。

  1. CFileRecord(const CNTFSVolume *volume)
  2. BOOL ParseFileRecord(ULONGLONG fileRef)
  3. BOOL ParseAttrs()
  4. BOOL InstallAttrRawCB(DWORD attrType, ATTR_RAW_CALLBACK cb)
    • attrType:属性类型。
    • cb:回调函数。
  5. void ClearAttrRawCB()
  6. void SetAttrMask(DWORD mask)
  7. void TraverseAttrs(ATTRS_CALLBACK attrCallBack, void *context)
    • attrCallBack:用户定义的函数。
    • context:要传递给回调函数的上下文。
  8. const CAttrBase* FindFirstAttr(DWORD attrType) const
  9. const CAttrBase* FindNextAttr(DWORD attrType) const
  10. int GetFileName(_TCHAR *buf, DWORD bufLen) const
    • buf:用于保存返回的文件名的缓冲区。
    • bufLen:缓冲区大小(字符数,不是字节数!)。
    • 大于0:文件名的长度(字符数)。
    • 等于0:此文件未命名。
    • 小于0:缓冲区大小小于文件名的所需大小,负值表示所需的缓冲区大小。例如,返回值为-20表示您需要一个至少20个字符大小的缓冲区。
  11. ULONGLONG GetFileSize() const
  12. void GetFileTime(FILETIME *writeTm, FILETIME *createTm = NULL, FILETIME *accessTm = NULL) const
  13. void TraverseSubEntries(SUBENTRY_CALLBACK seCallBack) const
  14. const BOOL FindSubEntry(const _TCHAR *fileName, CIndexEntry &ieFound) const
    • fileName:要查找的子文件名。
    • ieFound:找到的CIndexEntry对象。
  15. const CAttrBase* FindStream(_TCHAR *name = NULL)
  16. BOOL IsDeleted() const
  17. BOOL IsDirectory() const
  18. BOOL IsReadOnly() const
  19. BOOL IsHidden() const
  20. BOOL IsSystem() const
  21. BOOL IsCompressed() const
  22. BOOL IsEncrypted() const
  23. BOOL IsSparse() const

NTFS属性类

Attributes               Class

$STANDARD_INFORMATION    CAttr_StdInfo
$ATTRIBUTE_LIST          CAttr_AttrList<TYPE_RESIENT>
$FILE_NAME               CAttr_FileName
$VOLUME_NAME             CAttr_VolName
$VOLUME_INFORMATION      CAttr_VolInfo
$DATA                    CAttr_Data<TYPE_RESIDENT>
$INDEX_ROOT              CAttr_IndexRoot
$INDEX_ALLOCATION        CAttr_IndexAlloc
$BITMAP                  CAttr_Bitmap<TYPE_RESIENT>

NTFS属性分为驻留属性(CAttrResident)和非驻留属性(CAttrNonResident)。驻留和非驻留属性共享一个通用头(CAttrBase)。所有属性类都派生自CAttrResidentCAttrNonResident,它们又派生自CAttrBase。某些属性,如$DATA$ATTRIBUTE_LIST,可以是驻留或非驻留的;这些类使用模板参数作为它们的基类。

1. CAttrBase

所有属性类的基类。

allocSize是数据分配的大小(字节)。如果您不想要,请将此参数留空。

返回值:数据的实际大小(字节)。

获取此属性数据的大小(字节)。它被声明为纯虚函数。派生类CAttrResidentCAttrNonResident将实际实现此函数。感谢C++引入的多态性,通过此函数和下面的ReadData()函数,驻留和非驻留属性可以使用相同的接口访问其数据,尽管它们差异很大。

返回值:成功时为TRUE,否则为FALSE

将属性数据读取到缓冲区中。

__inline const ATTR_HEADER_COMMON* GetAttrHeader() const
__inline DWORD GetAttrType() const
__inline DWORD GetAttrTotalSize() const
__inline BOOL IsNonResident() const
__inline WORD GetAttrFlags() const
int GetAttrName(char *buf, DWORD bufLen) const
int GetAttrName(wchar_t *buf, DWORD bufLen) const

获取属性名称。返回值遵循与CFileRecord::GetFileName()相同的规则。

__inline BOOL IsUnNamed() const

检查此属性是否未命名。

  1. CAttrBase(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr)
    • ahc:指向属性头缓冲区。
    • fr:拥有此属性的文件记录。
  2. virtual __inline ULONGLONG GetDataSize(ULONGLONG *allocSize = NULL) const = 0
  3. virtual BOOL ReadData(const ULONGLONG &offset, void *bufv, DWORD bufLen, DWORD *actural) const = 0
    • offset:读取指针相对于开头的起始地址(字节)。
    • bufv:用户提供的用于接收数据的缓冲区。
    • bufLen:用户提供的缓冲区大小(字节)。
    • actural:实际读取的数据大小。抱歉拼写错误。现在微软Word告诉我了,但我太懒了,无法在我的源代码中查找和替换所有错误。我建议微软在Visual Studio中添加拼写检查,以帮助我们这些非英语母语的人,呵呵。
  4. 其他导出例程

2. CAttrResident

 

所有驻留属性类的基类。

实现驻留属性特有的虚拟函数GetDataSize()ReadData()

3. CAttrNonResident

所有非驻留属性类的基类。实现虚拟函数GetDataSize()ReadData(),这些函数是为非驻留属性设计的。它比CAttrResident的实现要复杂得多,因为它应该解析数据运行并构建一个列表来保存信息。我认为NTFS数据运行不是一个好的设计,因为节省的磁盘空间无法弥补浪费的解析时间。

4. CAttr_StdInfo

NTFSParseLib/stdinfo.JPG

实现$STANDARD_INFORMATION属性。派生自CAttrResident。导出函数:

void GetFileTime(FILETIME *writeTm, 
     FILETIME *createTm = NULL, FILETIME *accessTm = NULL) const
__inline DWORD GetFilePermission() const
__inline BOOL IsReadOnly() const
__inline BOOL IsHidden() const
__inline BOOL IsSystem() const
__inline BOOL IsCompressed() const
__inline BOOL IsEncrypted() const
__inline BOOL IsSparse() const

5. CAttr_FileName

NTFSParseLib/filename.JPG

实现$FILE_NAME属性。派生自CAttrResidentCFileName辅助类。

所有有用的函数都位于CFileName基类中,该类将在后面介绍。位于$FILE_NAME属性中的文件权限和时间仅在文件名更改时更新,因此从CFileName派生的相关函数在CAttr_FileName中被声明为“private”,以防止用户获取错误的信息。$STANDARD_INFORMATION和索引条目保存更新的文件权限和时间戳。

6. CAttr_VolInfo

NTFSParseLib/volinfo.JPG

实现$VOLUME_INFORMATION属性。派生自CAttrResident。导出函数:

__inline WORD GetVersion()

返回NTFS卷版本。高字节保存主版本,低字节保存次版本。在Windows XP和Windows7中,NTFS版本是3.1,Windows 2000是3.0,Windows NT是1.2。版本低于3.0的NTFS卷不受此库支持。

7. CAttr_VolName

NTFSParseLib/volname.JPG

实现$VOLUME_NAME属性。派生自CAttrResident

导出函数:

__inline int GetName(wchar_t *buf, DWORD len) const
__inline int GetName(char *buf, DWORD len) const

获取Unicode或ANSI卷名。返回值遵循与CFileRecord::GetFileName()相同的规则。

8. CAttr_Data

NTFSParseLib/data.JPG

实现$DATA属性。派生自模板类,该模板类是CAttrResidentCAttrNonResident

GetDataSize()ReadData()是从模板基类派生的。我们在处理$DATA属性时只需要这两个函数。

9. CAttr_IndexRoot

NTFSParseLib/indexroot.JPG

实现$INDEX_ROOT属性。派生自CAttrResidentCIndexEntryList辅助类。所有有用的函数都位于CIndexEntryList持有的CIndexEntry对象中,该对象将在后面介绍。

10. CAttr_IndexAlloc

NTFSParseLib/indexalloc.JPG

实现$INDEX_ALLOCATION属性。派生自CAttrNonResident

11. CAttr_Bitmap

NTFSParseLib/bitmap.JPG

实现$BITMAP属性。派生自模板类,该模板类是CAttrResidentCAttrNonResident

12. CAttr_AttrList

NTFSParseLib/attrlist.JPG

实现$ATTRIBUTE_LIST属性。派生自模板类,该模板类是CAttrResidentCAttrNonResident

这是最复杂的属性处理,因为它涉及文件记录和所有其他属性。但实现简洁,代码量少。

用户无需关心此属性;所有解析的子属性都将被插入到父文件记录的属性列表中,就好像它们直接包含在同一个文件记录中一样。

助手类

1. CFileName

此类帮助CAttr_FileNameCIndexEntry处理文件名相关信息。

导出函数:

int Compare(const wchar_t *fn) const
int Compare(const char *fn) const

将文件名与输入字符串进行比较。如果匹配则返回0,如果文件名小于输入字符串则返回负值,否则返回正值。此例程用于在索引根和索引分配构建的B+树中搜索特定文件。

__inline ULONGLONG GetFileSize() const
__inline DWORD GetFilePermission() const
__inline BOOL IsReadOnly() const
__inline BOOL IsHidden() const
__inline BOOL IsSystem() const
__inline BOOL IsDirectory() const
__inline BOOL IsCompressed() const
__inline BOOL IsEncrypted() const
__inline BOOL IsSparse() const
int GetFileName(char *buf, DWORD bufLen) const
int GetFileName(wchar_t *buf, DWORD bufLen) const

获取Unicode或ANSI文件名。返回值遵循与CFileRecord::GetFileName()相同的规则。

__inline BOOL HasName() const

检查它是否包含文件名或未命名。

__inline BOOL IsWin32Name() const

无法放入DOS 8.3格式的文件名将具有DOS别名。例如,Win32名称“C:\Program files”将具有DOS兼容文件名“C:\Progra~1”。使用此函数检查它是否包含合法的Win32名称。

void GetFileTime(FILETIME *writeTm, FILETIME *createTm = NULL, 
                 FILETIME *accessTm = NULL) const

2. CIndexEntry

此类封装了单个文件名索引条目。它派生自CFileName,并且所有CFileName导出函数都可以直接使用。

导出函数:

__inline ULONGLONG GetFileReference() const

获取此索引条目的文件引用。

__inline BOOL IsSubNodePtr() const

检查索引条目是否指向子节点。这些条目将不同的索引块链接成一个B+树。

__inline ULONGLONG GetSubNodeVCN() const

使用此函数定位子节点索引块。

3. CIndexBlock

此类有助于将单个索引块解析为CIndexEntry列表。

© . All rights reserved.