Qt/C++ 了解NTFS文件系统,遍历Run Lists数据列表,读取0x30 $FILE_NAME属性,获取所有文件/目录数据

系列文章目录

整个专栏系列是根据GitHub开源项目NTFS-File-Search获取分区所有文件/目录列表的思路。

具体的如下:

  1. Qt/C++ 了解NTFS文件系统,了解MFT(Master File Table)主文件表(一)
    介绍NTFS文件系统,对比通过MFT(Master File Table)主文件表获取数据的优劣,简单介绍开源项目NTFS-File-Search,以及了解借鉴NTFS系统中所参考的所有文章。
  2. Qt/C++ 了解NTFS文件系统,解析盘符引导扇区数据获取MFT(Master File Table)主文件表偏移地址
    读取Boot引导分区扇区数据,获取单个簇大小(4096字节),MFT元数据大小(1024字节)和MFT元数据的起始簇号,计算出MFT元数据在磁盘的偏移地址。
  3. Qt/C++ 了解NTFS文件系统,获取首张MFT表数据,解析文件记录头内容找到第一个属性偏移地址
    解析MFT元数据结构,根据MFT元数据文件记录头获取属性的偏移地址,用于后面解析0x80 $Data属性,获取Run List数据列表
  4. Qt/C++ 了解NTFS文件系统,解析MFT主文件表中的常驻属性与非常驻属性
    简单介绍$MFT元数据中的常驻属性与非常驻属性结构.
  5. Qt/C++ 了解NTFS文件系统,解析0x80 $Data属性,获取Run List数据列表
    根据0x80 Data属性,找到存放所有MFT元数据的区间列表(Run List数据列表)
  6. **Qt/C++ 了解NTFS文件系统,遍历Run Lists数据列表,读取0x30 FILE_NAME属性,获取所有文件/目录数据** 根据前面获取的 获取Run List数据列表, 遍历所有Run List数据列表读取所有MFT元数据,并解析 FILE_NAME属性和STANDARD_INFORMATION属性获取文件或目录的实际大小,名称,磁盘分配大小,记录号 ,文件属性等信息

