← back to theory
Evasion

Windows Defense Evasion — AV, EDR & ASR

Modern offensive tradecraft spends most of its effort not on the exploit but on not being seen. To understand why techniques like in-memory execution, unhooking, and packing work — and why a heavily-signatured tool such as mimikatz can still be run — you have to understand how endpoint defences actually inspect code. This page is the research view of the detection surfaces and the structural reasons each can be evaded. It deliberately explains mechanisms, not payloads: the goal is to know why a class of bypass exists, which is what makes both attack and detection legible. The concrete commands live in the Toolkit (PEzor, Invisi-Shell, Stracciatella, Sliver), and the logging side is on the Windows Telemetry page.

Two detection surfaces: static and runtime

Every endpoint product inspects code across two fundamentally different moments, and almost every evasion technique is really a choice about which surface to defeat.

SurfaceWhat it inspects
Static (on disk / pre-execution)The file's bytes before it runs — signatures, hashes, entropy, imports, strings. Cheap and safe, but blind to anything that only exists once the code is running
Runtime (in memory / behavioural)What the code does as it executes — the API calls it makes, the memory it allocates, the process tree it creates, the scripts it feeds to interpreters. Far harder to fool, but also harder and costlier to observe
The single biggest move in modern tradecraft — running tools in memory instead of from disk — is simply refusing to present anything to the static surface at all. If the tool never becomes a file on the target, file scanning has nothing to scan, and the whole contest shifts to the runtime surface where the attacker has more room to manoeuvre.

How AV and EDR actually inspect

"Antivirus" and "EDR" sit on a spectrum of the same idea, differing in how much runtime visibility they have.

MechanismHow it works
SignaturesMatch known-bad byte patterns/hashes. Precise but brittle — any change to the bytes defeats them
Heuristics / MLScore suspicious traits (packed sections, rare imports, odd entropy) rather than exact matches — catches variants but generates false positives
Userland API hookingThe product injects a DLL into processes and redirects sensitive API calls through its own inspection code before they reach the real function
Kernel callbacks / ETWKernel-level notifications on process/thread/image events and a telemetry stream (ETW) the sensor subscribes to
AMSIAn interface that lets scripting engines and .NET submit content to the installed AV for scanning at the moment it would execute

The key research insight is where each of these lives. Signatures and heuristics act on the static surface. Kernel callbacks live below the process and are hard to touch from userland. But userland hooking and AMSI both live inside the target process, in the same address space as the code they inspect — and that co-location is the structural weakness the next sections exploit.

Userland API hooking and unhooking

