This project is an ongoing exploration of the Windows Endpoint Security Platform (WESP, or ESP), introduced in Windows 11 Insider Preview builds (25H2, builds 10.0.29641 to 10.0.29667). WESP changes how Windows handles endpoint security by shifting away from traditional, synchronous minifilter round-trips toward in-kernel decision graphs and decoupled asynchronous telemetry.
Using AI-assisted reverse engineering, the full platform stack has been analyzed and documented across user and kernel space:
wesp.sys: The core filesystem minifilter driver, executive callbacks, and in-kernel decision engine.espclient.dll: The user-mode client library, export catalog, and BDD compilation pipeline.wesp_elam.sys: The early-boot sensor and shared telemetry staging queue.
To explore and test these internals firsthand, the repository includes esptool, a standalone C++20 research harness, accompanied by a test corpus of 118 XML rule documents to exercise the platform's APIs, event types, and enforcement gates.
Documentation Index
The technical findings, specifications, and tooling are documented across three core references:
- WESP Reverse-Engineered Architecture and Technical Reference: Detailed reverse engineering analysis of the kernel driver architecture (
wesp.sys), Filter Manager communication port wire protocols, the in-kernel Reduced Ordered Binary Decision Diagram (ROBDD) evaluation engine, disposition tables, security gates, and early-boot synchronization (wesp_elam.sys). - Esptool Architectural Reference: Complete technical reference for the research harness, detailing operational planes, the 36-command execution reference with parameter arities and sequences, live diagnostic scenarios, trust scaffolding, and the automated smoke test runner.
- Esptool Declarative XML Rule Specification and Reference: Full specification for esptool's declarative XML rule format, test fixtures, and XmlLite parser. Covers the XML grammar, 47-item error catalog, element schemas, action selectors, kernel disposition codes, relational and numeric operators 1 through 11, complete 15-family property catalogs, and the 118-document test corpus.
What is WESP?
In traditional Windows endpoint security, third-party filesystem minifilter drivers pair with user-mode service daemons. When an I/O request occurs (such as an NtCreateFile call), the minifilter intercepts the request in a pre-operation callback, calls FltSendMessage, and suspends the calling application thread while waiting for the user-mode service to return an authorization verdict via FilterReplyMessage.
This synchronous design causes latency spikes on I/O paths, introduces deadlock risks during memory-mapped paging operations, and leaves host responsiveness vulnerable to service hangs or crashes.
WESP eliminates synchronous user-mode round-trips through an asymmetric architecture: user space compiles detection rules into binary decision graphs, and the kernel evaluates them directly during pre-operation callbacks.
The architecture operates across three functional planes:
- Control Plane: Operates over the Filter Manager communication port
\EspFilterPort. It authenticates security agents, manages sessions and client registrations, and transmits pre-compiled rule sets from user space into the kernel. - Data Plane: Intercepts operations across five kernel subsystems (Process Manager, Object Manager, Configuration Manager, Filter Manager, and Kernel Transaction Manager) across 47 event types. The kernel driver (
wesp.sys) evaluates rules directly within pre-operation callbacks. If an enforcing rule matches, the callback returns an immediate blocking status (FLT_PREOP_COMPLETEwithSTATUS_NOT_FOUND, orPS_CREATE_NOTIFY_INFO.CreationStatusdenial) before the I/O ever reaches the filesystem or process manager. - Telemetry Plane: Mediates non-blocking event transfer. For observation rules, the driver enqueues event envelopes into memory-budgeted kernel queues. User-mode listeners retrieve events asynchronously through overlapped reads without suspending monitored applications. Telemetry completion calls serve only as memory quota releases rather than synchronous authorization replies.
For complete architectural diagrams, callback listings, and wire protocol details, see docs/wesp_re_tech_reference.md.
Repository Layout
| Directory / File | Contents |
|---|---|
docs/ |
Comprehensive technical references for WESP kernel architecture and esptool. |
esptool/ |
Source code and Visual Studio solution for the C++20 research harness. |
rules/ |
Corpus of 118 declarative XML test rule documents used by esptool. |
smoke/ |
Automated PowerShell test runner (Run-EsptoolSmoke.ps1) covering 312 smoke test cases. |
Installation and Environment Setup
Building esptool
The harness is written in C++20 and targets x64 Windows. Build the project using Visual Studio 2022 or MSBuild:
msbuild esptool\esptool.vcxproj -p:Configuration=Release -p:Platform=x64
Build specifications:
- Platform: x64 only.
- Language Standard: ISO C++20 (
/std:c++20). - Compiler Flags:
/W4 /WX /guard:cf /permissive-. - Runtime Library: Statically linked CRT (
/MTfor Release,/MTdfor Debug) ensuring the binary has no external dependencies onvcruntime140.dllormsvcp140.dll. - SDK Dependencies:
XmlLite.lib,Shlwapi.lib,Bcrypt.lib. - Output Artifact:
esptool\x64\Release\esptool.exe.
Target Machine Setup
WESP components ship in Windows 11 Insider Preview builds (such as builds 10.0.29641 to 10.0.29667). To prepare a virtual machine for research:
- Enable test signing and reboot:
bcdedit /set {current} testsigning on shutdown /r /t 0 - Verify that test signing is active:
bcdedit /enum {current} - Load the WESP minifilter driver:
fltmc load wesp - Verify that the filter is running at altitude
329500:fltmc filters
Research Workflows with esptool
The esptool harness and its accompanying XML rule corpus enable hands-on validation of the WESP subsystem across its control, data, and telemetry planes. Execute these workflows from an elevated PowerShell prompt within the directory containing esptool.exe and rules\ (or the repository root).
Operational notes:
- Automatic test-signing adaptation: In standard test-signed lab environments where
esptoolruns without an AM-PPL signature, the tool automatically detects the environment and bypasses token attribute stamping. Manual--no-provisionflags are optional. To disable auto-detection and restore traditional behavior, pass--no-auto-provision. - Selective worker hopping: Commands that interact with the driver hop to a same-image child worker running as
NT AUTHORITY\SYSTEMwithSeTcbPrivilegeonly when token stamping or cleanup is required, or when forced via--force-hop. When running directly as a worker,--workersuppresses further hops. - Diagnostic tracing: Diagnostic tracing is enabled by appending
--log <path>combined with--verbose. - Event delivery mechanism: Asynchronous notification retrieval defaults to completion callbacks; supply
--iocpto pump notifications viaEspConnectEventQueueWithIocp.
Sanity Check: DLL and Trust State
Verify that espclient.dll loads, confirm export resolution, and inspect the PPL protection byte and WESP://Permission token security attribute:
esptool.exe status
esptool.exe exports
Execution breakdown:
esptool.exe statusdisplays the loaded DLL path, total export resolution count (120 of 121), process elevation, TCB privilege presence, token attribute state, and system code integrity policy:dll: espclient.dll resolved 120 of 121 account: SYSTEM elevated: yes tcb: enabled protection: 0x00 (None, None, audit=0) attribute: absent (query 0x00000000) codeintegrity: options=0x00280303 testsigning=on ci=on secureboot: offesptool.exe exportslists all 121 cataloged exports, their parameter arities, and whetherGetProcAddresssucceeded. On reference builds, 120 exports resolve successfully (_DllMainCRTStartupis cataloged but unresolved).
Live Monitor: Any Process Creation
Deploy a queue-backed rule to capture all process creation activity system-wide without applying filtering predicates. Under test-signing environments, esptool automatically enables un-provisioned mode:
esptool.exe monitor --rules rules\monitor_process_create.xml --duration 10000 --max 10
Rule specification (rules\monitor_process_create.xml):
<?xml version="1.0" encoding="utf-8"?>
<esptool>
<client name="esptool-mon-proc" altitude="385000"/>
<rules>
<rule name="process-create" event="ProcessCreate" eventType="1000"
lifetime="transient" action="0"/>
</rules>
</esptool>
Testing and verification:
- In a second PowerShell window, launch an application while the monitor pumps:
notepad.exe - The primary console receives the decoded notification with all process properties:
[INFO ] auto-provision: test-signing active on non-PPL process; automatic --no-provision selected [INFO ] monitor: rules installed, pumping [INFO ] notification 1 id=1 kind=3277015211 payload=0 event=1000 pid=4812 tid=0 name=\Device\HarddiskVolume3\Windows\System32\notepad.exe prop[Process:6] (ProcessId, type 5): 4812 (0x12CC) prop[Process:20] (ImagePath, type 8): \Device\HarddiskVolume3\Windows\System32\notepad.exe prop[Process:1] (CommandLine, type 8): "C:\Windows\System32\notepad.exe" prop[Process:2] (SessionId, type 10): 0x1DD455AE1AADB2C [INFO ] notification pump received 1 received 1 notifications - Exit code behavior: The command exits with code
0only if at least one notification arrives within the duration window. If zero notifications arrive, the command exits with code1.
Filtered Monitor: Targeted Process Matching
Deploy a rule configured with an EspCreateProcessFilter predicate to isolate execution of cmd.exe while ignoring other processes:
esptool.exe monitor --rules rules\filter_process_create_process_ntpath.xml --duration 15000 --max 5
Rule specification (rules\filter_process_create_process_ntpath.xml):
<?xml version="1.0" encoding="utf-8"?>
<esptool>
<client name="esptool-filt-ntpath" altitude="410000"/>
<rules>
<rule name="proc-nt-cmd" event="ProcessCreate" eventType="1000"
lifetime="transient" action="1">
<filter type="10" comparand="1" property="1"
value="$nt:C:\Windows\System32\cmd.exe"/>
</rule>
</rules>
</esptool>
Filter mechanics:
filter type="10"invokesEspCreateProcessFilter.property="1"selects the process image path.comparand="1"enforces an exact string equality comparison.- Value
$nt:C:\Windows\System32\cmd.exeis automatically expanded to the NT device namespace path (\Device\HarddiskVolumeN\Windows\System32\cmd.exe).
Testing and verification:
- In a second window, launch
notepad.exe:
No notification arrives; the monitor remains silent.notepad.exe - In the second window, launch
cmd.exe:
A matchingcmd.exe /c echo testevent=1000notification is captured and decoded immediately.
Named Pipe Monitor: Browser and Programmatic IPC Triggers
Deploy a queue-backed rule to capture Named Pipe creation (PIPE_CREATE / 5000). Unlike process creation telemetry, the kernel Named Pipe callback fully populates the process property bag (pid), thread property bag (tid), and FileObject name bag (name):
esptool.exe monitor --rules rules\monitor_pipe_mailslot.xml --duration 20000 --max 10
Rule specification (rules\monitor_pipe_mailslot.xml):
<?xml version="1.0" encoding="utf-8"?>
<esptool>
<client name="esptool-ipcobj" altitude="385000"/>
<rules>
<rule name="pipe-create" event="PipeCreate" eventType="5000"
lifetime="transient" action="0"/>
<rule name="mailslot-create" event="MailslotCreate" eventType="6000"
lifetime="transient" action="0"/>
</rules>
</esptool>
Testing and verification:
- Method A (Browser IPC Trigger): Launch a Chromium-based browser such as Microsoft Edge or Google Chrome. Modern multi-process browsers establish Windows Named Pipes immediately upon startup for Mojo IPC broker communication (
\Device\NamedPipe\mojo.*) and crash reporting (\Device\NamedPipe\crashpad_*):Start-Process msedge.exe -ArgumentList "about:blank" - Method B (Programmatic PowerShell Trigger): To create a deterministic, isolated Named Pipe server stream without launching external applications:
$pipe = [System.IO.Pipes.NamedPipeServerStream]::new('esptool-live-pipe', [System.IO.Pipes.PipeDirection]::InOut, 1) $pipe.Dispose() - Decoded notification output:
Field analysis:[INFO ] auto-provision: test-signing active on non-PPL process; automatic --no-provision selected [INFO ] monitor: rules installed, pumping [INFO ] notification 1 id=1 kind=1014787250 payload=0 event=5000 pid=4812 tid=6412 name=\Device\NamedPipe\mojo.4812.5120.12847102948102 prop[Process:6] (ProcessId, type 5): 4812 (0x12CC) prop[Process:20] (ImagePath, type 8): \Device\HarddiskVolume3\Program Files (x86)\Microsoft\Edge\Application\msedge.exe prop[Process:1] (CommandLine, type 8): "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" --single-argument about:blank prop[Process:2] (SessionId, type 10): 0x1DD4552F1242DBD prop[Thread:1] (ThreadId, type 5): 6412 (0x190C) prop[Pipe:1] (PipeName, type 8): \Device\NamedPipe\mojo.4812.5120.12847102948102 [INFO ] notification 2 id=2 kind=1014787250 payload=0 event=5000 pid=6124 tid=6128 name=\Device\NamedPipe\esptool-live-pipe prop[Process:6] (ProcessId, type 5): 6124 (0x17EC) prop[Process:20] (ImagePath, type 8): \Device\HarddiskVolume3\Windows\System32\WindowsPowerShell\v1.0\powershell.exe prop[Pipe:1] (PipeName, type 8): \Device\NamedPipe\esptool-live-pipe [INFO ] notification pump received 2 received 2 notificationsevent=5000: Sparse identifier forPIPE_CREATE.pidandtid: Populated from the process and thread property bags at parameter block offsets+16and+8.name: Populated from the FileObject property bag as a boxedUNICODE_STRING(type8), displaying the full NT device namespace pipe path.
Native Deny: Block File Path
Deploy an enforcing rule to prevent file creation at a specific path. This enforcement path requires no binary modifications and operates natively on unmodified builds:
esptool.exe rules --rules rules\deny_file_create.xml --duration 15000
Rule specification (rules\deny_file_create.xml):
<?xml version="1.0" encoding="utf-8"?>
<esptool>
<client name="esptool-deny-file" altitude="820300"/>
<rules>
<rule name="deny-file-create" event="FoCreate" eventType="2000"
lifetime="transient" action="deny">
<filter type="6" comparand="1" property="1"
value="$nt:C:\tools\esptool-deny\file_target.txt"/>
</rule>
</rules>
</esptool>
Testing and verification:
- While
esptoolprintsrules-hold 15000, attempt to create the target file in a second window:- In
cmd.exe:
The operation fails with:echo a > C:\tools\esptool-deny\file_target.txtElement not found. - In PowerShell:
The operation fails withNew-Item C:\tools\esptool-deny\file_target.txt -ItemType FileThe system cannot find the file specified(STATUS_NOT_FOUND/0xC0000225/ Win321168ERROR_NOT_FOUND). Filter Manager returnsFLT_PREOP_COMPLETEduring pre-create, blocking the IRP before it reaches the filesystem driver.
- In
- Attempt to create a file with a non-matching name:
The creation succeeds normally.New-Item C:\tools\esptool-deny\other_file.txt -ItemType File
Returning Access is Denied (Custom Enforcing Dispositions)
By default, an enforcing deny rule on FoCreate (2000) selects disposition slot 2 in the kernel driver's Filesystem/KTM disposition table, completing pre-create with STATUS_NOT_FOUND (0xC0000225).
To return Access is denied. (STATUS_ACCESS_DENIED / 0xC0000022 / Win32 error 5) instead, specify disposition="access_denied" (or disposition="denied", or numeric modifyKind="2") on the <rule> element:
<?xml version="1.0" encoding="utf-8"?>
<esptool>
<client name="esptool-deny-file" altitude="820300"/>
<rules>
<rule name="deny-file-create" event="FoCreate" eventType="2000"
lifetime="transient" action="deny" disposition="access_denied">
<filter type="6" comparand="1" property="1"
value="$nt:C:\tools\esptool-deny\file_target.txt"/>
</rule>
</rules>
</esptool>
Testing with disposition="access_denied":
C:\tools\esptool-deny>echo a > file_target.txt
Access is denied.
Supported disposition values across the kernel Filesystem/KTM disposition table:
disposition="access_denied"(ormodifyKind="2"): Slot 1 maps toSTATUS_ACCESS_DENIED(0xC0000022/Access is denied.).disposition="not_found"(default, ormodifyKind="3"): Slot 2 maps toSTATUS_NOT_FOUND(0xC0000225/Element not found./The system cannot find the file specified.).disposition="virus"(ormodifyKind="1"): Slot 0 maps toSTATUS_VIRUS_INFECTED(0xC0000906/Operation did not complete successfully because the file contains a virus or potentially unwanted software.).
Compatibility Deny: Block Process Execution
Deploy an enforcing rule to block process execution by path. Because an unmodified espclient.dll refuses the enforcing descriptor for PROCESS_CREATE (1000), this workflow requires the --enforce-compat in-memory patch:
esptool.exe rules --rules rules\deny_process_create.xml --enforce-compat --duration 20000
Rule specification (rules\deny_process_create.xml):
<?xml version="1.0" encoding="utf-8"?>
<esptool>
<client name="esptool-deny-proc" altitude="820330"/>
<rules>
<rule name="deny-process-create" event="ProcessCreate" eventType="1000"
lifetime="transient" action="deny">
<filter type="10" comparand="1" property="1"
value="$nt:C:\tools\esptool-deny\proc_target.exe"/>
</rule>
</rules>
</esptool>
Testing and verification:
esptoollogs the patch confirmation and holds the session active:[INFO ] auto-provision: test-signing active on non-PPL process; automatic --no-provision selected [WARN ] enforce-compat: rule 'deny-process-create' installed via in-memory client patch; confirm live denial with canary trigger rules-hold 20000- While the rule holds, attempt to execute
proc_target.exein a second window:
The process launch fails withStart-Process C:\tools\esptool-deny\proc_target.exeElement not found.(STATUS_NOT_FOUND/0xC0000225/ Win321168ERROR_NOT_FOUND). The kernel driver process creation callback writes this status directly intoPS_CREATE_NOTIFY_INFO.CreationStatus, aborting initialization before user-mode code executes. - Launching any other executable image proceeds without interference.
Live Kernel Object References: Minting and Inspection
WESP exposes an object reference subsystem allowing security clients to obtain kernel object handles across intercepted executive entities (process, file, registry, thread, desktop, token), inspect runtime identity metadata, and query property buffers:
esptool.exe refs process --pid self --properties 6,20,1,2
esptool.exe refs file --path C:\Windows\System32\ntdll.dll --properties 1,6
esptool.exe refs registry --path HKLM\Software --properties 1,2
Internal execution sequence:
esptoolconnects to\EspFilterPortand invokes the subsystem-specific reference constructor (EspCreateProcessReference,EspCreateFileReferenceByPath, orEspCreateRegistryKeyReference).- The driver returns an opaque reference handle.
esptoolcallsEspGetEventObjectFromReferenceto unwrap the internal event object view. EspGetEventObjectTypereturns the executive object type code (2for Process,3for File,8for Registry Key).EspGetEventObjectIdreturns the unique kernel-assigned 64-bit object identifier.- If
--propertiesis specified,esptoolallocates memory and calls the corresponding query export (EspQueryProcessPropertiesorEspQueryRegistryKeyProperties) to retrieve property data.
Command output:
ref created kind=process
event-object-type 2
event-object-id 1730
query EspQueryProcessProperties 0x00000000 buffer=0000008000537EC0
ok EspRegisterClient 0x00000000 S_OK
ok EspConnectClient 0x00000000 S_OK
ok EspCreateProcessReference 0x00000000 S_OK
ok EspGetEventObjectFromReference 0x00000000 S_OK
ok EspGetEventObjectType 0x00000000 S_OK
ok EspGetEventObjectId 0x00000000 S_OK
ok EspQueryProcessProperties 0x00000000 S_OK
In-Kernel Collections and Property Capability Probing
WESP supports in-kernel collections that maintain dynamic sets of strings, integers, or binary blobs evaluated directly by in-kernel ROBDD decision graphs. esptool provides commands to create, update, and inspect collections, as well as probe whether specific property IDs are supported across platforms:
esptool.exe collections --type 2 --lifetime 1
esptool.exe query --kind process --properties 6,20,1,2 --supported 6
Collection mechanics:
collections --type 2 --lifetime 1creates a string collection (type 2) with transient lifetime (lifetime 1).EspCreateCollectionreturns a collection GUID.EspUpdateCollectiontransmits new entries across\EspFilterPort, expanding$nt:prefixes to full NT device namespace paths.EspEnumerateCollectionEntriesverifies that entries are active in non-paged pool memory.
Query probing mechanics:
--supported 6callsEspIsProcessPropertySupported(6), probing whetherProcessId(6) is recognized by the kernel driver on the active Windows build. The export returns1for supported.EspQueryProcessPropertiesreads the property buffer for the requested property identifiers.
Command output:
collection-id {391d246d-f7bc-dd43-894a-0ab429fa8dad}
collection-type 2
collection-entries 1
ok EspRegisterClient 0x00000000 S_OK
ok EspConnectClient 0x00000000 S_OK
ok EspCreateCollection 0x00000000 S_OK
ok EspUpdateCollection 0x00000000 S_OK
ok EspGetCollectionId 0x00000000 S_OK
ok EspGetCollectionType 0x00000000 S_OK
ok EspEnumerateCollectionEntries 0x00000000 S_OK
query EspQueryProcessProperties 0x00000000 buffer=000000DF3DE666E0
property-supported process 6 1
ok EspRegisterClient 0x00000000 S_OK
ok EspConnectClient 0x00000000 S_OK
ok EspCreateProcessReference 0x00000000 S_OK
ok EspGetEventObjectFromReference 0x00000000 S_OK
ok EspQueryProcessProperties 0x00000000 S_OK
ok EspIsProcessPropertySupported 0x00000000 S_OK
In-Kernel Rule Persistence and Client Lifecycle Management
Standard WESP monitoring sessions use transient rules (lifetime="transient", FFI 1) that the kernel driver automatically removes when the user-mode client disconnects. To enforce policies across client restarts or validate tamper resistance, WESP supports persistent rules (lifetime="persistent", FFI 3) stored in kernel memory and the system registry:
esptool.exe persist-rules --rules rules\persist_process_create_empty_deny.xml
esptool.exe clients
esptool.exe enum-rules
esptool.exe unregister --all
Operational lifecycle:
- Persistent Rule Deployment (
persist-rules): Parses the rule specification, overrides rule lifetime to persistent (3), and installs rules without establishing a user-mode queue. The driver persist store stores the rules and maintains client state across process termination. - Client Discovery (
clients): InvokesEspEnumerateRegisteredClientsto query all active WESP registrations across the system, displaying the persistent client GUID. - Rule Enumeration (
enum-rules): CallsEspEnumerateRuleIdsto inspect active rule counts broken down by FFI lifetime (lifetimes 1 through 4). - Teardown (
unregister --all): InvokesEspUnregisterClienton every discovered client GUID, releasing kernel non-paged quota, clearing BDD decision graphs, and removing persistent registry entries.
Command output:
persist-rules 1
ok EspRegisterClient 0x00000000 S_OK
ok EspConnectClient 0x00000000 S_OK
ok EspCreateRule:persist-create 0x00000000 S_OK
ok EspUpdateRules 0x00000000 S_OK
ok EspEnumerateRuleIds 0x00000000 S_OK
registered-clients 1
client {e43fe653-c426-064d-8811-8b722a964d65}
ok EspEnumerateRegisteredClients 0x00000000 S_OK
unregistered 1
ok EspEnumerateRegisteredClients 0x00000000 S_OK
ok EspUnregisterClient 0x00000000 S_OK
Where to Learn More
For complete technical specifications, consult the dedicated documentation files:
- Command and CLI Reference: For full syntax, options, and diagnostic trace details for all 36 commands, see docs/esptool_tech_reference.md.
- Kernel Architecture and Internals: For in-depth analysis of
wesp.sys, callback interception, Filter Manager port wire protocols, BDD graph layouts, and disposition tables, see docs/wesp_re_tech_reference.md. - Rule Authoring and Property Space: For XML schema attributes, combinators, relational operators, and property ID catalogs across all 15 executive families, see rules/README.md.
Comments