DEV Community

ShadowStrike Labs
ShadowStrike Labs

Posted on

We security-audited 400,000+ lines of our own EDR code. Here's what we found.

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, 
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)