Windows注册表regf文件格式深度解析

本文深入解析Windows注册表regf二进制文件格式的技术细节,涵盖基础块、存储桶、单元格结构、安全描述符等核心组件,并探讨其独特的内存磁盘统一设计带来的安全影响与历史漏洞分析。

Windows注册表regf文件格式深度解析

概述

Windows注册表使用一种称为"regf"的二进制格式来编码注册表 hive 文件,从 Windows NT 3.1 一直到现代的 Windows 11 都使用这一格式[citation:1]。与其他常见文件格式不同,regf 格式的特殊之处在于它同时在磁盘和内存中表示注册表子树,而不是像文档、图像、视频等格式那样设计为在磁盘上高效存储数据,然后在读写时解析为不同的内存表示形式。

这种独特的方法旨在绕过重新解析步骤(可能是为了优化内存/磁盘同步过程),并将两种数据类型编码统一为一种相对紧凑且易于操作的格式。这也解释了为什么 hive 本身不支持压缩(但客户端当然可以在注册表中存储压缩数据)。

基本结构

整体文件布局

在最低级别上,hive 中的数据被组织成 4 KiB(0x1000 字节)的块,这恰好是 x86 架构中标准内存页面的大小:

  • 第一个 4 KiB 始终对应头部(也称为基础块)
  • 后面跟着一个或多个存储桶(bin),每个存储桶的长度是 4 KiB 的倍数

存储桶(Bins)结构

每个存储桶以一个 32 字节(0x20)的头部开始,后面跟着一个或多个完全填充该存储桶的单元格(cell):

1
2
3
4
5
6
7
8
9
// 存储桶头部结构
typedef struct _HBIN {
    DWORD Signature;      // 签名 'hbin'
    DWORD FileOffset;     // 存储桶在文件中的偏移量
    DWORD Size;           // 存储桶大小
    DWORD Reserved1[2];   // 保留字段
    LARGE_INTEGER TimeStamp; // 时间戳
    DWORD Spare;          // 备用字段
} HBIN;

单元格(Cells)系统

单元格是注册表 hive 中最小的数据单元,它们是任意长度的连续缓冲区。每个单元格由一个带符号的 32 位大小标记和单元格数据组成:

  • 状态表示:正大小值表示空闲单元格,负值表示已分配单元格
  • 大小约束:大小值必须为 8 的倍数(即其三个最低有效位清零)
  • 空间利用:存储桶头部后紧跟紧密打包的单元格(无间隙),完全填充存储桶空间

基础块(Base Block)详解

基础块由 Windows 内核中的 _HBASE_BLOCK 结构表示,其布局包含多个关键字段:

 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
// 基础块关键字段
typedef struct _HBASE_BLOCK {
    DWORD Signature;           // 签名 'regf'
    DWORD Sequence1;           // 序列号1
    DWORD Sequence2;           // 序列号2
    LARGE_INTEGER TimeStamp;   // 时间戳
    DWORD Major;               // 主版本号
    DWORD Minor;               // 次版本号
    DWORD Type;                // 类型
    DWORD Format;              // 格式
    DWORD RootCell;            // 根键单元格索引
    DWORD Length;              // hive总长度
    DWORD Cluster;             // 簇大小
    CHAR FileName[64];         // 文件名
    GUID RmId;                 // 资源管理器ID
    GUID LogId;                // 日志ID
    DWORD Flags;               // 标志位
    GUID TmId;                 // 事务管理器ID
    DWORD GuidSignature;       // GUID签名
    ULONGLONG LastReorganizeTime; // 最后重组时间
    DWORD Reserved1[83];       // 保留字段1
    DWORD CheckSum;            // 校验和
    DWORD Reserved2[882];      // 保留字段2
    GUID ThawTmId;             // 解冻事务管理器ID
    GUID ThawRmId;             // 解冻资源管理器ID
    GUID ThawLogId;            // 解冻日志ID
    DWORD BootType;            // 启动类型
    DWORD BootRecover;         // 启动恢复
} HBASE_BLOCK;

关键字段说明

  1. Sequence1 和 Sequence2:这两个32位数字在内核执行注册表写操作时更新,用于跟踪hive的一致性状态。如果加载时这两个值相等,则hive处于"干净"状态;如果不相等,则表示未所有挂起的更改已完全提交到主hive文件,必须根据伴随的.LOG/.LOG1/.LOG2文件应用其他修改。

  2. Major 和 Minor:头部中最重要的字段之一,代表hive的主版本和次版本。唯一有效的主版本是1,次版本历史上是0到6之间的整数。现代Windows系统中主要使用1.3、1.5和1.6版本。

  3. RootCell:根键的单元格索引(在hive文件中的偏移量),是配置管理器解析hive树的起点。根单元格在许多方面都很特殊:它是hive中唯一没有父键的键、不能被删除或重命名、其安全描述符被视为安全描述符链表的头部。

  4. Length:指定hive中所有存储桶的累积大小(即文件大小减去4096字节的头部大小),限制为0x7FFFE000,反映了hive稳定存储部分(驻留在磁盘上的hive部分)的约2 GiB容量。

  5. CheckSum:存储在偏移0x1FC处的校验和字段,是头部前508字节的32位XOR校验和。如果计算值等于0xFFFFFFFF(-1),则校验和设置为0xFFFFFFFE(-2);如果计算值为0x0,则校验和设置为0x1。

单元格类型及其关系

注册表hive仅使用七种不同的单元格类型来表示注册表中的各种数据结构:

单元格类型 描述 结构
键节点 表示单个注册表键及其关联的元数据 _CM_KEY_NODE
子键索引 键节点单元格索引的可变长度列表,表示特定键的子键 _CM_KEY_INDEX
安全描述符 定义一个或多个键的访问控制信息 _CM_KEY_SECURITY
键值 定义与键关联的单个值,包括其名称、类型、数据长度和对包含实际数据的单元格的引用 _CM_KEY_VALUE
大数据 用于在hive版本1.4及更高版本中存储超过16,344字节(~16 KiB)的值数据 _CM_BIG_DATA
值列表和块列表 这些单元格是32位单元格索引的简单数组,用于存储与键关联的值列表和大型值数据的块列表 -
数据单元格 这些单元格存储与键和值关联的原始数据 -

键节点深入分析

键节点是最重要和最复杂的单元格类型,使用 _CM_KEY_NODE 结构表示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 键节点结构关键字段
typedef struct _CM_KEY_NODE {
    WORD Signature;              // 签名 'nk'
    WORD Flags;                  // 标志位
    LARGE_INTEGER LastWriteTime; // 最后写入时间
    BYTE AccessBits;             // 访问位
    BYTE LayerSemantics;         // 层语义(2位)
    BYTE InheritClass;           // 继承类(1位)
    WORD Spare2;                 // 备用字段
    DWORD Parent;                // 父键单元格索引
    DWORD SubKeyCounts[2];       // 子键计数(稳定和易失)
    DWORD SubKeyLists[2];        // 子键列表(稳定和易失)
    CHILD_LIST ValueList;        // 值列表
    DWORD Security;              // 安全描述符单元格索引
    DWORD Class;                 // 类数据单元格索引
    DWORD MaxNameLen;            // 最大名称长度
    DWORD MaxClassLen;           // 最大类长度
    DWORD MaxValueNameLen;       // 最大值名称长度
    DWORD MaxValueDataLen;       // 最大值数据长度
    DWORD WorkVar;               // 工作变量
    WORD NameLength;             // 名称长度
    WORD ClassLength;            // 类长度
    WCHAR Name[1];               // 键名称(可变长度)
} CM_KEY_NODE;

重要标志位

