Back to Blog

Making Windows UAC Interactive From a Browser-Based Viewer

Christopher 7 min read
remote-desktopwindowsuacsecurity

Most browser-based remote-control tools have the same Windows handoff when a UAC prompt fires: the technician's viewer goes black or freezes on the last frame, the technician calls the user, the user clicks "Yes," and work resumes. ET Ducky's Windows helper now handles UAC prompts directly. The operator sees the prompt in the viewer and can click through it from there. No phone call, no user at the keyboard.

This post walks through why that is a meaningfully hard problem on Windows, what Windows requires of a process before it will let it reach the secure desktop, and the specific chain of preconditions ET Ducky's remote-desktop helper satisfies to make UAC visible and clickable in the viewer.

Why most remote control freezes on UAC

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. That isolation is intentional: it is part of how Windows prevents a malicious process running as the user from forging a "Yes" click on its own privilege-elevation prompt.

A screen-capture API that bound itself to the user's desktop at session start is not looking at the active desktop while UAC is up. DXGI Desktop Duplication, the modern Windows capture API, returns DXGI_ERROR_ACCESS_LOST the moment the secure desktop activates. Until the user physically dismisses the prompt and Windows switches back, no new frames flow from DXGI, so the viewer freezes on whatever frame arrived last.

That default experience is consistent across most browser-based remote-control products. It is a documented Windows architectural property, not a bug in the tools. The secure desktop is designed to be hard to reach by remote processes.

What Windows requires of a process that wants to cross the boundary

Windows does provide a path for trusted 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 normal user-session process runs at Medium. Without uiAccess=true, SendInput calls from a remote-control helper aimed at UAC would be discarded by UIPI. With it honored, they are delivered.

Windows does not take a manifest claim at face value. uiAccess=true is honored 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. Granted is not enough; the privilege must be explicitly activated via AdjustTokenPrivileges before the CreateProcessAsUser call.
  4. The launching process stamps the uiAccess flag on the target token before launch. This is the non-obvious one. CreateProcessAsUser does not auto-apply the manifest's uiAccess claim to the duplicated user token; the caller has to call SetTokenInformation(token, TokenUIAccess, 1) before the launch. Without this step, the manifest claim is silently ignored.

Missing any one returns Win32 error 740 (ERROR_ELEVATION_REQUIRED) at process launch, with no indication of which precondition failed. The error message reads as a UAC-elevation problem and sends most implementers down a dead end of "make the helper elevated," which is not the fix.

How ET Ducky satisfies the chain

Each precondition is handled explicitly 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 happens server-side rather than at developer-machine build time so the signing credentials live as environment variables on the cloud host, not embedded in any released binary. Re-deploys that ship the same binary bytes are short-circuited by a source-SHA-256 idempotency check, so signing operations are billed only when the helper actually changes.

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. A corrupted or tampered binary is rejected at the cache layer, not at process-launch time.

Service privilege whitelist. The agent service runs as LocalSystem but post-Vista Windows requires services to enumerate the privileges they want. 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. The list is exhaustive and intentional: it represents the minimum set required for the agent's documented operations, not the full SYSTEM privilege set.

Per-launch privilege enable + token stamp. The agent's SessionLauncher enables SeTcbPrivilege on the calling thread's process token via AdjustTokenPrivileges before each helper launch, then stamps 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 so the launch carries enough authorization to act on behalf of the user.

With all four preconditions met, Windows honors the helper's uiAccess=true manifest, the helper runs with UIPI bypass, and the work of actually capturing and injecting on the secure desktop becomes possible.

Capturing the secure desktop

Privilege to access the secure desktop is necessary but not sufficient. DXGI Desktop Duplication's binding is set when the duplication object is created and cannot be re-pointed at a different desktop dynamically. Even if the helper has uiAccess=true honored, its DXGI session is still bound to the user's interactive desktop. Reliable secure-desktop capture across GPU drivers requires a different path.

The path that works deterministically across hardware is GDI BitBlt. 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 helper transitions to a second capture engine: the capture thread switches its desktop binding to Winlogon via SetThreadDesktop, allocates an offscreen bitmap, and calls BitBlt against the screen DC. GDI captures the desktop framebuffer but not the cursor, so the helper overlays the cursor manually 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 its original binding, and the DXGI duplication on the user's desktop re-initializes on its next AcquireNextFrame via the existing access-lost recovery path. The H.264 encoder, when active, is forced to produce an IDR on each transition so the browser's WebCodecs decoder does not try to inter-frame-predict the new content against the old reference frame. The operator sees a continuous video stream across the transition with no missing frames and no decoder artifacts.

Input is symmetric. The helper's input-injection thread re-attaches to the current input desktop before each batch of SendInput calls. While UAC is active, clicks and keystrokes from the dashboard land on the UAC dialog because the active input desktop is Winlogon; once UAC dismisses, the same code path lands input on the user's desktop. The operator does not have to switch modes; the helper handles the desktop transition on its own.

What the helper cannot do

It is worth being precise about what uiAccess=true grants and does not grant. The flag is a UI-layer privilege, not a generalized escalation. It permits the helper to bypass UIPI for the purposes of 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), not elevated. It does not get an administrator token, cannot take ownership of arbitrary files, cannot read other users' protected data, and cannot load kernel drivers.

An attacker who tried to replace the cached helper binary with their own 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. The signing credentials are not present on any agent host, so a compromised agent cannot mint a new signed helper. The same signing pipeline produces the helper for every agent in the fleet; there is no per-agent code-signing key to steal.

The practical result

The combination of all of the above produces one operator-visible behavior: a technician working an unattended endpoint can drive a software install or driver update through to completion without anyone at the local machine. UAC prompts appear in the viewer; the technician clicks "Yes" or "No"; the install continues. Workflows that previously required coordinating a phone call to "tell me when you see the UAC popup and click yes" become single-operator workflows.

The technical work to support that behavior spans a chain of fixes in the agent service, the helper binary, the cloud API's signing pipeline, the Windows installer's privilege configuration, and the dual capture engine. Most of those fixes are invisible: they exist to satisfy Windows' precondition chain. The visible result is one of the differences between a remote-control tool that can drive a Windows session and one that can only watch it.

Drive Windows installs end-to-end from the browser

ET Ducky's remote desktop captures the user's interactive desktop and the Windows secure desktop, so technicians can interact with UAC prompts and lock-screen workflows without anyone at the target machine.

Get Started Free