COM object

We will explore Windows COM objects to understand their structure and behavior, and develop a C/C# tool for their enumeration and exploitation.

IppY0kai from the future check out my tool, ComDumper.

Intorduction

The Microsoft Component Object Model (COM) is a platform-independent, distributed, object-oriented system for creating binary software components that can interact. COM is the foundation technology for Microsoft's OLE (compound documents), ActiveX (Internet-enabled components), as well as others.

In other words, COM can be seen as a programming paradigm that Microsoft uses to build reusable system components. These components can be shared across different applications, enabling seamless integration and interaction—even between programs written in different languages.

Although Microsoft provides extensive documentation on COM, it is important to understand that COM is not simply an object-oriented programming (OOP) paradigm. Instead, COM defines a binary standard that allows software components to interact at the byte level through well-defined interfaces.

These interfaces are essentially pointers to compiled code, enabling communication between components regardless of the programming language they were written in. This low-level mechanism allows for language-agnostic reuse of functionality without requiring source code sharing or inheritance typical in OOP

COM object can be created using any programing language like C/C++ or C# using the predefinded interfaces but the defintion of an interface in this context slightly diffrent from the OOP context is like a predfined set of function ready to use.

Why Target COM?

Windows, as always, hides much of its complexity behind the COM model. The operating system relies heavily on COM to implement many advanced features such as authentication, process context switching, and inter-process communication.

Because COM objects can run with different privileges — including SYSTEM or specific user contexts — manipulating them can allow an attacker to impersonate users, maintain persistence, and escalate privileges.

For example, triggering a COM object in another user’s session can leak their NTLM hash, enabling impersonation. More advanced exploitation techniques, like the well-known RemotePotato0 vulnerability abusing DCOM objects.

Simplified Visual COM Model

COM interfaces & Objects

COM Object

An object is an instance of a class, just like an interface, which is a particular type of class. An interface represents a set of functions and attributes that are ready to be used and manipulated by the client who instantiates the object. In this case, a COM object acts like a pointer to an interface.

Interface

An interface in COM language is a set of predefined functions unlik OOP interfaces where we extend them the class should implimente the methods provided by the interface. Which mean an instance of iterface implimentation is a pointer to an array of pointers to methods — that is a function table that referes to an implimentation of all the methods specified in the interface.

Objects with multiple interfaces can provide pointers to more than one function table. Any code that has a pointer through which it can access the array can call the methods in that interface.

An instance of an interface implementation is actually a pointer to an array of pointers to methods - that is, a function table that refers to an implementation of all of the methods specified in the interface. Objects with multiple interfaces can provide pointers to more than one function table. Any code that has a pointer through which it can access the array can call the methods in that interface.

In some languages that support COM like C++ we can like call function from an interface directly by there names instead of incriment the pointer to reach it in function table. or we can define this schema.

Interface Instantation
COM client Instantiation

How Interfaces are mapped ?

Each interface is an immutable contract of a functional group of methods. You reference an interface at run time with a globally unique interface identifier (IID). This IID, which is a specific instance of a globally unique identifier (GUID) supported by COM, allows a client to ask an object precisely whether it supports the semantics of the interface, without unnecessary overhead and without the confusion that could arise in a system from having multiple versions of the same interface with the same name.

A COM class can implement multiple interfaces, grouping related functionality under one concrete implementation. Each COM class is identified by a unique CLSID (Class ID), which is a globally unique identifier (GUID) used by the system to locate and instantiate the class. Optionally, a class can also have a ProgID (Programmatic Identifier), which is a human-readable alias (like "Word.Application"). Unlike the CLSID, a ProgID is not guaranteed to be globally unique, but it's often used for convenience, especially in scripting languages.

Summery :

  • A COM interface is not a C++ class. C++ can implement a COM interface as a pure virtual class, but COM does not care about implementation details—only the binary contract.

  • A COM interface is not an object. It is just a set of function pointers. Objects implement interfaces but are separate from them.

  • COM interfaces are strongly typed. Each interface has a unique IID (a GUID), ensuring precise identification at runtime.

  • COM interfaces are immutable. You cannot modify an existing interface. Changes require defining a new one. This guarantees compatibility between components.

  • Objects can support multiple interfaces simultaneously. Each interface defines a separate contract. Clients can query for interfaces and interact with only the functionality they need.

