• 参考博客:硬盘分区表知识——详解硬盘MBR - chuyaoxin - 博客园

  • 参考视频:

  • 操作系统只稍微讲到文件系统,以及磁盘的扇区、柱面,好像也有讲稍微有讲到LVM逻辑卷管理,但是对磁盘整个格式的理解没有串下来,导致当时美亚的时候RAID搞不出来。

  • 磁盘这边就讨论的是单个磁盘,不考虑RAID的情况,RAID的情况之后再说。(有一说一会RAID手动恢复还挺赚钱的?一个单子至少都是几百,一般都是千元往上?但是不知道现在行情怎么样)

  • 先给一个图,看看磁盘的总体结构,对磁盘中的一些结构有个总体把握。首先介绍一下比较早期的磁盘中的数据结构格式(这个早期的磁盘结构格式其实就是指的GPT磁盘分区表出来之前,用的还是MBR引导分区),对于MBRGPT目前就先知道这两个名词先。其他的无需过多关注。

总体图片

  • 对于目前先全面理解一下一个硬盘,目前先不考虑UEFI这个程序到底放在哪里(总之不是放在磁盘中,而是放在主板的ROM上)
  • 先来回顾一下操作系统的内容,计算机在按下电源键的开机时,执行流程如下:
    • 按下电源键开机时,先执行主板bios程序,进行完一系列的检测和配置以后。开始按照bios设定的系统引导顺序引导系统。
    • 假设现在是从磁盘中引导系统,bios就会将控制权交给硬盘去执行硬盘中的一段二进制程序。那么这个二进制程序是在哪里存放的,这就引出了MBR(master boot record)中文名为主引导记录区。(注意并不是直接转到磁盘中执行,而是先将MBR载入到内存地址起始位置为0x7C00后,bios有段汇编是jmp 0x7C00,最终执行MBR那一段代码。)
    • 之后就会加载操作系统的引导程序(该程序在本篇文章中不做介绍),执行该程序之后就会加载内核、驱动、UI等程序

image-20251208222836885

MBR型

MBR大致结构图1

下面这张图就表示了MBR型的主要大致结构,这里大致介绍一下,主引导记录MBR。图中有三个要点需要注意的:

  • MBR引导代码其实就是BIOS自检完要将控制权交给磁盘后,磁盘要执行的代码,其实就是MBR引导代码
  • MBR分区表,主要就是用来标识下面的四个主分区,并且MBR分区表最多只能标识四个主分区(因为设计比较早所以没有考虑太长远。)
  • 之后就是标识着MBR结尾的标识符,类似与PE文件头,这个其实算是MBR文件尾。

image-20251208223424364

  • 对于MBR分区表这里再给出一个图片,该图片其实表名了MBR分区表可能起到索引作用。由于一开始设计MBR主引导记录,固定了其大小为512字节,所以MBR分区只能分出四个主分区表项。

image-20251208224444440

MBR大致结构图2

  • 主引导记录的大致结构已经看完了,接下来看看每个主分区的结构。对于每个主分区的结构都会有一个引导扇区,该引导扇区被称做DBR,英文全称为Dos / Volume Boot Record,有的时候也被称为VBR
  • 目前先有个了解,对于DBR的具体结构后面会具体介绍。

注意:DBR其实是和文件系统在一个层面的,只是这里为了方便循序渐进的理解,将DBR与文件系统分离了。在大致结构图3中会贴出一个图片。

image-20251208230136404

MBR大致结构图3

  • 对于每个主分区中数据这块还需要花费一块空间存储文件系统的一些结构和数据,这个该分区的剩余部分才是真正存放文件的地方。
  • 这里我就以FAT系列的文件系统为例子,画出下图中主分区2的引导程序,这个图才是真正的一块能存储文件的硬盘的主要格式。
  • 注意:之前画的下面两张图FAT文件系统部分有错误,已于2025年12月12日进行更改,更改为一张图

  • 注意:表示文件系统和DBR其实是在同一个层面,而并非MBRDBR在同一个层面

image-20251212165342546

MBR大致结构图4

  • 在大致结构图1中有提到过,使用MBR磁盘分区只能有四个主分区。但是这个限制在不使用GPT型磁盘分区也可以绕过该限制
  • 绕过该限制的方法就是将任意一个主分区当做一个扩展分区,进行分区的嵌套就可以绕过只能有四个主分区的限制了。
  • 我们就以主分区4作为扩展分区来画出一个大致的图片,具体的还是在后面介绍。
  • 这个图还是得注意一下文件系统和DBR其实是在同一个层面,这里只是简化了而已。

image-20251208233633650

特殊情况的MBR结构

  • 当一块硬盘中有两个系统,那么这个硬盘的情况就比较特殊了,如下图所示。

image-20251208234022748

GPT型

GPT型的磁盘分区格式比较复杂,这里来简单描述一下,GPT的磁盘分区格式以便画图能理解。对于MBR,它所在的位置就是磁盘的第一个扇区(每个扇区大小为512字节)。而GPT的磁盘分区格式,一共要占用67个扇区。

  • 在磁盘的开头需要使用34个扇区包括如下:
    • 1个扇区:存放Protective MBR (保护型MBR),只要是用于兼容MBR的磁盘分区
    • 2个扇区:存放GPT Header,也就是GPT磁盘分区格式的头即标识符
    • 3~34个扇区:存放GPT Partition Entries,也就是GPT 分区项表,用来描述每一个分区的详细信息。
  • 在磁盘的结尾需要使用33个扇区,其使用部分如下:
    • 最后一个扇区:存放Backup GPT Header,也就是备份GPT头信息
    • 倒数第2~33个扇区:存放Backup GPT Partition Entries,也就是备份的GPT分区项表。

注意:如果一个磁盘分区是GPT分区,那么其最好是使用UEFI进行启动。

GPT大致结构图1

  • 如果一块硬盘采用GPT分区,那么它的分区结构大致如下:

image-20251209141943108

