Browse CTFs New CTF Sign in

Detecting Process Injection: Identifying DLL Injection, Hollowing and Reflective Loading Artifacts

memory_forensics Difficulté 1–5 30 min certifiable

Théorie

Why This Matters

Process injection is the cornerstone technique of advanced Windows malware: by executing code inside a legitimate host process, an attacker inherits that process's security context, network connections, and trust relationship with the OS. The Cobalt Strike beacon, used in a majority of enterprise-level intrusions since 2016, defaults to injecting into svchost.exe. The TrickBot trojan injected into wermgr.exe. PlugX routinely targets explorer.exe. In each case, a memory dump reveals the injected code through a consistent set of structural anomalies: executable memory pages that have no backing file on disk, PE headers in unexpected address ranges, and VAD entries with PAGE_EXECUTE_READWRITE protection. Analysts who can run Volatility's injection-detection plugins and interpret their output can identify these implants in minutes.

Core Concept

Process injection refers to techniques that cause code to execute within the address space of a different process. The three most common variants are:

  1. DLL Injection: The attacker writes a DLL path into the target process's memory and calls LoadLibrary via CreateRemoteThread. The injected DLL is loaded by the Windows loader and appears in the process's module list — but its on-disk path may point to a temporary or non-standard location.

  2. Process Hollowing (RunPE): The attacker creates a legitimate process in suspended state, unmaps its executable image from memory, writes a malicious PE image into the now-empty address space, and resumes execution. The process name appears legitimate but the in-memory PE header does not match the on-disk executable.

  3. Reflective DLL Injection: A self-loading DLL is written into the target process's memory as a raw PE image and executed directly, without calling the Windows loader. It appears as a committed memory region with no associated file-backed VAD entry — a key detection indicator.

VAD (Virtual Address Descriptor) tree anomalies are the primary detection surface. The Windows kernel maintains a red-black tree of VAD nodes for each process; each node describes a committed memory region with its base address, size, protection flags, and (for file-backed regions) the filename. Injected code regions typically appear as:

  • VadS (private, non-file-backed) nodes with PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_READ protection.
  • A MZ or PE header at the start of a private committed region (absent from the module list).
  • Protection mismatch: a memory region that was originally PAGE_EXECUTE_READ (normal for code sections) has been changed to PAGE_EXECUTE_READWRITE to allow post-load patching — indicating reflective loading or hook patching.

Technical Deep-Dive

# Volatility 2: malfind — finds private executable memory with PE headers
vol.py -f memdump.raw --profile=Win7SP1x64 malfind

# malfind output shows: process name, PID, VAD start address, protection flags,
# and a hex/ASCII dump of the first 64 bytes of the region.
# Look for "MZ" (4D 5A) at the start of the hex dump.

# Dump malfind regions for further analysis (Ghidra, PE-bear, etc.)
vol.py -f memdump.raw --profile=Win7SP1x64 malfind --dump-dir=./malfind_out/

# Volatility 2: vadinfo — list all VAD entries for a process
vol.py -f memdump.raw --profile=Win7SP1x64 vadinfo --pid=1234

# Volatility 2: vaddump — dump all VAD regions to files
vol.py -f memdump.raw --profile=Win7SP1x64 vaddump --pid=1234 --dump-dir=./vad_dump/

# Volatility 2: pstree — show process parent-child relationships
vol.py -f memdump.raw --profile=Win7SP1x64 pstree
# Volatility 2: dlllist — detect DLLs loaded from unexpected paths
vol.py -f memdump.raw --profile=Win7SP1x64 dlllist | 
  grep -v -iE "^(C:\\Windows|C:\\Program Files)" | head -40

# Volatility 2: ldrmodules — detect hidden DLLs (not in PEB loader but in VAD)
vol.py -f memdump.raw --profile=Win7SP1x64 ldrmodules --pid=1234

# ldrmodules output columns: InLoad InInit InMem MappedPath
# False = not present in that list; "True False False" = reflective injection indicator
# Post-process malfind output: filter for actual MZ headers
import os, re

MALFIND_DIR = "./malfind_out/"
results = []

for fname in os.listdir(MALFIND_DIR):
    if not fname.endswith(".dmp"):
        continue
    path = os.path.join(MALFIND_DIR, fname)
    with open(path, "rb") as f:
        header = f.read(2)
    if header == b'MZ':
        size = os.path.getsize(path)
        results.append((fname, size))
        print(f"PE header found: {fname}  ({size} bytes)")