Where COM objects are stored ?

Most COM information is stored in the Windows Registry under specific keys. The Registry acts as a central database that maps COM class IDs (CLSIDs), interface IDs (IIDs), and programmatic identifiers (ProgIDs) to their implementations.

Here are the main Registry locations relevant to COM:

Registry Key
Purpose

HKEY_CLASSES_ROOT\CLSID\{CLSID}

Stores info about COM classes, including DLL/EXE location, threading model, and ProgIDs

HKEY_CLASSES_ROOT\Interface\{IID}

Stores info about COM interfaces (human-readable names, type libraries)

HKEY_CLASSES_ROOT\ProgID

Maps human-readable ProgIDs (like "Excel.Application") to CLSIDs

HKEY_CLASSES_ROOT\TypeLib\{TypeLibID}

Stores information about type libraries (ID and versioning info)

In order to enumeratre COM objects using there identifiers ( by CLSID, by ProgID, by server executable) we can use multi tools such as OleViewDotNet created by James Forshaw and the tool COMView.

First we can create a simple python script to get the name of registed interface using his GUID.

testing it with 7zip CLSID

7zip shell extention

Now we can start explore COM object using OleViewDotNet

windows has alot of CLSIDs

CLSID Count

  • Total unique Class IDs (globally unique identifiers for COM classes)

  • Value: 11,787

InProcServer CLSID Count

  • Classes designed to run inside the caller's process (typically DLLs)

  • Value: 10,713

LocalServer CLSID Count

  • Classes running in dedicated processes (typically EXEs)

  • Value: 978

InProcHandler CLSID Count

  • Classes handling custom marshaling/communication between processes

  • Value: 135

COM statics

we can lookup CLSID for 7 zip again we can see the associated interfaces :

Notice that Interface names start with I as a naming convention.

Factory Class

factory interfaces are special interfaces used to create (instantiate) COM objects — they are like "constructors" in object-oriented programming, but more powerful and flexible because they’re COM-based.

Key Concepts:

  • Class Factory:A COM component (typically implementing IClassFactory or IClassFactory2) that acts as a creator for other COM objects. It provides methods like CreateInstance to generate new objects.

  • IClassFactory:The fundamental interface for class factories, providing the core CreateInstance and LockServer methods.

  • IClassFactory2:An extension of IClassFactory, adding features like licensing support.

  • CoGetClassObject:A function used to retrieve a class factory object from a given class identifier (CLSID).

  • CoCreateInstance:A function that combines the steps of getting the class factory and creating an instance.

we can take a look at the header file for this interface for more details check github

How it works:

1. Client requests an object:A client component, needing to use a COM object, calls CoGetClassObject or CoCreateInstance with the target object's CLSID.

2. Class factory is retrieved:CoGetClassObject locates and returns an IClassFactory interface for the specified CLSID, if available.

3. Object is created:The client calls the CreateInstance method on the retrieved IClassFactory interface, passing an interface pointer (e.g., IUnknown*) to receive the new object's reference.

4. Object is used:The client can now interact with the newly created object through the requested interface.

taking look at another commenly used class 13709620-C279-11CE-A49E-444553540000

Shell Automation Service

we can see that this Class impliment many interfaces & we have the factory interfaces too. we can take a look on IIDs of interfaces

Supported Interface + Vtables

As we can see, Vtables from shell32.dll, offsets, and some factory interfaces with IIDs are involved. Now, we need to pay much more attention to these interfaces.

IUnknown

Enables clients to get pointers to other interfaces on a given object through the QueryInterface method, and manage the existence of the object through the AddRef and Release methods. All other COM interfaces are inherited, directly or indirectly, from IUnknown. Therefore, the three methods in IUnknown are the first entries in the vtable for every interface.

In short, we can abstract it as follows (a simplified abstraction of it ).

We can take a look at its header file in the Windows SDK header files on github.

We can use COMView to see the functions implemented in a given interface.

We can observe that the IUnknown interface methods are the first three functions of every COM interface.

IUnkown implimetntation is every where