GPT大致结构图2

  • 对于上面的图片中,只是为了方便理解一下GPT分区格式大致结构,接下来就需要稍微了解一下GPT分区格式是如何分区的。
  • 首先GPT分区的相关信息都记录GPT分区项表中,该分区项表一共有128项表。对于这128项表,有以下几个注意点:
  1. GPT分区表虽然有128项表,但是实际上是你使用多少分区就有多少分区在使用,其他分区还没被初始化。
  2. GPT分区表只标明分区的起止扇区,并没标明分区所使用的文件系统类型。
  3. GPT分区表的每一项都是固定128字节

image-20251209145020493

  • 假设我们现在有两个分区,那么就只有两个分区表项有相应的信息

image-20251209145235665

GPT大致结构图3

  • 首先明确一点就是GPT磁盘分区格式主要就是用来兼容UEFI启动的。所以在GPT制作中有如下几个规范:
  1. 需要有EFI系统分区(EFI System Partition),简称ESP
  2. 对于ESP这个分区,其文件系统必须要求是FAT32
  3. 分区项表中有一个分区类型 ,该分区类型在表示ESP分区的时候要设置为EFI System Partition
  • 但是由于一般在编写GPT磁盘的时候,基本上都是把ESP放在第一个分区(其实并不强制放在第几个分区),并且对于EFI系统分区其实不止保持着UEFI程序,还有其他系统的启动程序
  1. 下面这些.efi程序都是可执行文件,其作用都相当于一个引导程序,也就是Bootloader,该Bootloader运行在运行在UEFI 固件提供的环境下。
  2. 其中bootx64.efi其实就是,UEFI固件启动时默认寻找的标准启动程序,对于单个系统可以直接加载操作系统内核;而对于多个系统可能调用更高级的Bootloader来载入指定的操作系统内核。也就是bootx64.efi 在执行的时候用户可以选择指定操作系统的bootloader
  3. 对应目录中的BCD grub.cfg文件都是对应bootloader的配置文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
# 注意:这些都是规定好的比如:bootx64.efi文件名字都是规定好的,也必须要放在/EFI/BOOT/这个文件目录下面
ESP (FAT32)
├─ /EFI
│ ├─ /Boot
│ │ └─ bootx64.efi ← 默认启动程序
│ ├─ /Microsoft
│ │ ├─ bootmgfw.efi ← Windows Boot Manager
│ │ └─ BCD ← Windows 启动配置
│ ├─ /ubuntu
│ │ ├─ grubx64.efi ← GRUB 引导程序
│ │ └─ grub.cfg
│ └─ /fedora
│ └─ shimx64.efi

image-20251209154919166

MBR与GPT兼容性

  • 对于GPT兼容MBR,下面一个图片可以大致说明,就是如下图所示的一个:
    • GPT中保护型MBR(其实就是MBR的结构),只不过其四个分区表项中只有一个在使用
    • 保护型MBR分区标识为0xEE,并且这个分区大小通常是0xFFFFFFFF(32位),这样使得旧软件看到这个分区会认为磁盘已占用,避免GPT格式被破坏

image-20251209150046407

存储设备与原理

  • 存储设备这块内容算是比较偏向的是操作系统的内容,算是重新回顾一下操作系统。

磁存储

磁带与磁鼓

磁带在1928年就发明了,实际上磁带是一个纸带(今天一般都使用塑料),该纸带(塑料)上均匀粘上铁磁性颗粒

  • 读取和写入都只需要一个机械部件(转动)定位,当实际上大多都是磁带转动
  • 读取:放大感应电流
  • 写入:电磁头(电磁铁)改变磁畴化方向
  • 存储特性:
  • 价格低,廉价材料,几乎不涉及打规模集成电路
  • 容量高
  • 可靠性高(可以适当封装)
  • 读写性能:
  • 勉强可以顺序读写(需要等待定位)
  • 随机读写几乎完全不行
  • 应用场景:冷数据的存档和备份

image-20251209161912337

磁鼓是对磁带用作类似与内存的发展,但是速度还是比较慢,后面被淘汰(也是对磁带是否能用作随机读写的尝试)

  • 用旋转的二维平面存储数据(无法内卷,容量变小)
  • 读写延迟不会超过旋转周期(随机读写速度大幅提升)

image-20251209162600620

磁盘

  • 结合磁带与磁鼓的优点就有了曾经很热门的存储设备磁盘,在二维平面上放置许多磁带。
  • 存储特性:
  • 价格低:高密度、低成本
  • 容量高:2.5D,上万磁道
  • 可靠性高(高速运转的机械部件是潜在的威胁)
  • 读写性能:
  • 顺序读写:较高
  • 随机读写:勉强(需要定位)
  • 应用场景:计算机系统的主力数据存储(便宜,坏了还有可能修)
  • 读写扇区:
  • 读写头需要到对应的磁道:7200pm->120rps,寻道时间大约为8.3ms
  • 转轴盘片旋转到读写头的位置:读写头移动时间通常也需要几个ms
  • 通过缓存/调度等缓解:比如著名的电梯调度算法(已经成为历史的尘埃)。

image-20251209162822656

软盘

软盘主要就是把读写头和盘片分开,实现数据的移动

光存储

光盘

电存储

闪存

分区表结构

接下来就要进入正题了,具体学习一些分区表的数据结构。对于取证而言需要掌握的是MBRDBREBR,而对于UEFI开发来说,要熟练掌握的就是GPT磁盘分区表。

MBR分区表

  • MBR全称也叫做master-boot-record,翻译过来就是主引导记录
  • MBR是给硬盘分区的,其也被称为DOS分区表。0号扇区的主引导记录MBR
  • DOS分区体系的硬盘用分区表记录每个分区的类型、起始位置和分区的大小,其中分区表就在0号扇区内,所以0号扇区如果损坏那么这个硬盘就不能正确识别分区。
  • MBR分区表主要由三部分组成:主引导记录代码(可执行代码)、主分区表项、签名标志,其偏移和大小如下图所示:

image-20251209194103707

  • 下面就来具体介绍MBR分区表的具体结构,首先找一个镜像文件使用Winhex打开它(实在不行就打开磁盘的,但是要注意千万不能一不小心修改了什么东东),为了保险起见这里直接用别人博客上的图片。对于下面这张图片来说最重要的有两个:DPT硬盘分区表、分区有效标志0x55AA

img

  • 对于学习MBR分区表,主要学习的就是DPT硬盘分区表,因为硬盘分区表中记录了四个主分区(如果有)的一些信息。而DPT硬盘分区表中的数据本质上是一个结构体数组,数组中的元素大小为0x10字节。

image-20251209195030928

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct {
uint8_t boot_flag; // 0x80 = active, 0x00 = inactive
uint8_t start_chs[3]; // 起始 CHS(已基本废弃)
uint8_t partition_type; // 分区类型 (0x07 NTFS, 0x83 Linux, 0xEE GPT)
uint8_t end_chs[3]; // 结束 CHS
uint32_t start_lba; // 起始 LBA
uint32_t sector_count; // 分区扇区数
} MBR_PartitionEntry;

MBR_PartitionEntry DPT[4];

/*
partition_type这边还是需要详细说明一下的
0x00表示未使用
0x07表示NTFS文件系统
0x0B、0x0c表示FAT32文件系统
0x05、0x0F表示扩展分区
0x82表示Linux Swap
0x83表示Linux文件系统ext4等
0xEE表示GPT保护型MBR
*/
名称 字节数 描述 偏移
boot_flag 1 相当于标志位,0x00标示不可引导,0x80可引导(一般是系统盘) 0x00-0x00
start_chs 3 分区的起始CHS地址(由于CHS只能寻址到8G,早已淘汰不用,可空) 0x01-0x03
partition_type 1 分区的类型,0x00表示未使用,0x07表示NTFS文件系统,0x0c表示FAT32,0x83表示Linux文件系统ext4等,0x82表示Linux Swap,0x0F表示扩展分区,0xEE表示GPT保护型MBR 0x04-0x04
end_chs 3 结束CHS地址(可空) 0x05-0x07
start_lba 4 分区的起始扇区号 0x08-0x0B
sector_count 4 分区扇区数 0x0C-0x0F

注意

  1. 由于分区扇区数存储的数据只有4字节,所以只能存储4294967295个扇区,如果每个扇区512字节的话,那每个主分区的大小最多只能是4294967295 × 512字节,约为2T,这就说明了使用MBR格式的分区大小最大只能是2T。随着技术的发展2T的硬盘很常见了,这就导致了MBR格式有点过时了。

DBR分区表

  • 大致了解了DBR分区表后,现在就可以来了解主分区中的,引导扇区DBR的结构。
  • DBR也称为VBR,英文全称为DOS Boot Record / Volume Boot Record,对于不同的文件系统,DBR数据结构是不同的。
  • 这里主要介绍两种文件系统的DBR表,分别是NTFS文件系统和FAT文件系统的DBR表
  • 绝大多数文件系统的DBR在逻辑上就是一个扇区,但是可能会有扩展引导区。这边DBR分区表,只简单的列出其数据结构,详细的讲解在文件系统中会讲解。

image-20251209204025987

FAT文件系统DBR

  • 为了先做完取证实验,就先来学习FAT文件系统的DBR,这里对着别人博客的结构体敲一遍吧。首先先来看这个结构体。

  • 先来看看FAT文件系统的DBR总体结构

1
2
3
4
5
6
7
8
typedef struct FAT32_DBR{
BYTE JumpInstruction[3]; // 0x00,跳转指令,通常为0xEB5890,其中58指示了跳转位置,在x86中,58+2就代表了跳转到5A处
BYTE OEMID[8]; // 0x03,厂商标识和OS版本信息
Basic_BPB BPB; // 0x0B BIOS Parameter Block,BIOS参数块.FAT 文件系统中最核心的一块结构
FAT32_Extend_BPB Extend_BPB; // 0x40 扩展BPB
BYTE Boot_Strap[420]; // 0x5A,文件系统引导代码
BYTE endSignature[2]; // 0x01FE,结束标识,固定为0x55 0xAA
}FAT32_DBR, *pFAT32_DBR;
  • Basic_BPB结构体如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
typedef struct Basic_BPB{
unsigned short int Bytes_per_Sector; // 0xB~0xC
BYTE Sectors_per_Cluster; // 0xD
unsigned short int Reserved_Sector; // 0xE~0xF保留扇区
BYTE FATs; //0x10
unsigned short int RootEntry; // 0x11~0x12
unsigned short int Small_Sector; // 0x13~0x14
BYTE Media; // 0x15
unsigned short int Sector_per_FAT_FAT16; // 0x16~0x17
unsigned short int Sector_per_Track; // 0x18~0x19
unsigned short int Heads; // 0x1A~0x1B
unsigned int Hidden_Sector; // 0x1C~0x1F
unsigned int Large_Sector; // 0x20~0x23

FAT32_Sector Fat32_Sector;
}Basic_BPB, *pBasic_BPB;



typedef struct FAT32_Sector{
unsigned int Sectors_per_FAT_FAT32; // 0x24~0x27
unsigned short int Extend_Flag; // 0x28~0x29
unsigned short int FS_Version; // 0x2A~0x2B
unsigned int Root_Cluster_Number; // 0x2C~0x2F
unsigned short int FS_Info_Sector; // 0x30~0x31
unsigned short int Backup_Sector; // 0x32~0x33
BYTE Reserved_Sector[12]; // 0x34~0x3F
}FAT32_Sector, *pFAT32_Sector;
  • FAT32_Extend_BPB结构体
1
2
3
4
5
6
7
8
typedef struct FAT32_Extend_BPB{
BYTE Physical_Drive; // 0x40
BYTE Reserved; // 0x41
BYTE Extend_Signure; // 0x42
unsigned int Vol_Serial; // 0x43~0x46
BYTE Vol_Label[11]; // 0x47~0x51
BYTE System_ID[8]; // 0x52~0x59
}FAT32_Extend_BPB, *pFAT32_Extend_BPB;

NTFS文件系统DBR

  • NTFS文件系统中DBR的结构体如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
typedef struct _NTFSDBR{
BYTE JMP[3]; // 0x0~0x2
BYTE FsID[8]; // 0x3~0xA
unsigned short int bytePerSector; // 0xB~0xC
BYTE secPerCluster; // 0xD
BYTE reservedBytes[2]; // 0xE~0xF
BYTE zeroBytes[3]; // 0x10~0x12
BYTE unusedBytes1[2]; // 0x13~0x14
BYTE mediaType; // 0x15
BYTE unusedBytes2[2]; // 0x16~0x17
unsigned short int secPerTrack; // 0x18~0x19
unsigned short int Heads; // 0x1A~0x1B
unsigned int hideSectors; // 0x1C~0x0x1F
BYTE unusedByte3[4]; // 0x20~0x23
BYTE usedBytes[4]; // 0x24~0x27
unsigned __int64 totalSectors; // 0x28~0x2F
unsigned __int64 MFT; // 0x30~0x37
unsigned __int64 MFTMirror; // 0x38~0x3F
char fileRecord; // 0x40
BYTE unusedBytes4[3]; // 0x41~0x43
char indexSize; // 0x44
BYTE unusedBytes5[4]; // 0x45~0x48
BYTE volumeSerialID64[8]; // 0x49~0x4F
unsigned int checksum; // 0x50~0x53
BYTE bootCode[426]; // 0x54~0x1FD
BYTE endSignature[2]; // 0x1FE~0x1FF
}NTFSDBR, *pNTFSDBR;

EBR分区表

  • 对于EBR分区表,其实比较好理解,它的数据结构其实就和MBR分区表的数据结构一样,只不过引导代码没用就是了(其实是比较少用到),全部都被设置为0
  • 对于EBR分区表的DPT硬盘分区表其实数据结构就与MBRDPT硬盘分区表一样,虽然EBR分区表的DPT也是有四个数组,但是只有两个是有效项,字段也完全一致,并且结束标识符也完全一样。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef struct {
uint8_t boot_flag; // 0x80 = active, 0x00 = inactive
uint8_t start_chs[3]; // 起始 CHS(已基本废弃)
uint8_t partition_type; // 分区类型 (0x07 NTFS, 0x83 Linux, 0xEE GPT)
uint8_t end_chs[3]; // 结束 CHS
uint32_t start_lba; // 起始 LBA
uint32_t sector_count; // 分区扇区数
} MBR_PartitionEntry;

MBR_PartitionEntry DPT[4];

/*
partition_type这边还是需要详细说明一下的
0x00表示未使用
0x01表示FAT12
0x04、0x0E表示FAT16
0x07表示NTFS、exFAT文件系统
0x0B、0x0c表示FAT32文件系统
0x05、0x0F表示扩展分区
0x82表示Linux Swap
0x83表示Linux文件系统ext4等
0xEE表示GPT保护型MBR
*/

GPT分区表

  • 参考文章:全局唯一标识分区表 - 维基百科,自由的百科全书

  • GPT的全称为GUID Partition Table,其缩写就是GPT,中文名就被称之为全局唯一标识分区表。是使用全局唯一标识符(GUID,也称通用唯一标识符)对物理计算机存储设备的分区表进行布局的标准。

  • GPT分区表是统一可扩展固件接口(UEFI)标准的一部分,因为MBR分区表的限制,使得GPT分区表的出现。

MBR分区表的限制

  1. MBR分区表不支持容量大于2.2TB的分区。不过一些硬盘制造商可以通过扩大扇区字节数,使得MBR分区表所支持的容量达到16TB
  2. MBR分区表不支持超过四个的分区。不过可以通过扩展分区表可以绕过这个限制使得分区数能超过4个。

GPT分区表的改善

  1. GPT分区表可以支持容量到9.4ZB大小的分区(也就是26412^{64}-1个扇区×512512字节/扇区)。
  2. GPT分区表可以支持128个分区,因为它有128个分区表项。
  • 这里再大致介绍一下GPT分区表各个扇区之间的一些结构分布,以便能快速定位到一些扇区。

img

  • 直接创建一个虚拟磁盘,使用GPT分区表。

image-20251217093030664

  • 对该磁盘进行初始化操作,并且使用GPT分区表。

image-20251217093103852

image-20251217093126974

  • 直接分离该虚拟磁盘,然后使用Winhex查看,就会看到保留MBR的相关数据

image-20251217093222707

image-20251217094711570

保留MRB

  • 为了兼容BIOS启动,在BIOS启动的时候不破坏GPT分区表,这就使得第一个扇区(也就是物理计算机存储设备的第一个512字节)存放的仍然是MBR,在GPT分区表中该MBR被称为保留MBR
  • 保留MBR中有数据会标识该MBR是保留MBR,关键数据如下。

image-20251217095504030

GPT头

  • 接下来这个GPT头才是比较重要的东西,因为MBR在前面已经比较详细的学习过了,所以保留型MBR就找找关键的特征即可。GPT头是位于LBA1,也就是磁盘开头第二个扇区这边。

image-20251217101326441

  • 下面就来介绍一下GPT头的数据结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct _GPTHeader{
BYTE signature[8]; // 0x0~0x7 GPT头标识,已经是固定好的 EFI PART
unsigned int revision; // 0x8~0xB 版本号
unsigned int headerSize; // 0xC~0xF 头的大小(是指有效数据)
unsigned int headerCRC32; // 0x10~0x13 头校验
unsigned int reserved; // 0x14~0x17 保留字段
unsigned __int64 currentLBA; // 0x18~0x1F 当前GPT Header所在LBA
unsigned __int64 backupLBA; // 0x20~0x27 备份GPT头所在的LBA
unsigned __int64 firstUsableLBA; // 0x28~0x2F 第一个可用的数据扇区
unsigned __int64 lastUsableLBA; // 0x30~0x37 最后一个可用的数据扇区
BYTE diskGUID[16]; // 0x38~0x47 磁盘的GUID
unsigned __int64 partitionEntryLBA; // 0x48~0x4F 分区表的起始LBA
unsigned int numberOfPartitionEntries; // 0x50~0x53 分区表项数量
unsigned int sizeOfPartitionEntry; // 0x54~0x57 单个分区表项的大小
unsigned int partitionEntryArrayCRC32; // 0x58~0x5B 分区表校验,对整个分区项数组计算CRC32
BYTE reserved2[420]; // 0x5C~0x200 填充字段也算是保留字段
}GPTHeader, *pGPTHeader;