print(f"
Total PE-containing malfind regions: {len(results)}")
# Submit to file hash lookup or disassemble with:
# python3 -m pefile <file>   or   load in Ghidra/IDA

Analytical Methodology

  1. Run pstree to review the process hierarchy: vol.py -f memdump.raw --profile=<profile> pstree. Look for processes with unusual parent processes (e.g., cmd.exe as a child of svchost.exe, or powershell.exe as a child of word.exe), processes with misspelled names, or duplicate instances of single-instance processes.
  2. Run malfind against the full dump: vol.py -f memdump.raw --profile=<profile> malfind --dump-dir=./malfind/. Examine each flagged region; specifically look for regions where the hex dump begins with 4D 5A (MZ). False positives include JIT-compiled code from .NET CLR and Java JVM.
  3. For each process of interest, run ldrmodules: vol.py -f memdump.raw --profile=<profile> ldrmodules --pid=<PID>. Entries where InLoad=False and MappedPath is empty indicate a memory-resident DLL not loaded through the PEB — a strong reflective injection indicator.
  4. Run vadinfo for the suspicious process: vol.py -f memdump.raw --profile=<profile> vadinfo --pid=<PID>. Filter for entries with PAGE_EXECUTE_READWRITE protection and type VadS (private). Private executable-writable regions without a file path are injection candidates.
  5. Dump VAD regions of interest with vaddump and inspect the first 64 bytes of each: hexdump -C region.dmp | head -4. A MZ header followed by a valid PE structure (offset 0x3C points to the PE signature) confirms a PE image.
  6. For process hollowing detection, compare the in-memory PE header of the main executable with the on-disk image using procdump and diff: vol.py -f memdump.raw --profile=<profile> procdump --pid=<PID> --dump-dir=./. Hash both and compare with sha256sum.
  7. Submit malfind PE dumps to YARA scanning: yara -r yara_rules/ ./malfind/. Community YARA rules for Cobalt Strike, Metasploit, and common RAT families will match injected beacons.
  8. Document each injection indicator: process name and PID, VAD address range, protection flags, PE header presence, ldrmodules anomalies, YARA hits, and the on-disk vs in-memory hash comparison result.

Common Analytical Errors

  • Treating all malfind output as confirmed injections: malfind produces false positives for JIT-compiled .NET, Java, and browser JIT regions, all of which legitimately use PAGE_EXECUTE_READWRITE. Always verify the MZ header and PE structure before concluding injection occurred.
  • Not using ldrmodules alongside malfind: malfind finds private executable regions; ldrmodules finds DLLs present in VAD but absent from the PEB loader lists. A reflective DLL injection is invisible to malfind if the region does not have PAGE_EXECUTE_READWRITE protection but is visible via ldrmodules.
  • Ignoring false process names: Process hollowing replaces the executable image while the process name (from the PEB) remains legitimate. pstree shows the original process name; procdump reveals the replaced PE. Always hash and verify the in-memory executable against the known-good on-disk image.
  • Missing injection into 32-bit processes on 64-bit systems: WoW64 processes have a separate PEB and module list. Volatility plugins that parse only the 64-bit PEB miss injections into the 32-bit address space. Use --wow64 or appropriate 32-bit profile variants when analysing WoW64 processes.
  • Not correlating with network connections: An injected beacon will maintain network connections from the host process. Cross-reference netscan output with the PID of the injected process — a svchost.exe with an established connection to an unusual external IP is the definitive combined indicator.

NICE Framework Alignment

Code Knowledge/Skill/Task Statement How This Card Develops It
K0017 Knowledge of concepts and practices of processing digital forensic data Understanding VAD structures, PE headers, and PEB loader lists as process injection detection surfaces
K0042 Knowledge of incident response and handling methodologies Applying malfind, ldrmodules, and vadinfo as structured detection steps in malware incident response
K0187 Knowledge of file type abuse by adversaries for data exfiltration Recognising reflective DLL injection and process hollowing as techniques to hide malicious PE files from on-disk detection
S0047 Skill in preserving evidence integrity according to standard operating procedures Dumping and hashing injected PE regions; preserving malfind output as forensic artifacts
T0049 Decrypt seized data using technical means Extracting and reconstructing injected PE images from binary process memory for disassembly and analysis

Further Reading

  • The Art of Memory Forensics Ch. 17: Process Injection — Ligh, Case, Levy, Walters (Wiley)
  • Practical Malware Analysis Ch. 12: Covert Launching — Sikorski, Honig (No Starch Press)
  • Hunting for Process Injection — FireEye / Mandiant (public blog series, 2019)

Challenge Lab

Renforcez votre apprentissage avec un défi généré basé sur la compétence de cette carte.