Under The hood

COM Objects Creation

Now that we have a basic understanding of how COM works, let’s dive deeper into how the instantiation mechanism operates under the hood.

The creation process goes as follow :

The client call CoCreateInstance with CLSID then com subsystem call CoGetClassObject locate regestry entry for the COM server code ( e.g DLL, EXE ) load the server code that export the implimentation of DllGetClassObject this function create an instance of the class factory that impliment IClassFactory return a pointer to it , then the client call IClassFactory::CreateInstance to get the object reference.

Simplified View Off come ecosystem

Let's take a deep look at each function we call from Microsoft Docs. Since CoCreateInstance calls CoGetClassObject under the hood, we can start with it directly.

CoGetClassObject

Provides a pointer to an interface on a class object associated with a specified CLSID. CoGetClassObject locates, and if necessary, dynamically loads the executable code required to do this.

Call CoGetClassObject directly to create multiple objects through a class object for which there is a CLSID in the system registry. You can also retrieve a class object from a specific remote computer. Most class objects implement the IClassFactory interface. You would then call CreateInstance to create an uninitialized object. It is not always necessary to go through this process however. To create a single object, call the CoCreateInstanceEx function, which allows you to create an instance on a remote machine. This replaces the CoCreateInstance function, which can still be used to create an instance on a local computer. Both functions encapsulate connecting to the class object, creating the instance, and releasing the class object. Two other functions, CoGetInstanceFromFile and CoGetInstanceFromIStorage, provide both instance creation on a remote system and object activation. There are numerous functions and interface methods whose purpose is to create objects of a single type and provide a pointer to an interface on that object.

Code Syntaxt call

[in] rclsid

The CLSID associated with the data and code that you will use to create the objects.

[in] dwClsContext

The context in which the executable code is to be run. To enable a remote activation, include CLSCTX_REMOTE_SERVER. For more information on the context values and their use, see the CLSCTX enumeration.

[in, optional] pvReserved

A pointer to computer on which to instantiate the class object. If this parameter is NULL, the class object is instantiated on the current computer or at the computer specified under the class's RemoteServerName key, according to the interpretation of the dwClsCtx parameter. See COSERVERINFO.

[in] riid

Reference to the identifier of the interface, which will be supplied in ppv on successful return. This interface will be used to communicate with the class object. Typically this value is IID_IClassFactory, although other values such as IID_IClassFactory2 which supports a form of licensing are allowed. All OLE-defined interface IIDs are defined in the OLE header files as IID_interfacename, where interfacename is the name of the interface.

[out] ppv

The address of pointer variable that receives the interface pointer requested in riid. Upon successful return, *ppv contains the requested interface pointer.

To abstract the mechanism for obtaining an object reference, we can create a demo. This will involve creating and registering a COM Class that prints hello from ippokai or any other message for a hands-on experience with C++. First, we need to create the header HelloCOM.h.

Create the HelloCOM.def file to specify which function names should be exported. This prevents the compiler from altering the exported function names, such as adding type information that changes the function name, ensuring the COM object can be registered correctly.

Create a HelloCOM.cpp file that implements the IClassFactory and IUnknown interfaces with an export function.

To compile this file and create a DLL, you can use the VS Command Prompt with the cl compiler.

we can register our new COM interface

Now, we can create the client.

Compile it.

Input:

Upon running the client, we received the message box.

Successfully called the COM object

Now, we can start working on instantiating a COM object using OleViewDoNet.exe.

How To Create Instance

now we can see the Shell Automation Service Instance

Created Instance

Tracking COM Instantiation

The process of how OleViewDoNet.exe creates the instance is quite abstract. We can try to hook API calls to see exactly what happens under the hood. Now, we can use WinDbg to debug and track the instantiation process. From previous sections, we know that the program will call DLLGetObjectClass, which returns the Class Factory pointer. We can attempt to monitor this process.

Methods from the loaded DLLs after the first run.

Loaded Functions

Now, we can set the breakpoint on DllGetClassObject.

Windbg

Here is the Stack function. We can also see there are COM base functions like CoCreateInstance and CoBase!DLLGetClassObject.

It uses a CLSID to obtain the Class Factory address and then proceeds to the next code.

