Windows注册表冒险 #8:hive内存损坏的实战利用
作者:Mateusz Jurczyk,Google Project Zero
在上一篇博客文章中,我们重点讨论了注册表的一般安全分析以及如何有效寻找其中的漏洞。在这里,我们将把注意力转向基于hive的内存损坏漏洞的利用,即那些允许攻击者覆盖活动hive映射中数据的漏洞。这是一类Windows注册表特有的问题,但也具有足够的通用性,使得这里描述的技术适用于我过去的17个漏洞,以及未来任何类似的漏洞。正如我们所知,hive在低级内存管理(如何以及在何处映射到内存)、自定义分配器处理已分配和释放的内存块以及存储数据的性质方面表现出非常特殊的行为。所有这些都使得从攻击性安全的角度来看,利用这类漏洞尤其有趣,这也是我想在这里详细描述的原因。
与任何其他类型的内存损坏类似,绝大多数hive内存损坏问题可以分为两类:空间违规(如缓冲区溢出)和时间违规(如释放后使用条件)。
在这篇文章中,我们将选择最有希望的漏洞候选,然后为其创建一个分步利用程序,将系统普通用户的权限从中等完整性级别提升到系统级权限。我们的目标是Windows 11,并额外要求成功绕过所有现代安全缓解措施。我之前在OffensiveCon 2024上以“Windows内核中注册表漏洞的实战利用”为题介绍了这个主题,这篇博客文章可以看作是对那里展示信息的补充和扩展。对该主题深感兴趣的读者鼓励查看该演示的幻灯片和录音。
从哪里开始:潜在选项的高级概述
让我们先回顾一些关键点。正如你可能记得的,Windows注册表单元格分配器(即内部的HvAllocateCell、HvReallocateCell和HvFreeCell函数)的操作方式非常有利于利用。首先,它完全缺乏任何针对内存损坏的防护措施;其次,它没有任何随机性元素,使其行为完全可预测。因此,不需要采用任何“hive喷洒”或其他典型的堆利用技术——如果我们能在测试机器上实现所需的单元格布局,它将在其他计算机上无需额外步骤即可重现。一个潜在的例外可能是对HKLM和HKU中的全局共享hive进行攻击,因为我们不知道它们的初始状态,并且可能由于其他应用程序同时执行的操作而产生一些随机性。然而,即使这也不应构成特别重大的挑战。我们可以安全地假设安排hive的内存布局是直接的,并且如果我们在其中具有某种内存损坏能力,只要有耐心和实验,我们最终将能够覆盖任何类型的单元格。
经典内存损坏漏洞的利用通常涉及以下步骤: 初始内存损坏原语 → ??? → 利润(以任意代码执行、权限提升等形式)
利用开发人员的任务是填补此列表中的空白,设计导致期望目标的中间步骤。通常有几个这样的中间步骤,因为考虑到当前的安全状态和缓解措施,漏洞很少直接从内存损坏一步导致代码执行。相反,采用逐步开发更强原语的策略,其中最终链可能如下所示:
在此模型中,第二/第三步是通过找到另一个有趣的对象,安排它分配在被覆盖的缓冲区附近,然后以创建新原语的方式损坏它来实现的。然而,在hive的情况下,我们在这方面的选项似乎有限:我们假设我们可以完全控制hive中任何单元格的表示,但问题是从利用的角度来看,其中没有立即有趣的数据。例如,regf格式不包含任何直接影响控制流的数据(例如函数指针),也不包含虚拟内存中的任何其他地址,这些地址可以通过某种聪明的方式被覆盖以改进原始原语。下图描绘了我们当前的情况:
这是否意味着hive内存损坏不可利用,它唯一允许的是在孤立的hive内存视图中进行数据损坏?不完全。在以下小节中,我们将仔细考虑各种想法,即控制内部hive数据如何对系统的整体安全性产生更广泛的影响。然后,我们将尝试确定可用方法中哪一种最适合在实际利用中使用。
内部hive损坏
让我们首先调查覆盖内部hive数据是否像最初看起来那样不切实际。
在特权系统hive中执行仅hive攻击
需要明确的是,说hive不包含任何值得覆盖的数据并不完全准确。如果你仔细想想,情况恰恰相反——注册表存储了大量系统配置、注册服务信息、用户密码等。唯一的问题是所有这些关键数据都位于特定的hive中,即那些挂载在HKEY_LOCAL_MACHINE下的hive,以及HKEY_USERS中的一些(例如,HKU.Default,对应于System用户的私有hive)。要能够通过仅损坏regf格式数据(而不访问其他内核内存或实现任意代码执行)成功执行攻击并提升权限,必须满足两个条件:
- 漏洞必须仅通过API/系统调用触发,并且不需要对hive的二进制控制,因为我们显然对任何系统hive都没有这种控制。
- 目标hive必须包含至少一个具有足够宽松访问权限的键,允许非特权用户创建值(KEY_SET_VALUE权限)和/或新的子键(KEY_CREATE_SUB_KEY)。根据特定漏洞的先决条件,可能还需要其他一些访问权限。
在上述两点中,第一点肯定更难满足。许多hive内存损坏漏洞是由hive结构中的奇怪、不可预见的状态引起的,这种状态只能“离线”生成,从完全控制给定文件开始。仅API漏洞似乎相对罕见:例如,在我的17个基于hive的内存损坏案例中,理论上只有不到一半(具体是8个)可以仅通过对现有hive的操作触发。此外,仔细观察发现,其中一些不满足针对系统hive所需的其他条件(例如,它们仅影响差异hive),或者非常不切实际,例如需要分配超过500 GB的内存,或需要许多小时才能触发。实际上,在广泛的漏洞中,只有两个非常适合直接攻击系统hive:CVE-2023-23420(在报告的“操作事务性重命名键的子键”部分讨论)和CVE-2023-23423(在“释放键节点的浅拷贝与CmpFreeKeyByCell”中讨论)。
关于第二个问题——可写键的可用性——攻击者的情况要好得多。这有三个原因:
- 要成功对系统键执行仅数据攻击,我们通常不限于一个特定的hive,而是可以选择任何适合我们的hive。利用大多数(如果不是全部)挂载在HKLM下的hive中的hive损坏将使攻击者能够提升权限。
- Windows内核内部通过首先在注册表树中进行完整路径查找来实现键打开过程,然后才检查所需的用户权限。访问检查仅针对特定键的安全描述符执行,而不考虑其祖先。这意味着为键设置过于宽松的安全设置会自动使其易受攻击,因为根据此逻辑,它不会从其祖先键获得额外保护,即使它们具有更严格的访问控制。
- HKLM\SOFTWARE和HKLM\SYSTEM hive中存在大量用户可写键。它们不存在于HKLM\BCD00000000、HKLM\SAM或HKLM\SECURITY中,但正如我上面提到的,只有一个这样的键就足以成功利用。
要找到此类公开可访问键的具体示例,需要编写自定义工具。该工具应首先递归列出低级\Registry\Machine和\Registry\User路径中的所有现有键,同时以最高可能的权限操作,理想情况下作为System用户。这将确保该进程可以看到注册表树中的所有键——即使是那些隐藏在受限父键后面的键。不值得尝试枚举\Registry\A的子键,因为对它的任何引用都被Windows内核无条件阻止。类似地,\Registry\WC可能可以跳过,除非对容器化应用程序使用的差异hive攻击感兴趣。一旦我们有了所有键的完整列表,下一步是验证哪些键可由非特权用户写入。这可以通过读取它们的安全描述符(使用RegGetKeySecurity)并手动检查它们的访问权限(使用AccessCheck)来实现,或者通过完全将此任务委托给内核,并仅以普通用户权限尝试以所需权限打开每个键。无论哪种情况,我们最终应该能够获得可用于损坏系统hive的潜在键列表。
根据我的测试,在当前Windows 11系统上,HKLM内大约有1678个键授予普通用户子键创建权限。其中,1660个位于HKLM\SOFTWARE,18个位于HKLM\SYSTEM。一些例子包括:
- HKLM\SOFTWARE\Microsoft\CoreShell
- HKLM\SOFTWARE\Microsoft\DRM
- HKLM\SOFTWARE\Microsoft\Input\Locales(及其一些子键)
- HKLM\SOFTWARE\Microsoft\Input\Settings(及其一些子键)
- HKLM\SOFTWARE\Microsoft\Shell\Oobe
- HKLM\SOFTWARE\Microsoft\Shell\Session
- HKLM\SOFTWARE\Microsoft\Tracing(及其一些子键)
- HKLM\SOFTWARE\Microsoft\Windows\UpdateApi
- HKLM\SOFTWARE\Microsoft\WindowsUpdate\UX
- HKLM\SOFTWARE\WOW6432Node\Microsoft\DRM
- HKLM\SOFTWARE\WOW6432Node\Microsoft\Tracing
- HKLM\SYSTEM\Software\Microsoft\TIP(及其一些子键)
- HKLM\SYSTEM\ControlSet001\Control\Cryptography\WebSignIn\Navigation
- HKLM\SYSTEM\ControlSet001\Control\MUI\StringCacheSettings
- HKLM\SYSTEM\ControlSet001\Control\USB\AutomaticSurpriseRemoval
- HKLM\SYSTEM\ControlSet001\Services\BTAGService\Parameters\Settings
如我们所见,有相当多的可能性。列表中的第二个键HKLM\SOFTWARE\Microsoft\DRM在过去有些流行,因为它之前被James Forshaw用来演示他在2019-2020年发现的两个漏洞(CVE-2019-0881,CVE-2020-1377)。随后,我也用它作为触发与注册表虚拟化相关的某些行为的方式(CVE-2023-21675,CVE-2023-21748,CVE-2023-35357),并作为填充SOFTWARE hive到其容量的潜在途径,从而作为利用另一个漏洞(CVE-2023-32019)的一部分导致OOM条件。此键的主要优点是它存在于所有现代系统版本中(至少从Windows 7开始),并且它向所有用户(Everyone组,也称为World或S-1-1-0)授予广泛权限。上面提到的其他键也允许普通用户写入操作,但它们通常通过其他可能更受限制的组(如Interactive(S-1-5-4)、Users(S-1-5-32-545)或Authenticated Users(S-1-5-11))这样做,这可能需要注意。
除了全局系统hive,我还发现了HKCU\Software\Microsoft\Input\TypingInsights键在每个用户hive中都存在的奇怪情况,它允许系统所有其他用户读写访问。我在2023年12月向微软报告了它(报告链接),但被认为严重性低,到目前为止尚未修复。这个决定有些可以理解,因为该行为对系统安全没有直接、严重的后果,但它仍然可以作为有用的利用技术。由于任何用户都可以在任何其他用户的用户hive中打开一个键进行写入,他们获得了以下能力:
- 填充该hive的整个2 GiB空间,导致DoS条件(用户及其应用程序无法写入HKCU),并可能启用与hive内错误处理OOM条件相关的漏洞利用。
- 不仅写入HKCU本身的“TypingInsights”键,还写入覆盖在其上的差异hive中的任何对应键。这提供了攻击以该用户权限在应用程序/服务器隔离中运行的应用程序的机会。
- 不仅对系统hive执行基于hive的内存损坏攻击,还对特定用户的hive执行,允许更横向的权限提升场景。
如所示,即使单个注册表键的安全描述符中存在看似微小的弱点,也可能对系统安全产生重大后果。
总之,使用hive内存损坏攻击系统hive当然是可能的,但需要找到一个非常好的漏洞,该漏洞可以在现有键上触发,无需加载自定义hive。这是一个很好的起点,但也许我们可以找到更通用的技术。
滥用regf不一致性触发内核池损坏
虽然内存中的hive映射在某种程度上是隔离和自包含的,但它们并非存在于真空中。Windows内核在内核池空间中分配和管理许多额外的注册表相关对象,如博客文章#6中讨论的。这些对象通过数据缓存实现优化,并帮助实现仅通过对hive空间的操作无法实现的某些功能(例如事务、分层键)。其中一些对象是长期存在的,只要hive挂载就持久存在于内存中。其他缓冲区在同一系统调用内分配并立即释放,仅用作临时数据存储。所有这些对象的内存安全与hive映射中相应数据的一致性密切相关。在内核在CmCheckRegistry和相关函数中仔细验证hive有效性之后,它假设注册表hive的数据与其自身结构和关联的辅助结构保持一致。
对于潜在攻击者来说,这意味着hive内存损坏可能升级为某种形式的池损坏。这为利用提供了更广泛的选项,因为内核的各个部分使用了各种池分配。事实上,我甚至在我的微软报告中利用了这种行为:在每次安全描述符的释放后使用情况下,我会启用特殊池并通过_CM_KEY_CONTROL_BLOCK.CachedSecurity字段触发对池上该描述符缓存副本的引用。我这样做是因为访问池上已释放的分配比访问hive中已释放但仍映射的单元格更容易生成可靠可重现的崩溃。
然而,这肯定不是通过修改regf格式的内部数据导致池内存损坏的唯一方法。另一个想法是,例如,在hive中创建一个非常长的“大数据”值(在版本≥1.4的hive中超过~16 KiB),然后导致_CM_KEY_VALUE.DataLength与_CM_BIG_DATA.Count字段不一致,该字段表示支持缓冲区中的16千字节块数。如果我们查看内部CmpGetValueData函数的实现,很容易看到它基于前一个值分配一个分页池缓冲区,然后基于后一个值将数据复制到其中。因此,如果我们将_CM_KEY_VALUE.DataLength设置为小于16344 × (_CM_BIG_DATA.Count - 1)的数字,那么下次请求值数据时,将发生线性池缓冲区溢出。
这种原语是有希望的,因为它打开了针对比以前可能更广泛的内存对象的大门。下一步可能涉及找到合适的对象立即放置在被覆盖的缓冲区之后(例如,2020年这篇文章中提到的管道属性),然后损坏它以实现更强大的原语,如任意内核读/写。简而言之,这种攻击将归结为相当通用的基于池的内存损坏利用,这是一个在现有资源中广泛讨论的主题。我们不会在这里进一步探讨,而是鼓励感兴趣的读者自行调查。
跨hive内存损坏
到目前为止,在我们的分析中,我们假设使用基于hive的内存损坏漏洞,我们只能修改我们正在操作的特定hive内的数据。然而,在实践中,情况不一定如此,因为可能有其他数据位于我们bin映射内存的紧邻区域。如果发生这种情况,可能可以使用线性缓冲区溢出无缝跨越原始hive和更高内存地址中一些更有趣的对象之间的边界。在以下部分中,我们将看两个这样的场景:一个是被攻击hive的映射在“注册表”进程的用户模式空间中,另一个是它驻留在内核地址空间中。
注册表进程用户空间中的其他hive映射
在注册表进程的用户空间中映射hive的节视图是绝大多数注册表的默认行为。内存中各个映射的布局可以很容易地从WinDbg中观察到。为此,找到注册表进程(通常是系统进程列表中的第二个),切换到其上下文,然后发出!vad命令。执行这些操作的示例如下所示。
|
|