DLL search-order hijack discovery

← recon index · docs/index

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 .exe whose manifest carries autoElevate=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

SymbolDescription
ScanAll(opts...) ([]Opportunity, error)Aggregate all four scanners
ScanServices, ScanProcesses, ScanScheduledTasks, ScanAutoElevateIndividual scanners
Rank(opps) []OpportunityScore by integrity gain + autoElevate
Validate(opp, canary, opts) (*ValidationResult, error)Drop canary, trigger, observe
SearchOrder(exeDir) []stringDLL search-order resolution
HijackPath(exeDir, dllName) (hijackDir, resolvedDir string)First writable dir < first legitimate dir
IsAutoElevate(peBytes) boolManifest 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

ArtefactWhere defenders look
Write to service directory by non-installer processEDR file-write telemetry — high-fidelity
New DLL in %PROGRAMFILES%\… written by user-context processDefender ASR rule
DLL load from non-System32 path with System32 binary nameEDR module-load rule
AutoElevate exe spawning child from unusual pathDefender for Endpoint MsSense flags
Sysmon Event 7 (image loaded) for unsigned DLL in System32-adjacent pathUniversal high-fidelity

D3FEND counters:

  • D3-EAL — strict allowlisting catches unsigned DLLs.
  • D3-FCA — DLL signature verification.

Hardening for the operator:

  • Drop the hijack DLL with a Microsoft Authenticode signature via pe/cert.Copy.
  • Match VERSIONINFO to the legitimate DLL via pe/masquerade.
  • Validate before deploying — Validate runs the canary in isolation, no implant exposure.
  • Prefer ScanAutoElevate results: UAC bypass is the highest integrity-gain category.

MITRE ATT&CK

T-IDNameSub-coverageD3FEND counter
T1574.001Hijack Execution Flow: DLL Search Order HijackingfullD3-EAL, D3-FCA
T1548.002Abuse Elevation Control Mechanism: Bypass UACpartial — autoElevate hijacksD3-EAL

Limitations

  • Static IAT only by default. Runtime LoadLibrary calls not in the IAT are missed unless ScanProcesses happens to catch them via Toolhelp32.
  • Validate may detonate. Validate actually runs the canary in the target's context — operators must understand the side-effects of triggering the victim.
  • Admin scans. ScanServices enumerates 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