Qt/C++ 深入了解NTFS文件系统,解析0x90 $INDEX_ROOT和0xA0 $INDEX_ALLOCATION 属性,获取索引(例如目录)的B+树的根节点和子节点
解析x90 $INDEX_ROOT和0xA0 $INDEX_ALLOCATION 属性结构,获取索引(例如目录)的B+树的根节点和子节点,即可通过MFT RECORD ID定位到MFT RECORD表,加载当前MFT RECORD的子目录/文件数据.
系列文章目录
整个专栏系列是根据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属性获取文件或目录的实际大小,名称,磁盘分配大小,记录号 ,文件属性等信息- Qt/C++ 深入了解NTFS文件系统,解析0x90 $INDEX_ROOT和0xA0 $INDEX_ALLOCATION 属性,获取索引(例如目录)的B+树的根节点和子节点
解析x90 $INDEX_ROOT和0xA0 $INDEX_ALLOCATION 属性结构,获取索引(例如目录)的B+树的根节点和子节点,即可通过MFT RECORD ID定位到MFT RECORD表,加载当前MFT RECORD的子目录/文件数据.- 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属性 结构图示:
- 数据结构定义:
索引根的结构:
| 偏移 | 大小 | 描述 |
|---|---|---|
| ~ | ~ | 标准属性头 |
| 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的数据的异常数据,也包括当前节点的索引数据。获取数据的时候需要进行(去噪)处理
实际属性数据结构 参考如下图示:
更多推荐



所有评论(0)