# 什么是 COM

什么是 COM,说白了,就是一堆功能相关的 interface,它是某种语言向另一种语言暴露功能的最大单位。
COMcomponent(COM 组件)是微软公司为了计算机工业的软件生产更加符合人类的行为方式开发的一种新的软件开发技术。
在 COM 构架下,人们可以开发出各种各样的功能专一的组件,然后将它们按照需要组合起来,构成复杂的应用系统。 由此带来的好处是多方面的:可以将系统中的组件用新的替换掉,以便随时进行系统的升级和定制;可以在多个应用系统中重复利用同一个组件;可以方便的将应用系统扩展到网络环境下;COM 与语言,平台无关的特性使所有的程序员均可充分发挥自己的才智与专长编写组件模块。
COM 的最核心的思想,说白了就是要做个跨语言的 “class” “object” “function” 。

# 什么是 CLSID(不重要)

当初微软设计 com 规范的时候,有两种选择来保证用户的设计的 com 组件可以全球唯一(其实就相当于 COM 的唯一 ID):
第一种是采用和 Internet 地址一样的管理方式,成立一个管理机构,用户如果想开发一个 COM 组件的时候需要向该机构提出申请,并交一定的费用。
第二种是发明一种算法,每次都能产生一个全球唯一的 COM 组件标识符。
第一种方法,用户使用起来太不方便,微软采用第二种方法,并发明了一种算法,这种算法用 GUID(Globally Unique Identifiers)来标识 COM 组件,GUID 是一个 128 位长的数字,一般用 16 进制表示。算法的核心思想是结合机器的网卡、当地时间、一个随即数来生成 GUID。从理论上讲,如果一台机器每秒产生 10000000 个 GUID,则可以保证(概率意义上)3240 年不重复。
GUID 的例子: 54BF6567–1007–11D1–B0AA–444553540000
HKEY_CLASSES_ROOT\CLSID{002B9E07-2E10-438F-AF1E-40E6A96F1EE4}
在微软的 COM 中 GUID 和 UUID、CLSID、IID 是一回事,只不过各自代表的意义不同:
・UUID : 代表 COM
・CLSID : 代表 COM 组件中的类
・IID :代表 COM 组件中的接口
在程序中,实际对象数据对应的处理程序路径 string 往往不尽相同,比如有的放 C 盘有的 D 盘,微软想出了一个解决方案,那就是不使用直接的路径表示方法,而使用一个叫 CLSID 的方式间接描述这些对象数据的处理程序路径。
CLSID 其实就是一个号码,CLSID 的结构定义如下:

image-20211226141149190

# COM 和注册表的关系

通常 windows 内建 com 已经在注册表内存储着相关信息,而自定义 com 需要创建注册表入口点告诉 windows com 组件服务器在上面位置,我们可以在 HKEY_CLASSES_ROOT\CLSID {clsid} 位置找到所有 windows 已注册的 com 组件。

注册后 com 通过 GUID 唯一标识符来寻找并使用这个 com 组件,理论上每一个 GUID 都是唯一的,GUID 在标识不同的对象时会有不同的称呼,标识类对象时称之为 CLSID (类标识符)、标识接口时被称为 IID (接口标识符)。

在每一个注册的 clsid 表项中都包含一个名为 InprocServer32 或 LocalServer 的子项,该子项内存有映射到该 com 二进制文件的键值对,操作系统通过该键值对将 com 组件载入进程或另起进程。(进程内组件和进程外组件,二进制代码的表现形式为 dll (内) 和 exe (外))。

简单的理解就是,我们编写好一个 COM 组件,都需要注册到注册表中(也可以设置不用注册的 COM 组件,但是一般都是使用的注册方法),这样当调用 COM 组件的这个功能的时候,程序会进注册表进行读取相应位置的 DLL 或者 EXE,加载到进程还是线程中,供我们使用。

COM 注册的位置一般在以下地方,执行时是通过 CLSID 从上往下找,找到就停止查找直接执行。

HKEY_CURRENT_USER\Software\Classes\CLSID
HKEY_CLASSES_ROOT\CLSID   ps:这个其实就是HKEY_LOCAL_MACHINE\SOFTWARE\Classes的映射
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\ShellCompatibility\Objects\

而 InprocServer32 和 LocalServer32(以及 InprocServer 和 LocalServer)这两个键值是 COM 服务(例如 DLL、CPL、EXE 和 OCX)的引用点,也就是 COM 具体调用的 dll 和 exe 的绝对路径

如图,这里就是通过注册表去看这个 COM 的具体信息。这里 COM 的 CLSID 为 {BF87B6......},它具体的实现是在 qcap.dll 中。