目录导读

  • 系列文章目录
  • 前言
  • [遍历Run Lists数据列表](#遍历Run Lists数据列表)
  • [MFT元数据 属性结构解析](#MFT元数据 属性结构解析)
    • [0x10 $STANDARD_INFORMATION属性](#0x10 $STANDARD_INFORMATION属性)
    • [0x30 $FILE_NAME属性](#0x30 $FILE_NAME属性)
    • [获取文件/目录相关内容 代码示例](#获取文件/目录相关内容 代码示例)
  • 总结

前言

根据前面获取的 获取Run List数据列表 遍历读取所有MFT元数据,再根据0x30 $FILE_NAME属性0x10 $STANDARD_INFORMATION属性获取NTFS系统中的所有文件和目录数据。

遍历Run Lists数据列表

以开源项目NTFS-File-Search中的方法为例:

已知一个簇包含8个扇区,每个扇区512字节,

一个簇8*512=4096个字节,

每个MFT表元数据1024字节(前面根据$boot表得到的数据);

一个簇包含4个MFT元数据
遍历每个Run Lists数据:

定义每次读取 #define FILE_RECORDS_PER_FILE_BUF 65536个簇 的磁盘数据,不足 65536个簇 的读取剩余字节数据,并按 一个MFT元数据大小(1024字节) 循环解析读取的磁盘数据。

直到读取完这一个Run List(MFT_DATARUN )数据

再读取下一个Run List(MFT_DATARUN )数据,

直到所有数据全部读取完.

  • 代码示例

m_ullRecordSize ☞每个MFT表大小 - 1024字节
m_ullClusterSize☞ 每个簇大小 -8*512字节

cpp 复制代码
//! 开始读取每个文件记录
DWORD cbFileRecordBuffer = FILE_RECORDS_PER_FILE_BUF * m_ullRecordSize;
UINT64 ullVolumeOffset=0;
pbFileRecordBuffer=new BYTE[cbFileRecordBuffer];
     for (size_t i = 0; i < m_rgDataRuns.size(); ++i)
{
    //UINT64 nRemainingFiles = RunLength * m_pVolume->ClusterSize() / m_pVolume->RecordSize();
	
	//! 当前run list 列表长度
    VCN_t RunLength = m_rgDataRuns[i].Length;
    //! 偏移量 -以簇为单位
    ullVolumeOffset += m_rgDataRuns[i].Offset;
    qDebug()<<"";
    qDebug()<<"[i] "<<QString::number(i,10)<<" [Offset] "<<QString::number(ullVolumeOffset,10)<<" [Length] "<<QString::number(m_rgDataRuns[i].Length,10);
    /* Read the files in chunks */
    //! 读取(当前run list 列表长度* 簇大小)字节后结束
    for (UINT64 nChunkOffset = 0; nChunkOffset < RunLength * m_ullClusterSize;)
    {
        /* Determine the number of files to read */
        //! 每次读取的大小 -固定65536* 4096字节大小
        UINT64 cbReadLength = cbFileRecordBuffer;
        if (RunLength * m_ullClusterSize - nChunkOffset < cbReadLength) {
        	//! 读取几次固定65536* 4096字节后,剩下的数据
            cbReadLength = RunLength * m_ullClusterSize - nChunkOffset;
        }
        qDebug()<<"[cbReadLength]: "<<QString::number(cbReadLength,16)<<" -> "<<QString::number(cbReadLength,10);


        /* Read the file records from the volume */
        //! ullVolumeOffset * m_ullClusterSize + nChunkOffset 相对偏移量
        //! 开始读取磁盘数据
        DWORD cbBytesRead = ReadBytes(m_hVolume,pbFileRecordBuffer, cbReadLength, ullVolumeOffset * m_ullClusterSize + nChunkOffset);
        if (!cbBytesRead) {
            break;
        }
		
		//! 计算此次读取了多少字节,算作偏移量
        nChunkOffset += cbReadLength;
		
		//! 解析MFT元数据
        ParseRecordChunk(pbFileRecordBuffer,cbReadLength);

    }
}

MFT元数据 属性结构解析

参考 :
NTFS文件系统详解(三)之NTFS元文件解析 : 分析30H属性
属性-$文件名(0x30)

两篇文章,解析 STANDARD_INFORMATION** 属性和 **FILE_NAME属性获取文件/目录相关内容


0x10 $STANDARD_INFORMATION属性

0x10 $STANDARD_INFORMATION 属性存储标准信息,包括一些基本文件属性,如只读,系统,存档;时间属性,如文件的创建和最后修改时间;有多少目录指向该文件(即其硬链接)

在旧版本的NTFS中,这个属性只包含DOS文件权限和文件时间。

Windows 2000引入了四个新字段,用于引用配额、安全性、文件大小和日志记录信息。

$AttrDef,此属性的中所定义,最小大小为48字节,最大大小为72字节。

  • 数据结构:
  • C++结构体声明示例:
cpp 复制代码
/*
* $MFT Standard Information Attribute - $MFT标准信息属性
* http://inform.pucp.edu.pe/~inf232/Ntfs/ntfs_doc_v0.5/attributes/standard_information.html
* https://flatcap.github.io/linux-ntfs/ntfs/attributes/standard_information.html
* 部分字段不一致,但是属性前面字段是一样的。
*/
typedef struct MFT_STANDARD_INFORMATION_ATTRIBUTE_HDR
{
    //! 8字节
    LONGLONG	CreationTime;   //C Time - File Creation
    //! 8字节
    LONGLONG	ChangeTime;     //A Time - File Altered
    //! 8字节
    LONGLONG	LastWriteTime;  //M Time - MFT Changed
    //! 8字节
    LONGLONG	LastAccessTime; //R Time - File Read
    //! 4字节
    ULONG		FileAttributes; //DOS File Permissions(Also called attributes in DOS terminology.)
    //! 4字节
    ULONG		Unknown[3];
    //! 4字节
    ULONG		QuotaId;
    //! 4字节
    ULONG		SecurityId;
    //! 8字节
    ULONGLONG	QuotaChange;
    //! 8字节
    USN			Usn;
}*PMFT_STANDARD_INFORMATION_ATTRIBUTE_HDR;

偏移0×20处的文件属性解释如下:

标志 常用字母缩写 (WizTree软件中的文件属性就是这个缩写) 名称 描述
0x0001 R 只读(Read-only) 表示文件只能被读取,不能被修改或删除。
0x0002 H 隐藏(Hidden) 表示文件或文件夹是隐藏的,通常不会在文件浏览器中显示。
0x0004 S 系统(System) 表示文件或文件夹是系统文件或系统文件夹,这些通常是操作系统的一部分。
0x0020 A 存档(Archive) 表示文件可以被备份程序标记为需要备份
0x0040 设备文件 设备文件
0x0080 常规文件 常规文件
0x0100 临时文件 临时文件
0x0200 L 稀疏文件(Sparse File) 表示文件具有稀疏性,即文件中存在大量的空白区域,这些空白区域在磁盘上并不实际占用空间。
0x0400 O 重解析点(Reparse Point) 表示文件是一个重解析点,如符号链接或快捷方式等。
0x0800 C 压缩(Compressed) 表示文件已经被压缩以节省磁盘空间。
0x1000 脱机文件 脱机文件
0x2000 I 未编入索引 孤立(Indexed) 表示文件不包含任何索引信息,主要用于NTFS文件系统中,影响文件的搜索速度。
0x4000 E 加密(Encrypted) 表示文件使用了EFS(加密文件系统)进行了加密。
  • 文件属性宏定义示例:

// MFT File 属性- $STANDARD_INFORMATION (MFT_FILERECORD_ATTR_STANDARD_INFO)

// https://flatcap.github.io/linux-ntfs/ntfs/attributes/standard_information.html
#define MFT_STANDARD_INFORMATION_ATTR_R 0x0001
#define MFT_STANDARD_INFORMATION_ATTR_H 0x0002
#define MFT_STANDARD_INFORMATION_ATTR_S 0x0004
#define MFT_STANDARD_INFORMATION_ATTR_A 0x0020
#define MFT_STANDARD_INFORMATION_ATTR_L 0x0200
#define MFT_STANDARD_INFORMATION_ATTR_O 0x0400
#define MFT_STANDARD_INFORMATION_ATTR_C 0x0800
#define MFT_STANDARD_INFORMATION_ATTR_I 0x2000
#define MFT_STANDARD_INFORMATION_ATTR_E 0x4000


0x30 $FILE_NAME属性

0x30 $FILE_NAME 属性 用于存储文件名 ,用Unicode字符表示的文件名,由于MS DOS 不能识别长文件名,所以NTFS系统会自动生成一个8.3文件名,

并且始终是常驻属性

正如在$AttrDef中定义的那样,这个属性的最小大小为68字节,最大大小为578字节。这相当于文件名的最大长度为255个Unicode字符。

  • 数据结构:
  • C++ 结构体的定义:
cpp 复制代码
/*
* $FILE_NAME Attribute Header Layout - 属性头布局
* https://flatcap.github.io/linux-ntfs/ntfs/attributes/file_name.html
*/
typedef struct MFT_FILENAME_ATTRIBUTE_HDR
{
    MFT_FILE_ID	ParentReference;	// File reference to the parent directory -对父目录的文件引用
    ULONGLONG	CreationTime;		// File creation time - 文件创建时间
    ULONGLONG	ModiciationTime;	// File altered time - 文件修改时间
    ULONGLONG	MFTTime;			// MFT changed time - MFT改变时间
    ULONGLONG	ReadTime;			// File read time - 文件读取时间
    ULONGLONG	AllocatedSize;		// Allocated size of the file -已分配的文件大小
    ULONGLONG	RealSize;			// Real size of the file - 文件的实际大小
    DWORD		Flags;				// Flags - 标志
    DWORD		ER;
    BYTE		NameLength;			// Filename length in characters - 文件名长度(以字符为单位)
    BYTE		NameSpaceType;		// File namespace type - 文件命名空间类型
    WCHAR		Name[1];			// Filename - 文件名
}*PMFT_FILENAME_ATTRIBUTE_HDR;

获取文件/目录相关内容 代码示例

参考开源项目NTFS-File-Search的示例:

在每次解析MFT元数据时,都需要先执行ApplyFixup 方法修正数据。

在开源项目NTFS-File-Search是直接声明了一整块内存供数据处理使用

我这里为了方便,每条数据都是单独处理的,由系统声明内存,尤其是文件名的读取直接复制的内存,

这里是将文件名同样保存到了两个字段。

cpp 复制代码
//! 数据结构体
typedef struct NTFS_FILE_ENTRYW
{
    qint64		AllocatedSize;		// File size on disk (bytes)
    qint64		RealSize;		// File size on disk (bytes)
    qint64		FileAttributes;
    qint64		NextEntryOffset;
    ULONGLONG   FileUpdateTime;
    qint64      NumberFolders;               // 文件夹数量
    qint64      NumberDocuments;             // 文件数量
    MFT_FILE_ID	MFTFileId;				// $MFT Record Number
    UINT64		ParentDirectoryRecord;	// Parent Directory $MFT-Record Number
    BOOL		IsDirectory;
    LPWSTR      Name;                   // filename/directory nane
    LPWSTR		lpszFileName;			// Full path of file/directory
}*PNTFS_FILE_ENTRYW;

//按照1024字节解析 MFT元数据
void ParseRecordChunk(PBYTE pbRecordChunk, UINT64 cbRecordChunk)
{
    for (UINT64 iRecord = 0; iRecord < cbRecordChunk / m_ullRecordSize; ++iRecord)
    {
       PMFT_FILE_RECORD_HEADER m_pFileRecord=POINTER_ADD(PMFT_FILE_RECORD_HEADER, pbRecordChunk, iRecord * m_ullRecordSize);
       //! 文件是否在使用
       if (!(m_pFileRecord->Flags & MFT_FILERECORD_FLAG_IN_USE))
       {
           continue;
       }


       ApplyFixup(m_pFileRecord);
       RecordAttrMultiMap mpAttributes = GetAttributes(m_pFileRecord);
       auto FileNamesList = mpAttributes.equal_range(MFT_FILERECORD_ATTR_FILENAME);
       /* Find a valid (non-DOS file-name namespace) $FILE_NAME attribute */
       for (auto i = FileNamesList.first; i != FileNamesList.second; ++i)
       {


           PMFT_FILENAME_ATTRIBUTE_HDR pFileName = POINTER_ADD(PMFT_FILENAME_ATTRIBUTE_HDR,
               i->second,
               i->second->Resdient.AttributeOffset
           );


           if (pFileName && pFileName->NameSpaceType != FILENAME_NAMESPACE_DOS)
           {
               NTFS_FILE_ENTRY FileEntry;
               //
               // Set the file entry info
               //
               FileEntry.AllocatedSize              = pFileName->AllocatedSize;
               FileEntry.RealSize                   = pFileName->RealSize;
               FileEntry.NumberFolders              = 0;
               FileEntry.NumberDocuments            = 0;
               FileEntry.FileUpdateTime             = pFileName->ModiciationTime;
               FileEntry.FileAttributes             = 0;
               FileEntry.MFTFileId.MftRecordIndex	= m_pFileRecord->RecordNumber;
               FileEntry.MFTFileId.SequenceNumber   = m_pFileRecord->SequenceNumber;
               FileEntry.IsDirectory			    = (m_pFileRecord->Flags & MFT_FILERECORD_FLAG_IS_DIRECTORY) ? TRUE : FALSE;
               FileEntry.ParentDirectoryRecord      = pFileName->ParentReference.MftRecordIndex;
               FileEntry.Name  =new WCHAR[pFileName->NameLength];
               CopyMemory(FileEntry.Name , pFileName->Name, pFileName->NameLength * sizeof(WCHAR));
               FileEntry.Name[pFileName->NameLength] = L'\0';

               FileEntry.lpszFileName  =new WCHAR[pFileName->NameLength];
               CopyMemory(FileEntry.lpszFileName , pFileName->Name, pFileName->NameLength * sizeof(WCHAR));
               FileEntry.lpszFileName[pFileName->NameLength] = L'\0';

               if (FindAttribute(m_pFileRecord,MFT_FILERECORD_ATTR_STANDARD_INFO))
               {
                   PMFT_STANDARD_INFORMATION_ATTRIBUTE_HDR pStandardInfo = POINTER_ADD(
                       PMFT_STANDARD_INFORMATION_ATTRIBUTE_HDR,
                       FindAttribute(m_pFileRecord,MFT_FILERECORD_ATTR_STANDARD_INFO),
                       FindAttribute(m_pFileRecord,MFT_FILERECORD_ATTR_STANDARD_INFO)->Resdient.AttributeOffset
                   );
                   FileEntry.FileAttributes = pStandardInfo->FileAttributes;
               }
               /* Check if file should be skipped */
               if (QString::fromWCharArray(FileEntry.lpszFileName)=="")
               {
                   break;
               }
               /* Add entry to map */
               if (FileEntry.IsDirectory) {
                   m_DirectoryMap[m_pFileRecord->RecordNumber] = FileEntry;

                   NumberFolders++;
               }
               else {
                   m_FileMap[m_pFileRecord->RecordNumber] = FileEntry;
                   NumberDocuments++;
               }
                break;
           }
       }

    }
}

总结

通过开源项目NTFS-File-Search

获取到的数据会出现数据大小不一致,会多出/缺少部分文件的问题.

参考 「NTFS:让你的硬盘更安全、更高效!」NTFS文件系统详解,一文中遍历分区文件列表的思路,

发现是没有读取索引属性(IndexEntries列表 )不知道是不是这个原因,后面看NTFS-File-Search的源码作者会不会再次优化。

整个了解NTFS文件系统示例到此完毕。

后面还有不了解的内容建议直接参考开源NTFS-File-Search项目源码。

相关推荐
南东山人1 小时前
一文说清:C和C++混合编程
c语言·c++
Ysjt | 深4 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__4 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
Microsoft Word4 小时前
c++基础语法
开发语言·c++·算法
一只小小汤圆4 小时前
opencascade源码学习之BRepOffsetAPI包 -BRepOffsetAPI_DraftAngle
c++·学习·opencascade
legend_jz5 小时前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
嘿BRE5 小时前
【C++】几个基本容器的模拟实现(string,vector,list,stack,queue,priority_queue)
c++
ö Constancy6 小时前
c++ 笔记
开发语言·c++
fengbizhe6 小时前
笔试-笔记2
c++·笔记