系列文章目录

整个专栏系列是根据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属性获取文件或目录的实际大小,名称,磁盘分配大小,记录号 ,文件属性等信息
  7. Qt/C++ 深入了解NTFS文件系统,解析0x90 $INDEX_ROOT和0xA0 $INDEX_ALLOCATION 属性,获取索引(例如目录)的B+树的根节点和子节点
    解析x90 $INDEX_ROOT和0xA0 $INDEX_ALLOCATION 属性结构,获取索引(例如目录)的B+树的根节点和子节点,即可通过MFT RECORD ID定位到MFT RECORD表,加载当前MFT RECORD的子目录/文件数据.
  8. Qt/C++ 深入了解NTFS文件系统,解析0x20 $ATTRIBUTE_LIST,获取MFT RECORD记录表的其他扩展属性和ID对应的地址偏移量
    在尝试遍历NTFS文件系统中的所有文件目录时,发现包含很多子文件的文件夹,并没有0xA0 $INDEX_ALLOCATION 属性,只有个0x20 $ATTRIBUTE_LIST属性,于是通过解析x20 $ATTRIBUTE_LIST属性获取其他单个MFT RECORD表无法包含的扩展属性.并且通过扩展属性中的MFT RECORD ID找到属性所在的MFT RECORD记录表

杂谈

最近闲着没事,就想着干脆把之前了解的NTFS文件系统在深入了解一下,解决之前没处理的问题,然后再重新汇总做个笔记。

MFT元数据 属性结构解析

参考:
数据结构:
NTFS - Attributes
NTFS文件系统详解(三)之NTFS元文件解析
代码部分,主要还是在NTFS-File-Search基础上进行的增改,不熟悉的代码示例请参考NTFS-File-Search中的代码。

解析 0x90 $INDEX_ROOT属性

这是实现索引(例如目录)的B+树的根节点。这个文件属性总是常驻的。
如果每个索引记录的字节数小于簇大小,则每个索引记录的簇和所有文件的索引记录的vcn在当前扇区

$INDEX_ROOT属性 结构图示:

Created with Raphaël 2.3.0 标准属性头 索引根 索引头 索引项列表 属性结束? 0XFF FF FF FF 下一个属性 yes no
  • 数据结构定义:

索引根的结构:

偏移 大小 描述
~ ~ 标准属性头
0X00 4 属性类型
0X04 4 排序规则
0X08 4 索引项分配大小(子节)
0X0C 1 每索引记录的簇数
0X0D 3 填充(到8子节)

索引头的结构:

偏移 大小 描述
~ ~ 标准属性头
0X00 4 第一个索引项的偏移
0X04 4 索引项的总大小
0X08 4 索引项分配大小(子节)
0X0C 1 标志
0X0D 3 填充(到8子节)
0X0E ~ 索引体列表…
  • 索引根 索引头 C++结构体声明示例:
/*
 * https://flatcap.github.io/linux-ntfs/ntfs/attributes/index_root.html
 * 这是实现索引(例如目录)的B+树的根节点。这个文件属性总是常驻的。
 * $INDEX_ROOT (0x90) 索引根
*/
typedef struct MFT_INDEX_ROOT_HDR
{
    struct INDEX_ROOT_ENTRY {
        //! 4字节
         DWORD		Flags;          //属性类型
         //! 4字节
         DWORD      CollationRule;  //排序规则
         //! 4字节
         DWORD      RecordBytes;    //索引项分配大小
         //! 1字节
         BYTE       RecordClusters; //每索引记录的簇数
         //! 3字节 填充(到8字节)
         BYTE		Zeros3[3];      //填充
    } Root;
    struct INDEX_HEADER_ENTRY
    {
        //! 4字节
         DWORD		IndexEntryOffset;     //第一个索引项的偏移
         //! 4字节
         DWORD      RealSize;             //索引项的总大小
         //! 4字节
         DWORD      AllocationSize;       //索引项的分配大小
         //! 1字节
         BYTE       Mark;                //标志
         //! 3字节 填充(到8字节)
         BYTE		Zeros3[3];            //填充
    } Header;
    //! 索引项
    //INDEX_ENTRY IndexEntry;
    //INDEX_ENTRY IndexEntry;
    //INDEX_ENTRY IndexEntry;
    //INDEX_ENTRY IndexEntry;
}*PMFT_INDEX_ROOT_HDR;

索引体结构:
在这里插入图片描述

  • 索引体 C++结构体声明示例:

#define	INDEX_ENTRY_FLAG_SUBNODE	0x01	// Index entry points to a sub-node
#define	INDEX_ENTRY_FLAG_LAST		0x02	// Last index entry in the node, no Stream
////! 新补充 测试
////! https://blog.csdn.net/enjoy5512/article/details/50966009/
///* 索引项 */
typedef struct INDEX_ENTRY
{
     MFT_FILE_ID	MFTFileId;	     	// 文件的MFT参考号
     WORD		    Size;               // 索引项大小
     WORD		    FileNameOffset;	    // 文件名偏移
     WORD		    IndexFlags;         // 索引标志 0文件 为1表示包含子节点,为2表示最后一个项
     BYTE		    Zeros3[2];          // 填充(到8字节)
     MFT_FILE_ID	ParentReference;	// 父目录的MFT文件参考号
     ULONGLONG	    CreationTime;		// 文件创建时间
     ULONGLONG	    LastUpdateTime;	    // 最后修改时间
     ULONGLONG	    FileRecordLastTime;	// 文件记录最后修改时间
     ULONGLONG      LastAccessTime;		// 最后访问时间
     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 - 文件名
}*PINDEX_ENTRY;

实际属性数据结构 参考如下图示:

在这里插入图片描述
在这里插入图片描述

值得注意的是 在正常数据中 $INDEX_ROOT属性 中的索引项数据会出现在0XFF FF FF FF 结束后,并且此时的索引项IndexFlags数据为3,这是无效数据!

部分目录,会将子节点保存在$INDEX_ROOT属性中
如只有一个空文件夹的目录结构,或者只有一个文件的目录。
其他数据目录结构多在0xA0 $INDEX_ALLOCATION属性中

解析 0xA0 $INDEX_ALLOCATION属性

这是实现索引(例如目录)的B+树的所有子节点的存储位置。这个文件属性总是非常驻的。
索引数据都在DataRunList数据中,解析DataRunList数据可以参考:
Qt/C++ 了解NTFS文件系统,解析0x80 $Data属性,获取Run Lists数据列表
DataRunlist 数据都是由 标准索引头+索引项+索引项+索引项… 组成

标准索引头 定义:
在这里插入图片描述

  • C++结构体声明示例:
#define	INDEX_BLOCK_MAGIC		'XDNI'
//! A0H 标准索引头
typedef struct MFT_INDEX_ALLOCATION_HDR
{
    //! 4字节 总是INDX
    BYTE INDX[4];
    //! 2字节 更新序列号的偏移a
    WORD UpdateSequenceOffset;
    //! 2字节 更新序列号与更新数组以字为单位的大小
    WORD SizeOfUpdateSequence;
    //! 8字节 日志文件序列号
    ULONGLONG LogSequenceNumber;
    //! 8字节 本索引缓存在索引分配中的VCN
    VCN_t	IndexVCN;
    //! 4字节 索引项的偏移
    DWORD IndexEntryOffset;
    //! 4字节 索引项的大小
    DWORD IndexRealSize;
    //! 4字节 索引项分配大小;
    DWORD IndexAllocationSize;
    //! 1字节 如果不是叶节点,置1,表示还有子节点
    BYTE  ISRFD;
    //! 3字节 填充(到8字节)
    BYTE		Zeros3[3];            //填充
    // ....更新序列 更新序列数组
}*PMFT_INDEX_ALLOCATION_HDR;

