前言
最近闲着无聊分析了 clfs.sys 中的一些过去的安全漏洞,在 Windows 操作系统的内核中,通用日志文件系统 (CLFS) 提供了一项用于日志记录的服务。由于CLFS的复杂性和其驱动程序 clfs.sys 的设计特点,历史上曾多次发现安全漏洞。本文将深入分析 CVE-2022-35803 这一 CLFS 中的安全漏洞,该漏洞允许攻击者通过类型混淆问题绕过微软先前针对 CVE-2022-24481 的修复,进而实现权限提升。
首先从分析 CVE-2022-24481 开始,解释其原理和微软的修复措施,接着介绍如何通过类型混淆的技术手段绕过修复并最终实现权限提升。通过这一过程,带大家深入了解 CLFS 的工作原理以及如何利用特定的数据结构和内核行为来实现攻击目的。
废话不多说,让我们直奔主题!
旧漏洞:CVE-2022-24481 分析
在 CLFS 中,CLFS_BASE_RECORD_HEADER 结构用于表示基本记录头:
结构中的 rgClients 和 rgContainers 字段用于表示一个 32 位数组,该数组存储每个客户端上下文和容器上下文的偏移量。用户可以在磁盘上修改它们。漏洞的根本原因是缺乏对客户端偏移的有效验证,导致客户端上下文与容器上下文重叠。结果,由于 clfs.sys 修改客户端上下文会导致同时改变下一个容器上下文。
实例样本会在日志文件关闭时触发函数 CClfsLogFcbPhysical::FlushMetadata:
上述代码中命名的 CClfsLogFcbPhy 变量指针是一个 CClfsLogFcbPhysical 对象,在 CClfsLogFcbPhysical::Initialize 函数中初始化,当打开日志文件时会调用该函数。
在精心构造的 CLFS 日志文件中,攻击者使客户端上下文的 llCreateTime 字段与容器上下文的 pContainer 字段重叠,并将 llCreateTime 字段设置为用户空间中的一个地址。在调用 CClfsLogFcbPhysical::FlushMetadata 函数之后,容器上下文的 pContainer 字段(在运行时存储指向 CClfsContainer 对象的内核指针)将被修改。
FlushMetadata 结束后,clfs.sys 将调用 CClfsLogFcbPhysical::CloseContainers 函数来关闭容器:
在 [1] 之后,pContainer 将指向攻击者控制的内存空间(在用户空间)。当调用 CClfsContainer::Close [2] 时,将触发内部函数 ObfDereferenceObject。这会导致任意地址递减。这个技巧被在野外样本所使用以减少 PreviousMode 到零,这会导致调用线程从用户模式被设置到内核模式,并最终使用户能够通过 NtWriteVirtualMemory / NtReadVirtualMemory 读写内核空间中的任意地址。之后,用户可以通过覆盖当前进程的 _EPROCESS 对象中的 token 字段到系统进程的 _EPROCESS 对象中的 token 的地址(样本通过 NtQuerySystemInformation 获取这些地址)来实现权限提升。
CVE-2022-24481 的补丁
通过分析补丁,可以看到该漏洞的修复是在 CClfsBaseFile::ValidateRgOffsets 中添加了对 rgClients 和 rgContainers 偏移量的检查:
可以看到该漏洞的补丁在 [3] 引入了一个重要的检查。它要求容器上下文与其下一个上下文之间的间隔至少为 0x30 + 12 * 4 = 0x60 字节,而客户端上下文与其下一个上下文之间的间隔至少为 0x30 + 34 * 4 = 0x88 = 0xb8 字节。然而,在 CClfsLogFcbPhysical::FlushMetadata 函数中,我们最多只能修改位于偏移量 0x78 字节处的字段 (ClfsClientContext->eState)。因此,通过添加的检查修复了该漏洞。
如何绕过
通过分析补丁,我们可以看到直接修改客户端上下文偏移量的问题已经被修复。但是,还有其他方式可以导致字段重叠并引发漏洞吗?
经过研究分析,发现从 ValidateRgOffsets 验证偏移量到 FlushMetadata 修改数据,再到 CloseContainers 关闭容器的过程中,没有对客户端上下文字段 cidNode.cType(代表上下文类型)的有效性进行检查。这可能会导致类型混淆问题:如果我们把客户端上下文的 cidNode.cType 设置为 0xC1FDF008,那么当 ValidateRgOffsets 计算时,客户端上下文与下一个上下文之间的间隔会被错误地限制为 0x60 字节,这会导致客户端上下文 0x60~0x88 字节处的字段与下一个上下文 0~0x28 字节处的字段重叠。这样就产生了一个新的漏洞。
我重新审查了 CClfsLogFcbPhysical::FlushMetadata,并确定了可以通过 ClfsClientContext->eState 修改下一个容器上下文中的 pContainer。
由于原始的 pContainer 已经按 0x10 字节对齐,这会使 pContainer 字段指向原始容器对象位置的 8 字节偏移处,那里正好存储着容器文件的大小,这是通过用户调用 AddLogContainer 函数时的参数 p