Why Off-the-Shelf Bypasses Die Fast
The second a public exploit drops, defenders patch. The second a common bypass gets documented, EDR signatures light up. If you’re still leaning on pre-baked ROP chains or copy-paste ASLR defeats from Exploit-DB, you’re already burned. Real tradecraft means writing your own bypass logic from scratch—specific to the target binary, its runtime quirks, and the heap manager or kernel subsystem you’re abusing.
I’m not talking about 2007-era stack smashing. Modern Windows 11 and Linux 6.x kernels, CFG, CET, and PAC have raised the bar. But the bar is not uniform. Every mitigation has a blind spot, and every blind spot can be reached if you’re willing to instrument the target deeply enough. This piece walks through the thought process, not just the code.

Mapping the Mitigation Stack
Before you write a single shellcode stub, map out what you’re actually up against. A typical hardened binary on a modern OS throws a stack of obstacles at you:
- ASLR – base randomization, often per-reboot or per-fork.
- DEP/NX – non-executable stack and heap.
- Stack canaries – __stack_chk_fail on buffer overflow.
- SafeSEH/SEHOP – structured exception handler validation.
- Control Flow Guard (CFG) / Indirect Branch Tracking (IBT) – forward-edge protection.
- Shadow stack / Hardware-enforced stack protection – backward-edge integrity.
- PAC (ARM64) / Pointer Authentication – signed pointers.
Each piece expects a specific invariant. Your job is to find the one invariant the target application accidentally violates. Often it’s a legacy module loaded without ASLR, a JIT region with RWX memory, or an exception handler chain that never got recompiled with CET-aware metadata. A single crack in the armor is all you need.
Information Leaks as Primitives
Almost no modern bypass works blind. You need an info leak – a read primitive that discloses a code or data address. Format string bugs in verbose loggers, uninitialized kernel memory returned to userland, and side-channel timer attacks on page faults all serve this purpose. Once you have a single .text pointer, you can compute gadget offsets from the binary’s disk layout, assuming you’ve fetched the exact build.
I usually chain two leaks: one for a module base, and one for a stack or heap cookie. With those two values, you can craft a payload that defeats both ASLR and canaries in one go. The leak doesn’t need to be elegant—a use-after-free that dangles a vtable pointer into a printable string often works. Ugly, but it gets the job done.

Building a Custom ROP/JOP Chain
Once you’ve got code addresses, the next step is stitching gadgets. Automated tools like ROPgadget are fine for initial discovery, but the chains they produce are generic. A custom bypass needs hand-tuned sequences that avoid CET shadow stack violations—meaning no ret-based gadget chaining if CET is enforced.
For CET-hardened targets, you switch to JOP (Jump-Oriented Programming) using indirect jmp dispatchers, or abuse exception-based unwinding where the dispatcher context lives in a controlled CONTEXT record. On ARM64 with PAC, you look for signing gadgets that let you forge a valid pointer to system() or a similar import. The trick is exploiting PAC key reuse across modules—a browser’s JIT process might share the same IA key as a system library, letting you sign a pointer with a gadget from the JIT region. That’s the sort of weird, real-world quirk you bank on.
Heap-Specific Bypass Strategies
Heap mitigations like LFH randomization, guard pages, and allocation canaries are tougher to bypass than stack cookies because the layout is less predictable. My preferred approach is heap feng shui – massaging the allocator into a deterministic state by spraying objects of controlled size, freeing them in a specific order, and then reclaiming the memory with attacker data.
On the Windows NT heap, you can abuse Low Fragmentation Heap buckets to place an allocation adjacent to a target object’s header. Corrupt the header’s size field to create an overlapping allocation, then use that to overwrite a function pointer. The key is to stay within the same page to avoid guard page checks. Tools like heap-explorer.py (a custom Windbg extension I keep handy) let you peek at the heap state in real time. It’s messy, manual work, but that’s the point.

