Gabriel Landau

微软计划终结 PPLFault

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

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

2023 年 9 月 1 日,微软发布了 Windows Insider Canary 的新版本 25941。Insider 版本是 Windows 的预发布版本,其中包含可能永远不会达到正式可用 (GA) 的实验性功能。版本 25941 包括对代码完整性 (CI) 子系统的改进,这些改进缓解了一个长期存在的问题,该问题使攻击者能够将未签名的代码加载到受保护的进程轻量级 (PPL) 进程中。

PPL 机制是在 Windows 8.1 中引入的,它使经过特殊签名的程序能够以一种方式运行,即使是管理进程,也能保护它们免受篡改和终止。其目标是阻止恶意软件肆虐,篡改关键系统进程并终止反恶意软件应用程序。PPL“级别”有一个层次结构,较高权限的 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 节,这是一种被诸如 进程 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 进程中加载的 DLL 中,WinTcb-Light 是 PPL 的最高权限形式。PPL 到内核的利用程序 GodFault 首先使用 PPLFault 来获得 WinTcb-Light 代码执行,然后利用内核对 WinTcb-Light 进程的信任来修改内核内存,从而授予自身对物理内存的完全读写访问权限。

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

缓解

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

反编译为 C 后,新代码如下所示

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

PPLFault 需要通过 SMB 网络重定向器加载其有效负载 DLL 才能实现所需的分页行为。通过强制为此类网络托管的 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 输出

结论

尽管花费的时间可能比应该的要长,但令人兴奋的是看到 Microsoft 正在采取措施来保护 PPL 进程(包括反恶意软件)免受以管理员身份运行的恶意软件的攻击,如果此改进能尽快达到 GA,用户将受益匪浅。Insider 中的许多功能,甚至是安全功能,在 GA 中都不可用(并且可能永远无法达到)。在涉及潜在的稳定性、兼容性或性能风险时,Microsoft 非常保守;内存管理器更改是风险较高的类型之一。例如,去年 11 月在 Insider 中发现的 PreviousMode 内核漏洞利用缓解措施,即使在至少 10 个月后仍未达到 GA。

特别感谢 Grzegorz Tworek 在反向工程某些内核函数方面提供的帮助。