Detecting fileless and in-memory execution¶
Fileless payloads leave no file on disk. Detection relies on script block logging, ETW telemetry, memory scanning, and behavioural indicators rather than file hashes.
Enable script block logging¶
Script block logging captures the decoded content of every PowerShell script block before execution, including content downloaded from the network and decoded from base64. This is the primary control for PowerShell-based fileless attacks.
# enable via registry (applies to current machine)
$sbPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging'
if (-not (Test-Path $sbPath)) { New-Item $sbPath -Force }
Set-ItemProperty $sbPath -Name EnableScriptBlockLogging -Value 1
Set-ItemProperty $sbPath -Name EnableScriptBlockInvocationLogging -Value 1
# verify
Get-ItemProperty $sbPath
Script block logging fires Event ID 4104 in Microsoft-Windows-PowerShell/Operational.
Collect this log centrally: it can be very high volume on busy systems.
# search script block logs for suspicious content
Get-WinEvent -LogName 'Microsoft-Windows-PowerShell/Operational' |
Where-Object { $_.Id -eq 4104 } |
Where-Object {
$_.Message -match 'DownloadString|DownloadData|IEX|Invoke-Expression|AmsiUtils|amsiContext|reflection\.assembly'
} |
Select-Object TimeCreated, @{n='Content';e={$_.Message}} |
Format-List
Detect in-memory .NET assembly loads¶
The CLR ETW provider logs assembly loads including those from byte arrays. Events
appear in Microsoft-Windows-DotNETRuntime ETW session.
# search Windows event logs for anonymous assembly loads
# (assemblies loaded from memory have no file path)
Get-WinEvent -LogName 'Microsoft-Windows-DotNETRuntime/Operational' -ErrorAction SilentlyContinue |
Where-Object { $_.Id -eq 1150 } | # Assembly load event
Where-Object { $_.Message -notmatch 'file:///' } | # no file path
Select-Object TimeCreated, Message
Sysmon Event ID 7 (ImageLoad) logs DLL loads. An executable image loaded from memory without an associated file path is suspicious:
<!-- Sysmon: flag in-memory image loads (no file path) -->
<ImageLoad onmatch="include">
<ImageLoaded condition="contains">FromMemory</ImageLoaded>
</ImageLoad>
Detect WMI event subscription persistence¶
Fileless persistence via WMI subscriptions is detectable in WMI event logs and via active query:
# list all WMI event subscriptions
Get-WMIObject -Namespace root\subscription -Class __EventFilter |
Select-Object Name, Query, QueryLanguage
Get-WMIObject -Namespace root\subscription -Class CommandLineEventConsumer |
Select-Object Name, CommandLineTemplate
Get-WMIObject -Namespace root\subscription -Class __FilterToConsumerBinding |
Select-Object Filter, Consumer
Any subscription with a command line containing PowerShell, cmd.exe, base64, or download functions warrants investigation. Legitimate WMI subscriptions are rare in most environments; document all known-good ones and alert on the rest.
# WMI activity log: Event ID 5859, 5860, 5861 in Microsoft-Windows-WMI-Activity/Operational
Get-WinEvent -LogName 'Microsoft-Windows-WMI-Activity/Operational' |
Where-Object { $_.Id -in @(5859, 5860, 5861) } |
Where-Object { $_.Message -match 'powershell|cmd|encode|download' } |
Select-Object TimeCreated, Message | Format-List
Memory scanning for injected code¶
For live systems where injection is suspected:
# check all running processes for anonymous executable memory regions
$processes = Get-Process
foreach ($proc in $processes) {
try {
$modules = $proc.Modules
# if process has fewer modules than expected, possible injection target
} catch { }
}
# use Get-InjectedThread (PowerShell script by Jared Atkinson)
# detects threads starting in unbacked memory regions
# https://gist.github.com/jaredcatkinson/23905d34537ce4b5b1818c3e6405c1d2
For offline or post-incident analysis, Volatility:
# detect injected code: executable memory not backed by a file on disk
vol -f memory.raw windows.malfind --pid 0 # all processes
vol -f memory.raw windows.malfind --pid TARGET_PID
# look for:
# - VAD regions marked executable with no file path
# - PE headers in anonymous memory (DWORD 0x4d5a = "MZ")
# - High-entropy executable regions (potential encoded payload)
Detect direct syscall usage¶
Direct syscall usage bypasses userland API hooks but still generates kernel-level events. The thread call stack at the time of the syscall does not pass through ntdll, which is detectable:
EDR products with kernel stack inspection flag threads that enter the kernel without the expected ntdll call frames. This is a specialised capability; check whether the deployed EDR includes it.
At the SIEM level, flag processes that perform sensitive operations (VirtualAllocEx, WriteProcessMemory equivalents) without corresponding API calls visible in userland hooks. The absence of the expected hook telemetry alongside kernel-level events is the signal.
Detecting AMSI bypass attempts¶
AMSI bypass attempts are often themselves detectable:
# search script block logs for known AMSI bypass patterns
Get-WinEvent -LogName 'Microsoft-Windows-PowerShell/Operational' |
Where-Object { $_.Id -eq 4104 } |
Where-Object {
$_.Message -match 'amsiUtils|amsiContext|amsiInitFailed|AmsiScanBuffer|amsi\.dll'
} |
Select-Object TimeCreated, @{n='ScriptBlock';e={$_.Message[0..500] -join ''}}
Note: a successful AMSI bypass means subsequent script blocks are not scanned and may not be logged. The bypass attempt itself is logged (before AMSI is disabled); subsequent activity may not be. Treat any AMSI bypass detection as a high-priority incident, not just an informational alert.
Correlating across telemetry sources¶
Fileless attacks rarely leave a single detectable event. Correlation is required:
Script block log showing encoded PowerShell download
DNS log showing query to attacker domain
Network log showing HTTPS connection to cloud storage
WMI activity log showing subscription creation
No file creation events (absence of expected events is itself a signal)
A SIEM correlation rule that fires when a host shows script block logging activity AND network connections to new domains AND WMI subscription changes within a 10-minute window is far more reliable than any single indicator.