Taming Kernel Mitigations
When the target is a kernel driver, the game changes. KASLR, SMEP/SMAP, and kCFG are standard. But kernel exploits often have the luxury of arbitrary physical memory access via DMA or MmMapIoSpace abuse. If you can read/write physical pages, you sidestep all virtual address–based mitigations entirely.
For logical bugs, I look for token-stealing payloads that don’t need to execute in kernel mode. A classic approach: use a write-what-where primitive to overwrite the current process’s _EPROCESS.Token with that of a SYSTEM process. No shellcode, no execution—just a single 8-byte write. This bypasses SMEP because we never execute anything in kernel space; we just corrupt a data structure.
On newer Windows builds, Token is a pointer to a reference-counted object, so you need to swap the pointer and adjust the reference count. Find the SYSTEM process’s token, increment its refcount, and overwrite your own token. The whole operation can be done in a few lines of C if you have a read/write primitive. Simple, surgical, hard to spot.
Writing the Payload
Custom shellcode is where most people get lazy. Don’t. Your payload should be position-independent, null-free, and short enough to fit in the available memory. Use call/pop for string references, hash API names instead of using import tables, and avoid suspicious syscall patterns that EDRs hook.
For Windows, I prefer a syscall stub that extracts SSNs dynamically from ntdll.dll’s memory, rather than hardcoding them. This ensures the payload works across builds. The stub walks the syscall instruction bytes, reads the service number from the preceding mov eax, SSN, and stores them in a table. It’s more code, but it’s future-proof—a fair trade-off.
Testing Against Actual Defenses
Your bypass doesn’t mean anything until it survives a live EDR. Spin up a Windows 11 VM with Defender for Endpoint in passive mode, and instrument your exploit with breakpoints on NtWriteVirtualMemory and NtQueueApcThread. If the EDR injects a hook DLL, your payload needs to detect the trampoline and avoid walking into a trap.
I use a technique called syscall whispering: instead of calling NtWriteVirtualMemory through the DLL, I issue the syscall directly with the correct SSN and arguments in r10/rcx. This skips user-mode hooks entirely. The catch is that the kernel checks the syscall instruction’s origin—on x64, it expects it to come from a specific KiSystemCall64 stub. You can mimic that by copying the stub’s code into your own executable region. It’s a little extra work, but it keeps you off the radar.
Evading ETW and Telemetry
Even if you bypass hooks, Event Tracing for Windows (ETW) can log your process creation, memory allocation, and thread injection. Modern EDRs feed on ETW events. To silence them, patch the EtwEventWrite function in ntdll at runtime to return immediately. Or, more cleanly, set the SystemTraceProvider GUID’s enable flags to zero via NtTraceControl. This requires a bit of reverse engineering to find the right control codes, but it’s a one-time effort that pays off every run.
FAQ
What’s the minimum info leak needed to bypass both ASLR and stack canaries?
You need two values: a code address (to defeat ASLR) and the stack canary value (or a pointer to the canary’s location). Often a single memory disclosure that leaks a return address and the adjacent canary in one buffer over-read is enough. If the canary is copied to a global variable in a poorly compiled binary, you can leak it from the data section instead—less work, same result.
How do you handle Control Flow Guard when building a ROP chain?
CFG validates indirect calls against a bitmap of valid function entry points. To bypass it, you either call only functions that are valid CFG targets (like VirtualProtect), or you abuse a CFG-exempt module—older DLLs compiled without /guard:cf. If the target process loads such a module, you can pivot the stack into that module’s memory and execute your gadgets there without CFG checks. It’s an old trick, but it still works on plenty of systems.
Is it still possible to exploit a kernel driver without a code-execution primitive?
Absolutely. Arbitrary read/write primitives let you modify security-critical structures like _TOKEN or _KPROCESS directly. The classic token-stealing attack never executes code in kernel mode. More recent variants overwrite the _SEP_TOKEN_PRIVILEGES bitmask to enable dangerous privileges like SeDebugPrivilege, then call OpenProcess on a SYSTEM process from user mode. No kernel shellcode needed—just a careful nudge to the right bits.
What’s the best way to test a bypass without triggering production EDR?
Build an isolated lab with a snapshot-capable hypervisor. Use a Windows 11 evaluation VM with the same patch level as your target. Install Defender for Endpoint in passive mode so you can see alerts without automatic remediation. Run your exploit while capturing a kernel trace with wpr and ETW events with logman. Analyze the traces offline to see exactly which events your payload generated. It beats getting your test box nuked by a real-deploy sensor.