Qt/C++ 了解NTFS文件系统,获取首张MFT表数据,解析文件记录头内容找到第一个属性偏移地址
系列文章目录
一、Qt/C++ 了解NTFS文件系统,了解MFT(Master File Table)主文件表(一)
二、Qt/C++ 了解NTFS文件系统,解析盘符引导扇区数据获取MFT(Master File Table)主文件表偏移地址
三、Qt/C++ 了解NTFS文件系统,获取首张MFT表数据,解析文件记录头内容找到第一个属性偏移地址
目录导读
- 系列文章目录
- 前言
- 解析MFT(Master File Table)主文件表
- 解析 文件记录头布局($MFT File Record Header Layout)
前言
根据前文获取到的首张MFT(Master File Table)主文件表偏移地址,
获取到首张MFT(Master File Table)主文件表数据,解析文件记录头内容数据,获取到的第一个属性的偏移地址。
用于遍历查找所有属性,直到找到 $DATA(0X80) 属性,获取DataRun数据列表。
NTFS文件系统中每个文件或目录都由一张或多张MFT(Master File Table)主文件表保存数据
DataRun数据列表是指分区盘符的文件目录的所有MFT(Master File Table)主文件表集合所在的偏移地址列表
- 读取首张MFT(Master File Table)主文件表数据
设置的MFT_LCN偏移地址,读取m_ullRecordSize(1024) 个字节的数据,
参考NTFS-File-Search
//! 设置偏移量读取数据
DWORD ReadBytes(HANDLE m_hVolume,PVOID pBuffer, DWORD cbReadSize, UINT64 ullAbsoluteOffset)
{
DWORD dwNumberOfBytesRead=0;
BOOL bSuccess;
if (ullAbsoluteOffset != UINT64_MAX)
{
LARGE_INTEGER liPosition;
liPosition.QuadPart = ullAbsoluteOffset;
LARGE_INTEGER liUpdatedPosition;
if (SetFilePointerEx(m_hVolume, liPosition, &liUpdatedPosition, FILE_BEGIN))
m_ullCurrentOffset = liUpdatedPosition.QuadPart;
else
return 0;
}
bSuccess = ReadFile(m_hVolume, pBuffer, cbReadSize, &dwNumberOfBytesRead, NULL);
if (bSuccess) {
m_ullCurrentOffset += cbReadSize;
}
return dwNumberOfBytesRead;
}
//! 开始读取首个MFT表
PBYTE pbMFTBuffer = new BYTE[m_ullRecordSize];
UINT64 MFT_LCN= (UINT64)(m_BootRecord.MFT_LCN * m_BootRecord.BytesPerSector * m_BootRecord.SectorsPerCluster);
if (m_ullRecordSize != ReadBytes(m_hVolume,pbMFTBuffer, (INT64)m_ullRecordSize, MFT_LCN))
goto out;
qDebug()<<"输出...";
for(int i=0;i<m_ullRecordSize;i++)
{
Val+=QString("%1 ").arg(pbMFTBuffer[i],2,16,QLatin1Char('0')).toUpper();
if((i+1)%16==0)
{
qDebug()<<Val;
Val="";
}
}
/*输出...
"46 49 4C 45 30 00 03 00 EC F3 01 ED 15 00 00 00 "
"01 00 01 00 38 00 01 00 B8 01 00 00 00 04 00 00 "
"00 00 00 00 00 00 00 00 17 00 00 00 00 00 00 00 "
"D6 03 00 00 00 00 00 00 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 "
....
....
*/
- Bootice工具查看数据
在Bootice工具中,第一张MFT(Master File Table)主文件表在引导扇区后第16个扇区。
解析MFT(Master File Table)主文件表
理解MFT(Master File Table)的结构对于理解NTFS文件系统的运作方式至关重要。以下是MFT结构的详细解释:
MFT记录:
MFT由一系列固定大小的记录组成,每个记录对应一个文件、目录或元数据文件。
MFT记录包含了文件或目录的元数据信息,如文件属性、数据位置、文件名等。
MFT记录号:
每个MFT记录都有一个唯一的标识符,称为MFT记录号。
MFT记录号用于在MFT中定位和识别特定的文件或目录。
摘要出自:MFT(Master File Table,主文件表)是Windows操作系统中NTFS(New Technology File System,新技术文件系统)的关键组成部分,用于存储文件和目录的元数据信息。MFT类似于Unix和Linux系统中的inode,但在实现上有所不同。
MFT是一组文件记录。卷中的每个文件都由一个或多个这样的文件记录完整地描述。在Unix术语中,文件记录相当于索引节点。描述给定文件的第一个文件记录称为基本文件记录,其他记录称为扩展文件记录。
文件记录由一个标题、几个可变长度属性和一个结束标记(简单地说就是0xFFFFFFFF)组成。
出自:File - $MFT (0)
Concept - File Record
以第一张MFT(Master File Table)主文件表为例,解析数据结构
MFT(Master File Table)主文件表以FILE0字符串开始。
解析 文件记录头布局($MFT File Record Header Layout)
MFT表中前48字节是文件记录头内容,根据文件记录头数据获取属性开始偏移地址,其数据结构为:
Offset | 字节大小 | OS | Description | 描述 |
---|---|---|---|---|
0x00 | 4字节 | (Magic number ‘FILE’ ) | 固定值 一定是"FILE" | |
0x04 | 2字节 | (Offset to the Update Sequence) | 更新序列号的偏移 | |
0x06 | 2字节 | (Size in words of Update Sequence) (S) | 更新序列号与更新数组以字为单位(s) | |
0x08 | 8字节 | $LogFile Sequence Number (LSN) | 日志序列号(每次记录被修改,都将导致该序列加1) | |
0x10 | 2字节 | Sequence number | 序列号(用于记录本文件记录被重复使用的次数,每次文件删除时加1,跳过0值,如果为0,则保持为0) | |
0x12 | 2字节 | Hard link count | 硬链接数 只出现在基本文件记录中,目录所含项数要使用到他 | |
0x14 | 2字节 | Offset to the first Attribute | 第一个属性流的偏移位置 | |
0x16 | 2字节 | Flags | 标志字节,1表示记录使用中,2表示该记录为目录 | |
0x18 | 4字节 | Real size of the FILE record | 文件记录实际大小(填充到8字节,即以8字节为边界) | |
0x1C | 4字节 | Allocated size of the FILE record | 文件记录分配大小(填充到8字节,即以8字节为边界) | |
0x20 | 8字节 | File reference to the base FILE record | 所对应的基本文件记录的文件参考号(扩展文件记录中使用,基本文件记录中为0,在基本文件记录的属性列表0x20属性存储中扩展文件记录的相关信息) | |
0x28 | 2字节 | Next Attribute Id | 下一个自由ID号,当增加新的属性时,将该值分配给新属性,然后该值增加,如果IFT记录重新使用,则将它置0,第一个实例总是0 | |
0x2A | 2字节 | XP | Align to 4 byte boundary | 边界, WINDOWS XP 中使用,也就是本记录使用的两个扇区的最后两个字节的值 |
0x2C | 4字节 | XP | Number of this MFT Record | WINDOWS XP中使用,本MFT记录号 |
- 其中 Flags 字段:
值 | 描述 |
---|---|
0x01 | 表示记录正在使用中 |
0x02 | 记录是一个目录(文件名索引存在) |
0x04 | Record是一个扩展名(为$Extend目录中的记录设置) |
0x08 | 存在特殊索引(为包含索引的非目录记录设置: S e c u r e , Secure, Secure,ObjID, Q u o t a , Quota, Quota,Reparse) |
- 实际/分配的大小:
分配的大小是记录在磁盘上占用的空间。这应该是集群大小的倍数,并且可能等于MFT文件记录的大小。实际大小是记录中实际使用的字节数。
注意:实际大小将被填充到一个8字节的边界。
参考:NTFS文件系统详解(三)之NTFS元文件解析
Concept - File Record
- 实际数据 使用Bootice工具图示
- 定义一个结构体
定义一个48字节的结构体,对齐字节,用于数据结构转换,例如NTFS-File-Search项目中的MFT_FILE_RECORD_HEADER结构:
/*
* $MFT File Record Header Layout - 文件记录头布局 标准属性标题,
* MFT是一组文件记录。卷中的每个文件都由一个或多个这样的文件记录完整地描述。
* 在Unix术语中,文件记录相当于索引节点。描述给定文件的第一个文件记录称为基本文件记录,其他记录称为扩展文件记录。
* 文件记录由一个标题、几个可变长度属性和一个结束标记(简单地说就是0xFFFFFFFF)组成。
* https://flatcap.github.io/linux-ntfs/ntfs/concepts/file_record.html
*/
typedef struct MFT_FILE_RECORD_HEADER
{
//!字节4 固定值 一定是"FILE"
DWORD Magic; // "FILE" (0x454C4946)
//!字节2 更新序列号的偏移
WORD UpdateSequenceOffset; // Update Sequence offset - 偏移到更新序列
//!字节2 更新序列号与更新数组以字为单位(s)
WORD SizeOfUpdateSequence; // Size in words of Update Sequence - 更新序列的字数
//!字节8 日志序列号(每次记录被修改,都将导致该序列加1)
ULONGLONG LogSequenceNumber; // $LogFile Sequence Number (LSN) - $日志文件序列号(LSN)
//!字节2 序列号(用于记录本文件记录被重复使用的次数,每次文件删除时加1,跳过0值,如果为0,则保持为0)
WORD SequenceNumber; // Sequence number - 序列号
//!字节2 硬链接数 只出现在基本文件记录中,目录所含项数要使用到他
WORD HardLinkCount; // Hard link count - 硬链接计数
//!字节2 第一个属性流的偏移位置
WORD FirstAttributeOffset; // First attribute offset - 到第一个属性的偏移量
//!字节2 标志字节,1表示记录使用中,2表示该记录为目录
WORD Flags;
//!字节4 文件记录实际大小(填充到8字节,即以8字节为边界)
DWORD RealSize; // Real size of the FILE record - FILE记录的实际大小
//!字节4 文件记录分配大小(填充到8字节,即以8字节为边界)
DWORD AllocatedSize; // Allocated size of the FILE record - 文件记录的分配大小
//!字节8 所对应的基本文件记录的文件参考号(扩展文件记录中使用,基本文件记录中为0,在基本文件记录的属性列表0x20属性存储中扩展文件记录的相关信息)
ULONGLONG FileReference; // File reference to the base FILE record - 对基本文件记录的文件引用
//!字节2 下一个自由ID号,当增加新的属性时,将该值分配给新属性,然后该值增加,如果IFT记录重新使用,则将它置0,第一个实例总是0
WORD NextAttributeId; // 下一个属性Id
//!字节2 边界, WINDOWS XP 中使用,也就是本记录使用的两个扇区的最后两个字节的值
WORD Align; // 对齐4字节边界
//!字节4 WINDOWS XP中使用,本MFT记录号
DWORD RecordNumber; // MFT Record Number - MFT记录号
}*PMFT_FILE_RECORD_HEADER;
- 实际数据转换
PMFT_FILE_RECORD_HEADER header=(PMFT_FILE_RECORD_HEADER)pbMFTBuffer;
qDebug()<<"FirstAttributeOffset: "<<QString::number(header->FirstAttributeOffset,16).toUpper()<<" -> "<<QString::number(header->FirstAttributeOffset,10);
qDebug()<<"LogSequenceNumber: "<<QString::number(header->LogSequenceNumber,16).toUpper()<<" -> "<<QString::number(header->LogSequenceNumber,10);
qDebug()<<"Flags: "<<QString::number(header->Flags,16).toUpper()<<" -> "<<QString::number(header->Flags,10);
qDebug()<<"RealSize: "<<QString::number(header->RealSize,16).toUpper()<<" -> "<<QString::number(header->RealSize,10);
qDebug()<<"AllocatedSize: "<<QString::number(header->AllocatedSize,16).toUpper()<<" -> "<<QString::number(header->AllocatedSize,10);
/*
FirstAttributeOffset: "38" -> "56"
LogSequenceNumber: "15ED01F3EC" -> "94170641388"
Flags: "1" -> "1"
RealSize: "1B8" -> "440"
AllocatedSize: "400" -> "1024"
*/
获取到第一个属性偏移地址(FirstAttributeOffset)是从56字节处开始
如下图示: