Qt/C++ 了解NTFS文件系统,解析盘符引导扇区数据获取MFT(Master File Table)主文件表偏移地址

系列文章目录

整个专栏系列是根据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属性获取文件或目录的实际大小,名称,磁盘分配大小,记录号 ,文件属性等信息


前言

根据NT File System (NTFS) 一文中对引导扇区数据结构的分析,结合前文介绍的GitHub开源项目 NTFS-File-Search中的NTFS_BOOT_SECTOR结构体设计,使用Bootice工具查看分区引导扇区实际数据,计算首张MFT(Master File Table)主文件表在磁盘的偏移地址。

BOOTICE工具介绍

BOOTICE 引导修复工具是款USM的启动相关维护的工具。用于编辑修改磁盘上的引导扇区的信息,也就是MBR,BOOTICE可在磁盘(硬盘、移动硬盘、U盘、SD卡等)上安装磁盘引导程序。BOOTICE引导修复工具此外还具有磁盘扇区编辑、磁盘填充、分区管理等等功能。BOOTICE有着强大的兼容性,在硬盘维护中起来了很大的作用。
BOOTICE支持的引导程序主要有 WEE, GRUB4DOS, SYSLINUX, Plop BootManager, Ms-Dos 及 Windows NT 5/6 等。BOOTICE 还具有分区管理、扇区查看以及对 USB 移动存储设备进行重新格式化的功能。
具体参考 BOOTICE 1.3.4.0最新版

这里注意用于查看磁盘扇区数据,进行数据字节对照,
其中显示的数值都是16进制数据,以C盘目录为例:
在这里插入图片描述


读取分区引导扇区(PBS)

分区引导扇区是从NTFS文件系统格式 分区 的第一个扇区,

只有盘符是NTFS文件系统格式才能通过MFT表获取数据,
其他FAT,FAT32,REFS文件格式有其他的读取方式

如C盘的第一个扇区,以上面显示的C盘数据为例,
引导扇区在第878592个扇区。
读取整段扇区数据,到55 AA结束,一般都是512字节大小,
解析第一个扇区(引导扇区)数据,获取NTFS文件系统中第一个MFT表对应起始地址。

  • NTFS引导扇区内容结构:

表格出自Windows NT文件系统(NT File System)[使用QQ浏览器翻译]

字节偏移量 段长度 平均数 字段名 目的
0x00 3字节 0xEB5290 x86JMP和nototherwiseprovided(for) 除非另有规定说明 导致在该引导扇区中的数据结构之后继续执行。
0x03 8字节 "NTFS    "单词“NTFS”后跟四个尾随空格(0x20) OEM ID 这是一个神奇的数字,表明这是一个NTFS文件系统。
0x0B 2字节 0x0200 BPB 每扇区字节数 磁盘扇区中的字节数。
0x0D 1字节 0x08 BPB 每簇扇区 簇中的扇区数量。如果该值大于0x80,则扇区数是2的绝对值的幂,认为该字段为负。
0x0E 2字节 0x0000 BPB 未使用的保留扇区
0x10 3字节 0x000000 BPB 不用的 该字段始终为0
0x13 2字节 0x0000 BPB NTFS未使用 该字段始终为0
0x15 1字节 0xF8 BPB 媒体描述符 驱动器的类型。0xF8用于表示硬盘驱动器(与几种大小的软盘不同)。
0x16 2字节 0x0000 BPB 不用的 该字段始终为0
0x18 2字节 0x003F BPB 每个磁道的扇区 驱动器磁道中的磁盘扇区数量。
0x1A 2字节 0x00FF BPB 头数 驱动器上的磁头数。
0x1C 4字节 0x000D6800 BPB 隐藏扇区 分区前的扇区数量。
0x20 4字节 0x00000000 BPB 不用的 NTFS不使用
0x24 4字节 0x00800080 EBPB 不用的 NTFS不使用
0x28 8字节 0x000000000C8007FF EBPB 总部门 扇区中的分区大小。
0x30 8字节 0x00000000000C0000 EBPB $MFT集群号 包含主文件表的群
0x38 8字节 0x0000000000000002 EBPB $MFTMirr群集号 包含主文件表备份的群集
0x40 1字节 0xF6 EBPB 每个文件记录段的字节或簇 正值表示文件记录段中簇的数量。负值表示文件记录段中的字节数,在这种情况下,大小是绝对值的2次方。(0xF6 = -10 → 210 = 1024).
0x41 3字节 0x000000 EBPB 不用的 NTFS不使用此字段
0x44 1字节 0x01 EBPB 每个索引缓冲区的字节或簇 正值表示索引缓冲区中的簇数量。负值表示字节数,它对负数使用与“每个文件记录段的字节数或簇数”相同的算法
0x45 3字节 0x000000 EBPB 不用的 NTFS不使用此字段
0x48 8字节 0x86EEA7DEEEA7C4B1 EBPB 卷序列号 分配给该分区的唯一随机数,以保持有序。
0x50 4字节 0x00000000 校验和,未使用 应该是校验和。
0x54 426字节 引导代码 加载操作系统其余部分的代码。这由该扇区的前3个字节指向。
0x01FE 2字节 0xAA55 扇区结束标记 此标志表示这是一个有效的引导扇区。

需要注意的是:磁盘扇区数据中除字符串外的所有值都以小端序(little endian)存储,如
磁盘大小为0x000000000C8007FF字节
但是数据格式是按FF 07 80 0C 00 00 00 00顺序存储

扫描二维码关注公众号,回复: 17521767 查看本文章

大端模式(Big_endian):字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。
小端模式(Little_endian):字数据的高字节存储在高地址中,而字数据的低字节则存放在低地址中。
参考:
Big-endian and Little-endian (大小端)
字节序:Big Endian 和 Little Endian

  • 查看实际扇区数据:

使用BOOTICE工具查看磁盘扇区实际数据,并按上面表格占用字节划分标注;
请添加图片描述
结合引导分区结构表格与盘符数据进行对照,就可以设计一个512字节的C++结构体,来获取引导分区数据:
例如 GitHub开源项目 NTFS-File-Search 设计的NTFS_BOOT_SECTOR 结构

  • NTFS_BOOT_SECTOR结构体

/*
* BIOS Parameter Block (BPB) - BIOS参数块(BPB)
* https://en.wikipedia.org/wiki/NTFS
* https://en.wikipedia.org/wiki/BIOS_parameter_block
* https://www.ntfs.com/ntfs-partition-boot-sector.htm
* NTFS文件系统详解(三)之NTFS元文件解析(https://blog.csdn.net/enjoy5512/article/details/50966009/)
*/
typedef struct NTFS_BOOT_SECTOR
{
    
    
    /* Jump instruction */
    /* 跳转指令 EB 52 90*/
    BYTE Jmp[3];

    /* Signature*/
    /* 文件系统的ASSIIC码表示形式 NTFS*/
    BYTE Signature[8];

    //
    // BPB and extended BPB
    //
    //!字节2 每个扇区的字节总数 一般是00 02H  (一般后面描述加H表示十六进制)
    WORD		BytesPerSector;
    //!字节1 簇大小 08 每个簇占8个扇区
    BYTE		SectorsPerCluster;
    //!字节2 保留扇区
    WORD		ReservedSectors;
    //!字节3 总为0
    BYTE		Zeros1[3];
    //!字节2 不使用
    WORD		Unused1;
    //!字节1 介质描述,硬盘为F8
    BYTE		MediaDescriptor;
    //!字节2 总为0
    WORD		Zeros2;
    //!字节2 每磁头扇区数
    WORD		SectorsPerTrack;
    //!字节2 每柱面磁头数
    WORD		NumberOfHeads;
    //!字节4 隐含扇区数 (从MBR到DBR的扇区总数)
    DWORD		HiddenSectors;
    //!字节4 不使用
    DWORD		Unused2;
    //!字节4 不使用 总为80 00 80 00
    DWORD		Unused3;
    //!字节8 扇区总数,即分区大小
    ULONGLONG	TotalSectors;
    //!字节8 $MFT的开始簇号
    ULONGLONG	MFT_LCN;		/* $MFT Logical Cluster Number (LCN) - $MFT逻辑簇数(LCN) */
    //!字节8 $MFTMirr的开始簇号
    ULONGLONG	MFTMirr_LCN;	/* $MFTMirr Logical Cluster Number (LCN) - $MFTMirr 逻辑集群号(LCN) */
    //!字节4 每个MFT记录的簇数
    DWORD		ClustersPerFileRecord;
    //!字节4 每索引的簇数
    DWORD		ClustersPerIndexBlock;
    //! 分区的逻辑序列号
    BYTE		VolumeSN[8];

    /* Boot Code - 引导代码 */
    BYTE BootCode[430];

    //
    // 0xAA55
    //

    BYTE _AA;
    BYTE _55;
};

注意使用
#pragma pack( push, 1 )
#pragma pack( pop )
进行字节对齐。

学习C/C++源码案例的时候,pragma pack是经常会遇到
参考#pragma pack 详解

  • 读取引导分区数据

读取引导分区数据转换成指定结构体,获取首个MFT表地址

    qDebug()<<" Start ---->";
    //!磁盘第一个512字节结构 BIOS参数
    NTFS_BOOT_SECTOR	m_BootRecord;
    const WCHAR lpszVolumeName[7] = {
    
     L'\\', L'\\', L'.', L'\\', L'C', L':' };
    HANDLE m_hVolume= CreateFile(lpszVolumeName,
                                 GENERIC_READ,
                                 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                 NULL,
                                 OPEN_EXISTING,
                                 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
                                 NULL
                             );
       if (m_hVolume == INVALID_HANDLE_VALUE)
           goto out ;
       DWORD dwNumberOfBytesRead;
       if (ReadFile(m_hVolume, &m_BootRecord, NTFS_BOOTSECTOR_SIZE, &dwNumberOfBytesRead, NULL))
       {
    
    
           if (dwNumberOfBytesRead == NTFS_BOOTSECTOR_SIZE)
           {
    
    
              qDebug()<<"[磁盘扇区中的字节数] :  "<<QString::number(m_BootRecord.BytesPerSector,16).toUpper()<<" -> "<<QString::number(m_BootRecord.BytesPerSector,10);
              qDebug()<<"[簇中的扇区数量] :     "<<QString::number(m_BootRecord.SectorsPerCluster,16).toUpper()<<" -> "<<QString::number(m_BootRecord.SectorsPerCluster,10);
              qDebug()<<"[每磁头扇区数] :       "<<QString::number(m_BootRecord.SectorsPerTrack,16).toUpper()<<" -> "<<QString::number(m_BootRecord.SectorsPerTrack,10);
              qDebug()<<"[每柱面磁头数] :       "<<QString::number(m_BootRecord.NumberOfHeads,16).toUpper()<<" -> "<<QString::number(m_BootRecord.NumberOfHeads,10);
              qDebug()<<"[分区大小] :          "<<QString::number(m_BootRecord.TotalSectors,16).toUpper()<<" -> "<<QString::number(m_BootRecord.TotalSectors,10);
              qDebug()<<"[$MFT的开始簇号] :     "<<QString::number(m_BootRecord.MFT_LCN,16).toUpper()<<" -> "<<QString::number(m_BootRecord.MFT_LCN,10);
              qDebug()<<"[$MFTMirr的开始簇号] : "<<QString::number(m_BootRecord.MFTMirr_LCN,16).toUpper()<<" -> "<<QString::number(m_BootRecord.MFTMirr_LCN,10);
           }
       }
       CloseHandle(m_hVolume);
out:
    qDebug()<<" End ---->";
/*
QCoreApplication Start ---->
[磁盘扇区中的字节数] :   "200"  ->  "512"
[簇中的扇区数量] :      "8"  ->  "8"
[每磁头扇区数] :        "3F"  ->  "63"
[每柱面磁头数] :        "FF"  ->  "255"
[分区大小] :           "C8007FF"  ->  "209717247"
[$MFT的开始簇号] :      "C0000"  ->  "786432"
[$MFTMirr的开始簇号] :  "2"  ->  "2"
QCoreApplication End ---->
*/

由此得到首个MFT在簇号 786432 位置,
计算首张MFT表磁盘地址字节偏移量MFT_LCN:
UINT64 MFT_LCN= (UINT64)(m_BootRecord.MFT_LCN * m_BootRecord.BytesPerSector * m_BootRecord.SectorsPerCluster);
MFT偏移簇号786432
7864328512= “3221225472” =“0XC0000000”

计算出的地址通过ReadFile设置偏移量读取出来确实是MFT结构表数据,
但是这在其他文章中计算方式又不一样:

  • 【1】.MFT偏移簇号786432
    (786432∗4096(偏移)+63∗512(C盘偏移地址)=3221257728=0xC0007E00)

出自 NTFS文件系统详解

  • 【2】.文件记录由两部分构成,一部分是文件记录头,另一部分是属性列表,最后结尾是四个“FF”。然后我们根据上面BPB中的偏移簇号偏移盘偏移地址找到系统MFT所在地址 (789632 * 8 +63 = 6291519),

出自 NTFS文件系统详解(三)之NTFS元文件解析

  • 【3】.在NTFS文件系统中,每个文件记录都有一个唯一的标识符,称为文件记录号(File Record Number,简称FRN)。要访问某个文件记录,需要先找到该文件记录在$MFT元文件中的偏移地址。计算偏移地址的公式如下:
    $MFTOffset = MFTStartCluster * ClusterSize + FRN * RecordSize
    其中,MFTStartCluster是$MFT元文件的起始簇号ClusterSize是簇的大小FRN是文件记录号RecordSize是文件记录的大小

出自 「NTFS:让你的硬盘更安全、更高效!」NTFS文件系统详解

带入上面【1】,【2】的公式,我读取的数据反而是错误的,而C://盘符的文件记录号一般为5固定值,带入计算也错误,带入0计算正常,不知道是不是NTFS版本的问题,后面还是以NTFS-File-Search 示例中的计算方式为准。

下一篇:Qt/C++ 了解NTFS文件系统,获取首张MFT表数据,解析文件记录头内容找到第一个属性偏移地址

猜你喜欢

转载自blog.csdn.net/qq_35554617/article/details/142178547