Prancer Blog / GOAD Challenge: Swarmhack AD pentesting

22 Agents, Zero impacket — The Architecture of Native AD Pentesting

A native Rust stack for LDAP, Kerberos, DCERPC and BloodHound replaces impacket + certipy + Responder — 80 ms cold start, 620 MB image, structured OCSF output.

SwarmHack Team · 2026-05-13 · 12 min

TL;DR

  • The 22 native Active Directory agents that make up SwarmHack's AD suite, and the GOAP planner that sequences them
  • A native Rust protocol stack — LDAP, Kerberos, DCERPC (including DCSync), NTLMv2, BloodHound — replacing impacket + certipy + BloodHound.py + pywhisker + Responder + evil-winrm
  • Why this matters: dramatically faster cold start, much smaller image, zero on-disk Mimikatz/Rubeus/Responder, and structured OCSF crown jewels instead of free-form stdout

This is Part 3 of 3. In Part 1 we built a GOAD lab. In Part 2 we ran the autonomous kill chain to Domain Admin. Now we open the engine.

Series:
- Part 1 — Build the GOAD lab
- Part 2 — Run the engagement
- Part 3 — Under the hood (you are here)

1. Four layers, top to bottom

<diagram title="Runtime architecture — outermost to innermost">

┌────────────────────────────────────────────────────────────────┐
│  CLI Layer                                                     │
│  Argument parsing, target validation, kill-chain mode resolve  │
└──────────────────────┬─────────────────────────────────────────┘
                       │
┌──────────────────────┴─────────────────────────────────────────┐
│  Orchestrator                                                  │
│  GOAP planner, A* search, parallelism, rate limiting           │
└──────────────────────┬─────────────────────────────────────────┘
                       │
┌──────────────────────┴─────────────────────────────────────────┐
│  Agent Layer                                                   │
│  22 native AD agents + 22 web agents + network agents          │
└──────────────────────┬─────────────────────────────────────────┘
                       │
┌──────────────────────┴─────────────────────────────────────────┐
│  Protocol Layer                                                │
│  LDAP, Kerberos, DCERPC, HTTP-Auth, LLMNR/NBT-NS/MDNS,         │
│  BloodHound graph + collector                                  │
└────────────────────────────────────────────────────────────────┘

</diagram>

Each layer has exactly one responsibility. The CLI doesn't know about Kerberos. The protocol layer doesn't know about findings. Agents are the only thing that bridges intent (CLI) and wire (protocol).

2. The 22 AD agents

Sequenced by the GOAP planner into three logical phases. Within a phase, agents run concurrently with a bounded worker pool.

| Phase | Agent | What it does |

| ------- | ------- | -------------- |

| Discovery | LDAP enumeration | Anonymous bind, naming-context dump, SPN/UAC/adminCount/SD enumeration |

| | SMB enumeration | Default-credential sweep (Vagrant baseline), share enum, SYSVOL GPP cpassword |

| | LDAP spray | Username==password and seasonal-style sprays (rate-limited) |

| | FTP / SSH / WinRM / MSSQL probes | Auxiliary service detection + default cred test |

| | Host identification | Server-banner + SMB-OS identification |

| Credential production | Kerberos attack | Kerberoast (-m 13100) + AS-REP roast (-m 18200) + targeted Kerberoast |

| | LAPS reader | ms-Mcs-AdmPwd cleartext disclosure |

| | gMSA reader | msDS-ManagedPassword blob decode |

| | Credential dump | Unified hash repository, deduplication, hash-cracking queue |

| | NTLM downgrade | Detects environments allowing NTLMv1 / LM downgrade |

| Post-exploitation | ACL abuse | Native nTSecurityDescriptor walk + shortest path to DA |

| | Shadow Credentials | msDS-KeyCredentialLink add — V2 KeyCredential blob |

| | Delegation exploit | Unconstrained / constrained / RBCD detection + abuse |

| | GPO abuse | groupPolicyContainer walk + scheduled-task injection |

| | Authentication coercion | MS-RPRN PrinterBug, MS-EFSR PetitPotam, MS-DFSNM DFSCoerce |

| | ADCS exploit | ESC1–ESC15 template inspection + autonomous enrollment |

