Inside NTDS.dit: the ESE database and the PEK
A domain controller stores every account hash in an ESE database. Here is how the datatable is laid out, how the Password Encryption Key is derived, and how each hash is unwrapped.
On a domain controller every account hash in the forest lives in a single file:
NTDS.dit. Unlike the registry hives it is a full ESE database (Extensible
Storage Engine, also called JET Blue) — the same engine behind Exchange. Dumping
it means parsing that database, then applying the same kind of layered
decryption you saw in the SAM.
The ESE layout
An ESE file is a tree of fixed-size pages (8 KiB for NTDS). The pieces a dumper cares about:
- The header (page 0) — version, revision, and page size.
- The catalog (page 4) — defines every table, and for each table its columns (name, identifier, type, codepage).
- The datatable — one giant table holding every directory object as a row.
Records are decoded in three regions: fixed-size columns, variable-size
columns (length array up front), and tagged columns (a sparse index of
present attributes). The columns we want carry cryptic names like ATTk589914
(unicodePwd / NT hash) and ATTr589970 (objectSid).
The Password Encryption Key
Hashes in NTDS are wrapped with a PEK rather than the boot key directly. The
PEK list is stored in the pekList attribute of the domain root object and is
encrypted under the boot key:
# up to Server 2012 R2
tmpKey = MD5(bootKey ‖ keyMaterial × 1000)
peks = RC4(tmpKey, encryptedPek)
# Server 2016+
peks = AES-CBC(bootKey, encryptedPek, keyMaterial)
A DC may hold several PEKs (after key rollover); each ciphered hash names its PEK index in its header byte.
Unwrapping a hash
For each account row, the unicodePwd (NT) and dBCSPwd (LM) blobs are peeled
in two layers — exactly mirroring the SAM, but keyed by the PEK instead of the
hashed boot key:
# layer 1 (PEK)
tmpKey = MD5(PEK[idx] ‖ keyMaterial) # or AES on 2016+
inner = RC4(tmpKey, encryptedHash)
# layer 2 (per-RID DES)
rid = big-endian last 4 bytes of objectSid
hash = removeDESLayer(inner, rid)
The RID comes from the account's objectSid. Only rows whose sAMAccountType
marks them as a user, machine, or trust account are dumped; the datatable also
holds groups, OUs, schema objects and much more.
Beyond NT hashes
The supplementalCredentials attribute additionally stores Kerberos keys
(AES256/AES128/DES) and, where reversible encryption is enabled, cleartext
passwords — derived with the same PEK unwrap. Those are what make a full
NTDS.dit dump so valuable to an attacker and so worth protecting: it is, quite
literally, every credential in the domain.
Defensive notes
NTDS.dit only leaves a DC through a backup, a volume shadow copy, or
ntdsutil — so the controls that matter are tight backup custody, monitoring
for shadow-copy creation and DRSUAPI replication (DCSync), and Tier-0 isolation
of domain controllers.