Samir Bousseaden

野外 Windows 本地提权零日漏洞:见解与检测策略

本文将使用 Elastic Defend 功能,基于动态行为分析评估 Windows 本地提权技术的检测方法。

13 分钟阅读安全运营
In-the-Wild Windows LPE 0-days: Insights & Detection Strategies

根据 MicrosoftGoogleKasperskyCheckpoint 和其他行业参与者的披露,野外 Windows 本地提权 (LPE) 零日漏洞越来越普遍,并且是复杂的网络犯罪和 APT 攻击武器库中的重要组成部分。对于检测工程师来说,仔细检查这些公开可用的样本并评估可能的检测途径非常重要。

本文不会深入探讨漏洞的根本原因或具体细节;但是,我们会提供指向相关漏洞研究文章的链接。我们将基于使用 Elastic Defend 功能的动态行为分析来评估检测方法。

案例 1 - 通用日志文件系统

通用日志文件系统 (CLFS) 是一种通用的日志记录服务,可供需要高性能事件日志记录的软件客户端使用。 Microsoft 安全更新指南显示,自 2018 年以来,已修补了 30 多个 CLFS 漏洞,其中 5 个是在 2023 年的勒索软件攻击中观察到的。2024 年也以针对同一 CLFS 驱动程序的漏洞报告(由多位研究人员提交)开始。

您可以在此处找到一系列深入研究 CLFS 漏洞内部原理的优秀文章。这些漏洞的一个共同点是,它们利用一些 clfsw32.dll API(CreateLogFileAddLogContainer)来创建和操作 BLF 日志,使它们能够写入或破坏内核模式地址。与其他利用原语结合使用,这可能会导致成功提升权限。

基于这些漏洞的细节,可以设计一个高级别检测来识别不寻常的进程。例如,以低或中等完整性运行的进程可以创建 BLF 文件,然后出乎意料地执行系统完整性级别的活动(生成系统子进程,使用系统权限进行 API 调用、文件或注册表操作)。

以下 EQL 查询可用于关联 Elastic Defend 文件事件,其中调用堆栈包含用户模式 API CreateLogFileAddLogContainerSet 的引用,特别是当以普通用户身份运行时,随后创建以 SYSTEM 身份运行的子进程时

sequence with maxspan=5m
 [file where event.action != "deletion" and not user.id : "S-1-5-18" and   user.id != null and 
  _arraysearch(process.thread.Ext.call_stack, $entry, 
               $entry.symbol_info: ("*clfsw32.dll!CreateLogFile*", "*clfsw32.dll!AddLogContainerSet*"))] by process.entity_id
 [process where event.action == "start" and user.id : "S-1-5-18"] by process.parent.entity_id

以下是 CVE-2022-24521 上的匹配示例,其中 cmd.exe 以 SYSTEM 身份启动

以下 EQL 查询使用与前一个查询类似的逻辑,但它不是生成子进程,而是查找在 BLF 文件事件之后具有 SYSTEM 权限的 API、文件或注册表活动

sequence by process.entity_id 
 [file where event.action != "deletion" and not user.id : "S-1-5-18" and user.id != null and 
  _arraysearch(process.thread.Ext.call_stack, $entry, $entry.symbol_info : ("*clfsw32.dll!CreateLogFile*", "*clfsw32.dll!AddLogContainerSet*"))]
 [any where event.category : ("file", "registry", "api") and user.id : "S-1-5-18"]
 until [process where event.action:"end"]

以下屏幕截图与 CLFS 漏洞提升权限后的清理阶段匹配(使用系统权限删除文件)

除了之前的两个行为检测之外,我们还可以利用 YARA 来搜索导入用户模式 API CreateLogFileAddLogContainerSet 的未签名 PE 文件,以及来自 clfsw32.dll 的数量异常多的函数(正常的 CLFS 客户端程序会从同一 DLL 导入更多函数)

import "pe" 

rule lpe_clfs_strings {
    strings:
     $s1 = "NtQuerySystemInformation"
     $s2 = "clfs.sys" nocase
    condition:
     uint16(0)==0x5a4d and (pe.imports("clfsw32.dll", "CreateLogFile") or pe.imports("clfsw32.dll", "AddLogContainer")) and all of ($s*)
}