CoGetClassObject

Interacting with COM objects

To interact with a COM class, we first need to get the Class Factory Pointer to instantiate the interfaces. To do so, we need to define the GUID as a global constant variable. We must initialize COM using CoInitialize, which can be called once at the beginning of our program. We can refer to Microsoft documentation for each function and global variable definition. For example, the GUID of the shell automation server 13709620-C279-11CE-A49E-444553540000 becomes as follows: GUID

To begin, we can create the first code snippet to interact with the COM interface. For clarity, we can use the StringFromCLSID function to convert the GUID Struct back to its usual form. Additionally, we can call the ProgIDFromCLSID function to obtain a human-readable string of the COM class.

[in] rclsid

The CLSID to be converted.

[out] lplpsz

The address of a pointer variable that receives a pointer to the resulting string. The string that represents rclsid includes enclosing braces.

[in] clsid

The CLSID for which the ProgID is to be requested.

[out] lplpszProgID

The address of a pointer variable that receives the ProgID string. The string that represents clsid includes enclosing braces.

Next we can initialze the COM via CoInitialize

Parameters

[in, optional] pvReserved

This parameter is reserved and must be NULL.

Load the Shell32.dll to get a pointer to the DllGetClassObject function. Add some offset calculations to verify access to the factory class function tables.

Compiling the code

Offset

We can see that we have a valid pointer to the class factory table. Now, we can call it to create an instance of our COM object. We need to use CreateInstance to get a pointer to the IDispatch interface.

Parameters

[in] pUnkOuter

If the object is being created as part of an aggregate, specify a pointer to the controlling IUnknown interface of the aggregate. Otherwise, this parameter must be NULL.

[in] riid

A reference to the identifier of the interface to be used to communicate with the newly created object. If pUnkOuter is NULL, this parameter is generally the IID of the initializing interface; if pUnkOuter is non-NULL, riid must be IID_IUnknown.

[out] ppvObject

The address of pointer variable that receives the interface pointer requested in riid. Upon successful return, *ppvObject contains the requested interface pointer. If the object does not support the interface specified in riid, the implementation must set *ppvObject to NULL.

Once we have the IDispatch interface, we can call its GetIDsOfNames function to get the COM dispatch identifier (DISPID) of ShellExecute so we can invoke it via invoke function.

Maps a single member and an optional set of argument names to a corresponding set of integer DISPIDs, which can be used on subsequent calls to Invoke. The dispatch function DispGetIDsOfNames provides a standard implementation of GetIDsOfNames.

Parameters

[in] riid

Reserved for future use. Must be IID_NULL.

[in] rgszNames

The array of names to be mapped.

[in] cNames

The count of the names to be mapped.

[in] lcid

The locale context in which to interpret the names.

[out] rgDispId

Caller-allocated array, each element of which contains an identifier (ID) corresponding to one of the names passed in the rgszNames array. The first element represents the member name. The subsequent elements represent each of the member's parameters.

And the last thing is to use Invoke function with correct program to pop a program

Parameters

[in] dispIdMember

Identifies the member. Use GetIDsOfNames or the object's documentation to obtain the dispatch identifier.

[in] riid

Reserved for future use. Must be IID_NULL.

[in] lcid

The locale context in which to interpret arguments. The lcid is used by the GetIDsOfNames function, and is also passed to Invoke to allow the object to interpret its arguments specific to a locale.

Applications that do not support multiple national languages can ignore this parameter. For more information, refer to Supporting Multiple National Languages and Exposing ActiveX Objects.

[in] wFlags

Flags describing the context of the Invoke call.

Value
Meaning

DISPATCH_METHOD

The member is invoked as a method. If a property has the same name, both this and the DISPATCH_PROPERTYGET flag can be set.

DISPATCH_PROPERTYGET

The member is retrieved as a property or data member.

DISPATCH_PROPERTYPUT

The member is changed as a property or data member.

DISPATCH_PROPERTYPUTREF

The member is changed by a reference assignment, rather than a value assignment. This flag is valid only when the property accepts a reference to an object.

[in, out] pDispParams

Pointer to a DISPPARAMS structure containing an array of arguments, an array of argument DISPIDs for named arguments, and counts for the number of elements in the arrays.

