skip to content
David Carliez
Table of Contents

Executive summary

Microsoft assigned CVE-2026-66804, Microsoft Windows Cross Device Service Elevation of Privilege Vulnerability, to an issue I reported in CrossDevice and Windows Camera Frame Server a little while ago.

Windows had a machine-wide COM registration for the CrossDevice media source. Its InProcServer32 value pointed to %PROGRAMDATA%\CrossDevice\CrossDevice.Streaming.Source.dll, but the CrossDevice directory and DLL could both be absent. A standard Windows user could then create the missing directory and place a DLL at the registered path without changing the registry.

In order to trigger it, a standard user could create a current-user virtual camera backed by the registered CrossDevice class. Windows Camera Frame Server then loaded the planted DLL into its NT AUTHORITY\LOCAL SERVICE process in session 0.

I reproduced this LocalService DLL load on Windows 11 25H2 build 26200.8875 and Windows 11 Canary build 29617.1000. A preserved run from build 26200.8875 records IMFVirtualCamera_Start=0x00000000, followed by a DLL_GET_CLASS_OBJECT event from svchost.exe running as LOCAL SERVICE with SID S-1-5-19.

Microsoft classifies the issue as CWE-284, Improper Access Control, with CVSS 7.8 (AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H). The advisory was published on August 11 as part of Patch Tuesday. Microsoft’s affected-product list includes Windows 10 22H2 and Windows 11 24H2, 25H2, and 26H1.

Attack path

Standard user
|
| creates missing C:\ProgramData\CrossDevice and plants the registered DLL
v
HKLM CrossDevice InProcServer32 registration
|
| MFCreateVirtualCamera(... CurrentUser, CrossDevice CLSID ...)
v
IMFVirtualCamera::Start
|
v
Camera Frame Server in svchost.exe, LocalService, session 0
|
v
Attacker-controlled native DLL loaded as LocalService
|
| separate generic post-compromise conversion, demonstrated on 26200.8875
v
SYSTEM marker and interactive SYSTEM shell

The SYSTEM conversion in the last step is separate from the Microsoft boundary failure. The vulnerable path ends when Frame Server loads attacker-controlled native code as LocalService. A generic impersonation technique only becomes relevant after that load.

Why CrossDevice reaches Frame Server

CrossDevice supports Windows experiences that connect a PC to a companion device. Its connected-camera feature can expose a phone to Windows applications as a camera, so the package needs a media source that bridges the companion-device stream into the Windows camera stack. On the affected systems, CLSID {E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496} identified that COM media source.

Windows Camera Frame Server brokers access to camera frames. Applications use the Media Foundation capture stack, while Frame Server hosts source-side processing in a service process. This supports shared camera access and keeps camera components out of application processes. It also creates a trust boundary: a camera request from an ordinary user can cause source code to run inside the LocalService-hosted Frame Server.

That architecture is safe only if the broker loads trusted code. In this case, a privileged broker, an in-process COM source, and a machine registration that resolved to a user-populatable path broke that assumption.

The stale COM registration

The investigation began with this machine-wide registration:

HKLM\SOFTWARE\Classes\CLSID\{E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496}\InProcServer32
(Default) = %PROGRAMDATA%\CrossDevice\CrossDevice.Streaming.Source.dll
ThreadingModel = Both

The expanded server path is C:\ProgramData\CrossDevice\CrossDevice.Streaming.Source.dll. On a clean Windows 11 25H2 build 26200.8875 system, the CLSID key existed, but neither the directory nor the DLL did. A standard user can create a missing child directory under C:\ProgramData and owns the files placed there. The registry therefore resolved to bytes that an ordinary user could supply.

The system also contained the Microsoft-signed provisioned package MicrosoftWindows.CrossDevice_0.26052.99.0_x64__cw5n1h2txyewy. This ties the registration to the Windows CrossDevice feature before proof-of-concept staging. It does not show which servicing component wrote the classic COM value or why its expected DLL was missing.

A privileged process that instantiates a machine-wide in-process COM class must load from a path the unprivileged caller cannot create, replace, or redirect. An independent validation of the resolved image could provide another control. The CrossDevice activation path I tested had neither protection.

The Media Foundation trigger

Media Foundation exposes virtual-camera creation through MFCreateVirtualCamera. The proof of concept requests a software camera source with MFVirtualCameraAccess_CurrentUser, gives it a friendly name, and identifies the source with the CrossDevice CLSID. IMFVirtualCamera::Start passes the request into Camera Frame Server.

Frame Server runs in svchost.exe -k Camera as LocalService. The caller remains an ordinary interactive user, but Frame Server instantiates the selected media source in a session 0 service process. MFVirtualCameraAccess_CurrentUser controls who can use the virtual camera. It does not make the source execute inside the caller’s process or under the caller’s token.

