系列文章目录
整个专栏系列是根据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记录表
MFT元数据 属性结构解析
参考:
修正了一下$ATTRIBUTE_LIST模板
一文中的结构和属性说明
$ATTRIBUTE_LIST (0x20)
代码部分,依旧主要还是在NTFS-File-Search基础上进行的增改,不熟悉的代码示例请参考NTFS-File-Search中的代码。
解析0x20 $ATTRIBUTE_LIST属性
当MFT记录中有很多属性而空间不足时,所有那些可以成为非常驻属性的属性都会被移出MFT。如果仍然没有足够的空间,那么就需要一个$ATTRIBUTE_LIST属性。其余的属性放在一个新的MFT记录中,而ATTRIBUTE _ LIST描述了在哪里可以找到它们。看到这个属性是很不寻常的。
在标准头之后,该属性包含一个可变长度记录的列表,描述了属于该文件的所有其他属性的类型和位置(在MFT中)。每个记录在8字节边界上对齐。
需要注意的是$ATTRIBUTE_LIST属性可以是常驻的,也可以是非常驻的。此属性没有最小或最大大小。本文只介绍为常驻属性时的解析结构!
为常驻属性时
- 数据结构定义:
属性列表子项结构:
偏移 | 大小 | 描述 |
---|---|---|
0x00 | 4 | 类型 |
0x04 | 2 | 记录长度 |
0x06 | 1 | 名称长度(N) |
0x07 | 1 | 到名称的偏移量(a) |
0x08 | 8 | 从VCN开始(b) |
0x10 | 8 | 属性的基本文件引用 |
0x18 | 2 | 属性Id © |
0x1A | 2N | Unicode中的名称(如果N > 0) |
- 属性列表子项 C++结构体声明示例:
/*
* $ATTRIBUTE_LIST (0x20) - 属性列表
* https://flatcap.github.io/linux-ntfs/ntfs/attributes/attribute_list.html
*
* 补充
* https://blog.youkuaiyun.com/weixin_34037977/article/details/93089139
*
*/
typedef struct MFT_ATTRIBUTE_ENTRY
{
ULONG AttributeType; //标识属性类型,例如标准信息、文件名、数据等。
WORD AttributeSize; //属性数据的大小
BYTE NameLength; //名称长度
BYTE NameOffset; //到名称的偏移量
//如果属性是驻留的,这是数据在MFT记录中的偏移量;如果是非驻留的,这是指向实际数据的MFT记录的索引。
ULONGLONG VCNOffset; //从VCN开始 或者如果属性是常驻的,则为零
ULONGLONG ObjReference; //属性的基本文件引用
WORD ObjFlag; //属性Id
}* PMFT_ATTRIBUTE_ENTRY;
实际属性数据结构 参考如下图示:
计算MFT RECORD ID对应磁盘地址偏移
一开始在计算MFT RECORD ID对应的磁盘地址偏移量时,也是按照
LCN=$MFT 起始地址偏移
MFT起始物理地址=LCN×每簇扇区数×每扇区字节数
目标物理偏移=MFT起始物理地址+(Record ID×MFT记录大小)
结果计算出来的时候发现获取的数据不对,
后来发现当MFT RECORD表数据集在第一个MFT记录Data RunList数据中时这么计算没问题,
但是当Data RunList数据出现多个分段时,这样计算就有问题了。
应该在获取Data RunLIst数据时,按MFT RECORD ID与地址偏移量分段计算地址偏移量:
- 加载第一个MFT RECORD的Data RunList数据:
定义一个 **QMap<DWORD,LONGLONG> IntervalRange;类型数据
用来保存QMap<MFT RECORD ID,地址偏移量>**结构数据.
C++代码示例:
#define FILE_RECORDS_PER_FILE_BUF 65536
//定义每65536个Mft Record 记录表作为一个分段
//! 解析runData区间
/* 解析DataRun数据列表 */
DWORD cbFileRecordBuffer = FILE_RECORDS_PER_FILE_BUF * m_ullRecordSize;
UINT64 ullVolumeOffset=0;
pbFileRecordBuffer=new BYTE[cbFileRecordBuffer];
for (size_t i = 0; i < m_rgDataRuns.size(); ++i)
{
VCN_t RunLength = m_rgDataRuns[i].Length;
ullVolumeOffset += m_rgDataRuns[i].Offset;
for (UINT64 nChunkOffset = 0; nChunkOffset < RunLength * m_ullClusterSize;)
{
/* Determine the number of files to read */
UINT64 cbReadLength = cbFileRecordBuffer;
if (RunLength * m_ullClusterSize - nChunkOffset < cbReadLength) {
cbReadLength = RunLength * m_ullClusterSize - nChunkOffset;
}
qDebug()<<"[cbReadLength]: "<<QString::number(cbReadLength,16)<<" -> "<<QString::number(cbReadLength,10);
ULONGLONG Addressoffset=ullVolumeOffset * m_ullClusterSize + nChunkOffset;
/* Read the file records from the volume */
DWORD cbBytesRead = WinApi::ReadBytes(m_hVolume,pbFileRecordBuffer, cbReadLength, Addressoffset);
qDebug()<<"[cbBytesRead]: "<<QString::number(cbBytesRead,16)<<" -> "<<QString::number(cbBytesRead,10);
if (!cbBytesRead) {
break;
}
nChunkOffset += cbReadLength;
PMFT_FILE_RECORD_HEADER m_pFileRecord=POINTER_ADD(PMFT_FILE_RECORD_HEADER, pbFileRecordBuffer, 0);
qDebug().noquote()<<QString("MFT 记录分区 -> \n"
"Addressoffset : %1 \n"
"MftRecordIndex: %2 \n")
.arg(QString::number(Addressoffset,10))
.arg(QString::number(m_pFileRecord->RecordNumber,10));
IntervalRange.insert(m_pFileRecord->RecordNumber,Addressoffset);
}
}
- 再根据上面的公式计算地址偏移量:
找到MFT RECORD ID所在的地址偏离量分段,计算地址偏移量:
目标物理偏移=当前分段的MFT起始物理偏移地址+((MFT Record ID- 当前分段的起始MFT Record ID)×MFT记录大小)
LONGLONG GetOffsetAddressBy(DWORD MftRecordId)
{
//! MFT 记录大小
// ULONG32 m_ullRecordSize;
QList<DWORD> Keys=IntervalRange.keys();
for(int i=0;i<Keys.count();i++)
{
if(i==Keys.count()-1)
{
if(MftRecordId>=Keys[i])
{
//qDebug()<<"i : "<<i<<" RecordId : "<<MftRecordId<<" Keys[i] : "<<QString::number(Keys[i],10)<<" V: "<<QString::number(IntervalRange[Keys[i]],10);
DWORD DiffRecord=MftRecordId-Keys[i];
return DiffRecord*m_ullRecordSize+IntervalRange[Keys[i]];
}
}
else
{
if(MftRecordId>=Keys[i]
&& Keys[i+1]>MftRecordId)
{
//qDebug()<<"i : "<<i<<" RecordId : "<<MftRecordId<<" Keys[i] : "<<QString::number(Keys[i],10)<<" V: "<<QString::number(IntervalRange[Keys[i]],10);
DWORD DiffRecord=MftRecordId-Keys[i];
return DiffRecord*m_ullRecordSize+IntervalRange[Keys[i]];
}
}
}
return -1;
}
实测通过这么计算的地址偏移量是没问题的,
MFT RECORD 记录表属性可视化工具
在学习NFTS文件系统,了解MFT RECORD记录表时,
我用Qt写了一个简单的查询工具,能查看MFT RECORD记录表的对应1024字节的数据,以及属性的划分和定义:
能更简单的查看MFT RECORD 表记录数据u
左侧目录树是通过解析
$INDEX_ROOT,$INDEX_ALLOCATION,$ATTRIBUTE_LIST获取的所有子节点索引,
然后根据MFT Record ID找到对应的MFT RECord表记录,获取文件名信息显示.
左侧目录树展开节点或者右键选项加载MFT Record记录时
会把选中的MFT Record 表记录的1024字节的十六进制数据显示到中间 QGraphicsView 控件中。
并且显示所有的MFT Record属性,
可通过鼠标中键放大缩小,拖拽查看.
最右侧的
第一个表格是通过分区的第一个512字节数据获取的数据,
第二个表格是MFT RECORD表记录头布局和属性列表和范围字段
可通过双击查看具体属性解析说明
软件是Qt 5.13.1 MSCV2017 Release 32位编译器编译
如果无法运行,请安装MSCV2017 32位库
exe工具详见文件附件资源,整个NTFS文件系统系列到此结束。