数据获取 代码示例:

 qDebug()<<"ParseRecordIndex -->"<<ParentMftRecordIndex;
    //! 加载索引项
    //! 查找索引列表所在
    DWORD cbFileRecordBuffer = FILE_RECORDS_PER_FILE_BUF * m_ullRecordSize;
    UINT64 ullIndexsOffset=0;
    BYTE* pbIndexRecordBuffer=new BYTE[cbFileRecordBuffer];
    for (size_t i = 0; i < m_indexDataRuns.size(); ++i)
    {
        VCN_t RunLength = m_indexDataRuns[i].Length;
        ullIndexsOffset += m_indexDataRuns[i].Offset;
        for (UINT64 nChunkOffset = 0; nChunkOffset < RunLength * m_ullClusterSize;)
        {
            UINT64 cbReadLength = cbFileRecordBuffer;
            if (RunLength * m_ullClusterSize - nChunkOffset < cbReadLength) {
                cbReadLength = RunLength * m_ullClusterSize - nChunkOffset;
            }
            DWORD cbBytesRead = WinApi::ReadBytes(m_hVolume,pbIndexRecordBuffer, cbReadLength, ullIndexsOffset * m_ullClusterSize + nChunkOffset);
            if (!cbBytesRead) {
                break;
            }
            for (UINT64 iIndex = 0; iIndex < cbReadLength / m_ullBlockSize; ++iIndex)
            {
                PMFT_INDEX_ALLOCATION_HDR m_pIndexRecord=POINTER_ADD(PMFT_INDEX_ALLOCATION_HDR, pbIndexRecordBuffer,iIndex * m_ullBlockSize);
                //! 遍历索引项
                int Space=0x18;
                PINDEX_ENTRY Entry=POINTER_ADD(PINDEX_ENTRY,
                                               m_pIndexRecord,
                                               m_pIndexRecord->IndexEntryOffset+Space);
                do{
                    if (!Entry || POINTER_SUB(Entry, m_pIndexRecord) >= m_ullBlockSize)
                    {
                        break;
                    }
                    wchar_t* Name=new WCHAR[Entry->NameLength+1];
                    CopyMemory(Name , Entry->Name, Entry->NameLength * sizeof(WCHAR));
                    Name[Entry->NameLength] = L'\0';

                    QString NameStr=QString::fromWCharArray(Name);
                    delete []Name;
                    Name=nullptr;

                    NTFS_INDEX_ENTRY _entry;
                    _entry.MFTFileId.MftRecordIndex=Entry->MFTFileId.MftRecordIndex;
                    _entry.MFTFileId.SequenceNumber=Entry->MFTFileId.SequenceNumber;
                    _entry.ParentReference.MftRecordIndex=Entry->ParentReference.MftRecordIndex;
                    _entry.ParentReference.MftRecordIndex=Entry->ParentReference.MftRecordIndex;
                    _entry.AllocatedSize=Entry->AllocatedSize;
                    _entry.RealSize=Entry->RealSize;
                    _entry.IndexFlags=Entry->IndexFlags;
                    _entry.Name=NameStr;

                    if(Entry->IndexFlags==INDEX_ENTRY_FLAG_LAST)
                        break;

                    if(Entry->ParentReference.MftRecordIndex==ParentMftRecordIndex
                            && NameStr!=""
                            && Entry->MFTFileId.MftRecordIndex!=ParentMftRecordIndex)
                    {
                        indexentrys.insert(_entry.MFTFileId.MftRecordIndex,_entry);
                    }

                    Entry = POINTER_ADD(PINDEX_ENTRY, Entry, Entry->Size);

                }while (Entry->Size>0);
            }
            nChunkOffset += cbReadLength;
        }
    }


    if(pbIndexRecordBuffer!=nullptr && pbIndexRecordBuffer!=NULL)
    {
        delete [] pbIndexRecordBuffer;
        pbIndexRecordBuffer=nullptr;
    }

在$INDEX_ALLOCATION的索引列表中存在无文件名,无MFT RECORD ID的数据的异常数据,也包括当前节点的索引数据。获取数据的时候需要进行(去噪)处理

实际属性数据结构 参考如下图示:
在这里插入图片描述

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