[out] pVarResult

Pointer to the location where the result is to be stored, or NULL if the caller expects no result. This argument is ignored if DISPATCH_PROPERTYPUT or DISPATCH_PROPERTYPUTREF is specified.

[out] pExcepInfo

Pointer to a structure that contains exception information. This structure should be filled in if DISP_E_EXCEPTION is returned. Can be NULL.

[out] puArgErr

The index within rgvarg of the first argument that has an error. Arguments are stored in pDispParams->rgvarg in reverse order, so the first argument is the one with the highest index in the array. This parameter is returned only when the resulting return value is DISP_E_TYPEMISMATCH or DISP_E_PARAMNOTFOUND. This argument can be set to null. For details, see Returning Errors.

we need first to create structure of paramters. Contains the arguments passed to a method or property.

Members

rgvarg

An array of arguments.

Note: these arguments appear in reverse order

rgdispidNamedArgs

The dispatch IDs of the named arguments.

cArgs

The number of arguments.

cNamedArgs

The number of named arguments.

putting all of this in one code section

we can see that we Popuped a shell

Using Shell Extension to Get a Shell

For this part, I uploaded the file onto my Windows Commando VM. However, this code can also be used on my latest Windows 11 host to get a shell without any errors, even if Defender uploads it to the cloud. Just make sure to add an AMSI bypass or change the execution policy first to run PowerShell correctly.

We can see it has a pretty low static detection on VirusTotal, even with a hardcoded reverse shell on it. So, we can create an XORed shellcode, then create a Decoder.exe. Drop it with this binary on the box, give it encode_shellcode.txt as an argument, decode it, and inject it dynamically into memory.

Virus Total

So now we have a better understanding of how to work with COM objects.

Enumerating COM Objects

Now the fun begins. Since COM objects are everywhere, we need to find a way to safely use the COM target to execute our payload and run a persistence module without crashing the COM client. Flying under the radar.

Hunting for COM Objects

To hunt for COM object targets, we need to step back and understand the lookup and registration process. The system throws a "Name not found" error if there is no DLL or EXE associated with the COM server's CLSID. In userland, if the registry is writable, we can hijack this path to gain execution.

COM object registration

Important subkeys:

  • InprocServer/InprocServer32 - in process COM objects

  • LocalServer/LocalServer32 - external COM objects, in another process

  • InprocServer/LocalServer keys are for backwards compatibility for 16 bit exes and not common

  • 32-bit, 64-bit processes/process servers use InprocServer32/LocalServer32

COM object registration registry hives:

  • Per-user object registration: HKEY_CURRENT_USER\Software\Classes\CLSID

  • System-wide registration: HKEY_LOCAL_MACHINE\Software\Classes\CLSID

  • HKEY_CLASSES_ROOT (HKCR) is a virtual registry hive, showing merged view of HKCU and HKLM

CLSID Count

The trick lies in the lookup mechanism. The resolution of a CLSID starts from HKCU (HKEY_CURRENT_USER) and then falls back to HKLM (HKEY_LOCAL_MACHINE) for normal COM clients. However, processes running with elevated privileges (e.g., as Administrator) start directly from HKLM. Any user can add registry entries to HKCU, which opens the door for COM hijacking in userland.

Keys are loaded from

  1. Per-user object registration: HKCU\Software\Classes\CLSID < -- PRECEDENCE

  2. System-wide object registration: HKLM\Software\Classes\CLSID

  • Per-user COM registration has precedence over system-wide COM object registration

    • This means keys are read from HKCU before they are read from HKLM

      • Exception to this rule: elevated process read directly from HKLM to prevent trivial privilege escalation

  • As we saw above, the vast majority of objects are registered at system-wide

  • No special privileges are required to add keys to HKCU!

  • Even if client read keys from HKCR (HKLM+HKCU), a CLSID duplicated in HKLM and HKCU will only show the value from HKCU!

