Gabriel Landau

微软消灭 PPLFault 的计划内幕

在本篇研究报告中,我们将了解即将对 Windows 代码完整性子系统进行的改进,这些改进将使恶意软件更难以篡改反恶意软件进程和其他重要的安全功能。

阅读时间:11分钟安全研究
Inside Microsoft's plan to kill PPLFault

2023年9月1日,微软发布了 Windows 预览体验计划 Canary 版本的新构建,版本号为 25941。预览体验计划版本是 Windows 的预发布版本,包含实验性功能,这些功能可能最终会发布到正式版 (GA),也可能不会。构建 25941 包含对代码完整性 (CI) 子系统的改进,从而减轻了一个长期存在的问题,该问题使攻击者能够将未签名的代码加载到受保护进程轻量级 (PPL) 进程中。

PPL 机制在 Windows 8.1 中引入,允许使用特殊签名程序以这样一种方式运行:即使是管理进程也无法篡改或终止它们。其目标是防止恶意软件肆虐——篡改关键系统进程并终止反恶意软件应用程序。PPL 存在一个“级别”层次结构,较高权限的级别不受较低权限级别的篡改,反之则不然。大多数 PPL 进程由微软管理,但微软病毒计划 的成员被允许在其不太受信任的反恶意软件 PPL 级别运行其产品。

一些核心 Windows 组件在最高级别的 PPL 中运行,称为 Windows 可信计算基 (WinTcb-Light)。由于这些组件的保护及其有限的功能范围,它们被认为比大多数用户模式代码更可信。这些进程(例如csrss.exe)及其复杂的内核模式对应部分(例如win32k.sys)中的大多数是在几十年前编写的,当时的内核-用户边界甚至比现在还要弱。微软没有重写所有这些组件,而是将这些用户模式进程设为WinTcb-Light,从而减轻了篡改和注入攻击。Alex Ionescu 在 2013 年清楚地阐述了这一点。

由于 Win32k.sys 开发人员没有预料到本地代码注入攻击会成为问题(毕竟这需要管理员权限),因此许多这些 API 甚至没有 SEH,或者存在其他假设和错误。也许最著名的是,其中一个由j00ru 发现,并且至今仍未修复,已被用作 Windows 8 RT 越狱的唯一基础。在Windows 8.1 RT 中,这种越狱被“修复”了,因为代码无法再注入到 Csrss.exe 中进行攻击。类似的依赖于 Csrss.exe 的 Win32k.sys 漏洞也通过这种方式得到缓解。

为了减少攻击面,微软运行其大部分 PPL 代码的权限低于WinTcb-Light

微软不认为 PPL 是一个安全边界,这意味着他们不会优先处理在此发现的代码执行漏洞的安全补丁,但他们历来处理过一些此类漏洞,但优先级较低。

将代码加载到 PPL 进程中

要将代码加载到 PPL 进程中,它必须由特殊的证书签名。这适用于可执行文件(进程创建)和库(DLL 加载)。为简单起见,我们将重点关注 DLL 加载,但 CI 验证过程对于两者都非常相似。本文重点关注 PPL,因此我们将不讨论内核模式代码完整性。

可移植可执行文件 (PE) 文件有多种扩展名,包括 EXE、DLL、SYS、OCX、CPL 和 SCR。虽然扩展名可能有所不同,但它们在二进制级别都非常相似。对于 PPL 进程加载并执行 DLL,必须采取以下几个步骤。请注意,这些步骤已简化,但对于本文应该足够了。

  1. 应用程序调用LoadLibrary,传递要加载的 DLL 的路径。
  2. LoadLibrary 调用 NTDLL 中的加载程序(例如ntdll!LdrLoadDll),该加载程序使用诸如NtCreateFile之类的 API 打开文件句柄。
  3. 然后,加载程序将此文件句柄传递给NtCreateSection,要求内核内存管理器创建一个节对象,该对象描述如何将文件映射到内存中。节对象在较高的抽象层(例如 Win32)中也称为文件映射对象,但由于我们关注的是内核,因此我们将继续称其为节对象。Windows 加载程序始终使用一种称为可执行映像(又称SEC_IMAGE)的特定类型的节,该节只能从 PE 文件创建。
  4. 在将节对象返回到用户模式之前,内存管理器会检查文件上的数字签名,以确保它满足给定 PPL 级别要求。内部内存管理器函数MiValidateSectionCreate依赖于代码完整性模块ci.dll来处理必要的加密和PKI策略。
  5. 内存管理器重新构造 PE,以便可以将其映射到内存中并执行。此步骤涉及创建多个子节,每个子节对应 PE 文件的不同部分,这些部分必须以不同的方式映射。例如,全局变量可能是读写的,而代码可能是可执行的。为了实现这种粒度,生成的内存区域必须具有不同的页表条目,这些条目具有不同的页权限。此处可能还会应用其他更改,例如应用重定位,但它们不在本研究报告的范围内。
  6. 内核将新的节句柄返回到 NTDLL 中的加载程序。
  7. 然后,NTDLL 加载程序要求内核内存管理器通过NtMapViewOfSection系统调用将节的视图映射到进程地址空间。内存管理器会执行此操作。
  8. 映射视图后,加载程序将完成创建内存中功能性 DLL 所需的处理。这方面的详细信息不在本研究报告的范围内。

页面哈希值

在上一步中,我们可以看到在节创建期间会验证 PE 的数字签名,但还有一种方法可以将代码加载到 PPL 进程的地址空间——分页

属于文件支持节(包括SEC_IMAGE)的未修改页面可以在系统内存不足时快速丢弃,因为磁盘上存在该数据的精确副本。如果以后触摸该页面,CPU 将发出页面错误,而内存管理器的页面错误处理程序将从磁盘重新读取该数据。由于SEC_IMAGE节只能从不可变的文件数据创建,并且签名已验证,因此数据被认为是可信的。

PE 文件可以选择使用 /INTEGRITYCHECK 标记进行构建。这会在 PE 头部设置一个标记,该标记除其他事项外,还会指示内存管理器为 PE 的每个页面(也称为“页面哈希”)创建和存储哈希值,这些页面是从 PE 中创建的节。从磁盘读取页面后,页面错误处理程序会调用 MiValidateInPage 来验证自签名最初验证以来页面哈希是否已更改。如果页面哈希已更改,则处理程序将引发异常。此功能可用于检测页面文件中的 位腐烂 和几种类型的攻击。除了 /INTEGRITYCHECK 映像之外,对于加载到完全受保护的进程(非 PPL)中的所有模块以及加载到内核中的驱动程序,也会 启用 页面哈希。

注意:可以使用具有 用户可写引用 的文件创建 SEC_IMAGE 节,这是诸如 Process Herpaderping 之类的技术所采用的策略。用户可写引用的存在意味着可以在创建映像节后修改文件。当程序尝试使用此类可变文件时,内存管理器首先将文件内容复制到页面文件,为映像节创建一个不可变的备份以防止篡改。在这种情况下,节将不会由原始文件支持,而是由页面文件支持。有关用户可写引用的更多信息,请参阅 这篇 Microsoft 文章

漏洞利用

2022 年 9 月,来自 Elastic Security 的 Gabriel Landau 向 MSRC 提交了 VULN-074311,通知他们 Windows 中存在两个 零日 漏洞:一个管理员到 PPL 漏洞和一个 PPL 到内核漏洞。提供了针对这些漏洞的两个漏洞利用程序,分别命名为 PPLFaultGodFault,以及它们的源代码。这些漏洞利用程序允许恶意软件 绕过 LSA 保护、终止或屏蔽 EDR 软件以及修改内核内存以篡改核心操作系统行为——所有这些都不需要任何易受攻击的驱动程序。有关其影响的更多详细信息,请参阅 这篇文章

管理员到 PPL 漏洞利用程序 PPLFault 利用了这样一个事实:PPL 的页面哈希未经验证,并使用 云过滤器 API 来违反支持 SEC_IMAGE 节的文件的不可变性假设。PPLFault 使用分页将代码注入到以 WinTcb-Light(PPL 的最高权限形式)运行的 PPL 进程中加载的 DLL 中。PPL 到内核漏洞利用程序 GodFault 首先使用 PPLFault 获取 WinTcb-Light 代码执行,然后利用内核对 WinTcb-Light 进程的信任来修改内核内存,从而授予自身对物理内存的完全读写访问权限。

尽管 MSRC 拒绝 对这些漏洞采取任何行动,但 Windows Defender 团队已 表示关注。PPLFault 和 GodFault 于 2023 年 5 月在 Black Hat Asia 上发布,同时还发布了一个名为 NoFault 的缓解措施来阻止这些漏洞利用程序。

