NTDS.dit Plugin
Description
This merge request introduces an initial implementation of an NTDS.dit Plugin. The parser can parse and decrypt secret records (e.g., user hashes, supplemental credentials) from an Active Directory NTDS database.
At this stage, the parser is in a late development preview:
Core functionality for parsing and decrypting secret records is available.
Support for additional record types (e.g., groups, ACLs, domain data) is not yet implemented.
Output support for BloodHound-compatible JSON is planned.
Automated tests have not been added yet — these will be included in a follow-up update.
The goal of creating this MR now is to gather early feedback and design review before finalizing the remaining features.
Checklist
- [x] Implementation (core)
- [ ] Tests
- [ ] BloodHound export
- [ ] Full record support
Regarding this PR, I've managed to get it to work with my own PR (https://github.com/fox-it/dissect.database/pull/8) by changing a few things:
In the __init__():
ntds_path_key = target.registry.value(
key="HKLM\\SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters",
value="DSA Database file"
)
self.ntds = NTDS(target.fs.open(ntds_path_key.value))
Changing all the record[NAME_TO_INTERNAL["XXX"]] references to record.XXX where XXX refers to the official ldapDisplayName, since these names are already automatically converted. Lastly, by changing the database data collection part:
def _collect_pek_and_user_records(self) -> tuple[bytes, Generator[User]]:
return next(self.ntds.lookup(objectCategory="domainDNS")).pekList, self.ntds.users()
and some other minor adjustments. Even on a small dataset that already seemed to result in a performance boost, since the NTDS class uses indexes to look for the pekList and User records.
Note: not all accounts seem to have entries for dBCSPwd, ntPwdHistory, lmPwdHistory and even unicodePwd, so that probably needs some try/exception wrapping.
Please let me know what you think of this and if you need any help with refactoring & editing the code. You can also DM me on the Dissect discord
Hi @joost-j ,
Thanks for the suggestion! I’ve converted the PR to use your approach, and it’s working well with the changes you mentioned. I’ve also wrapped the optional attributes (dBCSPwd, ntPwdHistory, lmPwdHistory, unicodePwd etc) as you noted. Really appreciate the guidance — the adjustments to record.XXX and the NTDS data collection logic make the code cleaner and improve performance.
Cool! Good to hear - a quick check from my side also confirms that it works well. When running your ntds.user_accounts plugin I can see the NTDS User class attributes, as well as the hashes, really nice! Hoping to wrap up the NTDS parser this year, so that this PR can be finished as well. Just a small remark; I did need to change the from Cryptodome.Cipher on line 9 to from Crypto.Cipher.
Cool! Good to hear - a quick check from my side also confirms that it works well. When running your
ntds.user_accountsplugin I can see the NTDSUserclass attributes, as well as the hashes, really nice! Hoping to wrap up theNTDSparser this year, so that this PR can be finished as well. Just a small remark; I did need to change thefrom Cryptodome.Cipheron line 9 tofrom Crypto.Cipher.
Hi @joost-j , Thanks for the insight! I’ve fixed that import as well.
By the way, do you plan to test your database parser against any older domain controllers? The extraction seems to be a bit different — especially the hash extraction — and I currently don’t have good test data from older DC versions to compare with.
By the way, do you plan to test your database parser against any older domain controllers? The extraction seems to be a bit different — especially the hash extraction — and I currently don’t have good test data from older DC versions to compare with.
That is the goal, but we will incrementally improve the parser.
CodSpeed Performance Report
Merging #1347 will not alter performance
Comparing B0TAxy:feautre/ntds_dit_parsing (d3d658b) with main (1337e8a)
Summary
✅ 9 untouched