键节点中的Flags字段包含多个安全相关的标志:

  • KEY_HIVE_EXIT (0x0002):指示该键是另一个注册表hive的挂载点
  • KEY_HIVE_ENTRY (0x0004):指示给定键是hive的入口,即hive的根键
  • KEY_SYM_LINK (0x0010):指示该键是符号链接
  • KEY_COMP_NAME (0x0020):指示键名仅由ASCII字符组成,因此已被"压缩"以适应每个16位宽字符中的两个8位字符

安全描述符处理

安全描述符在强制执行对注册表中存储信息的访问控制方面起着核心作用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 安全描述符结构
typedef struct _CM_KEY_SECURITY {
    WORD Signature;           // 签名 'sk'
    WORD Reserved;            // 保留字段
    DWORD Flink;              // 前向链接
    DWORD Blink;              // 后向链接
    DWORD ReferenceCount;     // 引用计数
    DWORD DescriptorLength;   // 描述符长度
    BYTE Descriptor[1];       // 安全描述符数据(可变长度)
} CM_KEY_SECURITY;

安全描述符的关键特性

  1. 全局链表:稳定空间中的所有安全描述符被组织成一个双向链表,从根键的描述符开始
  2. 引用计数:每个描述符都包含一个引用计数,跟踪有多少键引用该安全描述符
  3. 验证过程:在加载hive时,内核会验证安全描述符的二进制格式有效性、链表一致性和引用计数的正确性
  4. 自愈能力:如果发现不一致,系统会尝试修复,如将描述符集重置为仅包含根描述符的单条目列表

Hive加载和清理过程

Windows注册表具有自愈特性:系统会尽力成功加载hive,即使它部分损坏。这种设计的目的是使机制能够抵抗磁盘上的随机数据损坏,因为在启动早期无法加载系统hive会使Windows无法使用。

hive加载过程涉及多个层次的验证:

  1. Hive头部、存储桶和单元格布局一致性:验证hive版本、长度、根单元格索引、头部中的标志等
  2. 单元格内一致性:验证每个单元格的大小是否足够存储其数据、正确的签名、有效的标志组合等
  3. 单元格间一致性:验证单元格索引中的有效引用、单独单元格中冗余数据之间的一致性等
  4. 高级结构的结构正确性:验证安全描述符链表的一致性、子键在所有子键索引中的字典顺序等
  5. 全局hive属性正确性:验证每个hive始终包含至少一个键(根键)和至少一个安全描述符等

当检测到错误时,系统会尝试以下一种或多种方式处理:

  • 存储桶重建
  • 单元格重建
  • 小的直接修复
  • 单值删除
  • 整个值列表删除
  • 单键删除
  • 整个子键索引删除
  • 安全描述符列表重置
  • 整个hive拒绝

安全影响和历史漏洞

regf格式的独特设计(统一的内存/磁盘表示)带来了一系列安全挑战,并成为许多历史漏洞的促成因素。在整个注册表相关代码库中,只有约10.4%对应于hive内存管理,但其中发现了多个关键漏洞:

  1. CVE-2022-37988:HvReallocateCell中的逻辑错误导致内存损坏
  2. CVE-2024-43452:从远程网络共享加载hive时的双重获取漏洞
  3. CVE-2022-38037:由于hive版本而不是列表本身的二进制表示来确定子键列表的格式
  4. CVE-2023-35357 和 CVE-2023-35358:无效单元格索引处理问题

安全描述符引用计数错误尤其危险,可能导致use-after-free场景。近年来,Microsoft已逐步在相关内部函数中添加了溢出保护,提高了系统的安全性。

总结

regf格式代表了Windows注册表的核心技术基础,其独特的内存磁盘统一设计既带来了性能优势,也引入了特殊的安全挑战。通过深入了解其内部结构、验证过程和历史漏洞,安全研究人员可以更好地评估注册表相关代码的安全性,并发现新的潜在攻击面。

这种格式的复杂性和自愈能力体现了Windows注册表系统的成熟度和韧性,同时也提醒我们,即使是最基础的系统组件也可能包含深刻的技术细节和安全考虑。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计