Qt/C++ 了解NTFS文件系统,解析0x80 $Data属性,获取Run Lists数据列表

系列文章目录

整个专栏系列是根据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属性获取文件或目录的实际大小,名称,磁盘分配大小,记录号 ,文件属性等信息

目录导读


MFT元数据常见属性类型介绍

MFT元数据属性类型很多,这里以表格的形式简单介绍常见的数据类型。

MFT元数据常见属性类型介绍,参考:
NTFS - Attributes
File - $AttrDef (4)

NTFS文件系统详解->NTFS文件系统中的所有属性体的简介
关于NTFS-MFT

Type OS Name 名称 描述
0x10 $STANDARD_INFORMATION 标准信息 在旧版本的NTFS中,此属性仅包含DOS文件权限和文件时间。Windows 2000引入了四个新字段,用于引用配额、安全、文件大小和日志信息。正如在$AttrDef中定义的那样,这个属性的大小最小为48字节,最大为72字节。
0x20 $ATTRIBUTE_LIST 属性列表 当属性很多且MFT记录中的空间很短时,可以将所有非常驻属性移出MFT。如果仍然没有足够的空间,则需要使用 ATTRIBUTE_LIST** 属性。其余的属性放在一个新的MFT记录中,**ATTRIBUTE_LIST 描述在哪里可以找到它们。看到这个属性是非常不寻常的。
0x30 $FILE_NAME 文件名 此属性存储文件属性的名称,并且始终是常驻属性。正如在$AttrDef中定义的那样,这个属性的最小大小为68字节,最大大小为578字节。这相当于文件名的最大长度为255个Unicode字符。
0x40 NT $VOLUME_VERSION 在早期的NTFS c1.2中为卷版本
0x40 2K $OBJECT_ID 对象ID 对象Id是在Windows 2000中引入的。每个MFT记录被分配一个唯一的GUID。此外,一条记录可能有一个Birth Volume Id、一个Birth Object Id和一个Domain Id,它们都是guid。正如在$AttrDef中定义的那样,这个属性没有最小大小,但最大256字节。
0x50 $SECURITY_DESCRIPTOR 安全描述符 安全描述符
0x60 $VOLUME_NAME 卷名(卷标识) 该属性仅包含卷的名称。正如在$AttrDef中定义的那样,这个属性的大小最小为2字节,最大为256字节。这相当于卷名的最大长度为127个Unicode字符。
0x70 $VOLUME_INFORMATION 卷信息 卷的版本和状态。正如在$AttrDef中定义的那样,这个属性的最小和最大大小为12字节。
0x80 $DATA 文件数据 此属性包含文件的数据。文件的大小是其未命名数据流的大小。正如在$AttrDef中定义的那样,这个属性没有最小或最大大小。
0x90 $INDEX_ROOT 索引根 这是实现索引(例如目录)的B+树的根节点。这个文件属性始终是常驻的。
0xA0 $INDEX_ALLOCATION 索引分配 这是实现索引(例如目录)的B+树的所有子节点的存储位置。此文件属性始终是非常驻的。
0xB0 $BITMAP 位图 这个文件属性是一个比特序列,每个比特代表一个实体的状态。正如在$AttrDef中定义的那样,这个属性没有最小或最大大小。
0xC0 NT $SYMBOLIC_LINK 符号链接 符号链接
0xC0 2K $REPARSE_POINT 重解析点 正如在$AttrDef中定义的那样,这个属性没有最小大小,但最大为16384字节。
0xD0 $EA_INFORMATION 扩充属性信息 用于在NTFS下实现Windows NT服务器的OS/2信息子系统和OS/2客户端所使用的HPFS扩展属性。此文件属性可能是非常驻的,因为它的流可能会增长。正如在$AttrDef中定义的那样,这个属性的最小和最大大小为8字节。
0xE0 $EA 扩充属性 用于在NTFS下实现HPFS扩展属性。此文件属性可能是非常驻的,因为它的流可能会增长。正如在$AttrDef中定义的那样,这个属性没有最小大小,但最大为65536字节。
0xF0 NT $PROPERTY_SET 早期的NFT v1.2中才有
0x100 2K $LOGGED_UTILITY_STREAM EFS加密属性 正如在$AttrDef中定义的那样,这个属性没有最小大小,但最大为65536字节。