| | DCSync | MS-DRSR IDL_DRSGetNCChanges — native NTDS dump |

| | Pass-the-hash | NTLMv2 over SMB / WinRM / MSSQL |

| | Trust exploit | trustedDomain walk + cross-forest TGT abuse |

| | Flag collector | Final crown-jewel aggregation + provenance breadcrumbs |

That's 22 specialists, each owning one slice of the kill chain. None of them shells out to impacket-* or certipy for the core path.

3. The GOAP planner

The planner models a world state of boolean predicates — things like anonymous_bind_allowed, creds_ad_set, kerberoastable_spns_known, acl_chain_targets_known, da_tier_principal_identified — and each agent declares its preconditions and effects. For example:

  • LDAP enumeration requires LDAP reachability, and on success it learns whether anonymous bind is allowed, captures kerberoastable SPNs and AS-REP-roastable users, and identifies DA-tier principals.
  • Kerberos attack requires a list of kerberoastable SPNs, and on success produces Kerberos hashes ready for cracking.
  • Shadow Credentials requires both an ACL chain target and a usable AD credential, and on success compromises a DA-tier principal.

An A* search over those predicates produces a topologically sorted plan: discovery → credential production → post-exploitation. Within each rank, agents execute in parallel.

The point: add a new agent and the planner figures out where it belongs. No central registry of "agent X must run after agent Y" — just declare what you need and what you produce.

4. The shared-memory bus

Agents never call each other directly. They write into a namespaced shared-memory store and other agents read from it. Typical namespaces include:

  • Captured AD and SMB credentials (with provenance)
  • Kerberoastable SPNs and AS-REP-roastable users
  • ACL targets discovered by the graph walker (ForceChangePassword, GenericAll, AddKeyCredentialLink, RBCD…)
  • Discovered users and services (MSSQL, WinRM, IIS, WebDAV)

Every entry carries provenance breadcrumbs (where the credential came from, which step in the chain produced it) so downstream agents can reason about history. Cross-agent bridges — SMB → AD, ACL → Shadow Credentials, ACL → RBCD, ACL → Targeted Kerberoast — are just direct writes into the namespace the consuming agent reads.

This is what makes "one command, full kill chain" actually work.

5. The native protocol stack

This is the part that makes SwarmHack different from a Python-wrapper agent framework.

5.1 LDAP

Both reader and writer paths. The writer implements every primitive a real AD modify operation needs:

  • DACL grant — read existing nTSecurityDescriptor blob, parse per MS-DTYP §2.4.6, append a new ACCESS_ALLOWED_ACE, fix offsets, serialize, write
  • Set owner — the WriteOwner-attack primitive (followed up by a DACL grant)
  • Force change password — the ForceChangePassword path
  • Add KeyCredential — the Shadow Credentials primitive

SID parsing/serialization follows MS-DTYP §2.4.2.2. Every primitive produces the exact LDAP modify operation a real Active Directory accepts. The Shadow-Credentials blob is built per MS-ADTS §2.2.20 — the V2 KeyCredential structure (Version, KeyID, KeyHash, KeyMaterial, KeyUsage, KeySource, DeviceId, CustomKeyInfo, KeyApproximateLastLogonTimeStamp, KeyCreationTime). RSA-2048 public key as SPKI DER, private key as PKCS#8 DER+PEM.

5.2 Kerberos

A hand-rolled DER codec, RC4-HMAC etype 23 fully implemented per RFC 4757 §4, RFC 4120 §5 PDU structures. AES etypes 17/18 are decode-only today; the encrypt path gracefully falls back to impacket-GetUserSPNs for AES-only KDCs (modern Windows defaults). Native AES is on the roadmap.

The output formats hashcat directly — -m 18200 for AS-REP, -m 13100 for TGS — no in-the-middle reformatting step.

5.3 DCERPC

A small native stack covering exactly the opnums needed for autonomous AD exploitation:

| Protocol | Opnum(s) | Purpose |

| ---------- | ---------- | --------- |

| MS-RPCE | — | PDU codec (Bind, BindAck, Request, Response, Fault) |

| EPM | 3 | ept_map for dynamic-port resolution |

| MS-RPRN | 65 | PrinterBug |

| MS-EFSR | 0 + 13 alt | PetitPotam |