To watch what a program does without kernel-level cost, many products place inline hooks in the userland libraries every program calls — chiefly ntdll.dll, the lowest userland layer before the kernel. A hook redirects the start of a sensitive function (say, the routine that writes another process's memory or opens a handle to LSASS) to the sensor's code, which inspects the arguments, decides, and then continues to the real function. This is how an EDR "sees" behaviour that never generates a file.

The weakness is architectural: those hooks are just instructions in the calling process's own memory, and the process can read and write its own memory. Unhooking exploits exactly this — the tooling maps a fresh, clean copy of ntdll from disk (or a known-good source) and restores the original function prologues, so the sensor's redirects are overwritten and subsequent calls run straight to the real functions with no inspection. A related family avoids the hooks rather than removing them by invoking the kernel transition directly instead of going through the hooked library entry points. Either way, the principle is the same: a defence that lives in the attacker's own address space can be edited by the attacker. This is the mechanism behind PEzor's -unhook option.

In-memory execution and reflective loading

The static surface only exists if the tool is a file. In-memory execution removes the file entirely: the tool's bytes are delivered over the C2 channel and loaded directly into the memory of a process that is already running, then executed there. For .NET tooling this is what execute-assembly does — it loads a C# assembly into the CLR in memory and invokes its entry point; for native code the equivalent is reflectively mapping a PE into memory rather than letting the OS loader read it from disk.

Because no file is written, on-write and on-disk scanning never fire, and only the runtime surface remains. That surface can still catch the tool — memory scanners look for the decrypted tool image, and the behaviour it produces is still visible — which is why in-memory execution is usually combined with the other techniques here: unhooking to blind the API inspection, and, for a resident implant, sleep masking to keep the image encrypted while dormant (see in-memory tradecraft).

Packing, encoding, and donut

Packing changes what the static surface sees. A packer wraps the original code in a layer that only reveals the real payload at runtime, so the file's bytes — its signature, its imports, its strings — no longer match anything in a signature set, and the interesting content only appears in memory once the loader has run. Tools like donut turn a PE or .NET assembly into position-independent code that can be loaded in memory, and packers such as PEzor combine that with unhooking, anti-analysis and delayed execution.

The research point is the trade-off. Packing reliably defeats exact signatures, but the traits that make a packed file work — high entropy, a small stub that allocates executable memory and jumps to it — are themselves what heuristic and ML classifiers score as suspicious. So packing shifts the problem from "matches a known tool" to "looks like a packed thing", and the operator's job becomes making the loader's runtime behaviour unremarkable rather than making the file unmatchable. This is why serious evasion is layered: no single technique covers both surfaces at once.

AMSI and ETW: userland instrumentation

The Antimalware Scan Interface (AMSI) and Event Tracing for Windows (ETW) are the two pieces of instrumentation that give defenders visibility into script and .NET activity that would otherwise be invisible. AMSI lets the PowerShell and .NET runtimes hand content to the installed AV to be scanned at the moment it would run — defeating the old trick of never writing a script to disk. ETW carries a rich stream of runtime events (including .NET method and assembly-load events) that an EDR can subscribe to.

Both are powerful, and both share the same structural property as userland hooks: the check is performed inside the process, by code and state that the process itself can reach. AMSI's scan is a userland function call whose result the runtime trusts; ETW's provider state is likewise reachable from the process. That co-location is the reason a long line of published bypasses exists — they influence the in-process check or its cached result so that content is treated as clean, or so that no event is emitted, without ever involving the kernel or the AV engine proper. This page does not reproduce those payloads; the load-bearing idea is simply that a check living in the same trust boundary as the code it inspects can be influenced by that code. It is also why the durable defensive answer is layering these userland signals with kernel-level telemetry that the process cannot reach.

Helpers like Invisi-Shell and Stracciatella apply this at the engine level: rather than patching AMSI after the fact, they arrange for a PowerShell runspace in which the instrumentation was never wired up, so scripts run in an environment that is clean from the start. The Windows Telemetry page covers the logging channels these affect.

Attack Surface Reduction (ASR) rules

ASR is a set of Defender rules that block behaviours commonly associated with intrusion regardless of the file involved — for example, Office spawning child processes, scripts launching downloaded executables, or processes stealing credentials from LSASS. Rather than asking "is this file malicious?", an ASR rule asks "is this action one attackers rely on?" and blocks the action.

That framing is why ASR is bypassed differently from signature detection. An operator does not try to make a file look clean; they route the same objective through an action the rule does not cover — a different parent process, a different execution primitive, moving the sensitive step onto a host or path the rule does not watch, or repackaging the tool so the blocked behaviour (e.g. a particular loader touching disk) is replaced by an in-memory equivalent. The research lesson is that behavioural controls raise the cost of the obvious path and push the attacker toward less-instrumented alternatives, which is exactly what a defender wants ASR to do even when a determined operator gets around a specific rule.

Defence in depth is the real answer

Every technique on this page defeats one surface, and each has a matching defensive counter-move: static evasion is answered by runtime telemetry, userland bypasses by kernel-level sensors and memory scanning, behavioural blocks by breadth of coverage. No single layer is sufficient, and neither is any single bypass. That symmetry is the takeaway — an operator layers techniques because the defence is layered, and a blue team invests in kernel-level and cross-surface visibility precisely because the userland surfaces can be influenced from within.

  • Static-only evasion (packing, encoding) is defeated by watching runtime behaviour, so it is never used alone.
  • Userland bypasses (unhooking, AMSI/ETW) are answered by kernel callbacks and memory scanning the process cannot touch.
  • In-memory execution removes the file but not the behaviour — the resident image and the actions still betray it.
  • Behavioural blocks (ASR) raise cost and force less-instrumented paths, which is a win even when a rule is individually bypassed.

The takeaway

Endpoint defence inspects code statically before it runs and behaviourally as it runs; almost every evasion technique is a decision about which surface to avoid and which to fight. In-memory execution removes the static surface, unhooking and AMSI/ETW bypasses neutralise userland runtime checks that share the process's own trust boundary, packing shifts the static picture at the cost of looking packed, and ASR forces attackers off the obvious behavioural path. Hold the two-surface model and every tool in the Toolkit — from PEzor to Invisi-Shell to Sliver's in-memory execution — reads as an answer to a specific detection surface rather than a bag of tricks.

Related reading