My binary analysis also found the CrossDevice GUID in a small exception set inside mfsensorgroup.dll’s VerifyCOMServerByClsid logic. The GUID appeared in both the 64-bit and 32-bit copies among the Windows binaries I searched. This shipped-binary observation explains why the class was reachable through the virtual-camera path, but it cannot establish the intent of Microsoft’s source code.

Reproducing the boundary crossing

The proof of concept starts from a standard, non-elevated account. It reads the machine registration, verifies that C:\ProgramData\CrossDevice is absent, creates the directory, and writes the registered target last:

C:\ProgramData\CrossDevice\CrossDevice.Streaming.Source.dll

Writing the target last matters because a persisted virtual-camera instance may cause Frame Server to load the DLL as soon as it appears. To preserve the clean-state precondition, the launcher refuses to overwrite an existing target directory.

The captured control output records the registry and service state before staging:

BUILD=26200.8875
CLSID_KEY_EXISTS=True
INPROC_DEFAULT=%PROGRAMDATA%\CrossDevice\CrossDevice.Streaming.Source.dll
PATH_EXISTS|C:\ProgramData\CrossDevice=False
PATH_EXISTS|C:\ProgramData\CrossDevice\CrossDevice.Streaming.Source.dll=False
SERVICE|FrameServer|START_NAME=NT AUTHORITY\LocalService

The attacker does not add a COM class or modify an HKLM value. The existing class already points to a location the user can populate.

The public trigger in src/virtualcamera_probe.c calls the registered class directly:

hr = create(MFVirtualCameraType_SoftwareCameraSource,
MFVirtualCameraLifetime_Session,
MFVirtualCameraAccess_CurrentUser,
friendly_name,
source_clsid,
NULL, 0, &camera);
hr = IMFVirtualCamera_Start(camera, NULL);

source_clsid defaults to {E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496}. The trigger requests a session-lifetime, current-user camera and requires no elevated access. In the successful run, IMFVirtualCamera_Start returned 0x00000000.

The payload wraps a reference Media Foundation virtual-camera source. It writes process markers, then forwards the normal COM class request to the included VCamSample reference DLL. This avoids confusing an incomplete media-source implementation with an activation failure. The original investigation recorded successful principal API calls from the wrapper together with the privileged load.

Procmon captured both caller-side and service-side image loads:

virtualcamera_probe.exe Load Image C:\ProgramData\CrossDevice\CrossDevice.Streaming.Source.dll SUCCESS
svchost.exe (PID 6248) Load Image C:\ProgramData\CrossDevice\CrossDevice.Streaming.Source.dll SUCCESS
svchost.exe (PID 6248) Load Image C:\Windows\System32\FrameServer.dll SUCCESS
svchost.exe (PID 6248) Load Image C:\Windows\System32\FrameServerCore.dll SUCCESS

The service-side event is the one that crosses the security boundary. The user-controlled DLL was unsigned, yet the existing machine registration led Frame Server to load it into a LocalService process. The caller did not receive an elevated token before this happened.

The payload marker provides a second record of the service context:

IMFVirtualCamera_Start=0x00000000
EVENT=DLL_GET_CLASS_OBJECT|PID=1124|SESSION=0|USER=LOCAL SERVICE|SID=S-1-5-19|IL=16384|IMAGE=C:\Windows\System32\svchost.exe

This establishes the primitive demonstrated in the lab: Frame Server loaded the supplied DLL in a LocalService svchost.exe process in session 0. The marker does not, on its own, describe every capability or resource available to every LocalService process.

Exploitability and scope

The demonstrated path starts with a local account that can create the missing C:\ProgramData\CrossDevice directory. It ends with a complete attacker-selected native DLL loaded by Frame Server as LocalService.

The evidence ties the service-side load to this path. The registration predates the staging directory. The launcher refuses to overwrite an existing target. The trigger uses the registered CLSID and returns success from IMFVirtualCamera_Start. The marker identifies the LocalService service process, rather than only the unprivileged caller’s own DLL load. These controls rule out a new registry registration or a user-only load as explanations for the event.

I reproduced the LocalService primitive on Windows 11 25H2 build 26200.8875 and Canary build 29617.1000. Microsoft’s advisory lists additional Windows 10 and Windows 11 releases. That product coverage comes from Microsoft, not from my two lab systems. I do not have hands-on results for the other listed releases.

The separate LocalService-to-SYSTEM stage

On build 26200.8875, the LocalService token had SeImpersonatePrivilege enabled:

nt authority\local service S-1-5-19
SeImpersonatePrivilege Impersonate a client after authentication Enabled

After the DLL was already running in Frame Server, I used a pinned, well-known generic impersonation helper as a post-compromise control. The output records a SYSTEM token and a SYSTEM-owned canary under C:\Windows\System32:

[+] PID: 428 | Token: 0x728 | User: NT AUTHORITY\SYSTEM
LOCAL_SERVICE_CHAIN=1
SIGMA_CREATE_ERROR=0
SIGMA_EXIT_CODE=0
SYSTEM_MARKER=1|PID=5984|SESSION=0|USER=SYSTEM|SID=S-1-5-18|IL=16384|IMAGE=C:\ProgramData\CrossDevice\crossdevice_system_marker.exe
SYSTEM32_CANARY_OWNER=NT AUTHORITY\SYSTEM