rule lpe_clfs_unsigned {
    condition:
     uint16(0)==0x5a4d and pe.number_of_signatures == 0 and filesize <= 200KB and 
      (pe.imports("clfsw32.dll", "CreateLogFile") or pe.imports("clfsw32.dll", "AddLogContainer")) and 
      not (pe.imports("clfsw32.dll", "ReadLogRecord") or pe.imports("clfsw32.dll", "CreateLogMarshallingArea"))
}

以下是使用 Elastic 的 YARA 规则进行 CVE-2023-2825VT 匹配示例

CVE-2023-2825 的 YARA 规则匹配

案例 2 - Windows DWM 核心库 EoP

桌面窗口管理器 (dwm.exe) 自 Windows Vista 以来一直是 Microsoft Windows 中的合成窗口管理器。此程序启用硬件加速来呈现 Windows 图形用户界面,并具有高权限;但是,低权限的用户可以与 DWM 进程交互,这会大大增加攻击面。

安全研究员 Quan Jin 报告了针对 CVE-2023-36033 的野外漏洞利用,Google Project Zero 随后发布了详细的 文章,解释了该漏洞利用的阶段。

根据我们的理解,DWM 核心库 (dwmcore.dll) 漏洞利用很可能会在以窗口管理器\DWM 用户权限运行时在 dwm.exe 进程中触发 shellcode 执行。请注意,这是高完整性,但还不是 SYSTEM。

在 Elastic Defend 上引爆 ITW 公开样本确实会触发自注入 shellcode 警报。在没有事先了解和上下文的情况下,人们可能会将其与通用代码注入警报或误报混淆,因为它是由 Microsoft 可信系统二进制文件进行的自注入警报,具有正常的父进程且未加载恶意库。

以下 KQL 搜索可用于查找类似的 shellcode 警报

event.code : "shellcode_thread" and process.name : "dwm.exe" and user.name : DWM*

除了 shellcode 执行之外,我们还可以通过基准化子进程和文件活动来查找 dwm.exe 中的不寻常活动。在下面,我们可以看到 dwm.exe 由于漏洞利用而生成 cmd.exe 的示例

根据我们的遥测可见性,dwm.exe 很少生成合法的子进程。可以使用以下检测来查找异常子进程。

process where event.action == "start" and
 process.parent.executable : "?:\\Windows\\system32\\dwm.exe" and user.id : ("S-1-5-90-0-*", "S-1-5-18") and process.executable : "?:\\*" and 
 not process.executable : ("?:\\Windows\\System32\\WerFault.exe", "?:\\Windows\\System32\\ISM.exe", "?:\\Windows\\system32\\dwm.exe")

为了将权限从窗口管理器\DWM 用户提升到 SYSTEM,shellcode 会将 DLL 丢弃到磁盘,并在 dwm.exe 进程内的 kernelbase!MapViewOfFile 调用上放置 JMP 钩子。然后,它通过执行 shutdown /l 命令来触发注销。

注销操作会触发以 SYSTEM 用户身份运行的 LogonUI.exe 进程的执行。LogonUI.exe 进程将与桌面窗口管理器进程进行通信,类似于任何桌面 GUI 进程,这将编组/解组 Direct Composition 对象。

dwm.exe 内的 MapViewOfFile 钩子会监视映射的堆内容。它会修改该内容,使用另一组精心制作的小工具,以便在 LogonUI.exe 进程中解组资源堆数据时,执行已丢弃 DLL 的 LoadLibraryA 调用。

此处有两个主要的检测点:当 dwm.exe 将 PE 文件丢弃到磁盘时,以及当 LogonUI.exe 加载 DLL 时,调用堆栈指向 dcomp.dll,这是编组/解组 Direct Composition 对象的指示。

下面是一个 KQL 查询,用于在文件事件和恶意软件警报中查找 dwm.exe 将 PE 文件丢弃到磁盘的情况

(event.category :"file" or event.code :"malicious_file") and 

process.name :"dwm.exe" and user.id:S-1-5-90-0-* and 

(file.extension :(dll or exe) or file.Ext.header_bytes :4d5a*)

下面是一个检测 EQL 查询,用于查找 LogonUI DLL 加载劫持。

