EDR bypass at exploitation time
TL;DR: EDR hooks userland (NTDLL inline patches, kernel callbacks) and kernel (ETW providers, minifilter drivers). An exploit fires before any malware is on disk — so EDR catches you mid-exploit via behaviour: memory write to executable region, child-process spawn, suspicious thread, IRP pattern. Bypasses at exploit time: (1) syscall directly (no NTDLL hook), (2) write only to memory the EDR doesn’t watch, (3) use existing trusted process for follow-on actions, (4) BYOVD to disable EDR before payload runs. Companion to edr-hooks-and-unhooking and syscall-direct-and-indirect.
What EDR sees during an exploit
Pre-trigger:
- Process spawn / loaded modules (your exploit binary).
- Network connection (your remote attacker).
At trigger:
- Memory allocations with PAGE_EXECUTE_READWRITE.
- WriteProcessMemory / NtWriteVirtualMemory to other processes.
- CreateRemoteThread / NtCreateThreadEx into other processes.
- VirtualProtect of memory to RWX.
Post-trigger (payload):
- Child process spawn (powershell.exe, cmd.exe).
- Token manipulation events.
- LSASS access.
EDR alerts on any of these. The exploit must avoid producing them, OR produce them in trusted-looking ways.
Bypass 1 — syscall directly
Userland EDRs hook NTDLL functions: when your code calls WriteProcessMemory, NTDLL’s NtWriteVirtualMemory jumps to the EDR first. The EDR inspects args, returns to NTDLL.
Bypass: don’t go through NTDLL. Build a syscall stub:
; syscall stub for x64 Windows
mov r10, rcx ; syscall convention: r10 = rcx
mov eax, 0x3A ; syscall number for NtWriteVirtualMemory (varies by build)
syscall
ret
eax = syscall number. The number changes per Windows build; resolve at runtime by reading NTDLL’s exports.
See syscall-direct-and-indirect.
Bypass 2 — indirect syscalls
Direct syscalls bypass userland hooks but defenders can detect by looking at the call site (syscall instruction not inside NTDLL is suspicious). Indirect syscalls jump to NTDLL’s syscall;ret sequence — call site looks legitimate.
SysWhispers3 generates such stubs.
Bypass 3 — write-where-EDR-doesn’t-watch
Some EDR products only hook specific APIs (Heap, VirtualAlloc, etc.). Bypasses use uncommon ones:
NtMapViewOfSectioninstead ofVirtualAllocEx.RtlCreateUserThreadinstead ofCreateRemoteThread.QueueUserAPCfor thread hijack.
The bypass works until the EDR vendor updates rules — arms race.
Bypass 4 — use existing trusted process
Instead of executing in your exploit’s process (which the EDR is watching), inject into a process the EDR considers trusted (explorer.exe, svchost.exe).
Approaches:
- Process hollowing into
notepad.exe. - DLL side-loading via signed application.
- COM hijack to make a system process load your DLL.
EDR catches some of these (CreateRemoteThread); workarounds use uncommon injection paths (module-stomping, atom-bombing, early-bird-apc).
Bypass 5 — disable EDR via BYOVD
Bring a vulnerable signed driver, exploit it to:
- Unload the EDR’s kernel driver.
- Modify kernel data structures the EDR watches.
- Zero out the EDR’s process callbacks.
Tools: EDRSandblast, EDR-Killer, RealBlindingEDR.
Risk: EDR vendors detect BYOVD attempts at hash level; Microsoft maintains a blocklist. New vulnerable drivers get added quickly; check loldrivers.io for current.
Bypass 6 — patch ETW / AMSI in your process
If the EDR uses ETW for telemetry, patch the EtwEventWrite function pointer in your process to a no-op. AMSI patch is the analogous JS / PowerShell control.
1
2
3
4
5
6
// Patch EtwEventWrite to ret 0
PVOID etw = GetProcAddress(GetModuleHandle("ntdll.dll"), "EtwEventWrite");
DWORD oldProt;
VirtualProtect(etw, 1, PAGE_EXECUTE_READWRITE, &oldProt);
*(BYTE*)etw = 0xC3; // ret
VirtualProtect(etw, 1, oldProt, &oldProt);
Same trick as AMSI bypass (amsi-bypass).
Bypass 7 — operate at IRQL where ETW can’t telemetry
Some kernel-mode exploits at DISPATCH_LEVEL or higher operate where ETW providers can’t fire. The exploit’s data-only attack happens, then drops back to PASSIVE_LEVEL for normal return.
Bypass 8 — race the EDR loader
If the EDR injects its hooks into new processes via a kernel callback, but your exploit runs before the callback fires, you have a window. Some EDRs have a startup delay; exploit-quickly-on-process-start avoids the hook.
Sandbox vs EDR conflicts
In a renderer process (Edge / Chrome), the AppContainer sandbox restricts what your exploit can do; the EDR adds further telemetry. Bypass requires:
- Stay inside renderer (sandbox limits your damage).
- Avoid Mojo IPC patterns flagged by EDR.
- Use renderer-internal-only state changes.
The full chain: renderer exploit → no EDR triggers → sandbox escape via known broker bug → EDR sees IPC abuse but only logs it (no kill).
Detection on the defender side
EDR rules to alert on:
- Direct syscall instructions outside NTDLL.
NtMapViewOfSectionfrom non-system process.BYOVDdriver-load events.- ETW provider state changes (some EDRs hook EtwEventWrite themselves).
A rule that fires often produces noise; mature EDRs use multiple signal correlation.
OSEE workflow with EDR on
- Run the exploit under EDR; observe what fires.
- For each alert, refactor the exploit (different API, different memory area, different injection).
- Iterate until clean.
Real exam may include EDR in scope (modern OSEE updates).
Tools
- SysWhispers / SysWhispers3 — direct syscall stub generation.
- EDRSandblast — BYOVD-based EDR disable.
- API Monitor — observe what your exploit triggers.
- ETW Explorer — see ETW providers active.
- HellsGate / Halo’s Gate — dynamic syscall resolution.
OSCP / OSEP / OSWE / OSEE relevance
OSCP: EDR rarely on lab boxes. OSEP: EDR in scope; tradecraft level evasion. OSEE: exploit-level evasion — the techniques above are exam-relevant.