image-20211226141424757

同样的,我们也可也用 powershell 命令去看系统注册了的 COM 组件。

Get-WmiObject Win32_COMSetting

image-20211226141134992

# 自己实现一个 COM(理解 COM 到底是什么,有什么用)

为了更好的理解 COM 到底是个什么玩意,这里我们自己实现一个 COM 然后注册到注册表中,再跨语言调用它。

# 编写 com

这里用的是 VS2019

新建一个 ALT 项目,按照下面的流程走。

image-20211226142036273

这里我选择的 dll 文件,exe 总感觉有点碍眼。

image-20211226142055185

到这里的时候,我们就获得了两个项目,然后不需要管下面那个。

image-20211226142101513

然后向主项目添加一个新的 ATL 对象。

image-20211226142158825

短名称随便写,然后其他地方会自动填充,然后 ProgID 需要设置为项目名称。短名称,然后直接点完成即可。

image-20211226142217611

此时在项目下会自动添加 Temp.h 以及 Temp.cpp 以及生成一个 CTemp 类,接着我们就可以创建我们自己的方法了。

在 Temp.h 头文件中给 Ctemp 这个类加函数声明。

image-20211226142256774

然后再到 Temp.cpp 文件中实现这个函数的具体功能。这里我另函数 Number 实现的功能是输入一个数字,返回其平方(这里随便加了个弹计算机操作)。

STDMETHODIMP CTemp::Number(LONG __num, LONG* __result)
{
	*__result = __num * __num;
	system("calc");
	return S_OK;
}

image-20211226142331907

然后定义好方法后去编辑 comtest.idl,然后找到 ITemp : IDispatch 这个接口,在里面写入方法接口。

image-20211226142641043

com 的 clsid 也在这个文件中,如下,这个是随机生成的。

image-20211226142701258

最后就是编译生成了。出现这个报错不用管,这里是前面我们在创建项目的时候取消了注册选项。(如果选择了这个选项,这个 com 就会注册在你本机中,这不是我们想要的)

image-20211226142622822

# 注册 COM

使用 regsvr32 去将编译得到的 COM 注册的注册表中。PS:如果报错的话,请提升权限运行。

regsvr32.exe -i C:\Users\momo\Desktop\comtest.dll #这是注册
regsvr32.exe /u C:\Users\momo\Desktop\comtest.dll #这是卸载

image-20211226142858873

然后我们的 com 就成功注册了,在注册表中也能找到它的存在。(这里我是通过 COM name 去找的,也可以通过 CLSID 去找)

image-20211226143140496

image-20211226144911241

# 多种方式调用 COM

# vbs 调用

这里首先用 COM Name 去创建了一个 com 对象,然后再调用这个对象里面的 Number 方法,其实就是我们前面在编译时候写入的方法。返回输入的平方。

set com=CreateObject("ComTest.Temp")
dim num
num=com.Number(2)
msgbox num

image-20211226143229644

image-20211226143406483

# powershell 直接调用

powershell 调用是最简单的,没什么好说的。

[activator]::CreateInstance([type]::GetTypeFromCLSID("169fcde5-b131-4fa7-bd67-d738152cc4e5")).Number(2)

image-20211226145500021

# java 调用 COM

https://www.jianshu.com/p/bf6841ccd2d5

# COM 调用分析

这里我们可以使用火绒剑去监听,动作设置为 REG_openkey,路径设置为 InprocServer32 或者 LocalServer32。然后调用这个组件,就能监听到它的调用过程,当然,傻傻的去翻也不是不行。

如图,这里我们就监听到了它 open 的键,进而追踪找到了它的 InprocServer32 值。

image-20211226154950671

# COM 劫持的原理和实现

前面讲了 COM 和注册表直接的关系,而 COM 劫持也正是基于这个关系的,我们知道,系统在查询 COM 对象的时候是从注册表的三个位置从上往下找的,所以 COM 劫持就是在这个读取过程中进行劫持。

首先,这里编写一个用于劫持的 dll,老样子,弹 calc

PS:需要注意的是,在以下所有过程中,如果想要上线 cs 的话,是不能直接用 cs 生成的那个 dll 的,需要自己去用 shellcode 重新编译生成一个,这里不会的话,可以看我以前发的那个 CVE-2021-1675 的文章中免杀 dll 的生成。

image-20211226154617122

# 注册表查询顺序提前劫持

# 原理

一种常见的 COM 劫持方法是在程序读取注册表信息中的 DLL 或者 EXE 功能的路径上,做一个拦截,让程序提前读取我们的设置好的恶意 DLL 或者 EXE。COM 劫持原理在某种程度上近似于 DLL 劫持。(和 dll 劫持一样,读取的顺序有先后,我们在上游做个拦截)