文件记录属性类型 宏定义示例:
#define MFT_FILERECORD_ATTR_STANDARD_INFO 0x10
#define MFT_FILERECORD_ATTR_ATTRIBUTE_LIST 0x20
#define MFT_FILERECORD_ATTR_FILENAME 0x30
#define MFT_FILERECORD_ATTR_OBJECT_ID 0x40
#define MFT_FILERECORD_ATTR_SECURITY_DESCRIPTOR 0x50
#define MFT_FILERECORD_ATTR_VOLUME_NAME 0x60
#define MFT_FILERECORD_ATTR_VOLUME_INFORMATION 0x70
#define MFT_FILERECORD_ATTR_DATA 0x80
#define MFT_FILERECORD_ATTR_INDEX_ROOT 0x90
#define MFT_FILERECORD_ATTR_INDEX_ALLOCATION 0xA0
#define MFT_FILERECORD_ATTR_BITMAP 0xB0
#define MFT_FILERECORD_ATTR_REPARSE_POINT 0xC0
#define MFT_FILERECORD_ATTR_EA_INFORMATION 0xD0
#define MFT_FILERECORD_ATTR_EA 0xE0
#define MFT_FILERECORD_ATTR_LOGGED_UTILITY_STREAM 0x100
#define MFT_FILERECORD_ATTR_STOP_TAG 0xFFFFFFFF

前言

根据获取的第一个MFT元数据的属性列表,获取到0x80 $Data属性 数据。

解析数据获取到Run List数据

有了Run List数据才能找到具体的文件MFT元数据。

了解Run List数据

当MFT元数据属性不能存放完数据,系统就会在NTFS数据区域开辟一个空间存放,这个区域是以簇为单位的。Run List就是记录这个数据区域的起始簇号和大小。

NTFS系统中的Run List数据是什么:

在NTFS(New Technology File System)系统中,Run List数据是一个重要的概念,它用于描述文件或数据属性在磁盘上的存储位置和大小。具体来说,Run List记录了一个或多个数据流的起始簇号(Starting Cluster Number,SCN)和每个数据流的长度(即占用的簇数),这些信息对于定位和访问文件或数据属性的实际存储位置至关重要。

  • Run List通常由一个或多个Run组成,每个Run包含以下信息:
  • 起始簇号(SCN): 表示数据流在磁盘上的起始位置。这是一个簇的编号,用于定位数据流的第一个簇。
  • 长度: 表示数据流占用的簇数。这是一个整数,用于指定从起始簇号开始连续占用的簇的数量。

摘要出自:文言一心

  • 定义Run Lists数据结构

开源项目NTFS-File-Search中Run List的数据结构:

cpp 复制代码
/* Cluster info of Non-Residental Attributes (Data runs)-- 非居住属性聚类信息(数据运行) */
typedef struct MFT_DATARUN
{
    //! 长度(以簇为单位)
    UINT64	Length;
    //! 起始簇号
    INT64	Offset;
}*PMFT_DATARUN;

解析0x80 $Data属性获取 Run List数据

以获取的第一个MFT元数据为例,获取到的0x80属性数据:

按照前文定义的 PMFT_ATTRIBUTE_HEADER 结构可以判断为非常驻属性,获取 MFT_NONRESIDENT_ATTRIBUTE_HDR 结构中的 DataRunOffset 字段值即可或得Run Lists 数据偏移地址。

按字节计算,也就是从第32字节开始,占2字节数据为 40 00H 换算成十进制为64,即从64为开始到 00 为Run Lists 数据偏移地址。即【33 20 C8 00 00 00 0C ... ... ... ... 5C 72 00 00 00】都为Run Lists 数据,以 00 结束。

  • 解析Run Lists数据 :

第一个字节 33 是压缩字节,高位和低位相加,3 + 3 = 6,

表示这个Data Run信息占用6个字节,即

20 C8 00 00 00 0C

其中高位表示起始簇号占用多少个字节,低位表示大小占用的字节数。在这里,起始簇号占用3个字节,为00 00 0C,大小占用3个字节,为 20 C8 00。

解析后,得到这个数据流起始簇号为786432,大小为51232簇。(小端序计算(little endian))

  • 如图示 :

包含获取到五个Run List数据;

解析runlist数据参考:
NTFS文件系统详解(三)之NTFS元文件解析 : 分析80H属性
关于NTFS-MFT : (3)80H属性$DATA

代码示例:

参考开源项目NTFS-File-Search中获取Run list数据的示例:

cpp 复制代码
UINT64 GetDataRuns(PMFT_ATTRIBUTE_HEADER pAttribute, MFTDataRunList *prgDataRuns)
 {
     if (!pAttribute || !prgDataRuns)
     {
         return 0;
     }

     prgDataRuns->clear();

     VCN_t nStartVCN	= MFT_NONRESIDENT_ATTR(pAttribute).StartVCN;
     VCN_t nLastVCN		= MFT_NONRESIDENT_ATTR(pAttribute).LastVCN;
     VCN_t nCurrentVCN	= 0;

     qDebug()<<"[DataRunOffset] :" <<QString::number(MFT_NONRESIDENT_ATTR(pAttribute).DataRunOffset,10);

     PBYTE pbCurrent = POINTER_ADD(PBYTE, pAttribute, MFT_NONRESIDENT_ATTR(pAttribute).DataRunOffset);
     do
     {
         MFT_DATARUN DataRun = { 0 };

         /*!
          * 在C或C++中,表达式 *pbCurrent & 0xF 执行了位与(bitwise AND)操作。这里,*pbCurrent 是解引用指针 pbCurrent,即获取该指针指向的 BYTE(或等效的8位无符号整数)值。& 是位与操作符,而 0xF 是一个十六进制数,等价于二进制的 00001111。
          * 位与操作符 & 对其两边的操作数进行逐位比较,只有在两个相应的位都为1时,结果的该位才为1,否则为0。因此,*pbCurrent & 0xF 的作用是将 *pbCurrent 的低4位保留下来,而将高4位清零。
          * 例如:
          * 如果 *pbCurrent 的值是 0xF3(二进制 11110011),
          * 那么 *pbCurrent & 0xF 的结果是 0x3(二进制 00000011),
          * 因为只有低4位(0011)与 0xF(00001111)的对应位都为1,所以结果保留了这些位,而高4位被清零。
          * 这种操作通常用于提取或掩码特定的位字段。在这个例子中,它用于提取 *pbCurrent 值的低4位。
          */
         int LengthSize = *pbCurrent & 0xF;

         /*!
          * 在C或C++中,PBYTE是一个指向BYTE类型的指针,其中BYTE通常是一个无符号字符类型,用于表示8位(1字节)的数据。表达式*pbCurrent >> 4执行了两个操作:
          * pbCurrent:这是解引用操作,它获取指针pbCurrent指向的值。换句话说,它获取了pbCurrent指向的内存地址中存储的BYTE值。
          * >> 4:这是一个位右移操作,它将*pbCurrent的值向右移动4位。位右移操作通常用于从位字段中提取值或将数值除以2的幂。在这个上下文中,它将BYTE值的高4位移动到低4位的位置,同时丢弃原始的低4位。
          * 例如,如果*pbCurrent的值是0xF3(二进制11110011),那么*pbCurrent >> 4的结果是0xF(二进制1111),因为原始值向右移动了4位,丢弃了最低的4位(0011),保留了最高的4位(1111)。
          * 这种操作通常用于处理位字段或执行特定的位操作任务,如提取、修改或组合数据。
          */
         int OffsetSize = *pbCurrent >> 4;
         if ((LengthSize < 1 || LengthSize > 8) || (OffsetSize < 1 || OffsetSize > 8))
         {
             prgDataRuns->clear();
             return 0;
         }
         ++pbCurrent;

         //打印 长度
         QString Lengsizestr="";
         for(int i=0;i<LengthSize;i++)
             Lengsizestr+=QString(" %1").arg(pbCurrent[i],2,16,QLatin1Char('0')).toUpper();
         qDebug()<<"LengthSize: "<<Lengsizestr;

         CopyMemory(&DataRun.Length, pbCurrent, LengthSize);

         pbCurrent += LengthSize;
         if (pbCurrent[OffsetSize - 1] & 0x80) {
             DataRun.Offset = -1;
         }

         //打印 偏移量
         QString OffsetSizestr="";
         for(int i=0;i<OffsetSize;i++)
             OffsetSizestr+=QString(" %1").arg(pbCurrent[i],2,16,QLatin1Char('0')).toUpper();
         qDebug()<<"OffsetSize: "<<OffsetSizestr;
         CopyMemory(&DataRun.Offset, pbCurrent, OffsetSize);

         prgDataRuns->push_back(DataRun);

         nCurrentVCN += DataRun.Length;
         pbCurrent	+= OffsetSize;
     } while (nCurrentVCN <= nLastVCN);

     return prgDataRuns->size();
 }
/* 输出的数据
[DataRunOffset] : "64"
LengthSize:  " 20 C8 00"
OffsetSize:  " 00 00 0C"
LengthSize:  " B3 C8 00"
OffsetSize:  " 8D 90 98 00"
LengthSize:  " 4E D8 00"
OffsetSize:  " E8 72 97 00"
LengthSize:  " 0A C8 00"
OffsetSize:  " 01 87 1C FF"
LengthSize:  " D5 78"
OffsetSize:  " B8 5C 72"
*/
相关推荐
全貌10 分钟前
C++笔记 --基本语法(命名空间/函数重载/缺省参数/引用/inline/nulltpr)
开发语言·c++·笔记
斯派的曼14 分钟前
学习C++的第七天!
开发语言·c++·学习
hhhcbw25 分钟前
C++ STL容器(二) —— list 底层剖析
c++·链表·list·msvc·c++容器
Leaf Ye1 小时前
Android常用C++特性之std::move
c++
Ddddddd_1581 小时前
C++ | Leetcode C++题解之第432题全O(1)的数据结构
c++·leetcode·题解
Prejudices1 小时前
C++编程测试网络带宽
开发语言·c++
John_ToDebug2 小时前
设计模式之门面(Facade)模式
c++·设计模式·设计规范
wkd_0072 小时前
【Qt | QList 】QList<T> 容器详细介绍和例子代码
开发语言·qt·qlist·qlist 详细介绍·qlist 使用总结
AVICCI3 小时前
C++ 基础
开发语言·c++