Let’s consider a common scenario: you install a program, and it registers a COM object in the system registry. Sometimes, when you later uninstall that program, it only deletes the executable or DLL used by the COM class, but leaves the registry entry behind. This creates a misconfiguration that we can hunt for. If the registry is writable, it becomes possible to hijack the COM entry — by registering our own DLL at the same path. Then, whenever a user (or any process) interacts with that COM class, our malicious DLL will be loaded inside their process. This technique can also be used for persistence, since COM is triggered automatically by many apps and system services.

To stay stealthy, we can implement DLL proxying to avoid breaking the original COM behavior. This means exporting critical functions like DllGetClassObject and forwarding any others needed by the original application to work properly.

I have created a tool called ComDumper to enumerate COM objects and access them based on what we discussed earlier. I added some functions to add registry entries, which we will explore in detail shortly.

help menu

In this step, I will target a PDF reader. Foxit Reader

We can search for it using my created tool. The -sA option allows us to browse all the CLSIDs and dump all important information about them, including Access Control and path.

A few COM objects were added to the registry.

find Forixt COM objects

I haven't added the search by name feature yet, but we can use this to grab the CLSID associated with each DLL.

Received the following CLSID:

Foxit COM CLSIDs

Let's target the Thumbnail Handler. We can see that Administrators have full control over that key, so it's a good target in a scenario after privilege escalation for persistence. However, you might find another software where a normal user has full access. The good thing about COM objects is that they are everywhere, so they are triggered easily. You could also target something that opens at startup.

For the sake of this demo, I will run my shell as admin and backup the DLL so we can test the case where it's missing.

Shell Extension

Ensure no process is using the DLL file before deleting it.

I have modified the tool to add a --missing flag to check for missing DLLs. We can see that the entry is still there but with a missing DLL path.

New flags

Now, we can check for missing DLLs.

Missing DLL

Now, we can craft a DLL to be used normally in a complex assignment. We need to use DLL proxying to avoid breaking the software function. But first, let's hijack that DLL with a simple DLL that executes a reverse shell for us. We can use a simple VS Code DLLMain project as follows. I will use nc (no need for msfvenom DLL).

Once the solution is built, we can replace it in the same location and name in the file system.

For the sake of the demo, I just found out that Explorer is not using this DLL at all to step into PDF previews. So, I will work around a simple C code to trigger the COM object, and afterward, I will target 7-Zip, which will work perfectly.

Obtained shell access on my main Windows host

COMMANDO VM
Shell as COMMANDO\ippyokai

Now, we can target the same 7-Zip. I have changed some aspects of the ComDumper logic for file output handling. The full version will be presented on my GitHub repo, IppYokai.

7-Zip Shell Extension

lookup the CLSID

7-Zip CLSID

Now, we can enumerate the access as earlier {23170F69-40C1-278A-1000-000100020000}.

7-zip DLL

To back up the DLL safely, replace it, and trigger execution, we can first verify that explorer.exe loads the shell extension whenever we visit a folder with a .zip extension.

explorer loaded DLL

To release the DLL, restart the system or explorer process. Then, back up and replace it as follows:

Open just a folder that contains a zip file, and we got a shell.

Shell as commando\ippyokai

Recover the DLL.

in some senarios some groups in AD env have the ability to edit registry entries we can abuse that too using the same tool that i have created with -e flag to change the registry entry to your preferd dll

so you can directly alter InprocServer32 or LocalServer32 entries.

COM for presistence

In this section, we'll dive deeper into using DLL proxying for persistence through COM objects, while preserving original software behavior. This approach is inspired by techniques similar to those outlined in the SpecterOps article.

We will start hunting for persistence opportunities. To do this, we will use Procmon to identify COM objects accessed by processes.

As we know, the registry search order for COM objects typically follows this order: HKCU → HKLM. If Procmon shows a "NAME NOT FOUND" error for an attempt to access a CLSID under HKCU, this means the key doesn’t exist — and we can potentially create it ourselves under that location.

Since we have write permissions to HKCU, we can register our own malicious DLL there. However, to avoid breaking the functionality of the target application or the system, we must either proxy the original DLL or export the required functions expected by the process.

For example, I broke Windows Explorer during my first attempt to hijack windowscodecs.dll by not properly replicating the exports. However, I recovered my system by deleting it from another user account, using runas to access my user context and retrieve my previous HKCU.

