当前位置: 首页 > article >正文

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

系列文章目录

一、Qt/C++ 了解NTFS文件系统,了解MFT(Master File Table)主文件表(一)
二、Qt/C++ 了解NTFS文件系统,解析盘符引导扇区数据获取MFT(Master File Table)主文件表偏移地址
三、Qt/C++ 了解NTFS文件系统,获取首张MFT表数据,解析文件记录头内容找到第一个属性偏移地址


目录导读

  • 系列文章目录
  • 前言
  • BOOTICE工具介绍
  • 读取分区引导扇区(PBS)


前言

根据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浏览器翻译]

字节偏移量段长度平均数字段名目的
0x003字节0xEB5290x86JMP和nototherwiseprovided(for)除非另有规定说明 导致在该引导扇区中的数据结构之后继续执行。
0x038字节"NTFS    "单词“NTFS”后跟四个尾随空格(0x20)OEM ID这是一个神奇的数字,表明这是一个NTFS文件系统。
0x0B2字节0x0200BPB每扇区字节数磁盘扇区中的字节数。
0x0D1字节0x08BPB每簇扇区簇中的扇区数量。如果该值大于0x80,则扇区数是2的绝对值的幂,认为该字段为负。
0x0E2字节0x0000BPB未使用的保留扇区
0x103字节0x000000BPB不用的该字段始终为0
0x132字节0x0000BPBNTFS未使用该字段始终为0
0x151字节0xF8BPB媒体描述符驱动器的类型。0xF8用于表示硬盘驱动器(与几种大小的软盘不同)。
0x162字节0x0000BPB不用的该字段始终为0
0x182字节0x003FBPB每个磁道的扇区驱动器磁道中的磁盘扇区数量。
0x1A2字节0x00FFBPB头数驱动器上的磁头数。
0x1C4字节0x000D6800BPB隐藏扇区分区前的扇区数量。
0x204字节0x00000000BPB不用的NTFS不使用
0x244字节0x00800080EBPB不用的NTFS不使用
0x288字节0x000000000C8007FFEBPB总部门扇区中的分区大小。
0x308字节0x00000000000C0000EBPB$MFT集群号包含主文件表的群
0x388字节0x0000000000000002EBPB$MFTMirr群集号包含主文件表备份的群集
0x401字节0xF6EBPB每个文件记录段的字节或簇正值表示文件记录段中簇的数量。负值表示文件记录段中的字节数,在这种情况下,大小是绝对值的2次方。(0xF6 = -10 → 210 = 1024).
0x413字节0x000000EBPB不用的NTFS不使用此字段
0x441字节0x01EBPB每个索引缓冲区的字节或簇正值表示索引缓冲区中的簇数量。负值表示字节数,它对负数使用与“每个文件记录段的字节数或簇数”相同的算法
0x453字节0x000000EBPB不用的NTFS不使用此字段
0x488字节0x86EEA7DEEEA7C4B1EBPB卷序列号分配给该分区的唯一随机数,以保持有序。
0x504字节0x00000000校验和,未使用应该是校验和。
0x54426字节引导代码加载操作系统其余部分的代码。这由该扇区的前3个字节指向。
0x01FE2字节0xAA55扇区结束标记 此标志表示这是一个有效的引导扇区。

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

大端模式(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表数据,解析文件记录头内容找到第一个属性偏移地址


http://www.kler.cn/news/312042.html

相关文章:

  • 服务发现和代理实例的自动更新
  • Linux 基本使用和 web 程序部署 ( 8000 字 Linux 入门 )
  • 【python】后台程序初始化全流程
  • electron-vue安装与打包问题解决
  • js中箭头函数与普通函数的区别
  • 删除视频最后几帧 剪切视频
  • Vue3:el-table实现日期的格式化
  • 安卓 uniapp跨端开发
  • JVM 内存模型:堆、栈、方法区讲解
  • 如何使用Postman搞定带有token认证的接口实战!
  • VSCode C++ Tasks.json中的变量
  • 住宅HTTP代理:提升网络隐私与安全的新选择
  • Electron-vue asar 局部打包优化处理方案——绕开每次npm run build 超级慢的打包问题
  • 1.MySQL在Centos 7环境安装
  • STM32 -中断
  • mysql使用sql函数对json数组的处理
  • 首席数据官(CCRC-CDO)的职业价值
  • 学习最佳实践G4F中的编程技术:获得python项目的当前安装版本
  • 2024年【汽车驾驶员(高级)】考试报名及汽车驾驶员(高级)模拟考试题
  • 项目实战bug修复
  • pikachu XXE(XML外部实体注入)通关
  • TCP协议分析《实验报告》
  • 第三方接口-苹果-获取天气接口
  • Flask、Werkzeug 和 WSGI 间的关系
  • 代码随想录算法训练营第三十二天 | 509. 斐波那契数,70. 爬楼梯,746. 使用最小花费爬楼梯
  • PHP 实现 redis 分布式锁
  • 中间件安全(二)
  • 作品集生成链接或二维码:设计师求职
  • 数据结构和算法之线性结构
  • Java中Integer的缓存池是怎么实现的?