Qt/C++ 了解NTFS文件系统,解析MFT主文件表中的常驻属性与非常驻属性

系列文章目录

整个专栏系列是根据GitHub开源项目NTFS-File-Search获取分区所有文件/目录列表的思路。
具体的如下:

  1. Qt/C++ 了解NTFS文件系统,了解MFT(Master File Table)主文件表(一)
    介绍NTFS文件系统,对比通过MFT(Master File Table)主文件表获取数据的优劣,简单介绍开源项目NTFS-File-Search,以及了解借鉴NTFS系统中所参考的所有文章。
  2. Qt/C++ 了解NTFS文件系统,解析盘符引导扇区数据获取MFT(Master File Table)主文件表偏移地址
    读取$Boot引导分区扇区数据,获取单个簇大小(4096字节),$MFT元数据大小(1024字节)和$MFT元数据的起始簇号,计算出$MFT元数据在磁盘的偏移地址。
  3. Qt/C++ 了解NTFS文件系统,获取首张MFT表数据,解析文件记录头内容找到第一个属性偏移地址
    解析$MFT元数据结构,根据$MFT元数据文件记录头获取属性的偏移地址,用于后面解析0x80 $Data属性,获取Run List数据列表
  4. Qt/C++ 了解NTFS文件系统,解析MFT主文件表中的常驻属性与非常驻属性
    简单介绍$MFT元数据中的常驻属性与非常驻属性结构.
  5. Qt/C++ 了解NTFS文件系统,解析0x80 $Data属性,获取Run List数据列表
    根据0x80 $Data属性,找到存放所有$MFT元数据的区间列表(Run List数据列表)
  6. 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记录中的每个属性都有一个标准标题。头存储关于属性的类型、大小、名称(可选)以及它是否是常驻的信息。
属性的大小取决于两个因素。它有名字吗?它是常驻的吗?为了简化表格,将全部显示所有四种可能性(其中一些值已经填写)。
通过游览器直接翻译:

  • 常驻属性 没有名称

偏移地址字节大小描述
0x004属性类型(例如0x10、0x60)
0x044长度(包括此标题)
0x0810x00非居民旗帜
0x0910x00名称长度
0x0A20x00名称的偏移量
0x0C20x00旗帜(Flags: 压缩、加密、稀疏标志)
0x0E2属性Id (a)
0x104L属性的长度
0x1420x18属性的偏移量
0x161索引标志
0x1710x00填料
0x18L属性

(a) 是指每个属性都有一个唯一的标识符

  • 常驻属性 有名称

偏移地址字节大小描述
0x004属性类型(例如0x90、0xB0)
0x044长度(包括此标题)
0x0810x00非居民旗帜
0x091N名称长度
0x0A20x18名称的偏移量
0x0C20x00旗帜(Flags: 压缩、加密、稀疏标志)
0x0E2属性Id (a)
0x104L属性的长度
0x1422N+0x18属性的偏移量(b)
0x161索引标志
0x1710x00填料
0x182N统一码属性的名称
2N+0x18L属性(b)

(a) 是指每个属性都有一个唯一的标识符
(b) 是指向上舍入到4字节的倍数

  • 非常驻属性 没有名称

偏移地址字节大小描述
0x004属性类型(如0x20、0x80)
0x044长度(包括此标题)
0x0810x01非居民旗帜
0x0910x00名称长度
0x0A20x00名称的偏移量
0x0C2旗帜(Flags: 压缩、加密、稀疏标志)
0x0E2属性Id (a)
0x108从VCN开始
0x188最后的VCN
0x2020x40数据运行的偏移
0x222压缩单元大小(b)
0x2440x00填料
0x288属性的分配大小(c )
0x308属性的实际大小
0x388流的初始化数据大小(d)
0x40数据运行

(a) 是指每个属性都有一个唯一的标识符
(b) 是指压缩单元大小= 2x集群。0表示未压缩
(c ) 是指这是向上舍入到集群大小的属性大小
(d) 是指压缩数据的大小。

  • 非常驻属性 有名称

偏移地址字节大小描述
0x004属性类型(例如0x80、0xA0)
0x044长度(包括此标题)
0x0810x01非居民旗帜
0x091N名称长度
0x0A20x40名称的偏移量
0x0C2旗帜(Flags: 压缩、加密、稀疏标志)
0x0E2属性Id (a)
0x108从VCN开始
0x188最后的VCN
0x2022N+0x40数据运行的偏移(b)
0x222压缩单元尺寸(c )
0x2440x00填料
0x288属性的分配大小(d)
0x308属性的实际大小
0x388流的初始化数据大小(e)
0x402N统一码属性的名称
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 "
*/
  • 如BootIce工具 图示

请添加图片描述
实际上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数据列表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

得鹿梦鱼、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值