利用受困 COM 对象实现的无文件横向移动

在昏暗、蓝光闪烁的办公室里,一名男子的手部特写:他一边在笔记本电脑上打字,一边手持平板电脑

自 20 世纪 90 年代初以来,组件对象模型 (COM) 一直是 Microsoft Windows 开发的核心基石,至今仍在现代 Windows 操作系统和应用程序中广泛应用。多年来对 COM 组件的依赖以及大量功能开发,导致其形成了庞大的攻击面。2025 年 2 月,Google Project Zero 的 James Forshaw (@tiraniddo) 发布了一篇博客文章,详细介绍了一种滥用分布式 COM(DCOM)远程技术的新方法——通过捕获的 COM 对象,可在服务器端 DCOM 进程的上下文环境中执行 .NET 托管代码。Forshaw 重点介绍了权限提升与受保护进程轻量版 (PPL) 绕过的多个用例。

基于 Forshaw 的研究,Mohamed Fakroud (@T3nb3w) 于 2025 年 3 月初发布了该技术的实现方案,用于绕过 PPL 保护机制。Jimmy Bayne (@bohops) 与我于 2025 年 2 月开展了类似研究,最终开发出一种概念验证级无文件横向移动技术,其核心是滥用捕获的 COM 对象。

背景

COM 是一种二进制接口标准及中间件服务层,支持不同的模块化组件彼此交互、与应用程序对接,且不受底层编程语言限制。例如,使用 C++ 开发的 COM 对象可轻松与 .NET 应用程序对接,帮助开发人员高效整合各类软件模块。DCOM 是一种远程技术,支持 COM 客户端通过进程间通信 (IPC) 或远程过程调用 (RPC) 与 COM 服务器通信。许多 Windows 服务都实现了可本地或远程访问的 DCOM 组件。

COM 类通常会注册并存储在 Windows 注册表中。客户端程序通过创建 COM 类的实例(即 COM 对象)与 COM 服务器进行交互。该对象会提供一个指向标准化接口的指针。客户端通过该指针访问对象的方法和属性,为客户端与服务器之间的通信及功能实现提供支持。

COM 对象通常是评估漏洞暴露面、发现可滥用功能的研究目标。捕获的 COM 对象是一类漏洞场景:COM 客户端在进程外 DCOM 服务器中实例化 COM 类,通过引用封送对象指针控制该 COM 对象。根据具体场景,该控制路径可能存在与安全相关的逻辑漏洞。

Forshaw 的博客描述了一个 PPL 绕过用例:通过操纵 WaaSRemediation COM 类中公开的 IDispatch 接口,实现对捕获 COM 对象的滥用及 .NET 代码执行。WaaSRRemediationWaaSMedicSvc 服务中实现,该服务以 NT AUTHORITY\SYSTEM 权限,作为受保护的 svchost.exe 进程运行。Forshaw 详尽的技术详解,为我们开发概念验证级无文件横向移动技术提供了应用研究基础。

男子正在看电脑

增强安全情报


每周在 Think 时事通讯中获取有关安全、AI 等的新闻和洞察分析,从而预防威胁。

研究概述

我们的研究从深入了解支持 IDispatch 接口的 WaaSRemediation COM 类起步。该接口支持客户端执行延迟绑定操作。通常,COM 客户端会在编译阶段定义其使用对象的接口及类型信息。而延迟绑定允许客户端在运行时发现并调用对象的方法。IDispatch 包含 GetTypeInfo 方法,该方法会返回 ITypeInfo 接口。ITypeInfo 提供的方法可用于获取实现该接口的对象的类型信息。

如果 COM 类使用类型库,客户端可通过 ITypeLib(通过 ITypeInfo->GetContainingTypeLib 获取)查询该类型库以获取类型信息。此外,类型库还可能引用其他类型库以补充类型信息。

根据 Forshaw 的博客内容,WaaSRemediation 引用了 WaaSRemediationLib 类型库,而该类型库又引用了 stdole(OLE 自动化)。WaaSRemediationLib 采用了该库中的两个 COM 类:StdFontStdPicture。通过修改 StdFont 对象的 TreatAs 注册表项对其执行 COM 劫持,可使该类指向我们选定的另一个 COM 类(例如 .NET Framework 中的 System.Object)。值得注意的是,Forshaw 指出 StdPicture 不可行,因为该对象会对进程外实例化进行校验,因此我们将研究重点放在了 StdFont 的应用上。

