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