Overview

Three open-source collectors produce data for SpecterOps’ BloodHound CE graph analysis platform: SharpHound (C#, SpecterOps), RustHound-CE (Rust, g0h4n), and BloodHound.py CE (Python, dirkjanm). All three query Active Directory via LDAP, parse security descriptors, and output JSON files ingestible by BloodHound CE, but they differ significantly in collection depth, platform support, and operational characteristics. This report compares them based on source code analysis of SharpHound (2.X branch, 4dca071), RustHound-CE (main branch, b6f809f), and BloodHound.py (bloodhound-ce branch, 6fa5ba5).

BloodHound.py is the oldest of the three — its ACL parsing logic served as the reference implementation that RustHound-CE's parser was derived from. The CE branch adds BloodHound CE output format support (v6 metadata) to the existing Python collector.

Key Findings

Architecture Comparison

SharpHound uses a producer-consumer pipeline built on System.Threading.Channels. CollectionTask.cs creates three channels (LDAP objects, output objects, computer status), spawns N consumer tasks (default 50 threads) that call ObjectProcessors.ProcessObject(), and coordinates a single producer writing paged LDAP results. The chain-of-responsibility pattern in SharpLinks.cs orchestrates initialization, connection testing, collection, looping, cache saving, and output finalization.

RustHound-CE uses a linear single-pass pipeline: ldap_search() streams all entries from each naming context into a storage backend (in-memory Vec or disk-backed DiskStorage), then prepare_results_from_source() iterates over all entries sequentially to classify and parse them by type. There is no concurrent processing — the async runtime (tokio) handles the LDAP I/O, but object parsing is single-threaded.

BloodHound.py CE uses a per-domain enumeration pipeline with layered caching. For each domain in the forest, it instantiates MembershipEnumerator and ComputerEnumerator classes. LDAP paged searches (ldap3 with configurable page size, default 1000) feed into resolution caches (DNS, SID, SAM, DN), then optional ACL parsing is offloaded to multiprocessing.Pool (CPU-bound cstruct binary parsing bypasses the GIL). Computer enumeration uses threaded worker pools (default 10 threads) for RPC calls, with results pushed to a writer thread via queue.Queue.

AspectSharpHoundRustHound-CEBloodHound.py CE
Concurrency modelMulti-threaded channels (configurable, default 50)Single-threaded parse, async LDAP I/OMultiprocess ACL parse, threaded RPC, sequential LDAP
Pipeline styleProducer → bounded channel → N consumers → outputLDAP stream → storage → sequential parse → JSONLDAP → cache → parallel ACL + threaded RPC → JSON
Naming contextsDefault NC + Config NC, separate passesAll NCs from root DSEPer-domain with GC cross-ref
Filter generationMethod-aware LDAP filter builderFixed (objectClass=*)Per-method attribute selection
Output streamingIn-memory assembly → single writeSequential parse → file writeOutputWorker with file rotation at 40K entries

SharpHound's filter generation is collection-method-aware — it only requests the object classes and attributes needed for the selected methods. RustHound-CE always pulls all attributes ( ["*", "nTSecurityDescriptor"]), increasing bandwidth and server load. BloodHound.py uses per-method attribute lists, similar to SharpHound's approach.

Collection Method Parity

SharpHound exposes 25+ collection methods as a bitmask enum. RustHound-CE offers All and DCOnly. BloodHound.py CE supports individual method flags (--collect, --excludedcs) with defaults covering all LDAP methods plus optional computer enumeration.

MethodSharpHoundRustHound-CEBloodHound.py CENotes
Group (membership)✅✅✅All parse member attribute
ACL/ACE parsing✅✅✅All parse nTSecurityDescriptor
ObjectProps✅✅ (subset)✅BH.py includes sIDHistory resolution
Container✅✅✅GPOLinks, containment hierarchy
Trusts✅✅✅BH.py classifies trust types (ParentChild/Forest/External)
Session (NetSessionEnum)✅ SMB❌✅ SMBBoth SharpHound and BH.py via RPC
LoggedOn (privileged)✅ SMB❌✅ SMBRequires remote registry/SAM access
Registry sessions✅ SMB❌✅ SMBRemote registry reads
LocalAdmin / RDP / DCOM / PSRemote✅ SMB❌✅ SMBRemote RPC/SMB enumeration
LocalGroup✅ SMB❌✅ SMBSAMR protocol calls
GPOLocalGroup✅ LDAP✅✅GPO filtered group membership
SPNTargets✅ LDAP❌✅Resolves SPN hostnames to SIDs
UserRights✅ SMB❌❌LSA policy queries
CertServices✅ LDAP+SMB✅ LDAP only✅ LDAP onlyNo CA registry collection
CARegistry✅ SMB❌❌Remote registry on CA servers
DCRegistry✅ SMB❌❌Remote registry on DCs
WebClientService✅ SMB❌❌Web Client service status
LdapServices✅ SMB❌❌LDAP signing/channel binding checks
SmbInfo✅ SMB❌❌SMB signing enforcement checks
NTLMRegistry✅ SMB❌❌NTLM audit settings from registry
Stealth mode✅❌❌Target inference from user homedir/scriptpath
Loop mode✅❌❌Periodic re-collection
Throttle / Jitter✅❌❌Delay between computer connections
Forest / RecurseDomains✅❌✅Cross-domain trust traversal
Credentialless recon❌❌✅dumpntlm.py — NTLM info without auth
Services / Scheduled tasks❌❌✅via SAMR/SCMR/ATSVC RPC calls

RustHound-CE's All mode name is misleading. It performs LDAP-only collection — equivalent to SharpHound's DCOnly. BloodHound.py CE fills the middle ground: it supports remote computer enumeration via Impacket's RPC stack, but cannot match SharpHound's full set of registry-based collectors.

ACL/ACE Parsing

All three tools parse raw nTSecurityDescriptor byte arrays into BloodHound-compatible ACE relations, but their implementations differ significantly.

SharpHound delegates ACL processing to ACLProcessor (from SharpHoundCommonLib), which uses .NET’s System.DirectoryServices.Protocols built-in SD parsing. It tracks AdminSDHolder implicit ACL hashes per-domain and detects AdminSDHolder protection status — a BloodHound 5.0 feature. It computes IsACLProtected, InheritanceHashes, owner-rights ACE detection, and GMSA reader ACEs.

RustHound-CE implements a hand-rolled binary parser in enums/secdesc.rs using nom7 for the security descriptor structure, and enums/acl.rs for the ACE-to-BloodHound-rights mapping (~2700 lines). The logic closely follows BloodHound.py’s original implementation, handling Access Allowed (0x00) and Access Allowed Object (0x05) ACE types with mask flag checks.

BloodHound.py CE implements a binary SD parser in enumeration/acls.py using its own bloodhound.lib.cstruct library. It handles the same two ACE types (0x00 and 0x05) and maps them to BloodHound right names. ACL parsing runs in multiprocessing.Pool to bypass the GIL — a practical choice since cstruct binary parsing is CPU-bound.

Key differences in ACL handling:

FeatureSharpHoundRustHound-CEBloodHound.py CE
SD parsing.NET built-inHand-rolled nom parserHand-rolled cstruct parser
AdminSDHolder protection✅ Hash-based detection❌❌
Inheritance hashes✅ Per-object❌❌
Owner-rights ACE detection✅❌❌
GMSA reader ACEs✅❌✅ (separate msDS-GroupMSAMembership parsing)
LAPS read detection✅✅✅ (ms-mcs-admpwd, ms-laps-password, ms-laps-encryptedpassword)
SID history✅✅✅ (with cross-forest GC resolution)
Property-specific GUIDsComprehensiveGood coverageComprehensive
ConcurrencyThread poolSingle-threadedMultiprocess pool

BloodHound.py CE is the progenitor of RustHound-CE's ACL logic — RustHound's enums/acl.rs is a direct port of BloodHound.py's approach. BloodHound.py additionally parses GMSA DACLs from msDS-GroupMSAMembership as a separate attribute, which RustHound-CE does not handle.

Output Format Compatibility

All three produce JSON files structured for BloodHound CE ingestion with the same data/meta envelope:

AspectSharpHoundRustHound-CEBloodHound.py CE
Meta versionversion: 6version: 6version: 6
Collector versionAssembly versionRustHound-CE v{version}BloodHound.py CE
Object typesFull (users, groups, computers, domains, OUs, GPOs, containers, AD CS)FullFull
ZIP packagingDefault (zipped), password-protected, randomized filenamesOpt-in (--zip)❌ (individual JSON files)
File rotationN/A (single archive)N/A✅ (auto-rotate at 40K entries)
BH 4.0 guardN/A (CE-only)N/A (CE-only)✅ (ValueError if legacy acetype used)
API upload✅ (direct to BH CE)❌❌

SharpHound is the only collector that supports direct API upload to BloodHound CE. RustHound-CE and BloodHound.py CE output files that must be manually uploaded via the BH CE web interface or API.

Remote Enumeration

This is the largest differentiator. SharpHound and BloodHound.py both support authenticated SMB/RPC connections to remote computers. RustHound-CE is a pure LDAP client.

SharpHound maintains separate processor classes for every remote enumeration technique: ComputerSessionProcessor, LocalGroupProcessor, UserRightsAssignmentProcessor, DCRegistryProcessor, CertAbuseProcessor, WebClientServiceProcessor, SmbProcessor, RegistryProcessor.

BloodHound.py CE covers a subset via Impacket’s RPC stack:

  • Sessions: I_rpc_get_loggedon (logged-on users), NetWkstaUserEnum, DsRoleGetPrimaryDomainInfo
  • Local groups: SAMR QueryLocalGroupMembership for RDP/DCOM/PSRemote/LocalAdmin
  • Services/Scheduled tasks: SCMR EnumerateServicesW, ATSVR NetJobEnum
  • Registry sessions: OpenBaseRegKey via Impacket drsuapi

BloodHound.py lacks SharpHound’s UserRights (LSA), DCRegistry, CARegistry, WebClientService, LdapServices, and SmbInfo collectors.

For environments where only LDAP-based collection is feasible (e.g., from a non-domain-joined Linux host without SMB access), RustHound-CE is the lightest option. BloodHound.py CE adds remote computer enumeration when SMB access is available.

Deployment and OPSEC Considerations

SharpHound requires .NET Framework 4.7.2+ (Windows only). It uses Costura.Fody to embed dependencies into a single DLL (~2 MB), typically executed via Invoke-SharpHound PowerShell wrapper. As a .NET assembly, it is detectable by AMSI and well-known to endpoint security products.

RustHound-CE compiles to a static native binary (~1-2 MB) with aggressive optimization (opt-level = "z", LTO, strip, panic = "abort"). Cross-compiles to Linux (x86_64, armv7 musl), macOS, and Windows. Uses obfstr for compile-time string obfuscation. No runtime dependency.

BloodHound.py CE requires Python 3 with ldap3, impacket, and optionally python3-krb5. It runs on any platform supporting Python without compilation. There is no binary distribution — deployment requires installing dependencies (often via pip). The dumpntlm.py module depends on unmerged Impacket changes (PR #1404), requiring a patched Impacket install.

FactorSharpHoundRustHound-CEBloodHound.py CE
PlatformWindows only (.NET 4.7.2+)Linux, macOS, WindowsLinux, macOS, Windows
Runtime dependency.NET FrameworkNone (static binary)Python 3 + ldap3 + impacket
DistributionPre-built DLL/EXEPre-built binarySource (pip install)
AMSI detectionHigh (well-known .NET assembly)Lower (native binary)Low (interpreted Python)
Certificate verificationConfigurableAlways disabledConfigurable (-d for disable)
Kerberos authNegotiate (native SSPI)GSSAPI via ldap3python3-krb5 (ccache or password)
NTLM hash auth❌ (password only)✅ (via NTLM hash)✅ (via NTLM hash)
Stealth featuresTarget inference, loop, throttle, jitter, random filenamesNoneNone
Python ecosystem integration❌❌✅ (native Python, scriptable)

RustHound-CE hardcodes set_no_tls_verify(true), making it impossible to use where LDAPS certificate validation is required. BloodHound.py and SharpHound both support configurable certificate verification.

Authentication Methods

All three support standard LDAP authentication. Notable differences:

MethodSharpHoundRustHound-CEBloodHound.py CE
Simple bind (plaintext)✅✅✅
NTLM password✅ (Negotiate)✅✅
NTLM hash (pass-the-hash)❌✅✅
Kerberos✅ (SSPI, native)✅ (GSSAPI ccache)✅ (python3-krb5 ccache or password)
SSPI (Windows native)✅❌✅ (via Impacket)

BloodHound.py CE and RustHound-CE both support pass-the-hash authentication via NTLM, which SharpHound does not. This matters in scenarios where only NTLM hashes are available (e.g., from a credential dump).

Assessment

SharpHound is the reference implementation. It offers the most complete collection coverage (25+ methods including all registry-based collectors), fine-grained method selection, OPSEC controls (stealth, throttle, jitter, loop), and direct API upload to BloodHound CE. Its limitation is Windows-only execution and lack of pass-the-hash support.

RustHound-CE fills a niche for lightweight, LDAP-only collection from any platform. Its native binary is the most portable and least detectable option. However, it lacks remote computer enumeration entirely, has no stealth features, and always disables TLS verification. The All mode name overstates capabilities.

BloodHound.py CE occupies the middle ground. It provides the broadest cross-platform remote enumeration (sessions, local groups, services, scheduled tasks via Impacket RPC), supports pass-the-hash authentication, offers per-method collection control, and integrates natively with the Python offensive ecosystem. Its ACL parser is the reference implementation that RustHound-CE was derived from, and it uniquely supports GMSA DACL parsing and credentialless NTLM reconnaissance. The trade-off is requiring Python dependencies and lacking SharpHound’s registry-based collectors and OPSEC features.

When to use SharpHound:

  • Full-fidelity collection with all registry-based methods
  • Production engagements requiring stealth/throttle/jitter
  • Environments where Windows execution is available
  • AdminSDHolder protection detection and inheritance hashing needed
  • Direct API upload to BloodHound CE desired

When to use RustHound-CE:

  • LDAP-only collection from any platform
  • Minimal footprint required (single static binary)
  • OPSEC priority (native binary, compile-time obfuscation)
  • No Python runtime available on collection host

When to use BloodHound.py CE:

  • Cross-platform collection with remote computer enumeration
  • Pass-the-hash authentication required
  • Python offensive pipeline integration (e.g., C2 scripting)
  • Credentialless initial reconnaissance (dumpntlm.py)
  • Multi-domain forest collection with GC-based resolution
  • GMSA password ACL extraction needed

Gaps common to RustHound-CE and BloodHound.py CE:

  • No AdminSDHolder protection or inheritance hash detection
  • No UserRights (LSA) enumeration
  • No DC/CA registry collectors
  • No stealth mode, throttling, or loop collection
  • No direct API upload to BloodHound CE