Learning Objectives
- Explain how Windows makes access control decisions at the kernel level
- Describe the role of access tokens, integrity levels, and SIDs in authorization
- Understand Mandatory Integrity Control and why it exists as a defense layer
- Use Python tooling to inspect token state on a live system
Access Tokens & the Privilege Hierarchy
Every process in Windows runs in the context of an access token — a kernel object that encapsulates the security identity of the thread or process. When a user logs in, the Local Security Authority Subsystem Service (LSASS) builds a token containing the user's SID, group SIDs, and privilege set. This token is then inherited by every process spawned in that session.
The privilege hierarchy flows from SYSTEM at the top, through high-integrity administrative processes, down to medium-integrity standard user processes, and finally to low-integrity sandboxed contexts like browser tabs and downloaded executables. Your job as a privilege escalation practitioner is to find the paths that allow movement between these levels.
-
A primary token is assigned to a process at creation and represents its full security context. An impersonation token is a temporary token a thread adopts to act on behalf of another user — commonly used by services and RPC servers.
From an attacker's perspective: if you can steal or duplicate an impersonation token with
SecurityImpersonationorSecurityDelegationlevel, you can callImpersonateLoggedOnUser()orSetThreadToken()to run code as that user.💡 UseGetTokenInformation(TokenType)to distinguish primary (1) vs impersonation (2) tokens at runtime. -
Privileges are per-token rights that gate specific system operations. They exist in three states: present, enabled, or enabled by default. Many dangerous privileges are present but disabled at medium integrity — elevation or bypass is needed to use them.
SeImpersonatePrivilege— impersonate any authenticated user (potato attacks)SeDebugPrivilege— open any process including LSASSSeAssignPrimaryTokenPrivilege— assign a primary token to a new processSeTakeOwnershipPrivilege— take ownership of any objectSeLoadDriverPrivilege— load arbitrary kernel drivers
⚠ Even 'disabled' dangerous privileges can be re-enabled withAdjustTokenPrivileges()if the privilege is present in the token. -
Each interactive logon creates a unique session. Processes spawned within a session inherit the parent's token by default. Session 0 is reserved for services and runs in strict isolation from interactive user sessions since Windows Vista.
Token inheritance matters for privilege escalation because spawning a child process with
CreateProcess()clones the parent token. If you can inject into or manipulate a parent process before it spawns children, you may influence the inherited token.💡CreateProcessWithTokenW()andCreateProcessAsUserW()allow explicitly specifying a different token for the new process — core to most token-based privesc techniques. -
Restricted tokens are created via
CreateRestrictedToken()and are a subset of a normal token — privileges and SIDs can only be removed, never added. They're used by sandboxes (Chrome, IE protected mode, AppContainers) to limit what a compromised process can do.As a pentester, restricted tokens are relevant when you're trying to escape a sandboxed context. The key insight: a restricted token cannot access objects that its restricting SIDs don't have access to, even if the base token would allow it.
- Restricting SIDs add an extra DENY layer on top of normal access checks
SANDBOX_INERTflag — disables AppLocker and Software Restriction Policies in the child
How Windows Makes Authorization Decisions
When a process attempts to access a securable object — a file, registry key, process, thread, or named pipe — the Security Reference Monitor (SRM) performs an access check. It compares the SIDs in the calling thread's token against the Discretionary Access Control List (DACL) on the target object. Each ACE in the DACL either grants or denies specific access rights to a particular SID.
Understanding this flow is essential because most privilege escalation techniques are fundamentally about either modifying a DACL, impersonating a higher-privileged token, or placing attacker-controlled content somewhere that a higher-privileged process will execute it.
Mandatory Integrity Control (MIC)
Introduced in Windows Vista, MIC adds an additional layer on top of DACL-based access control. Every securable object and every process is assigned an integrity level — System, High, Medium, Low, or Untrusted. The No-Write-Up policy prevents a lower integrity process from writing to a higher integrity object, even if the DACL would otherwise permit it.
This is the primary mechanism that UAC bypass techniques are designed to circumvent. When you understand why MIC exists, the attack surface becomes obvious: the system must have auto-elevation pathways for legitimate use cases, and those pathways are the target.
SIDs, DACLs, and ACEs — The Building Blocks
A Security Identifier (SID) is a unique value that identifies a user, group, or computer account. Well-known SIDs like S-1-5-18 (SYSTEM) and S-1-5-32-544 (Administrators) appear constantly during enumeration and are worth memorizing. Every DACL is composed of Access Control Entries (ACEs), each specifying a SID and an access mask.
-
Security Identifiers (SIDs) appear constantly during enumeration. Memorizing the most common well-known SIDs saves time and prevents misidentification during a live engagement.
S-1-5-18— NT AUTHORITY\SYSTEM (local system account)S-1-5-19— NT AUTHORITY\LOCAL SERVICES-1-5-20— NT AUTHORITY\NETWORK SERVICES-1-5-32-544— BUILTIN\AdministratorsS-1-5-32-545— BUILTIN\UsersS-1-16-8192— Medium Integrity LevelS-1-16-12288— High Integrity LevelS-1-16-16384— System Integrity Level
-
icaclsis the command-line tool for reading and modifying DACLs on files and directories. For registry keys, useGet-Aclin PowerShell orregwith appropriate flags.Key
icaclspermission flags to recognize during enumeration:(F)— Full control (attacker can replace the binary)(W)— Write (can modify content or append)(M)— Modify (write + delete, often enough)(RX)— Read & execute only (not exploitable via write)
💡 PowerShell:(Get-Acl 'HKLM:\SYSTEM\CurrentControlSet\Services\vuln').Access— check IdentityReference and FileSystemRights on each ACE. -
Misconfigured DACLs are one of the most reliable escalation vectors in mature environments where patch levels are high. The key is finding objects that privileged processes interact with but that non-admin users can write to.
Priority targets during DACL enumeration:
- Service binary paths — can we overwrite the executable?
- Service image path registry keys — can we change where the service binary points?
- DLL directories in the service binary's path — can we plant a DLL?
- Scheduled task action executables — same logic as services
💡 Tool:accesschk.exe -uwcqv "Authenticated Users" *— Sysinternals AccessChk finds services writable by the current user. -
The DACL (Discretionary ACL) controls who can access an object — it's the authorization layer. The SACL (System ACL) controls what access attempts get logged to the Security event log — it's the audit layer. SACLs require
SeSecurityPrivilegeto read or write.Operational significance: a target object might have a SACL that logs all access attempts. Modifying a service registry key or replacing a DLL without checking the SACL first can generate detectable audit events even if the DACL allows the write.
⚠ During an authorized engagement, always check whether sensitive registry keys and directories have SACLs before touching them. Unnecessary audit events complicate report writing and may trigger SOC alerts.
Learning Objectives
- Systematically enumerate a Windows host for privilege escalation vectors
- Identify weak service permissions, unquoted paths, and registry misconfigurations
- Locate stored credentials across common locations
- Build and run a modular Python enumeration script
Enumeration Methodology
Effective privilege escalation starts with disciplined enumeration before any exploitation attempt. Rushing to a known technique without understanding the target environment leads to noise, failed attempts, and missed vectors. The goal of the enumeration phase is to build a complete picture of the attack surface and identify the highest-confidence path to escalation.
Enumeration falls into three categories: system configuration (OS version, patches, installed software), permission misconfigurations (services, files, registry), and credential exposure (cached credentials, configuration files, browser stores).
Service Misconfigurations
Windows services running as SYSTEM or a privileged account are prime escalation targets when their configuration is improperly secured. Three categories of service misconfigurations matter most: weak binary permissions (attacker can overwrite the executable), weak service permissions (attacker can modify the service config to point to a different binary), and unquoted service paths (attacker can plant an executable at a path that Windows will resolve first).
-
When a Windows service's binary path contains spaces and is not enclosed in quotes, Windows resolves it using an ambiguous path traversal. For a path like
C:\Program Files\My App\service.exe, Windows tries these in order:C:\Program.exeC:\Program Files\My.exeC:\Program Files\My App\service.exe
If an attacker can write to
C:\Program Files\, they can plantMy.exeand have it run as the service account (often SYSTEM) on next service start or system reboot.💡 Find candidates:wmic service get name,pathname,startmode | findstr /i auto | findstr /iv "\""— no quotes = potential target. -
The service's DACL (accessible via
sc sdshow <servicename>) controls who can modify the service configuration. If non-admin users haveSERVICE_CHANGE_CONFIGorSERVICE_ALL_ACCESS, they can point the service binary at an attacker-controlled executable.SDDL string to look for in
sc sdshowoutput:(A;;RPWP;;;WD)— this grants RP (start) and WP (write properties) to World (everyone). Any non-trivial write right to a privileged service is exploitable.💡 Change config:sc config <svc> binpath= "C:\\temp\\shell.exe"thensc start <svc>. -
Even if the service DACL is locked down, if the service binary itself or its parent directory has weak permissions, the file can be overwritten directly. The service then executes the attacker's binary on next start.
Check the binary:
icacls "C:\path\to\service.exe"— look for(M),(W), or(F)forBUILTIN\UsersorNT AUTHORITY\Authenticated Users.⚠ Always back up the original binary before overwriting on authorized engagements. The service will be broken until restored. -
When both
HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer\AlwaysInstallElevatedand the correspondingHKCUkey are set to1, any user can install MSI packages with SYSTEM privileges.Exploitation is trivial: generate a malicious MSI with
msfvenom -p windows/exec CMD=cmd.exe -f msi > evil.msiand run it withmsiexec /quiet /qn /i evil.msi. The installer runs as SYSTEM.💡 This is one of the first checks any automated tool runs — it's rare in modern environments but appears in legacy enterprise builds and dev machines. -
Scheduled tasks run under a configured user context. If the action executable or its directory is writable by the current user, replacing it achieves execution under the task's configured account (often SYSTEM or a privileged service account).
Enumerate tasks:
schtasks /query /fo LIST /v | findstr /i "task name\|run as user\|task to run". Then check the binary path permissions withicacls.💡 Also checkHKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasksfor registry-based task definitions that may expose the action path directly. -
Programs placed in startup folders or registered as autoruns execute under the logged-on user's context — useful for persistence but typically not direct privilege escalation unless the startup location itself is writable and a higher-privileged user logs in.
Key autorun locations:
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunHKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\RunC:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUpC:\Users\<user>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
Credential Hunting
Credentials stored or cached on the local system represent some of the highest-value findings during a privilege escalation assessment. Windows provides several locations where credentials accumulate: the SAM hive (local account hashes), DPAPI-protected credential blobs, Windows Credential Manager entries, and configuration files left by administrators or developers.
-
The SAM hive stores local account NTLM password hashes. It's encrypted using a key derived from the SYSTEM hive. Both are locked by the OS while running, but can be extracted using Volume Shadow Copies or registry export with SYSTEM privileges.
With SYSTEM access:
reg save HKLM\SAM sam.bakandreg save HKLM\SYSTEM system.bak. Then usesecretsdump.pyormimikatzoffline to extract the hashes.⚠ VSS-based extraction:vssadmin create shadow /for=C:then copy the hive from the shadow copy path — bypasses the file lock without needing direct SAM access. -
DPAPI (Data Protection API) encrypts secrets on behalf of users and processes. The encryption key chain flows: secret → DPAPI blob → master key → user password (or domain backup key). Master keys live in
%APPDATA%\Microsoft\Protect\{SID}\.DPAPI blobs are used by: Chrome/Edge saved passwords, Windows Credential Manager, RDP credentials, WiFi passwords, and certificate private keys. If you have the user's password or SYSTEM access, you can decrypt all of them.
💡mimikatz:dpapi::masterkey /in:<path> /password:<user_pass>thendpapi::cred /in:<blob> /masterkey:<hex> -
Windows Credential Manager stores saved credentials for network shares, RDP sessions, and web logins. Enumerate with
cmdkey /list— this shows what's stored but not the plaintext. If you have SYSTEM or the user's context, you can extract them.Types of stored credentials:
- Windows credentials — domain/server credentials (NTLM hashes extractable)
- Certificate-based credentials — PKI auth materials
- Generic credentials — application-specific (often plaintext-recoverable via DPAPI)
💡 RunAs with saved credentials:runas /savecred /user:DOMAIN\admin cmd.exe— if an admin has saved credentials, a lower-priv user may be able to use them. -
These files are low-hanging fruit that appear frequently in real engagements — especially in environments where admins work interactively on servers.
- PSReadLine history:
%APPDATA%\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt— admins frequently type credentials directly into PS commands - .rdp files: May contain saved usernames; passwords are DPAPI-encrypted but recoverable
- unattend.xml / sysprep.xml: Deployment config files that sometimes contain plaintext or base64-encoded local admin passwords
- web.config / app.config: Application configs with database connection strings
⚠ Always grep recursively:findstr /si password *.xml *.txt *.config *.ini— catches custom credential storage patterns. - PSReadLine history:
Learning Objectives
- Understand token impersonation at the Windows API level
- Exploit SeImpersonatePrivilege in realistic scenarios
- Implement token duplication and impersonation in C++
- Recognize named pipe impersonation and its detection signatures
Token Impersonation Fundamentals
Token impersonation allows a process or thread to temporarily adopt the security context of another user. Windows provides this mechanism for legitimate use cases — a server process needs to act on behalf of a client, for example. The impersonation level determines how far the impersonation can propagate: Identification allows inspection but no resource access, Impersonation allows local resource access, and Delegation allows resource access across the network.
From an attacker's perspective, the goal is to obtain a SYSTEM-level impersonation token and use it to either spawn a new process or perform privileged actions directly. The SeImpersonatePrivilege right is the critical enabler — if a process holds it, token impersonation attacks become viable.
SeImpersonatePrivilege and Potato-Family Attacks
Service accounts (IIS AppPool, mssql, network service) typically hold SeImpersonatePrivilege by design. Potato-family attacks exploit the way Windows authenticates COM activation requests — by coercing the SYSTEM account to authenticate to an attacker-controlled endpoint and capturing its token in the process. JuicyPotato, RoguePotato, and GodPotato represent successive iterations of this technique as Microsoft patched each variant.
-
COM activation goes through the COM Service Control Manager (RPCSS). When a client requests a COM object via
CoCreateInstance(), RPCSS authenticates the request using NTLM. Potato attacks force SYSTEM to authenticate to an attacker-controlled endpoint in this flow, capturing a SYSTEM-level token in the process.The coercion works because certain COM activations are triggered as SYSTEM internally — the attacker doesn't need to coerce a user, they set up a fake endpoint and wait for the OS to connect to it as part of normal COM infrastructure operation.
-
JuicyPotato requires a CLSID that can be activated from the target service account's context. Not all CLSIDs work on all OS versions. The original JuicyPotato GitHub lists hundreds of tested CLSIDs by OS version.
If a CLSID fails, rotate through alternatives for the target OS. Common reliable ones for Windows Server 2019:
{F7FD3FD6-9994-452D-8DA7-9A8FD87AEEF4}. For Windows 10 1903+, use RoguePotato or GodPotato instead.⚠ JuicyPotato does not work on Windows 10 1809+ or Server 2019+ due to DCOM authentication hardening. Use RoguePotato or GodPotato for modern targets. -
RoguePotato adapts the potato technique to work post-JuicyPotato patch by using a fake OXID resolver (a component in the DCOM infrastructure) rather than directly abusing RPCSS. It spins up a local fake OXID resolver on a configurable port and redirects DCOM activation through it.
Usage requires a redirector on the attacking machine (or port forwarding) to relay traffic from port 135 to the fake OXID port on the target. Works on Windows Server 2019 and Windows 10 20H2.
💡 RoguePotato is more infrastructure-intensive than GodPotato. Prefer GodPotato on modern targets unless you have specific reasons to use Rogue. -
GodPotato is the current state-of-the-art potato variant, working against Windows Server 2012-2022 and Windows 8-11. It abuses the
ImpersonateNamedPipeClient()flow via a new COM activation coercion path that wasn't patched with previous potato mitigations.It requires
SeImpersonatePrivilege(standard for IIS, MSSQL, WCF service accounts) and produces a SYSTEM-level token reliably without external infrastructure.💡 Basic usage:GodPotato.exe -cmd "cmd /c whoami > C:\\temp\\proof.txt". EDR detection is high — obfuscate or use the technique manually via the documented API calls for stealth. -
PrintSpoofer abuses the Windows Print Spooler service to coerce SYSTEM authentication to a named pipe the attacker controls, then calls
ImpersonateNamedPipeClient()to capture the SYSTEM token. It does not use COM/DCOM and therefore evades some potato-specific signatures.Works on: Windows 10 (all versions), Server 2016/2019. Requires
SeImpersonatePrivilegeorSeAssignPrimaryTokenPrivilege.⚠ PrintSpoofer may not work if the Print Spooler service is disabled — increasingly common in hardened environments since PrintNightmare. Verify:sc query spooler. -
Quick reference for technique selection based on target OS:
- Server 2008 / Win 7: RottenPotato or JuicyPotato
- Server 2012 R2 / Win 8.1: JuicyPotato (most CLSIDs work)
- Server 2016 / Win 10 pre-1809: JuicyPotato or PrintSpoofer
- Server 2019 / Win 10 1809+: RoguePotato, GodPotato, or PrintSpoofer
- Server 2022 / Win 11: GodPotato (most reliable)
💡 Always checkwinverorsysteminfo | findstr /B /C:"OS Name" /C:"OS Version"first.
Named Pipe Impersonation
Named pipe impersonation is a lower-noise alternative to potato attacks when the environment is heavily monitored. An attacker creates a named pipe server and convinces a higher-privileged process to connect as a client. Once connected, the server calls ImpersonateNamedPipeClient() to assume the client's security context. This technique is particularly effective when you have code execution in a service account context and can trigger a privileged process to touch your pipe.
| Technique | MITRE ID | Sub-technique |
|---|---|---|
| Access Token Manipulation | T1134 | Token Impersonation/Theft (.001) |
| Create Process with Token | T1134 | Make and Impersonate Token (.003) |
| Named Pipe Impersonation | T1134 | Named Pipe (.003 variant) |
Learning Objectives
- Explain the UAC auto-elevation mechanism and its attack surface
- Implement and understand COM object hijacking for UAC bypass
- Understand fodhelper and CMSTPLUA bypass techniques at the API level
- Identify detection signatures each technique produces
What UAC Actually Does (and Doesn't)
User Account Control is a consent and credential prompting mechanism — it is not a security boundary in the same sense as a kernel exploit mitigation. Microsoft's own documentation acknowledges this. UAC's job is to prevent accidental privilege use and to make privilege escalation visible to the user. A determined local attacker with code execution can almost always bypass it.
The attack surface exists because Windows must support auto-elevation for a subset of trusted, Microsoft-signed binaries. These binaries are allowed to elevate without a UAC prompt because they are trusted to know what they're doing. The bypass techniques in this module all involve abusing the context in which those auto-elevating binaries execute.
The Auto-Elevation Mechanism
When a process marked as autoElevate in its manifest runs from a trusted directory (typically System32 or Program Files), Windows elevates it without prompting. The Application Information service (appinfo.dll) is responsible for this decision. It checks the binary's manifest, verifies its signature, confirms the path, and if all checks pass, creates a high-integrity process without user interaction.
The bypass opportunities arise in the gaps: auto-elevating binaries that load COM objects from HKCU (which is writable at medium integrity), binaries that resolve DLLs through a path the attacker controls, and binaries that execute child processes whose command line the attacker can influence via environment variables or registry values.
-
The
autoElevateelement in an application manifest signals to Windows that the binary should be elevated without a UAC prompt when run by an administrator. This flag is only honored for executables signed by Microsoft and located in trusted directories (System32,Program Files, etc.).You can inspect any binary's manifest with:
sigcheck.exe -m C:\Windows\System32\fodhelper.exe(Sysinternals) or extract it withmt.exe. Look for<autoElevate>true</autoElevate>in the output.💡 All auto-elevating binaries are attack surface. Each one that reads from HKCU or loads DLLs from user-writable paths is a potential bypass. -
The Application Information service (
appinfo.dll, running insvchost.exe) handles UAC elevation requests. Its decision flow for auto-elevation:- Is the calling user a local admin? (token contains Administrators SID)
- Is the executable signed by Microsoft?
- Is the executable in a trusted directory?
- Does the manifest declare
autoElevate?
All four must be true. The bypass strategies all attack what happens after the process is spawned at high integrity — specifically, what DLLs it loads and what registry keys it reads in its elevated context.
-
TrustedInstaller is a separate service account (
NT SERVICE\TrustedInstaller) above SYSTEM in the Windows object hierarchy. It owns core system files and registry keys that even SYSTEM cannot modify. Auto-elevation elevates to Administrator level (high integrity), not TrustedInstaller.Reaching TrustedInstaller requires token manipulation techniques beyond auto-elevation — typically by enabling
SeDebugPrivilegefrom a SYSTEM context and duplicating the TrustedInstaller service's token.⚠ Most privesc chains stop at SYSTEM. TrustedInstaller is only needed for patching protected system files — rarely required in penetration test scenarios. -
appinfo.dll verifies the binary's location is in a trusted directory using a path normalization check. The check can be bypassed by creating a junction or directory structure that makes an untrusted path appear trusted — but this is generally mitigated on modern Windows.
The more common failure point is what the verified binary does after it's elevated: if it reads
HKCUfor COM CLSIDs, resolves DLL paths using user-controlled environment variables, or writes to paths the attacker controls, the path verification becomes irrelevant.💡 Process Monitor with admin rights shows every registry and file access a binary makes at high integrity. Filter onNAME NOT FOUND+HKCUpath to find hijackable reads.
fodhelper UAC Bypass
fodhelper.exe is a Windows binary (Optional Features manager) that auto-elevates and reads from HKCU\Software\Classes\ms-settings\ before executing. Since HKCU is writable at medium integrity, an attacker can plant a shell command in that registry path and have it executed at high integrity when fodhelper reads it. This technique has been in the wild since 2017 and is flagged by most EDR products — its value today is primarily educational for understanding the COM hijack pattern.
COM Object Hijacking for UAC Bypass
The more generalizable pattern is COM object hijacking. Many auto-elevating binaries instantiate COM objects using CoCreateInstance. Windows resolves COM CLSIDs by checking HKCU\Software\Classes\CLSID first, before the machine-wide HKLM. If the attacker registers a malicious COM server under the target CLSID in HKCU, the auto-elevating binary will load the attacker's DLL at high integrity.
-
When a process calls
CoCreateInstance(CLSID), Windows resolves the CLSID to a server executable or DLL using this lookup order:HKCU\Software\Classes\CLSID\{guid}(user hive — writable at medium integrity)HKCR\CLSID\{guid}(merged view of HKCU + HKLM)HKLM\SOFTWARE\Classes\CLSID\{guid}(machine hive — requires admin)
Since HKCU is checked first and is writable without elevation, any auto-elevating binary that calls
CoCreateInstance()is potentially vulnerable — the elevated process will load the CLSID definition the attacker planted in HKCU. -
The methodology for finding UAC bypass targets with Process Monitor:
- Run Process Monitor as admin, then trigger the auto-elevating binary
- Filter:
Process Name is fodhelper.exe(or target binary) - Filter:
Result is NAME NOT FOUND - Filter:
Path begins with HKCU
Any HKCU registry key the elevated binary tries to read and doesn't find is a potential hijack point — plant a CLSID registration there and it will be loaded at high integrity.
💡 Save the ProcMon filter as a .pmc file for reuse across different target binaries. -
InprocServer32specifies a DLL to load in-process into the calling process.LocalServer32specifies an EXE to launch as an out-of-process COM server. For UAC bypass,InprocServer32is preferred because:- The DLL loads into the elevated process's address space directly
- The payload runs at the elevated process's integrity level
- No separate process is spawned (lower detection surface)
Planting a
LocalServer32launches a new process which may not inherit the full elevated context and is more visible in process telemetry. -
CMSTPLUA (
{3E5FC7F9-9A51-4367-9063-A120244FBEC7}) is a well-known UAC bypass target. Thecmstp.exebinary (auto-elevating) instantiates this COM object. By hijacking the CLSID in HKCU, a payload DLL is loaded at high integrity.CMSTPLUA is older and heavily signatured by modern EDR. Its value is primarily educational — understanding the pattern lets you apply the same methodology to find novel, unsignatured targets via ProcMon analysis on a target system.
⚠ CMSTPLUA bypass is detected by Windows Defender with default signatures. Use only in lab environments or develop novel targets via the ProcMon methodology above.
Detection Signatures
-
Security Event 4688 logs process creation with creator process info when process creation auditing is enabled. A UAC bypass via auto-elevation produces a distinctive chain:
medium integrity process → fodhelper.exe → high integrity child process.Key fields to correlate:
Creator Process Name(should be an expected parent for the auto-elevating binary) andToken Elevation Type(%%1937= full token / elevated,%%1938= limited). An elevated child spawned from an unexpected parent is high signal.💡 Enable:auditpol /set /subcategory:"Process Creation" /success:enable -
Sysmon Event ID 13 (RegistryEvent — Value Set) captures registry writes when configured to monitor HKCU paths. A COM hijack for UAC bypass always involves writing to
HKCU\Software\Classes\CLSID\{guid}\InprocServer32.Detection query (Elastic):
registry.path: *\\Software\\Classes\\CLSID\\*\\InprocServer32 AND registry.data.strings: *.dll— correlate with subsequent auto-elevating binary execution within a short time window. -
Sysmon Event ID 7 (ImageLoaded) logs every DLL load when enabled (high volume — filter carefully). A UAC bypass loads a payload DLL from a user-writable path (temp, AppData, etc.) into an elevated process.
Detection:
Image: *\\fodhelper.exe AND ImageLoaded: *\\AppData\\*— an auto-elevating system binary should never load DLLs from user AppData directories.⚠ Sysmon Event 7 generates enormous volume. Use targeted filtering and baseline known-good DLL loads before enabling broadly. -
Windows Defender has behavioral detections for common UAC bypass patterns including fodhelper, eventvwr, sdclt, and CMSTPLUA-based techniques. The detection fires on the behavioral sequence (registry write + auto-elevating binary + elevated child process) rather than signatures.
Evasion approaches (for authorized red team use): novel auto-elevating binary targets identified via ProcMon (no existing Defender signature), in-memory COM registration without touching the registry, or tampering with the elevation chain at the API level via hooking.
Learning Objectives
- Understand DLL search order and where hijacking opportunities arise
- Implement a functional DLL hijack payload in C++
- Identify phantom DLL hijacking opportunities in common software
- Recognize registry permission abuse vectors
DLL Search Order Hijacking
Windows resolves DLL names to paths using a predictable search order: the application directory first, then System32, then the Windows directory, then the current working directory, and finally directories listed in the PATH. If an attacker controls any directory earlier in this order than where a legitimate DLL lives, they can plant a malicious DLL that gets loaded instead.
Phantom DLL hijacking targets DLLs that Windows or applications attempt to load but that don't exist on the system — the load fails silently, but if an attacker plants a DLL at a location in the search path, it gets loaded. Process Monitor with a NAME NOT FOUND filter on .dll extensions is the standard tool for finding these opportunities.
-
With
SafeDllSearchModeenabled (default since Windows XP SP2), the DLL search order is:- The application's own directory
- The system directory (
C:\Windows\System32) - The 16-bit system directory (
C:\Windows\System) - The Windows directory (
C:\Windows) - The current working directory
- Directories in the
PATHenvironment variable
Without SafeDllSearchMode (legacy apps), the current working directory moves to position 2 — a much larger attack surface. Check for this flag:
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SafeDllSearchMode. -
The KnownDLLs registry key (
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs) lists DLLs that Windows loads from a protected cache rather than the search path. These DLLs cannot be hijacked via standard search order manipulation.Common KnownDLLs:
ntdll.dll,kernel32.dll,kernelbase.dll,advapi32.dll,user32.dll,gdi32.dll. Any DLL not on this list is potentially hijackable if it appears in a process's import table and isn't in System32.💡 Enumerate the full list:reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs" -
Process Monitor is the definitive tool for DLL hijack discovery. The workflow:
- Open ProcMon as admin → Add filter:
Result is NAME NOT FOUND - Add filter:
Path ends with .dll - Add filter:
Process Name is target.exe - Run the target application and observe failed DLL loads
- For each failed load, check if the search path directory is user-writable
A failed load from a writable directory (AppData, temp, or a misconfigured PATH entry) is a confirmed hijack opportunity.
💡 Automation:procmon /quiet /minimized /backingfile out.pmlthen parse the PML file programmatically with the ProcMon SDK or a Python parser. - Open ProcMon as admin → Add filter:
-
If any directory in the system
PATHis writable by non-admin users, all processes that fail to load a DLL from earlier positions will attempt to load from the attacker-controlled directory. This is particularly impactful because it affects any process, not just a specific target.Check:
for %A in ("%path:;="" "%") do icacls "%~A" 2>nul | findstr /i "(M) (W) (F) :users :everyone :authenticated"⚠ Enterprise environments with poorly-maintained application installs are frequent offenders — custom PATH entries added by 3rd party software installers sometimes point to directories in Program Files that weren't locked down properly. -
Phantom DLLs are DLLs that applications attempt to load but that don't exist on the system. The load silently fails, but if an attacker plants a DLL at a searched location, it gets loaded. These are particularly valuable because they don't require displacing a legitimate DLL.
Discovery: run ProcMon on a test system → filter for
NAME NOT FOUND+.dllextension → for each candidate, verify the DLL doesn't exist anywhere on the system → check if any search path location is writable.Common phantom DLL targets appear in older software, legacy APIs, and print/fax subsystems.
-
C:\Windows\Tempis writable by all authenticated users by default (SYSTEM and admins write here frequently). If any privileged process fails to load a DLL and searchesC:\Windows\Tempin its path resolution, a DLL planted there will be loaded at that process's privilege level.This is rare on properly maintained systems but appears in:
- Misconfigured installer frameworks that temporarily add Temp to PATH
- Poorly-written services that set CWD to Temp
- Installation scripts that run privileged operations from Temp with a writable working directory
💡 Monitor during software installs: ProcMon during an installer run frequently reveals temporary PATH manipulation and DLL loads from writable locations.
Registry Permission Abuse
Service image paths, autorun values, and COM registrations stored in the registry become escalation vectors when their ACLs allow write access to non-administrative users. This is less common on modern, properly configured systems but appears frequently in legacy enterprise environments and on machines where software has been installed carelessly.
Learning Objectives
- Execute a complete privilege escalation chain from medium integrity to SYSTEM
- Chain enumeration output directly into exploit selection
- Document the chain in a format suitable for a pentest report
- Understand artifact cleanup and operational security
Simulated Engagement Scenario
Starting condition: code execution as a standard domain user, member of local Administrators, running at medium integrity on a Windows 10 22H2 target. No EDR present but Windows Defender is active. Goal: SYSTEM shell.
Step 1 — Enumeration
Run local_enum.py from Module 2. Key findings to look for: AlwaysInstallElevated (both hives), unquoted service paths with writable directories, and any credential files. Also run token_inspector.py to confirm current integrity level and identify which dangerous privileges are present or disabled.
Step 2 — UAC Bypass to High Integrity
If the user is a local admin but running at medium integrity, apply the COM hijack technique from Module 4 to spawn a high-integrity process. Confirm the elevated context by re-running token_inspector.py and verifying integrity level is now High.
Step 3 — Token Abuse to SYSTEM
From the high-integrity context, apply token_dup.cpp from Module 3 to duplicate the SYSTEM token from winlogon.exe and spawn a cmd.exe as SYSTEM. Alternatively, if SeImpersonatePrivilege is available, apply the appropriate potato variant for the target OS version.
Step 4 — Cleanup and Reporting
Remove all planted registry keys, delete any temporary DLL payloads, and restore any modified service configurations. Document each step with timestamped screenshots or command output. A well-documented privilege escalation finding in a report includes: starting context, technique applied, root cause, evidence, and remediation recommendation.
-
After a privilege escalation chain involving registry modifications (especially COM hijacks and service path changes), a clean engagement leaves no persistence artifacts. Work through this checklist before declaring the host clean:
- Remove all HKCU\Software\Classes\CLSID entries planted during the engagement
- Restore any service
ImagePathvalues that were modified - Delete AlwaysInstallElevated keys if you set them for testing
- Remove any Run/RunOnce autorun entries added for testing
- Verify with
reg querythat keys are actually gone (not just logically deleted)
⚠ On real engagements, document every registry change before making it and restore immediately after proving the finding. Never leave modified service configs that could crash the host after your session ends. -
After a privilege escalation chain, review what was logged before handing back the system. Key sources to check:
- Security log 4688: Every process we spawned is logged with parent/child relationship
- Security log 4698/4702: Scheduled task creation/modification if we touched tasks
- System log 7045: New service installation if we registered a service
- Sysmon 1, 7, 11, 12, 13: Process, DLL, file, and registry events
On authorized engagements, you generally do NOT clear logs — document what was generated and include it in the report as evidence of detection opportunities.
-
A privilege escalation finding in a pentest report needs five components:
- Title: Concise, specific (e.g., 'UAC Bypass via COM Object Hijacking — fodhelper.exe')
- Severity: CVSS score or qualitative rating with justification
- Description: What the vulnerability is and why it exists
- Evidence: Screenshots, command output, or log excerpts proving exploitation
- Recommendation: Specific, actionable remediation steps
💡 Avoid vague recommendations like 'fix permissions'. Write specific steps: 'Remove WRITE_DAC from Authenticated Users on C:\Program Files\VulnApp\ and set icacls inheritance from the parent directory.' -
Most enterprise clients expect MITRE ATT&CK mapping in pentest deliverables. For a complete foothold → SYSTEM chain, the ATT&CK coverage looks like:
T1082— System Information Discovery (enumeration phase)T1083— File and Directory Discovery (binary/path enumeration)T1548.002— Abuse Elevation Control Mechanism: Bypass User Account ControlT1134.001— Access Token Manipulation: Token Impersonation/TheftT1574.001— Hijack Execution Flow: DLL Search Order HijackingT1012— Query RegistryT1112— Modify Registry
💡 Use the MITRE ATT&CK Navigator (attack.mitre.org/matrices/enterprise/) to generate a visual heatmap of techniques used — clients love including it in their board-level summaries.
Learning Objectives
- Map each module's techniques to event log signatures
- Write detection logic for the techniques covered in this course
- Understand what a SOC analyst sees when these attacks occur
- Build Sysmon and Elastic detection rules for the covered techniques
ETW and Event Log Coverage by Technique
| Technique | Event Source | Event ID | Key Indicator |
|---|---|---|---|
| Token Duplication | Security | 4688 | Creator PID ≠ expected parent; integrity level mismatch |
| UAC COM Hijack | Sysmon | 13 | HKCU\Software\Classes\CLSID write followed by auto-elevate binary launch |
| DLL Hijack | Sysmon | 7 | DLL loaded from non-standard or user-writable path |
| fodhelper Bypass | Sysmon | 1 | fodhelper.exe spawning unexpected child process |
| AlwaysInstallElevated | Security / AppLocker | 4697 | MSI install as SYSTEM from user context |
| Unquoted Service Path | System | 7045 / 7000 | Service binary path contains spaces, binary in unexpected location |
Elastic ES|QL Detection Rules
The following detection logic targets the UAC COM hijack pattern specifically — the highest-signal technique from this course given its registry footprint.
Defensive Recommendations by Technique
-
Sysmon (System Monitor) from Sysinternals provides granular telemetry that Windows event logs don't capture by default. The SwiftOnSecurity config (
github.com/SwiftOnSecurity/sysmon-config) is a well-maintained, production-ready baseline that covers the most important events without generating unmanageable volume.Key event IDs enabled by the baseline: 1 (process create), 3 (network connect), 7 (DLL load — filtered), 10 (process access), 11 (file create), 12/13 (registry events), 22 (DNS query).
💡 Deploy via GPO: push sysmon64.exe and the config XML to all endpoints, install withsysmon64.exe -accepteula -i sysmonconfig.xml. Updates are non-disruptive:sysmon64.exe -c newconfig.xml. -
Enable SACL-based auditing on the HKCU COM registration path to generate Security event 4657 (registry value modified) whenever a COM hijack is planted. Configure via Group Policy:
Computer Configuration → Windows Settings → Security Settings → Advanced Audit Policy → Object Access → Audit Registry.Add a SACL to the key:
HKEY_USERS\*\Software\Classes\CLSID— auditSet ValueforEveryone. The resulting 4657 events combined with a subsequent auto-elevating binary execution make a high-confidence detection. -
Setting UAC to 'Always Notify' (the highest setting) disables the auto-elevation mechanism that all UAC bypass techniques depend on. Every elevation requires an explicit user consent prompt — there is no auto-elevate path for the attacker to abuse.
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 → Prompt for credentials on the secure desktop.⚠ This significantly increases friction for administrators who perform frequent privileged operations. Evaluate user experience impact before deploying broadly — dedicated admin workstations (PAWs) are a better long-term architecture. -
Sysinternals AccessChk automates the process of finding services with misconfigured permissions. Run quarterly as part of a vulnerability management cycle, especially after new software installations.
Key commands:
accesschk.exe -uwcqv "Authenticated Users" *— services writable by any authenticated useraccesschk.exe -uwdq "C:\Program Files"— directories writable by standard usersaccesschk.exe -uwks "Authenticated Users" HKLM\System\CurrentControlSet\Services— registry-level service key permissions
-
Credential Guard uses Hyper-V virtualization to isolate LSASS credential material in a protected virtual trust level (VTL 1) that even SYSTEM cannot access directly. NTLM hashes and Kerberos TGTs are protected from
sekurlsa::logonpasswords-style extraction.Requirements: 64-bit Windows 10/11 Enterprise or Server 2016+, UEFI with Secure Boot, Hyper-V enabled. Enable via GPO:
Computer Configuration → Administrative Templates → System → Device Guard → Turn On Virtualization Based Security.💡 Credential Guard does not protect against golden ticket attacks (if the KDC is compromised) or against attacks that operate via legitimate Kerberos flows. It specifically closes the LSASS dump attack path. -
Any directory in the system PATH that non-admin users can write to is a persistent DLL hijack risk. Run this audit after every software installation that modifies PATH:
for %A in ("%path:;="" "%") do icacls "%~A" 2>nul | findstr /i "(M) (W) (F)"For any finding, correct the permissions:
icacls "C:\vuln\path" /inheritance:r /grant:r "BUILTIN\Administrators:(F)" "NT AUTHORITY\SYSTEM:(F)"— removes inherited permissions and sets explicit admin-only control. -
SeImpersonatePrivilegeis the prerequisite for all potato-family attacks. By default it's granted to LOCAL SERVICE, NETWORK SERVICE, and IIS/MSSQL service accounts. It should never be granted to interactive user accounts or development accounts.Audit who has it:
whoami /privfrom each service account context, or useGet-LocalGroupMemberto inspect group membership that grants the privilege. Remove it from any account that doesn't explicitly need to perform server-side impersonation.💡 If removing the privilege breaks an application, investigate why it needs impersonation — it may indicate a design flaw that should be addressed architecturally rather than just accepted. -
ASR rules are policy-based mitigations that block specific attack behaviors regardless of AV signature state. Relevant rules for the techniques in this course:
75668C1F-...— Block Office applications from creating child processesD4F940AB-...— Block all Office applications from creating child processes92E97FA1-...— Block Win32 API calls from Office macrosBE9BA2D9-...— Block executable content from email client and webmail
Enable via GPO or Intune:
Computer Configuration → Administrative Templates → Windows Components → Microsoft Defender Antivirus → Microsoft Defender Exploit Guard → Attack Surface Reduction.⚠ Always deploy ASR rules in Audit mode first (AuditMode=2) and review the generated events before switching to Block mode — false positives can break legitimate applications.
Concepts, Token Management & Enabling Privileges
Part 1 establishes the conceptual foundation — what drives ElevationStation, how Windows tokens work at a high level, and the setProcessPrivs() routine that every escalation technique in the tool depends on. If this function fails, nothing else works.
ElevationStation was born from wanting to understand how Metasploit's getsystem command actually works under the hood. The research path it opened led through Windows API fundamentals, Process Hacker/System Informer usage, UAC bypass techniques, and — most significantly — the differences between CreateProcessAsUser and CreateProcessWithToken when spawning an elevated shell.
The tool implements two primary escalation paths: stealing a primary token from a SYSTEM-level process, and stealing an impersonation thread token and converting it to a primary token. A third DLL injection path is covered in Part 4.
- Tokens are accessible via the Handles section and Tokens tab in System Informer/Process Hacker
- Every logged-in user's process list contains both primary and impersonation tokens
- A process starts with a fixed set of token privileges — you cannot enable one that isn't listed
LookupPrivilegeValueconverts the string name to a LUID — the OS's internal numeric ID for that privilegeOpenProcessToken(GetCurrentProcess(), ...)— we're adjusting our own process token, not a remote oneAdjustTokenPrivilegescan only enable or disable privileges — it cannot add ones that aren't already in the token- The
ERROR_NOT_ALL_ASSIGNEDcheck is mandatory — the function returns success even when it silently fails to set the privilege
Each process token contains a fixed list of privileges — some enabled, some disabled. AdjustTokenPrivileges can toggle between enabled and disabled, but it cannot add a privilege that isn't in the list. SE_ASSIGNPRIMARYTOKEN_NAME simply doesn't appear in a standard elevated admin token, so there's no way to enable it through normal means.
ElevationStation's solution (Part 3) is to steal the impersonation token from a SYSTEM process — which does have that privilege — assign it to the current thread via SetThreadToken, and then call setThreadPrivs(SE_ASSIGNPRIMARYTOKEN_NAME). Now the thread's context has the privilege available, and it can be enabled successfully.
CreateProcessAsUser— which lets you spawn a shell in the current console — specifically requiresSE_ASSIGNPRIMARYTOKEN_NAMECreateProcessWithTokendoesn't require it, but always opens a new separate window — not useful for an interactive shell- This catch-22 is what makes Part 3 the most complex and most interesting part of the whole series
Primary Token Duplication — Stealing a SYSTEM Shell
Part 2 is the first working escalation technique: open a SYSTEM process, steal its primary token via DuplicateTokenEx, and use CreateProcessWithTokenW to pop a SYSTEM shell. Simple, effective — and limited by one frustrating API constraint.
PROCESS_QUERY_LIMITED_INFORMATION is the lowest-privilege open flag that still allows calling OpenProcessToken() against a process. Requesting PROCESS_ALL_ACCESS or PROCESS_QUERY_INFORMATION when you don't need them increases the risk of failure against hardened targets.
OpenProcessToken uses MAXIMUM_ALLOWED — this requests every right the caller is entitled to given the current token context, which after enabling SeDebugPrivilege is quite broad against SYSTEM processes.
DuplicateTokenEx creates a new, independent token handle that is an exact replica of the source token including all its privileges, SIDs, and integrity level. The caller owns the new handle — it's not a borrowed reference that could be invalidated when the source process exits.
The TokenPrimary flag (vs. TokenImpersonation) determines how the duplicated token can be used:
- TokenPrimary — can be passed to
CreateProcessWithTokenWorCreateProcessAsUserto spawn a new process running under that identity - TokenImpersonation — can be assigned to a thread via
SetThreadTokenfor temporary impersonation, but cannot be used directly with CreateProcess APIs
Part 2 specifies TokenPrimary because the goal is to call CreateProcessWithTokenW. Part 3 first creates an impersonation token for SetThreadToken, then later creates a second primary token from the thread context for CreateProcessAsUser.
This is the design constraint that makes Part 3 necessary. CreateProcessWithTokenW is easy to use — it has forgiving privilege requirements and produces a working SYSTEM shell — but it always opens the new process in a separate window. There is no flag or option to redirect it to the calling console.
For a red team operator running an interactive shell (e.g., through a C2 session), a SYSTEM shell in a separate window is useless — they can't interact with it. The shell they care about is the one inside their current console session.
The solution in Part 3: use CreateProcessAsUser, which does spawn within the current console context — but it requires SE_ASSIGNPRIMARYTOKEN_NAME, which requires stealing a SYSTEM thread token first. That's the entire motivation for the thread token impersonation chain.
CreateProcessWithToken→ forgiving privilege requirements, new separate windowCreateProcessAsUser→ strict privilege requirements, runs in current console ✓
Thread Token Impersonation — SYSTEM Shell in the Current Console
Part 3 is the hardest and most rewarding section — Ernie's personal favorite. Instead of opening a new window, this technique impersonates a SYSTEM thread token, uses it to unlock a privilege our admin token doesn't have, then converts everything into a primary token for CreateProcessAsUser. The result: a SYSTEM shell appearing directly inside the current console.
The critical insight is that setThreadPrivs is called after SetThreadToken — because those privileges only exist in the SYSTEM token, not in our original admin token. The thread must be running under the SYSTEM context before those privileges become available to enable.
This is the trickiest conceptual point in the entire series. The rule is absolute: you cannot enable a privilege that isn't present in the token. SE_ASSIGNPRIMARYTOKEN_NAME is not listed in a standard elevated admin token at all — not disabled, not present in any form.
The sequence:
- Before SetThreadToken: thread runs under process primary token →
SE_ASSIGNPRIMARYTOKEN_NAMEdoesn't exist →setThreadPrivsexits immediately withERROR_NOT_ALL_ASSIGNED - After SetThreadToken(NULL, hNewToken): thread now runs under SYSTEM impersonation token → that privilege does exist in the SYSTEM token →
setThreadPrivsenables it successfully
This is the elegant core of Part 3's approach: borrow the SYSTEM token temporarily just to unlock the privilege, then use it to create a proper primary token for process spawning. The tool's own description says this part of the research "took the longest to figure out" — and it shows why.
AppleMobileDeviceService.exe is a background service installed by iTunes/Apple software that runs as NT AUTHORITY\SYSTEM. ElevationStation targets it because it satisfies every requirement for a good injection/token theft target:
- Always SYSTEM — verified in Process Hacker; it runs as full SYSTEM not LocalService or NetworkService
- 64-bit — matches ElevationStation's architecture, passing the
IsWow64Processcheck - Not PPL-protected — Protected Process Light (PPL) processes resist even
SeDebugPrivilege-level access; this one doesn't - Stable and persistent — long-running, won't exit mid-operation
winlogon.exe is an equally valid alternative available on every Windows machine. The tool's process enumeration logic lets the user specify any PID, so any qualifying SYSTEM process works.
DLL Injection — Local Admin to SYSTEM via Remote Thread
The finale brings DLL injection into the picture as a third escalation path. A reverse shell DLL is injected into a SYSTEM process using VirtualAllocEx, WriteProcessMemory, and CreateRemoteThread. When the DLL loads, its DLL_PROCESS_ATTACH handler fires and calls back to the attacker on a separate port — as SYSTEM. Then the DLL is cleanly unloaded.
Windows ASLR (Address Space Layout Randomization) randomizes the load address of DLLs once at boot time. After that, every process on the system loads kernel32.dll at the same base address for the lifetime of that boot session. This means the virtual address of LoadLibraryW inside kernel32.dll is identical in our process and in the target SYSTEM process.
This is why GetProcAddress(GetModuleHandle("Kernel32"), "LoadLibraryW") gives us an address we can safely pass to CreateRemoteThread as the thread entry point — we looked it up in our process, but it's the same in the target's address space.
- This assumption holds for
kernel32.dllandntdll.dll— they are shared across all processes - It does not hold for modules loaded by only some processes (e.g., ws2_32, user32 if not pre-loaded)
- This is the foundational trick behind almost all classic DLL injection implementations
After the reverse shell DLL fires and the attacker has their SYSTEM callback, leaving the DLL loaded in the target process is a forensic artifact. Process Hacker/System Informer would show an unexpected DLL in AppleMobileDeviceService.exe's module list — an immediate red flag for a defender.
The cleanup uses the same CreateRemoteThread trick, this time with FreeLibrary as the entry point and the DLL's module handle as the argument. The DLL is evicted from the process's memory, and the module list returns to its expected state.
The reverse shell connection in Part 4 originates from AppleMobileDeviceService.exe (or whichever SYSTEM process was targeted), not from ElevationStation.exe. These are two completely separate network connections from two different processes:
- Port 4444 — your initial shell running in ElevationStation.exe's context
- Port 4445 — the SYSTEM shell originating from the injected SYSTEM process
This separation has genuine OPSEC value: network telemetry shows the SYSTEM shell callback coming from a legitimate-looking Windows service, not from ElevationStation.exe. A defender correlating process → network connections may not immediately link the two.
The mig2.dll source hardcodes 127.0.0.1:4445 for lab use. For a real engagement, recompile with your actual attacker IP before use. The compile commands are in the Part 4 source comments:
- x64:
x86_64-w64-mingw32-gcc -o mig2.dll -shared mig2.c -lws2_32 - x86:
i686-w64-mingw32-gcc -o mig2.dll -shared mig2.c -lws2_32
github.com/g3tsyst3m/elevationstation. Study the code, run it in a lab, and then build your own version from scratch. That's when it actually sticks.
Learning Objectives
- Understand what an arbitrary write vulnerability is and why SYSTEM writes to user-accessible paths create exploitable conditions
- Use ProcMon to discover SYSTEM processes writing to ProgramData directories
- Chain Object Manager symlinks and directory junctions to redirect a SYSTEM write to a privileged target
- Leverage the resulting write primitive to plant a DLL in a protected directory and execute it via Task Scheduler
- Understand the responsible disclosure timeline and CVE process behind CVE-2024-50804
Background — What Is an Arbitrary Write?
An arbitrary write vulnerability occurs when a privileged process (running as SYSTEM or Administrator) writes to a file or directory path that a lower-privileged user can influence or redirect. On its own, a SYSTEM process writing to C:\ProgramData is not immediately dangerous — but if a standard user can manipulate what that write targets, the write primitive becomes a powerful escalation tool.
CVE-2024-50804 was discovered in MSI Center Pro 2.1.37.0 — software shipped on MSI-branded laptops — and was responsibly disclosed, acknowledged, and patched by MSI's PSIRT team. The patch shipped in version 2.1.41.0 on November 14, 2024. This module walks through the full discovery-to-exploitation methodology so you understand the technique generically, not just for this specific CVE.
The discovery approach that found this CVE — ProcMon observation of SYSTEM writes to ProgramData — is directly applicable to auditing any software installation on any Windows machine.
github.com/user-attachments/files/21607135/code.zip. MSI Hall of Fame: csr.msi.com/global/product-security-advisories
Step 1 — Discovery with ProcMon
The vulnerability was found the same way most arbitrary write bugs are found: open ProcMon, launch the target application, and watch for a SYSTEM-level process writing to a path where a standard user has influence over the file contents or name.
In this case, C:\Program Files (x86)\MSI\One Dragon Center\MSI.CentralServer.exe — running as SYSTEM — was observed writing to C:\ProgramData\MSI\One Dragon Center\Data\Data\. Specifically, it was performing a two-step file operation:
-
1
Writing data into
Device_DeviceID.dat.bak -
2
Renaming
Device_DeviceID.dat.bak→Device_DeviceID.dat
The ProgramData directory inherits permissive ACLs — standard users can read and write files there by default. This meant the .bak file was a target we could manipulate before the SYSTEM process touched it.
Step 2 — The Symlink/Junction Chain
Windows provides two types of filesystem redirection primitives that standard users can create without elevation: directory junctions (redirect a directory path to another directory) and Object Manager symbolic links (redirect a file path to another file, operating at the kernel object level below Win32). Chaining them together is the core of this technique.
The attack chain has two phases:
Device_DeviceID.dat.bak pointing to an attacker-controlled file in C: emp. When the SYSTEM process renames .bak to .dat, the resulting .dat file inherits the permissive ACLs of the ProgramData\Data\Data directory — giving the standard user the ability to delete it (normally only Administrators could delete this file).
Data folder. Create a directory junction from Data to a target directory of your choosing. Create two Object Manager RPC control symlinks — one mapping the .bak filename to your payload DLL in C: emp, and one mapping the renamed .dat filename to a file inside the privileged target directory. When the SYSTEM process runs again and performs the rename, it writes your payload into the privileged directory.
The Target — EdgeUpdate Directory
The privileged target chosen for this exploit is C:\Program Files (x86)\Microsoft\EdgeUpdate\. This directory is writable only by Administrators under normal conditions — a standard user cannot place files there. By using the arbitrary write primitive to land a custom DLL in this directory, and then triggering the EdgeUpdate scheduled task, we get our DLL loaded as SYSTEM.
The principle generalizes: any directory containing an executable run by a scheduled task at elevated privileges is a viable target. EdgeUpdate is reliable because its scheduled task runs regularly and automatically.
The Exploit Code — Object Manager Symlink Chain
The critical code snippet that creates the two Object Manager RPC control symbolic links is what makes the arbitrary write work. The first symlink redirects the .bak filename to the attacker's payload DLL. The second symlink redirects the renamed .dat filename to the target file path in the privileged directory. When SYSTEM performs the rename, it follows the symlink chain and lands the payload in the protected location.
Step 3 — The Payload DLL and Task Scheduler Execution
With the arbitrary write primitive delivering our DLL into the EdgeUpdate directory, the final step is triggering execution. The EdgeUpdate scheduled task runs automatically at system intervals and loads DLLs from its own directory. A DLL placed there with the right name gets loaded by a SYSTEM process — completing the escalation from standard user to NT AUTHORITY\SYSTEM.
The payload DLL follows the same pattern as Module 5's DLL hijack template: a DllMain that fires on DLL_PROCESS_ATTACH and spawns an elevated shell or calls back to a C2 listener.
The Responsible Disclosure Timeline
This CVE is also a case study in professional vulnerability disclosure. The full timeline from discovery to patch:
-
The exact ProcMon workflow that led to discovering CVE-2024-50804:
- Open ProcMon as administrator and start capture
- Add filter:
User is SYSTEM— only show operations by the SYSTEM account - Add filter:
Operation contains WriteFileOROperation contains SetRenameInformationFile - Add filter:
Path contains ProgramData - Launch your target application (or restart the associated service)
- Watch for SYSTEM writes to paths that include user-writable subdirectories
Key indicators of an exploitable condition:
- SYSTEM writes to a file inside a directory where standard users have write/delete rights
- Two-phase file operations — write to a temp/bak file, then rename — create a race window
- SYSTEM reads a file path that is user-controllable (e.g., reads a config file, then acts on it)
💡 Software installed inC:\ProgramDatais almost always worth investigating — by design, ProgramData is accessible to all users, and developers frequently make permission mistakes there. -
Windows has two symlink layers. Win32 symbolic links (created with
mklink) requireSeCreateSymbolicLinkPrivilege— an admin-level privilege. Standard users cannot create them by default.Object Manager symbolic links operate at the NT kernel object namespace level, below Win32. The
\RPC Control\directory in the NT object namespace is writable by all authenticated users — this is by design, as it's used for legitimate IPC pipe name resolution. Standard users can freely create symlinks there without any special privileges.This asymmetry is the foundation of the technique:
- Create a directory junction from the Data folder to
\RPC Control\— standard user can do this - Create Object Manager symlinks in
\RPC Control\mapping the filenames to target paths — standard user can do this - When SYSTEM resolves the path through the junction, it follows into
\RPC Control\and then follows the symlinks to the target paths - The end result: SYSTEM writes to a path of the attacker's choosing
⚠ This technique requires precise timing — the symlinks must be in place before the SYSTEM process performs the rename. In practice the MSI service runs on a predictable schedule, so timing is reliable. - Create a directory junction from the Data folder to
-
A directory junction (also called a reparse point or mount point) redirects all filesystem operations targeting a directory path to a different directory. Unlike file symlinks, junctions can be created by standard users without elevation using
mklink /JorCreateSymbolicLinkwith theSYMBOLIC_LINK_FLAG_DIRECTORYflag.In CVE-2024-50804, the junction serves as the first hop in the redirection chain:
- Original path:
C:\ProgramData\MSI\One Dragon Center\Data\Data\ - Junction target:
\RPC Control\(the NT object namespace directory) - When SYSTEM accesses
Data\Device_DeviceID.dat.bak, the junction redirects it to\RPC Control\Device_DeviceID.dat.bak - The Object Manager symlink there then redirects it to the actual payload path
This two-hop redirection (junction → object manager symlink) is the canonical technique for converting a "SYSTEM writes to user-accessible directory" condition into a full arbitrary write to any path on the system.
💡 Tools like symboliclink-testing-tools by James Forshaw (Google Project Zero) provide pre-built utilities for creating these junctions and Object Manager symlinks, making PoC development faster. The full PoC for CVE-2024-50804 builds on these primitives. - Original path:
-
This CVE is a textbook example of professional responsible disclosure. The full timeline:
- September 6, 2024 — Initial contact to MSI PSIRT at
psirt@msi.comvia their responsible disclosure policy page - September 15, 2024 — MSI confirms receipt of the report and begins review
- September 26, 2024 — MSI PSIRT requests additional technical details
- September 27, 2024 — Two video walkthroughs of the exploit submitted to PSIRT team
- October 4, 2024 — MSI confirms the vulnerability and implements a fix preventing the
.bakfile from being manipulated during the rename operation - November 14, 2024 — Patched version 2.1.41.0 released to the Microsoft Store
- November 15, 2024 — MSI PSIRT confirms patch release, adds discoverer to MSI Hall of Fame
- November 19, 2024 — Public blog post and CVE publication
Total time from first contact to patch: 69 days. This is considered a reasonable timeline for a vendor patch cycle — PSIRT teams typically target 90 days.
💡 The fix: MSI patched by preventing the.bakfile from being manipulated during the rename operation — likely by holding a file lock on the.bakfile during the entire write-and-rename sequence, eliminating the race window that made the symlink substitution possible. - September 6, 2024 — Initial contact to MSI PSIRT at
-
CVE-2024-50804 is an instance of a well-documented vulnerability class. The general conditions required for an exploitable arbitrary write:
- A privileged process (SYSTEM or Admin-level service) performs a write or rename operation
- The file or directory path involved is user-controllable — either the path itself is in a user-writable location, or a component of the path (directory name, filename) can be replaced with a symlink/junction
- There is a window of time between path resolution and the write operation where a standard user can substitute the target
MITRE ATT&CK:
T1574.010— Services File Permissions Weakness. Related:T1574.009— Path Interception by Unquoted Path.Tools that automate hunting for this class of vulnerability:
- WinPrivCheck — scans for writable service paths and DLL hijack opportunities
- ProcMon (manual, as described above) — the most reliable discovery method
- AccessChk (Sysinternals) —
accesschk -uwdq "C:\ProgramData"to find user-writable subdirectories under ProgramData