image-20251217152941257

image-20251217162254898

image-20251217162656894

GPT分区表项

  • 由于之前没有对磁盘进行分区,导致GPT分区表项并没有数据,现在将该虚拟磁盘进行挂载,然后进行分区。新建一个FAT32的分区其大小为50MB

image-20251217165200783

image-20251217165214707

  • 接下来查看一下取消附加虚拟磁盘,再次使用Winhex打开这个文件,发现第二个扇区有数据了,数据如下:

image-20251217170143011

  • GPT分区表项数据结构如下:
1
2
3
4
5
6
7
8
typedef struct _GPTPartitionEntry{
BYTE partitionTypeGUID[16]; // 0x0~0xf 分区类型GUID
BYTE uniquePartitionGUID[16]; // 0x10~0x1f 分区GUID
unsigned __int64 startingLBA; // 0x20~0x27 起始LBA
unsigned __int64 endingLBA; // 0x28~0x2f 结束LBA
unsigned __int64 attributes; // 0x30~0x37 分区属性
BYTE partitionName[72]; // 0x38~0x7f 分区名称,可以包括36个UTF-16
}

image-20251217170552001

image-20251217170702319

文件系统

这里主要介绍FAT32文件系统和NTFS文件系统,下图还有一下常见的文件系统可以看看

image-20251215024431781

簇与文件系统

FAT32文件系统

  • 参考博客:FAT32文件系统解析-CSDN博客

  • 参考博客:FAT32系统文件详解 - 知乎

  • 下图是FAT分区的主要结构,其主要结构分为三个部分:

    • 保留区:其中DBR位于保留区的第一个扇区,其中如果该文件系统是FAT32文件系统的话,那么该保留区的第二个扇区就会是FSinfo(FSinfo这个并没有在图中画出来,其实保留区还存放着FAT32的备份文件)
    • FAT1:文件分配表,
    • FAT2:文件分配表,通常用做FAT1的备份文件
    • 根目录:无描述

image-20251211174432273

注:这里只详细讲解FAT32,讲解完FAT32之后基本上FAT12和FAT16都能理解。

FAT32的保留区

这里直接来学习FAT32保留区的一些相关数据结构,熟悉之先来创建一个FAT32以真实的FAT32文件系统来进行学习。

  • 使用Windows自带的磁盘管理创建一个虚拟磁盘VHD

image-20251215014553120

  • 具体选项按照规定的来,大小就50MB

image-20251215014626599

  • 创建好后磁盘管理器会自动将这个虚拟磁盘挂载起来,但是挂载的这个磁盘还是空数据,连分区表都没有创建。

image-20251215014733290

  • 现在先进行初始化磁盘,为了方便学习FAT32文件系统,这里就选用简单的MBR分区表

image-20251215014818591

image-20251215014857429

  • 初始化之后就新建简单卷即可。

image-20251215014927087

  • 一直下一步直到出现下图这个界面

image-20251215015026789

image-20251215015037700

  • 点击完成后一个带有FAT32文件系统的虚拟磁盘就创建好了

image-20251215015112234

  • 接下来使用Winhex查看这个虚拟磁盘的二进制数据,首先第0个扇区定位到的是MBR分区表结构,通过第一个分区表可以快速定位FAT32的起始扇区

image-20251215015342876

  • 定位到FAT32的起始扇区后具体看看这个FAT32文件系统的结构,首先就是FAT32文件系统的分区引导扇区DBR

image-20251215015517181

  • 接下来看下一个扇区,分区的第二个扇区其实就是FSinfo,该扇区存储的数据还是比较少的。

image-20251215015620676

  • 接下来就是该分区的第2个扇区,该扇区只有最后两个字节是0x55、0xAA,但是其余位置都是零。大概猜测也是用作拓展用的一个扇区

image-20251215015917311

  • 接下来的分区中第3、4、5个扇区都是没有数据的,而第6个扇区就有数据,并且该扇区的数据和FAT32的DBR数据是一样的。所以该扇区就是DBR的备份扇区,第7个扇区其实就是FSinfo的备份扇区,而第8个扇区其实就是与该分区的第2个扇区一样。这里就不贴图了。

image-20251215020130548

  • 之后到第12个扇区的时候还会存储一个扇区的数据,该扇区其实是真正的引导代码(算是一段代码),该扇区开头的字符串是出错时的一个错误提示。

image-20251215021140863

FAT32的DBR

  • 大致了解了FAT32的保留分区存放着数据的相关内容,接下来就来看看具体的DBR数据结构的具体内容。首先来回顾一下上面所学的FAT32的DBR结构体
1
2
3
4
5
6
7
8
typedef struct FAT32_DBR{
BYTE JumpInstruction[3]; // 0x00,跳转指令,通常为0xEB5890,其中58指示了跳转位置,在x86中,58+2就代表了跳转到5A处
BYTE OEMID[8]; // 0x03,厂商标识和OS版本信息
Basic_BPB BPB; // 0x0B BIOS Parameter Block,BIOS参数块.FAT 文件系统中最核心的一块结构
FAT32_Extend_BPB Extend_BPB; // 0x40 扩展BPB
BYTE Boot_Strap[420]; // 0x5A,文件系统引导代码
BYTE endSignature[2]; // 0x01FE,结束标识,固定为0x55 0xAA
}FAT32_DBR, *pFAT32_DBR;

image-20251215022258791

  • 这里的最重要的部分其实就是BIOS参数块以及拓展BIOS参数块,接下来逐个看看这些参数块,先重新回顾一下BIOS参数块的结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
