Skip to content

Add/ tdo_dump module - #1274

Closed
Goultarde wants to merge 4 commits into
Pennyw0rth:mainfrom
Goultarde:add/tdo-dump-module
Closed

Add/ tdo_dump module#1274
Goultarde wants to merge 4 commits into
Pennyw0rth:mainfrom
Goultarde:add/tdo-dump-module

Conversation

@Goultarde

@Goultarde Goultarde commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

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 --ntds already does), but the LDAP aspect of the tool is what makes a standalone ldap module the natural fit because discovery is entirely LDAP-driven. The module enumerates every trustedDomain object and locates the source DSA (nTDSDSA) over LDAP before doing any replication, so it slots cleanly into the ldap protocol where that enumeration already lives, rather than into an SMB-based dumping path.

It reads trusted domain object (TDO) secrets via DRSUAPI replication (DRSGetNCChanges on the trustAuthIncoming / trustAuthOutgoing attributes) 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_dump reads 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).
  • Practical impact: with these keys an operator can forge inter-realm referral tickets to move laterally across a trust, which the --ntds output does not directly provide.

The two also read different objects and secrets: --ntds replicates the trust account (CN=<PARTNER>$,CN=Users) and its stored key material, while tdo_dump replicates the trustedDomain object (CN=<partner>,CN=System) and decrypts its trustAuth* cleartext. On top of that, the salt fed to string_to_key differs. Both salts follow the same {}krbtgt{} shape, but with different names (example with local SERVAL.INT and partner DEV.SERVAL.INT):

account key salt (--ntds, partner NetBIOS / short name):  {SERVAL.INT}krbtgt{DEV}
inter-realm salt (tdo_dump, partner FQDN):                {SERVAL.INT}krbtgt{DEV.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:

  • Ported from a standalone impacket script to a NetExec ldap module. GUIDs are auto-discovered: the module finds the source DSA objectGUID (nTDSDSA under CN=Configuration) and enumerates every trustedDomain object via LDAP, then iterates all trusts automatically. No manual --dsa-guid / --tdo-guid arguments.
  • objectGUID byte order is handled deterministically from the raw LDAP bytes (read back as bytes_le so impacket.uuid.string_to_bin reproduces 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.
  • Reuses the live NetExec session credentials (NTLM, LM/NT hashes, AES key, Kerberos / ccache). The DRSUAPI interface is bound over ncacn_ip_tcp via the endpoint mapper, with PKT_PRIVACY.
  • Two output formats: secretsdump (default, one key per line, colon-separated, greppable / tooling-friendly) and pretty (grouped per trust and direction, human-readable). Selectable with -o OUTPUT_FORMAT=pretty (optional, off by default).
  • Aligned with NetExec conventions (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 objectGUID byte-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

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Deprecation of feature or functionality
  • This change requires a documentation update
  • This requires a third party update (such as Impacket, Dploot, lsassy, etc)
  • This PR was created with the assistance of AI (list what type of assistance, tool(s)/model(s) in the description)

Setup guide for the review

  • Attacker host: Linux (Arch) container, NetExec installed via pipx.
  • Target: a Domain Controller (tested against Windows Server 2022, build 20348) in a domain that has at least one outbound or bidirectional trust, so that trustedDomain objects with trustAuth* attributes exist.
  • Privileges: an account with DS-Replication-Get-Changes rights (Domain Admin or equivalent), since the module performs a targeted DRSUAPI replication of the TDO.

Run:

# default (secretsdump-style, greppable)
nxc ldap "$DC_IP" -u "$USER" -p "$PASSWORD" -M tdo_dump

# Kerberos auth also supported
nxc ldap "$DC_IP" -u "$USER" -p "$PASSWORD" -k -M tdo_dump

# grouped, human-readable
nxc ldap "$DC_IP" -u "$USER" -p "$PASSWORD" -M tdo_dump -o OUTPUT_FORMAT=pretty

Screenshots (if appropriate)

Default
image

Pretty
image

Checklist

  • I have ran Ruff against my changes (poetry: poetry run ruff check ., use --fix to automatically fix what it can)
  • I have added or updated the tests/e2e_commands.txt file if necessary (new modules or features are required to be added to the e2e tests)
  • If reliant on changes of third party dependencies, such as Impacket, dploot, lsassy, etc, I have linked the relevant PRs in those projects
  • I have linked relevant sources that describes the added technique (blog posts, documentation, etc)
  • I have performed a self-review of my own code (not an AI review)
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (PR here: https://github.com/Pennyw0rth/NetExec-Wiki)

Goultarde and others added 4 commits June 6, 2026 05:48
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.
@Goultarde

Copy link
Copy Markdown
Contributor Author

Update: while working on this I realized the discovery can be done without LDAP at all, over LSARPC (LsarEnumerateTrustedDomainsEx).
So I built the whole thing as a secretsdump integration in impacket, behind a new -trust-keys flag, reusing the existing DCSync engine (RemoteOperations / NTDSHashes):

  • Discovery: LsarEnumerateTrustedDomainsEx (LSARPC) lists the trusts, then DRSCrackNames resolves each trustedDomain object to its objectGUID. No LDAP connection needed.
  • Extraction (online): the TDO is replicated with DRSGetNCChanges (EXOP_REPL_OBJ) requesting trustAuthIncoming / trustAuthOutgoing, then the inter-realm Kerberos keys (AES256/AES128 + RC4) are derived, exactly like this module
  • Offline: it also works on a parsed NTDS.dit (VSS / -ntds), by reading the trustAuth* columns and decrypting them with the PEK.

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.
If that niche is worth keeping we can have both, otherwise I'd rather close this PR in favor of the --ntds integration if the impacket change lands.

@NeffIsBack

Copy link
Copy Markdown
Member

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)

@Goultarde

Copy link
Copy Markdown
Contributor Author

Thanks for getting back to me. Here is the link to the PR: fortra/impacket#2207

@NeffIsBack NeffIsBack added the dependencies Pull requests that update a dependency file label Jul 14, 2026
@Goultarde

Copy link
Copy Markdown
Contributor Author

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).

@Goultarde Goultarde closed this Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants