EDR bypass at exploitation time

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:

  • NtMapViewOfSection instead of VirtualAllocEx.
  • RtlCreateUserThread instead of CreateRemoteThread.
  • QueueUserAPC for 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.
  • NtMapViewOfSection from non-system process.
  • BYOVD driver-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

  1. Run the exploit under EDR; observe what fires.
  2. For each alert, refactor the exploit (different API, different memory area, different injection).
  3. 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.

References