typedef struct Basic_BPB{
unsigned short int Bytes_per_Sector; // 0xB~0xC 一个扇区占用的字节数
BYTE Sectors_per_Cluster; // 0xD 一个簇占用的扇区数
unsigned short int Reserved_Sector; // 0xE~0xF 保留扇区
BYTE FATs; //0x10 FAT表的个数,如果值为1表示只有一个FAT表没有备份;值为2表示有一个FAT表备份
unsigned short int RootEntry; // 0x11~0x12 根目录项数量,只有FAT12/FAT16使用此字段。FAT32分区这个字段必须为0
unsigned short int Small_Sector; // 0x13~0x14
BYTE Media; // 0x15媒体描述符,提供有关媒体被使用的信息。值0xF8表示硬盘,0xF0表示高密度的3.5寸软盘.媒体描述符要用于MS—DOS FAT16磁盘,在Windows2000中未被使用
unsigned short int Sector_per_FAT_FAT16; // 0x16~0x17 单张FAT表的扇区数,只被FAT12/FAT16所使用,FAT32分区本字段必须设置为0
unsigned short int Sector_per_Track; // 0x18~0x19 磁盘几何参数,来源与CHS寻址时代,表示每一条磁道上包含多少个分区,目前主要是用于历史兼容填充0x3F
unsigned short int Heads; // 0x1A~0x1B 磁头数,磁盘的磁头数量,这个是CHS寻址模型中的H,现代硬盘一般都是0xFF,几乎没有实际用途
unsigned int Hidden_Sector; // 0x1C~0x1F 隐藏扇区数该分区上引导扇区之前的扇区数,也就是DBR之前的扇区数
unsigned int Large_Sector; // 0x20~0x23 总扇区数,本字段包含FAT32分区中总的扇区数

FAT32_Sector Fat32_Sector; // 在这个结构体中的只有FAT32文件系统才有效(后面就不多说了)
}Basic_BPB, *pBasic_BPB;

// 在这里
typedef struct FAT32_Sector{
unsigned int Sectors_per_FAT_FAT32; // 0x24~0x27 该分区每个FAT表所占的扇区数
unsigned short int Extend_Flag; // 0x28~0x29 拓展表示,该两个字节结构中各位的值为
//位:0~3: 活动FAT数(从0开始计数,而不是1),只有在不使用镜像时才有效
//位4~6: 保留位
//位7: 0值意味着在运行时FAT被映射到所有的FAT,1值表示只有一个FAT是活动的
//位8~15: 保留
unsigned short int FS_Version; // 0x2A~0x2B 文件系统版本,只提供FAT32使用,高字节主要的修订好,而低字节是次要的修订好,本字段支持将来对该FAT32媒体类型进行扩展。如果本字段非零,以前的Windows版本将不支持这样的分区
unsigned int Root_Cluster_Number; // 0x2C~0x2F 根目录的起始簇号,本字段的值一般为2,但不总是如此
unsigned short int FS_Info_Sector; // 0x30~0x31 文件系统信息扇区号,FAT分区的保留区中的文件系统信息FSinfo的扇区号.其值一般为1。在备份引导扇区中保留了FSinfo的备份,但是这个备份不会保持跟新
unsigned short int Backup_Sector; // 0x32~0x33 备份引导扇区,这个非零值表示引导扇区备份的起始扇区.本字段一般为6,建议不要使用其他值
BYTE Reserved_Sector[12]; // 0x34~0x3F 一般全部为0,保留字段为以后扩充使用。
}FAT32_Sector, *pFAT32_Sector;

image-20251215022551588

  • 接下来看看拓展BIOS参数块
1
2
3
4
5
6
7
8
typedef struct FAT32_Extend_BPB{
BYTE Physical_Drive; // 0x40 物理驱动器号与BIOS物理驱动器号有关。软盘驱动器被标识为0x00,物理硬盘被标识为0x80与物理硬盘驱动器无关.一般地,在发出一个INT 13h BIOS调用之前设置该值,具体指定所访问的设备.只有当该设备是一个引导设备时,这个值才有意义
BYTE Reserved; // 0x41 保留字段,一般为0
BYTE Extend_Signure; // 0x42 扩展引导标签,本字段必须要有能被Windows 2000所识别的值0x20或0x29
unsigned int Vol_Serial; // 0x43~0x46 分区序号,在格式化磁盘时所产生的一个随机序号,有助于分区磁盘
BYTE Vol_Label[11]; // 0x47~0x51 卷标,本字段只能使用一次,它被用来保存卷表号。现在卷标被作为一个特殊文件保存在根目录中
BYTE System_ID[8]; // 0x52~0x59 系统ID,FAT32文件系统中一般取位FAT32
}FAT32_Extend_BPB, *pFAT32_Extend_BPB;

image-20251215023236818

FAT32的FSinfo

  • 现在可以来具体学一下FSinfo所存储的数据内容以及数据的具体含义,先来给出FSinfo的这个内容图片。
1
2
3
4
5
0x0~0x3 // 拓展引导签名0x41615252,即RRaA,用于判断该扇区是否是合法FSinfo
0x1E4~0x1E7 // 文件系统信息签名rrAa
0x1E8~0x1EB // 空闲簇数量,当值为0xffffffff时是个未知,需要重新扫描FAT
0x1EC~0x1EF // 下一个可能的空闲簇,下次分配时建议从哪个簇号开始找
// 注意FSinfo不是太重点,该表可能不准也可能不同步,在Windows上挂载FAT32时如果FSinfo无效或者是FAT被重新扫描该表都会被重建

image-20251215142236106

FAT32的FAT1和FAT2

  • 接下来就是FAT1表和FAT2表,看FAT1和FAT2表中数据之前,需要先了解一下如何快速定位FAT1表。
  • 主要使用的是DBR中的数据区去定位FAT1表,其实FAT1表所在扇区就是DBR所在扇区+保留扇区数
  • 首选先从MBR中确定DBR所在的扇区为0x80

image-20251215175826155

  • 然后再到DBR所在的扇区,在该扇区偏移0xE~0xF处读取保留扇区数0x1A9E,这样就可以得到FAT1所在扇区为0x1A9E+0x80=0x1B1E

image-20251215175958615

  • 这样就可以直接定位到FAT1表所在的起始位置,该表的大小也在DBR中有描述,但是目前有数据的地方只有下图所示

image-20251215180240757

  • 对于FAT1表项有以下几点注意的:
    • 表项的第0项一般都是0x0FFFFFF8,表项的第1项一般都是0xFFFFFFFF,表项第0项和第1项均不与实际物理地址对应
    • 从表项第2项开始才与物理地址对应,2号表项为FAT2结束之后开始的簇,这个簇为根目录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 注意:FAT表中每四字节描述一个簇的状态,其中第0项保留,第1项保留
