Learning Objectives
- Understand what UAC does and — critically — what it doesn't protect against
- Explain auto-elevation, the COM elevation moniker, and why trusted processes matter
- Know which UAC setting levels each bypass technique requires
- Understand why all techniques in this course require the user to already be a local admin
What UAC Actually Is
User Account Control is a consent and friction mechanism, not a security boundary. Microsoft's own documentation acknowledges it cannot stop a determined local attacker with code execution. Its purpose is twofold: prevent accidental privilege use by administrators running at medium integrity, and make privilege escalation visible so users can make an informed decision.
Every technique in this course requires the attacker to already be a local administrator running at medium integrity — UAC is the only barrier between them and a high-integrity shell. These are not techniques for gaining initial access.
Auto-Elevation — The Core Attack Surface
Windows maintains a list of trusted, Microsoft-signed binaries that are allowed to elevate without prompting the user. These are marked with autoElevate: true in their application manifests and must reside in a trusted directory (C:\Windows\System32, Program Files, etc.). The Application Information service (appinfo.dll) enforces these checks.
Every bypass in this course targets what happens after an auto-elevating process or COM object is granted elevation — specifically, what DLLs it loads, what registry keys it reads, and what child processes it spawns. The auto-elevation itself is legitimate. The abuse is in the context the elevated process provides to the attacker.
UAC Setting Levels and Bypass Applicability
Windows UAC has four settings. All techniques in this course target the default setting (second from top) and below. The "Always Notify" setting (maximum) eliminates auto-elevation entirely and breaks all these techniques.
-
Windows UAC offers four levels, controlled via
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System:- Always Notify (max) — every elevation prompts, auto-elevation disabled. Breaks ALL techniques in this course. Rarely deployed because it severely disrupts admin workflows.
- Notify when apps try to make changes (default) — Microsoft-signed apps auto-elevate silently. This is the setting that makes all bypasses in this course viable.
- Notify only (no secure desktop) — same as default but no secure desktop dimming. Slightly weaker.
- Never Notify (off) — UAC completely disabled. No bypass needed — processes can elevate freely.
💡 Check current UAC level:reg query HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v ConsentPromptBehaviorAdmin— value 5 = default, value 2 = always notify, value 0 = disabled. -
The COM Elevation Moniker is a special string format that requests an elevated COM object without a UAC prompt, provided the COM class is on the auto-approval list and the caller is running in an appropriate context:
Format:
"Elevation:Administrator!new:{CLSID}"When passed to
CoGetObject(), this moniker asks COM to instantiate the class at high integrity. The key requirements:- The CLSID must be registered in
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UAC\COMAutoApprovalList - The calling process must be running in a trusted context — typically explorer.exe or another shell process
- The COM class must be marked as auto-elevated in its registry entry
⚠ If called from an arbitrary process (not explorer.exe), CoGetObject with the elevation moniker will fail or prompt. This is why Bypass #1 injects into explorer.exe first. - The CLSID must be registered in
-
COM auto-elevation checks the calling process's trust level before granting elevation. Specifically, it verifies the caller is running in an interactive user context via a shell window (explorer.exe or derivative). A standalone attacker process doesn't satisfy this check.
Two approaches used in this course:
- DLL injection into explorer.exe (Bypass #1) — inject the COM activation code into the trusted explorer.exe process. The COM call originates from a trusted context. Simple and reliable.
- PEB Masquerading (Subscriber bonus) — modify our own process's PEB to make it appear as if it's explorer.exe. More elegant, no injection required, but more complex to implement correctly.
💡 PEB masquerading works because the trust check reads the process image path from the PEB, not from the actual on-disk binary. By overwriting the relevant PEB fields with explorer.exe's path, we satisfy the check without actually being explorer.exe. -
Two tools for verifying COM auto-elevation status before writing any code:
Registry check: Look up the CLSID in
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UAC\COMAutoApprovalList. If it's listed with a value of 1, it's on the auto-approval list.OleView.exe (from the Windows SDK): Load the COM class → check the Properties panel for "Auto Approval" and "Elevation" flags. OleView also shows all exposed interfaces, which is how the ICMLuaUtil interface and its ShellExec method were identified for Bypass #1.
- OleView path (after SDK install):
C:\Program Files (x86)\Windows Kits\10\bin\x64\oleview.exe - Search by CLSID: Edit → Find → paste the GUID
- Binary Ninja or IDA for analyzing the actual DLL that implements the interface
- OleView path (after SDK install):
Learning Objectives
- Understand the CMSTPLUA COM object and why its ICMLuaUtil interface enables elevation
- Define a COM virtual function table (vftable) stub correctly in C++ to reach ShellExec
- Use CoGetObject() with the elevation moniker to request an elevated COM instance
- Inject the COM activation DLL into explorer.exe to satisfy the trusted context requirement
- Build both the payload DLL and the injector executable in Visual Studio
CMSTPLUA — The COM Object
CMSTPLUA is an auto-elevated COM class object identified by CLSID {3E5FC7F9-9A51-4367-9063-A120244FBEC7}. It lives in the registry at HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UAC\COMAutoApprovalList with a value of 1, meaning Windows will grant it elevation without a UAC prompt.
The COM class exposes an interface called ICMLuaUtil (IID: {6EDD6D74-C007-4E75-B76A-E5740995E24C}), implemented in C:\Windows\System32\cmlua.dll. This interface contains a ShellExec method that executes a file of our choosing — at high integrity — once we have a valid interface pointer.
The technique: inject a DLL into explorer.exe, call CoGetObject() with the elevation moniker to get an elevated ICMLuaUtil*, then call ICMLuaUtil::ShellExec() to launch our payload as Administrator.
Part 1 — The Payload DLL
The DLL that gets injected into explorer.exe contains the COM activation logic. Two critical pieces before writing any code: the CLSID/IID constants, and the virtual function table stub.
The vftable stub matters because ICMLuaUtil inherits from IUnknown (which provides QueryInterface, AddRef, Release — handled automatically). The remaining methods must be declared in order from the interface definition. ShellExec is the 7th method. You cannot skip to it — all preceding stubs must be present as pure virtual placeholders.
Part 2 — CoGetObject with the Elevation Moniker
With the interface defined, the injector() function builds the elevation moniker string, converts the CLSID and IID strings to GUIDs, configures binding options specifying a local server COM context, and calls CoGetObject(). If the call succeeds and we're running from inside explorer.exe, we get back an elevated ICMLuaUtil* pointer ready to call ShellExec.
Part 3 — DllMain Wrapper and the Injector EXE
The DLL's DllMain spawns a thread on DLL_PROCESS_ATTACH to call injector(). Spawning a thread rather than calling directly from DllMain avoids loader lock deadlocks. A separate injector EXE finds explorer.exe by process name, allocates memory in it, writes the DLL path, and launches a remote thread calling LoadLibraryW.
github.com/g3tsyst3m/CodefromBlog under the 2024-10-16 folder.
-
COM virtual function tables are ordered structures. The C++ compiler lays out vtable entries in declaration order. When you call
spLuaUtil->ShellExec(), the compiler emits code that reads the 7th pointer from the vtable. If your stub is out of order, you'll call the wrong function — or crash.How to determine the order: open
cmlua.dllin Binary Ninja or IDA. Navigate to the vtable for ICMLuaUtil. Count the function pointers from the start of the table.AddRef,Release, andQueryInterface(inherited from IUnknown) occupy slots 0–2 in the base class vtable — these are handled automatically byIUnknowninheritance. Your struct's virtual methods start at slot 3 (Method1 in the stub).ShellExecis at slot 9 in the full vtable, which is slot 7 in your struct (after the 3 inherited IUnknown methods).💡 OleView shows the method names. Binary Ninja shows their addresses in the vtable. Together, you can verify the exact order before writing the stub. -
BIND_OPTS3is the binding options structure passed toCoGetObject(). ThedwClassContextfield specifies where the COM server should run. For elevated COM objects,CLSCTX_LOCAL_SERVERis required — it tells COM to use an out-of-process local server (a separate elevated surrogate process), which is how the elevation boundary is crossed.Using
CLSCTX_INPROC_SERVERhere would attempt to load the COM class DLL in-process — which can't cross the elevation boundary. The elevation moniker only works with local server context.cbStructmust be set tosizeof(BIND_OPTS3)— the API uses this to determine the struct versionZeroMemorythe struct first to avoid garbage in reserved fields- The rest of BIND_OPTS3 fields can be zero for this use case
-
The subscriber version of this technique skips DLL injection entirely. Instead, it modifies the calling process's PEB (Process Environment Block) to make it appear as if the process is explorer.exe — satisfying COM's trusted context check without actually being inside explorer.
Key PEB fields to modify:
PEB.ProcessParameters.ImagePathName— set to explorer.exe's full pathPEB.ProcessParameters.CommandLine— set to match explorer.exe's command line
After masquerading, the same CoGetObject elevation moniker call works from the standalone process. The technique is cleaner OPSEC-wise — no remote thread, no WriteProcessMemory, no cross-process handles. Available as C++, Rust, and C# versions at the ko-fi link in the course header.
⚠ PEB masquerading affects the entire process for the duration of the masquerade. Restore the original PEB values after the COM call to avoid issues with other operations in the same process.
Learning Objectives
- Understand the ms-settings handler registry hijack mechanism
- Apply relative path notation to evade Windows Defender's path-based detection
- Chain a Node.js LOLBin with a JS reverse shell for a clean payload delivery
- Understand why DelegateExecute is required alongside the default value
How ComputerDefaults Registry Hijack Works
ComputerDefaults.exe is a Microsoft-signed auto-elevating binary in C:\Windows\System32. When executed, it attempts to open the ms-settings: URI handler. Windows resolves URI handlers via the registry — specifically at HKCU\software\classes\ms-settings\shell\open\command. Because this key is in HKCU (not HKLM), any standard user can write to it without elevation.
The technique: write our payload path to the (default) value of that registry key, set a DelegateExecute value to signal command execution, and then launch ComputerDefaults.exe. It auto-elevates, reads our registry key, and executes our payload as Administrator.
The Defender Evasion Problem — and the Elegant Solution
Windows Defender aggressively monitors the (default) registry value under the ms-settings handler. It flags specific patterns:
- ✗
-Value "cmd"— flagged immediately - ✗
-Value "C:\Users\Public\payload.exe"— flagged (Public is a known payload location) - ✗
-Value "C:\Temp\payload.exe"— flagged (Temp is monitored) - ✓
-Value "../../myfolder/payload.exe"— clean!
The relative path resolves from C:\Windows\System32 (where ComputerDefaults.exe lives) — so ../../myfolder/ traverses up to the drive root, then into your custom folder. Defender doesn't flag it because it doesn't match known absolute payload paths. Think smarter, not harder.
The Payload Chain — C++ Loader + Node.js LOLBin + JS Reverse Shell
The payload registered in the registry value (barney.exe) is a minimal C++ wrapper that launches Node.js with the reverse shell script. Node.js is a trusted LOLBin — a legitimate portable binary that Defender generally trusts — making it ideal for running the actual shell code.
nodejs.org, extract node.exe, rename it to something innocuous (n0de.exe, runtime.exe, etc.), and drop it alongside your JS payload. No install required. Defender generally trusts Node.js binaries.
-
The
DelegateExecuteregistry value tells Windows Shell to use the(default)value as a command to execute rather than as a COM class identifier. Without it, Windows may try to interpret the default value as a ProgID or CLSID and fail silently.The value itself is empty — what matters is its presence. Setting it to an empty string via
-Value ""is sufficient. The registry key structure that triggers command execution:HKCU\software\classes\ms-settings\shell\open\command\(Default)= your payload pathHKCU\software\classes\ms-settings\shell\open\command\DelegateExecute= "" (empty)
💡 This same pattern (HKCU handler + DelegateExecute) is used by other classic bypasses like fodhelper.exe and eventvwr.exe. The mechanism is consistent — only the triggering binary changes. -
When
ComputerDefaults.exeexecutes the value from the registry, its working directory isC:\Windows\System32. Relative path traversal works from there:C:\Windows\System32— starting point../— up toC:\Windows../../— up toC:\(drive root)../../myfolder/barney.exe— resolves toC:\myfolder\barney.exe
Create your payload folder at the drive root (or one level deep) to keep the path short. Deep folder nesting still works but the relative path gets longer. Avoid folder names matching known malware paths.
⚠ Don't useC:\Users\Public,C:\Temp,C:\Windows\Temp, orC:\ProgramDataas your payload folder — these are all in Defender's watchlist. Create a new, innocuous-looking folder name at the root. -
The ms-settings URI handler hijack works against any auto-elevating binary that opens an ms-settings URI. Several well-known ones:
fodhelper.exe— one of the earliest documented targets (2017), heavily signaturedcomputerdefaults.exe— still works as of 2025, less signatured than fodhelpereventvwr.exe— uses a different registry key path but same mechanismsdclt.exe— backup/restore tool, reads from HKCU
When one gets patched or signatured, look for other auto-elevating binaries that read from HKCU paths via ProcMon with a
NAME NOT FOUND+HKCUfilter. The pattern is consistent — only the binary and the specific key path change.
Learning Objectives
- Understand the MSDT diagnostic tool auto-elevation chain through the XML manifest
- Identify BluetoothDiagnosticUtil.dll as a phantom DLL loaded by sdiagnhost.exe
- Plant the payload DLL in a user-controlled PATH directory for interception
- Compile a 32-bit (x86) payload DLL — why architecture matters here
The Technique — One Command
This entire bypass triggers with a single command:
How It Works — The Auto-Elevation Chain
msdt.exe (Microsoft Support Diagnostic Tool) is auto-elevated when launched with specific XML diagnostic packages. The BluetoothDiagnostic.xml package triggers this auto-elevation. After msdt.exe elevates, it spawns C:\WINDOWS\SysWOW64\sdiagnhost.exe to run the actual diagnostic.
Here's the critical detail: sdiagnhost.exe is a 32-bit process (SysWOW64 directory), and it attempts to load BluetoothDiagnosticUtil.dll. This DLL doesn't exist in System32 or SysWOW64 — making it a phantom DLL. Windows resolves it by walking the DLL search path, which includes directories listed in the user's %PATH% environment variable.
If we place our own BluetoothDiagnosticUtil.dll in any directory that appears in the user's PATH before the system directories, sdiagnhost.exe loads it — at high integrity, as part of the auto-elevated diagnostic chain.
The Payload DLL — Must Be x86/32-bit
Because sdiagnhost.exe is a 32-bit process running under SysWOW64, the payload DLL must be compiled as x86 (32-bit). An x64 DLL loaded into a 32-bit process will fail with ERROR_BAD_EXE_FORMAT. In Visual Studio, set the platform to x86 before building.
Deployment — Adding Your Folder to User PATH
The DLL must be in a directory listed in the user's %PATH% environment variable. Add a folder to the user PATH (not system PATH — no elevation needed) and place the DLL there:
blog.sevagas.com/?MSDT-DLL-Hijack-UAC-bypass. The course adaptation focuses on modern Windows compatibility and Defender evasion testing.
-
msdt.exeis not universally auto-elevated — it depends on the diagnostic package XML being loaded. TheBluetoothDiagnostic.xmlpackage includes an auto-elevation directive that tells Windows to elevate the process for this specific diagnostic. Without the correct XML, msdt.exe would prompt for UAC consent normally.The XML file lives at
C:\WINDOWS\diagnostics\index\BluetoothDiagnostic.xmland is a legitimate Windows component. We're not modifying it — just using it as the trigger to activate the auto-elevation code path in msdt.exe.💡 Read Emeric Nasi's original research for a deep dive into how he identified the specific XML + DLL combination. The methodology (ProcMon + auto-elevated binaries) is transferable to finding new targets. -
The user
%PATH%variable (stored inHKCU\Environment\PATH) is checked during DLL resolution for processes running in the user's session — including auto-elevated processes that originate from user actions. Modifying it requires no elevation.System PATH (
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\Path) requires admin rights to modify and affects all users. For this bypass, user PATH is sufficient and leaves a smaller footprint.- User PATH changes take effect immediately for new processes — no logoff required
- Cleanup: remove the folder from PATH and delete the DLL after the payload executes
- The path entry is visible in
sysdm.cpl→ Environment Variables → User variables — basic forensic artifact to clean up
-
If you compile
BluetoothDiagnosticUtil.dllas x64, the injection fails silently.sdiagnhost.exeis a 32-bit process — it callsLoadLibraryon your DLL, the loader tries to map it into a 32-bit address space, detects the PE architecture mismatch, and returnsERROR_BAD_EXE_FORMAT(193). The process continues normally with no visible error and your code never runs.In Visual Studio: right-click project → Properties → Configuration Manager → change Active Solution Platform from
x64tox86orWin32. Verify the output:dumpbin /headers BluetoothDiagnosticUtil.dll | findstr machineshould show14C machine (x86).⚠ This architecture constraint is a common trap when adapting DLL hijack payloads. Always verify the target process architecture before compiling your DLL — check with Process Hacker orGet-Process | Where-Object {$_.Name -eq "sdiagnhost"} | Select-Object *.
Learning Objectives
- Understand how the SilentCleanup scheduled task bypasses UAC via auto-elevation
- Use ProcMon to identify a phantom DLL loaded by dismhost.exe
- Understand the DLL sideloading technique and how to prevent DLL crashes via export proxying
- Apply the methodology to discover new DLL sideloading targets
The SilentCleanup Technique
The SilentCleanup scheduled task runs cleanmgr.exe which spawns dismhost.exe — both auto-elevated as part of the disk cleanup operation. When dismhost.exe runs, it attempts to load api-ms-win-core-kernel32-legacy-l1.dll. This DLL doesn't exist in the standard search path — it's a phantom DLL. By placing our payload DLL with that name in %TEMP% (which is in the DLL search path), dismhost.exe loads it at high integrity.
Trigger the scheduled task with: Start-ScheduledTask -TaskName SilentCleanup — no elevation required to start it.
-
Even though SilentCleanup is patched, the discovery methodology is evergreen:
- Open ProcMon → Add filter:
Process Name contains dismhost(or your target binary) - Add filter:
Result is NAME NOT FOUND - Add filter:
Path ends with .dll - Run the auto-elevated binary or trigger the scheduled task
- Look for DLL loads from
%TEMP%,%PATH%directories, or the application directory - For each candidate, verify the directory is user-writable
This pattern was used to find SilentCleanup's target originally, and it will find the next one. Scheduled tasks that auto-elevate are particularly rich targets because they're designed to run elevated without user interaction.
💡 Check all scheduled tasks that run as SYSTEM or elevated:Get-ScheduledTask | Where-Object {$_.Principal.RunLevel -eq "Highest"} | Select-Object TaskName, TaskPath - Open ProcMon → Add filter:
-
When a process loads a DLL, it may immediately call exported functions from it. If your payload DLL doesn't export those functions, the host process crashes — making the bypass visible and potentially incomplete if your payload needs time to execute.
Export proxy stubs solve this: your DLL exports functions with the same names as the legitimate DLL's exports, but each stub simply returns
S_OKor a benign value. The host process gets valid-looking responses and continues normally while your payload executes in the background.Automation: tools like
SharpDllProxyor manual.deffiles can generate the stub exports from the original DLL's export table. For phantom DLLs (that don't exist), you need to identify the expected exports from documentation or disassembly of the calling binary.💡 For the SilentCleanup target specifically, the blog post mentions implementing export stubs to preventdismhost.exefrom crashing. This is good practice for any DLL sideload payload.
Learning Objectives
- Understand how appinfo.dll verifies a binary's path before granting auto-elevation
- Explain the mock trusted directory technique — creating a folder that passes path verification
- Understand why the trailing space trick defeated the original path check
- Recognize why this was patched and what the fix looked like
The Concept — Path Verification Spoofing
Auto-elevation requires the binary to reside in a trusted directory. appinfo.dll checks whether the binary's path starts with a trusted prefix (e.g., C:\Windows\System32\). The mock trusted directory technique exploited a quirk in how this path check was performed: a folder named C:\Windows \System32\ (with a trailing space after "Windows") passed the string prefix check because the comparison didn't normalize the path first.
By creating C:\Windows \System32\, copying an auto-elevating binary (like wusa.exe) there, and placing a malicious DLL alongside it, an attacker could trigger auto-elevation from an untrusted directory.
-
Windows NTFS allows trailing spaces and periods in directory names at the API level, even though Explorer and most UI tools strip them. The folder
C:\Windows \(note the space) is a distinct path fromC:\Windows\and can be created withCreateDirectoryusing the\\?\extended path prefix to bypass Win32 path normalization.Creation:
cmd /c mkdir "\\?\C:\Windows \"— the\\?\prefix bypasses the Win32 subsystem's path normalization, allowing the trailing space.These folders are notoriously difficult to delete via Explorer — you need to use the same
\\?\prefix:cmd /c rmdir /s /q "\\?\C:\Windows \"⚠ Always clean up mock trusted directories after use. LeavingC:\Windows \on a system is an obvious forensic indicator and a persistent artifact. -
Microsoft's fix was straightforward: normalize the path before comparison in
appinfo.dll. The updated code callsGetLongPathNameor equivalent normalization APIs to resolve the actual canonical path before checking whether it starts with a trusted prefix. A trailing space directory resolves to a different canonical path thanC:\Windows\System32\, failing the check.The lesson: the vulnerability existed because the check was a simple string prefix comparison without normalization. It's a reminder that path-based security checks need to handle Unicode normalization, junction points, symbolic links, and extended path prefixes correctly — each of these can create apparent matches that don't correspond to the actual filesystem location.
Learning Objectives
- Map each bypass technique to its specific event log and Sysmon signatures
- Understand why the CMSTPLUA technique is harder to detect than registry-based bypasses
- Write detection logic targeting the common behavioral patterns across all techniques
- Apply the single most effective mitigation: UAC set to Always Notify
Detection by Technique
-
The CMSTPLUA DLL injection technique produces a distinctive artifact chain:
- Sysmon Event 8 (CreateRemoteThread) — source process creating a thread in explorer.exe. Filter:
SourceImage != known software updaters AND TargetImage = *explorer.exe - Sysmon Event 7 (ImageLoaded) — unexpected DLL loaded into explorer.exe from a non-standard path (AppData, Public, Temp)
- Sysmon Event 10 (ProcessAccess) — process opening explorer.exe with
PROCESS_ALL_ACCESS - Security Event 4688 — cmd.exe spawned as high-integrity with parent explorer.exe when no user interaction occurred
💡 Elastic ES|QL:FROM logs-* | WHERE event.category == "process" AND process.name == "cmd.exe" AND process.parent.name == "explorer.exe" AND process.token.integrity_level == "high" AND NOT user.name == "SYSTEM" - Sysmon Event 8 (CreateRemoteThread) — source process creating a thread in explorer.exe. Filter:
-
Registry-based bypasses are highly detectable via registry monitoring:
- Sysmon Event 12/13 (Registry create/set) — creation of
HKCU\Software\Classes\ms-settings\shell\open\commandfollowed by value set - Sysmon Event 1 — ComputerDefaults.exe spawning an unexpected child process
- Security Event 4688 — elevated child process with ComputerDefaults.exe as creator
Elastic/Sysmon correlation rule: registry write to
*\ms-settings\shell\open\command*within 30 seconds ofComputerDefaults.exeexecution = high-confidence UAC bypass attempt.Also applies to the same pattern targeting
fodhelper.exe,eventvwr.exe, andsdclt.exe.⚠ TheDelegateExecutevalue creation is particularly high-signal — legitimate software rarely writes to this specific key path. - Sysmon Event 12/13 (Registry create/set) — creation of
-
- Sysmon Event 7 — sdiagnhost.exe loading a DLL from a user-controlled PATH directory (not System32 or SysWOW64)
- Sysmon Event 1 — cmd.exe spawned by sdiagnhost.exe (unexpected parent)
- User environment PATH modification — adding a new directory to HKCU\Environment\PATH followed shortly by msdt.exe execution is a high-confidence signal
💡 Sysmon Event 7 is high-volume. Filter specifically for:Image = *sdiagnhost.exe AND ImageLoaded != *SysWOW64* AND ImageLoaded != *System32* -
Setting UAC to Always Notify eliminates every technique in this course. Every bypass depends on the auto-elevation mechanism — remove auto-elevation and the attack surface disappears entirely.
Configure via GPO:
Computer Configuration → Windows Settings → Security Settings → Local Policies → Security Options → User Account Control: Behavior of the elevation prompt for administrators in Admin Approval Mode→ set to Prompt for credentials on the secure desktop.Why it's rarely deployed despite being effective:
- Administrators performing frequent privileged operations face constant prompts
- Some enterprise software breaks under Always Notify (poorly written installers expecting silent elevation)
- Organizations deploy Privileged Access Workstations (PAWs) instead — dedicated admin machines running Always Notify that admins use only for privileged tasks
For individual machines (developer laptops, analyst workstations) where the user is technically sophisticated, Always Notify is a reasonable and highly effective hardening measure that costs very little in practice.
-
Defense-in-depth beyond the UAC setting:
- Audit HKCU\Software\Classes writes — the COM hijack and registry bypass families all write to HKCU. Enable SACL auditing on this key and alert on writes to CLSID paths and ms-settings handler paths.
- Monitor user PATH modifications — writes to
HKCU\Environment\PATHare rare in normal usage. Alert on changes, especially those adding directories at the drive root. - Sysmon with SwiftOnSecurity config — covers CreateRemoteThread, ImageLoaded (filtered), and registry events out of the box.
- Windows Defender ASR rules — the "Block process injections" ASR rule catches some DLL injection variants. Test in audit mode before enforcing.
- Restrict PROCESS_ALL_ACCESS on explorer.exe — advanced: use a kernel callback or EDR rule to alert when non-system processes open explorer.exe with
PROCESS_ALL_ACCESS.
github.com/g3tsyst3m/CodefromBlog.