| MS-DFSNM | 13 | DFSCoerce |

| MS-DRSR | 3 | DCSync (IDL_DRSGetNCChanges) |

| MS-WCCE | 0 | ADCS RPC enrollment (ICertRequest) |

| LSARPC | 57, 76 | LookupSids2 / LookupNames3 |

Transport is ncacn_ip_tcp via the endpoint mapper on port 135 — sidestepping the SMB named-pipe transport entirely (every coercion target supports ncacn_ip_tcp, and TCP is markedly easier to implement than \PIPE\spoolss over SMB2).

The DCSync path includes a native FIPS 46-2 DES implementation for unicodePwd decryption per MS-DRSR §5.16. The remaining gap is NTLMSSP-DCERPC signing for live runs against a real DC — the codec is byte-exact and verified against fixtures, with a fallback to impacket-secretsdump behind the --allow-legacy-tools flag.

5.4 HTTP authentication

Native NTLM-over-HTTP and Kerberos-over-HTTP for ADCS web enrollment, WinRM, and SCCM AdminService. NTLM messages (Type-1, Type-2, Type-3) per MS-NLMP §2.2. NTLMv2 response computed via the standard HMAC-MD5(NTOWFv2(password), HMAC-MD5(NTOWFv2(password), challenge || temp)) construction. The 401-WWW-Authenticate handshake state machine selects NTLM > Negotiate > Basic. Connection: keep-alive is forced because NTLM is connection-oriented.

5.5 LLMNR / NBT-NS / MDNS

A native Responder replacement. UDP socket with SO_REUSEADDR set before bind (essential for the multicast case), join the appropriate multicast group, listen loop. Queries parsed per RFC 1035 (LLMNR / MDNS) or RFC 1002 §4.2 (NBT-NS), responses crafted with the attacker's IP as RDATA. The MDNS poisoner sets the cache-flush bit per RFC 6762 §10.2. The NBT-NS path includes the half-ASCII NetBIOS name codec.

5.6 BloodHound

Native BloodHound CE-format JSON ingest plus an in-memory directed graph with both objectid and name lookup indexes. The collector queries AD via the native LDAP client and emits BloodHound CE-compatible JSON without invoking BloodHound.py.

6. Why native instead of wrapping impacket?

Every section above could in principle be replaced by subprocess.run(["impacket-*", ...]). Many tools do exactly that. We don't. Here's why.

6.1 Image size and cold start

A wrapper-based stack typically extracts to several gigabytes of Docker image and takes around 12 seconds to cold-start (Python interpreter + venv + import resolution). A native binary fits in a fraction of that and starts in well under a second.

In a fleet of 50 pods, that's the difference between minutes of interpreter startup overhead and a few seconds of binary spawn.

6.2 Operational reproducibility

Wrapper tools depend on upstream Python package versions, which are not pinned at the OS level. An impacket update can change the wire format of one opnum, break four downstream tools, and produce silent test failures across an entire engagement portfolio. SwarmHack pins every protocol implementation. The wire format is constant; the upstream maintenance burden is decoupled.

6.3 Defender posture

Rubeus.exe is Defender-flagged. Mimikatz.exe is the most-blocked binary in enterprise environments. Responder.py triggers behavioral signatures on Windows DCs.

SwarmHack's native Kerberos client, native NTLM-relay, and native LSARPC client are all in-process Rust code. Zero on-disk binary executes on the target DC. No PowerShell module loaded. The kerberoast operation is bind() → write_socket() → read_socket() → format_string() — no Defender hook. This matters for engagements like GOAD's NHA variant (Defender enabled, no rockyou allowed).

6.4 Memory safety and concurrency

Rust's borrow checker eliminates the use-after-free / double-free class of bug that has shipped multiple CVEs in impacket over recent years. The agent harness uses an async worker pool with a bounded semaphore and a global rate-limit token bucket — wire-level concurrency without GIL overhead, with deterministic cancellation propagation. A wrapper-based stack runs each tool as a subprocess, with no cross-tool cancellation handle and no shared rate limit; one slow impacket-secretsdump invocation can block subsequent agents for tens of minutes.

6.5 Testability

