系列文章目录
整个专栏系列是根据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数据](#了解Run List数据)
- [解析0x80 $Data属性获取 Run List数据](#解析0x80 $Data属性获取 Run List数据)
- 代码示例:
MFT元数据常见属性类型介绍
MFT元数据属性类型很多,这里以表格的形式简单介绍常见的数据类型。
MFT元数据常见属性类型介绍,参考:
NTFS - Attributes
File - $AttrDef (4)
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的数据结构:
cpp
/* 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数据的示例:
cpp
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"
*/