缓解措施

2023 年 9 月 1 日,微软发布了 Windows 预览体验计划 Canary 版本 25941。此版本在内存管理器函数 MiValidateSectionCreate 中添加了一个新的检查,该检查为驻留在远程设备上的所有映像启用页面哈希。通过比较 25941 与其前身 25936,我们可以看到以下两个新的基本块。

反编译成 C 代码后,新的代码如下所示。

运行 PPLFault 时,Windows 错误报告会生成一个事件日志,指示分页操作期间发生故障。

PPLFault 需要其有效负载 DLL 通过 SMB 网络重定向器加载才能实现所需的分页行为。通过强制对这种网络托管的 DLL 使用页面哈希,漏洞利用程序将无法再注入其有效负载,因此漏洞已修复。在 Black Hat 上发布的上述 NoFault 缓解措施也针对网络重定向器,完全阻止此类 DLL 加载到 PPL 中。Elastic Defend 8.9.0 及更高版本阻止 PPLFault——如果您尚未更新,请立即更新。

在内核调试器中追踪故障的确切点,我们可以看到页面错误处理程序调用 CI 来验证页面哈希,这会失败并返回 STATUS_INVALID_IMAGE_HASH (0xC0000428)。这稍后会被转换为 STATUS_IN_PAGE_ERROR (0xC0000006)

0: kd> g
Breakpoint 1 hit
CI!CiValidateImagePages+0x360:
0010:fffff805`725028b4 b8280400c0      mov     eax,0C0000428h
7: kd> k
 # Child-SP          RetAddr               Call Site
00 fffff508`1b4a6dc0 fffff805`72502487     CI!CiValidateImagePages+0x360
01 fffff508`1b4a6f90 fffff805`6f2f1bbd     CI!CiValidateImageData+0x27
02 fffff508`1b4a6fd0 fffff805`6ee35de5     nt!SeValidateImageData+0x2d
03 fffff508`1b4a7020 fffff805`6efa167b     nt!MiValidateInPage+0x305
04 fffff508`1b4a70d0 fffff805`6ef9fffe     nt!MiWaitForInPageComplete+0x31b
05 fffff508`1b4a71d0 fffff805`6ef68692     nt!MiIssueHardFault+0x3fe
06 fffff508`1b4a72e0 fffff805`6f0a784b     nt!MmAccessFault+0x3b2
07 fffff508`1b4a7460 00007fff`ccf71500     nt!KiPageFault+0x38b
08 000000b6`776bf1b8 00007fff`d5500ac0     0x00007fff`ccf71500
09 000000b6`776bf1c0 00000000`00000000     0x00007fff`d5500ac0
7: kd> !error C0000428
Error code: (NTSTATUS) 0xc0000428 (3221226536) - Windows cannot verify the 
 digital signature for this file. A recent hardware or software change 
 might have installed a file that is signed incorrectly or damaged, or 
 that might be malicious software from an unknown source.

行为比较

使用在版本 25941 中引入的修复程序,最终的易受攻击版本为 25936。在内核调试器下运行这两个版本的 PPLFault,我们可以使用以下 WinDbg 命令查看 CI 为其计算页面哈希的文件。

bp /w "&CI!CipValidatePageHash == @rcx" CI!CipValidateImageHash 
 "dt _FILE_OBJECT @r8 FileName; g"

此命令为修复之前的版本 25936 生成以下 WinDbg 输出。

以下是包含修复程序的版本 25941 的 WinDbg 输出。

结论

尽管花费的时间 可能比预期要长,但看到微软采取措施保护 PPL 进程(包括反恶意软件)免受以管理员身份运行的恶意软件的攻击,这令人兴奋,如果此改进很快进入 GA 版本,用户将从中受益。Insider 版本中的许多功能,甚至是安全功能,在(可能永远不会)GA 版本中不可用。微软在可能影响稳定性、兼容性或性能的更改方面非常保守;内存管理器更改属于风险较高的更改类型。例如,去年 11 月在 Insider 版本中发现的 PreviousMode 内核漏洞利用缓解措施 至今仍未进入 GA 版本,即使已经过去了至少 10 个月。

特别感谢 Grzegorz Tworek 帮助我们反向工程一些内核函数。