I'm building ShadowStrike Phantom — an open-source endpoint detection and response platform for Windows.
From-scratch. Custom kernel driver, behavioral analysis, exploit prevention, the whole stack.
This isn't a weekend project. PhantomSensor.sys alone is 380,000 lines of C. It's a WDM minifilter running at
altitude 385210 with 14 operation callbacks, syscall monitoring, memory protection, and a behavioral engine with
MITRE ATT&CK mapping.
Over the past few weeks, I've been doing line-by-line security audits of the user-mode shared modules — the engines
that actually make detection decisions. 12 modules so far. Here's an honest look at what I found in my own code.
The Numbers
- 12 modules audited: SignatureStore, ThreatIntel, ExploitPrevention, BehaviorBlocker, AccessControlManager,
Whitelist, FileProtection, RegistryProtection, SelfProtection, and more
- 400+ issues found and fixed
- 60+ critical/high severity findings
The Worst Bugs
ExploitPrevention was protecting itself, not the target. SetProcessMitigationPolicy applies to the calling process.
My code was applying DEP/ASLR/CFG enforcement to the EDR agent, not to the monitored process. For existing remote
processes, most Windows mitigation policies are immutable post-creation — you can only query them. Fixed by
switching to query + report for existing processes, and PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY at creation time
for new ones.
22 declared methods with zero implementation. The ExploitPrevention header declared 35+ public methods. 22 of them
had no implementation in the .cpp file. OnTableAccess (EAF handler), OnException (SEHOP handler), EnableIAF — all
dead. The EnableIAF method was particularly bad: it returned true (success) while doing absolutely nothing.
EAF breakpoints were using wrong width. Hardware debug breakpoints (Dr0-Dr3) for Export Address Table access
monitoring had the Dr7 LEN field set wrong — 1-byte breakpoints instead of 8-byte (x64) or 4-byte (x86). An
attacker
accessing the EAT at an offset beyond byte 0 would bypass detection completely.
Deadlock: holding mutex during thread suspension. EAF protection held the EAF mutex while iterating and suspending
threads. If a suspended thread also needed that mutex → deadlock. Fixed by collecting thread IDs under lock,
releasing the lock, then suspending.
Stats struct with std::atomic made the class non-copyable. ExploitPreventionStats had std::atomic
members.
Returning it from GetStats() triggered C2280 (deleted copy constructor). Fixed by using plain uint64_t in the
public
API and internal std::atomic counters in the PIMPL, with a Snapshot() method.
What I Learned
Writing security software means your code is under double scrutiny — it must be correct and it must resist active
attempts to break it. Every unimplemented method is a gap. Every wrong parameter is a bypass. Every deadlock is a
denial of service against your own protection.
The audits also forced proper kernel ↔ user-mode wiring. Before the audits, FilterMessageType_MemoryAlert was a
logging-only stub — the kernel would send memory anomaly alerts and the user-mode agent would log "got an alert"
and
do nothing. Now it routes through ExploitPrevention::OnKernelMemoryAlert() for actual analysis.
Current Status
- Kernel driver: complete, Coverity
0.25 defect/KLoC, Driver Verifier passed
- User-mode: 76% complete, security audit phase
- Beta target: 2027 (moved up from 2028-2029)
- Next: on-device ML integration, product splits (Home/EDR/XDR)
Everything is AGPL-3.0. Every commit is public.
GitHub: github.com/ShadowStrike-Labs/ShadowStrike (https://github.com/ShadowStrike-Labs/ShadowStrike) Website:
shadowstrike.dev (https://shadowstrike.dev)
If you're interested in open-source endpoint security, give us a star or follow the project. We're building this in
public because security software shouldn't be a black box.
Top comments (0)