利用受困 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 代码执行。WaaSRemediationWaaSMedicSvc 服务中实现,该服务以 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 框架中的 System.Object。值得注意的是,Forshaw 指出 StdPicture 不可行,因为该对象会检查进程外实例化,因此我们将注意力集中在使用 StdFont 上。

.NET 对象之所以引起我们关注,是因为 System.ObjectGetType 方法。通过 GetType, 我们可以执行 .NET 反射,最终访问 Assembly.Load。虽然选择了 System.Object,但该类恰巧是 .NET 类型层次结构的根类。因此任何 .NET COM 对象都可被利用。

在完成初始阶段设置后,还需要修改 HKLM\Software\Microsoft\.NetFramework 项下的两个 DWORD 值,才能使预想的使用场景成为现实:

  • AllowDCOMReflection:正如 Forshaw 所述,启用此值允许我们执行任意反射以调用任何 .NET 方法。通常,通过 DCOM 进行 .NET 反射会因 MS14-009 补丁的缓解措施而被阻止。
  • 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_3 是接收单个字节数组的 Assembly.Load 重载版本。因此,我们需要将字节 SAFEARRAY 包装在另一个 VARIANT 类型 SAFEARRAY——最初我们始终尝试直接传递单个字节 SAFEARRAY

展示如何创建 Object Byte 的非托管等效形式的代码
创建非托管等效 Object Byte

另一难题在于定位正确的 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 上成功建立 Beacon
在 Windows 2019 Server 上成功建立 Beacon
Beacon 在 PPL svchost 进程中运行的屏幕截图
Beacon 在 PPL svchost 进程中运行
ForShops.exe 在 Beacon 退出后删除更改的示例
ForShops.exe 在 Beacon 退出后删除更改

防御建议

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

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

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

  • 检测 HKLM\SOFTWARE\Classes\CLSID{0BE35203-8F91-11CE-9DE3-00AA004BB851} 的 DACL 操纵行为
  • 搜寻 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft.NETFramework 中已启用的 OnlyUseLatestCLR AllowDCOMReflection
  • 尽可能启用基于主机的防火墙以限制 DCOM 临时端口访问

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

rule 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) 解决方案,我们的专家都随时准备为您提供支持,助力企业建立强大的安全环境。 在网络安全咨询、云端和安全托管服务方面的全球行业领导者的帮助下,推动业务转型并有效管控风险。

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