Back to Blog

Making Windows UAC Interactive From a Browser-Based Viewer

Christopher 7 min read
remote-desktopwindowsuacsecurity

This post documents the mechanism by which the ET Ducky Windows remote-desktop helper captures the Windows secure desktop (the desktop on which UAC consent prompts, Ctrl+Alt+Del, and lock screens render). It describes the Windows preconditions for uiAccess=true, the agent's deployment steps that satisfy each precondition, and the capture path used while the secure desktop is active.

Windows secure desktop isolation

When Windows triggers a UAC consent prompt, the OS switches the active input desktop from the user's interactive desktop to a separate desktop called Winlogon. The two desktops are isolated at the kernel level. Processes bound to the user's desktop cannot read pixels off the secure desktop and cannot inject input into it. This isolation is a documented Windows architectural property.

A screen-capture API bound to the user's desktop at session start is not bound to the active desktop while UAC is up. DXGI Desktop Duplication returns DXGI_ERROR_ACCESS_LOST when the secure desktop activates. No new frames are emitted from that DXGI duplication until the secure desktop dismisses and Windows switches back.

Windows preconditions for uiAccess=true

Windows provides a path for UI-injection processes to operate against the secure desktop. The mechanism is a manifest flag called uiAccess="true", which grants the bearer process the right to bypass User Interface Privilege Isolation. UIPI is the kernel facility that prevents lower-integrity processes from sending input to higher-integrity targets. The Windows secure desktop runs at SYSTEM integrity; a user-session process runs at Medium. Without uiAccess=true honored, SendInput calls from a user-session process aimed at the secure desktop are discarded by UIPI.

Windows honors uiAccess=true only when four preconditions are satisfied at the same time:

  1. The binary is Authenticode-signed by a CA-trusted publisher. Self-signed certificates and unsigned binaries are rejected. The publisher must chain to a root in the local machine's trusted-publisher store.
  2. The binary resides in a Windows-defined secure location. Program Files, Program Files (x86), Windows, and System32 are the four. ProgramData, the user's profile, and arbitrary paths are not.
  3. The launching process holds SeTcbPrivilege enabled on the calling thread's token. The privilege must be explicitly activated via AdjustTokenPrivileges before the CreateProcessAsUser call. Granted but disabled is not sufficient.
  4. The launching process sets the uiAccess flag on the target token before launch. CreateProcessAsUser does not auto-apply the manifest's uiAccess claim to the duplicated user token. The caller must call SetTokenInformation(token, TokenUIAccess, 1) before the launch. Without this step, the manifest claim is ignored.

If any precondition is missing, CreateProcessAsUser returns Win32 error 740 (ERROR_ELEVATION_REQUIRED) at process launch with no indication of which precondition failed.

Agent deployment that satisfies the chain

Each precondition is handled in the agent's deployment.

Signing. The remote-desktop helper, ETDucky.RdpHelper.exe, is signed once per release on the cloud API server using SSL.com's CodeSignTool with the ET Ducky LLC EV code-signing certificate. The same certificate signs the agent installer. Signing runs server-side; the signing credentials are stored as environment variables on the cloud host and are not embedded in any released binary. A source-SHA-256 idempotency check short-circuits re-deploys that ship identical binary bytes.

Secure location. Each agent's RdpHelperUpdaterService downloads the signed binary from the cloud API and stages it under C:\Program Files\ETDucky\Agent\RdpHelper\<version>\. Before caching the new copy, the updater verifies the SHA-256 against the value the API advertised and runs WinVerifyTrust to validate the Authenticode chain locally. Corrupted or tampered binaries are rejected at the cache layer.

Service privilege whitelist. The agent service runs as LocalSystem. Post-Vista Windows requires services to enumerate the privileges they require. The agent installer runs sc privs ETDuckyAgent immediately after sc create, setting the required-privileges list to SeChangeNotifyPrivilege, SeAuditPrivilege, SeImpersonatePrivilege, SeTcbPrivilege, SeAssignPrimaryTokenPrivilege, SeIncreaseQuotaPrivilege, and SeDebugPrivilege. Privileges outside the list are removed from the service's process token at start.

Per-launch privilege enable and token stamp. The agent's SessionLauncher enables SeTcbPrivilege on the calling thread's process token via AdjustTokenPrivileges before each helper launch, then sets the uiAccess flag on the duplicated user token via SetTokenInformation(TokenUIAccess, 1) immediately before CreateProcessAsUser. The token's impersonation level is set to SecurityImpersonation rather than SecurityIdentification.

With all four preconditions satisfied, Windows honors the helper's uiAccess=true manifest and the helper runs with UIPI bypass.

Secure-desktop capture path

DXGI Desktop Duplication's binding is set when the duplication object is created and cannot be re-pointed at a different desktop dynamically. The helper's DXGI session is bound to the user's interactive desktop. Secure-desktop capture uses GDI BitBlt as a separate path.

The helper's capture loop polls the current input desktop each iteration via OpenInputDesktop and GetUserObjectInformation. When the input desktop is the secure desktop, the capture thread switches its desktop binding to Winlogon via SetThreadDesktop, allocates an offscreen bitmap, and calls BitBlt against the screen DC. GDI does not capture the cursor, so the helper overlays the cursor with GetCursorInfo and DrawIconEx. The captured BGRA pixels go through the same encoder pipeline as user-desktop captures and ship over the same agent-to-cloud WebSocket.

When the secure desktop dismisses, the helper detects the input-desktop change on the next poll, switches the capture thread back to the user-desktop binding, and the DXGI duplication on the user's desktop re-initializes on its next AcquireNextFrame via the access-lost recovery path. When H.264 encoding is active, the encoder is forced to emit an IDR on each transition so the WebCodecs decoder does not inter-frame-predict across the transition.

Input injection follows the same desktop-binding pattern. The helper's input-injection thread re-attaches to the current input desktop before each batch of SendInput calls. Input lands on the active input desktop (Winlogon while UAC is active, the user's desktop otherwise).

Scope of uiAccess=true

uiAccess=true is a UI-layer privilege. It permits the helper to bypass UIPI for reading the screen and injecting input across integrity-level boundaries. It does not grant the helper additional file system, registry, network, or process-token privileges. The helper runs at the interactive user's integrity level (Medium on a standard user account). It does not receive an administrator token, does not take ownership of arbitrary files, does not read other users' protected data, and does not load kernel drivers.

A binary replacement attack on the cached helper would fail at three checkpoints, the agent's update-time SHA-256 verification, the agent's update-time WinVerifyTrust validation, and Windows' process-launch-time Authenticode validation for uiAccess. Signing credentials are not present on agent hosts. The signing pipeline produces a single signed helper per release for the entire fleet; there is no per-agent code-signing key.

ET Ducky

Documentation and pricing are available on this site.

View Pricing