// 第2项表示数据区的第一个簇,第3项表示数据区的第二个簇.实际上第n项其实就是表示数据区n-2个簇号,实际上簇号就是从数据区开始编号的
┌───────────────┐
│ 保留区 │ ← DBR / FSInfo / 备份 DBR
├───────────────┤
│ FAT1 │
├───────────────┤
│ FAT2 │
├───────────────┤
│ 数据区 │ ← 簇 2 从这里开始
└───────────────┘
// 0x00000000表示空闲簇
// 0x00000005表示下一个簇是5
// 0x0FFFFFFF表示文件结束EOF
// 0x0FFFFFF7表示坏簇

// 这里简单举个例子说一下一个文件如何被存储的
FAT[2] = 5
FAT[5] = 8
FAT[8] = 0x0FFFFFFF
这表示这三个簇的内容拼起来可以组成一个完整的文件
也就是簇2+簇5+簇8

image-20251215180725816

  • 接下来我们来定位FAT2表的位置,该位置使用FAT1表起始扇区+FAT表扇区大小就可以确定。而上面以及计算出FAT1表的起始扇区为0x1B1E,然后通过DBR中偏移为0x24~0x27可以得到每个FAT表所占用的扇区数为0x2B1,可以得到FAT2的起始扇区为0x1B1E+0x2B1=0x1DCF

image-20251215181508527

  • 这样就可以得到FAT2表的位置,可以发现FAT2其实就是FAT1的备份文件

image-20251215181713262

FAT32的根目录1

  • 接下来先讲一下确定根目录起始扇区的方法,DBR起始扇区+保留扇区数+每个FAT表扇区数×FAT表个数+(根目录起始簇号-2)*一个簇占用的扇区数,这些都在DBR中给出了。偏移如下:
1
2
3
4
5
BYTE Sectors_per_Cluster; // 0xD  一个簇占用的扇区数
unsigned short int Reserved_Sector; // 0xE~0xF 保留扇区
BYTE FATs; //0x10 FAT表的个数,如果值为1表示只有一个FAT表没有备份;值为2表示有一个FAT表备份
unsigned int Sectors_per_FAT_FAT32; // 0x24~0x27 该分区每个FAT表所占的扇区数
unsigned int Root_Cluster_Number; // 0x2C~0x2F 根目录的起始簇号,本字段的值一般为2,但不总是如此
  • 接下来回去看看DBR扇区的相关数据,可以得到:
    • DBR起始扇区为0x80从MBR中得到
    • 一个簇占用的扇区数为:2
    • 保留扇区数为:0x1A9E
    • FAT表的个数为:2
    • 每个FAT表所占的扇区数:0x2B1
    • 根目录的起始簇号:0x2

image-20251215184603270

  • 从而可以计算出根目录的起始扇区为0x80+0x1A9E+2*0x2B1+(2-2)*2=0x2080,通过计算偏移就可以得到根目录所在位置,由于根目录只占1个簇(从FAT1表中可以得到),所以下图中的数据都是FAT32的根目录的数据。接下来分析一下FAT32根目录的数据结构。

image-20251215185036598

  • 对于根目录目前我们还是不太了解,那么我们就先挂在这个虚拟磁盘文件,然后创建一个123.txt文件,以及一个文件夹document_test,看看这个根目录到底有什么改变。直接将这个虚拟磁盘文件挂在到Windows操作系统中。

image-20251215222238389

  • 然后创建一个名为document_test的文件夹,在根目录创建一个123.txt文件,该txt文件中存放着abcd这个内容。

image-20251215222435092

  • 接下来我们重新用winhex打开这个虚拟磁盘文件,并且直接跳转到根目录这个偏移处看看情况,我们会发现根目录会多出一些数据(根目录簇的第一个扇区和第二个扇区都会新增加数据)

image-20251215223041904

FAT32的根目录2

  • 从上面的在根目录中创建新文件夹我们可以得到,创建一个文件夹根目录这边会多出一些信息。这些信息就是我们要理解的数据结构。
  • 我们回到没有创建文件夹和文件目录的这个结构来说明相关的数据结构,这里有两个数据结构,这两个数据结构主要表明的是文件的属性并且充当索引作用,用于寻找文件内容存储的地方。这两个数据结构分别称为LFN(长文件名目录项)和SFN(标准目录项),它们的结构体具体如下:
    • 当文件名比较短的时候,一般都是只用SFN就可以标识该文件的属性。
    • 当文件名比较长的时候,只用SFN的话没办法将一个文件名存储下来,就要使用LFN+SFN共同标识该文件的属性。如果有多个LFN的话一般结构都是从低地址往高地址LFN(3)->LFN(2)->LFN(1)->SFN
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 标准目录项如下SFN:
typedef struct _FAT_DIR_ENTRY {
uint8_t DIR_Name[11]; // 0x00~0x0A 文件名(8+3,空格填充)
uint8_t DIR_Attr; // 0x0B 文件属性,0x08表示卷标签
uint8_t DIR_NTRes; // 0x0C 保留(NT)
uint8_t DIR_CrtTimeTenth; // 0x0D 创建时间(0.1 秒)
uint16_t DIR_CrtTime; // 0x0E~0x0F 创建时间
uint16_t DIR_CrtDate; // 0x10~0x11 创建日期
uint16_t DIR_LstAccDate; // 0x12~0x13 最后访问日期
uint16_t DIR_FstClusHI; // 0x14~0x15 起始簇号高 16 位
uint16_t DIR_WrtTime; // 0x16~0x17 最后写入时间
uint16_t DIR_WrtDate; // 0x18~0x19 最后写入日期
uint16_t DIR_FstClusLO; // 0x1A~0x1B 起始簇号低 16 位
uint32_t DIR_FileSize; // 0x1C~0x1F 文件大小(字节)因为FAT32使用4个字节表示文件或目录大小,因此当文件或目录大于4Gb时将会溢出,做截断处理。
} FAT_DIR_ENTRY;

