DLL search-order hijack discovery
TL;DR
Discover DLL-search-order hijack opportunities across services,
running processes, scheduled tasks, and autoElevate=true
binaries. ScanAll returns
Opportunity records carrying the writable hijack path + the
legitimate resolved DLL location. Validate
proves the hijack works by dropping a canary; Rank
prioritises by integrity gain.
Primer
A DLL hijack works when an application loads xyz.dll and
Windows resolves the load via the search-order rules — first
the application directory, then System32, then PATH. If the
operator can drop a xyz.dll in a writable directory the
application checks before System32, the operator's code
runs at the next load.
This package finds those opportunities programmatically:
- Services — services running as SYSTEM whose binary path is in a writable directory + missing IAT-imported DLL = root on next service start.
- Processes — live process IATs walked via Toolhelp32; same filter.
- Scheduled tasks — registered tasks parsed via COM ITaskService.
- AutoElevate — System32
.exewhose manifest carriesautoElevate=true(fodhelper, sdclt, eventvwr, …) — these silently elevate without UAC prompt; a hijack here is a textbook UAC bypass.
KnownDLLs (HKLM\…\Session Manager\KnownDLLs) are excluded —
those are early-load-mapped from \KnownDlls\ and bypass the
search order entirely.
How It Works
flowchart LR
subgraph scan [Scanners]
SVC["ScanServices<br>SCM enum + IAT walk"]
PROC["ScanProcesses<br>Toolhelp32 + loaded modules"]
TASK["ScanScheduledTasks<br>COM ITaskService"]
AE["ScanAutoElevate<br>System32 manifest filter"]
end
SVC --> ALL["ScanAll returns Opportunity slice"]
PROC --> ALL
TASK --> ALL
AE --> ALL
ALL --> RANK["Rank<br>integrity-gain score"]
RANK --> VAL["Validate<br>drop canary + trigger"]
VAL --> CONF["ValidationResult<br>confirmed hijack"]
API Reference
| Symbol | Description |
|---|---|
ScanAll(opts...) ([]Opportunity, error) | Aggregate all four scanners |
ScanServices, ScanProcesses, ScanScheduledTasks, ScanAutoElevate | Individual scanners |
Rank(opps) []Opportunity | Score by integrity gain + autoElevate |
Validate(opp, canary, opts) (*ValidationResult, error) | Drop canary, trigger, observe |
SearchOrder(exeDir) []string | DLL search-order resolution |
HijackPath(exeDir, dllName) (hijackDir, resolvedDir string) | First writable dir < first legitimate dir |
IsAutoElevate(peBytes) bool | Manifest probe |
Opportunity carries: Kind, ID, DisplayName, Binary,
MissingDLL, HijackedPath, ResolvedDLL, IntegrityGain,
AutoElevate.
Examples
Simple — list ranked opportunities
import "github.com/oioio-space/maldev/recon/dllhijack"
opps, _ := dllhijack.ScanAll()
for _, o := range dllhijack.Rank(opps)[:5] {
fmt.Printf("%s %s → %s\n", o.Kind, o.DisplayName, o.HijackedPath)
}
Composed — UAC-bypass scan only
ae, _ := dllhijack.ScanAutoElevate()
for _, o := range ae {
fmt.Printf("UAC bypass: drop %s in %s\n", o.MissingDLL, o.HijackedPath)
}
Advanced — validate before deploying
canary, _ := os.ReadFile("canary.dll") // emits a marker file on load
res, err := dllhijack.Validate(opp, canary, dllhijack.ValidateOpts{
TriggerFunc: func() error { /* invoke the victim */ return nil },
Timeout: 30 * time.Second,
})
if err == nil && res.Triggered {
// confirmed; safe to drop the real payload
}
OPSEC & Detection
| Artefact | Where defenders look |
|---|---|
| Write to service directory by non-installer process | EDR file-write telemetry — high-fidelity |
New DLL in %PROGRAMFILES%\… written by user-context process | Defender ASR rule |
| DLL load from non-System32 path with System32 binary name | EDR module-load rule |
| AutoElevate exe spawning child from unusual path | Defender for Endpoint MsSense flags |
| Sysmon Event 7 (image loaded) for unsigned DLL in System32-adjacent path | Universal high-fidelity |
D3FEND counters:
Hardening for the operator:
- Drop the hijack DLL with a Microsoft Authenticode signature
via
pe/cert.Copy. - Match
VERSIONINFOto the legitimate DLL viape/masquerade. - Validate before deploying —
Validateruns the canary in isolation, no implant exposure. - Prefer
ScanAutoElevateresults: UAC bypass is the highest integrity-gain category.
MITRE ATT&CK
| T-ID | Name | Sub-coverage | D3FEND counter |
|---|---|---|---|
| T1574.001 | Hijack Execution Flow: DLL Search Order Hijacking | full | D3-EAL, D3-FCA |
| T1548.002 | Abuse Elevation Control Mechanism: Bypass UAC | partial — autoElevate hijacks | D3-EAL |
Limitations
- Static IAT only by default. Runtime
LoadLibrarycalls not in the IAT are missed unlessScanProcesseshappens to catch them via Toolhelp32. - Validate may detonate.
Validateactually runs the canary in the target's context — operators must understand the side-effects of triggering the victim. - Admin scans.
ScanServicesenumerates SCM-registered services; some entries return ACCESS_DENIED without admin. - AutoElevate fragility. Microsoft has been silently hardening autoElevate binaries — the canonical fodhelper bypass is patched on Win11; verify per build.
See also
pe/dllproxy— pure-Go forwarder DLL emitter; the natural payload generator for the Opportunities discovered here.pe/imports— sibling import-table walker.pe/cert— sign the hijack DLL.pe/masquerade— clone target DLL identity.persistence/service— alternative SYSTEM persistence.- Operator path.
- Detection eng path.