library where process.executable : "?:\\Windows\\System32\\LogonUI.exe" and 
 user.id : "S-1-5-18" and 
 not dll.code_signature.status : "trusted" and 
 process.thread.Ext.call_stack_summary : "*combase.dll|dcomp.dll*"

案例 3 - Windows 激活上下文 EoP

CVE-2022-41073 是另一个有趣的野外漏洞。核心漏洞是用户可以在模拟期间为特权进程重新映射根驱动器 (C:\)。这个特定样本欺骗 printfilterpipelinesvc.exe 进程加载任意 DLL,方法是在客户端服务器运行时子系统 (CSRSS) 中的激活上下文生成期间,将 C:\ 驱动器重定向到 C:\OneDriveRoot。然后,它伪装成 C:\Windows\WinSxS 目录,非特权用户无法写入该目录。

从行为角度来看,它属于由低/中等完整性进程丢弃的 SYSTEM 完整性进程加载 DLL 的类别。还有伪装成合法 Windows WinSxS 文件夹的标记。

以下 EQL 搜索可用于查找伪装成可信系统文件夹进行重定向的类似尝试

any where (event.category in ("file", "library") or event.code : "malicious_file") and 
(
  file.path : ("C:\\*\\Windows\\WinSxS\\*.dll", "C:\\*\\Windows\\system32\\*.dll", "C:\\*\\Windows\\syswow64\\*.dll", "C:\\*\\Windows\\assembly\\NativeImages*.dll") or 
 
  dll.path : ("C:\\*\\Windows\\WinSxS\\*.dll", "C:\\*\\Windows\\system32\\*.dll", "C:\\*\\Windows\\syswow64\\*.dll", "C:\\*\\Windows\\assembly\\NativeImages*.dll")
 )

这也匹配通用端点检测,该检测查找由提升的系统本地进程加载的不受信任的模块

通用行为检测

上面提供的示例说明了每个漏洞都具有不同的特征。利用方法因原语的灵活性而异,例如写入地址、执行 shellcode、加载任意 DLL 或创建文件。某些系统组件可能比其他组件存在更多漏洞,因此需要专门的检测工作(例如,CLFS、win32k)。

尽管如此,这些漏洞的最终目标和影响仍然是一致的。这突出了制定更有效检测策略的机会。

特权提升可以以各种形式表现出来

  • 一个低/中等完整性进程生成一个提升的子进程
  • 一个低/中等完整性进程将代码注入到提升的进程中
  • 一个系统完整性进程意外加载一个不受信任的 DLL
  • 一个系统本地进程意外丢弃 PE 文件
  • 一个低/中等完整性进程将文件丢弃到受系统保护的文件夹中
  • 一个用户模式进程写入内核模式地址

利用 Elastic Defend 的功能,我们可以设计检测并查找上述每种可能性。

低/中等完整性进程生成一个提升的子进程:

sequence with maxspan=5m
 [process where event.action == "start" and
  process.Ext.token.integrity_level_name in ("medium", "low")] by process.entity_id
 [process where event.action == "start" and
  process.Ext.token.integrity_level_name == "system" and user.id : "S-1-5-18"] by process.parent.entity_id

在利用易受攻击驱动程序 (Zemana zam64.sys) 生成作为 SYSTEM 的 cmd.exe示例中匹配的示例

低/中等完整性进程将代码注入到提升的进程中:

这是一个ES|QL 查询,用于查找罕见的跨进程 API 调用

from logs-endpoint.events.api*
| where process.Ext.token.integrity_level_name in ("medium", "low") and Target.process.Ext.token.integrity_level_name == "system" and
 process.Ext.api.name in ("WriteProcessMemory", "VirtualProtect", "VirtualAllocEx", "VirtualProtectEx", "QueueUserAPC", "MapViewOfFile", "MapViewOfFileEx")
| stats occurrences = count(*), agents = count_distinct(host.id) by process.Ext.api.name, process.executable, Target.process.executable
| where agents == 1 and occurrences <= 100

当我们运行此查询时,会获得 LPE 漏洞利用程序,这些漏洞利用程序在通过令牌交换提升后注入到 winlogon.exe

系统完整性进程意外加载一个不受信任的 DLL

这是一个 ES|QL 查询,用于查找由提升的 Microsoft 二进制文件加载的罕见未签名 DLL

