Warmup Guide — How an EDR Builds Its Picture, and How to Engineer Its Detections
Zero-to-principal primer for Phase 07. It builds every concept the phase depends on from first principles: the architecture of a modern EDR, the kernel callbacks that make it hard to blind, ETW and the kernel-emitted ETW-TI provider, the user-mode hooks in
ntdlland how syscalls relate to them, AMSI, the full sensor map and its blind spots, and then detection engineering — Sysmon config, the anatomy of a Sigma rule, the Pyramid of Pain, behavioral vs IOC, robust vs brittle detections — and the telemetry-gap methodology that ties it together. It assumes only that you can write software and have read Phase 00's authorization boundary. By the end you can predict what a sensor sees for a given technique and build the detection that fires.Safety frame (not optional). This guide is detection-forward. It explains EDR internals and the theory of evasion strictly so you can predict telemetry and engineer detections. Every evasion concept is paired, in the same breath, with the detection or visibility-gap it implies. There is no working bypass, no unhooking code, no ETW/AMSI patch routine, and no deployable evasion here — and there is none in the labs, which reason over synthetic sensor metadata. The difference between a red teamer and a criminal is authorization and intent, not knowledge (Phase 00). The skill being trained is predicting what a sensor sees and closing the gap.
Table of Contents
- Chapter 1: What an EDR Is and How It Builds Its Picture
- Chapter 2: Kernel Callbacks — the Sensors You Cannot Reach from User Mode
- Chapter 3: ETW and ETW-TI — In-Process Telemetry vs the Kernel's Own
- Chapter 4: User-Mode Hooks in ntdll — and How Syscalls Relate to Them
- Chapter 5: AMSI — Scanning Content at Runtime
- Chapter 6: The Sensor Map and Its Blind Spots
- Chapter 7: Detection Engineering — Sysmon, Sigma, and the Pyramid of Pain
- Chapter 8: Robust vs Brittle Detections — Behavioral Beats IOC
- Chapter 9: Telemetry-Gap Analysis — the Methodology
- Lab Walkthrough Guidance
- Success Criteria
- Common Mistakes and OPSEC Failures
- Interview Q&A
- References
Chapter 1: What an EDR Is and How It Builds Its Picture
Zero background. "EDR" is Endpoint Detection and Response — the software that watches a Windows (or Linux/macOS) host and tries to notice when something malicious happens, record it, and let a responder act. Older antivirus asked a single question — "does this file match a known-bad signature?" — and answered it when a file landed on disk. EDR asks a richer one: "is the behavior on this host consistent with an attack?" — and to answer it, the EDR must observe behavior as it happens. Everything in this phase follows from one fact: to observe behavior, the EDR must place sensors at the points where behavior occurs, and each sensor sits at a particular trust boundary that determines what it can see and who can blind it.
What it is. A modern EDR is three cooperating parts:
+-------------------------------------------------------------+
| CLOUD BACKEND |
| correlation, ML, threat intel, retro-hunt, analyst console |
+----------------------------^--------------------------------+
| (batched, signed telemetry)
+----------------------------|--------------------------------+
| HOST | |
| +------------------------+-----------------------------+ |
| | USER-MODE AGENT (ring 3) | |
| | - reads ETW sessions (CLR, DNS, WMI, PowerShell) | |
| | - inline hooks in ntdll of monitored processes | |
| | - AMSI provider; log shipping; local rules | |
| +------------------------^-----------------------------+ |
| | (events up) |
| +------------------------+-----------------------------+ |
| | KERNEL DRIVER (ring 0) | |
| | - process/image/thread/handle notify callbacks | |
| | - minifilter (file) + registry callbacks | |
| | - consumes ETW-TI (kernel Threat-Intelligence) | |
| +------------------------------------------------------+ |
+-------------------------------------------------------------+
- The kernel-mode driver (ring 0). A signed driver that registers with Windows kernel notification facilities so the OS tells it about security-relevant events: a process starts, an image loads, a thread is created, a handle is opened, a file is written, a registry key changes. The driver also consumes the kernel's own ETW-TI provider. Because it lives in the kernel, user-mode code in a target process cannot reach it — this is the source of the phase's whole thesis.
- The user-mode agent (ring 3). A service plus, often, code injected into monitored processes. It
reads ETW sessions (the CLR/.NET provider, DNS, WMI, PowerShell), places inline hooks in
ntdllto inspect syscall arguments, registers an AMSI provider to scan scripts, applies local rules, and ships telemetry up. Because it runs in user mode, much of it sits in the same trust boundary as the attacker's payload — which is exactly why user-mode evasions exist and why they are limited. - The cloud backend. Where per-host telemetry is correlated across the fleet, scored, enriched with threat intel, retro-hunted, and surfaced to an analyst. The host is the sensor; the cloud is the brain.
Why it exists. Signatures fail against anything new or obfuscated; a one-byte change defeats a hash.
Behavior is far stickier: an attacker can rename their tool, but if their goal is to inject into another
process and dump LSASS, the behavior — a remote thread, a cross-process handle to LSASS, a
suspicious memory allocation — recurs no matter the binary. EDR is the bet that behavior is harder to
change than bytes, which is the same bet as the Pyramid of Pain (Chapter 7).
Under the hood — trust boundaries are the whole game. Sort every sensor by who can tamper with it:
| Boundary | Sensors | Who can blind it |
|---|---|---|
| Kernel (ring 0) | process/image/thread/handle callbacks, minifilter, registry callbacks, ETW-TI | only kernel-level code (a malicious driver, BYOVD) — not a user-mode payload |
| User-mode, out-of-process | network telemetry, the agent service, cloud correlation | not reachable by patching the payload's own memory |
| User-mode, in-process | ntdll inline hooks, user-mode ETW (EtwEventWrite), AMSI | the payload runs here too — it can patch these in its own address space |
The entire theory of user-mode evasion is: "blind the in-process row." The entire theory of robust detection is: "build on the kernel and out-of-process rows, which the in-process payload cannot touch." Hold this table in your head for the rest of the phase.
What telemetry it emits. The EDR itself emits a stream of normalized events: ProcessCreate,
ImageLoad, NetworkConnect, RemoteThread, ProcessAccess (handle open), FileCreate,
RegistryEvent, plus parsed ETW. Sysmon (Chapter 7) is a free, transparent stand-in that emits the
same shape of events with documented IDs — which is why we learn detection on Sysmon.
Significance. When the Operation Cedar Lattice report says "Meridian's EDR did not detect the injection," the only useful version of that sentence names the sensor and the boundary: "the host collected user-mode ETW but not ETW-TI, so an in-process ETW patch blinded the only injection sensor present; the kernel callback that would have survived was not being collected." That sentence is a finding. "The EDR missed it" is not.
Common misconceptions.
- "The EDR is one thing that either sees you or doesn't." It is a stack of sensors at different boundaries. Evasion is always against a specific sensor.
- "Kernel driver means it sees everything." It sees what it registers for and what it is collecting and shipping. An uncollected callback is a blind spot even though the driver is in the kernel.
- "Cloud ML will catch what the host misses." The cloud can only correlate telemetry the host actually sent. No data source, no detection — which is why telemetry-gap analysis (Chapter 9) is the root discipline.
Chapter 2: Kernel Callbacks — the Sensors You Cannot Reach from User Mode
Zero background. A "callback" is a function you hand to someone else so they call you when something happens. Windows lets a driver register callbacks with the kernel for security-relevant events. Because the kernel invokes them from ring 0, before or as the event completes, a user-mode payload — which lives in ring 3 — cannot intercept, suppress, or lie to them from its own process. These are the EDR's most evasion-resistant sensors.
What they are. The core notify routines an EDR registers:
| Callback | API | What it delivers |
|---|---|---|
| Process-create | PsSetCreateProcessNotifyRoutineEx(2) | every process start/exit: PID/PPID, image path, and (via the EDR) the command line and token |
| Image-load | PsSetLoadImageNotifyRoutine(Ex) | every DLL/driver mapped into a process: full path, base, signing info |
| Thread-create | PsSetCreateThreadNotifyRoutine(Ex) | every thread start, including remote threads created cross-process |
| Object/handle | ObRegisterCallbacks | handle creation/duplication — e.g. a process opening a handle to LSASS with read rights |
| File (minifilter) | FltRegisterFilter | file create/read/write/rename at the filesystem layer |
| Registry | CmRegisterCallbackEx | registry key/value creation and modification |
Why they exist. Microsoft built these so security products can observe the system without the fragile, easily-bypassed approach of patching kernel code (which also trips PatchGuard). They are the supported, stable way to get ground-truth on process, image, thread, handle, file, and registry activity — exactly the events that constitute most ATT&CK techniques.
Under the hood — why kernel residency is decisive. Consider process injection (T1055): the
attacker allocates memory in a remote process, writes a payload, and starts a thread there. Even if the
attacker has perfectly blinded every user-mode sensor in their own process:
- The remote thread trips
PsSetCreateThreadNotifyRoutinein the kernel — the attacker's process cannot un-register another process's kernel callbacks. - The cross-process handle open to the target (often with
PROCESS_VM_WRITE/PROCESS_VM_OPERATIONrights) tripsObRegisterCallbacks. - The memory allocation/protect in the remote process surfaces through ETW-TI (Chapter 3), which is also kernel-emitted.
So the lesson the labs encode is true at the mechanism level: a user-mode evasion blinds user-mode
sensors; the kernel callback is the survivor. This is why Lab 02 splits sensors into USER_MODE and
KERNEL sets and asserts that no modeled evasion blinds the kernel set.
What telemetry it emits. Each callback yields a normalized event. In Sysmon terms: process-create is
Event ID 1, image-load is 7, remote-thread-create is 8 (CreateRemoteThread), handle access to another
process is 10 (ProcessAccess), file create is 11, registry is 12-14. A real EDR emits the same shape
under its own schema. The fields that matter for detection: parent/child image and command line
(process tree), the GrantedAccess mask on a ProcessAccess event (e.g. 0x1010/0x1410 rights on
LSASS are a credential-dumping tell), and the StartAddress of a remote thread landing in unbacked
memory.
How a defender detects with them. The kernel callbacks are where your robust, behavioral detections live (Chapter 8). Examples:
- Credential dumping: a
ProcessAccess(Sysmon 10) openinglsass.exewith VM-read rights from a non-system process. This survives user-mode unhooking because the handle open is a kernel event. - Injection: a
CreateRemoteThread(Sysmon 8) whose start address is in private/unbacked memory, correlated with a prior cross-process handle. - Unbacked image: an
ImageLoad(Sysmon 7) of a secondntdll, or execution from memory with no backing file on disk.
Significance. When you build a detection, prefer the kernel callback for any technique that can be blinded in user mode. In the gap analysis (Lab 01) the highest-value sensors are usually the kernel ones, precisely because they cover techniques no user-mode-only sensor can robustly see.
Common misconceptions.
- "If I unhook ntdll, the EDR is blind." You blinded the user-mode hook. The kernel callbacks did not move. (Lab 02 makes this concrete.)
- "Kernel callbacks see arguments." The notify routines deliver the fact and identifiers; the EDR enriches (command line, token) from there. Some detail (e.g. full command line) comes from the agent, not the raw callback — which is why the data must actually be collected and shipped.
- "Only a kernel implant could blind these." Correct — and that (a malicious or vulnerable signed driver, BYOVD) is a far louder, far rarer act that has its own detections (driver-load events, Microsoft's vulnerable-driver blocklist). Raising the cost from a user-mode patch to a kernel implant is itself a win on the Pyramid of Pain.
Chapter 3: ETW and ETW-TI — In-Process Telemetry vs the Kernel's Own
Zero background. ETW is Event Tracing for Windows — a high-speed logging bus baked into the
OS since Windows 2000. Components all over Windows (the .NET runtime, DNS client, WMI, PowerShell,
TCP/IP, the kernel itself) are providers that emit structured events. A consumer (the EDR, or a
tool like logman/SilkETW) subscribes to a session and receives those events. ETW is how an EDR
gets visibility into things that have no kernel callback — like what a .NET assembly did in memory.
What it is. Two flavors matter enormously and are constantly confused:
- User-mode ETW providers. Most providers run in the process being observed. The .NET CLR
provider (
Microsoft-Windows-DotNETRuntime), the PowerShell provider, the WMI provider — they callEtwEventWrite(inntdll) from inside the target process to emit their events. That is the point of leverage: code in that process can reach the writer. - ETW-TI — the Threat-Intelligence provider (
Microsoft-Windows-Threat-Intelligence). This is a kernel-emitted provider, restricted to anti-malware (PPL-protected) consumers. It surfaces the sensitive operations attackers rely on —NtAllocateVirtualMemory,NtProtectVirtualMemory,NtMapViewOfSection, remoteWriteVirtualMemory,SetThreadContext, queueing APCs — from ring 0. Because the kernel emits it, a user-mode patch in the attacker's process cannot suppress it.
user-mode ETW (CLR/DNS/WMI): payload --calls--> EtwEventWrite (ntdll, in-process) --> session
^ attacker can patch this in its own memory (BLINDABLE)
ETW-TI: kernel syscall path --emits--> Threat-Intelligence provider (ring 0) --> PPL consumer
^ attacker has no reach into ring 0 (SURVIVES)
Why it exists. Many high-value behaviors are invisible to disk-and-process sensors: a fileless .NET loader never touches disk, a PowerShell payload runs in an existing process. ETW gives the EDR an in-runtime view (what the CLR JIT-compiled, what assemblies loaded), and ETW-TI gives it a kernel-truth view of the memory operations that injection and in-memory execution require.
Under the hood — the "ETW patch" and why it is half a story. A widely-described evasion patches the
first bytes of EtwEventWrite in the attacker's own process so it returns immediately without emitting.
What that actually does: it silences the user-mode providers that route through that function in
that process — e.g. the CLR provider stops reporting the in-memory .NET activity of that process.
What it does not do: it does not touch ETW-TI, which is emitted by the kernel on the syscall path,
nor any provider in another process, nor the kernel callbacks. So a .NET injection that patches ETW to
hide its assembly loads is still seen allocating and protecting remote memory via ETW-TI and
creating a remote thread via the kernel callback.
We describe the concept and its blast radius to predict telemetry. There is no patch routine here, and none is needed to reason about what goes blind: the question is always "which provider, which boundary."
What telemetry it emits / the detection of the patch. This is the crucial pairing — the evasion is itself a detection:
- The patch is a code modification of
ntdll.text. MakingEtwEventWritewritable requires anRWX/RWprotection change on a system DLL's executable section — exactly the kind of event ETW-TI reports (a protect-to-executable on a loaded module's code). - A provider going silent is itself a signal. If a process loaded the CLR but the CLR provider emitted nothing for it, that gap is anomalous and correlatable in the backend.
- Integrity check. A defensive agent can hash or compare the first bytes of
EtwEventWriteand the known hooked functions against a clean copy; a mismatch is the tamper.
Significance. ETW-TI is the single most important data source for the in-memory tradecraft this phase covers, and it is the textbook case of the phase thesis: the in-process evasion blinds the in-process provider; the kernel provider survives and detects both the behavior and the evasion. In the gap analysis, a host that collects user-mode ETW but not ETW-TI has a named, high-value blind spot for injection and in-memory execution — and Lab 01 will rank ETW-TI as a top sensor to add.
Common misconceptions.
- "Patching ETW blinds the EDR's ETW." It blinds the user-mode providers in that process. It does not touch ETW-TI or other processes.
- "ETW-TI is just another provider I can patch." It is kernel-emitted and consumable only by a protected (PPL) anti-malware process. A user-mode payload has no patch target for it.
- "If I block the EDR's ETW session, I'm clear." Tampering with sessions (stopping a trace, removing a provider) requires privilege and is loudly auditable — another self-detecting evasion.
Chapter 4: User-Mode Hooks in ntdll — and How Syscalls Relate to Them
Zero background. When a program wants the OS to do something privileged — allocate memory, open a
process, create a thread — it calls a Win32 API (VirtualAllocEx, OpenProcess), which eventually
calls a thin ntdll stub (NtAllocateVirtualMemory, NtOpenProcess). That stub runs a syscall
instruction that traps into the kernel. ntdll is the last user-mode code on the path to the
kernel — which is exactly why an EDR wants to watch it.
What it is. Many EDRs place inline hooks in the Nt* stubs of ntdll inside monitored
processes: they overwrite the first instruction with a jmp to the EDR's own code, which inspects the
arguments (which process are you opening? what memory are you protecting to executable?), decides to
allow/block/log, then continues into the real syscall. This gives the agent argument-level
visibility and lets it act before the kernel does.
Why it exists. It is the cheapest way to get rich, synchronous, argument-level interception in user mode without a driver doing all the work. The cost: it lives in the same process as the payload, so it is reachable — the defining weakness of any in-process sensor.
Under the hood — how syscalls relate to the hooks (theory, for prediction). Because the hook is a
jmp at the start of the ntdll stub, anything that reaches the kernel without executing that stub
is not seen by the hook:
- Direct syscalls assemble the syscall sequence themselves instead of calling the hooked
ntdllstub — the call never traverses the hooked bytes. - Indirect syscalls jump to the real
syscallinstruction inside cleanntdllbut past the hook — keeping the return address insidentdllto look more legitimate. - Unhooking maps a fresh, clean copy of
ntdll(from disk or a known-good source) over the hooked one, removing the EDR'sjmp.
We describe the concept to predict what the hook stops seeing — not to build it. The point for a detection engineer is the next paragraph.
The detection — what each leaves behind. Every one of these is self-detecting, and the detection lives on a sensor the in-process trick cannot reach:
- Call-stack / return-address anomaly. When the syscall finally traps to the kernel, the kernel (via
ETW-TI stack walks) can see where the call came from. A legitimate syscall returns into
ntdll; a direct syscall returns into the payload's own private/unbacked memory — a return address outside any loaded module is a strong injection/evasion signal. - A second clean
ntdll. Unhooking maps another copy ofntdll— anImageLoad/memory-mapped section ofntdllwhen one is already present, or execution ofntdllcode that is not backed by the on-disk file. The kernel image-load callback sees the map. - Hook integrity. The agent can verify its own hooks are intact; a removed
jmpis the tamper. - The behavior still surfaces in the kernel. Whatever the syscall did — allocate remote memory,
open
LSASS, create a remote thread — still fires the kernel callback and ETW-TI. Evading the hook does not evade the consequence.
What telemetry it emits. Practically: ETW-TI events for the memory/thread operations with a
suspicious call-stack; Sysmon image-load (7) of a duplicate ntdll; the kernel CreateRemoteThread
(8) and ProcessAccess (10) for the underlying behavior. The robust detection keys on the call stack
and the kernel event, not on the API name (which the evasion specifically avoids).
Significance. This is the cleanest example in the phase of defending on the right boundary. A
detection that depends on the ntdll hook firing is brittle — the whole class of syscall techniques
exists to defeat it. A detection that keys on the kernel-observed consequence and the call-stack
anomaly is robust. When you read "the operator used indirect syscalls," your job is to say "so
the user-mode hook saw nothing; here is the ETW-TI + call-stack detection that still fires."
Common misconceptions.
- "Direct syscalls make me invisible." They make you invisible to the user-mode hook. The kernel still does the work and still reports it; the unusual call stack is more anomalous, not less.
- "Unhooking is silent." Mapping a clean
ntdllis an observable image event, and your hooks-gone state is checkable. - "Hooks are the EDR's main sensor." They are the most fragile one. Mature EDRs treat hooks as a convenience and lean on kernel callbacks + ETW-TI for ground truth.
Chapter 5: AMSI — Scanning Content at Runtime
Zero background. AMSI is the Antimalware Scan Interface. Obfuscation defeats file signatures: a PowerShell script can be Base64-encoded, string-concatenated, and compressed so nothing on disk matches a rule. But to run, the script must eventually be de-obfuscated in memory into the real commands. AMSI is Microsoft's hook at exactly that moment: scripting engines and the .NET runtime hand the final, de-obfuscated buffer to the registered antimalware provider for a verdict before they execute it.
What it is. AMSI is an API (amsi.dll, principally AmsiScanBuffer) that script hosts call to
submit content for scanning. The integrations that matter: PowerShell (every script block, including
de-obfuscated ones), Windows Script Host (VBScript/JScript), the .NET runtime (Assembly.Load
of in-memory assemblies, since .NET 4.8+), Office VBA macros, and the JScript/VBScript engines. The
registered provider (Defender, or a third-party EDR) returns clean/blocked.
Why it exists. It closes the obfuscation gap: it does not matter how the payload was encoded on disk or over the wire, because AMSI sees the content as it will actually execute. It moved the detection point from bytes at rest to content at runtime — a meaningful climb up the Pyramid of Pain.
Under the hood — where it sits, and the bypass concept. AMSI runs in-process, in the same
address space as the script host — which puts it on the blindable in-process row of Chapter 1. The
concept of an AMSI bypass is to make AmsiScanBuffer (or the AMSI context/result) stop returning a
"malicious" verdict for that process — by forcing a clean result, neutralizing the context, or
preventing amsi.dll from scanning. (We name the concept to predict telemetry; no bypass is provided
or needed to reason about the detection.) The blast radius: AMSI goes blind for that process only,
for content scanning only — it does not touch the kernel callbacks, ETW-TI, or process-create
telemetry.
The detection — the bypass is observable. AMSI sits in-process, so it can be blinded — and the act of blinding it is one of the most reliably detected things in this whole phase:
- AMSI itself is a tripwire. Submitting the well-known
AMSItest string (the EICAR-equivalent for AMSI) should always return "detected." If a process loadedamsi.dllbut a known-malicious test buffer comes back clean, AMSI was tampered with. - A patch on
amsi.dll.text. Forcing a clean result usually means modifyingamsi.dllcode or its result in memory — anRWX/write onamsi.dll, visible to ETW-TI (kernel) and to image/ memory telemetry. - PowerShell script-block logging (4104) still fires. Script-block logging is a separate sensor from AMSI; bypassing AMSI does not disable 4104. The de-obfuscated script is still logged — a classic "you blinded one sensor, the sibling sensor still saw it."
- The amsi.dll load pattern. A process that loads
amsi.dlland immediately writes to it, or one that suspiciously avoids loading it for known scriptable content, is anomalous.
What telemetry it emits. AMSI verdicts (Defender/EDR), amsi.dll image-load and any write to it
(ETW-TI / image telemetry), PowerShell 4104/4103 script-block and module logging, and process-create of
the script host with its command line. The robust detection correlates the bypass artifact with
the surviving script-block log.
Significance. AMSI is the phase's third example of the same shape: an in-process sensor that can be blinded, paired with a detection of the blinding and a sibling/kernel sensor that survives. In Lab 02, the AMSI-only script behavior, fully bypassed, is still detectable — because the bypass itself is observable. That is the single most important thing to be able to say about AMSI in an interview.
Common misconceptions.
- "Bypassing AMSI means PowerShell logging is off." No — script-block logging (4104) is a separate sensor and keeps firing.
- "AMSI blocks malware." AMSI scans content and returns a verdict; blocking is the provider's job. AMSI is a visibility surface as much as a prevention one.
- "AMSI is only for PowerShell." It also covers WSH, Office VBA, and in-memory .NET assembly loads — which is why it is relevant to the .NET tradecraft in Chapter 3.
Chapter 6: The Sensor Map and Its Blind Spots
Zero background. Now assemble the pieces. A sensor map is the inventory of every telemetry source a host actually collects, organized by what it sees and what boundary it sits at. A blind spot is a behavior for which the host collects no sensor that can see it — or only sensors a common evasion can blind. Detection engineering starts here: you cannot detect what you do not collect.
What it is. The consolidated map for this phase:
| Sensor | Boundary | Sees | Blinded by | Survivor / detection |
|---|---|---|---|---|
| Process-create callback | kernel | every process: tree, image, cmdline (enriched) | — | baseline of process trees |
| Image-load callback | kernel | every module map, signer | reflective/in-memory load (no disk module) | unbacked-memory exec; duplicate ntdll |
| Thread/handle callback | kernel | remote threads, cross-proc handles | — | injection + LSASS-access detection |
| ETW-TI | kernel | alloc/protect/inject, syscall stacks | — | the durable in-memory detection |
| Minifilter (file) | kernel | file create/write | in-memory-only tradecraft | canary/mass-write; no-disk-exec correlation |
| Network | out-of-process | connections, JA3/SNI | — | beacon timing, egress anomalies |
| user-mode ETW (CLR/etc.) | in-process | in-memory .NET, DNS, WMI | EtwEventWrite patch | ETW-TI (kernel) survives |
| ntdll hooks | in-process | syscall args | direct/indirect syscalls, unhook | call-stack anomaly; kernel consequence |
| AMSI | in-process | de-obfuscated script/.NET content | AMSI bypass | 4104 script-block; bypass artifact |
| script-block (4104) | host log | de-obfuscated PowerShell | disable logging (audited) | the disable event is logged |
Why it matters. Read the table by column: every blindable sensor is in-process, and every one has a survivor in the kernel or out-of-process rows or a sibling host log. That is the structural reason the phase thesis holds. Read it by row and you have the gap-analysis input for Lab 01 (which techniques need which sensors) and Lab 02 (which sensors a given evasion removes).
Under the hood — blind spots are missing rows, not clever attackers. The most common real blind
spot is not a sophisticated bypass; it is a data source nobody turned on. Hosts routinely run
without ETW-TI, without command-line logging, without script-block logging, without ProcessAccess
auditing. Each missing row makes a class of techniques invisible regardless of attacker skill. The gap
analysis quantifies this: given the rows you have, which techniques are detectable, partial, blind, and
which single missing row recovers the most?
The detection / methodology. The output of the sensor map is a gap map (Lab 01) and a residual analysis (Lab 02). Together they answer the report's two questions: "what could we not see at all?" (missing rows) and "what did we have a sensor for but got blinded?" (evaded rows, and the survivor that still caught it).
Significance. This chapter is the Operation Cedar Lattice Phase 07 artifact in miniature. The detection-gap map you deliver to Meridian is this table, instantiated with their actual collection, with the blind spots ranked and a plan to close them.
Common misconceptions.
- "We have an EDR, so we have visibility." You have the visibility you collect and ship. An EDR with ETW-TI disabled has the same injection blind spot as no EDR for that technique.
- "The clever evasion is the threat." More often the missing data source is. Fix the rows first.
- "More logs = better." Volume without the right rows is cost without coverage; the gap analysis optimizes for coverage gain, not log volume.
Chapter 7: Detection Engineering — Sysmon, Sigma, and the Pyramid of Pain
Zero background. Knowing what a sensor can see is half the job; the other half is writing the detection — the rule that turns telemetry into an alert. Three tools/ideas: Sysmon (a free, transparent sensor that emits EDR-shaped events you can learn on), Sigma (a vendor-neutral way to write a detection rule once and translate it to any backend), and the Pyramid of Pain (the model that tells you whether your rule is worth keeping).
Sysmon — the sensor you can read. System Monitor is a free Microsoft Sysinternals driver + service that registers the same kernel callbacks an EDR does and writes normalized events to the Windows event log. The event IDs you must know:
| Sysmon ID | Event | Detection value |
|---|---|---|
| 1 | ProcessCreate | the backbone: image, command line, hashes, parent |
| 3 | NetworkConnect | egress, beaconing |
| 7 | ImageLoad | unsigned/unbacked module loads, duplicate ntdll |
| 8 | CreateRemoteThread | injection |
| 10 | ProcessAccess | LSASS handle opens (credential dumping) — watch GrantedAccess |
| 11 | FileCreate | drops, staging |
| 12-14 | RegistryEvent | persistence keys, autoruns |
| 22 | DnsQuery | C2 over DNS, domain anomalies |
Sysmon is configured by an XML config that decides which events to capture and which to drop —
this is where you tune the sensor. The community gold standards are
SwiftOnSecurity/sysmon-config (a well-commented baseline) and Olaf Hartong's sysmon-modular (an
ATT&CK-tagged, modular config). A Sysmon config change is a telemetry decision: enabling command-line
capture or ProcessAccess on lsass.exe flips techniques from blind to detectable — the exact move Lab
01 recommends.
Sigma — write the rule once. A Sigma rule is a small YAML document describing a detection in a generic schema; converters translate it to Splunk SPL, Elastic DSL, Sentinel KQL, etc. Anatomy:
title: Suspicious LSASS Access (Credential Dumping)
id: 0e2c3f1a-... # stable UUID
status: experimental
description: A non-system process opened a handle to lsass.exe with VM-read rights.
references:
- https://attack.mitre.org/techniques/T1003/001/
logsource:
product: windows
category: process_access # Sysmon Event ID 10
detection:
selection:
TargetImage|endswith: '\lsass.exe'
GrantedAccess|contains: # read/clone rights used to dump
- '0x1010'
- '0x1410'
- '0x143a'
filter_legit:
SourceImage|endswith:
- '\MsMpEng.exe' # Defender legitimately reads lsass
- '\wininit.exe'
condition: selection and not filter_legit
falsepositives:
- Some EDR/AV and backup agents open lsass legitimately (tune by SourceImage).
level: high
tags:
- attack.credential-access
- attack.t1003.001
The parts that matter: logsource (which sensor — this is where the data-source dependency is
explicit), detection (the selection/filter/condition logic), falsepositives (honesty about
noise), and tags (ATT&CK mapping). A good rule names its data source, its FPs, and its ATT&CK
technique — which is exactly what the track's detection-pairing bar demands.
The Pyramid of Pain — is this rule worth keeping? David Bianco's model ranks indicator types by how much pain changing them costs the adversary:
TTPs <- changing how they operate: very hard (BUILD HERE)
Tools <- swap the toolkit: hard
Network/Host Artifacts <- change named pipes, paths: annoying
Domain Names <- register a new domain: simple
IP Addresses <- rotate: easy
Hash Values <- recompile/repack: trivial (AVOID RELYING HERE)
A detection on a hash is defeated by recompilation; on a TTP (the behavior — a non-system
process opening LSASS with read rights) by a fundamental redesign. The whole reason this phase teaches
which sensor sees the behavior is so you can write detections at the TTP tip, on the surviving
kernel sensor, where they cost the adversary the most.
What telemetry / how it ties together. The loop is: pick a technique → find the sensor that sees its behavior robustly (Chapters 2-6) → ensure the Sysmon/EDR config collects that sensor → write the Sigma rule on it, at the TTP level, with FP tuning → verify it fires on a benign test (the HITCHHIKER'S GUIDE). That loop is detection engineering.
Significance. This is the deliverable that makes the offensive knowledge hireable. A red teamer who can say "and here is the Sigma rule, on the kernel sensor, at the TTP level, with the false positives tuned, that closes this gap" is doing the consultant's job, not the script kiddie's.
Common misconceptions.
- "Sigma is a detection engine." No — it is a rule language; a converter compiles it to your SIEM's query language. Its value is portability and shared rules.
- "A high-fidelity hash rule is good." It is precise but brittle — bottom of the pyramid. Good detection trades a little precision for durability.
- "Sysmon is the EDR." Sysmon is a transparent sensor you learn and prototype on; an EDR adds hooks, ETW-TI, response, and a backend. Detections written on Sysmon IDs translate to EDR fields.
Chapter 8: Robust vs Brittle Detections — Behavioral Beats IOC
Zero background. Two rules can fire on the same incident yet have wildly different lifespans. A brittle rule keys on something the adversary can change in seconds; a robust rule keys on something they would have to redesign their operation to change. The difference is the Pyramid of Pain applied to your own rule.
What it is. Concretely, for credential dumping (T1003.001):
| Rule keys on | Pyramid level | Lifespan |
|---|---|---|
the hash of mimikatz.exe | hash | until they recompile (minutes) |
the string sekurlsa::logonpasswords | artifact | until they obfuscate (minutes) |
the filename mimikatz.exe | artifact | until they rename (seconds) |
a non-system process opening lsass.exe with VM-read GrantedAccess | TTP | until they find a way to read credentials without opening that handle (hard) |
The bottom three are IOC detections; the last is behavioral. The behavioral one keys on the kernel-observed consequence of the technique — the very thing the attacker's goal requires — so it survives renaming, recompiling, and (critically) user-mode evasion, because the handle open is a kernel event.
Why it matters. Every evasion in this phase is, in pyramid terms, an attempt to push your detection down the pyramid — to make your hook-based or signature-based rule miss. The counter is to build up the pyramid in the first place, on the surviving sensor. A detection engineer's instinct should be: "what is the behavior's irreducible kernel-observable consequence, and can I key on that?"
Under the hood — robustness comes from the boundary. Recall Chapter 1's boundary table. A rule on an
in-process sensor (an AMSI string, an ntdll-hook event) is brittle because the attacker shares
that boundary and can blind it. A rule on a kernel or out-of-process sensor (a ProcessAccess
to LSASS, an ETW-TI memory-protect, a beacon's network timing) is robust because the attacker cannot
reach that boundary from their payload. Robustness is not a property of cleverness; it is a property
of which boundary you key on.
The detection / how to build robust. Practical rules of thumb:
- Key on the consequence, not the tool. Detect the
LSASShandle, notmimikatz. - Prefer the lowest boundary that sees the behavior. Kernel callback > ETW-TI > user-mode ETW > hook > signature.
- Correlate siblings. Injection = remote thread (8) + cross-process handle (10) + ETW-TI alloc; any one alone is noisier, the conjunction is robust.
- Treat evasion as a detection. RWX on
ntdll/amsi.dll, a duplicatentdll, a clean AMSI verdict on the test string — the act of blinding a sensor is a high-fidelity behavioral signal. - Tune FPs by legitimate source, not by weakening the behavior. Whitelist Defender opening
LSASSbySourceImage, not by dropping the rule's rights mask.
Significance. This is the principle that separates a detection that survives a single engagement from one the client keeps for years. When Lab 01 marks a technique "partial," it is flagging a brittle coverage situation — the only present sensor is one an evasion can drop. The fix is to add the kernel sensor and rewrite the rule on it.
Common misconceptions.
- "Behavioral rules are too noisy." Well-built behavioral rules (consequence + sibling correlation + source-based FP tuning) are both durable and precise. Noise usually means the rule keyed on one weak field instead of the conjunction.
- "IOCs are useless." IOCs are cheap, fast blocks with a short shelf life — fine as a layer, fatal as the only layer. Use them, but never depend on them.
- "If it fired once, it's a good rule." Firing on the seeded test says nothing about surviving the next evasion. Judge a rule by where it sits on the pyramid.
Chapter 9: Telemetry-Gap Analysis — the Methodology
Zero background. Telemetry-gap analysis is the disciplined process of measuring, for a real host or org, the distance between the techniques you care about and the data sources you actually collect — and producing a ranked plan to close the most valuable gaps first. It is the methodology behind both labs and the Phase 07 deliverable.
What it is — the steps.
- Enumerate the techniques that matter. Not all of ATT&CK — the techniques the threat model (FIN-LATTICE, the actor you emulate) actually uses. This is the threat-informed input from Phase 01.
- Map each technique to its required data sources. What sensor(s) must exist to see this behavior robustly? (ATT&CK data sources / data components and the CTID Sensor Mappings give the canonical map; the labs use a curated slice.)
- Inventory what the host actually collects. The sensor map of Chapter 6, instantiated: which callbacks, which ETW providers (and is ETW-TI on?), command-line logging on?, AMSI?, script-block?
- Classify each technique. Detectable (all required sensors present), partial (some — a brittle
state), blind (none). This is
coverage()in Lab 01. - Find the highest-value gap. Which single missing sensor flips the most blind techniques to
detectable? That is the next investment. (
best_sensor_to_add().) - Produce the prioritized plan. Greedily add the highest-gain sensor, recompute, repeat — the
ranked telemetry-improvement roadmap. (
improvement_plan().) - For collected-but-evadable sensors, do residual analysis. For techniques that are covered, which evasions would blind the present sensor, and does a survivor remain? (Lab 02.) A technique "covered" only by a blindable in-process sensor is a hidden gap.
- Close the gap and verify. Change the Sysmon/EDR config (a new data source) and write the Sigma rule; verify it fires on a benign Atomic test (the HITCHHIKER'S GUIDE).
Why it exists. Without it, "improve our logging" is an unbounded, unprioritized wish. With it, you deliver a ranked, gain-maximizing, cost-aware roadmap: "turn on ETW-TI first — it recovers six blind techniques; then command-line logging — four more; here is each Sigma rule." That is a consulting artifact a CISO can fund.
Under the hood — it is set cover. "Which sensors cover the most techniques" is the classic set cover problem (NP-hard), so the labs use a greedy 1-step lookahead (add the locally highest-gain sensor, repeat). Greedy set cover is a well-understood approximation — a defensible prioritization, not a proven optimum, and the labs say so. In practice you also weight sensors by cost and noise (command-line and AMSI are cheap and high-value; raw kernel ETW tracing is expensive), making it a weighted set cover.
The detection / output. The artifacts: a coverage table (detectable/partial/blind per technique), the highest-value missing sensor, the improvement plan, the residual-visibility findings for evadable sensors, and the Sigma + Sysmon changes that close the top gaps. That bundle is Meridian's detection-gap map.
Significance. This methodology is the whole phase as a process. Everything else — kernel callbacks, ETW-TI, hooks, AMSI, the boundary table — exists so you can fill in steps 2, 3, and 7 correctly. Master this and you can walk into any environment and produce a defensible visibility roadmap.
Common misconceptions.
- "Coverage is binary." It is three-state, and partial is the dangerous middle — it looks covered but a single evasion drops it.
- "Add the most sensors." Add the highest-gain sensor first; the plan is about marginal coverage per unit cost, not total sensors.
- "Detectable means a rule fires noise-free." Detectable means the data exists. Writing the tuned rule on it (Chapters 7-8) is the next, separate step.
Lab Walkthrough Guidance
Both labs are pure-Python, stdlib-only, deterministic, and reason over synthetic sensor metadata.
Run the reference first to see the target behavior, then fill in lab.py.
Lab 01 — EDR Telemetry-Gap Analyzer. Order of attack:
classify(technique_id, sensors)— the core. Return detectable when all required sensors are present, blind when none are (or the technique is unknown), partial otherwise. Getpresent/missingas tuples in required order. Everything else builds on this.coverage(...)— bucket every technique byclassify's state, sort the buckets, compute thescore(fraction detectable; 0.0 for empty).blind_techniques(...)— just the blind bucket.best_sensor_to_add(...)— for each missing candidate sensor, compute how many techniques flip to detectable; return the max (ties → lexicographically smallest sensor name, for determinism). ReturnNonewhen nothing helps.improvement_plan(...)— callbest_sensor_to_addgreedily, add it to the inventory, repeat untilNone. Recordcumulative_detectableeach step.
The lesson to feel: the recommendation is the kernel/ETW-TI sensors, because they unlock the techniques no user-mode sensor robustly covers.
Lab 02 — AMSI/ETW Residual-Visibility Mapper. Order of attack:
residual_visibility(behavior, evasions)— subtract the sensors the evasions blind from the behavior's observed set; split the residual into kernel vs user. The whole lab hinges on theEVASIONSmap blinding only user-mode sensors — verify that invariant in your head first.still_detectable(...)— True if any residual sensor remains OR any applied evasion is itself detectable. The second clause is the lesson: a fully-blinded behavior is still caught because the bypass is observable.residual_detection(...)— list surviving sensors kernel-first, attach each sensor's detection and each evasion's self-detection, and summarize. When no sensor survives but an evasion is detectable, the detection is on the evasion itself.
The test you should be able to predict before running: apply every user-mode evasion to a
kernel-observed injection — the kernel sensors remain, still_detectable is True.
Success Criteria
You understand this phase when you can, without notes:
- Draw the EDR's three parts and the boundary table, and place any sensor in it.
- For any technique, name the sensor that sees it most robustly and the boundary that sensor sits at.
- Explain, for ETW patch / ntdll unhook / direct syscalls / AMSI bypass: exactly what goes blind, exactly what survives, and the detection for the evasion itself.
- State and defend the invariant: user-mode evasions do not blind kernel sensors.
- Take a host's collection, produce the coverage classification, name the highest-value missing sensor, and sketch the improvement plan — and say why partial coverage is dangerous.
- Write a Sigma rule on a kernel sensor at the TTP level with FP tuning, and a Sysmon config change that turns on a missing data source — and say how you would verify it fires.
- Tell a robust detection from a brittle one and explain the difference in Pyramid-of-Pain and trust-boundary terms.
Passing the tests is necessary, not sufficient — the bar is being able to walk an interviewer through what each sensor sees, how each evasion blinds it, and the detection that fires regardless.
Common Mistakes and OPSEC Failures
- Saying "we evaded the EDR" without naming the sensor and boundary. The finding is the named sensor, the evasion, and the survivor — not the headline.
- Conflating ETW and ETW-TI. The single most common error: user-mode ETW is in-process and patchable; ETW-TI is kernel-emitted and is not.
- Believing a user-mode patch is invisible. RWX on
ntdll/amsi.dll, a duplicatentdll, a clean AMSI verdict on the test string — every bypass is itself a high-fidelity detection. - Treating "partial" coverage as covered. One missing data source in a multi-sensor technique is a brittle, single-evasion-from-blind state.
- Writing brittle, bottom-of-pyramid rules. A hash or a command-line string is defeated in seconds; key on the kernel-observed behavior.
- Improving logging without a methodology. "Log more" is not a plan; the gap analysis produces the ranked, gain-maximizing roadmap.
- OPSEC (engagement side): assuming the operator's evasion is silent. A mature SOC alerts on the evasion artifacts. In a report, the durable, embarrassing finding for the client is often "your EDR could have caught the bypass and didn't, because the data source was off."
Interview Q&A
Q1. Walk me through how a modern EDR builds its picture.
Three parts. A kernel-mode driver (ring 0) registers Windows notify callbacks — process-create,
image-load, thread-create, handle (ObRegisterCallbacks), file (minifilter), registry — and consumes
the kernel ETW-TI provider; because it is in the kernel, a user-mode payload cannot reach it. A
user-mode agent (ring 3) reads user-mode ETW sessions (CLR/DNS/WMI/PowerShell), places inline
hooks in ntdll to inspect syscall arguments, registers an AMSI provider for scripts, and ships
telemetry up. A cloud backend correlates across the fleet, scores, enriches with intel, and surfaces
to analysts. The key framing is trust boundaries: kernel and out-of-process sensors are
evasion-resistant; in-process sensors (ntdll hooks, user-mode ETW, AMSI) share the boundary with the
payload and can be blinded — which is why every robust detection keys on the lower boundary.
Q2. Which kernel callbacks does an EDR register, and why does kernel residency matter?
PsSetCreateProcessNotifyRoutineEx (process), PsSetLoadImageNotifyRoutine (image), PsSetCreate ThreadNotifyRoutine (thread, including remote), ObRegisterCallbacks (handle/object), FltRegister Filter (file), CmRegisterCallbackEx (registry). Residency matters because they fire from ring 0: a
user-mode payload cannot un-register or intercept another process's kernel callbacks. So even a payload
that has perfectly unhooked ntdll and patched ETW in its own process still trips the kernel callback
when it creates a remote thread or opens a handle to LSASS. The kernel callback is the survivor —
which is why my robust detections live there.
Q3. Explain ETW vs ETW-TI. If someone patches EtwEventWrite, what goes blind and what survives?
User-mode ETW providers (CLR, DNS, WMI, PowerShell) run in the observed process and route through
EtwEventWrite in ntdll. Patching it in the payload's own memory silences those user-mode
providers for that process — e.g. the CLR provider stops reporting in-memory .NET. It does not
touch ETW-TI, the kernel-emitted Threat-Intelligence provider (alloc/protect/inject, syscall
stacks), nor other processes, nor the kernel callbacks. And the patch is self-detecting: making
EtwEventWrite writable is an RWX protection change on ntdll .text, which ETW-TI reports; a CLR
process that emits no CLR events is an anomalous gap; an integrity check on the function's bytes catches
the modification. So the right answer is: user-mode ETW blind, ETW-TI + kernel callbacks survive, and
the patch itself is a detection.
Q4. How do ntdll hooks work, how do syscalls evade them, and what is the detection?
EDRs overwrite the first bytes of ntdll's Nt* stubs with a jmp to their code to inspect syscall
arguments before the kernel acts. Reaching the kernel without executing the hooked stub evades it:
direct syscalls assemble the syscall themselves; indirect syscalls jump to the real syscall
instruction inside clean ntdll past the hook; unhooking maps a clean ntdll over the hooked one.
All evade the user-mode hook only. The detections: a call-stack / return-address anomaly (the
syscall returns into private/unbacked memory instead of ntdll, visible to ETW-TI stack walks); a
duplicate ntdll image map; hook-integrity checks; and the kernel consequence — the memory
op or remote thread still fires the kernel callback and ETW-TI. So I never key a detection on the hook
firing; I key on the call stack and the kernel event.
Q5. Where does AMSI sit, what does it scan, and if it's bypassed, how is that detectable?
AMSI (amsi.dll, AmsiScanBuffer) sits in-process and receives the de-obfuscated buffer from
PowerShell, WSH, Office VBA, and in-memory .NET assembly loads — content as it will execute, defeating
on-disk obfuscation. Because it is in-process it can be blinded (force a clean result / neutralize the
context). But the bypass is one of the most reliably detected acts in the phase: the AMSI test string
should always come back detected, so a clean verdict on a known-bad buffer is tamper; forcing the result
usually patches amsi.dll .text (RWX/write, visible to ETW-TI); and PowerShell script-block logging
(4104) is a separate sensor that keeps firing — the de-obfuscated script is still logged. So AMSI
bypass blinds one sensor, and a sibling sensor plus the bypass artifact still catch it.
Q6. A technique "evaded the EDR." How do you reason about what was actually blinded?
I refuse the headline and decompose it. Which sensor saw this behavior on a fully-instrumented host?
Which boundary is it at? Which specific evasion was used, and which sensor does it blind — and is that
sensor in-process (blindable) or kernel/out-of-process (not)? Then: which sensor survives, and is the
evasion itself observable? That is exactly Lab 02's residual_visibility / still_detectable /
residual_detection. The output is "user-mode ETW and the ntdll hook were blinded; the kernel callback
and ETW-TI survived and saw the injection; and the ETW patch is detectable as RWX on ntdll." A finding,
not a shrug.
Q7. What is the Pyramid of Pain and how does it judge a rule you wrote?
It ranks indicators by cost-to-change: hashes (trivial) → IPs → domains → host/network artifacts → tools
→ TTPs (very hard). A rule keyed low on the pyramid (a hash, an AMSI string, a filename) dies the moment
the adversary recompiles or renames; a rule keyed on a TTP — the behavior's irreducible kernel
consequence, like a non-system process opening LSASS with read rights — survives. Robustness comes from
the trust boundary: TTP-level rules sit on kernel/out-of-process sensors the payload can't reach. I
judge my own rules by where they sit and try to build at the tip.
Q8. Walk me from a telemetry gap to a closed gap. Enumerate the threat-model techniques; map each to required data sources; inventory what the host collects; classify detectable/partial/blind; find the single missing sensor that recovers the most (say, ETW-TI or command-line logging); produce the ranked improvement plan; for covered-but-evadable sensors, do residual analysis to find hidden gaps. Then close one: change the Sysmon config to turn on the data source, write the Sigma rule on the kernel sensor at the TTP level with FP tuning, and verify it fires by running a benign Atomic test and confirming the alert — with no false positive on normal activity. That loop, with the artifacts, is the consulting deliverable.
References
Microsoft primary docs
- Kernel notify routines:
PsSetCreateProcessNotifyRoutineEx,PsSetLoadImageNotifyRoutine,PsSetCreateThreadNotifyRoutine,ObRegisterCallbacks,CmRegisterCallbackEx,FltRegisterFilter(Windows Driver Kit /ntddk.hreference on learn.microsoft.com). - Event Tracing for Windows (ETW) and the Microsoft-Windows-Threat-Intelligence (ETW-TI) provider documentation.
- AMSI (Antimalware Scan Interface) developer documentation, including the AMSI test sample.
- WDAC (Windows Defender Application Control) and the Microsoft vulnerable-driver blocklist.
EDR internals and detection research
- Elastic Security Labs posts on EDR internals, ETW-TI, and call-stack-based detection.
- CrowdStrike and Microsoft engineering blogs on kernel callbacks and in-memory detection.
- Windows Internals (Russinovich, Solomon, Ionescu) — kernel notifications and ETW chapters.
- Windows APT Warfare — Sheng-Hao Ma (PE format, loaders, native internals).
Detection engineering
- The Sigma specification and rule repository (SigmaHQ).
- Sysmon documentation and configs: SwiftOnSecurity/sysmon-config and Olaf Hartong sysmon-modular.
- David Bianco — The Pyramid of Pain.
- MITRE ATT&CK data sources / data components; CTID Sensor Mappings to ATT&CK; MITRE D3FEND.
- Atomic Red Team (benign technique tests) and SilkETW / Sealighter(-TI) for ETW(-TI) consumption.
Every concept above is presented for authorized detection engineering and emulation. The labs and guides contain no working bypass, no unhooking code, and no deployable evasion — only models that turn "what could blind us" into "the detection that fires regardless."