Add/ tdo_dump module - #1274
Conversation
Read trusted domain object (TDO) secrets via DRSUAPI replication and derive the inter-realm Kerberos keys (AES256/AES128) and RC4 (NT) hash for each trust direction. Unlike --ntds, the keys are salted with the partner FQDN, giving the actual keys used for cross-realm referral tickets. Port of AlmondOffSec/tdo_dump.
|
Update: while working on this I realized the discovery can be done without LDAP at all, over LSARPC (LsarEnumerateTrustedDomainsEx).
Both paths now live entirely inside impacket's NTDSHashes, so the feature can be exposed directly through NetExec's --ntds with a new flag if it's merged into impacket, instead of as a standalone ldap module. Given that, I think the better home for this is nxc smb --ntds --trust-keys rather than this standalone module. The only thing this LDAP module does that the --ntds path can't is run without SMB protocole. |
|
Hi and thanks for the PR! Absolutely agreed, Imo its best for maintainability and usability if we just use it through impacket once it's merged. No need for some custom implementation (even if that means that we need SMB, which is available in 99.99% of the cases). Feel free to link the PR so we can have an eye on it (even if this PR is closed) |
|
Thanks for getting back to me. Here is the link to the PR: fortra/impacket#2207 |
|
The feature has indeed been merged into impacket, so I've opened a new PR to integrate it as options rather than a module (#1321). |
Add tdo_dump module: dump Trusted Domain Object secrets and derive inter-realm Kerberos keys
Description
This PR adds a new LDAP module,
tdo_dump, in the Credential Dumping category. It could conceivably be folded into a broader secrets-dumping feature (the key-derivation step is close to what--ntdsalready does), but the LDAP aspect of the tool is what makes a standaloneldapmodule the natural fit because discovery is entirely LDAP-driven. The module enumerates everytrustedDomainobject and locates the source DSA (nTDSDSA) over LDAP before doing any replication, so it slots cleanly into theldapprotocol where that enumeration already lives, rather than into an SMB-based dumping path.It reads trusted domain object (TDO) secrets via DRSUAPI replication (
DRSGetNCChangeson thetrustAuthIncoming/trustAuthOutgoingattributes) and derives the inter-realm Kerberos keys (AES256, AES128) and the RC4 (NT) hash for each trust direction.Why this is useful and how it differs from
--ntds:--ntds(DCSync) yields the trust account key, salted with the partner's NetBIOS (short) name. That is the key of the trust account object, not the key Kerberos actually uses on the wire for cross-realm referrals.tdo_dumpreads the TDO cleartext and re-derives the keys with the inter-realm salt (partner FQDN). These are the actual AES/RC4 keys used to sign and encrypt cross-realm referral tickets (the inter-realm ST issued by the TGS when a principal traverses the trust).--ntdsoutput does not directly provide.The two also read different objects and secrets:
--ntdsreplicates the trust account (CN=<PARTNER>$,CN=Users) and its stored key material, whiletdo_dumpreplicates the trustedDomain object (CN=<partner>,CN=System) and decrypts itstrustAuth*cleartext. On top of that, the salt fed tostring_to_keydiffers. Both salts follow the same{}krbtgt{}shape, but with different names (example with localSERVAL.INTand partnerDEV.SERVAL.INT):The two operands swap depending on the trust direction (Incoming uses
{LOCAL}krbtgt{PARTNER}, Outgoing uses{PARTNER}krbtgt{LOCAL}), and everything is upper-cased.Port of AlmondOffSec/tdo_dump by ThePirateWhoSmellsOfSunflowers.
Changes compared to the standalone tdo_dump:
ldapmodule. GUIDs are auto-discovered: the module finds the source DSAobjectGUID(nTDSDSAunderCN=Configuration) and enumerates everytrustedDomainobject via LDAP, then iterates all trusts automatically. No manual--dsa-guid/--tdo-guidarguments.objectGUIDbyte order is handled deterministically from the raw LDAP bytes (read back asbytes_lesoimpacket.uuid.string_to_binreproduces the on-wire GUID). Because the module has the actual bytes, it does not need the standalone's GUID normalization heuristic or the ambiguous-UUIDv4 retry logic that I pushed in a PR.ncacn_ip_tcpvia the endpoint mapper, withPKT_PRIVACY.secretsdump(default, one key per line, colon-separated, greppable / tooling-friendly) andpretty(grouped per trust and direction, human-readable). Selectable with-o OUTPUT_FORMAT=pretty(optional, off by default).CATEGORY.CREDENTIAL_DUMPING,parse_result_attributes,log.highlight/log.display).Dependencies: none new. Uses impacket (
drsuapi,epm,transport,krb5) and PyCryptodome (Cryptodome.Hash.MD4), both already required by NetExec.AI usage disclosure (per the AI policy): developed with Claude Code (Opus). AI assistance covered porting the standalone script into the NetExec module structure, the
objectGUIDbyte-order fix, the output formatting, and drafting this PR text. The underlying technique and the original standalone tool are by AlmondOffSec. I reviewed the code myself and tested it against a lab domain (see setup guide).Type of change
Setup guide for the review
trustedDomainobjects withtrustAuth*attributes exist.Run:
Screenshots (if appropriate)
Default

Pretty

Checklist
poetry run ruff check ., use--fixto automatically fix what it can)tests/e2e_commands.txtfile if necessary (new modules or features are required to be added to the e2e tests)