I also tested a session-aware helper that created cmd.exe on the active desktop. It recorded SYSTEM_SHELL_CREATED=1, and an independent query found the process in session 1 under NT AUTHORITY\SYSTEM.

I demonstrated this conversion only on build 26200.8875. Defender blocked the known helper while it was being copied to the Canary system, although the CrossDevice DLL still loaded as LocalService there. The blocked tool does not invalidate the LocalService primitive, but it also gives no evidence of an equivalent SYSTEM conversion on Canary.

The helper runs after the vulnerable DLL load. It has no role in the stale registry state, the CrossDevice exception, or Frame Server’s activation decision. Keeping the two stages separate makes the component failure clear and avoids assuming that every environment permits the same post-compromise technique.

Proof of concept

The CVE-2026-66804 CrossDevice LPE repository contains the launcher, staged DLL wrapper, virtual-camera probe, embedded reference camera source, and generic post-compromise helper.

This recording shows the proof of concept running from a standard account on Windows 11 build 26200.8875. The final shell runs as NT AUTHORITY\SYSTEM.

Build the repository, then run the launcher from a normal Command Prompt in a disposable Windows VM where the target directory is absent:

Terminal window
CrossDeviceLPE.exe

The launcher reads the CrossDevice registration, refuses an existing target directory, creates C:\ProgramData\CrossDevice, stages the components, and starts the virtual-camera probe. The probe creates a current-user virtual camera with the CrossDevice CLSID and calls IMFVirtualCamera::Start.

The repository includes an offline MinGW-w64 build script:

Terminal window
./build.sh

The build uses pinned, embedded third-party binaries and produces CrossDeviceLPE.exe. For source review, src/launcher.c checks the target state and stages the embedded components, src/virtualcamera_probe.c performs Media Foundation activation, and src/crossdevice_system_chain_wrapper.c records the LocalService stage before forwarding the camera-source request.

Run the proof of concept only in a disposable VM. It writes to C:\ProgramData\CrossDevice, activates a virtual camera, loads a DLL into a Windows service, and includes a generic SYSTEM-conversion stage. Snapshot reversion is the cleanest way to restore the system. Results may differ with the Windows build, endpoint product, existing CrossDevice state, or security policy.

To review only the root primitive, focus on four pieces of evidence: the missing target directory, the existing InProcServer32 value, the current-user virtual-camera call, and the LocalService marker. The SYSTEM components demonstrate the lab impact path on build 26200.8875 and belong to the separate post-compromise stage.

Microsoft’s fix

I compared vulnerable build 26200.8875 with patched build 26200.9168. The clearest change is the COM server registered for the CrossDevice CLSID:

HKLM\SOFTWARE\Classes\CLSID\{E9F83CF2-E0C0-4CA7-AF01-E90C70BEF496}\InProcServer32
%PROGRAMDATA%\CrossDevice\CrossDevice.Streaming.Source.dll
%SystemRoot%\System32\CrossDeviceVirtualCameraSource.dll

Microsoft did not fix the issue by creating or securing the old C:\ProgramData\CrossDevice directory. That directory remained absent on the patched VM. The update moved the COM server to System32 and installed CrossDeviceVirtualCameraSource.dll there. The file was absent on build 26200.8875. On build 26200.9168 it was version 1.26072.4.0, owned by TrustedInstaller, and signed by Microsoft Windows.

This change removes user control from the activation chain. Before the update, the registration expanded to a missing child of C:\ProgramData that a standard user could create and populate. After the update, the CLSID resolves to a Microsoft-serviced file under %SystemRoot%\System32. A standard user cannot supply or replace that image, and planting the old filename no longer affects activation because the registration no longer points to it.

The August cumulative update also changed mfsensorgroup.dll, FrameServer.dll, and FrameServerCore.dll. Different binary hashes do not prove that those files contain CVE-specific changes. I therefore treat the registration migration and the new protected COM server as the observable remediation, without claiming that they are the only internal changes Microsoft made.

Install the update for the affected Windows release. The Microsoft Security Update Guide entry lists the relevant packages.

Microsoft’s change restores the required trust property: the privileged process now resolves this COM registration to a Microsoft-owned file in a protected location. Frame Server and related activation paths can still benefit from independent checks of parent-directory permissions, reparse points, ownership, and image trust rather than relying only on the registry value.

Defenders can monitor for creation of C:\ProgramData\CrossDevice, an unsigned CrossDevice.Streaming.Source.dll at that path, and Frame Server module loads from locations outside protected Windows or package directories. Correlating virtual-camera creation with a subsequent Frame Server load from the CrossDevice directory can reduce noise. These detections can help find attempted exploitation, but they do not replace the security update.

References