// 长文件名目录项如下LFN:
typedef struct _FAT_LFN_ENTRY {
uint8_t LDIR_Ord; // 0x00 序号(0x40 表示最后一项)
uint16_t LDIR_Name1[5]; // 0x01~0x0A Unicode 字符(前 5)
uint8_t LDIR_Attr; // 0x0B 固定为 0x0F
uint8_t LDIR_Type; // 0x0C 固定为 0
uint8_t LDIR_Chksum; // 0x0D 对应 8.3 名的校验和
uint16_t LDIR_Name2[6]; // 0x0E~0x19 Unicode 字符(中 6)
uint16_t LDIR_FstClusLO; // 0x1A~0x1B 固定为 0
uint16_t LDIR_Name3[2]; // 0x1C~0x1F Unicode 字符(后 2)
} FAT_LFN_ENTRY;
  • 介绍完上面的几个结构体,我们回到这个图片中的这四个32字节,这四个32字节有三个SFN文件结构加一个LFN文件结构,也就是存放了三个文件,这三个文件其实是元数据目录项或者占位目录项
1
2
3
4
// 第0个32字节: 卷标(Volume Label)目录项,描述分卷属性的东东,其实就是在C盘、D盘、E盘的名称.
// 第1个32字节: B INFORMATION FAT32 系统信息,也就是FSinfo中的东西
// 第2个32字节: System VolumeS Windows的系统目录,主要用于卷影复制、系统还原点、索引服务、分区唯一ID
// 第3个32字节: System VolumeS Windows的SFN
  • 对于第0个32字节可能这样比较陌生,那们将UTF-8编码换成GBK编码其实就会发现是开头出现新加卷这三个字

image-20251215230246644

image-20251215223801557

FAT32的根目录3

  • 大致了解了根目录中的两个数据结构后,我们看看我们新建的一个文件夹和一个文本文件的对应属性。新建部分如下图所示

image-20251216000608050

NTFS文件系统

  • 参考博客:NTFS解析-CSDN博客

  • 参考博客:

  • NTFS文件系统英文全称为New Technology File System,是微软Windows NT内核系列操作系统支持的磁盘格式,为网络和磁盘配额、文件加密等管理安全特性设计。主要特点有:安全性高可恢复性文件压缩磁盘配额

  • NTFS文件系统是微软设计的,好像也是不开源的,但是网上还是有很多关于NTFS的资料的。

  • 下图是NTFS分区的主要结构大致如下,一共分为五个部分,其中两个部分是备份文件:

    • DBR:分区引导扇区,在NTFS文件系统中也被称为$Boot
    • MFT:主文件表,主要存放着文件和目录的元数据,指向真正存储文件和目录的位置,相当于是一个索引作用。(这个地方在有的文章中还可以再分为Reserved $MFT RecordsUser $MFT Records两部分,但是这里就不细分下去就按MFT来)
    • File System Data:这个就是正常的用户存放的数据
    • MFT备份:该备份在有的NTFS文件系统中是存放在文件系统的中间段(也就是中间存放着MFT备份文件,将文件系统数据区分割成两部分),有的NTFS文件系统中是存放在分区结尾(如下图所示)
    • DBR备份:NTFS的DBR备份位于该分区的最后一个扇区

image-20251214235152548

NTFS的DBR

  • 按照上面创建虚拟磁盘和FAT32分区的的方法,创建一个NTFS分区。然后就可以来查看一下这个磁盘的DBR数据了。

image-20251220033444488

  • 直接利用MBR的第一个分区表确定NTFS的DBR,该DBR在0x80的扇区

image-20251220033649717

  • 直接找到NTFS的DBR扇区,NTFS的DBR扇区如下:

image-20251220033821462

  • 对于NTFS的DBR扇区之后还有一些其他数据,而不是全部都是0,先来看看它截止多少,截止到这个位置0x11200~0x11400

image-20251220035254985

image-20251220035326336

  • 接下来直接列出NTFS中DBR扇区的数据结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
typedef struct _NTFSDBR{
BYTE JMP[3]; // 0x0~0x2 跳转指令
BYTE FsID[8]; // 0x3~0xA NTFS\x20\x20\x20x\20
unsigned short int bytePerSector; // 0xB~0xC 每个扇区的字节数
BYTE secPerCluster; // 0xD 每个簇的扇区数
BYTE reservedBytes[2]; // 0xE~0xF 保留扇区
BYTE zeroBytes[3]; // 0x10~0x12 保留字段,3个字节都是0
BYTE unusedBytes1[2]; // 0x13~0x14 两个未使用字节
BYTE mediaType; // 0x15 媒体类型, 0xF8是固定磁盘,0xF0是软盘
BYTE unusedBytes2[2]; // 0x16~0x17 两个未使用的字节
unsigned short int secPerTrack; // 0x18~0x19 每磁道扇区数
unsigned short int Heads; // 0x1A~0x1B 磁头数
unsigned int hideSectors; // 0x1C~0x0x1F 隐藏扇区数
BYTE unusedByte3[4]; // 0x20~0x23 未使用字节数
BYTE usedBytes[4]; // 0x24~0x27 4个固定字节
unsigned __int64 totalSectors; // 0x28~0x2F 总扇区数
unsigned __int64 MFT; // 0x30~0x37 MFT文件起始簇号
unsigned __int64 MFTMirror; // 0x38~0x3F MFTMirror文件起始簇号
char fileRecord; // 0x40 文件记录,单个MFT文件记录的大小
BYTE unusedBytes4[3]; // 0x41~0x43 未使用字节数
char indexSize; // 0x44 索引缓冲区大小
BYTE unusedBytes5[4]; // 0x45~0x48 未使用字节数
BYTE volumeSerialID64[8]; // 0x49~0x4F 卷序列号
unsigned int checksum; // 0x50~0x53 校验和
BYTE bootCode[426]; // 0x54~0x1FD 引导代码
BYTE endSignature[2]; // 0x1FE~0x1FF 结束标志
}NTFSDBR, *pNTFSDBR;

NTFS的MFT

  • 对于NTFS的MFT区域,这里给出一个比较详细的图片,对每个元数据有个大致的把握,再来逐个元数据研究。

image-20251215003015689

例子