NSPI-based domain enumeration and BloodHound-CE collector.
NSPIder collects domain principals via the Name Service Provider Interface (MS-NSPI) directly against a Global Catalog, producing BloodHound-CE v6 JSON output. It runs two collection passes on every execution: a browse walk that respects address-book filtering as a baseline, and a MId-range scan that reads raw DIT positions and bypasses that filtering entirely.
Exchange is required. The NSPI endpoint on a GC only registers after Exchange's AD preparation runs. NSPIder targets the GC directly over
ncacn_ip_tcp— not the Exchange server. Any valid domain credential works. No elevated privileges required.
Standard BloodHound collectors reach the directory over LDAP, which is heavily monitored in mature environments — MDI has dedicated SharpHound analytics, expensive-query thresholds fire on bulk enumeration, and LDAP-signature auditing catches the traffic pattern even when individual queries are benign.
NSPI is a different interface to the same data. It's what every Outlook client uses for address-book lookups, so moderate query volume is indistinguishable from normal mail client activity. Direct GC connections over ncacn_ip_tcp generate zero 4662 events under default DC audit configuration and fewer 4769 events than an equivalent LDAP session. The MId-range scan — 400 RPC calls that traverse the full DIT — produces a single logon event.
Beyond stealth, the MId-range scan surfaces data that LDAP-first collectors and every existing NSPI tool miss:
| Control | Browse (container-filtered) | MId-range scan |
|---|---|---|
msExchHideFromAddressLists |
Respected | Bypassed |
| Address Book Policy segmentation | Respected (browse) | Bypassed |
| Mail-enabled-only filter | Applied | Bypassed — full domain population |
| Non-mail AD groups (Domain Admins, etc.) | Not present | Surfaced with SIDs |
Objects not visible in any address book — hidden service accounts, restricted DLs, built-in AD groups, Exchange service mailboxes, machine accounts — appear in the MId scan output and are tagged nspi_hidden: true in the JSON so analysts can immediately spot what standard tooling would miss.
pip install impacket
git clone https://github.com/cbev0x/NSPIder
cd NSPIderPython 3.8+. No additional dependencies beyond Impacket.
NSPIder [[domain/]user[:pass]@]<DC IP or hostname> [options]
# Password auth
python3 NSPIder.py reflect.lab/jdoe:'Password1'@10.10.20.10
# NT hash
python3 NSPIder.py reflect.lab/jdoe@10.10.20.10 -hashes :8846f7eaee8fb117ad06bdd830b7586c
# Kerberos (from ccache)
python3 NSPIder.py reflect.lab/jdoe@dc01.reflect.lab -kOutput lands in NSPIder_<domain>_<timestamp>/ by default — two files ready for BloodHound-CE import:
NSPIder_reflect.lab_20260826055903/
├── 20260826055903_users.json # 65 users (62 hidden)
└── 20260826055903_groups.json # 110 groups (109 hidden)
Drag-drop both into BloodHound-CE's upload interface. Nodes key on objectSid and merge cleanly with SharpHound or other collector output.
# Add inter-batch jitter (200ms max) and narrow the MId range
python3 NSPIder.py reflect.lab/jdoe:'Password1'@10.10.20.10 \
--jitter 200 --mid-start 4000 --mid-end 15000
# Increase batch size (faster, slightly louder)
python3 NSPIder.py reflect.lab/jdoe:'Password1'@10.10.20.10 --batch 100The default batch size of 50 matches typical Outlook address-book paging. Jitter adds a random sleep between batches to flatten the request rate. The MId range 1-20000 covers most environments; adjust for very large directories.
By default NSPIder is pure NSPI — no LDAP traffic. The --enrich flag adds a targeted LDAP query for group membership, populating MemberOf edges in the output. This opens an LDAP session (additional logon event, signing negotiation).
# Enrich all discovered groups (targeted to NSPI-collected groups only)
python3 NSPIder.py reflect.lab/jdoe:'Password1'@10.10.20.10 --enrich
# Enrich only groups matching a name substring (more targeted)
python3 NSPIder.py reflect.lab/jdoe:'Password1'@10.10.20.10 \
--enrich --enrich-filter "domain admins"Enrichment scope is always limited to groups discovered during the NSPI collection pass. It is not a full LDAP group dump.
python3 NSPIder.py reflect.lab/jdoe:'Password1'@10.10.20.10 --bofhoundEmits a single combined JSON file compatible with BOFHound v2+ ingestion.
authentication:
-hashes LM:NT NTLM hashes (:NT for NT-only)
-k Kerberos auth (from ccache or -aesKey)
-aesKey hex AES-128/256 key for Kerberos
-dc-ip ip DC/GC IP (overrides target host for EPM resolution)
collection:
--mid-start INT MId-cycle start (default: 1)
--mid-end INT MId-cycle end (default: 20000)
--batch INT QueryRows batch size (default: 50)
--jitter ms Random inter-batch jitter in milliseconds
enrichment (adds LDAP session to footprint):
--enrich Enable LDAP membership pull for discovered groups
--enrich-filter STR Only enrich groups whose name contains STR
output:
-o, --output DIR Output directory (default: NSPIder_<domain>_<ts>)
--bofhound BOFHound-compatible JSON instead of BH-CE format
-debug Verbose debug output
Browse walk (container-filtered, runs first):
- Mail-enabled users and distribution groups visible in the Default GAL
- Establishes the visible-object baseline
MId-range scan (bypasses all container filtering):
- Everything in the browse walk, plus hidden mailboxes and DLs
- Non-mail-enabled AD users and groups (Domain Admins, Schema Admins, built-ins)
- Exchange service mailboxes (HealthMailbox, SystemMailbox, federation accounts)
- Machine accounts that surface through NSPI
- Any AD object at a scanned DIT position that has a display name and SID
Per object, NSPIder collects: objectSid, objectGUID, displayName, mail, mailNickname, title, department, and displayType for node classification. Objects without a SID are discarded (no BH node key).
Enrichment (optional): member attribute pulled from LDAP for collected groups, added as Members entries in the group JSON.
NSPI on a GC exposes two collection primitives. Browse-walk uses the address book container, which is exchange-managed and filtered. MId-range scan uses NspiQueryRows with an explicit MId table (lpETable), which reads DIT positions directly and ignores container-level filtering. Both run over ncacn_ip_tcp to the GC, resolved via the endpoint mapper on port 135.
The following operations are not available on the direct-GC path and are out of scope for this tool:
NspiResolveNamesW(ANR / name resolution) — requires the Exchange address-book service (ncacn_httpvia EXCH01)NspiGetMatches(restriction/filter queries) — same requirement- Group membership proptags (
PidTagAddressBookMemberetc.) — absent from the GC's exposed proptag set; hence the LDAP enrichment option
These live in the Exchange resolution plane (EXCH01 via RPC-over-HTTP). That path is outside NSPIder's scope but is worth a separate tool.
NSPI only registers on the GC after Exchange AD preparation has run. Running NSPIder against a domain with no Exchange installation will produce ept_s_not_registered.
Default run (no enrichment):
| Event | Count |
|---|---|
4662 (DS object access) |
0 (default SACLs have no read ACEs) |
4624 (logon) |
~1 |
4769 (Kerberos TGS) |
0-1 |
5145 (pipe/share access) |
0 (transport is ncacn_ip_tcp, not named pipe) |
Traffic pattern: one authenticated RPC bind followed by batched NspiQueryRows calls. Indistinguishable from an Outlook client opening the address book. A tuned SOC with MDI will see a "NSPI RPC Browse Requests/sec" spike on the DC during the MId-cycle pass — the tell is rate, not protocol. Jitter (--jitter) flattens this.
With --enrich: adds one LDAP session (one additional 4624, one 4769 for ldap/DC).
This tool came out of a characterization of the MS-NSPI attack surface for offensive security research. The full writeup with empirical lab results, footprint tables, and a breakdown of the two-plane NSPI architecture (GC data plane vs Exchange resolution plane) is at cbev0x.github.io.
Key findings documented there:
msExchHideFromAddressListsis a presentation-layer control only — direct MId queries bypass it- Address Book Policy segmentation is not enforced on the direct-GC NSPI path
- The MId-range scan surfaces the full domain user population, not just Exchange recipients
legacyExchangeDN(LDAP-readable at any privilege) enables targeted single-object access viaNspiDNToMIdwith a ~2-event footprint vs 400 calls for bulk discovery- Exchange infrastructure details (server name, mailbox DB GUID, service topology) are recoverable from HealthMailbox objects visible in the MId scan
Built on Impacket's dcerpc.v5.nspi implementation by mohemiv (PT SWARM), which provides the MS-NSPI / MS-OXNSPI / MS-OXABREF protocol layer. NSPIder adds the BloodHound-graph layer, the MId-bypass collection mode, and the OPSEC-aware tooling on top.
Inspired by BOFHound and SOAPHound for the "quiet collector" framing.