ntds
import "github.com/TheManticoreProject/Manticore/windows/database/ntds"
Package ntds implements the secret-decryption primitives for offline NTDS.dit (Active Directory database) analysis: decrypting the Password Encryption Key (PEK) list with a SYSTEM boot key, and decrypting the per-account hash blobs stored in NTDS attributes (unicodePwd, dBCSPwd, ntPwdHistory, lmPwdHistory, …).
The decryption matches the reference behaviour of impacket’s secretsdump (NTDSHashes): a PEK-keyed outer layer (RC4 or AES depending on the format version) followed by the RID-keyed DES layer of [MS-SAMR] 2.2.11.1. It is independent of how the encrypted bytes are obtained — an offline ESE reader or a remote DRSUAPI reply can both feed these functions.
References:
- [MS-SAMR] 2.2.11.1 Encrypting/Decrypting an NT or LM Hash Value
- impacket secretsdump.py NTDSHashes (__removeRC4Layer / __removeDESLayer, PEKLIST_*)
Index
- Constants
- func DecryptHash(peks []PEK, rid uint32, encrypted []byte) ([]byte, error)
- func DecryptHashHistory(peks []PEK, rid uint32, encrypted []byte) ([][]byte, error)
- func Dump(db *ese.Database, bootKey []byte, fn func(Account) error) error
- type Account
- type PEK
Constants
NTDS datatable column names. In NTDS.dit each attribute is an ESE column named “ATT<type><id>”; these are the columns needed to recover account secrets.
const (
AttSAMAccountName = "ATTm590045"
AttSAMAccountType = "ATTj590126"
AttObjectSid = "ATTr589970"
AttUserAccountControl = "ATTj589832"
AttUnicodePwd = "ATTk589914" // NT hash
AttDBCSPwd = "ATTk589879" // LM hash
AttNTPwdHistory = "ATTk589918"
AttLMPwdHistory = "ATTk589984"
AttPEKList = "ATTk590689"
AttUserPrincipalName = "ATTm590480"
AttSupplementalCredentials = "ATTk589949"
)
func DecryptHash
func DecryptHash(peks []PEK, rid uint32, encrypted []byte) ([]byte, error)
DecryptHash decrypts a single-hash attribute blob (e.g. unicodePwd / dBCSPwd) using the PEK list and the account’s RID, returning the raw 16-byte NT or LM hash. The PEK index is taken from the blob header.
func DecryptHashHistory
func DecryptHashHistory(peks []PEK, rid uint32, encrypted []byte) ([][]byte, error)
DecryptHashHistory decrypts a hash-history attribute blob (ntPwdHistory / lmPwdHistory) using the PEK list and the account’s RID, returning the sequence of raw 16-byte hashes (most recent first, as stored).
func Dump
func Dump(db *ese.Database, bootKey []byte, fn func(Account) error) error
Dump iterates the datatable of an opened NTDS database, decrypts each user account’s secrets with the PEK list (recovered from bootKey), and invokes fn for every account that has a sAMAccountName and objectSid. bootKey is the 16-byte SYSTEM boot key (the caller derives it).
type Account
Account is one decrypted NTDS account.
type Account struct {
SAMAccountName string
RID uint32
LMHash []byte // 16 bytes
NTHash []byte // 16 bytes
LMHistory [][]byte
NTHistory [][]byte
UserAccountControl uint32
HasUAC bool
}
func (*Account) Disabled
func (a *Account) Disabled() bool
Disabled reports whether the account’s userAccountControl marks it disabled.
func (*Account) HistoryLines
func (a *Account) HistoryLines() []string
HistoryLines formats the account’s password-history entries as secretsdump “user_historyN:rid:lm:nt:::” lines, skipping the current password (index 0).
func (*Account) SecretsdumpLine
func (a *Account) SecretsdumpLine() string
SecretsdumpLine formats the account as the secretsdump line “user:rid:lm:nt:::”.
type PEK
PEK is a 16-byte Password Encryption Key recovered from the pekList attribute.
type PEK []byte
func DecryptPEKList
func DecryptPEKList(bootKey, pekList []byte) ([]PEK, error)
DecryptPEKList decrypts the pekList attribute with the SYSTEM boot key and returns the contained PEKs (usually one). Both on-disk formats are supported, selected by the first header byte:
- 0x02 (up to Windows Server 2012 R2): RC4 keyed by MD5(bootKey + keyMaterial*1000).
- 0x03 (Windows Server 2016+): AES-CBC keyed by bootKey, IV = keyMaterial.
bootKey is the 16-byte boot key derived from the SYSTEM hive (the caller supplies it; boot-key derivation is out of scope here).
func FindPEKList
func FindPEKList(table *ese.Table, bootKey []byte) ([]PEK, error)
FindPEKList scans the datatable for the pekList attribute and decrypts it with bootKey.