The tooling problem is embarrassing
Everyone in AD security uses BloodHound. It's good at what it does, attack paths, delegation chains, ACL edges. No complaints there.
The problem is the how.
SharpHound is a .NET assembly. It floods your network with LDAP traffic patterns that zero legitimate workstations produce. EDR picks it up immediately, not because it's exploiting anything exotic, but because the behavioral signature is basically a neon sign. ADRecon, PowerView, Python alternatives, it's the same story. The runtime is the fingerprint.
Internal red teams and defenders are stuck in a ridiculous spot: you need to enumerate your own domain to find misconfigs before attackers do, but every tool available screams its presence. Unacceptable.
The solution was already there. Since Windows 2000.
Active Directory Service Interfaces, that is it. COM-based LDAP abstraction that Windows itself uses constantly. Group Policy uses it. MMC snap-ins use it. net user /domain uses it.
The traffic it produces is indistinguishable from normal domain activity. Because it is normal domain activity.
That's Kestrel's entire foundation.
It is just native, pure C. No managed runtime. No wrappers.
Direct COM vtable calls. No .NET. No PowerShell. No abstractions between you and the wire:
cIDirectorySearch *pSearch = NULL;
ADsGetObject(ldapPath, &IID_IDirectorySearch, (void **)&pSearch);
ADS_SEARCHPREF_INFO prefs[2];
prefs[0].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE;
prefs[0].vValue.Integer = ADS_SCOPE_SUBTREE;
prefs[1].dwSearchPref = ADS_SEARCHPREF_PAGESIZE;
prefs[1].vValue.Integer = 200;
pSearch->lpVtbl->SetSearchPreference(pSearch, prefs, 2);
pSearch->lpVtbl->ExecuteSearch(pSearch, filter, attrs, count, &hSearch);
From the wire's perspective: authenticated LDAP bind, paged search requests. What every DC sees from every domain workstation, every minute, every day. You're invisible by default.
v0.1 = just five passive modules
ADWS Endpoint Detection^ raw TCP connect to port 9389 per DC. One SYN. No WCF handshake, no ADWS framing. Just: is this port open, yes or no.
Computer Topology - full computer inventory, SPN decoding through a service-class table. MSSQLSvc → SQL Server, WSMAN → WinRM, TERMSRV → RDP. One LDAP query. Zero packets to target hosts.
Delegation Risks: actually separates the three categories most tools conflate:
Unconstrained (TRUSTED_FOR_DELEGATION on non-DC objects) - Kerberos ticket arrives with forwarded TGT. Serious.
Constrained (msDS-AllowedToDelegateTo) - exact target SPNs enumerated.
Protocol Transition / S4U2Self (UAC & 0x1000000) -- service gets a ticket for any domain user without their credentials. Different risk profile, reported separately. Most tools miss this distinction entirely.
LAPS Coverage detects both legacy LAPS (ms-Mcs-AdmPwdExpirationTime) and Windows LAPS 2023+ (msLAPS-EncryptedPasswordHistory). Splits computers into managed/unmanaged with percentage breakdown. You will know exactly how exposed your local admin situation is.
Stale Computers uses lastLogonTimestamp as primary reference. It replicates across DCs, unlike lastLogon which is per-DC only. Both values reported side by side.
Engineering discipline matters
Two things enforced that most open-source Windows tooling just ignores:
SAL 2.0 annotations on every function signature. Not for decoration it's PREfast (/analyze) validates them at compile time. Must_inspect_result on HRESULT-returning functions, Outptr vs Out where semantics differ. Contract violations caught before the code ever runs.
Single rootDSE resolution: defaultNamingContext read once at startup, passed as parameter to all five scan functions. The naive approach binds rootDSE in each module separately. That's five redundant DC round-trips for data that doesn't change mid-scan. Obviously wrong. Fixed.
Roadmap
v0.1 is the foundation. Here's what's coming:
v0.2 == raw SECURITY_DESCRIPTOR parsing via IDirectoryObject::GetObjectAttributes. Extended Rights GUID→name mapping built dynamically from CN=Extended-Rights,CN=Configuration. Full ACL edges on every AD object, no elevated privileges required.
v0.3 == transitive group membership via LDAP_MATCHING_RULE_IN_CHAIN (OID 1.2.840.113556.1.4.1941). One query, full recursive chain. Zero client-side BFS.
v0.4 == in-memory graph from ACL + membership + delegation data. JSON export for visualization.
v0.5 == BFS path finder across the graph. Attack path analysis from any principal to Domain Admins. Shortest privilege escalation route, computed natively.
What the point is: everything SharpHound does in managed code can be reproduced through interfaces Windows already exposes natively. Quietly. Without a detectable runtime. With full static analysis at build time.
This should have existed years ago.
→ Kestrel
Parent project: NetEnum
Top comments (0)