from logs-endpoint.events.library-*
| where host.os.family == "windows" and event.action == "load" and
  starts_with(process.code_signature.subject_name, "Microsoft") and        
  user.id in ("S-1-5-18", "S-1-5-19", "S-1-5-20") and 
  process.code_signature.status == "trusted" and 
  dll.Ext.relative_file_creation_time <= 500 and
  (dll.code_signature.exists == false or dll.code_signature.trusted == false) and   

  /* excluding noisy DLL paths */   
  not dll.path rlike """[C-F]:\\Windows\\(assembly|WinSxS|SoftwareDistribution|SystemTemp)\\.+\.dll""" and

 /* excluding noisy processes and potentially unrelated to exploits - svchost must be covered by a dedicated hunt to exclude service dlls and COM */
not process.name in ("rundll32.exe", "regsvr32.exe", "powershell.exe", "msiexec.exe", "svchost.exe", "w3wp.exe", "mscorsvw.exe", "OfficeClickToRun.exe", "SetupHost.exe", "UpData.exe", "DismHost.exe")

| stats occurrences = count(*), host_count = count_distinct(host.id) by dll.name, process.name
/* loaded once and the couple dll.name process.name are present in one agent across the fleet */
| where occurrences == 1 and host_count == 1

一个系统本地进程意外丢弃 PE 文件

以下 ES|QL 查询可用于搜索以下实例:具有低可执行文件创建历史记录的特权 Microsoft 签名二进制文件,并且仅限于跨受监视主机群的一个代理

from logs-endpoint.events.file-*
| where  @timestamp > now() - 30 day
| where host.os.family == "windows" and event.category == "file" and event.action == "creation" and user.id in ("S-1-5-18", "S-1-5-19", "S-1-5-20", "S-1-5-90-0-*") and
 starts_with(file.Ext.header_bytes, "4d5a") and process.code_signature.status == "trusted" and
 starts_with(process.code_signature.subject_name, "Microsoft") and 
 process.executable rlike """[c-fC-F]:\\Windows\\(System32|SysWOW64)\\[a-zA-Z0-9_]+.exe""" and
 not process.name in ("drvinst.exe", "MpSigStub.exe", "cmd.exe")
| keep process.executable, host.id
| stats occurrences = count(*), agents = count_distinct(host.id) by process.executable
| where agents == 1 and occurrences == 1

用户模式进程写入内核模式地址

破坏PreviousMode 是一种广泛流行的利用技术。覆盖 KTHREAD 结构中的这一个字节会绕过系统调用中的内核模式检查,例如 NtReadVirtualMemoryNtWriteVirtualMemory,允许用户模式攻击者读取和写入任意内核内存。

在 x64 上,虚拟地址空间分为用户模式地址,范围从 0x00000000 000000000x0000FFFF FFFFFFFF,内核模式地址范围从 0xFFFF0000 000000000xFFFFFFFF FFFFFFFF。以下 EQL 查询可用于检测目标地址是内核模式地址的 API NtReadVirtualMemoryNtReadVirtualMemory 调用,这是一种异常行为

api where process.pid != 4 and process.Ext.api.name : "WriteProcessMemory"
 and process.executable != null and 
   /*  kernel mode address range - decimal */
   process.Ext.api.parameters.address > 281474976710655

以下是这些警报在利用此原语的漏洞利用中触发的示例

结论

检测特定漏洞的特权提升需要深入了解漏洞及其利用方法,而这并非普遍知识。因此,投资于通用行为检测机制,重点关注漏洞利用对系统的影响以及常用的原语,例如 KASLR 绕过令牌交换PreviousMode 滥用和其他方法,证明更加有效。但是,对于 CLFS 和 win32k 等高度针对性的 Windows 系统组件,专用检测始终有价值,理想情况下是行为和 YARA 的组合。

尽管技术复杂且缺乏常见原语的日志,但蓝队不应忽视漏洞利用和漏洞研究内容;相反,他们应该努力理解并应用它。此外,通过 VirusTotal 或类似的野外 LPE 漏洞利用样本与防御社区共享将进一步促进检测控制的测试和增强。

有关利用特权提升的更多检测规则可以在此处访问。

参考