July 17, 2026
COM Oversimplified: Understanding The Windows Technology Behind COM Hijacking, DCOM, and Modern…
Before we start, my apologies in advance. This is going to be a long one. Needed to really break this down as much as I can for y’all…

By Mr. Robot.txt
32 min read
Before we start, my apologies in advance. This is going to be a long one. Needed to really break this down as much as I can for y'all. Enjoy the ride.
So a while back, while exploring COM objects from Powershell, I came across the following command:
$excel = New-Object -ComObject Excel.Application$excel = New-Object -ComObject Excel.ApplicationAt first glance, this doesn't look particularly interesting. We ask Powershell to create an instance of Excel.Application, and moments later Excel springs to life. Simple enough. But the more I stared at that one line, the more questions I found myself asking. How does Powershell know where Excel is installed? How does it know which executable to launch? How does it know what functionality Excel exposes? Most importantly, how can Powershell interact with an object that doesn't even live inside the Powershell process? If this works locally, could another machine somewhere on the network do the exact same thing remotely?
As it turns out, answering those questions takes us down a rabbit hole that eventually leads to COM, DCOM, marshaling, interfaces, COM hijacking, UAC bypasses, and some of the techniques that threat actors have been abusing for quite some time now.
By the end of this article, we'll understand what actually happens behind the scenes when Powershell executes that single line.
The Problem COM was Trying to Solve
To understand COM, it helps to first understand the problem Microsoft was trying to solve. Imagine you're developing a piece of software, and you need the ability to generate Excel spreadsheets. One option would be to build spreadsheet functionality yourself where you could spend months implementing:
- Workbook creation
- Formula calculations
- Cell formatting
- Charts
- File exports
Essentially rebuilding Excel from scratch. That obviously doesn't make much sense. What if your application could simply just ask Excel to do the work instead? If an application needed to:
- Schedule a task
- Browse files
- Manage shortcuts
- Interact with Office documents
- Access functionality exposed by another application
Before COM, every software vendor would have needed to invent their own way of exposing those capabilities.
· Different APIs.
· Different communication mechanisms.
· Different integration methods.
· Different documentation.
The result would have been chaotic. Microsoft needed a standard way for software components to expose functionality that other software could consume. Not just for C++ applications, not just for Microsoft applications, but for virtually any language capable of talking to Windows. The result was Component Object Model, more commonly known as COM.
At a high level, COM provides a standard that allows software components to expose functionality as reusable objects that other applications can interact with. Rather than rebuilding functionality, applications can simply request an existing component and use the capabilities it already exposes.
This idea becomes much easier to understand when we revisit our original example.
$excel = New-Object -ComObject Excel.Application$excel = New-Object -ComObject Excel.ApplicationPowershell isn't implementing spreadsheet functionality. It's instead asking Windows for a component that already knows how to do it. The question now becomes:
How does Windows know what Excel.Application actually refers to?
COM Objects as Reusable Capabilities
Let's go back to our original command.
$excel = New-Object -ComObject Excel.Application$excel = New-Object -ComObject Excel.ApplicationAt this point, we know Powershell is asking Windows for some sort of reusable component that already knows how to interact with Excel. But what exactly is Excel.Application? Many COM explanations immediately start throwing terms like CLSIDs, Interfaces, Type Libraries and GUIDs at the reader. Let's resist that temptation for a moment. Instead, let's think about COM objects the same way Windows thinks about them: as capabilities.
For example, imagine we wanted to automate Excel. We could ask Windows for Excel.Application, or if we wanted access to functionality exposed by Windows Explorer, we could ask for Shell.Application, or if we wanted to interact with the Task Scheduler service Schedule.Service. Notice a pattern? We're not asking Windows for files, we're not asking Windows for DLLs, we're not even asking Windows for executables, instead, we're asking Windows for capabilities.
In other words:
Need spreadsheet functionality?
↓
Excel.Application
Need shell functionality?
↓
Shell.Application
Need task scheduling functionality?
↓
Schedule.ServiceNeed spreadsheet functionality?
↓
Excel.Application
Need shell functionality?
↓
Shell.Application
Need task scheduling functionality?
↓
Schedule.ServiceOne of the most useful mental models for COM is to think of Windows as a giant catalogue of reusable capabilities. Applications register functionality they wish to expose. Other applications request that functionality when they need it. This is why COM appears absolutely everywhere throughout Windows as the operating system itself uses COM extensively:
· Microsoft Office uses COM.
· Windows Explorer uses COM.
· Administrative tools use COM.
· Third-party software uses COM.
In many cases, software that appears completely unrelated could be communicating through COM behind the scenes.
The next question naturally becomes, when we ask for Excel.Application how does Windows know what that actually refers to? Surely there must be some lookup happening somewhere. The answer lives inside the Windows Registry.
Following Excel.Application into the Registry
The first thing to understand is that Excel.Application is not the actual identity of the COM object. It's simply a friendly name. As we're all aware, we humans are terrible at remembering long identifiers. Microsoft knew this. So COM provides human-readable names known as Programmatic Identifiers, more commonly referred to as ProgIDs.
For example, Excel.Application is a ProgID. Similarly, Shell.Application is another ProgID. The operating system doesn't ultimately identify COM classes using these names. Instead, every COM class receives a globally unique identifier called a CLSID.
A CLSID typically looks like this:
{00024500-0000-0000-C000-000000000046}{00024500-0000-0000-C000-000000000046}Not exactly something most people want to memorize. So when we execute:
$excel = New-Object -ComObject Excel.Application$excel = New-Object -ComObject Excel.ApplicationWindows performs a lookup. Conceptually, it looks something like this:
Excel.Application
|
v
ProgID
|
v
CLSID
|
v
Registry EntryExcel.Application
|
v
ProgID
|
v
CLSID
|
v
Registry EntryThe first thing we can do is inspect the ProgID registration itself. Open Registry Editor and navigate to: HKEY_CLASSES_ROOT
Searching for Excel.Application reveals a registry entry containing information about the COM class. One particularly important value is the CLSID associated with that ProgID.
Conceptually:
Excel.Application
|
v
{Excel CLSID}Excel.Application
|
v
{Excel CLSID}Once Windows obtains the CLSID, it can locate the actual registration for the COM class. This is where things become interesting. Navigate to HKCR\CLSID
and you'll discover thousands of COM classes registered on a typical Windows system.
Each CLSID contains information describing:
- How the COM class should be activated
- Where its implementation lives
- Whether it is implemented as a DLL or executable
- Security information
- Additional metadata used by COM
At this point we've answered one question.
When Powershell requests, Excel.Application, Windows performs a series of registry lookups until it locates the corresponding COM registration. But we're still missing a critical piece of the puzzle. Finding the registration doesn't actually create the object. Something still needs to launch Excel and create the COM object for us. That process is known as COM Activation/instantiation and it's one of the most important concepts in understanding how COM works under the hood.
COM Activation: What Actually Happens When we Request a COM Object
One thing that's easy to forget is that COM objects don't simply materialize out of thin air. Somebody has to create them. Let's revisit our original command one more time:
$excel = New-Object -ComObject Excel.Application$excel = New-Object -ComObject Excel.ApplicationTo us, this looks like a single operation. In reality, a surprising amount of work occurs behind the scenes. At a very high level, the activation process looks like this:
Powershell
|
v
CoCreateInstance()
|
v
COM Runtime
|
v
COM Service Control Manager
|
v
Registry Lookup
|
v
Launch COM Server
|
v
Create COM Object
|
v
Return InterfacePowershell
|
v
CoCreateInstance()
|
v
COM Runtime
|
v
COM Service Control Manager
|
v
Registry Lookup
|
v
Launch COM Server
|
v
Create COM Object
|
v
Return InterfaceWe'll unpack each stage shortly. Before we do that, there's one important clarification. When discussing COM, you'll frequently encounter the acronym, SCM. Many Windows administrators immediately think, Service Control Manager which is responsible for Windows Services.
That is not the SCM we're talking about here. COM has its own SCM, COM Service Control Manager whose primary responsibility is locating and activating COM classes. Think of it as the traffic controller responsible for bringing COM objects to life. When Powershell invokes:
New-Object -ComObject Excel.ApplicationNew-Object -ComObject Excel.Applicationit eventually results in a call to a COM API known as: CoCreateInstance()
This API effectively tells the COM subsystem, "I want an instance of this COM class. Please go find it and create it for me." The COM runtime then hands the request to the COM Service Control Manager which performs several tasks:
- Locate the CLSID registration.
- Determine where the COM server lives.
- Determine how that server should be activated.
- Launch the server if necessary.
- Ask the server to create the requested object.
- Return a usable interface back to the caller.
The interesting part is how the SCM determines where the implementation actually lives. This brings us to the registry. We need to look at two registry values that frequently appear during COM investigations:
- InProcServer32
- LocalServer32
These two registry values determine whether a COM class is implemented as a DLL or an executable. This distinction is really important in how COM communication works.
InProcServer32 vs LocalServer32
Earlier, we followed our Powershell command through the COM activation process.
$excel = New-Object -ComObject Excel.Application$excel = New-Object -ComObject Excel.ApplicationWe saw that the COM Service Control Manager (SCM) eventually performs a registry lookup to determine how the requested COM class should be activated. At this point, the SCM has found the CLSID registration.
The next question becomes, where is the actual implementation of this COM object? After all, a CLSID is just an identifier. At some point Windows still needs to find the code responsible for implementing the functionality exposed by that COM class. This is where two registry values become extremely important:
InProcServer32
LocalServer32InProcServer32
LocalServer32If you've ever investigated COM hijacking, COM persistence, UAC bypasses, or COM-based malware, chances are you've already encountered one of these. Let's look at them individually.
InProcServer32
Suppose we inspect a COM registration and find something similar to:
HKCR\CLSID\{GUID}\InProcServer32HKCR\CLSID\{GUID}\InProcServer32The name itself gives us a clue, In Process Server. This means the COM class is implemented as a DLL which the COM runtime loads directly into the process requesting it.
Conceptually:
powershell.exe
|
|
+----> LoadLibrary()
|
v
COM DLLpowershell.exe
|
|
+----> LoadLibrary()
|
v
COM DLLNotice something interesting? There is no second process, there is no Excel.exe, there is no separate executable. The DLL simply gets loaded into the caller's process and the COM object is created there. This approach has a major advantage: it's fast. Since both the caller and the COM object exist within the same process meaning:
· No inter-process communication is required.
· No RPC
· No LRPC.
· No marshaling across process boundaries.
· The caller can directly interact with the object in memory.
This is why many COM components are implemented using DLLs. The overhead is minimal.
LocalServer32
Now let's look at another registration.
HKCR\CLSID\{GUID}\LocalServer32HKCR\CLSID\{GUID}\LocalServer32This means something very different. Instead of a DLL, the COM class is implemented by an executable. Take for instance:
reg query “HKCR\CLSID\{GUID}\LocalServer32”
(Default) = "C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE"reg query “HKCR\CLSID\{GUID}\LocalServer32”
(Default) = "C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE"Now our architecture changes completely. Instead of loading a DLL into PowerShell, Windows launches a separate process.
powershell.exe
|
|
v
excel.exepowershell.exe
|
|
v
excel.exeSuddenly we have two different processes which introduces a new problem. Powershell lives in one process, while the COM object lives in another. How do they communicate? We'll answer that shortly. For now, the important thing to understand is that LocalServer32 means:
Separate Process = Process BoundarySeparate Process = Process Boundaryand process boundaries always introduce complexity.
A Quick Investigation
Let's look at what this means from a defender's perspective. Suppose an incident response report contains the following CLSID:
{917E8742-AA3B-7318-FA12-10485FB322A2}{917E8742-AA3B-7318-FA12-10485FB322A2}One of the first things we might want to know is, what actually gets executed when this COM object is instantiated? We can begin by locating the CLSID registration.
reg query "HKCR\CLSID" /s /f "{917E8742-AA3B-7318-FA12-10485FB322A2}"reg query "HKCR\CLSID" /s /f "{917E8742-AA3B-7318-FA12-10485FB322A2}"Once we've located the registration, we can inspect its activation information.
reg query "HKCR\CLSID\{917E8742-AA3B-7318-FA12-10485FB322A2}"reg query "HKCR\CLSID\{917E8742-AA3B-7318-FA12-10485FB322A2}"Suppose the output reveals LocalServer32 rather than InProcServer32.
That tells us that the COM class is implemented as an executable rather than a DLL. We can then inspect the executable path:
reg query "HKCR\CLSID\{917E8742-AA3B-7318-FA12-10485FB322A2}\LocalServer32"
(Default) REG_SZ "C:\Users\LATITUDE7400 2in 1\AppData\Local\Microsoft\OneDrive\25.137.0715.0001\Microsoft.SharePoint.exe"reg query "HKCR\CLSID\{917E8742-AA3B-7318-FA12-10485FB322A2}\LocalServer32"
(Default) REG_SZ "C:\Users\LATITUDE7400 2in 1\AppData\Local\Microsoft\OneDrive\25.137.0715.0001\Microsoft.SharePoint.exe"At this point we've learned something useful. Whenever this COM class is activated, Windows will eventually launch Microsoft.SharePoint.exe.
This allows us to continue our investigation by:
· Hashing the executable
· Checking reputation
· Reviewing digital signatures
· Performing static analysis
· Performing dynamic analysis
Without ever having executed the COM object ourselves. This is one reason COM registration analysis is such a useful skill for defenders.
Why COM Hijacking Works
Understanding InProcServer32 and LocalServer32 also explains one of the most common COM persistence techniques.
Let's imagine a legitimate application expects the following registration:
InProcServer32
|
v
C:\Program Files\Vendor\Product.dllInProcServer32
|
v
C:\Program Files\Vendor\Product.dllWhenever the application requests the COM object, Windows loads Product.dll and everything behaves normally. But what if an attacker modifies the registration?
InProcServer32
|
v
C:\Users\Bob\AppData\Roaming\evil.dllInProcServer32
|
v
C:\Users\Bob\AppData\Roaming\evil.dllThe application itself hasn't changed, the executable hasn't changed, the user launches the same software they've always used. The difference is that Windows now loads a completely different DLL when resolving the COM class.
Conceptually:
Victim Application
|
v
COM Activation
|
v
Registry Lookup
|
v
Attacker DLLVictim Application
|
v
COM Activation
|
v
Registry Lookup
|
v
Attacker DLLThe victim application unknowingly loads attacker-controlled code. This technique is commonly referred to as COM Hijacking.
One reason COM hijacking remains attractive is that the application triggering execution is often perfectly legitimate. From the user's perspective, nothing appears unusual. The software launches normally, yet attacker-controlled code executes during the activation process.
The Problem at Hand
At this point we've answered several questions. We know that:
Excel.Application
|
v
ProgID
|
v
CLSID
|
v
Registry
|
v
LocalServer32
|
v
excel.exeExcel.Application
|
v
ProgID
|
v
CLSID
|
v
Registry
|
v
LocalServer32
|
v
excel.exeWe know how COM activation works: we know how Windows locates the COM server. We know the difference between DLL-based and executable-based COM servers but we still haven't answered one important question.
Consider the following command:
$excel.Visible = $true$excel.Visible = $trueExcel is running inside excel.exe Powershell is running inside powershell.exe. These are completely separate processes. So how is Powershell able to manipulate an object that lives inside another process? How can it call methods? How can it read properties? How can it receive responses? To answer that, we need to talk about interfaces and as we'll soon discover, interfaces are arguably the most important concept in COM.
COM is Really About Interfaces
Earlier, we followed our Powershell command through COM activation.
$excel = New-Object -ComObject Excel.Application$excel = New-Object -ComObject Excel.ApplicationWe watched the COM runtime locate the appropriate registration, activate the COM server, and eventually return something back to Powershell but what exactly was returned? Most people assume the answer is an Excel object. Technically that's true but it isn't the most useful way to think about COM. In reality, COM is less concerned with objects and far more concerned with interfaces.
In fact, if there's one concept you should take away from this article, it's this: COM objects are interesting because of the interfaces they expose; not because of their CLSIDs, not because of their ProgIDs, not because of where they're registered. Those things help us find the object. The interfaces tell us what we can actually do with it.
COM Object as a Building
Let's use a simple analogy. Imagine a large office building, you don't interact with the building itself. Instead, you enter through doors. Each door grants access to a particular set of functionalities.
COM Object
+---------------+
| |
| Building |
| |
+---------------+
| | |
| | |
V V V
Door1 Door2 Door3 COM Object
+---------------+
| |
| Building |
| |
+---------------+
| | |
| | |
V V V
Door1 Door2 Door3The building is the COM object. The doors are the interfaces. When a process wants to interact with a COM object, it doesn't typically say, "Give me everything." Instead it says, "Give me access through this specific interface." The interface defines:
· Which methods can be called
· Which properties can be read
· Which properties can be modified
· Which events can be received
In other words:
COM Object
|
v
Interfaces
|
v
Methods / Properties / EventsCOM Object
|
v
Interfaces
|
v
Methods / Properties / EventsThis distinction becomes extremely important when performing COM research. Two COM objects might expose completely different functionality. Likewise, a single COM object might expose multiple interfaces, each granting access to different capabilities.
Revisiting Excel.Application
Let's revisit our Excel example.
$excel = New-Object -ComObject Excel.Application$excel = New-Object -ComObject Excel.ApplicationWhat can we actually do with this object? One of the easiest ways to answer that question is:
$excel | Get-Member$excel | Get-Member
The output typically contains a large number of entries. Some examples include:
- Methods
- Properties
- Events
Immediately we learn something. The COM object isn't just sitting there doing nothing. It's exposing functionality: things we can call; things we can modify; things we can query.
For example:
$excel.Visible = $true$excel.Visible = $truemakes Excel visible.
$excel.Quit()$excel.Quit()terminates Excel.
$excel.Workbooks.Add()$excel.Workbooks.Add()creates a workbook. All of those capabilities originate from interfaces exposed by the COM object.
IUnknown: The Interface Every COM Object Starts With
Almost every COM object begins with a special interface known as IUnknown. You can think of this as the foundation of COM. Every other interface ultimately builds on top of it. IUnknown exposes three methods:
- QueryInterface()
- AddRef()
- Release()
At first glance these names don't seem particularly exciting, yet they are responsible for much of COM's architecture. Let's break them down.
QueryInterface()
Suppose we enter our office building through one door then later we discover another door exists. How do we ask for access to it? That's essentially what QueryInterface() does.
Conceptually:
Current Interface
|
|
QueryInterface()
|
v
Another InterfaceCurrent Interface
|
|
QueryInterface()
|
v
Another InterfaceA caller can ask, "Do you support Interface X?" If the COM object does, access is granted. If not, the request fails. This mechanism allows a single COM object to expose multiple interfaces without requiring callers to know everything up front.
AddRef()
Imagine several processes are using the same COM object. The COM runtime needs a way to determine whether the object is still required. This is where reference counting enters the scene. Whenever a process begins using an interface:
Reference Count
1Reference Count
1Another process uses it:
Reference Count
2Reference Count
2And so on. AddRef() increments that count.
Release()
Eventually a process finishes using the object. At that point it calls Release(), the reference count decreases by 1 until it eventually reaches zero. When the count eventually reaches zero:
Reference Count
0Reference Count
0the COM runtime knows nobody needs the object anymore and it can be cleaned up.
NOTE: When interacting with COM objects using Powershell users never directly interact with AddRef() or Release(), they're constantly operating behind the scenes.
IDispatch: The Interface Powershell Loves
When working with COM through Powershell, another interface becomes far more interesting, IDispatch. This interface is one of the reasons COM automations became so popular. To understand why, let's imagine life without it. Suppose a developer wants to interact with Excel. Without automation support, the developer would need detailed knowledge about:
· Which interfaces exist
· Which methods they expose
· Which parameters they accept
· Which data types they return
That quickly becomes painful but IDispatch solves this problem. It provides a mechanism that allows scripting environments to discover functionality dynamically. This is why Powershell can do things like:
$excel | Get-Member$excel | Get-Memberwithout requiring prior knowledge of Excel's internals. Powershell just asks, "Tell me what functionality you expose and COM responds."
High Level View of IDispatch
Internally, IDispatch exposes several methods. The most important ones conceptually are:
- GetTypeInfo()
- GetIDsOfNames()
- Invoke()
Let's imagine we execute:
$excel.Visible = $true$excel.Visible = $truePowershell doesn't magically know where the Visible property lives. Instead, a process roughly similar to the following occurs. First Visible is resolved to an internal identifier using GetIDsOfNames() method that helps perform the lookup. Then Invoke() is used to execute the operation associated with that identifier.
Conceptually:
Visible
|
v
GetIDsOfNames()
|
v
Internal ID
|
v
Invoke()
|
v
Execute OperationVisible
|
v
GetIDsOfNames()
|
v
Internal ID
|
v
Invoke()
|
v
Execute OperationThe exact implementation details aren't particularly important right now. What matters is understanding the role IDispatch plays. It acts as the bridge that makes COM automation practical and without it, Powershell automation would be significantly more complicated.
Nested COM Objects
There's another detail that's easy to miss when using the Get-Member powershell commandlet.
Consider:
$excel | Get-Member$excel | Get-MemberSome entries appear as properties, for example, Workbooks or ActiveMenuBar
Many people assume that means: Simple string, or boolean value but not necessarily. In COM, a property can itself be another COM object. Take for instance:
$excel.Workbooks$excel.Workbooksreturns another object which means we can continue exploring it.
$excel.Workbooks | Get-Member$excel.Workbooks | Get-MemberAnd discover yet another set of methods and properties.
Conceptually:
Excel.Application
|
+------> Workbooks
|
+------> Workbook
|
+------> WorksheetExcel.Application
|
+------> Workbooks
|
+------> Workbook
|
+------> WorksheetYou can think of COM objects almost like nested containers exposing additional functionality as you drill deeper. This becomes extremely useful during COM research because valuable functionality is often hidden several layers beneath the top-level object.
Creating Our First Workbook
Let's see a practical example.
First we create an Excel COM object:
$excel = New-Object -ComObject Excel.Application$excel = New-Object -ComObject Excel.ApplicationNext we access the Workbooks collection.
$workbook = $excel.Workbooks.Add()$workbook = $excel.Workbooks.Add()At first glance, this appears to be a simple method call. But something fascinating is happening here. Powershell is calling functionality exposed by a COM object that ultimately lives inside another process. That raises an important question. How is Powershell able to invoke methods inside Excel when Excel is running in a completely separate process? How can a method call originating inside powershell.exe reach an object living inside excel.exe? The answer lies in one of the most important concepts in COM, Marshaling.
Marshaling: The Problem COM Had to Solve
Earlier we created an Excel COM object.
$excel = New-Object -ComObject Excel.Application$excel = New-Object -ComObject Excel.ApplicationWe then called methods on it and even modified the value of some of its properties.
$excel.Visible = $true
$excel.Workbooks.Add()$excel.Visible = $true
$excel.Workbooks.Add()To us, these look like ordinary function calls. In fact, they look no different from interacting with a normal PowerShell object. But here's the problem: Excel isn't running inside Powershell; it's running inside excel.exe while Powershell is running inside powershell.exe. These are completely different processes. So how exactly is Powershell talking to Excel? To answer that, we need to briefly discuss how modern operating systems manage memory.
Why Powershell Can't Interact with Objects Exposed by Excel Directly
One of the responsibilities of an operating system is process isolation. Every process receives its own virtual address space. For example, powershell.exe might have an object located at 0x00A1BC2D. Meanwhile excel.exe might also have something located at 0x00A1BC2D. The address may be identical. The contents are not. This is because memory addresses are meaningful only within the context of the process that owns them. A pointer inside Excel is useless to Powershell.
To visualize this, consider:
powershell.exe
0x1000 -> Object A
0x2000 -> Object B
0x3000 -> Object Cpowershell.exe
0x1000 -> Object A
0x2000 -> Object B
0x3000 -> Object Cand
excel.exe
0x1000 -> Workbook
0x2000 -> Worksheet
0x3000 -> Cellexcel.exe
0x1000 -> Workbook
0x2000 -> Worksheet
0x3000 -> CellNotice that both processes can have identical memory addresses, yet they refer to completely different things. This means COM cannot simply return 0x2000 and tell Powershell, "There's your Excel object." That pointer would be meaningless outside Excel's process. Therefore, COM needed another solution: Marshaling
Marshaling as a Courier Service
When people first encounter marshaling, it often sounds incredibly complicated. In reality, the idea is surprisingly simple. Instead of sending memory addresses, COM sends instructions. Think of it like a courier service. Suppose Powershell executes:
$excel.Visible = $true$excel.Visible = $truePowershell doesn't tell Excel, "Here's a pointer. Go modify it." Instead, COM converts the request into something more structured.
Conceptually:
Object:
Excel.Application
Property:
Visible
Value:
TrueObject:
Excel.Application
Property:
Visible
Value:
TrueThe request is then packaged/serialized into a format suitable for transport. Something like:
[Excel.Application]
[Visible]
[True][Excel.Application]
[Visible]
[True]The exact format is more complicated, but the idea remains the same. The request becomes serialized data. Only then can it be transported to another thread, another process, or even another machine. Once Excel receives the request, the reverse process occurs. The message is unpacked. Excel determines which object should receive the request. Excel modifies the property. A response is generated and sent back.
Conceptually:
Powershell
|
v
Package Request
|
v
Transport
|
v
Excel
|
v
Execute
|
v
Return ResultPowershell
|
v
Package Request
|
v
Transport
|
v
Excel
|
v
Execute
|
v
Return ResultThis entire process is called Marshaling
Why Marshaling is Important
Without marshaling, COM would be limited to objects living inside the same process. The moment a process boundary appeared, things fall apart. Because of marshaling, COM can support:
Thread
↓
Thread
Process
↓
Process
Machine
↓
MachineThread
↓
Thread
Process
↓
Process
Machine
↓
MachineThe caller doesn't need to care where the object lives. COM handles the complexity. This is one of the reasons COM became such a powerful technology. Applications can now interact with functionality regardless of whether it lives:
· Inside the same process
· Inside another process
· On another machine
The programming model remains largely unchanged.
The Marshaling Process
At this point we understand what marshaling is. But another question immediately appears. What actually performs all of this work? What packages the request? What sends it? What receives it? What reconstructs it? To answer that, we need to introduce two of COM's most important components: Proxy and Stub.
The Proxy and The Stub
Imagine once again that we're interacting with Excel. powershell.exe contains the caller. excel.exe contains the actual COM object.
A simplified view looks like this:
Powershell
|
|
Proxy
|
|
======= Process Boundary =======
|
|
Stub
|
|
Excel ObjectPowershell
|
|
Proxy
|
|
======= Process Boundary =======
|
|
Stub
|
|
Excel ObjectThe Proxy lives on the client side while the Stub lives on the server side. Together they hide the complexity involved in marshaling.
The Proxy
The easiest way to think about the Proxy is that, it pretends to be the real COM object. When Powershell receives a COM object, what it often receives isn't the actual object. It's a local representative of it, a proxy. To Powershell, everything appears normal.
$excel.Visible = $true$excel.Visible = $truelooks like a direct method invocation but in reality the call first reaches the Proxy.
Powershell
|
v
ProxyPowershell
|
v
ProxyThe Proxy's job is to:
· Capture the request
· Marshal the request
· Send the request to the server
Conceptually:
Visible = True
|
v
Proxy
|
v
Serialized RequestVisible = True
|
v
Proxy
|
v
Serialized RequestThe Stub
The Stub performs the opposite operation. It receives the marshaled request.
Serialized Request
|
v
StubSerialized Request
|
v
StubIts job is to:
· Unmarshal the request
· Identify the target object
· Invoke the appropriate method
· Return the result
Conceptually:
Stub
|
v
Excel Object
|
v
Execute Method Stub
|
v
Excel Object
|
v
Execute MethodThe result then travels back through the same path.
Excel Object
|
v
Stub
|
v
Proxy
|
v
PowershellExcel Object
|
v
Stub
|
v
Proxy
|
v
PowershellWhy Interprocess Communication Feels Local
One of the clever things about COM is that it intentionally hides most of this complexity. From Powershell's perspective:
$excel.Visible = $true$excel.Visible = $truefeels like a local function call while what actually happened is something similar to:
Powershell
|
v
Proxy
|
v
Marshaling
|
v
LRPC / RPC
|
v
Stub
|
v
Excel Object
|
v
Execute
|
v
Return ResultPowershell
|
v
Proxy
|
v
Marshaling
|
v
LRPC / RPC
|
v
Stub
|
v
Excel Object
|
v
Execute
|
v
Return ResultThe entire communication stack remains invisible to the caller. This illusion is one of COM's greatest strengths. Developers can focus on functionality while COM handles communication.
Local COM vs Remote COM
The exact transport mechanism depends on where the object lives. If both processes reside on the same machine, COM commonly uses LRPC (Local Remote Procedure Call). Despite the strange name, it simply refers to an optimized RPC mechanism for local communication.
Conceptually:
powershell.exe
|
LRPC
|
excel.exepowershell.exe
|
LRPC
|
excel.exeIf the COM object resides on another machine, COM transitions to traditional RPC communication.
Machine A
|
RPC
|
Machine BMachine A
|
RPC
|
Machine BAnd just like that, we've crossed from COM into DCOM territory. But before we move to remote COM, there's another problem Microsoft had to solve. Imagine two different threads simultaneously trying to manipulate the same COM object. One thread creates a workbook, another thread immediately closes Excel. Who wins? More importantly, how do we stop the COM object from entering an inconsistent state? To solve that problem, COM introduced another concept that confuses us almost as much as marshaling, Apartments.
Apartments: How COM Prevents Chaos
We've now seen how Powershell can communicate with a COM object living inside another process.
Powershell
|
Proxy
|
Marshaling
|
Stub
|
Excel ObjectPowershell
|
Proxy
|
Marshaling
|
Stub
|
Excel ObjectThe request reaches Excel, Excel executes the operation, the result comes back. Problem solved. Or is it? Let's think about something for a moment. Suppose we have two threads interacting with the same Excel object.
Thread A executes:
$excel.Workbooks.Add()$excel.Workbooks.Add()while at the exact same time Thread B executes:
$excel.Quit()$excel.Quit()Which one wins the race? Does Excel create the workbook first? Does it terminate first? What happens if one thread is modifying a workbook while another thread attempts to save it? What happens if five threads are simultaneously trying to interact with the same object? These are not COM problems. These are software engineering problems, any application exposing functionality to multiple callers eventually runs into:
· Race conditions
· Deadlocks
· Data corruption
· Inconsistent state
Microsoft needed a way to ensure COM objects could safely handle concurrent access. The solution was something called, Apartments. Unfortunately, apartments are often explained in a way that makes them sound far more complicated than they really are. So let's do what we do best, oversimplify them.
Apartment as a Traffic Rule
Most people hear the term apartment and immediately imagine memory structures, threads, synchronization primitives and operating system internals. Forget all of that for now and just think of an apartment as a rule: more specifically, a rule that determines which threads are allowed to directly interact with a COM object. That's it.
The apartment model doesn't define the object, it doesn't define marshaling, it doesn't define interfaces; it defines how requests reach the object once they arrive.
The Simplest Apartment: Single Threaded Apartment (STA)
Suppose Excel decides, "I only trust one thread to interact with me." That's essentially what a Single Threaded Apartment does.
Conceptually:
Excel Object
|
|
STA ThreadExcel Object
|
|
STA ThreadOne thread owns the object and every other interaction must eventually reach or go through that thread. Now imagine several threads attempt to access Excel simultaneously.
- Thread A
- Thread B
- Thread C
Do they all interact with Excel directly? No. COM redirects those requests back to the apartment thread.
Conceptually:
Thread A ----\
\
Thread B ------> STA Thread ---> Excel Object
/
Thread C ----/Thread A ----\
\
Thread B ------> STA Thread ---> Excel Object
/
Thread C ----/This means only one thread interacts with the object at a time which dramatically simplifies development. Excel developers no longer need to worry about Thread A modifying Workbook X while Thread B deletes Workbook X because COM ensures requests are serialized. One request enters, the object processes it, then the next request arrives, then the next, and so on.
Why Office Applications Love STA
Many Office applications were designed long before multicore processors became the norm. They also tend to be heavily driven by user interfaces. Consider Excel for example, there is typically one user interacting with:
· Menus
· Buttons
· Worksheets
· Charts
at a given moment. This aligns very naturally with the STA model. As a result, applications such as:
· Excel
· Word
· PowerPoint
have historically relied heavily on Single Threaded Apartments. This concept becomes important when automating Office through COM because requests often need to be marshaled back to that apartment thread before they can execute.
Message Queues: The Hidden Mechanism
At this point you might be wondering: If every request must reach the STA thread, how does COM actually do that? The answer is message queues. Imagine the STA thread owns the Excel object, requests arrive from different threads. Instead of executing immediately, they are placed into a queue.
Conceptually:
Excel STA Thread
Queue
------------------
Visible = True
Calculate()
Save()
Quit()
------------------Excel STA Thread
Queue
------------------
Visible = True
Calculate()
Save()
Quit()
------------------The STA thread processes these requests one at a time.
Request 1
↓
Execute
Request 2
↓
Execute
Request 3
↓
ExecuteRequest 1
↓
Execute
Request 2
↓
Execute
Request 3
↓
ExecuteThis greatly reduces the likelihood of race conditions because the object never receives multiple simultaneous requests.
Effects of the Synchronous Nature of STA
If you've automated Office applications before, you may have encountered situations where COM appears unresponsive.
For example:
$excel.Quit()$excel.Quit()might unexpectedly fail or the call appears to hang or COM returns a "Server Busy" error. This often confuses newcomers because the COM object was successfully instantiated. The interface exists. Communication works. So why is the request failing? The answer often lies in the apartment model.
Imagine Excel is currently processing a lengthy operation. Perhaps:
· The user is interacting with a dialog box.
· A workbook is recalculating.
· Excel is displaying a modal prompt.
Meanwhile your automation request arrives only to find the STA thread already occupied. Since that thread owns the object, your request must wait. This means that COM has to queue the request and say server is busy or in some cases, reject it entirely. Therefore, what sometimes may appears to be a COM problem can often be a threading problem.
Multi-Threaded Apartments (MTA)
Now let's look at the other model. Suppose a COM server decides, "I don't want a single thread handling everything. I want multiple threads interacting with the object directly." This is called a Multi-Threaded Apartment (MTA).
Conceptually:
Thread A ----\
\
Thread B ------> COM Object
/
Thread C ----/Thread A ----\
\
Thread B ------> COM Object
/
Thread C ----/Unlike STA, requests do not need to be funneled through a single apartment thread. Multiple threads may execute concurrently. This can significantly improve performance but there's a tradeoff. The developer now becomes responsible for ensuring the object remains safe when accessed by multiple threads simultaneously. This means dealing with:
· Synchronization
· Locks
· Race conditions
· Shared resources
In other words:
More Performance = More ComplexityMore Performance = More ComplexityHow COM Knows Which Apartment to Use
Earlier we saw Powershell create an Excel object.
$excel = New-Object -ComObject Excel.Application$excel = New-Object -ComObject Excel.ApplicationBefore a thread can participate in COM operations, it must initialize the COM runtime. Historically this was performed using:
CoInitialize()CoInitialize()Modern applications commonly use:
CoInitializeEx()CoInitializeEx()When invoked, these functions tell COM that a thread intends to participate in COM operations. They also allow the caller to specify the apartment model when initializing the COM runtime.
For example:
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);creates an STA thread.
While:
CoInitializeEx(NULL, COINIT_MULTITHREADED);CoInitializeEx(NULL, COINIT_MULTITHREADED);creates an MTA thread.
At this point COM knows how requests should be routed and how the thread intends to interact with COM objects.
Importance of Apartments to Security Researchers
When most red teamers first encounter COM, their focus tends to be:
· COM hijacking
· DCOM
· UAC bypasses
· Privileged COM objects
Which is perfectly reasonable, but understanding apartments often explains some of the strange behavior researchers encounter while automating COM objects.
For example:
· Why certain calls hang.
· Why Office automation occasionally fails.
· Why COM returns busy errors.
· Why requests sometimes appear delayed.
The answer often isn't hidden inside the COM object itself. It's hidden inside the apartment model that governs how requests reach the object.
A Mental Model for COM So Far
At this point we've covered a lot of ground. Let's simplify everything we've learned.
When Powershell executes:
$excel.Workbooks.Add()$excel.Workbooks.Add()three major layers are involved.
Layer 1: Interfaces
These define what functionality is available.
- IUnknown
- IDispatch
- Excel Interfaces
Layer 2: Communication
These are responsible for moving requests between processes.
- Proxy
- Stub
- Marshaling
- RPC / LRPC
Layer 3: Execution
These determine how requests execute once they reach the object.
- STA
- MTA
- Thread Ownership
- Message Queues
Conceptually:
Powershell
|
IDispatch
|
Proxy
|
Marshaling
|
Stub
|
STA Thread
|
Excel ObjectPowershell
|
IDispatch
|
Proxy
|
Marshaling
|
Stub
|
STA Thread
|
Excel ObjectWhile all of this has been happening locally, nothing we've discussed actually requires the COM object to be on the same machine. What happens if the process boundary becomes a network boundary? What happens if the COM object lives on another computer entirely? That's where COM evolves into something called, Distributed COM (DCOM).
DCOM: COM Across the Network
Up until now, everything we've discussed has happened on a single machine. Our architecture has looked something like this:
powershell.exe
|
Proxy
|
Marshaling
|
Stub
|
excel.exepowershell.exe
|
Proxy
|
Marshaling
|
Stub
|
excel.exePowerShell lives in one process, Excel lives in another process, COM handles the communication, simple enough. But let's revisit a question we asked at the beginning of this article. If COM can communicate across process boundaries, what stops it from communicating across machine boundaries? Nothing!
From COM to DCOM
DCOM (Distributed Component Object Model), despite the intimidating name, DCOM is really just COM extended across the network.
Conceptually, our architecture changes from:
powershell.exe
|
excel.exepowershell.exe
|
excel.exeto
Machine A
|
Network
|
Machine BMachine A
|
Network
|
Machine BThe fascinating part is that most of the concepts we've already learned remain exactly the same. We still have:
· COM objects
· Interfaces
· Marshaling
· Proxies
· Stubs
The only difference is where the object lives. Instead of living inside another process on the same machine, it now lives inside another process on another machine.
Conceptually:
Machine A
PowerShell
|
Proxy
|
RPC
|
====================
Network
====================
|
RPC
|
Stub
|
COM Object
Machine BMachine A
PowerShell
|
Proxy
|
RPC
|
====================
Network
====================
|
RPC
|
Stub
|
COM Object
Machine BNotice something? We didn't have to invent an entirely new architecture. We simply replaced LRPC with RPC. Everything else remains largely unchanged. This is one reason COM was such a powerful design. Microsoft built a model that could operate:
Thread
↓
Thread
Process
↓
Process
Machine
↓
MachineThread
↓
Thread
Process
↓
Process
Machine
↓
Machinewithout fundamentally changing how developers interacted with objects.
The First Problem: How Does Machine A Find Machine B?
Suppose a COM client wants to activate an object remotely. How does it even know where to send the request? This is where Remote Procedure Call (RPC) enters the picture. When DCOM communication begins, one of the first components involved is RPC Endpoint Mapper which typically listens on TCP/135. If you've performed Windows network enumeration before, you've almost certainly encountered port 135.
Many people know it's related to RPC. Fewer people realize how important it is to DCOM. Think of the Endpoint Mapper as a receptionist. Suppose you arrive at a large office building. You know the company you're trying to reach but you don't know which office they're sitting in. The receptionist provides directions. Similarly, a DCOM client contacts the Endpoint Mapper and essentially asks, "Which endpoint should I use to communicate with this service?" The Endpoint Mapper responds with the appropriate information, allowing communication to continue.
Conceptually:
Client
|
TCP/135
|
Endpoint Mapper
|
Assigned Endpoint
|
Target ServiceClient
|
TCP/135
|
Endpoint Mapper
|
Assigned Endpoint
|
Target ServiceThis is why RPC and DCOM traffic frequently appear together during network investigations.
Remote Activation
Earlier we learned about COM activation. CoCreateInstance() eventually led to:
· Registry lookup
· COM activation
· Object creation
· Interface return
DCOM performs a very similar process. The difference is that activation occurs remotely.
Conceptually:
Machine A
|
Remote Activation Request
|
Machine B
|
COM SCM
|
Registry
|
Launch COM Server
|
Create ObjectMachine A
|
Remote Activation Request
|
Machine B
|
COM SCM
|
Registry
|
Launch COM Server
|
Create ObjectNotice something interesting? The COM Service Control Manager still exists. The Registry still exists. COM activation still occurs. It's simply happening on another machine. The architecture we spent the last several sections learning didn't disappear; it just moved.
AppIDs: The Security Layer
At this point, a reasonable question should appear. If COM objects can be activated remotely, what stops anybody on the network from activating them? The answer lies in another COM concept, AppID. An AppID acts as an additional layer of configuration and security. While a CLSID identifies a COM class, an AppID often controls settings such as:
· Launch permissions
· Activation permissions
· Access permissions
· Authentication requirements
Conceptually:
CLSID
|
v
AppID
|
v
Security SettingsCLSID
|
v
AppID
|
v
Security SettingsThis allows administrators to control who can:
· Launch a COM server
· Activate a COM server
· Communicate with a COM server
Without these controls, DCOM would be a security nightmare.
Why DCOM is Appealing to Attackers
At this point you might already see where this is going. Let's step back and think about what DCOM provides. A machine can expose functionality, another machine can request that functionality, objects can be instantiated remotely, methods can be invoked remotely and results can be returned remotely. To an attacker, that starts sounding very interesting because those same capabilities can potentially be used for:
· Remote administration
· Remote execution
· Lateral movement
And historically, they have been.
DCOM Abuse for Lateral Movement
One of the reasons DCOM appears so frequently in offensive security discussions is that certain COM objects expose functionality capable of launching processes or interacting with the operating system. Imagine a COM object that allows execution of commands. If that object can be activated remotely, the implications become obvious.
Historically, researchers have demonstrated remote execution through various DCOM-enabled objects including:
- MMC20.Application
- ShellWindows
- ShellBrowserWindow
The details vary between objects, but the underlying idea remains the same: an attacker authenticates to a remote machine, the attacker activates a COM object remotely, they invoke methods exposed by that object. Those methods ultimately trigger actions on the target machine.
From a defender's perspective, understanding DCOM often explains seemingly strange network activity involving:
- RPC
- TCP/135
- Dynamic Ephemeral RPC Ports
- svchost.exe
- mmc.exe
- explorer.exe
that might otherwise look unrelated.
A Familiar Pattern
One of the most interesting observations about DCOM is that it doesn't introduce fundamentally new concepts. In fact, most of what we've learned throughout this article still applies. Whether the object lives inside the same process or inside another process or On another machine, the architecture remains remarkably similar.
We still have:
Interface
↓
Proxy
↓
Marshaling
↓
Stub
↓
ObjectInterface
↓
Proxy
↓
Marshaling
↓
Stub
↓
ObjectThe distance changed but the design did not. This is precisely why understanding COM is so important for security practitioners. The same architecture that enables Excel automation also underpins:
· COM hijacking
· UAC bypasses
· DCOM lateral movement
· Living-off-the-land techniques
To truly understand how attackers abuse COM, we first needed to understand how COM actually works. Now that we've built that foundation, we can finally examine the techniques threat actors abuse in the wild.
Why Threat Actors Love COM
Most security technologies focus on detecting things that look unusual:
· Unknown executables.
· Suspicious scripts.
· Untrusted binaries.
· Unexpected network traffic.
COM presents a different challenge:
· COM is not a third-party framework.
· It's not an optional Windows feature.
· It's part of how Windows itself operates.
· Office uses COM.
· Explorer uses COM.
· Administrative tools use COM.
· Countless legitimate applications use COM every day.
This means attackers are often able to abuse trusted functionality that already exists on the system. Rather than introducing something new, they simply leverage something Windows already trusts and that makes detection significantly more difficult.
Let's start with one of the most common examples, COM Hijacking.
COM Hijacking: When Windows Loads The Wrong Component
Earlier in the article, we learned that COM activation ultimately depends on registry entries. When an application requests a COM object, Windows follows a path similar to:
ProgID
↓
CLSID
↓
Registry Lookup
↓
InProcServer32 / LocalServer32
↓
Load ComponentProgID
↓
CLSID
↓
Registry Lookup
↓
InProcServer32 / LocalServer32
↓
Load ComponentAt the time, we treated this as a normal part of COM activation but now let's look at it from an attacker's perspective. Imagine your malware running on a system and you discover that a legitimate application regularly requests a particular COM object, e.g:
· Microsoft Office
· Windows Explorer
· OneDrive
· A third-party application
The application isn't doing anything malicious. It's simply performing its normal startup routine. Eventually it reaches a point where Windows performs a COM lookup.
Application
|
v
COM Activation
|
v
Registry LookupApplication
|
v
COM Activation
|
v
Registry LookupNow ask yourself a simple question. What if the registry lookup returns an attacker-controlled component instead?
The Trust Problem
Most applications trust the COM subsystem and for good reason since COM has existed for decades. Applications typically assume:
Requested CLSID
|
v
Correct ComponentRequested CLSID
|
v
Correct ComponentBut Windows can only load what the registry tells it to load. If the registration changes:
Requested CLSID
|
v
Attacker DLLRequested CLSID
|
v
Attacker DLLWindows doesn't magically know something is wrong. From its perspective, it's simply following the registration information available to it. This is the core idea behind COM hijacking. The application remains unchanged, the user launches the same software, the executable remains trusted but the component being loaded is now attacker-controlled.
A Simplified Example
Imagine a COM registration originally looks like this:
CLSID
|
v
InProcServer32
|
v
C:\Program Files\Vendor\Product.dllCLSID
|
v
InProcServer32
|
v
C:\Program Files\Vendor\Product.dllEverything behaves normally. The application requests the COM object and Windows loads Product.dll.
Now imagine an attacker gains sufficient permissions to modify the registration and changes the path to.
CLSID
|
v
InProcServer32
|
v
C:\Users\Bob\AppData\Roaming\evil.dllCLSID
|
v
InProcServer32
|
v
C:\Users\Bob\AppData\Roaming\evil.dllThe next time the application requests the COM object:
Application
|
v
COM Activation
|
v
Registry Lookup
|
v
evil.dllApplication
|
v
COM Activation
|
v
Registry Lookup
|
v
evil.dllWindows inadvertently loads the attacker's DLL. From the application's perspective, nothing unusual happened. It requested a COM object and Windows returned one. The application has no idea the implementation changed underneath it.
Why COM Hijacking is Attractive
Notice what the attacker didn't have to do? They didn't need:
· Process injection
· DLL injection
· Exploiting a vulnerability
· Patching an executable
Instead, they abused a trusted mechanism already built into Windows and even better, execution often occurs through a legitimate process, for example:
· explorer.exe
· winword.exe
· excel.exe
· outlook.exe
· onedrive.exe· explorer.exe
· winword.exe
· excel.exe
· outlook.exe
· onedrive.exeFrom a defensive perspective, that can make analysis significantly more difficult. Seeing explorer.exe launch isn't unusual, seeing winword.exe launch isn't unusual. The interesting question becomes, what did they load?
HKCU vs HKLM: A Common Abuse Pattern
One reason COM hijacking became popular is because of how COM registrations are resolved. Earlier, we briefly mentioned HKEY_CLASSES_ROOT commonly referred to as HKCR. What many people don't realize is that HKCR is actually a merged view.
Conceptually:
HKCU\Software\Classes + HKLM\Software\Classes = HKCRHKCU\Software\Classes + HKLM\Software\Classes = HKCRThis behavior has significant security implications. Suppose a COM registration exists in HKLM\Software\Classes which typically requires administrative privileges to modify. That seems reasonably secure. But if Windows first checks HKCU\Software\Classes an attacker may be able to create a user-specific registration that takes precedence.
Conceptually:
HKCU Registration
|
v
Windows Uses This
Instead Of
HKLM RegistrationHKCU Registration
|
v
Windows Uses This
Instead Of
HKLM RegistrationNo administrator rights required, no modification of system-wide registrations; just a user-level override. This pattern appears repeatedly throughout COM hijacking research.
Hunting for COM Hijacks
Suppose you're investigating a potentially compromised machine. Where do you begin? One approach is to identify COM registrations that resolve to unusual locations.
For example:
- C:\Users\
- AppData\
- Temp\
- Downloads\
These locations aren't inherently malicious but they're often worth investigating particularly when a COM registration points somewhere unexpected (directories that don't require high privileges to write on).
For example:
InProcServer32
|
v
C:\Users\Bob\AppData\Roaming\example.dllInProcServer32
|
v
C:\Users\Bob\AppData\Roaming\example.dllmight warrant closer inspection than:
InProcServer32
|
v
C:\Program Files\Vendor\Product.dllInProcServer32
|
v
C:\Program Files\Vendor\Product.dllAdditional indicators might include:
· Unsigned DLLs
· Recently modified registrations
· Missing files
· Suspicious parent processes
· Unexpected user-specific COM registrations
The reason why this technique is coveted by threat actors is because it provides:
- Persistence
- Execution
- Trust
all while leveraging existing Windows functionality. Rather than dropping a suspicious startup executable or creating an obvious scheduled task, an attacker can hide behind a mechanism already used extensively by legitimate software and because COM activation happens constantly across Windows, malicious activity can easily blend into normal system behavior.
COM and UAC Bypasses
Another area where COM frequently appears is User Account Control (UAC) bypass research. To understand why, we need to revisit something we learned earlier. COM objects are capabilities. Some are relatively harmless while others are extremely powerful. For example, certain COM classes are designed to perform administrative tasks.
Historically, researchers discovered that some of these privileged COM objects could be abused in unexpected ways. One famous example involved, ICMLuaUtil which became associated with several UAC bypass techniques.
The specifics of individual bypasses have changed over time and Microsoft has addressed many of them, but the underlying lesson remains important. Attackers weren't exploiting a memory corruption vulnerability. They were studying how trusted COM objects behaved, then identifying ways to abuse that behavior.
Once again, the pattern should feel familiar. Rather than introducing malicious functionality, attackers leverage functionality Windows already trusts.
Living Off the Land with COM
At this point, a theme should be emerging. Whether we're discussing:
· COM hijacking
· UAC bypasses
· DCOM lateral movement
the attacker isn't usually creating something new. They're simply abusing something that already exists. This is one reason COM fits naturally into Living-Off-The-Land techniques. Windows ships with thousands of registered COM classes. Many expose powerful functionality. Some can:
· Manipulate files
· Interact with Office applications
· Launch processes
· Perform administrative actions
· Communicate across the network
From a threat actor's perspective, that's an enormous attack surface. The challenge isn't finding functionality but understanding what functionality already exists and that's exactly why understanding COM matters.
Following a COM Object End-To-End
Let's finish by revisiting the journey that started this entire article.
$excel = New-Object -ComObject Excel.Application$excel = New-Object -ComObject Excel.ApplicationWhat once looked like a simple Powershell command now tells a much larger story.
Windows first resolves Excel.Application into a CLSID. The COM Service Control Manager uses that information to locate the registration. The registration identifies where the implementation lives. The COM server is then activated and interfaces returned. From here client requests are marshaled to the COM server. Proxies and stubs handle the communication. Apartment models control execution and if necessary, the entire process can even occur across the network through DCOM.
Conceptually:
Excel.Application
|
v
ProgID
|
v
CLSID
|
v
Registry
|
v
COM Activation
|
v
Interfaces
|
v
Proxy / Stub
|
v
Marshaling
|
v
Excel ObjectExcel.Application
|
v
ProgID
|
v
CLSID
|
v
Registry
|
v
COM Activation
|
v
Interfaces
|
v
Proxy / Stub
|
v
Marshaling
|
v
Excel ObjectWhat began as a single line of Powershell turned out to be an entire ecosystem of technologies working together behind the scenes and once you understand how those pieces fit together, many security concepts that initially seem unrelated suddenly start making sense:
· COM hijacking.
· DCOM lateral movement.
· UAC bypasses.
· Office automation.
· Living-Off-The-Land techniques.
They're all built on the same foundation. A foundation that most Windows users interact with every day without ever realizing.
Conclusion
COM is one of those technologies that quietly sits beneath large portions of the Windows operating system. Most users never notice it, most administrators rarely think about it, yet countless applications rely on it every single day. For security practitioners, understanding COM provides something far more valuable than the ability to automate Excel or inspect registry keys.
It provides a framework for understanding how software components communicate, how functionality is exposed across process boundaries, and why certain offensive techniques work in the first place.
The next time you encounter a CLSID during an investigation, stumble across an AppID in the registry, or see a threat report mentioning COM hijacking or DCOM lateral movement, you'll know that the story didn't begin with the attack. It began much earlier with a technology designed to answer a deceptively simple question: How do programs talk to other programs.