core-jmp core-jmpdeath of core jump

A First Look Inside the Windows Endpoint Security Platform (WESP) on Insider 29661

Jonathan Johnson’s first public RE of WESP on Insider 29661: wesp.sys + espclient.dll, EspFilterPort, WESP://Permission and AM-PPL, process-create queues, a working consumer POC, and Microsoft.Windows.WESP.Client ETW.

oxfemale September 16, 2026 18 min read 138 reads
Export PDF
A First Look Inside the Windows Endpoint Security Platform (WESP) on Insider 29661
Original text: "A First Look Inside the Windows Endpoint Security Platform"Jonathan Johnson, jonny-jhnson.dev (11 September 2026; 11 min read). Figures and trimmed listings follow the source. Snippets are not 1:1 with the binaries (author’s note). POC: WespConsumerPOC.
A kernel cage around a small driver, user-mode workers outside
The point of WESP: Microsoft keeps the kernel collector; vendors detect in a normal process.
WESP architecture: driver, port, client, queues, ETW
Microsoft produces. Vendors consume. Preview build 29661.

Executive Summary

On 11 September 2026 Jonathan Johnson published the first public reverse-engineering look at the Windows Endpoint Security Platform (WESP) as it shipped in Windows Insider Preview build 29661: wesp.sys (kernel minifilter) and espclient.dll (user-mode client). No public SDK yet; symbols were enough for a working consumer POC. The political context is the CrowdStrike outage of July 2024, Microsoft’s Windows Resiliency Initiative (November 2024) and the June 2025 update that promised a private preview. CrowdStrike worked with Microsoft; Alex Ionescu showed a Fal.Con implementation. Johnson’s post is the public binary peek: how consumers talk to the driver over \EspFilterPort, who is allowed to connect (WESP://Permission and Antimalware PPL), how process-create events flow through a rule engine into a user-mode queue, a POC that actually prints events, and a TraceLogging provider in the client DLL.

A crash in the user-mode consumer does not, by itself, bring down the box which was the ultimate goal of this project.

Jonathan Johnson, 11 September 2026
Kitchen table: For twenty years every EDR put a detective in the kernel. One detective’s bad update (CrowdStrike, July 2024) locked the whole building. Microsoft’s answer is: they keep the only kernel badge, bag every interesting event, and hand bags to detectives who now sit in the lobby. If a detective faints, the elevators still run.
For operators: Preview only. Build 29661. APIs will move. Johnson says not to treat this as the final how-WESP-works guide. Test signing on this build lets an elevated non-PPL process connect — by design for developers, not a production hole you should assume will last.

Introduction: after CrowdStrike, Microsoft keeps the kernel

The July 2024 CrowdStrike kernel-driver outage made “should vendors still ship kernel components?” a Microsoft question, not a conference question. WRI’s pitch was more capability outside the kernel without stripping vendors. Private preview to partners the month after the June 2025 blog. CrowdStrike in the room. Ionescu’s Fal.Con talk had a working WESP consumer. Build 29661 is the first public drop of the two binaries. No SDK. Symbols. Johnson reversed enough of both to register a client and receive process-creation events. Code snippets in the post are trimmed on purpose — not 1:1 with the binaries.

This draft keeps every original figure and listing in order, then adds the kitchen picture, a permission-class table, and what hunters should log once this leaves Insider.

WESP architecture: kernel and user-mode

Consumer/producer. Microsoft owns the producer: wesp.sys, a driver/minifilter. Vendors own consumers: they register, create an event queue, install rules whose actions point at that queue, and get matching events on callbacks in a normal process. The driver still registers the OS callbacks and packages data. Parsing, detection, and response live in user mode. Driver-quality burden shifts to Microsoft. A consumer crash is not a bugcheck.

The Esp prefix is everywhere. Exports already look like an SDK that has not been published:

WESP API exports from espclient.dll
WESP API exports. Source: original article.

Johnson got a minimal POC working from those exports plus symbols and an AI assistant. That is the rest of the post.

PieceModeJob
wesp.sysKernel minifilterCollect, evaluate rules, deliver via filter port
espclient.dllUser-modeRegister, queue, rules, FilterGetMessage, ETW
\EspFilterPortFilter comm portUp to 512 connections; ACL + WESP://Permission
PersistedStoreRegistryHKLM\…\Services\wesp\PersistedStore\Clients\<guid>
Preview 29661 as Johnson mapped it.

How consumer and producer talk

wesp.sys creates a filter communication port named EspFilterPort with FltCreateCommunicationPort, max 512 connections — the same pattern minifilters have used for years:

UNICODE_STRING portName = RTL_CONSTANT_STRING(L"\\EspFilterPort");

FltBuildDefaultSecurityDescriptor(
    &securityDescriptor,
    FLT_PORT_ALL_ACCESS);

InitializeObjectAttributes(
    &objectAttributes,
    &portName,
    OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
    nullptr,
    securityDescriptor);

status = FltCreateCommunicationPort(
    Filter,
    &ServerPort,
    &objectAttributes,
    ServerContext,
    fltmgr::connect_callback,
    fltmgr::disconnect_callback,
    fltmgr::message_notify_callback,
    512);

Connect/disconnect/message callbacks. New connections hit fltmgr::connect_callback. FilterSendMessage from user mode hits message_notify_callback.

Who is allowed to connect?

The port has an ACL. FltBuildDefaultSecurityDescriptor with FLT_PORT_ALL_ACCESS still means administrators and SYSTEM on the default SD. After that, wesp::server::connect reads the caller’s primary token for a security attribute named WESP://Permission. It is not a boolean. It is one of two unsigned 64-bit values, shown in decimal:

WESP://Permission (decimal)Test signing off
10000000Does not require Antimalware PPL
1000000000Requires Antimalware PPL (protection level 0x31)
Permission class, not a yes/no flag. Source: original article.

For 1000000000, WESP queries ProcessProtectionInformation and checks Antimalware PPL (0x31). Familiar: a lot of vendor capability on Windows is already PPL-gated. On this preview build with test signing enabled, Johnson connected from an elevated test process without the token attribute and without AM-PPL. Port ACL still applied (admin/SYSTEM). He reads that as a developer convenience Microsoft left in on purpose.

After checks, server::connect reads context from espclient.dll: registration, unregistration, management, or event-queue delivery. That state is what disconnect and message callbacks use later.

Kitchen table: The slot in the sorting-room wall has a lock (ACL). Then a clerk looks at your badge sticker. Sticker 10,000,000 means “vendor intern, elevated.” Sticker 1,000,000,000 means “Antimalware PPL, the real uniform.” On the Insider machine with test signing, Johnson walked up as an admin without a sticker and they let him in so he could write the POC. Do not assume production will.

When do these checks happen?

On every new connection to \EspFilterPort: register, unregister, enumerate — each opens a short-lived connection. EspConnectClient opens the longer management connection and pays the check once. Later FilterSendMessage on that handle does not re-query token or PPL; the driver checks whether that connection may perform the operation. Event receive is a second connection via EspConnectEventQueueWithCallback, its own check, then FilterGetMessage. Individual messages do not re-check. Close and reopen, checks run again.

For operators: If you are attacking the preview: the interesting window is test-signing + admin without AM-PPL. If you are defending production: assume AM-PPL + the 1e9 permission class, and treat a non-PPL connector as a finding. Do not copy the Insider exception into a threat model.

How events move from producer to consumer

The product pitch: EDR-like visibility without each vendor shipping another kernel driver. WESP collects process, thread, image load, file, registry, handle — then sends events that match a client’s rules to user mode.

Process creation as the walkthrough. Init registers create_process_notify with PsSetCreateProcessNotifyRoutineEx2:

status = PsSetCreateProcessNotifyRoutineEx2(
    0,
    create_process_notify,
    FALSE);

Non-null PS_CREATE_NOTIFY_INFO is create; null is terminate. Metadata goes into the rule engine:

if (CreateInfo != nullptr)
    RuleEngine::process_event<ProcessCreate>(..., &event);
else
    RuleEngine::process_event<ProcessTerminate>(..., &event);

Before delivery: EspCreateEventQueue → message_notify_callback → ClientObject::create_event_queue. Then EspConnectEventQueueWithCallback with client and queue GUIDs. WESP starts a delivery worker with PsCreateSystemThread waiting on a KEVENT via KeWaitForSingleObject when empty.

Consumer: EspAllocateEventNotification, EspArmEventNotification — posts async FilterGetMessage and waits. On process create, matching ProcessCreate rules name a queue. EventQueue::queue_async_notification_internal enqueues and signals the KEVENT. Worker dequeues and FltSendMessage into the pending FilterGetMessage. Callback runs, EspCompleteEventNotification, rearm.

WESP consumer and producer event flow diagram
WESP consumer and producer event flow. Source: original article.
A queue ticket dispenser next to a rule book
Kitchen: the rule is not the callback. The rule is a ticket that says which queue. The callback sits on the queue’s window.

Building a WESP consumer POC

No SDK. espclient.dll exports were enough. Johnson published WespConsumerPOC: register, queue, ProcessCreate rule, enough output to prove events arrived. Full ProcessCreate layout still unknown, so not all metadata is printed. Everything goes through the DLL — register/clients/remove and monitor. The DLL owns \EspFilterPort.

.\wesp-consumer.exe register
.\wesp-consumer.exe clients
.\wesp-consumer.exe monitor "{CLIENT-GUID}" 60
.\wesp-consumer.exe remove "{CLIENT-GUID}"

Once registered, monitor:

  1. EspConnectClient — existing registered client.
  2. EspCreateEventQueue — kernel queue.
  3. EspConnectEventQueueWithCallback — user-mode callback on that queue.
  4. EspAllocateEventNotification + EspArmEventNotification — first receive.
  5. EspCreateRule — local ProcessCreate rule whose action points at the queue.
  6. EspUpdateRules — install for the connected client.
  7. Match → queue → FltSendMessage → FilterGetMessage → callback → EspCompleteEventNotification → rearm.

Queue-before-rule is the footgun. The rule does not hold the callback. It describes the event and points at a queue. The callback belongs to the queue’s delivery connection. Arm receive before installing the rule so the consumer is already waiting.

WESP consumer POC receiving process creation events
WESP consumer POC receiving process creation events. Source: original article.

Woo! Events. Remove persisted objects with:

.\wesp-consumer.exe remove "{FCB4EF81-F69B-4979-ADA2-C7BCF7B73792}"

Registering writes HKLM\SYSTEM\CurrentControlSet\Services\wesp\PersistedStore\Clients\. Johnson opened it from a SYSTEM prompt running as PPL (WinTcb) and still got access denied:

WESP persisted client registry access denied even as WinTcb PPL
WESP persisted client registry access denied. Source: original article.

He did not spend more time on the key. Client APIs already enumerate and remove registrations, which was enough for the POC.

For operators: If you are inventorying preview machines: that PersistedStore path is a high-value ACL study. WinTcb SYSTEM denied is a statement. Do not assume you can dump vendor registrations with a kernel debugger skip; the supported path is EspEnumerateRegisteredClients. The POC GUID FCB4EF81-F69B-4979-ADA2-C7BCF7B73792 is Johnson’s test client, not a Microsoft well-known.

WESP ETW visibility

One TraceLogging provider in espclient.dll: Microsoft.Windows.WESP.Client. Queue, rule, connection — success and failure. Johnson found it with Get-EtwProviders from ETWInspector:

$providers = Get-EtwProviders `
    -ProviderType TraceLogging `
    -FilePath C:\Users\TestUser\Desktop\espclient.dll

$providers.TraceloggingProviders
FilePath                                Providers
--------                                ---------
C:\Users\TestUser\Desktop\espclient.dll {Microsoft.Windows.WESP.Client}

The provider and event set are also in his EtwWatcher snapshot for 10.0.29661.1000. Preview schema will move. Example EspCreateEventQueue event:

WESP ETW EspCreateEventQueue event
WESP ETW event. Source: original article.
Kitchen table: Even if you never write a consumer, the lobby has cameras. Microsoft.Windows.WESP.Client is the camera on register/queue/rule. Hunt that provider on Insider rings before the GA SDK exists.

Wrapping up

Johnson’s tone: fun, first public binaries, moving out of the kernel is closer than people thought. The long pole is not the tech — it is enterprises upgrading. Eighteen months from WRI to public wesp.sys is fast. Plenty left: prevention rules (CrowdStrike showed blocking malware in Fal.Con), the rest of the event types, the SDK. Watch other reversers, especially Yarden Shafir.

Resources

Why this is not “EDR is leaving the kernel”

wesp.sys is still a kernel driver. PsSetCreateProcessNotifyRoutineEx2 is still a kernel callback. File and registry still go through a minifilter. What left the kernel is the vendor’s parser and the vendor’s bugcheck. That is a huge operational win (CrowdStrike-class outages become user-mode crashes) and a huge trust shift (Microsoft now owns the only collector; vendors see what WESP chooses to package). If WESP’s rule language cannot express a detection, the vendor either lives with the gap or keeps a driver. Johnson flags prevention rules as the next public fight.

PPL gating is the other half of the political design. Antimalware PPL is how Windows already decides who is a “real” AV. Tying WESP://Permission 1000000000 to 0x31 means the lobby is not open to every admin tool. The 10000000 class without PPL is the interesting preview knob — a vendor-shaped permission that is not the full uniform. Watch whether that class survives GA.

Kitchen table: Microsoft did not fire the detectives. They took away the detectives’ master keys to the elevator room and gave them a numbered window in the lobby. The sorting room still has a Microsoft employee with the only remaining master key. If that employee’s bags are incomplete, the detectives cannot invent a new elevator.

A researcher’s map of the remaining surface

  • Full event structs (ProcessCreate and the rest) — Johnson could not print all fields.
  • Rule language and prevention actions (Fal.Con malware block).
  • File/registry/image/handle paths through the same rule engine.
  • PersistedStore ACL vs WinTcb — who can read vendor GUIDs without EspEnumerate.
  • Whether test-signing bypass of Permission/PPL remains in later builds.
  • ETW schema drift as the preview client DLL moves.
  • Yarden Shafir and others as the preview widens.

Hunting WESP before it is GA

On 29661: Microsoft.Windows.WESP.Client, \EspFilterPort connections, services\wesp in the SCM, PersistedStore keys (even if you cannot open them, creation is a signal), wesp-consumer.exe if someone cloned the POC. A non-PPL process holding a long-lived Filter communication handle to EspFilterPort on a production-looking image is either Insider test signing or a story.

For operators: Do not load a random WESP consumer as SYSTEM on a production endpoint “to try EDR.” Preview binaries, no SDK, ACL you cannot read even as WinTcb. Lab VM, test signing, Johnson’s POC, then throw the VM away.

A glossary so the rest of the RE stays readable

TermKitchenOperator
WESPMicrosoft’s new mail-sorting room for endpoint events.Windows Endpoint Security Platform; wesp.sys + espclient.dll on 29661.
Producer / consumerSorting room vs detectives in the lobby.Driver collects; vendor process consumes.
EspFilterPortThe slot in the wall. Max 512 people in line.FltCreateCommunicationPort; FilterSendMessage / FilterGetMessage.
WESP://PermissionBadge sticker, not a yes/no stamp.Token attribute: 10000000 or 1000000000 (decimal).
AM-PPL 0x31The real uniform.Antimalware Protected Process Light; ProcessProtectionInformation.
Event queueThe window number on your ticket.EspCreateEventQueue; rule action points here, not at a callback.
PersistedStoreA locked filing cabinet even the building manager cannot open.HKLM\…\wesp\PersistedStore\Clients\; denied to WinTcb SYSTEM.
Dual-audience glossary. Terms from the original article.

Filter ports are not new. Putting all EDRs behind one is.

FltCreateCommunicationPort, FilterGetMessage, FltSendMessage, default SD for admin/SYSTEM — minifilter textbooks. What is new is that this port is meant to be the only kernel I/O path for a generation of vendors. 512 connections is a capacity number, not a security number. Each register/unregister/enumerate is a short connection; EspConnectClient and EspConnectEventQueueWithCallback are the long ones. Two long handles per consumer is the mental model: management vs delivery. Mixing them is how you confuse the driver’s “is this connection allowed to do X” check.

The access-check timing Johnson mapped matters for both testers and hunters. Token and PPL are sampled at connect, not at every FilterSendMessage. A handle stolen after a legitimate AM-PPL process connected is a different bug class than “can I open the port as admin.” Preview test-signing skipping Permission entirely is a third class. Write those three sentences on the whiteboard before you argue about whether WESP is “locked down.”

For operators: Handle lifetime: if a non-PPL process on a production-looking image has an open connection to \Device\EspFilterPort (or the filter port name EspFilterPort), that is either Insider test signing or a defect. On 29661 Johnson showed the former is intended. Track the flight.

Queue-before-rule is the API’s personality

People coming from ETW or minifilter callbacks expect “subscribe to ProcessCreate, here is my function pointer.” WESP splits that. A rule is a description plus an action that names a queue. The callback is a property of the queue’s delivery connection. That is why Johnson armed FilterGetMessage before EspUpdateRules. If you install the rule first, events can match while nobody is listening, and you will swear the API is broken. It is not. You subscribed a mailbox after the letters started moving.

The same split is how one client can have many queues and many rules without stuffing callbacks into the rule blob. It is also how a future prevention rule might point at a different action than “enqueue” — CrowdStrike’s Fal.Con block demo is the teaser. Johnson did not reverse that path. Do not invent it.

Kitchen table: You do not tell the post office “call my cell when a process is born.” You rent a PO box (queue), you fill a form that says “process births go to box 12” (rule), and you already have a clerk sitting at box 12 (armed FilterGetMessage). Rent the box first. Then file the form.

PPL, WinTcb, and a registry key that says no

Antimalware PPL is 0x31. WinTcb is higher. Johnson ran a SYSTEM prompt as WinTcb and still could not open PersistedStore. That is not a bug report; it is a product statement: the list of registered WESP clients is not a trophy for whoever can enable PPL. Enumeration goes through EspEnumerateRegisteredClients, which pays the port checks. If you are writing IR for “which EDR is registered with WESP,” that API (and ETW) is the supported answer. Direct registry is a dead end on this build, which is useful information by itself.

The 1e7 vs 1e9 permission class is the other PPL story. 1e9 is “you are AM-PPL.” 1e7 is “you have the attribute but we will not demand the uniform.” On a locked-down enterprise that 1e7 class is either a lab gift or a confused vendor identity. Demand Microsoft document it before you let a non-PPL binary hold a management connection next to Defender.

What a crash in the lobby actually buys

CrowdStrike 2024 was a kernel driver with a bad content update. WESP’s design goal, in Johnson’s sentence, is that a crash in the consumer does not bugcheck the machine. That is true only for logic that moved to user mode. wesp.sys can still bugcheck. A bad rule evaluation, a bug in packaging, a filter communication failure — those are now Microsoft’s. Vendors cannot ship a sensor.sys that takes the box with them, which is the point. Vendors also cannot patch wesp.sys on their own clock. That is the trade.

Enterprises will live on old Windows for years. Johnson says the long pole is upgrades, not the API. Until 29661-class OS is the floor, you will have kernel EDR and WESP consumers on the same estate. Plan coexistence, not a flip.

ETW as the public SDK until the SDK exists

No headers, no lib, no samples from Microsoft. The TraceLogging provider Microsoft.Windows.WESP.Client is the part you can subscribe to without reversing. Queue create, rule update, connect success/fail — Johnson’s EspCreateEventQueue screenshot is the template. EtwWatcher’s 29661 snapshot is a time capsule; the schema will move. Version the provider in your SIEM the way you version Defender ETW. Get-EtwProviders against espclient.dll is how you notice a new event name after the next flight.

If you clone the POC tomorrow

  1. Insider 29661 (or later with the same two binaries). Test signing on if you are not AM-PPL.
  2. Elevated admin. Filter port ACL is still admin/SYSTEM.
  3. register, clients, monitor “{guid}” 60, then start notepad and watch.
  4. remove the GUID. Confirm PersistedStore is gone via the API, not via regedit.
  5. Throw the VM away. Do not leave a registered client on a laptop you care about.

The printed ProcessCreate fields are incomplete because the struct is not public. Do not treat missing command-line or missing parent PID in the POC as “WESP does not have them.” Johnson said he does not know the full layout. Absence of evidence in a weekend POC is not evidence of absence in the driver.

For operators: If you extend the POC: keep using espclient.dll. Do not talk to EspFilterPort with raw FilterSendMessage until you have the message IDs from the DLL. Wrong opcode on a management connection is how you spend a day in WinDbg for nothing.

Key Takeaways

  • WESP public in Insider 29661: wesp.sys + espclient.dll. No SDK. Symbols enough for a process-create consumer.
  • Microsoft collects in kernel; vendors consume in user mode. Consumer crash ≠ bugcheck. That is the CrowdStrike lesson encoded in a minifilter.
  • \EspFilterPort, 512 connections, admin/SYSTEM ACL, then WESP://Permission 1e7 vs 1e9 (AM-PPL 0x31). Test signing on this build skipped both for elevated testers.
  • Queue then rule. Callback is on the queue connection. FilterGetMessage / FltSendMessage is the path.
  • PersistedStore denies even WinTcb SYSTEM. Use the client APIs. ETW: Microsoft.Windows.WESP.Client.
  • POC on GitHub. Preview will change. Watch prevention rules and Yarden.

Defensive Recommendations

  1. Do not treat 29661 as production WESP. Treat it as a map.
  2. Inventory who will get WESP://Permission and AM-PPL in your estate; that is the new EDR install identity.
  3. Plan ETW on Microsoft.Windows.WESP.Client the way you plan Defender/vendor ETW today.
  4. If you still run third-party kernel EDR, WRI is the migration story — the long pole is OS upgrade, not the API.
  5. Lab the POC. Confirm test-signing behavior on each new Insider flight; file it if the skip survives too long.
  6. Read the registry ACL on PersistedStore as a sensitivity label: vendor GUIDs are not for everyone with a kernel debugger habit.

Conclusion

Eighteen months after Microsoft asked whether vendors should still live in the kernel, the first public WESP binaries answer: the collector stays, the detectives move to user mode, the port is a filter communication port you already know how to reverse, and a weekend with symbols is enough to print process creates. The interesting arguments — prevention, PPL classes, what the rule engine cannot see — are still behind the private SDK. Johnson opened the curtain. The play is not over.

Original text: “A First Look Inside the Windows Endpoint Security Platform” by Jonathan Johnson at jonny-jhnson.dev.

oxfemale Vulnerability research, reverse engineering, and exploit development.
// Discussion