我们之所以关注 .NET 对象,核心原因是 System.Object 具备 GetType 方法。通过 GetType 方法,我们可执行 .NET 反射,最终调用 Assembly.Load。尽管我们选择了 System.Object,但该类型恰好是 .NET 类型层次结构的根类型。因此,任何 .NET COM 对象都可用于此类场景。

完成初始配置后,要将我们设想的用例落地,还需依赖 HKLM\Software\Microsoft.NetFramework 项下的另外两个 DWORD 值:

  • AllowDCOMReflection正如 Forshaw 所指出的,该值设为启用状态后,我们可执行任意反射操作,调用任何 .NET 方法。通常,由于 MS14-009 中提及的缓解措施,DCOM 上的 .NET 反射会被阻止。
  • OnlyUseLatestCLR通过 Procmon 工具检测发现,必须启用该值才能加载最新版本的 .NET CLR(4 版本),否则默认加载 2 版本。

初始测试中确认可加载最新版本的 CLR 和 .NET 后,我们确认方向正确。

从本地进程到远程计算机

我们将注意力转向远程编程层面,首先通过远程注册表工具操作 .NetFramework 注册表项值,并劫持目标机器上的 StdFont 对象。随后,我们将 CoCreateInstance 替换为 CoCreateInstanceEx,在远程目标上实例化 WaaSRemediation COM 对象,并获取指向 IDispatch 接口的指针。

获得 IDispatch 接口指针后,我们调用其 GetTypeInfo 成员方法,获取指向 ITypeInfo 接口的指针 —— 该接口会驻留在服务器端。此后调用的所有成员方法均在服务器端执行。在识别出目标类型库引用 (stdole) 并推导得出目标类对象引用 (StdFont) 后,我们最终通过 ITypeInfo 接口的“可远程调用”CreateInstance 方法,重定向 StdFont 对象的链接流程(通过此前的 TreatAs 操作,从而实例化 System.Object

由于 AllowDCOMReflection 已正确配置,我们可通过 DCOM 执行 .NET 反射,调用 Assembly.Load 将 .NET 程序集加载到 COM 服务器中。鉴于我们是通过 DCOM 调用 Assembly.Load,该横向移动技术完全属于无文件攻击,程序集字节传输由 DCOM 远程机制自动处理。要深入了解从对象实例化到反射的完整技术流程,请参阅下图:

System.Object 类实例化流程图
System.Object 类实例化流程

开发中的痛点

我们面临的首要问题是通过 IDispatch->Invoke 调用 Assembly.Load_3Invoke 会将参数的对象数组传递给目标函数,而 Load_3Assembly.Load 的重载方法,仅接受单个字节数组作为参数。因此,我们需要将字节类型的 SAFEARRAY 包装在变体类型 (VARIANT) 的 SAFEARRAY 中 —— 最初我们一直尝试直接传递单个字节类型的 SAFEARRAY,导致调用失败。

创建 Object 字节非托管等效项的代码示例
创建 Object 字节的非托管等效项

另一个问题是找到正确的 Assembly.Load 重载方法。辅助函数取自 Forshaw 的 CVE-2014-0257 相关代码,其中包含 GetStaticMethod 函数。该函数通过 DCOM 执行 .NET 反射,根据类型指针、方法名称及其参数数量查找静态方法。Assembly.Load 有两个仅接受单个参数的静态重载方法,因此我们最终采用了一种临时解决方案。我们发现,第三个仅接受单个参数的 Load 方法实例正是我们需要的。

用于查找正确的 Assembly.Load 重载方法的代码
查找正确的 Assembly.Load 重载方法

运营难题

我们观察到该技术的最大缺陷之一是:生成的信标生命周期与 COM 客户端绑定——在本测试场景中,即我们的武器化二进制程序 “ForsHops.exe” 的运行周期。(当然,这个命名相当巧妙)。因此,如果 ForsHops.exe 清理自身的 COM 引用或退出运行,远程机器 svchost.exe 进程下运行的信标也会随之终止。我们尝试了多种解决方案,例如让 .NET 程序集无限期挂起主线程、在另一个线程中执行 shellcode、让 ForsHops.exe 保持漏洞利用线程挂起状态,但均未达到理想效果。

.NET 加载器主线程挂起,shellcode 在独立线程中运行
.NET 加载器主线程挂起,shellcode 在独立线程中运行

目前版本中,ForsHops.exe 会持续运行直至信标退出,届时会清理其执行的注册表操作。该技术仍有改进空间,相关优化工作就留给读者自行探索。

ForsHops.exe 执行演示
执行 ForsHops.exe
Windows 2019 Server 上的信标成功部署
Windows 2019 Server 上的信标成功部署
信标在 PPL svchost 进程中运行的屏幕截图
信标在 PPL svchost 进程中运行
ForsHops.exe 在信标退出后清理修改的示例
ForShops.exe 在信标退出后移除更改

防御建议

检测指南由 Samir Bousseaden (@SBousseaden) 在 Mohamed Fakroud 发布实现方案后提出,同样适用于该横向移动技术:

  • 检测 WaaSMedicSvc 对应的 svchost.exe 进程中的 CLR 加载事件
  • 检测对以下注册表项的操作(或创建):HKLM\SOFTWARE\Classes\CLSID\{0BE35203-8F91-11CE-9DE3-00AA004BB851}\TreatAsStandardFont CLSID 对应的 TreatAs 键值)