Windows 系统中应用程序读取 COM 注册表信息的顺序如下:

通常情况下,com 不会注册到这里的 HKEY_CURRENT_USER\Software\Classes\CLSID,所以我们一般就是劫持做在这里。提前在这个注册表位置做跳转,这样当 COM 执行时,就会访问到这里不再往下,直接开始执行我们的恶意 dll 和 exe。

#这里需要注意的是,HKEY_CLASSES_ROO\ 下的键是从从 HKLM_LOCAL_MACHINE\SOFTWARE\Classes\ 映射过去的

所以当使用进程监控查看的时候,你可能会发现这个 com 调用的路径是是上述中的一种,但其实他们的本质都是一样的

HKEY_CURRENT_USER\Software\Classes\CLSID  
HKEY_CLASSES_ROOT\CLSID  or  HKLM_LOCAL_MACHINE\SOFTWARE\Classes\CLSID   HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\ShellCompatibility\Objects\  

# 实现

新建一个键 HKEY_CURRENT_USER\SOFTWARE\Classes\CLSID {169fcde5-b131-4fa7-bd67-d738152cc4e5}\InprocServer32, 然后修改值为我们的 dll。

reg add "HKEY_CURRENT_USER\SOFTWARE\Classes\CLSID\{169fcde5-b131-4fa7-bd67-d738152cc4e5}\InprocServer32" /d "C:\Users\administrator\Desktop\新建文件夹\dll远程创建弹calc.dll" /t REG_SZ /f

image-20211226154647783

image-20211226154704158

此时,通过火绒剑看到它的调用是直接走 HKCU 这个注册表的。

image-20211226155146679

# InprocServer32 和 LocalServer32 键值修改或文件替换

# 原理

因为系统是通过这两个键值将 com 组件载入进程或另起进程的。所以另一种劫持手段就是直接替换相应目录的文件或修改其原本的 InprocServer32 和 LocalServer32 键值,使其指向我们的恶意 dll 和 exe。

image-20211226153514511

# 实现

直接改目标键值的指向或者覆盖目标 dll 即可。

reg add "HKEY_CLASSES_ROOT\CLSID\{169fcde5-b131-4fa7-bd67-d738152cc4e5}\InprocServer32" /d "C:\Users\administrator\Desktop\新建文件夹\dll远程创建弹calc.dll" /t REG_SZ /f

image-20211226154224454

# 找到废弃的二进制引用(实用性不高)

# 原理

随着系统在使用过程中的各种应用安装卸载,而有些应用如果卸载不全面,比如在安装的时候注册了自己的 com,卸载的时候没卸载这个 com,只把文件给删了,就可能遗留下注册了的 COM 组件,但其 LocalServer32/InprocServer32 指向缺是空的情况。我们就可以扫描出设备本身缺少的或者遗留的空 COM 组件路径,然后放置我们自己恶意的文件。

而缺少对二进制路径的引用的 COM 类的数量取决于一些因素,例如:

  • 操作系统版本,家族和功能;
  • 第三方软件(已安装 / 卸载后);
  • 先前的 DLL/COM 注册。

# 实现

这里有大佬实现了,https://github.com/earthmanET/COM-Hijacker

但是其实这里有个很重要的问题,这种情况下的不好利用,因为这些被弃用的 COM 大概率是不会再被执行调用了,所以用来做权限维持不太理想,但万一呢,哪天用户又把组件下回来。

image-20211226154316432

如图,执行了该 powershell,我们可以看到,我们本机确实有不少被弃用的组件,但是其在注册表中的注册信息没被删除。

image-20211226154330505

image-20211226154336080

# COM 在权限维持中的应用

# 设置计划任务通过 powershell 或 vbs 脚本来调用自己注册的恶意 COM

这种操作会比普通 IEX 下载更迷惑,防守方如果不懂的话还可能没法进行下一步的排查分析。(缺点,还是会有蓝框弹出,需要注意,在这里不能用双引号,必须上单引号)

image-20211226155407785

# 利用现有任务进行劫持(隐蔽)

# 原理

首先,这里我们需要了解计划任务的触发器执行操作。计划任务并非只能设置运行 exe 等可执行文件,它还能设置 COM,如下图,在微软文档中声明了计划任务的 Action 存在四种操作,但后面两种弃用了。

这里我们就是劫持这些 Action 操作为 COM 的计划任务。

但通过 taskschd.msc 的管理控制器,只能新增一些 EXEC 的任务,其他任务要么弃用,要么就没显示在操作里面。

