系列文章目录
整个专栏系列是根据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表中的属性数据结构,而每个MFT记录中的属性分为常驻属性和非常驻属性
当一个文件很小时,其所有属性体都可以存放在文件记录中,该属性就称为常驻属性。如果某个文件很大,1KB的文件记录无法记录所有属性时,则文件系统会在MFT元文件之外的区域(也称为数据流)存放该文件的其他文件记录属性,这些存放在非MFT元文件内的记录就称为非常驻属性
摘要出自:NTFS文件系统详解
每个属性都有一个属性头,这个属性头包含了一些该属性的重要信息,如属性类型,属性大小,名字,标志,标识,属性长度等,并且属性的种类有很多,属性体的含义也不同
属性结构 详细划分
在 Concept - Attribute Header一文中,将属性结构 详细的划分成了四种:
每个MFT记录中的每个属性都有一个标准标题。头存储关于属性的类型、大小、名称(可选)以及它是否是常驻的信息。
属性的大小取决于两个因素。它有名字吗?它是常驻的吗?为了简化表格,将全部显示所有四种可能性(其中一些值已经填写)。
通过游览器直接翻译:
偏移地址 | 字节大小 | 值 | 描述 |
---|---|---|---|
0x00 | 4 | 属性类型(例如0x10、0x60) | |
0x04 | 4 | 长度(包括此标题) | |
0x08 | 1 | 0x00 | 非居民旗帜 |
0x09 | 1 | 0x00 | 名称长度 |
0x0A | 2 | 0x00 | 名称的偏移量 |
0x0C | 2 | 0x00 | 旗帜(Flags: 压缩、加密、稀疏标志) |
0x0E | 2 | 属性Id (a) | |
0x10 | 4 | L | 属性的长度 |
0x14 | 2 | 0x18 | 属性的偏移量 |
0x16 | 1 | 索引标志 | |
0x17 | 1 | 0x00 | 填料 |
0x18 | L | 属性 |
(a) 是指每个属性都有一个唯一的标识符
偏移地址 | 字节大小 | 值 | 描述 |
---|---|---|---|
0x00 | 4 | 属性类型(例如0x90、0xB0) | |
0x04 | 4 | 长度(包括此标题) | |
0x08 | 1 | 0x00 | 非居民旗帜 |
0x09 | 1 | N | 名称长度 |
0x0A | 2 | 0x18 | 名称的偏移量 |
0x0C | 2 | 0x00 | 旗帜(Flags: 压缩、加密、稀疏标志) |
0x0E | 2 | 属性Id (a) | |
0x10 | 4 | L | 属性的长度 |
0x14 | 2 | 2N+0x18 | 属性的偏移量(b) |
0x16 | 1 | 索引标志 | |
0x17 | 1 | 0x00 | 填料 |
0x18 | 2N | 统一码 | 属性的名称 |
2N+0x18 | L | 属性(b) |
(a) 是指每个属性都有一个唯一的标识符
(b) 是指向上舍入到4字节的倍数
偏移地址 | 字节大小 | 值 | 描述 |
---|---|---|---|
0x00 | 4 | 属性类型(如0x20、0x80) | |
0x04 | 4 | 长度(包括此标题) | |
0x08 | 1 | 0x01 | 非居民旗帜 |
0x09 | 1 | 0x00 | 名称长度 |
0x0A | 2 | 0x00 | 名称的偏移量 |
0x0C | 2 | 旗帜(Flags: 压缩、加密、稀疏标志) | |
0x0E | 2 | 属性Id (a) | |
0x10 | 8 | 从VCN开始 | |
0x18 | 8 | 最后的VCN | |
0x20 | 2 | 0x40 | 数据运行的偏移 |
0x22 | 2 | 压缩单元大小(b) | |
0x24 | 4 | 0x00 | 填料 |
0x28 | 8 | 属性的分配大小(c ) | |
0x30 | 8 | 属性的实际大小 | |
0x38 | 8 | 流的初始化数据大小(d) | |
0x40 | … | 数据运行 |
(a) 是指每个属性都有一个唯一的标识符
(b) 是指压缩单元大小= 2x集群。0表示未压缩
(c ) 是指这是向上舍入到集群大小的属性大小
(d) 是指压缩数据的大小。
偏移地址 | 字节大小 | 值 | 描述 |
---|---|---|---|
0x00 | 4 | 属性类型(例如0x80、0xA0) | |
0x04 | 4 | 长度(包括此标题) | |
0x08 | 1 | 0x01 | 非居民旗帜 |
0x09 | 1 | N | 名称长度 |
0x0A | 2 | 0x40 | 名称的偏移量 |
0x0C | 2 | 旗帜(Flags: 压缩、加密、稀疏标志) | |
0x0E | 2 | 属性Id (a) | |
0x10 | 8 | 从VCN开始 | |
0x18 | 8 | 最后的VCN | |
0x20 | 2 | 2N+0x40 | 数据运行的偏移(b) |
0x22 | 2 | 压缩单元尺寸(c ) | |
0x24 | 4 | 0x00 | 填料 |
0x28 | 8 | 属性的分配大小(d) | |
0x30 | 8 | 属性的实际大小 | |
0x38 | 8 | 流的初始化数据大小(e) | |
0x40 | 2N | 统一码 | 属性的名称 |
2N+0x40 | … | 数据运行(二) |
(a) 是指每个属性都有一个唯一的标识符
(b) 是指向上舍入到4字节的倍数
(c ) 是指压缩单元大小= 2x集群。0表示未压缩
(d)是指 这是向上舍入到集群大小的属性大小
(e) 是指压缩数据的大小。
属性结构 非常驻属性和常驻属性
而NTFS文件系统详解一文中的属性结构划分,
更贴近开源项目NTFS-File-Search中的结构
定义 C++ 结构体
例如开源项目NTFS-File-Search中定义的结构体
typedef UINT64 VCN_t; /* Virtual Cluster Number */
typedef INT64 LCN_t; /* Logical Cluster Number */
/*
* Non-Resident Attribute Header Layout - 非常驻属性标头布局
* https://flatcap.github.io/linux-ntfs/ntfs/concepts/attribute_header.html#:~:text=Overview,attribute%20depends%20on%20two%20things.
*/
typedef struct MFT_NONRESIDENT_ATTRIBUTE_HDR
{
//!字节8 起始虚拟簇号VCN
VCN_t StartVCN; // Starting Virtual Cluster Number (VCN) - 起始虚拟集群编号(VCN)
//!字节8 结束虚拟簇号VCN
VCN_t LastVCN; // Last Virtual Cluster Number (VCN) - 最后的VCN
//!字节2 Data RUN的偏移地址
WORD DataRunOffset; // Starting offset of the Data Runs - 数据运行的偏移(向上舍入到4字节的倍数)
//!字节2 压缩单位的大小 2的N次方
WORD CompressionUnitSize; // 压缩单元尺寸(压缩单元大小= 2x集群。0表示未压缩)
//!字节4 不使用
DWORD Padding; // 填料
//!字节8 属性分配大小
ULONGLONG AllocatedSize; // 属性的分配大小(这是向上舍入到集群大小的属性大小)
//!字节8 属性的实际大小
ULONGLONG RealSize; // 属性的实际大小
//!字节8 属性的原始大小
ULONGLONG StreamDataSize; // 流的初始化数据大小(压缩数据的大小)
//! ---->DATA RUN信息
}*PMFT_NONRESIDENT_ATTRIBUTE_HDR;
/*
* Resident Attribute Header Layout - 常驻属性标头布局
* https://flatcap.github.io/linux-ntfs/ntfs/concepts/attribute_header.html#:~:text=Overview,attribute%20depends%20on%20two%20things.
*/
typedef struct MFT_RESIDENT_ATTRIBUTE_HDR
{
//!字节4 属性体长度
DWORD AttributeSize; // Length of the attribute body - 属性的长度
//!字节2 属性体开始的位置
WORD AttributeOffset; // Offset to the Attribute - 属性的偏移量
//!字节1 索引标志
BYTE IndexedFlag; // Indexed flag - 索引标志
//!字节1 填充
BYTE Padding; // Padding - 填料
//!---->属性体开始
}*PMFT_RESIDENT_ATTRIBUTE_HDR;
/*
* File-Record Attribute Header - 文件-记录属性头
* 每个MFT记录中的每个属性都有一个标准标题。头存储关于属性的类型、大小、名称(可选)以及它是否是常驻的信息
* https://flatcap.github.io/linux-ntfs/ntfs/concepts/attribute_header.html#:~:text=Overview,attribute%20depends%20on%20two%20things.
*/
typedef struct MFT_ATTRIBUTE_HEADER
{
//!字节4 属性类型
DWORD Type; // Attribute Type - 属性类型
//!字节4 8的整数倍 整个属性的长度
DWORD TotalSize; // 长度(包括此标题)
//!字节1 是否是常驻属性,00表示为常驻
BYTE FormCode; // 0 = Resident 1 = Non resident
//!字节1 属性名的长度 00表示没有属性名
BYTE NameLength; // Attribute name length - 名称长度
//!字节2 属性值开始的偏移/属性名开始的偏移
WORD NameOffset; // Attribute name offset - 名称的偏移量
//!字节2 压缩,加密,稀疏标志
WORD Flags;
//! 属性id
WORD AttributeId; // Unique Id - 属性Id
union
{
//! 常驻属性
MFT_RESIDENT_ATTRIBUTE_HDR Resdient;
//! 非常驻属性
MFT_NONRESIDENT_ATTRIBUTE_HDR NonResident;
};
}*PMFT_ATTRIBUTE_HEADER;
获取属性列表
通过上面的内容,大致了解了MFT元数据中的属性数据结构,
根据前文中的 文件记录头内容获取的 第一个属性流的偏移位置 ,就可以开始获取属性列表。
例如开源项目NTFS-File-Search中获取属性实际数据的源码示例,
在获取MFT元数据属性列表前,需要先对MFT元数据中的数据进行一个数据纠正,
实际数据测试时,从读取第5个MFT表数据时,每次获取的MFT表数据都存在部分字节数据不一致,如果不对数据进行处理,那么可能会直接读取超过1024字节的数据,造成数据异常。
void ApplyFixup(PMFT_FILE_RECORD_HEADER& header)
{
//应用修正(必须) --测试发现属性的长度会发生偏移
WORD wUpdateSize;
PWORD pUpdateSequence;
DWORD dwOffset;
wUpdateSize = header->SizeOfUpdateSequence;
pUpdateSequence = POINTER_ADD(PWORD, header, header->UpdateSequenceOffset);
dwOffset =m_ullSectorSize;
for (DWORD i = 1; i < wUpdateSize; i++)
{
if (dwOffset <= m_ullRecordSize)
{
((PWORD)header)[(dwOffset - 2) / sizeof(WORD)] = pUpdateSequence[i];
dwOffset +=m_ullSectorSize;
}
else {
break;
}
}
}
RecordAttrMultiMap GetAttributes(PMFT_FILE_RECORD_HEADER m_pFileRecord)
{
RecordAttrMultiMap mpAttributes;
PMFT_ATTRIBUTE_HEADER pCurAttribute = POINTER_ADD(PMFT_ATTRIBUTE_HEADER,
m_pFileRecord,
m_pFileRecord->FirstAttributeOffset
);
do
{
if (!pCurAttribute || POINTER_SUB(pCurAttribute, m_pFileRecord) >= m_ullRecordSize)
{
break;
}
qDebug()<<"输出 属性-->";
QString v="";
for(int i=0;i<pCurAttribute->TotalSize;i++)
{
v+=QString("%1 ").arg(((PBYTE)pCurAttribute)[i],2,16,QLatin1Char('0')).toUpper();
if((i+1)%16==0)
{
qDebug()<<v;
v="";
}
}
mpAttributes.insert(std::make_pair(pCurAttribute->Type, pCurAttribute));
pCurAttribute = POINTER_ADD(PMFT_ATTRIBUTE_HEADER, pCurAttribute, pCurAttribute->TotalSize);
} while (pCurAttribute->Type != MFT_FILERECORD_ATTR_STOP_TAG);
return mpAttributes;
}
/*打印输出
-->
"10 00 00 00 60 00 00 00 00 00 18 00 00 00 00 00 "
"48 00 00 00 18 00 00 00 A0 73 73 A7 99 E8 D7 01 "
"A0 73 73 A7 99 E8 D7 01 A0 73 73 A7 99 E8 D7 01 "
"A0 73 73 A7 99 E8 D7 01 06 00 00 00 00 00 00 00 "
"00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 "
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
-->
"30 00 00 00 68 00 00 00 00 00 18 00 00 00 03 00 "
"4A 00 00 00 18 00 01 00 05 00 00 00 00 00 05 00 "
"A0 73 73 A7 99 E8 D7 01 A0 73 73 A7 99 E8 D7 01 "
"A0 73 73 A7 99 E8 D7 01 A0 73 73 A7 99 E8 D7 01 "
"00 00 1C 33 00 00 00 00 00 00 1C 33 00 00 00 00 "
"06 00 00 00 00 00 00 00 04 03 24 00 4D 00 46 00 "
-->
"80 00 00 00 68 00 00 00 01 00 40 00 00 00 16 00 "
"00 00 00 00 00 00 00 00 FF A9 03 00 00 00 00 00 "
"40 00 00 00 00 00 00 00 00 00 A0 3A 00 00 00 00 "
"00 00 A0 3A 00 00 00 00 00 00 A0 3A 00 00 00 00 "
"33 20 C8 00 00 00 0C 43 B3 C8 00 8D 90 98 00 43 "
"4E D8 00 E8 72 97 00 43 0A C8 00 01 87 1C FF 32 "
-->
"B0 00 00 00 48 00 00 00 01 00 40 00 00 00 15 00 "
"00 00 00 00 00 00 00 00 1D 00 00 00 00 00 00 00 "
"40 00 00 00 00 00 00 00 00 E0 01 00 00 00 00 00 "
"E0 D8 01 00 00 00 00 00 E0 D8 01 00 00 00 00 00 "
*/
实际上PMFT_FILE_RECORD_HEADER MFT元数据结构指针获取到PMFT_ATTRIBUTE_HEADER 属性结构指针。
是直接转换成PBYTE类型进行一个指针位移的运算。
其中非常重要的两个宏定义的解释说明(出自文言一心):
POINTER_ADD 宏定义:
在C++中,PBYTE 是一个指向 BYTE 类型的指针,而 DWORD 是一个32位无符号整数类型。当我们把一个 PBYTE 类型的指针加上一个 DWORD 类型的值时,实际上是在做指针算术运算
在 C++ 中,当我们将一个指针与一个整数值相加时,结果指针会向后移动这么多元素。这里的“元素”是指针所指向类型的大小。
例如,对于 PBYTE 指针来说,它指向的是 BYTE 类型,即每个元素大小为 1 字节。因此,当我们用 PBYTE 加上一个 DWORD 值时,实际上是让指针向后移动 DWORD 值所表示的字节数。
#define POINTER_ADD(type, base, offset) (type)((PBYTE)base + offset)
POINTER_SUB宏定义:
当你有两个指向同一数组内元素的指针时,
从一个指针减去另一个指针会得到它们之间相差的元素个数。
例如,如果你有两个指向同一个 char 数组内不同位置的指针,
那么指针间的差值将是一个整数,表示这两个位置之间相隔了多少个 char 元素。
这种操作只适用于指向同一数组或同一对象内部不同部分的指针。如果两个指针指向完全不相关的内存区域,则其间的差值没有意义,并且可能引发未定义行为。
#define POINTER_SUB(base, length) ((PBYTE)base - (PBYTE)length)
这种直接指针加减的操作,使我大受启发,在一些读取数据流的操作上,完全可以使用这种方法。。
以上可以获取到了第一个MFT元数据 的属性列表;
下一步就能找到0X80 $DATA 属性数据,解析数据得到 DataRuns数据列表