DCOM绕过PPL反射调用.Net原理

本篇只是记录James Forshaw, Google Project Zero提供的文章进行学习笔记,并非复现过程。具体细节还是去观摩大佬的文章学习比较好(参考链接1)。


攻击原理

Windows COM (Component Object Model) 架构是 Windows 操作系统的核心技术之一,它允许不同组件之间进行通信和交互。James Forshaw文章分析的攻击技术展示了如何利用 COM 架构中的特性,创建跨权限边界的攻击通道,实现对高权限服务的影响或权限提升。

COM 对象可以在不同的位置运行,这由其在注册表中的注册方式决定:

  • 本地进程 (In-Process):通过InprocServer32注册表项指定 DLL,在调用者进程内运行

  • 服务进程 (Service):通过LocalServer32AppID注册表项配置,在独立的服务进程中运行

  • 远程服务器 (Remote Server):在单独的进程中运行,但不是作为服务。

这种注册位置的差异对安全边界和攻击可行性有重大影响(攻击点)。

某些进程会加载InprocServer32类型的COM组件且暴露出一些公共接口(IDispatch),然后利用DCOM方式与COM组件公开的接口进行交互,碰巧微软有一个机制:允许使用DCOM方式进行.Net反射执行。这个同样在James Forshaw文章中提到(这个大佬是真牛逼)。这个机制在微软2014 年 2 月 11 日的时候进行了修复(https://learn.microsoft.com/en-us/security-updates/SecurityBulletins/2014/ms14-009),但依然留下了缺口,用户可对注册表下的AllowDCOMReflection写值,允许再次将其打开。因此若想要操作起来需要满足下列条件:

  • 注册为在本地进程中运行,而不是在服务中。

  • 开启AllowDCOMReflection项。

  • TreatAs注册表重定向,这个用于将传统COM对象视为 .NET对象,从而允许在 COM 上下文中调用 .NET 对象。这个与.Net与COM交互有关,可看一下CCW、RCW内容。

当这些条件同时满足时,攻击者可以在自己的进程中创建该 COM 类的实例,完全控制其内存和行为,同时利用它与高权限服务建立特权通信。这种攻击绕过了PPL保护,因为它利用了 COM 类型系统本身的特性。

分析

文章中,作者通过遍历本地服务实现的所有COM类,并过滤有暴露接口的类。

1
2
3
$cls = Get-ComClass -Service
$cls | % { Get-ComInterface -Class $_ | Out-Null }
$cls | ? { $true -in $_.Interfaces.InterfaceEntry.IsDispatch } | Select Name, Clsid

根据作者所述,存在可利用的是第一个WaaSRemediation,接着查询该类所运行在的进程

1
2
3
$obj = New-ComObject -Clsid 72566e27-1abb-4eb3-b4f0-eb431cb1cb32
$lib = Import-ComTypeLib -Object $obj
Get-ComObjRef $lib.Instance

这里发现COM组件类运行在svchost.exe进程中,接着解析对象使用的类型库。

1
2
$parsed = $lib.Parse()
$parsed

解析类。

1
$parsed.Classes

到这里解释一下为什么作者说了这么一段话。

这里手动查看一下两个类的注册方式。

1
2
3
4
5
$cls = Get-ComClass -Clsid 72566e27-1abb-4eb3-b4f0-eb431cb1cb32
$cls.Servers

$cls = Get-ComClass -Clsid 9ea82395-e31b-41ca-8df7-ec1cee7194df
$cls.Servers

Key都是LocalServer32,意味着当创建COM类实例时,实例将在服务进程中运行,而不是在目标进程中运行,导致代码执行的环境为**服务进程**上下文。因此不符合作者的目的。作者的初衷是找到某个类并且当这个类被实例的时,代码的执行环境是在目标进程上下文。(类似于LoadLibrary函数总是执行在进程上下文)

作者这里提到说,类型库也会引用到其他类型库,类似DLL中import了其他DLL。因此这里查询WaaSRemediationLib引用的库。

1
2
3
4
$parsed.ReferencedTypeLibs
$parsed.ReferencedTypeLibs[0].Parse().Classes
$cls = Get-ComClass -Clsid 0be35203-8f91-11ce-9de3-00aa004bb851
$cls.Servers

这里可以看到非常有意思的点,WaaSRemediationLib类型库引用了stdole类库,而stdole中的StdFont类注册为InProcServer32类型。这意味着StdFont COM组件会在调用者的进程上下文中运行,而不是在远程服务中。当进程创建StdFont实例时,系统会将oleaut32.dll加载到该进程的地址空间,使攻击者能够完全控制对象的内存和行为。

这种情况创造了一个独特的攻击路径:攻击者可以在自己的进程中创建并操控StdFont对象,同时利用它与高权限WaaS服务共享的类型定义和接口,建立一个特权通信通道。通过这种方式,攻击者能够在本地进程上下文中执行COM反射(Reflection)操作,绕过了通常的进程边界保护,实现对高权限服务的影响。

接着作者查询该类库被运行在的进程:

1
2
3
4
5
6
7
8
9
$iid = $parsed.Interfaces[0].Uuid
$ti = $lib.GetTypeInfoOfGuid($iid)
$href = $ti.GetRefTypeOfImplType(0)
$base = $ti.GetRefTypeInfo($href)
$stdole = $base.GetContainingTypeLib()
$stdole.Parse()
ti = $stdole.GetTypeInfoOfGuid("0be35203-8f91-11ce-9de3-00aa004bb851")
$font = $ti.CreateInstance()
Get-ComObjRef $font

巧了,运行在svchost.exe,说明可以利用这个东西对svchost.exe注入。查看一下接口

1
Get-ComInterface -Object $obj

(这里不知道为什么查询到的接口和文章的不同=。=),再看一下WaaSRemediationAgent的保护等级。

1
2
$cls = Get-ComClass -Clsid 72566e27-1abb-4eb3-b4f0-eb431cb1cb32
$cls.AppIDEntry.ServiceProtectionLevel

查询到的Level为WindowsLight,表明目标服务是被PPL进行了保护。意味着通过这种方式可以对高权限进程进行注入。具体的代码实现是在参考三中。

总结

作者这一套下来,我认为主要的难点就是如何去找到进程中引用了一个COM组件,并且组件对外暴露了接口,使得可以用DCOM与.NET进行交互,通过reflection进行执行攻击代码。

如果拓展到自己在实际使用场景,我认为可以让自己的程序加载一个拥有暴露接口的com组件,接着外部进程可以通过com组件进行反射调用代码,可以对目标执行类似于脚本化的功能。

参考

googleprojectzero.blogspot.com

googleprojectzero.blogspot.com

Abusing IDispatch for Trapped COM Object Access & Injecting into PPL Processes | Red Teaming’s Dojo

COM Callable Wrapper - .NET

Runtime Callable Wrapper - .NET

PPL保护 - 怎么可以吃突突 - 博客园