Windows注册表探险 #1:引言与研究成果
作者:Mateusz Jurczyk,Google Project Zero
在2022年5月至2023年12月的20个月期间,我深入审计了Windows注册表,以寻找本地权限提升漏洞。这一切始于一个意外:当时我正在基于Bochs x86模拟器开发一个基于覆盖率的Windows内核模糊测试工具(这是我最喜欢的安全研究工具之一:参见Bochspwn、Bochspwn Reloaded和我早期的字体模糊测试基础设施),并且需要一些二进制格式来测试它。我的首选是PE文件:它们在Windows环境中非常流行,这使得创建初始输入样本语料库变得容易,并且只需一个GetFileVersionInfoSizeW API调用就能轻松开发一个基本的模糊测试工具。测试很成功:尽管我之前在2019年模糊测试过PE文件,但代码覆盖率指导的新元素让我发现了一个全新的漏洞:issue #2281。
对于我的下一个目标,我选择了Windows注册表。这是因为可以通过RegLoadAppKey API(自Windows Vista起)在无需任何特殊权限的情况下从磁盘加载任意注册表配置单元。这些配置单元使用二进制格式并在内核中完全解析,使其成为一个值得注意的本地攻击面。此外,我对注册表的基本工具化也在2016年与James Forshaw一起进行过模糊测试,因此有些熟悉。代码覆盖率支持再次被证明是有用的,导致了issue #2299的发现。但当我开始对该漏洞进行根本原因分析时,我意识到:
- 配置单元二进制格式不太适合简单的位翻转式模糊测试,因为它在结构上很简单,随机突变更有可能使(部分)配置单元无法使用,而不是触发任何有趣的内存安全违规。
- 另一方面,注册表具有许多特性,使其成为进一步研究(尤其是手动审查)的有吸引力的攻击面。它已有30多年的历史,用C语言编写,在内核空间运行,但从用户模式高度可访问,并且它实现的逻辑比我之前想象的要复杂得多。
故事就这样开始了。我没有进一步改进模糊测试工具,而是绕道逆向工程了Windows内核中的注册表实现(内部称为配置管理器),并更多地了解了其内部工作原理。我学得越多,就越着迷,不久之后,我就全身心投入到尽可能多地审计注册表代码的旅程中。本系列博客文章旨在记录我对注册表的了解,包括其基本功能、高级特性、安全属性、典型漏洞类别、特定漏洞的案例研究以及利用技术。
虽然这篇博客是首批宣布这项努力的地方之一,但我已经在2023年10月的Microsoft BlueHat Redmond上做了一个题为“探索Windows注册表作为强大的LPE攻击面”的演讲(参见幻灯片和视频录制)。即将发布的博客文章将比演讲深入得多,但如果你特别好奇,等不及想了解更多,可以随时查看这些资源作为入门。🙂
研究成果
在研究过程中,我在Project Zero漏洞跟踪器中提交了39份漏洞报告,这些报告已被Microsoft修复为44个CVE。这些数字之间存在差异有几个原因:
- 一些单一报告包含了多个问题的信息,例如issue #2375被分配了四个CVE。
- 一些报告组被单个补丁修复,例如issues #2392和#2408被修复为CVE-2023-23420。
- 一份漏洞报告被关闭为WontFix,并且根本没有在安全公告中解决(issue #2508)。
所有报告均根据Project Zero的90天披露截止日期政策提交,Microsoft在所有情况下都成功满足了截止日期。从报告到修复的平均时间为81天。
此外,在2023年11月至2024年1月期间,我报告了20个安全影响较低或不明确的问题,但我认为供应商仍应了解它们。这些报告没有披露截止日期,也没有放在PZ跟踪器上;后来我将它们发布在我们团队的GitHub上。经过评估,Microsoft决定在2024年3月的安全公告中修复其中6个,而其他14个被关闭为WontFix,并可能在未来的Windows版本中解决。
这总计50个CVE,由Microsoft分类为:
- 39 × Windows内核权限提升漏洞
- 9 × Windows内核信息泄露漏洞
- 1 × Windows内核内存信息泄露漏洞
- 1 × Windows内核拒绝服务漏洞
安全服务漏洞的完整摘要如下所示:
| GPZ # | CVE | 标题 | 报告日期 | 修复日期 |
|---|---|---|---|---|
| 2295 | CVE-2022-34707 | Windows内核因注册表配置单元安全描述符引用计数溢出导致的释放后使用 | 2022-05-11 | 2022-08-09 |
| 2297 | CVE-2022-34708 | Windows内核因根安全描述符中未检查的Blink单元索引导致的无效读/写 | 2022-05-17 | |
| 2299 | CVE-2022-35768 | Windows内核处理注册表配置单元中格式错误的安全描述符时的多个内存问题 | 2022-05-20 | |
| 2318 | CVE-2022-37956 | Windows内核注册表子键列表中的整数溢出导致内存损坏 | 2022-06-22 | 2022-09-13 |
| 2330 | CVE-2022-37988 | Windows内核注册表因内存压力下重新分配失败处理不当导致的释放后使用 | 2022-07-08 | 2022-10-11 |
| 2332 | CVE-2022-38037 | Windows内核因注册表配置单元中子键索引叶子的类型混淆导致的内存损坏 | 2022-07-11 | |
| 2341 | CVE-2022-37990 | Windows内核在操作非常长的注册表路径时的多个内存损坏问题 | 2022-08-03 | |
| CVE-2022-38039 | ||||
| CVE-2022-38038 | ||||
| 2344 | CVE-2022-37991 | Windows内核在操作长注册表键和值名称时的越界读取和其他问题 | 2022-08-05 | |
| 2359 | CVE-2022-44683 | Windows内核因NtNotifyChangeMultipleKeys中预定义键处理不当导致的释放后使用 | 2022-09-22 | 2022-12-13 |
| 2366 | CVE-2023-21675 | Windows内核因注册表虚拟化中预定义键处理不足导致的内存损坏 | 2022-10-06 | 2023-01-10 |
| 2369 | CVE-2023-21747 | Windows内核因分页池内存压力下悬空的注册表链接节点导致的释放后使用 | 2022-10-13 | |
| 2389 | CVE-2023-21748 | Windows内核注册表虚拟化与事务不兼容,导致配置单元状态不一致和内存损坏 | 2022-11-30 | |
| 2375 | Windows内核注册表虚拟化键复制功能中的多个问题 | 2022-10-25 | ||
| CVE-2023-21772 | ||||
| CVE-2023-21773 | ||||
| CVE-2023-21774 | ||||
| 2378 | CVE-2023-21749 | Windows内核注册表SID表污染导致锁定问题和其他问题 | 2022-10-31 | |
| CVE-2023-21776 | ||||
| 2379 | CVE-2023-21750 | Windows内核允许使用KEY_READ和KEY_SET_VALUE访问权限删除虚拟化配置单元中的键 | 2022-11-02 | |
| 2392 | CVE-2023-23420 | Windows内核事务性重命名注册表键的子键的多个问题 | 2022-12-07 | 2023-03-14 |
| 2408 | Windows内核在事务性NtRenameKey中对新注册表键名称验证不足 | 2023-01-13 | ||
| 2394 | CVE-2023-23421 | Windows内核事务性注册表键重命名准备/提交阶段的多个问题 | 2022-12-14 | |
| CVE-2023-23422 | ||||
| CVE-2023-23423 | ||||
| 2410 | CVE-2023-28248 | Windows内核CmpCleanupLightWeightPrepare注册表安全描述符引用计数泄漏导致UAF | 2023-01-19 | 2023-04-11 |
| 2418 | CVE-2023-28271 | Windows内核通过注册表KTM事务日志文件泄露内核指针和未初始化内存 | 2023-01-31 | |
| 2419 | CVE-2023-28272 | Windows内核在CmpDoReDoCreateKey/CmpDoReOpenTransKey中操作无效注册表路径时的越界读取 | 2023-02-02 | |
| CVE-2023-28293 | ||||
| 2433 | CVE-2023-32019 | Windows内核KTM注册表事务可能具有非原子性结果 | 2023-03-07 | 2023-06-13 |
| 2445 | CVE-2023-35356 | Windows内核通过差异配置单元访问预定义键实现任意读取 | 2023-04-19 | 2023-07-11 |
| 2452 | Windows内核CmDeleteLayeredKey可能删除预定义墓碑键,导致安全描述符UAF | 2023-05-10 | ||
| 2446 | CVE-2023-35357 | Windows内核可能通过注册表虚拟化引用未备份的分层键 | 2023-04-20 | |
| 2447 | CVE-2023-35358 | Windows内核可能通过差异配置单元引用已回滚的事务键 | 2023-04-27 | |
| 2449 | CVE-2023-35382 | Windows内核重命名分层键时不引用计数安全描述符,导致UAF | 2023-05-02 | 2023-08-08 |
| 2454 | CVE-2023-35386 | Windows内核因注册表.LOG文件解析中的整数溢出导致的越界读取 | 2023-05-15 | |
| 2456 | CVE-2023-38154 | Windows内核注册表配置单元日志恢复的部分成功可能导致状态不一致和内存损坏 | 2023-05-22 | |
| 2457 | CVE-2023-38139 | Windows内核在自我修复期间不重置安全缓存,导致引用计数溢出和UAF | 2023-05-31 | 2023-09-12 |
| 2462 | CVE-2023-38141 | Windows内核将用户模式指针传递给注册表回调,导致竞争条件和内存损坏 | 2023-06-26 | |
| 2463 | CVE-2023-38140 | Windows内核在VrpPostEnumerateKey中泄露分页池内存 | 2023-06-27 | |
| 2464 | CVE-2023-36803 | Windows内核在VrpUpdateKeyInformation中的越界读取和分页池内存泄露 | 2023-06-27 | |
| 2466 | CVE-2023-36576 | Windows内核通过VrpBuildKeyPath中的整数溢出和其他弱点实现容器化注册表逃逸 | 2023-07-07 | 2023-10-10 |
| 2479 | CVE-2023-36404 | Windows内核验证分层键安全性的检查时间/使用时间问题可能导致特权注册表键的信息泄露 | 2023-08-10 | 2023-11-14 |
| 2480 | CVE-2023-36403 | Windows内核注册表虚拟化中的错误锁定导致竞争条件 | 2023-08-22 | |
| 2492 | CVE-2023-35633 | Windows注册表预定义键可能导致混淆代理问题和本地权限提升 | 2023-10-06 | 2023-12-12 |
| 2511 | CVE-2024-26182 | Windows内核因CmpAddSubKeyEx中部分成功处理不当导致的子键列表释放后使用 | 2023-12-13 | 2024-03-12 |
| 无 (MSRC-84131) | CVE-2024-26174 | Windows内核在加载损坏的配置单元时,CmpValidateHiveSecurityDescriptors中对键节点安全的越界读取 | 2023-11-29 | |
| 无 (MSRC-84149) | CVE-2024-26176 | Windows内核在CmpCheckValueList中验证符号链接时的越界读取 | 2023-11-29 | |
| 无 (MSRC-84046) | CVE-2024-26173 | Windows内核允许通过注册表事务在易失键下创建稳定的子键 | 2023-11-30 | |
| 无 (MSRC-84228) | CVE-2024-26177 | Windows内核在事务性重新创建注册表键时,CmpUndoDeleteKeyForTrans中的不安全行为 | 2023-12-01 | |
| 无 (MSRC-84237) | CVE-2024-26178 | Windows内核在CmpLightWeightPrepareSetSecDescUoW中的安全描述符链表混淆 | 2023-12-01 | |
| 无 (MSRC-84263) | CVE-2024-26181 | Windows内核注册表配额耗尽可能导致SAM数据库永久损坏 | 2023-12-11 |
可利用性
软件漏洞通常只有在具有实际安全影响时,才会引起安全社区的进攻/防御方的兴趣。不幸的是,由于注册表相关漏洞在多个层面上的巨大多样性,不可能对所有漏洞的可利用性给出一个笼统的陈述:
- 受影响平台:Windows 10、Windows 11、各种Windows Server版本(32/64位)
- 攻击目标:内核本身、实现注册表回调的驱动程序、特权用户模式应用程序/服务
- 入口点:直接注册表操作、配置单元加载、事务日志恢复
- 最终结果:内存损坏、安全保证破坏、API契约破坏、内存/指针泄露、越界读取、无效/受控单元索引访问
- 问题的根本原因:C语言特性、逻辑错误、错误的引用计数、锁定问题
- 内存损坏的性质:时间性(释放后使用)、空间性(缓冲区溢出)
- 损坏内存的类型:内核池、配置单元数据
- 利用时间:即时,最多几小时
正如我们所见,有多种因素决定了漏洞是如何产生的,以及它们在触发后使系统处于什么状态。然而,为了更好地理解这些发现的影响,我对每个漏洞的可利用性进行了粗略分析,试图根据我目前的知识和经验将其分类为“容易”、“中等”或“困难”利用(这当然是高度主观的)。这些可利用性评级的比例如下图所示:

评级主要基于以下考虑:
- 基于配置单元的内存损坏通常被认为容易利用,而基于池的内存损坏根据漏洞的具体情况被认为是中等/困难。
- 在配置单元空间中触发OOM类型条件是容易的,但完全耗尽内核池则更困难且更具侵入性。
- 逻辑漏洞通常比内存损坏更容易且更可靠地利用。
- 内核本身通常比其他用户模式进程(系统服务等)更容易攻击。
- 直接信息泄露(通过各种渠道泄露内核指针/未初始化内存)通常很容易利用。
- 然而,随机的越界读取,以及对无效/受控单元索引的读取访问,通常很难用于任何有用的目的。
总体而言,似乎超过一半的发现可以实际用于信息泄露或本地权限提升(评级为容易或中等)。更重要的是,其中许多漏洞表现出注册表特定的漏洞类别,可以实现特别独特的利用原语。例如,基于配置单元的内存损坏可以有效地转化为KASLR绕过和完全可靠的任意读/写能力,从而使得使用单个漏洞通过仅数据攻击来危害内核成为可能。为了证明这一点,我成功开发了CVE-2022-34707和CVE-2023-23420的利用程序。在Windows 11上运行其中一个以将权限提升到SYSTEM的结果如下截图所示:

本系列即将发布的文章将向您介绍Windows注册表作为一种系统机制和攻击面,并将深入探讨使用配置单元内存损坏、越界单元索引和其他有趣技术的实际利用。敬请关注!