We could obviously target 7-zip.dll again, but ideally, we want to hijack something that loads automatically at system startup — without requiring any user interaction.

To detect operations, we will rely on the power of Procmon.exe. We cannot create something as robust as Procmon hooking to detect operations using C#. Add the following filters to Procmon:

Filters

Here is the item that was targeted on the first try. It's commonly used in Windows for icon and image encoding and conversion, I believe.

Target

we can see it in the registry

registry

We can verify the entry and access control using my simple ComDumper.

Acess Control

Before diving into exploitation testing, we can use the acCOMplice tool to extract hijackable CLSIDs from a CSV file generated by Procmon.

Potential targets

We can see our target now.

Now we can add a DLL and register it to HKCU so that the system will load our DLL. We simply need to use the same threading module and components. We can use the following script. There are many tools available to generate the entry for a specific CLSID.

However, we will analyze the system to understand what we need to export from it.

Download FaceDancer tool it's will parse the .def file for us we can use it to get the exports and link them directly using #pragma . to export the function need by the system from the windowcodcs lib so that we wil not break the system.

FaceDancer

the The .def file is at the end. We can provide it to GPT or something similar to generate pragma entries.

To create a simple code that executes when the DLL is loaded, and since it is used frequently in Windows, we need to ensure our thread runs only once. We can achieve this by locking it with an Event. With this approach, use the following code to spawn only calc.exe.

We can build the solution using Visual Studio. Once the DLL is created, we can add the registry key and verify it as follows, using the same name and settings for the threading model.

we can see it's added successfully

reg edit

Visit anything that uses image encoding, such as opening an image or going to the desktop where there are some image icons. Reboot the system; it will spawn the calculator, and the system works normally. We successfully hijacked the COM object without any problems using DLL proxying.

While just browsing an image folder, calc.exe popped up.

calc popped up

It's more relevant to target something used by a web browser so that you can embed shell code on it. Or a task manager thing, but I will leave that for next time after learning a bit about Defender evasion.

Scheduled Task & COM objects

in this section we will target Scheduled Task specifc COMs. to enumerate them there is a great tool ScheduledTaskComHandler.ps1 created by Matt Nelson and Matthew Graeber .

Why is this important? Because Scheduled Tasks start without any user interaction and can execute automatically every time the system boots. This ensures we maintain persistent access to a compromised machine. They run silently in the background, so some tasks can be hijacked without generating much noise.

We can enumerate locations where we can hijack COM objects that do not require system or admin privileges, making it possible to achieve user-level persistence

Task COM's

we can spot CacheTask : This task is responsible for maintaining the Internet cache (Temporary Internet Files) and keeping it optimized. It's part of Internet Explorer / legacy networking components.

it's associated with the following CLSID {0358B920-0AC7-461F-98F4-58E32CD89148} that load wininet.dll

CacheTask

We can verify the DLL entry since I haven't added this task enumeration to my tool yet. I will add everything in the end.

The same previous method can be used since we can see that it is pointing directly to HKLM, so we can hijack it from HKCU.

Reg

This time, I will generate a simple msfvenom DLL, as it won’t break anything on the system. However, we can still apply the same technique as before using DLL proxying — this is just for the sake of the demo.

Using Silver C2

Now, we can modify the registry again.

relogin with any user

When you log out or restart the machine and log in with any user, the CacheTask will be executed automatically. We received the call from the stagger to send the rest of the payload, and we have obtained a session using Sliver C2.

Sliver C2

Clean up: Now we can delete the backdoor as follows.

Conclusion

COM abuse techniques offer a wide range of powerful opportunities for persistence and exploitation. While this overview provides a solid introduction, there is much more to explore and master for advanced learning and practical application.

The tool that I have been developing through this process can be found at: ComDumper

References & Thanks

I would like to acknowledge and thank the authors and contributors of the following outstanding resources and blogs that greatly helped deepen my understanding of COM abuse techniques and persistence:

These resources provided valuable insights, technical details, and practical examples that were instrumental in shaping this learning journey.

Sorry for any misunderstanding of the COM concept. If you have any questions, clarifications, or suggestions, feel free to reach out to me on Discord: kyuki0_1 aka 0xRabbit.

Last updated