Detecting Process Injection: Identifying DLL Injection, Hollowing and Reflective Loading Artifacts
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:
-
DLL Injection: The attacker writes a DLL path into the target process's memory and calls
LoadLibraryviaCreateRemoteThread. 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. -
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.
-
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 withPAGE_EXECUTE_READWRITEorPAGE_EXECUTE_READprotection.- A
MZorPEheader 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 toPAGE_EXECUTE_READWRITEto 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
- 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.exeas a child ofsvchost.exe, orpowershell.exeas a child ofword.exe), processes with misspelled names, or duplicate instances of single-instance processes. - 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 with4D 5A(MZ). False positives include JIT-compiled code from .NET CLR and Java JVM. - For each process of interest, run ldrmodules:
vol.py -f memdump.raw --profile=<profile> ldrmodules --pid=<PID>. Entries whereInLoad=FalseandMappedPathis empty indicate a memory-resident DLL not loaded through the PEB — a strong reflective injection indicator. - Run vadinfo for the suspicious process:
vol.py -f memdump.raw --profile=<profile> vadinfo --pid=<PID>. Filter for entries withPAGE_EXECUTE_READWRITEprotection and typeVadS(private). Private executable-writable regions without a file path are injection candidates. - Dump VAD regions of interest with vaddump and inspect the first 64 bytes of each:
hexdump -C region.dmp | head -4. AMZheader followed by a valid PE structure (offset 0x3C points to thePEsignature) confirms a PE image. - 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 withsha256sum. - 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. - 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_READWRITEprotection 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
--wow64or 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
netscanoutput with the PID of the injected process — asvchost.exewith 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.