此外,我们建议采取以下额外控制措施:

  • 检测对 HKLM\SOFTWARE\Classes\CLSID\{0BE35203-8F91-11CE-9DE3-00AA004BB851} 的 DACL 篡改行为
  • 排查 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft.NETFramework 中是否存在已启用的 OnlyUseLatestCLRAllowDCOMReflection 键值
  • 尽可能启用基于主机的防火墙,限制对 DCOM 临时端口的访问

此外,可利用以下概念验证级 YARA 规则检测标准的 ForsHops.exe 可执行文件:

规则 Detect_Standard_ForsHops_PE_By_Hash

{
    meta:   
        description = "Detects the standard ForShops PE file by strings"
        reference = "GitHub Project: https://github.com/xforcered/ForsHops/"
    strings:
        $s1 = "System.Reflection.Assembly, mscorlib" wide
        $s2 = "{72566E27-1ABB-4EB3-B4F0-EB431CB1CB32}" wide
        $s3 = "{34050212-8AEB-416D-AB76-1E45521DB615}" wide
        $s4 = "GetType" wide
        $s5 = "Load" wide

    condition:
        all of them
}

总结

我们的实现方案对 Forshaw 博客中阐述的 COM 滥用方式做了小幅扩展,即利用捕获的 COM 对象实现横向移动,而非通过本地执行来实现 PPL 绕过。因此,该方案仍会受到与本地执行实现方案相同的检测机制的监测。

您可以在此处获取 ForsHops.exe 横向移动的概念验证代码。

特别鸣谢

特别感谢 Dwight Hohnstein (@djhohnstein) 和 Sanjiv Kawa (@sanjivkawa) 为本研究提供反馈,并对博客文章内容进行审核。

资源

Mixture of Experts | 12 月 12 日,第 85 集

解码 AI:每周新闻摘要

加入我们世界级的专家小组——工程师、研究人员、产品负责人等将为您甄别 AI 领域的真知灼见,带来最新的 AI 资讯与深度解析。

相关解决方案
企业安全解决方案

部署源自最大企业安全供应商的解决方案,实现企业安全计划的转型。

深入了解网络安全解决方案
网络安全服务

通过网络安全咨询、云端和托管安全服务实现业务转型并有效管理风险。

    深入了解网络安全服务
    人工智能 (AI) 网络安全

    使用人工智能驱动的网络安全解决方案提高安全团队的速度、准确性和工作效率。

    深入了解 AI 网络安全
    采取后续步骤

    无论您需要的是数据安全、端点管理,还是身份和访问管理 (IAM) 解决方案,我们的专家都随时准备为您提供支持,助力企业建立强大的安全环境。 在网络安全咨询、云端和安全托管服务方面的全球行业领导者的帮助下,推动业务转型并有效管控风险。

    深入了解网络安全解决方案 发现网络安全服务