Every native protocol module ships unit tests against fixture byte-streams. Examples:

  • Security descriptor parsing — synthetic SD blobs covering all DACL primitives plus edge cases
  • Kerberos ASN.1 — RFC 4120 DER-encoded test vectors
  • HTTP NTLM — known Type-2 challenge with verified Type-3 NTLMv2 response

A wrapper-based stack cannot achieve this level of determinism — every agent's output depends on the upstream tool's behavior, which depends on the Python interpreter version, which depends on the Linux distribution.

6.6 The full comparison

| Dimension | Wrapper stack | SwarmHack |

| ----------- | -------------- | ----------- |

| Cold start | Slow (interpreter + imports) | Sub-second native binary |

| Image size | Multiple GB | Compact single binary |

| Tools to install | 27+ pkgs + 4 tools + Ruby gem | 0 |

| Concurrency | Subprocess-level | Async pool + shared rate limit |

| AV signature surface | Rubeus / Mimikatz / Responder / pyKerberos | None — in-process Rust |

| nTSecurityDescriptor parser | impacket + pywhisker + BH.py (3 separate code paths) | One canonical implementation |

| Cross-tool credential bridge | Manual operator copy-paste | Shared-memory bus with provenance |

| Wire-format reproducibility | Depends on impacket version | Fixed at compile time |

| Defender posture | On-disk Python, signature-detected | In-process, no on-disk artifact |

| Evidence quality | Free-form stdout text | Structured OCSF crown jewels |

7. Operational defaults

A few things worth calling out for anyone planning to run this in production:

  • Authorization-first. SwarmHack only operates under explicit written contracts. AD engagements are particularly sensitive — successful exploitation typically reaches Domain Admin.
  • Read-only by default. Destructive primitives (DACL grants, owner changes, forced password resets, Shadow Credentials, RBCD) require --allow-write-operations. When enabled, every write is preceded by a backup of the original nTSecurityDescriptor blob, so post-engagement cleanup can restore the original DACL exactly.
  • Audit trail. Every agent invocation is logged with mission ID, timestamp, and the LDAP-bind credential used — sufficient for incident-response correlation against the target DC's event log (Event IDs 4624 / 4625 / 4768 / 4769 / 4776).
  • Compliance mapping. Findings are tagged with framework references — PCI-DSS 4.0, NIST CSF 2.0, OWASP Top 10 2021, SOC 2, HIPAA, ISO 27001:2022, DORA, NIS2.

8. What's on the roadmap

Three substantive items in flight:

1. PKINIT authentication. AS-REQ + TGS-REQ are in. Adding PA-PK-AS-REQ (PKCS#7 SignedData containing the certificate + signed AuthPack with timestamp + KDF parameters) closes the ADCS ESC1+ chain end-to-end. Once PKINIT lands, the administrator.pfx from Part 2 can be used to obtain a TGT for Administrator without a password — and DCSync against krbtgt yields the full domain hash dump.

2. NTLMSSP-DCERPC signing. The DCSync codec is byte-exact and unit-tested. The remaining gap is NTLMSSP signing-key derivation, MIC computation per MS-NLMP §3.4.4, and integration with the existing PDU codec. After this lands, DCSync runs natively without the impacket-secretsdump fallback.

3. AES Kerberos. Native CTS mode plus AES-PRF and key-derivation per RFC 3962.

9. Wrapping the series

Across three posts you've now:

1. Built a real Windows Active Directory lab with GOAD 2. Attacked it with one command and watched the GOAP planner sequence 22 native agents from anonymous bind to administrator.pfx in under 10 minutes 3. Looked inside the engine — GOAP planning, the shared-memory bus, deterministic protocol implementations from LDAP and Kerberos through DCERPC, and the operational case for native Rust over a Python wrapper stack

The headline number is the simplest one to remember:

One command. 15 findings. 11 crown jewels. 9 minutes 9 seconds. ESC1 certificate captured. Zero humans.

That's what autonomous Active Directory pentesting actually looks like when the architecture is built for it from the ground up. *Pwning is coming* — autonomously, at scale, with the same depth-first tradecraft a senior AD pentester applies, and with the rate-limiting and non-destructive defaults a production engagement requires.

If you want to bring this into your own environment, the Demo page is the right next step. The Docs cover agent configuration, custom payload sets, and the OCSF report schema in full.

Thanks for reading.