无法自己自定义一个 Action 为 ComHandler 的任务。

https://docs.microsoft.com/zh-cn/windows/win32/taskschd/taskschedulerschema-comhandler-actiongroup-element

image-20211226155736213

首先,我们分析一个 Action 为 ComHandler 的任务,这里以 Microsoft\Windows\CertificateServicesClient\UserTask 为例子。

我们发现,在控制台这边看它的操作选项是一个自定义句柄,在这里完全无法分析。

image-20211226155725333

image-20211226160912967

所以,这里还要再讲一个知识点,计划任务其实是以 xml 文件存储在 C:\Windows\System32\Tasks 目录下的,所以 UserTask 这个计划任务的 xml 文件路径其是就是 C:\Windows\System32\Tasks\Microsoft\Windows\CertificateServicesClient\

然后我们可以查看该计划任务的具体 XML 来获取更多信息,使用 schtasks query 语句或者直接翻找指定路径的文件都可以,比如这里,我们就能看到该计划任务的 Action 不是 EXEC,而是 ComHandler,即调用 COM 组件。

从下面的 XML 中我们可以看到 Triggers 标签处就是触发器,其中有 LogonTrigger,说明是存在登录触发的。而操作 Action 这个字段下的是 ComHandler 字段(如果是执行 exe 的计划任务,这里的字段显示是 EXEC),说明它是调用 COM 组件的,并且下方给出了 CLassId,即类 ID。也就是说,它执行的 COM 组件是 58FB.... 这个组件。

所以到这里,我们就已经有两种思路进行任务的劫持了。

image-20211226160821205

image-20211226160829977

# 枚举可用于 COM 劫持的计划任务

这里我们可以通过大佬的脚本去枚举可被劫持利用的 COM 组件。

https://github.com/enigma0x3/Misc-PowerShell-Stuff/

Import-Module .\Get-ScheduledTaskComHandler.ps1
Get-ScheduledTaskComHandler

脚本可以检查主机上所有在用户登录时执行并且容易受到 COM 劫持的预定任务。

这里主要是找那种 Action 设置为 Custom Handler 的计划任务。

image-20211226163049051

参数 “PersistenceLocations” 将检索易受 COM 劫持的计划任务,这些任务可用于持久性且不需要提升的特权。

Get-ScheduledTaskComHandler -PersistenceLocations

image-20211226163108193

# 注册表提前劫持(懂 com 劫持的很容易被发现)

这里做一个提前劫持,然后重新登录用户。如图,成功劫持!!(如果劫持了但是没执行 dll,就要考虑下这个计划任务的启动权限是否有权限去读我们的 dll 所在的目录了)

image-20211226161524101

image-20211226161540741

# 直接修改目标的 dll 指向(隐蔽且很难排查,但需要权限)

因为提前劫持这种操作比较容易被发现,所以我们还可以直接修改目标的 dll 指向。但这里有个问题,这些 Action 为 Comhandler 的计划任务调用的全是系统内置 COM,他们在注册表中的修改权限都是 trustedinstaller,其他用户都只有读取权限。

这里有两种思路去修改。

①手动操作,修改注册表的所有者为当前用户。缺点:需要 3389 才能修改。

image-20211226161803428

这里通过手动将目标注册表的权限改成 administrator 了,然后就可以修改指向 dll,做我们想做的事了。

image-20211226161826265

image-20211226161812331

②利用备份还原特权去修改。没有缺点

如图,成功修改注册表,直接上线。

image-20211226162121156

image-20211226162130416

image-20211226162140974

# 自己生成一个计划任务,调用自己注册的 COM(还没实现)

有时可能系统自带的服务,他的触发器不是我们想要的,所以我们可能需要自己去创建一个任务,

然后随便调用一个系统存在的 COM,再进行劫持。(或者直接就是调用我们自己注册的恶意 COM)

这里没实现,并且已经排除了权限问题和 dll 问题。

猜测是计划任务中要的 COM 是注册在其他地方的,或者是计划任务中对 Comhandler 中的值做了签名校验,非微软签名的 com 没法做计划任务。有空再研究研究。

image-20211226162640839

# 其他想法

1. 这里如果要劫持系统自带的 COM 任务的话,可以在以下链接中可以找感兴趣的去劫持。

https://chentiangemalc.wordpress.com/2011/05/08/windows-7-default-scheduled-taskscomplete-overview/

2. 可以劫持常用的程序,如微信、qq、火狐中调用的 COM(这些软件可能自己注册了 COM,就可以劫持他们)。

image-20211226162722931

更新于 阅读次数