Qt/C++ 深入了解NTFS文件系统,解析0x20 $ATTRIBUTE_LIST,获取MFT RECORD记录表的其他扩展属性和MFT RECORD ID对应的地址偏移量

系列文章目录

整个专栏系列是根据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属性获取文件或目录的实际大小,名称,磁盘分配大小,记录号 ,文件属性等信息
  7. Qt/C++ 深入了解NTFS文件系统,解析0x90 $INDEX_ROOT和0xA0 $INDEX_ALLOCATION 属性,获取索引(例如目录)的B+树的根节点和子节点
    解析x90 $INDEX_ROOT和0xA0 $INDEX_ALLOCATION 属性结构,获取索引(例如目录)的B+树的根节点和子节点,即可通过MFT RECORD ID定位到MFT RECORD表,加载当前MFT RECORD的子目录/文件数据.
  8. 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属性可以是常驻的,也可以是非常驻的。此属性没有最小或最大大小。本文只介绍为常驻属性时的解析结构!

为常驻属性时

  • 数据结构定义:

属性列表子项结构:

偏移大小描述
0x004类型
0x042记录长度
0x061名称长度(N)
0x071到名称的偏移量(a)
0x088从VCN开始(b)
0x108属性的基本文件引用
0x182属性Id ©
0x1A2NUnicode中的名称(如果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文件系统系列到此结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

得鹿梦鱼、

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

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

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

打赏作者

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

抵扣说明:

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

余额充值