引言
本文将讨论Windows中的一个以前未命名的漏洞类别,展示Windows核心功能设计中长期存在的错误假设如何导致未定义行为和安全漏洞。我们将演示Windows 11内核中的一个此类漏洞如何被利用来实现任意代码的内核权限执行。
Windows文件共享
当应用程序在Windows上打开文件时,它通常使用Win32 [CreateFile] API的某种形式。
HANDLE CreateFileW(
[in] LPCWSTR lpFileName,
[in] DWORD dwDesiredAccess,
[in] DWORD dwShareMode,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
[in] DWORD dwCreationDisposition,
[in] DWORD dwFlagsAndAttributes,
[in, optional] HANDLE hTemplateFile
);
CreateFile的调用者在dwDesiredAccess中指定他们想要的访问权限。例如,调用者会传递FILE_READ_DATA来读取数据,或传递FILE_WRITE_DATA来写入数据。访问权限的完整集合在Microsoft Learn网站上有[文档]。
除了传递dwDesiredAccess外,调用者还必须在dwShareMode中传递一个“共享模式”,它由FILE_SHARE_READ、FILE_SHARE_WRITE和FILE_SHARE_DELETE中的零个或多个组成。您可以将共享模式视为调用者声明“我在使用此文件时,我允许其他人执行X操作”,其中X可以是读取、写入或重命名。例如,传递FILE_SHARE_WRITE的调用者允许其他人在他们使用文件时写入文件。
当文件被打开时,调用者的dwDesiredAccess会与所有现有文件句柄的dwShareMode进行测试。同时,调用者的dwShareMode会与所有现有文件句柄之前授予的dwDesiredAccess进行测试。如果这些测试中的任何一个失败,则CreateFile会因共享冲突而失败。
共享不是强制性的。调用者可以传递零的共享模式以获得独占访问权。根据Microsoft[文档]:
一个未共享的打开文件(dwShareMode设置为零)不能再次被打开,无论是由打开它的应用程序还是另一个应用程序,直到其句柄被关闭。这也被称为独占访问。
共享执行
在内核中,共享由文件系统驱动程序执行。当文件被打开时,文件系统驱动程序有责任调用[IoCheckShareAccess]或[IoCheckLinkShareAccess]来查看请求的DesiredAccess/ShareMode元组是否与正在打开的文件的任何现有句柄兼容。[NTFS]是Windows上的主要文件系统,但它是闭源的,因此为了说明目的,我们将查看Microsoft的FastFAT示例代码执行[相同的检查]。
//
// 检查Fcb是否具有适当的共享访问权限。
//
return IoCheckShareAccess( *DesiredAccess,
ShareAccess,
FileObject,
&FcbOrDcb->ShareAccess,
FALSE );
除了传统的读写文件操作外,Windows还允许应用程序将文件映射到内存中。在我们深入了解之前,重要的是要理解[section objects]是内核对[file mappings]的术语;它们是同一件事。本文重点关注内核,因此将主要将它们称为section对象。
有两种类型的section对象 – 数据section和可执行映像section。数据section是文件到内存的直接1:1映射。文件的内容在内存中将与它们在磁盘上完全一样。数据section还具有整个内存范围的统一内存权限。就底层文件而言,数据section可以是只读或读写。文件的读写视图使进程能够通过在其自己的地址空间内读取/写入内存来读取或写入文件的内容。
可执行映像section(有时缩写为映像section)准备[PE文件]以供执行。映像section必须从PE文件创建。PE文件的例子包括EXE、DLL、SYS、CPL、SCR和OCX文件。内核特别处理PE,以准备它们以供执行。不同的PE区域将根据它们的元数据以不同的页面权限映射到内存中。映像视图是[写时复制]的,这意味着内存中的任何更改将被保存到进程的私有工作集 – 永远不会写入支持的PE。
假设应用程序A想要使用数据section将文件映射到内存中。首先,它使用ZwCreateFile等API打开该文件,该API返回一个文件句柄。接下来,它将此文件句柄传递给ZwCreateSection等API,该API创建一个section对象,描述文件将如何映射到内存中;这产生一个section句柄。然后,进程使用section句柄将该section的“视图”映射到进程地址空间中,完成内存映射。
![显示文件如何映射到内存的图表]
图表显示了文件如何映射到内存
一旦文件成功映射,进程A可以关闭文件和section句柄,使文件没有打开的句柄。如果进程B稍后想要使用该文件而没有被外部修改的风险,它在打开文件时将省略FILE_SHARE_WRITE。IoCheckLinkShareAccess查找打开的文件句柄,但由于句柄之前已关闭,它不会使操作失败。
这为文件共享创建了一个问题。进程B认为它打开了文件,没有外部修改的风险,但进程A可以通过内存映射修改它。为此,文件系统还必须调用[MmDoesFileHaveUserWritableReferences]。这检查是否有任何活动的可写文件映射到给定的文件。我们可以在FastFAT示例[这里]看到此检查:
//
// Do an extra test for writeable user sections if the user did not allow
// write sharing - this is neccessary since a section may exist with no handles
// open to the file its based against.
//
if ((NodeType( FcbOrDcb ) == FAT_NTC_FCB) &&
!FlagOn( ShareAccess, FILE_SHARE_WRITE ) &&
FlagOn( *DesiredAccess, FILE_EXECUTE | FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | DELETE | MAXIMUM_ALLOWED ) &&
MmDoesFileHaveUserWritableReferences( &FcbOrDcb->NonPaged->SectionObjectPointers )) {
return STATUS_SHARING_VIOLATION;
}
Windows要求PE文件在运行时不可修改。这防止了EXE和DLL文件在内存中运行时被修改。文件系统驱动程序必须使用MmFlushImageSection函数检查PE文件是否有活动的映像映射,然后才允许FILE_WRITE_DATA访问。我们可以在FastFAT示例代码和Microsoft Learn上看到这一点。
//
// 如果用户想要对文件进行写访问,确保没有进程将此文件映射为映像。任何删除文件的尝试将在fileinfo.c中被阻止。
//
// 如果用户希望在关闭时删除文件,我们必须在此时进行检查。
//
if (FlagOn(*DesiredAccess, FILE_WRITE_DATA) || DeleteOnClose) {
Fcb->OpenCount += 1;
DecrementFcbOpenCount = TRUE;
if (!MmFlushImageSection( &Fcb->NonPaged->SectionObjectPointers,
MmFlushForWrite )) {
Iosb.Status = DeleteOnClose ? STATUS_CANNOT_DELETE :
STATUS_SHARING_VIOLATION;
try_return( Iosb );
}
}
另一种考虑这种检查的方法是**ZwMapViewOfSection(SEC_IMAGE)**暗示只要视图存在,就没有写共享。
Authenticode
Windows Authenticode Specification描述了一种使用加密技术“签署”PE文件的方法。数字签名通过加密技术证明PE文件是由特定实体生成的。数字签名具有防篡改功能,意味着任何对已签名文件的实质性修改都可以被检测到,因为数字签名将不再匹配。数字签名通常附加在PE文件的末尾。
Authenticode规范图显示了嵌入在PE中的签名
在这种情况下,Authenticode不能应用传统的哈希(例如sha256sum),因为附加签名的行为会改变文件的哈希,破坏刚生成的签名。相反,Authenticode规范描述了一种算法来跳过在签名过程中将被更改的PE文件的特定部分。这个算法称为authentihash。您可以使用任何哈希算法(如SHA256)与authentihash结合使用。当PE文件被数字签名时,实际上签署的是文件的authentihash。
代码完整性
Windows有几种不同的方法来验证Authenticode签名。用户模式应用程序可以调用WinVerifyTrust在用户模式下验证文件的签名。代码完整性(CI)子系统驻留在ci.dll
中,在内核中验证签名。如果Hypervisor-Protected Code Integrity正在运行,安全内核将使用skci.dll
来验证Authenticode。本文将重点关注常规内核中的代码完整性(ci.dll
)。
代码完整性提供内核模式代码完整性和用户模式代码完整性,每种方式都有不同的功能。
内核模式代码完整性(KMCI):
-
强制执行驱动程序签名强制和易受攻击的驱动程序阻止列表
用户模式代码完整性(UMCI):
-
CI在允许EXE和DLL加载之前验证它们的签名 -
强制执行受保护进程和轻量级受保护进程的签名要求 -
强制执行ProcessSignaturePolicy缓解措施(SetProcessMitigationPolicy) -
为FIPS 140-2模块强制执行INTEGRITYCHECK。 -
作为Smart App Control向消费者公开 -
作为企业应用控制(以前称为WDAC)向企业公开
KMCI和UMCI在不同场景中实现不同的策略。例如,受保护进程的策略与INTEGRITYCHECK的策略不同。
错误的假设
微软的文档暗示成功打开且没有写共享的文件不能被其他用户或进程修改。
FILE_SHARE_WRITE
0x00000002
允许随后对文件或设备的打开操作请求写访问。否则,如果其他进程请求写访问,将无法打开文件或设备。
如果未指定此标志,但文件或设备已打开以进行写访问或具有写访问的文件映射,则该函数将失败。
上面,我们讨论了文件系统如何强制共享,但如果文件系统不知道文件已经被修改怎么办?
像大多数用户模式内存一样,内核中的内存管理器(MM)在需要时可以将文件映射的部分页出,例如当系统需要更多可用物理内存时。数据和可执行映像映射都可能被页出。可执行映像部分永远不能修改备份文件,因此它们实际上被视为只读文件。正如之前提到的,映像部分是写时复制的,意味着任何内存中的更改都会立即创建给定页面的私有副本。
当内存管理器需要页出来自映像部分的页面时,可以使用以下决策树:
-
从未修改?丢弃它。我们可以从磁盘上的不可变文件中读取内容。 -
已修改?将其私有副本保存到页面文件。 -
示例:如果安全产品挂钩了 ntdll.dll
中的一个函数,MM将为每个修改过的页面创建一个私有副本。在页出时,私有页面将写入页面文件。
如果这些页出的页面后来被触及,CPU将发出页面错误,MM将恢复这些页面。
-
页面从未修改?从磁盘上的不可变文件中读取原始内容。 -
页面是私有的?从页面文件中读取它。
请注意以下例外情况:内存管理器可能将PE重定位页面视为未修改,并在页面错误期间动态重新应用重定位。
页面哈希
页面哈希是PE文件中每个4KB页面的哈希列表。由于页面是4KB的,页面错误通常发生在一次4KB的数据上。完整的Authenticode验证需要整个连续的PE文件,这在页面错误期间不可用。页面哈希允许MM在页面错误期间验证各个页面的哈希。
我们将页面哈希分为两种类型:静态和动态。静态页面哈希存储在PE的数字签名中,如果开发人员将/ph
传递给signtool
。通过预先计算这些,它们在模块加载时立即可用于MM和CI。
CI还可以在签名验证期间动态计算它们,我们称之为动态页面哈希。动态页面哈希使CI能够灵活地为从未签署过它们的文件强制执行页面哈希。
页面哈希并非免费使用——它们使用CPU并减慢页面错误。在大多数情况下,它们不会被使用。
攻击代码完整性
想象一个场景:勒索软件操作员想要勒索一家医院,于是他们向医院员工发送钓鱼邮件。员工打开邮件附件并启用宏,运行勒索软件。勒索软件使用UAC绕过立即提升到管理员权限,然后尝试终止系统上的任何安全软件,以便不受阻碍地运行。反恶意软件服务作为Protected Process Light(PPL)运行,保护它们免受具有管理员权限的恶意软件的篡改,因此勒索软件无法终止反恶意软件服务。
如果勒索软件也可以作为PPL运行,它可以终止反恶意软件产品。勒索软件不能直接作为PPL启动,因为UMCI阻止加载签名不正确的EXE和DLL,正如我们上面讨论的那样。勒索软件可能试图通过修改已经运行的EXE或DLL来注入代码到PPL,但上述的MmFlushImageSection确保正在使用的PE文件保持不可变,因此这不可能。
我们之前讨论了文件系统负责共享检查。如果攻击者将文件系统移动到另一台机器会怎样?
网络重定向器允许使用任何接受文件路径的API使用网络路径。这非常方便,允许用户和应用程序轻松打开和内存映射网络上的文件。任何产生的I/O都被透明地重定向到远程机器。如果从网络驱动器启动程序,EXE及其DLL的可执行映像将透明地从网络中获取。
当使用网络重定向器时,管道另一端的服务器不必是Windows机器。它可以是运行Samba的Linux机器,甚至是一个“讲”SMB网络协议的pythonimpacket脚本。这意味着服务器不必遵守Windows文件系统共享语义。
攻击者可以利用网络重定向器在服务器端修改PPL的DLL,绕过共享限制。这意味着支持可执行映像部分的PE文件被错误地假设为不可变。这是一类漏洞,我们称之为False File Immutability(FFI)。
利用分页漏洞
如果攻击者成功利用 False File Immutability 向正在使用的 PE 注入代码,页面哈希会不会发现这种攻击?答案是:有时会。如果我们看下表,可以看到页面哈希对内核驱动程序和受保护进程是强制的,但对 PPL(Protected Process Light)不是。因此,我们假设我们是攻击 PPL 的攻击者。
Authenticode | 页面哈希 | |
---|---|---|
内核驱动程序 | ✅ | ✅ |
受保护进程 (PP-Full) | ✅ | ✅ |
受保护进程 Light (PPL) | ✅ | ❌ |
去年在 2023 年的 Black Hat Asia 会议上(摘要,幻灯片,录像),我们披露了 Windows 内核中的一个漏洞,展示了如何利用分页中的错误假设向 PPL 注入代码,从而绕过 LSA 和 反恶意软件进程保护等安全特性。该攻击利用了我们刚刚描述的 DLL 中的 False File Immutability 假设,尽管当时我们还没有命名这一漏洞类别。
PPLFault 漏洞利用示意图
在演示中,我们发布了 PPLFault 漏洞利用,通过转储一个原本受保护的 PPL 的内存来展示漏洞。我们还发布了 GodFault 漏洞利用链,将 PPLFault 管理员到 PPL 的利用与 AngryOrchard PPL 到内核的利用结合在一起,从用户模式实现对物理内存的完全读写控制。我们这样做是为了促使微软对 MSRC 拒绝修复 的漏洞采取行动,因为它不符合他们的服务标准。幸运的是,微软的 Windows Defender 团队采取了行动,在 2024 年 2 月发布了修复,对通过网络重定向器加载的可执行映像强制实施动态页面哈希,从而打破了 PPLFault 的利用。
新的研究
上面,我们讨论了嵌入在 PE 文件中的 Authenticode 签名。除了嵌入签名,Windows 还支持一种称为安全目录的分离签名形式。安全目录(.cat 文件)本质上是一份签名的 authentihash 列表。每个在列表中具有 authentihash 的 PE 都被认为由该签名者签名。Windows 在 C:WindowsSystem32CatRoot
中保留了大量目录文件,CI 会加载、验证并缓存这些目录文件。
安全目录的简化结构
通过 Windows 资源管理器呈现的安全目录
典型的 Windows 系统有上千个目录文件,许多目录文件包含几十或几百个 authentihash。
Windows 11 23H2 系统上的安全目录
要使用安全目录,代码完整性必须首先加载它。这需要几个离散的步骤。首先,CI 使用 ZwOpenFile、ZwCreateSection 和 ZwMapViewOfSection 将文件映射到内核内存中。映射后,它使用 CI!MinCrypK_VerifySignedDataKModeEx 验证目录的数字签名。如果签名有效,它会使用 CI!I_MapFileHashes 解析哈希。
代码完整性目录解析过程
拆解来看,我们可以看到几个关键点。首先,ZwCreateSection(SEC_COMMIT) 告诉我们 CI 创建的是数据段,而不是映像段。这很重要,因为数据段没有页面哈希的概念。
其次,文件在没有 FILE_SHARE_WRITE 的情况下打开,这意味着拒绝写共享。这是为了防止在处理期间修改安全目录。然而,正如我们上面所展示的,这是一个错误的假设,是 False File Immutability 的另一个例子。理论上,应该可以对安全目录处理进行类似 PPLFault 的攻击。
规划攻击
攻击的大致流程如下:
-
攻击者将在其控制的存储设备上放置一个安全目录。然后,他们会在 CatRoot
目录中安装一个指向该目录的符号链接,这样 Windows 就知道在哪里找到它。 -
攻击者请求内核加载一个恶意的未签名内核驱动程序。 -
代码完整性尝试验证驱动程序,但找不到签名或可信的 authentihash,因此它重新扫描 CatRoot 目录并找到攻击者的新目录。 -
CI 将目录映射到内核内存并验证其签名。这会生成页面错误,这些错误会发送到攻击者的存储设备。存储设备返回一个合法的微软签名目录。 -
攻击者清空系统工作集,迫使所有先前获取的目录页面被丢弃。 -
CI 开始解析目录,生成新的页面错误。这次,存储设备注入其恶意驱动程序的 authentihash。 -
CI 在目录中找到恶意驱动程序的 authentihash 并加载驱动程序。此时,攻击者已在内核中实现任意代码执行。
实施和考虑事项
计划是使用 PPLFault 风格的攻击,但在这种情况下有一些重要的区别。PPLFault 使用了一个机会锁(oplock)来确定性地冻结受害进程的初始化。这给攻击者提供了切换到有效载荷并刷新系统工作集的时间。不幸的是,我们在这里找不到任何适合使用 oplocks 的好机会。相反,我们将采用一种概率性方法:快速在恶意和良性版本之间切换安全目录。
目录在良性和恶意版本之间切换;只有一个哈希值发生变化
验证步骤触及目录的每一页,这意味着在解析开始时所有这些页面都会驻留在内存中。如果攻击者更改其存储设备上的目录,直到随后发生页面错误之前,这种更改不会反映在内存中。要从内核内存中驱逐这些页面,攻击者必须在 MinCrypK_VerifySignedDataKModeEx 和 I_MapFileHashes 之间清空工作集。
这种方法本质上是一种竞争条件。在签名验证和目录解析之间没有内置的延迟——这是一场紧张的竞赛。我们需要采用几种技术来扩大我们的机会窗口。
系统上的大多数安全目录都很小,只有几千字节。通过选择一个大的 4MB 目录,我们可以大大增加 CI 花费在解析上的时间。假设目录解析是线性的,我们可以选择目录末尾附近的 authentihash 以最大化签名验证与 CI 达到我们篡改的页面之间的时间。此外,我们将为系统上的每个 CPU 创建线程,其唯一目的是消耗 CPU 周期。这些线程的优先级高于 CI,因此 CI 将缺乏 CPU 时间。还将有一个线程专门用于反复刷新系统的工作集页面,以及一个线程反复尝试加载未签名的驱动程序。
这种攻击有两个主要的失败模式。首先,如果在签名检查期间读取了有效载荷 authentihash,则签名将无效,并且目录将被拒绝。
代码完整性拒绝篡改的安全目录
其次,如果在签名验证和解析之间发生偶数次切换(包括零次),则 CI 将解析良性哈希并拒绝我们的驱动程序。
通过签名检查,但解析良性目录
如果 CI 验证良性目录然后解析恶意目录,则攻击者获胜。
代码完整性验证良性目录,然后解析恶意目录
漏洞利用演示
我们将漏洞利用命名为 ItsNotASecurityBoundary,向 MSRC 的政策致敬,认为“管理员到内核不是安全边界”。代码在 GitHub 上这里。
演示视频 这里。
理解这些漏洞
为了正确防御这些漏洞,我们首先需要更好地理解它们。
双重读取(即双重获取)漏洞可能发生在受害代码从攻击者控制的缓冲区中多次读取相同值时。攻击者可能在读取之间更改该缓冲区的值,从而导致受害者行为异常。
想象有一页内存在两个进程之间共享,用于 IPC 机制。客户端和服务器使用以下结构发送数据。要发送 IPC 请求,客户端首先将请求结构写入共享内存页,然后发出事件信号通知服务器有待处理的请求。
struct IPC_PACKET
{
SIZE_T length;
UCHAR data[];
};
双重读取攻击可能看起来像这样:
使用共享内存的双重读取攻击示例
首先,攻击客户端将数据包结构的长度字段设置为16字节,然后发出信号通知服务器数据包已准备好处理。受害服务器醒来并使用 malloc(pPacket->length)
分配一个16字节的缓冲区。随后,受害者将长度字段更改为32。接下来,受害服务器尝试通过调用 memcpy(pBuffer, pPacket->data, pPacket->length)
将数据包的内容复制到新缓冲区中,重新读取 pPacket->length
中的值,现在是32字节。结果,受害者将32字节的数据复制到16字节的缓冲区中,导致缓冲区溢出。
双重读取漏洞经常适用于共享内存场景。它们通常发生在操作用户可写缓冲区的驱动程序中。由于虚假的文件不可变性,开发人员需要意识到其范围实际上更广,包括所有可被攻击者写入的文件。拒绝写共享不一定能防止文件修改。
受影响的操作
哪些类型的操作会受到虚假的文件不可变性的影响?
操作 | API | 缓解措施 |
---|---|---|
图像段 | CreateProcess LoadLibrary | 1. 启用页面哈希 |
数据段 | MapViewOfFile ZwMapViewOfSection | 1. 避免双重读取 2. 在处理之前将文件复制到堆缓冲区 3. 通过 MmProbeAndLockPages/VirtualLock 防止分页 |
常规 I/O | ReadFile ZwReadFile | 1. 避免双重读取 2. 在处理之前将文件复制到堆缓冲区 |
还有哪些可能易受攻击?
在 NT 内核中寻找可能易受攻击的 ZwMapViewOfSection 调用,会发现很多有趣的功能:
NT 内核中 ZwMapViewOfSection 的潜在易受攻击使用
如果我们将搜索范围扩大到常规文件 I/O,我们会发现更多候选项。然而,一个重要的警告是 ZwReadFile 可能不仅仅用于文件。只有那些用于文件(或可能被迫用于文件)的使用才可能易受攻击。
NT 内核中 ZwReadFile 的潜在易受攻击使用
在 NT 内核之外,我们可以找到其他驱动程序进行调查:
Windows 11 内核驱动程序中 ZwReadFile 的潜在易受攻击使用
Windows 11 内核驱动程序中 ZwMapViewOfSection 的潜在易受攻击使用
不要忘记用户模式
到目前为止,我们主要讨论了内核,但需要注意的是,任何在攻击者可控文件上调用 ReadFile、MapViewOfFile 或 LoadLibrary 的用户模式应用程序,都可能会由于不可变性而易受攻击。以下是一些假设的例子。
MapViewOfFile
想象一个应用程序被分成两个组件——一个具有网络访问权限的低权限工作进程和一个安装更新的特权服务。工作进程下载更新并将其放置在特定文件夹中。当特权服务看到新的更新已放置时,它首先验证签名,然后再安装更新。攻击者可以滥用 FFI 在签名检查后修改更新。
ReadFile
由于文件易受双重读取漏洞的影响,任何解析复杂文件格式的东西都可能易受攻击,包括防病毒引擎和搜索索引器。
LoadLibrary
一些应用程序依赖 UMCI 防止攻击者将恶意 DLL 加载到其进程中。正如我们在 PPLFault 中所展示的那样,FFI 可以击败 UMCI。
停止利用
根据他们的官方服务指南,MSRC 不会默认服务于管理员到内核的漏洞。在这种说法中,服务意味着“通过安全更新修复”。然而,这种类型的漏洞允许恶意软件绕过AV 进程保护,使 AV 和 EDR 易受即时攻击。
作为第三方,我们无法修补代码完整性,那我们如何保护我们的客户呢?为了缓解 ItsNotASecurityBoundary,我们创建了 FineButWeCanStillEasilyStopIt,这是一个文件系统迷你过滤器驱动程序,阻止代码完整性通过网络重定向器打开安全目录。你可以在 GitHub 上找到它这里。
FineButWeCanStillEasilyStopIt 需要通过一些步骤来正确识别问题行为,同时最小化误报。理想情况下,CI 本身可以通过一些小的更改来修复。让我们看看这需要什么。
通过将目录复制到堆来修复目录处理
如上面受影响的操作部分所述,应用程序可以通过将文件内容从文件映射复制到堆中来缓解双重读取漏洞,并专门使用该堆副本进行所有后续操作。内核堆称为池,相应的分配函数是 ExAllocatePool。
通过将页面锁定到 RAM 来修复目录处理
打破这些类型利用的另一种缓解策略是使用 API(如 MmProbeAndLockPages)将文件映射的页面固定到物理内存中。这可以防止当攻击者清空工作集时,这些页面被逐出。
终端用户检测和缓解
幸运的是,终端用户可以通过启用虚拟机保护的代码完整性 (HVCI) 来缓解这种攻击,而无需微软的更改。如果启用了 HVCI,CI.dll 将不会进行目录解析。相反,它将目录内容发送到运行在同一主机上的独立虚拟机中的安全内核。安全内核将收到的目录内容存储在其自己的堆中,从中执行签名验证和解析。与上面描述的 ExAllocatePool 缓解方法类似,由于文件更改对堆副本没有影响,因此这种攻击得到了缓解。
由于此攻击具有概率性质,可能会有许多失败的尝试。Windows 会在 Microsoft-Windows-CodeIntegrity/Operational 事件日志中记录这些失败。用户可以检查此日志以查找利用证据。
Microsoft-Windows-CodeIntegrity/Operational 事件日志显示无效的驱动程序签名
Microsoft-Windows-CodeIntegrity/Operational 事件日志显示无效的安全目录
披露
披露时间表如下:
-
2024-02-14:我们将 ItsNotASecurityBoundary 和 FineButWeCanStillEasilyStopIt 作为 VULN-119340 报告给 MSRC,建议使用 ExAllocatePool 和 MmProbeAndLockPages 作为简单且低风险的修复措施 -
2024-02-29:Windows Defender 团队联系协调披露 -
2024-04-23:微软发布KB5036980 预览版,包含 MmProbeAndLockPages 修复 -
2024-05-14:修复程序通过 KB5037771 发布到 Windows 11 23H2;我们未测试其他平台(Win10,Server 等) -
2024-06-14:MSRC 关闭案件,称“我们已经完成了调查,并确定该案件目前不符合我们的服务标准。因此,我们已为该问题打开了下一版本候选错误,并将其评估为即将发布的版本。再次感谢您与我们分享此报告。”
修复代码完整性
查看 CI!I_MapAndSizeDataFile 的原始实现,我们可以看到遗留代码调用了 ZwCreateSection 和 ZwMapViewOfSection:
易受攻击的 CI!I_MapAndSizeDataFile 实现
与新实现 CI!CipMapAndSizeDataFileWithMDL 对比,后者随后调用了 MmProbeAndLockPages:
新 CI!CipMapAndSizeDataFileWithMDL 具有缓解措施
总结和结论
今天我们讨论并命名了一类漏洞:虚假的文件不可变性。我们知道有两个利用它的公开漏洞,PPLFault 和 ItsNotASecurityBoundary。
PPLFault:管理员 -> PPL [-> 通过 GodFault 到内核]
-
利用 CI/MM 中关于图像段的错误不可变性假设 -
2022年9月报告 -
2024年2月修补(约510天后)
ItsNotASecurityBoundary:管理员 -> 内核
-
利用 CI 中关于数据段的错误不可变性假设 -
2024年2月报告 -
2024年5月修补(约90天后)
如果您在编写操作文件的 Windows 代码,您需要意识到这些文件在您处理时可能会被修改,即使您拒绝写共享。请参阅上面的受影响操作部分,了解如何保护自己和客户免受这些类型的攻击。
ItsNotASecurityBoundary 并不是虚假的文件不可变性的终点。还有其他可利用的 FFI 漏洞。
原文始发于微信公众号(3072):Windows漏洞挖掘 False File Immutability 攻击面分析