系列文章目录



前言

根据前面获取的 获取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字节

//! 开始读取每个文件记录
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属性

  • 数据结构:

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

  • C++结构体声明示例:
/*
* $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处的文件属性解释如下:

  • 文件属性宏定义示例:

0x30 $FILE_NAME属性

  • 数据结构:

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

  • C++ 结构体的定义:
/*
* $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;


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

//! 数据结构体
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项目源码。

09-24 10:29