# Two Checkmarks and the Keys to the Kingdom: How Active Directory's Replication Protocol Became the Longest-Lived Credential Attack on Windows

> MS-DRSR was designed for domain controllers to replicate secrets to each other. Its access check gates on an ACL, not on whether the caller is a DC. Eleven years after Mimikatz proved it, no patch can fix it.

*Published: 2026-05-21*
*Canonical: https://paragmali.com/blog/two-checkmarks-and-the-keys-to-the-kingdom-how-active-direct*
*License: CC BY 4.0 - https://creativecommons.org/licenses/by/4.0/*

---
<TLDR>
Active Directory's replication protocol (MS-DRSR) lets any domain controller pull every secret in the directory from any other domain controller, and the access check gates on an ACL, not on whether the caller is actually a DC. Mimikatz's `lsadump::dcsync` (August 11, 2015) and `lsadump::dcshadow` (January 2018) are the read and write sides of this loophole; both remain in active operational use eleven years later because the protocol cannot be amended without breaking AD replication. Credential Guard cannot help -- the secret moves from a remote DC's NTDS.dit over the network, never touching the attacker's LSASS. The 2026 defender response is four parallel detection layers (posture, behavioral, network, graph) because no single layer catches every case, and the architectural fifth layer that would close the class is not coming.
</TLDR>

## 1. Two Checkmarks and the Keys to the Kingdom

A non-admin service account, created during a 2017 file-server migration and never cleaned up, holds exactly two access-control entries on the `contoso.local` domain object: `Replicating Directory Changes` and `Replicating Directory Changes All`. From a Windows 11 workstation -- not a domain controller, not a tier-zero admin host, just a desk -- the attacker opens a Mimikatz prompt and types:

```
lsadump::dcsync /domain:contoso.local /user:krbtgt
```

Sixty seconds later they have the `krbtgt` NT hash and [Kerberos AES keys](/blog/kerberos-in-windows-the-other-half-of-ntlmless/), and they own the domain. [Credential Guard](/blog/the-empty-hash-credential-guard-the-lsaiso-trustlet-and-the-/), enabled on every workstation in the building, never saw the credential transit anywhere in its scope.

That last sentence is the punchline of the entire field. The secret never touches the attacker's LSASS, so Credential Guard's design has nothing to say. The keys move from a remote domain controller's on-disk database, over an RPC channel, into the attacker's process memory. Credential Guard isolates LSASS on the *local* machine. It has no jurisdiction over what a remote DC chooses to send when asked nicely.

> **Note:** Credential Guard isolates LSASS-resident secrets in a separate virtual trust level. DCSync reads secrets from a remote DC's NTDS.dit over MS-DRSR -- a network protocol attack, not a local-memory attack. Microsoft's Credential Guard documentation does not claim protection against MS-DRSR-based credential extraction [@credential-guard-considerations].

The 60-second timeline is not a marketing number. One RPC round trip per principal is the protocol's per-principal cost, fixed by specification. For a single high-value target -- and `krbtgt` is the highest-value target in any Active Directory forest, because its long-term key signs every Kerberos ticket -- the attack is over before a typical SOC operator has finished pouring coffee.

The four people whose names recur in this story are worth meeting now. Benjamin Delpy (gentilkiwi) wrote Mimikatz and authored both the DCSync read primitive (2015) and, with Vincent Le Toux, the DCShadow write primitive (2018). Vincent Le Toux built the original DCShadow implementation and the PingCastle posture-assessment tool. Sean Metcalf (Trimarc / ADSecurity) translated DCSync into operator vocabulary in the weeks after its release. Microsoft's Defender for Identity team ships the canonical first-party detections that fire on this attack class today.

This story is what happens when a 23-year-old domain-controller-to-domain-controller protocol's access check turns out to gate on an ACL, not on whether the caller is a domain controller. Two ACEs on one object should not give an attacker the entire credential trove. The fact that they do is not a bug in the implementation. It is a 1999 design assumption -- that nobody who is not a DC would ever speak this protocol -- that survived twenty-five years as a social convention and was nowhere written down in the access-check code.

The next section explains why Microsoft built this protocol in the first place, and why its access check forgot to ask the one question that would have closed the loophole.

## 2. Why the Protocol Exists at All

Rewind to February 17, 2000. Microsoft ships Windows 2000 Server, and Active Directory replaces the old NT 4.0 PDC/BDC model [@microsoft-windows-2000-launch]. NT 4.0 was single-master: one Primary Domain Controller held the authoritative account database, every Backup Domain Controller held a read-only copy, and a password change on the PDC propagated to BDCs through a hub-and-spoke replication channel. AD inverts the model. Every domain controller in an AD domain holds a writable copy of the directory; a password change on *any* DC must propagate to every other DC within minutes; the replication topology is a mesh, not a star.

Multi-master replication is the design constraint that forces MS-DRSR's existence. If any DC can accept a write, then every DC must be able to pull every change -- *including secret attributes* like `unicodePwd`, `dBCSPwd`, `ntPwdHistory`, `lmPwdHistory`, and `supplementalCredentials` -- from every other DC. There is no second protocol. Replication of secrets is replication, and replication is MS-DRSR [@ms-drsr-spec].

<Definition term="MS-DRSR (Directory Replication Service Remote Protocol)">
The RPC protocol by which any Active Directory domain controller can replicate any object, including secret attributes, from any other DC in the same forest. Layered over DCE/RPC; the current public specification is revision 46.0 dated March 9, 2026 [@ms-drsr-spec]. MS-DRSR is the mechanism that makes AD's multi-master replication invariant work.
</Definition>

<Definition term="IDL_DRSGetNCChanges">
The MS-DRSR method (opnum 3) that returns changed objects within a naming context. A calling DC supplies a target DN and a set of replication flags; the called DC returns the object's attribute values, with secret attributes encrypted under a session-derived key. This is the protocol method that DCSync invokes.
</Definition>

<Mermaid caption="MS-DRSR's place in the AD multi-master replication model: every DC pulls every change, including secret attributes, from every peer DC over IDL_DRSGetNCChanges.">
flowchart LR
    subgraph Domain["contoso.local domain"]
        DCA[DC-A<br/>NTDS.dit]
        DCB[DC-B<br/>NTDS.dit]
        DCC[DC-C<br/>NTDS.dit]
    end
    DCA -- IDL_DRSGetNCChanges<br/>unicodePwd, ntPwdHistory --> DCB
    DCB -- IDL_DRSGetNCChanges<br/>supplementalCredentials --> DCA
    DCB -- IDL_DRSGetNCChanges<br/>dBCSPwd --> DCC
    DCC -- IDL_DRSGetNCChanges<br/>unicodePwd --> DCB
    DCA -- IDL_DRSGetNCChanges<br/>ntPwdHistory --> DCC
    DCC -- IDL_DRSGetNCChanges<br/>krbtgt keys --> DCA
</Mermaid>

The access check that gates `IDL_DRSGetNCChanges` is defined in [MS-DRSR] §4.1.10. It asks one question: does the calling principal hold the appropriate extended rights on the naming-context root being replicated? Extended rights are AD's mechanism for control-access permissions that do not fit into the standard ACL bit set [@ms-drsr-spec].

<Definition term="Extended Right">
A schema-defined access-control right identified by a GUID rather than by a standard ACL bit. Extended rights are granted via an access-control entry on a target object and checked at runtime by the operation that requires them. The three replication extended rights are the canonical example: each is identified only by GUID, and each is checked by `IDL_DRSGetNCChanges` against the caller's effective rights on the naming-context root.
</Definition>

<Definition term="Naming Context (NC)">
A top-level replication partition of the Active Directory database. Every AD forest has at least three NCs: the Schema NC, the Configuration NC, and one Domain NC per domain (plus optional Application NCs). DCSync operates against the Domain NC root.
</Definition>

Three extended rights together form what practitioners call the rights triad. The two-checkmark version from §1 is the most-common configuration; the third right unlocks a smaller set of attributes flagged confidential. Microsoft's own AD schema documentation enumerates each one [@ad-schema-get-changes][@ad-schema-get-changes-all][@ad-schema-get-changes-filtered-set]:

<Definition term="Rights Triad">
The combination of three replication extended rights on a naming-context root: `DS-Replication-Get-Changes` (the baseline read), `DS-Replication-Get-Changes-All` (unlocks secret attributes), and `DS-Replication-Get-Changes-In-Filtered-Set` (unlocks attributes with the confidential flag). The first two are what most operators mean when they say "DCSync rights"; the third is sometimes required for completeness.
</Definition>

| Right | Display name | Rights-GUID | What it unlocks | Introduced |
|---|---|---|---|---|
| `DS-Replication-Get-Changes` | Replicating Directory Changes | `1131f6aa-9c07-11d1-f79f-00c04fc2dcd2` | Baseline replication read | Windows 2000 Server [@ad-schema-get-changes] |
| `DS-Replication-Get-Changes-All` | Replicating Directory Changes All | `1131f6ad-9c07-11d1-f79f-00c04fc2dcd2` | Secret attribute replication (`unicodePwd`, `ntPwdHistory`, etc.) [@ad-schema-get-changes-all] | Windows Server 2003 |
| `DS-Replication-Get-Changes-In-Filtered-Set` | Replicating Directory Changes In Filtered Set | `89e95b76-444d-4c62-991a-0facbeda640c` | Confidential-flag attributes [@ad-schema-get-changes-filtered-set] | Windows Server 2008 |

In a freshly-installed AD domain, four principal sets hold the rights triad by default on the Domain NC root: the `Domain Controllers` group, the `Domain Admins` group, the `Enterprise Admins` group, and the built-in `Administrators` group [@sean-metcalf-dcsync-2015]. Every other ACE on that object is a deliberate delegation choice made by some operator at some point in the domain's history.

Now read the access check carefully. It asks whether the caller holds the rights. It does not ask anything about *who* the caller is. The protocol does not check whether the caller's Service Principal Name is `GC/...` or `ldap/...` or anything else. It does not check whether the caller's machine account is in the `Domain Controllers` group. It does not check whether the caller's Kerberos service ticket was issued for a DC service. The whole gate is the ACL on one object.

<Sidenote>This is the most consequential design omission in any Microsoft network protocol. Every other replication-style protocol in Windows ships some form of caller-machine-identity assertion. MS-DRSR shipped without one because, in 1999, nobody on the design team imagined an unprivileged workstation would speak the DC-to-DC protocol.</Sidenote>

The 1999 closed-population design assumption is the load-bearing fiction underneath this whole story. The protocol's designers assumed -- entirely reasonably for the deployment universe of that decade -- that the only software that would ever implement an MS-DRSR client was the DC role itself, which Microsoft shipped, signed, and audited. No defender in 1999 was thinking about a Python script speaking DRSUAPI from a desk. The assumption was a social convention dressed as a security model, and it survived for fifteen years.

If the protocol exists to replicate every secret in the directory, and the access check is the ACL on one object, what stopped anyone from abusing it for the protocol's first fifteen years? That accidental safety period is the subject of the next section.

## 3. The Fifteen-Year Accidental Safety Period

Between 2000 and 2015, attackers who wanted the entire credential trove of an Active Directory domain had exactly one road open to them: reach a domain controller and steal its database. The reasons are not subtle. The credentials lived in one file -- `C:\Windows\NTDS\NTDS.dit`, an Extensible Storage Engine database protected by a Password Encryption Key wrapped under the BootKey scattered across four registry values in the SYSTEM hive [@ntdsxtract-repo]. Without code execution on a DC, there was no obvious way to ask the directory for its secrets in bulk.

<Definition term="NTDS.dit">
The on-disk Extensible Storage Engine ("Jet Blue") database that holds every Active Directory object, including secret attributes. Located at `C:\Windows\NTDS\NTDS.dit` on every domain controller. The file is locked while AD DS is running; offline copying requires either Volume Shadow Copy snapshots, `ntdsutil ifm` bundling, or DC downtime [@mitre-t1003-003].
</Definition>

Three sub-techniques bloomed inside this constraint, and MITRE catalogs them collectively as T1003.003 OS Credential Dumping: NTDS [@mitre-t1003-003]. Each is operationally distinct, and each requires the same precondition.

**G1a -- Volume Shadow Copy plus offline parse.** The attacker runs `vssadmin create shadow /for=C:` on a domain controller, creating an instant point-in-time snapshot. They copy `NTDS.dit` and `SYSTEM` out of the shadow path, exfiltrate both files, and parse them offline with Csaba Barta's `ntdsxtract` toolkit [@ntdsxtract-repo] or Impacket's `secretsdump.py` running in offline mode. The parse walks the `datatable` ESE table row by row, decrypts the PEK with the BootKey, and decrypts each principal's secret attributes with the PEK.

**G1b -- `ntdsutil` Install From Media.** The built-in command `ntdsutil "activate instance ntds" "ifm" "create full C:\Temp\IFM"` packages NTDS.dit and the SYSTEM hive into a clean bundle, intended for legitimate seeding of a new replica DC. With local admin on any DC, an attacker invokes it without involving VSS.

**G1c -- LSASS injection of the long-term DC secrets.** Mimikatz's pre-DCSync technique reads cached long-term secret material (including the `krbtgt` key) directly out of `lsass.exe` memory on a domain controller via `lsadump::lsa /inject /name:krbtgt`. The variant that turned this surface into a persistence implant was Skeleton Key, disclosed by the Dell SecureWorks Counter Threat Unit on January 12, 2015 [@skeleton-key-wayback]. Skeleton Key patches the [NTLM](/blog/ntlmless-the-death-of-ntlm-in-windows/) and Kerberos validation routines in LSASS on a DC so that a single master password works for any account.

<Sidenote>Skeleton Key sits at the boundary between credential dumping and persistence. It does not produce usable NT hashes or Kerberos keys for offline forging; it gives the attacker a backdoor at the authentication step. The 2015 community debate over Skeleton Key versus the (then-imminent) DCSync technique was settled decisively by DCSync's August release: dumping the hashes is more useful than backdooring the auth path, because dumped hashes survive a DC reboot and are forge-ready material [@skeleton-key-wayback].</Sidenote>

| Sub-technique | Mechanism | Canonical tool | Emitted artifact | Host artifact on DC |
|---|---|---|---|---|
| G1a VSS + offline parse | Point-in-time snapshot, file copy, offline ESE parse | `vssadmin` + `secretsdump.py` / `ntdsxtract` | NT hashes, Kerberos keys, password history | VSS event in system log |
| G1b `ntdsutil` IFM | Built-in admin command produces clean bundle | `ntdsutil "ac i ntds" "ifm"` | Identical to G1a | IFM staging directory |
| G1c LSASS inject | Read long-term secrets from `lsass.exe` memory | `mimikatz lsadump::lsa /inject` | `krbtgt` key and other LSA-cached secrets | Process access of `lsass.exe` |

The structural cost across all three is the same: code execution as SYSTEM on a domain controller. Read that requirement slowly. In a mature enterprise with even a basic tiered-administration model -- no interactive logon to DCs from workstations, [restricted-admin RDP](/blog/rdp-authentication-26-years/), Privileged Access Workstations for Domain Admin sessions, Protected Users group membership for Tier Zero principals -- DCs are the most defended boundary in the network. An attacker who has crossed that boundary has, in operational terms, already won. The credential dump is the trophy lap.

<Mermaid caption="Generation 1's three roads to credential extraction. All three converge on the same precondition: SYSTEM-level code execution on a domain controller.">
flowchart TD
    Start[Attacker foothold<br/>on workstation]
    Start --> VSS[G1a: VSS snapshot<br/>+ offline parse]
    Start --> IFM[G1b: ntdsutil IFM<br/>create full]
    Start --> Inject[G1c: LSASS inject<br/>on DC]
    VSS --> Gate[SYSTEM on DC]
    IFM --> Gate
    Inject --> Gate
    Gate --> Creds[All domain credentials<br/>NT hashes, Kerberos keys, krbtgt]
</Mermaid>

This is what made the closed-population design assumption survive its first fifteen years. The protocol's design was open to abuse from any joined workstation that held the rights triad, but nobody noticed because the cheaper attack road -- compromise a DC, parse the database offline -- still passed through a defended chokepoint. The ACL on the Domain NC was nowhere on a defender's risk register, because no offensive tooling existed that treated the ACL as the gate.

There was a question waiting to be asked, though. *What if you could ask the DC to send you the credentials, using a protocol the DC already speaks fluently with its peers?* The protocol's wire format is published. Microsoft Learn hosts the IDL. The DCs trust each other's calls by ACL. The only missing piece was a client implementation that any attacker could run from any joined machine.

In August 2015 the missing piece arrived.

## 4. Generation by Generation

August 11, 2015, 01:27 Central European Time. Benjamin Delpy commits 47,132 insertions to the Mimikatz repository in a single push titled *"DCSync in mimikatz & for XP/2003."* The diff introduces four new modules -- `kull_m_rpc_drsr.c/.h` and `kull_m_rpc_ms-drsr.h/_c.c` -- generated from the [MS-DRSR] IDL. They are an MS-DRSR client surface, slotted into `kuhl_m_lsadump.c` as the new `lsadump::dcsync` command [@mimikatz-dcsync-commit][@mimikatz-repo]. Vincent Le Toux is credited as co-author.

Six weeks later Sean Metcalf writes the canonical operator post and presents the technique at DerbyCon V on Friday, September 25, 2015, in the Track 1 (Break Me) slot from 3:00 to 3:50 pm [@sean-metcalf-dcsync-2015][@sean-metcalf-derbycon-2015]. The closed-population assumption shatters in a weekend.

<Aside label="A note on the Mimikatz commit hash">
A commit hash widely cited in operator forums for the DCSync introduction -- `79b3577aed999baac0352cb1ba3a5f86b6d29f34`, dated 2015-07-20 -- does not exist in the Mimikatz repository. A `git --no-pager log --all` against a fresh clone returns `fatal: bad object` for that hash; the GitHub commit URL returns 404; the GitHub API returns 422. The actual introducing commit is `7717b7a7173fa6a6b6566bbbc3e7372b464d988f`, authored by Benjamin DELPY at 2015-08-11 01:27:13 +0200, with the subject line *"DCSync in mimikatz & for XP/2003"* [@mimikatz-dcsync-commit]. Sean Metcalf's contemporary writeup at ADSecurity confirms August 2015 as the release month [@sean-metcalf-dcsync-2015]. The lesson for verifying any third-party claim about a Mimikatz commit is the same lesson Stage 1 of this article's research pipeline applied: clone the repo, run `git show`.
</Aside>

What follows is the story of four generations of attacker approaches against this protocol, each motivated by the operational limit of the previous one.

### Generation 2: DCSync (2015-08-11 onward)

Mimikatz becomes a DRSUAPI client. Operator invocation is one line:

```
lsadump::dcsync /domain:contoso.local /user:krbtgt
```

Behind that line, six wire steps fire:

<Mermaid caption="DCSync on the wire: the six-step IDL_DRSGetNCChanges round trip. The attacker binds to the target DC's DRSUAPI endpoint, requests one principal, decrypts the reply with the session-derived key, and emits a hash record.">
sequenceDiagram
    participant A as Attacker workstation
    participant DC as Target DC (DRSUAPI)
    A->>DC: 1. resolve target DC by domain
    A->>DC: 2. IDL_DRSBind (interface UUID DRSUAPI)
    DC-->>A: 3. bind reply (handle, session key)
    A->>DC: 4. IDL_DRSGetNCChanges (opnum 3, MTX_ADDR DN, EXOP_REPL_OBJ)
    DC-->>A: 5. DRS_MSG_GETCHGREPLY (encrypted secret attributes)
    A->>A: 6. decrypt with session key, emit NT hash and Kerberos keys
</Mermaid>

The wire format is one round trip per principal. To dump every account in the domain, Mimikatz's `/all /csv` mode iterates the request server-side via the same opnum with paginating up-to-date vectors. Per-call wire size is a few kilobytes for a single user; full-domain bulk dumps run to tens of megabytes. Wall-clock time is dominated by network latency to the target DC [@sean-metcalf-dcsync-2015][@trellix-silent-domain-hijack].

The Generation-1 precondition is gone. The attack runs from any joined workstation. The Generation-1 host artifacts -- VSS events, IFM staging directories, multi-megabyte file copies -- all disappear. The only on-the-wire signature is *"an `IDL_DRSGetNCChanges` call from an IP that is not a domain controller"* -- which has to be detected at the protocol layer, not the host layer.

Sean Metcalf's 2015 post named the detection recipe in the same breath as the attack: *"Configure IDS to trigger if DsGetNCChange request originates an IP not on the 'Replication Allow List' ..."* [@sean-metcalf-dcsync-2015]. Every network detection rule in this space, including Microsoft's own ATA "Unusual Protocol Implementation" category two years later, descends from that one sentence [@ata-v17-release-notes].

DCSync is read-only. The ACL pair the attack exploits does not include directory-write rights. An attacker who wants to plant SID-history backdoors, modify `userAccountControl` flags, or write to AdminSDHolder needs a different primitive. The same trust loophole that gave them read access turns out to support a symmetric write side.

### Generation 3: DCShadow (2018-01-24)

January 23-24, 2018. Benjamin Delpy and Vincent Le Toux present at BlueHat IL in Tel Aviv. The talk is titled *"Active Directory: What can make your million dollar SIEM go blind?"* [@youtube-bluehat-2018-dcshadow]. Four days later, on January 27, 2018, Delpy pushes the mainline merge to Mimikatz with commit `ab18bd1`, subject *"Pushing @vletoux DCShadow in current branch with some adaptations"* [@mimikatz-dcshadow-commit].

<Sidenote>Vincent Le Toux contributed the original implementation; the BlueHat IL talk shared joint Delpy/Le Toux billing; the Mimikatz mainline merge commit `ab18bd103a5cd7e26fb8d475c5ea0157d6633ca9` is dated 2018-01-27 01:37:55 +0100, four days after the conference disclosure. Le Toux is the same author behind PingCastle's posture-assessment tooling and the canonical `dcshadow.com` reference site [@mimikatz-dcshadow-commit][@dcshadow-com].</Sidenote>

DCShadow inverts DCSync. Where DCSync makes the attacker a DRSUAPI client, DCShadow makes the attacker a DRSUAPI *server*. The mechanism: temporarily register an `nTDSDSA` object plus the associated SPNs in the Configuration NC; signal to a legitimate DC that the new "DC" wants to push changes via `IDL_DRSReplicaAdd` followed by `IDL_DRSReplicaSync`; the legitimate DC pulls the attacker's pre-staged writes through the replication channel as if they were legitimate peer replication; deregister to clean up [@mitre-t1207][@dcshadow-com].

<Mermaid caption="DCShadow: register, replicate, deregister. The attacker temporarily becomes a DC in the Configuration NC, pushes arbitrary writes through the replication channel that the directory's audit subsystem ignores by design, and removes the rogue registration afterwards.">
sequenceDiagram
    participant A as Attacker (rogue DC)
    participant Cfg as Configuration NC
    participant T as Target DC
    A->>Cfg: 1. create nTDSDSA + server object
    A->>Cfg: 2. add SPNs (GC, DRS UUID) to machine account
    A->>T: 3. IDL_DRSReplicaAdd (dsaSrc = attacker)
    T->>A: 4. IDL_DRSGetNCChanges callback (target pulls)
    A-->>T: 5. staged writes returned as replication payload
    A->>T: 6. IDL_DRSReplicaDel
    A->>Cfg: 7. delete nTDSDSA, remove SPNs
</Mermaid>

<Definition term="SACL-silent">
A directory operation that does not generate the standard Event ID 4662 / 4738 / 5136 object-modification events that the Domain Services Auditing subsystem emits for normal writes. Legitimate DC-to-DC replication is SACL-silent by design -- the audit subsystem intentionally suppresses change events on the replication channel to avoid drowning every DC in audit traffic on every directory change. DCShadow writes are SACL-silent because they ride the same channel.
</Definition>

That last design fact is the entire point of the talk title. A SIEM watching object-modification events sees nothing when a DCShadow write lands, because the modifications arrive on the replication channel that the SIEM intentionally ignores as routine DC chatter. The original 2018 framing -- "your million-dollar SIEM goes blind" -- was correct for SIEMs that monitored only object-modification events. We will see in §6 how that framing has aged.

DCShadow is *write* capability. An attacker who reaches it can plant SID-history backdoors (write `S-1-5-21-...-519` for Enterprise Admins into a low-privileged account's `sIDHistory`), modify `userAccountControl` to clear `ACCOUNTDISABLE` on dormant high-privilege accounts, add ACEs to AdminSDHolder (which then propagate via the SDProp process to every protected admin account every 60 minutes), or set `msDS-AllowedToActOnBehalfOfOtherIdentity` on a target computer to enable resource-based constrained delegation chains.

### Generation 4: Permission-graph attacks (2018-present)

By 2018 the attack-side question had shifted. *Holding the rights triad directly* was no longer the interesting precondition; the interesting question was how to reach it transitively, through whatever chain of ACL delegations a real-world domain happened to contain. The umbrella term that emerged is permission-graph attack. Its terminal edge is still DCSync; its novelty is the path that gets you to the terminal.

Three primitives anchor this generation. Elad Shamir's *Wagging the Dog* (January 28, 2019) showed how writing `msDS-AllowedToActOnBehalfOfOtherIdentity` on a target computer object enables S4U2Self plus S4U2Proxy impersonation of any user to that computer [@shenaniganslabs-wagging-the-dog]. Shamir's *Shadow Credentials* (June 21, 2021) demonstrated that writing `msDS-KeyCredentialLink` on a target account adds an attacker-controlled certificate trust, enabling Kerberos PKINIT authentication as that account without resetting its password [@eladshamir-shadow-credentials]. And Sean Metcalf's earlier work on AdminSDHolder template abuse showed that writing the AdminSDHolder ACL causes SDProp to propagate the new ACL onto every protected admin account every 60 minutes -- self-healing persistence that survives defender cleanup [@sean-metcalf-adminsdholder].

The unifying observation: DCSync is a terminal in a permission graph. The graph's nodes are AD principals; its edges are individual access-control entries (`WriteDACL`, `WriteOwner`, `GenericAll`, `WriteProperty`, `AddMember`, `ForceChangePassword`). Whoever can traverse the graph to the rights triad on the domain root has DCSync transitively. The attacker's job is no longer to invoke `IDL_DRSGetNCChanges`; it is to find the shortest path through the graph from a foothold to the rights triad. The defender's mirror job is to enumerate all paths into the rights triad and either prune them or monitor them.

<Mermaid caption="The replication attack class timeline, 2000-2026. Attacker generations are stacked above; major defender milestones are stacked below.">
gantt
    title Domain replication attack class evolution
    dateFormat YYYY-MM-DD
    axisFormat %Y
    section Attack
    Gen 1 NTDS.dit theft           :a1, 2000-02-17, 2015-08-11
    Gen 2 DCSync                   :active, a2, 2015-08-11, 2026-12-31
    Gen 3 DCShadow                 :active, a3, 2018-01-24, 2026-12-31
    Gen 4 Permission-graph chains  :active, a4, 2018-06-01, 2026-12-31
    section Defense
    BloodHound 1.0 DEF CON 24     :d1, 2016-08-06, 30d
    ATA 1.7 release                :d2, 2017-04-01, 30d
    Azure ATP DCShadow alerts      :d3, 2018-07-24, 30d
    Azure ATP rebrand to MDI       :d4, 2020-09-22, 30d
    BloodHound v6.0                :d5, 2024-09-30, 30d
    BloodHound v6.3 Butterfly      :d6, 2024-12-09, 30d
    BloodHound v8.0 OpenGraph      :d7, 2025-07-29, 30d
    Trellix NDR Silent Hijack      :d8, 2025-12-08, 30d
</Mermaid>

To make the read-side access check decision concrete -- and to expose how thin it actually is -- here is the logic of the gate written as a runnable function. This is pseudocode for the *decision*, not the protocol bytes:

<RunnableCode lang="js" title="DCSync access check decision (illustrative simulation, not exploit code)">{`
// Illustrative model of the MS-DRSR access check on IDL_DRSGetNCChanges.
// Returns "secrets returned" or "ACCESS_DENIED" given a caller's effective
// rights on the naming-context root.
function dcsyncAccessCheck(callerRights, ncRootDn, requestedAttrs) {
  const GET_CHANGES         = '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2';
  const GET_CHANGES_ALL     = '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2';
  const GET_CHANGES_FILTERED = '89e95b76-444d-4c62-991a-0facbeda640c';

  const SECRET_ATTRS = new Set(['unicodePwd', 'dBCSPwd',
    'ntPwdHistory', 'lmPwdHistory', 'supplementalCredentials']);
  const wantsSecrets = requestedAttrs.some(a => SECRET_ATTRS.has(a));

  // Baseline read requires GetChanges.
  if (!callerRights.has(GET_CHANGES)) return 'ACCESS_DENIED';
  // Secret attributes require GetChangesAll.
  if (wantsSecrets && !callerRights.has(GET_CHANGES_ALL))
    return 'ACCESS_DENIED';

  // Notice what is NOT checked: caller's SPN, machine-group membership,
  // ticket service, source IP. The access gate is the ACL, full stop.
  return 'secrets returned';
}

const attacker = new Set([
  '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2',
  '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2',
]);
console.log(dcsyncAccessCheck(attacker, 'DC=contoso,DC=local',
  ['unicodePwd', 'ntPwdHistory']));
`}</RunnableCode>

Four generations, eleven years, no Generation 5 in sight. What is the single insight that lets all of this exist, and why has nobody patched it?

## 5. There Is No DC Check

The whole structural error of this protocol is one missing question. Specification [MS-DRSR] §4.1.10 defines the access check on `IDL_DRSGetNCChanges` as: does the calling principal hold the rights triad on the naming context being replicated? [@ms-drsr-spec] That is the whole gate. There is no second check that asks "is the caller's service principal name a domain-controller SPN?" or "is the caller's machine account in the `Domain Controllers` group?" or "was the caller's Kerberos service ticket issued for a DC service?" The spec is empirically and literally just an ACL check on the NC root.

The empirical proof that a non-DC client succeeds lives in Mimikatz's `kuhl_m_lsadump.c` and its DRSUAPI client surface in `kull_m_rpc_drsr*` [@mimikatz-repo]. Mimikatz is software running on a workstation. It binds to the DRSUAPI interface UUID, calls `IDL_DRSGetNCChanges`, and the call returns. The protocol's behavior is correct by specification. The security model is the bug.

<PullQuote>
"A major feature added to Mimkatz in August 2015 is 'DCSync' which effectively 'impersonates' a Domain Controller and requests account password data from the targeted Domain Controller. DCSync was written by Benjamin Delpy and Vincent Le Toux." -- Sean Metcalf, ADSecurity, September 2015 [@sean-metcalf-dcsync-2015]
</PullQuote>

Read Metcalf's verb carefully: *impersonates*. The scare quotes are doing work. The Mimikatz client is not pretending to be a DC in any cryptographic sense. It is not forging a machine-account ticket. It is not spoofing an SPN. It is not bypassing any signature check.

It is honestly making an authenticated call as the principal it actually is -- some user or service account that happens to hold the rights triad -- and the protocol honestly responds because its gate is satisfied. The "impersonation" framing is operator vocabulary borrowed from the social model the protocol was written under.

The 1999 designers assumed only DCs would speak. By that social contract, anyone who speaks must be a DC. The spec encoded the social contract by way of *not encoding it at all*. The ACL was the whole gate because, in 1999, the ACL was always satisfied by something that was always a DC.

<Mermaid caption="What the MS-DRSR access check asks (left branch) and what it does not ask (right branch, shown ghosted to emphasize structural absence).">
flowchart TD
    Start[IDL_DRSGetNCChanges request arrives]
    Start --> Q1&#123;"Caller holds rights triad<br/>on the NC root?"&#125;
    Q1 -- yes --> Return[Return encrypted<br/>secret attributes]
    Q1 -- no --> Deny[ERROR_DS_DRA_ACCESS_DENIED]
    Start -.-> Absent["What the check does NOT ask:<br/>Is the caller a domain controller?<br/>Is the SPN a DC SPN?<br/>Is the machine in Domain Controllers?"]
    Absent -.-> Nothing[no second gate exists]
</Mermaid>

This is the article's intellectual fulcrum. The 1999 closed-population assumption -- only DCs speak this protocol -- survives in 2026 as an open-population reality (anyone with the rights speaks it), and the closed-population assumption is nowhere written down in the access-check code. The fix that would close the model would require a second gate. The spec did not write one. The implementation cannot synthesize one without breaking every legitimate consumer that already operates without one (more on this in §8).

> **Key idea:** The protocol's design is correct. The security model is the bug. No patch can fix this without breaking Active Directory replication itself.

There is a temptation, on first encountering this, to assume the missing gate is an oversight that Microsoft will eventually fix. That intuition is wrong, and it is wrong for a deeper reason than "Microsoft has not gotten around to it." If the protocol is the bug and the protocol cannot be amended, what defenses can possibly work in 2026? That question carries us into the next section.

## 6. What 2026 Actually Ships Against This

If the protocol cannot be fixed, the defender's question is no longer "how do I prevent DCSync?" but "which layer catches which class of attempt?" Four production detection layers ship against this attack class in 2026. None of them is individually sufficient. A fifth layer -- the architectural one that would close the structural error -- does not exist and is not coming.

### Posture: enumerate who holds the rights

The posture layer reads the static ACL on the Domain NC root and surfaces every principal that holds any of the three rights. Microsoft Defender for Identity ships this as an Accounts security posture assessment family, computed continuously from MDI's per-DC sensors [@mdi-security-posture-accounts]. Vincent Le Toux's PingCastle exposes it as the *C-DCSync* finding in its critical-risks section. Tenable Identity Exposure exposes it as an indicator of exposure. Christopher Keim's 2025 practitioner guide documents the PowerShell pattern that AD administrators without a posture-tool license can run on demand [@keim-dcsync-rights]:

<RunnableCode lang="python" title="Posture-layer inventory of rights-triad holders on a sample domain root ACL">{`
# Illustrative model of the posture-layer check.
# In production, replace SAMPLE_ACL with output from Get-Acl in PowerShell
# or python-ldap against the live Domain NC root.

GET_CHANGES          = '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2'
GET_CHANGES_ALL      = '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2'
GET_CHANGES_FILTERED = '89e95b76-444d-4c62-991a-0facbeda640c'
TRIAD = {GET_CHANGES, GET_CHANGES_ALL, GET_CHANGES_FILTERED}

DEFAULTS = {
    'BUILTIN\\\\Administrators',
    'CONTOSO\\\\Domain Controllers',
    'CONTOSO\\\\Domain Admins',
    'CONTOSO\\\\Enterprise Admins',
    'NT AUTHORITY\\\\ENTERPRISE DOMAIN CONTROLLERS',
}

SAMPLE_ACL = [
    {'principal': 'CONTOSO\\\\Domain Admins',     'right': GET_CHANGES},
    {'principal': 'CONTOSO\\\\Domain Admins',     'right': GET_CHANGES_ALL},
    {'principal': 'CONTOSO\\\\Enterprise Admins', 'right': GET_CHANGES},
    {'principal': 'CONTOSO\\\\Enterprise Admins', 'right': GET_CHANGES_ALL},
    {'principal': 'CONTOSO\\\\backup_svc_2017',   'right': GET_CHANGES},
    {'principal': 'CONTOSO\\\\backup_svc_2017',   'right': GET_CHANGES_ALL},
    {'principal': 'CONTOSO\\\\MSOL_a1b2c3',       'right': GET_CHANGES},
    {'principal': 'CONTOSO\\\\MSOL_a1b2c3',       'right': GET_CHANGES_ALL},
]

residual = {}
for ace in SAMPLE_ACL:
    if ace['right'] in TRIAD and ace['principal'] not in DEFAULTS:
        residual.setdefault(ace['principal'], set()).add(ace['right'])

for principal, rights in residual.items():
    print(f"{principal}: {len(rights)} of 3 replication rights")
`}</RunnableCode>

The residual set is the operational unit of work. In a freshly-installed domain it is empty. In a ten-year-old forest with a history of mergers and migrations it typically contains five to twenty entries -- a mix of legitimately delegated identity-sync products and forgotten service accounts from projects nobody on the current operations team remembers.

<Definition term="Tier Zero">
In Microsoft's Enhanced Security Administrative Environment and the BloodHound conventions, the set of principals and assets whose compromise yields domain-wide control. The `krbtgt` account, members of `Domain Admins` and `Enterprise Admins`, the Entra ID Connect MSOL_ sync account, and any non-default principal holding the rights triad on the domain root are all Tier Zero by construction.
</Definition>

<Definition term="MSOL_ account">
The Microsoft Entra ID (formerly Azure AD) Connect synchronization service account, created on-premises during Entra Connect installation. The account name uses an `MSOL_` prefix followed by a hex suffix. It legitimately holds the rights triad on the Domain NC root because it must replicate password hashes to the cloud directory; treating it as Tier Zero is the standard hardening recommendation.
</Definition>

<Definition term="Replication Allow List">
Microsoft Defender for Identity's (and ATA's earlier) internal baseline of which computers in the domain legitimately speak DRSUAPI to which DCs. Incoming `IDL_DRSGetNCChanges` requests are matched against this baseline; a request from a source outside the list fires the External ID 2006 alert.
</Definition>

<Sidenote>An earlier scope document for this article quoted an MDI assessment titled verbatim *"Remove non-admin accounts with DCSync permissions."* That exact title does not appear on the live MDI Accounts security-posture-assessment page in 2026 [@mdi-security-posture-accounts][@mdi-security-posture-hybrid]. The surface exists -- MDI's Accounts and Hybrid security posture-assessment families together cover non-default principals with replication rights -- but the verbatim title was not reproducible.</Sidenote>

### Behavioral: catch the act after it fires

The behavioral layer watches network traffic and event logs for the act of DCSync or DCShadow as it happens. Microsoft's first-party stack lives in Defender for Identity and ships three canonical alerts [@mdi-alerts-classic]:

- **External ID 2006 -- "Suspected DCSync attack (replication of directory services)."** Credential Access (TA0006), Persistence (TA0003). Technique T1003.006. Severity High. Trigger: a replication request is initiated from a computer that is not a domain controller.
- **External ID 2028 -- "Suspected DCShadow attack (domain controller promotion)."** Defense Evasion (TA0005). Technique T1207. Trigger: a machine in the network tries to register as a rogue domain controller.
- **External ID 2029 -- "Suspected DCShadow attack (domain controller replication request)."** Defense Evasion (TA0005). Technique T1207. Trigger: a suspicious replication request is generated against a genuine domain controller, indicative of DCShadow.

The product lineage is Microsoft Advanced Threat Analytics 1.7 (April 2017, where DCSync was covered under the umbrella "Unusual Protocol Implementation enhancements" detection category [@ata-v17-release-notes]); Azure Advanced Threat Protection (2018, where Tali Ash's July 24, 2018 Microsoft Tech Community post announced the two named DCShadow detections that became 2028 and 2029 [@tali-ash-azure-atp-dcshadow]); and Defender for Identity (the Microsoft Ignite 2020 rebrand, week of September 22-24, 2020 [@rcpmag-defender-rebrand]). The detection content carries forward unchanged across product renamings; what changes is the portal it surfaces in [@mdi-whats-new].

Open-source SIEM equivalents implement the same detection via Event ID 4662 on the domain object. The Sigma rule *Mimikatz DC Sync* (id `611eab06-a145-4dfa-a295-3ccc5c20f59a`) fires on Event ID 4662 where `Properties` contains any of the three rights GUIDs or the literal string *Replicating Directory Changes All*, with `AccessMask=0x100` [@sigma-rule-dcsync]. Splunk's parallel detection *Windows AD Replication Request Initiated by User Account* (rule `51307514-1236-49f6-8686-d46d93cc2821`) implements the equivalent SPL search with the same MITRE T1003.006 annotation and the same known-false-positive list (Azure AD Connect, `dcdiag.exe /Test:Replications`) [@splunk-research-dcsync].

<Sidenote>The SACL-event detection layer (Sigma + Splunk + every other SIEM-side implementation) requires that the operator first enable Advanced Security Audit policy `Audit Directory Services Access` under `DS Access`, with SACLs on the domain root auditing the three rights against `Everyone`, `Domain Computers`, and `Domain Controllers`. A fresh-install AD does not have these SACLs by default [@splunk-research-dcsync]. The detection rule's documentation calls out the prerequisite explicitly; many operators discover it only after wondering why their Splunk dashboard is silent.</Sidenote>

### Network: NDR on the DRSUAPI interface

The network layer parses DCE/RPC traffic on the wire, identifies the DRSUAPI interface by its UUID, and fires when an `IDL_DRSGetNCChanges` call originates from a source outside the legitimate-DC baseline. The canonical 2025 reference is Trellix Advanced Research Center's *"Silent Domain Hijack: Uncovering the DCSync Attack and Detecting with Trellix NDR"*, published in week 50 of 2025 [@trellix-silent-domain-hijack]. Equivalent capability ships in Microsoft Defender for Identity's network analytics layer (the same sensor that powers External ID 2006) [@mdi-alerts-classic].

The Trellix writeup is explicit about the architecture: *"Trellix NDR detects replication protocol abuse by analyzing abnormal DCE/RPC and MS-DRSR traffic. It detects DCSync-like behavior when replication requests are sent from non-DC hosts or unusual users"* [@trellix-silent-domain-hijack]. The detection is independent of host-side telemetry, so it survives EDR tampering and works in environments where a DC is un-sensored from MDI's perspective.

### Graph: BloodHound traverses the permission graph

The graph layer was built specifically for Generation 4. BloodHound, originally released by Andy Robbins, Rohan Vazarkar, and Will Schroeder at DEF CON 24 on August 6, 2016, models *"you have DCSync rights to the domain"* as a directed edge from a principal to the domain node, computed by combining the separately-collected `GetChanges` and `GetChangesAll` ACEs through nested-group membership [@wald0-bloodhound-intro][@defcon24-bloodhound-pdf][@bloodhound-edge-dcsync]. An operator queries *"shortest path from any compromised principal to the DCSync edge into a Domain node"* and gets back every transitive route into the rights triad.

The 2024-2026 release cadence is dense and matters:

- **BloodHound v6.0 (September 30, 2024)** improved logic for identifying and creating complex edges requiring multiple permissions, including DCSync, when `Authenticated Users` or `Everyone` groups are involved [@bloodhound-v6-0-release-notes]. This closed a long-standing blind spot in which rights granted to a wildcard principal (the worst kind of delegation drift, often dating from compatibility settings in pre-2003 forests) were not surfaced as DCSync edges.
- **BloodHound v6.3 (December 9, 2024)** introduced the *Butterfly* algorithm -- bi-directional risk analysis. Where the historical query was *"which principals can attack this target?"*, Butterfly adds *"which targets can be attacked from this compromised principal?"* SpecterOps describes the algorithm in their blog post *Unwrapping BloodHound v6.3 with Impact Analysis*: *"This is a massive upgrade to BloodHound Enterprise's risk analysis capability with a new algorithm we call 'Butterfly'"* [@specterops-butterfly-blog][@bloodhound-v6-3-release-notes].
- **BloodHound v8.0 (July 29, 2025)** introduced *OpenGraph*, generalizing the graph engine to model attack paths across non-AD systems (GitHub, 1Password, NPM, Snowflake) using the same Cypher front-end. The DCSync edge remains AD-specific; OpenGraph's relevance is that hybrid-cloud principals can now be modeled end-to-end [@bloodhound-v8-0-release-notes].

The graph layer is the only one that catches multi-hop chains to the rights triad. A principal A with `GenericAll` on group B, where B has the DCSync triad, is invisible to MDI's posture assessment but stands out as a one-hop path in BloodHound.

<Mermaid caption="The four-layer defense architecture. Each layer takes different inputs, detects a different slice of the attack surface, and feeds a unified SOC alert queue.">
flowchart TD
    subgraph Posture[Posture layer]
        P1[MDI Accounts assessments]
        P2[PingCastle C-DCSync]
        P3[Keim PowerShell pattern]
    end
    subgraph Behavioral[Behavioral layer]
        B1[MDI 2006 / 2028 / 2029]
        B2[Sigma 611eab06]
        B3[Splunk 51307514]
    end
    subgraph Network[Network layer NDR]
        N1[Trellix NDR]
        N2[MDI per-DC sensor]
        N3[CrowdStrike DCE/RPC IoA]
    end
    subgraph Graph[Graph layer]
        G1[BloodHound DCSync edge]
        G2[v6.3 Butterfly Impact]
        G3[v8.0 OpenGraph]
    end
    P1 --> SOC[SOC alert queue]
    P2 --> SOC
    P3 --> SOC
    B1 --> SOC
    B2 --> SOC
    B3 --> SOC
    N1 --> SOC
    N2 --> SOC
    N3 --> SOC
    G1 --> SOC
    G2 --> SOC
    G3 --> SOC
</Mermaid>

The full layer-by-layer comparison is the load-bearing matrix of the entire 2026 SOTA:

| Layer | What it inputs | What it detects | What it misses | False-positive class | Detection latency | Cost |
|---|---|---|---|---|---|---|
| Posture | NC-root ACL | Direct grant of the rights triad to a non-default principal | Multi-hop transitive paths; legitimate-principal abuse | Identity-sync products (Entra Connect MSOL_, third-party HR-IDM); legitimately delegated backup agents | 24h score recomputation; ACE changes near real time | Built in to MDI; PowerShell variant free |
| Behavioral | Event ID 4662 or sensor-captured DRSUAPI traffic | The act of DCSync, or the DCShadow registration / replication, after it happens | Pre-attack staging; compromised-DC speaker; un-sensored DC blind spot | Azure AD Connect syncs; `dcdiag.exe /Test:Replications` | Minutes (alert "after the fact") | MDI license; Sigma / Splunk free with SACL config |
| Network | DCE/RPC packet capture | Wire signature of `IDL_DRSGetNCChanges` from non-DC source | Encrypted RPC payloads (per-principal granularity); sub-DC replicas misclassified | Same as behavioral plus Samba-DC peers | Seconds (passive inspection) | Commercial NDR appliance |
| Graph | LDAP-collected ACEs and group nesting | Multi-hop graph paths that could reach the rights triad | Net-new ACEs created after the last collection | Stale data showing principals that no longer hold the right | Hours to weeks (collection cadence) | BloodHound CE free; BHE commercial |
| Architectural (un-shipped) | TPM-attested DC machine identity | Pre-empts non-DC speaker entirely | Compromised DC speaker; rogue `nTDSDSA` registration | N/A | N/A (preventive) | Requires Microsoft protocol amendment |

Four layers, dozens of products, no shortage of detections. Why isn't the problem solved?

## 7. Which Gap Does Each Defense Close?

The matrix in §6 has one structural observation that earns its keep. Read down the *What it misses* column. Each detection layer has a class of cases it cannot see. The posture layer cannot see transitive paths. The behavioral layer cannot see pre-attack staging or compromised-DC speakers. The network layer cannot see encrypted RPC payloads. The graph layer cannot see net-new ACEs created after the last collection. That is the load-bearing reason a mature defender runs all four in parallel instead of picking one.

Run a thought experiment. Suppose you ship only the posture layer. Your MDI assessment is green: no non-default principals hold the rights triad. An attacker compromises a workstation belonging to an unrelated business unit, finds that the BU has `GenericAll` on a group `LegacyApp_Operators` that itself has `WriteDACL` on the `BackupOperators` group, and adds themselves to `BackupOperators`. `BackupOperators` inherits a forgotten 2014 delegation of the rights triad through three levels of nesting. Then DCSync runs.

The posture layer never saw this because the residual list at the NC root is the four defaults. The graph layer would have surfaced the path. The behavioral or network layer would have fired the moment the DCSync call hit the wire. Without those layers, your green dashboard is a single-layer fantasy.

Now consider the inverse. You ship only the behavioral layer. MDI fires External ID 2006 -- but only after the request hits the DC. Your SOC's mean response time is twelve minutes. The attacker's mean *complete-the-extraction* time is sixty seconds. The detection is real; the response window is not.

The same structural observation applies on the attack side. The four attack primitives have very different precondition costs:

| Attack primitive | Pre-condition | Output | Default detection layer | Tier of damage |
|---|---|---|---|---|
| DCSync via Mimikatz | Rights triad on NC root | Every secret-attribute value for one or all principals | MDI 2006; Sigma 611eab06; Splunk 51307514; NDR | Domain takeover (via Golden Ticket from `krbtgt`) |
| DCSync via Impacket | Same as Mimikatz; no on-target binary footprint | Same as Mimikatz, plus PtH / PtT auth modes | Same as Mimikatz (wire signature is identical) | Domain takeover |
| DCShadow | Domain Admin, local administrator on a DC, or `KRBTGT` hash (for rogue-DC registration) [@mitre-t1207][@dcshadow-com] | Arbitrary directory write, SACL-silent | MDI 2028 + 2029 | Domain persistence (SID-history, AdminSDHolder ACE re-grant) |
| Gen-4 chains | Any ACE that transitively leads to the triad | Reach DCSync, then DCSync's output | BloodHound DCSync edge inbound paths | Domain takeover (composite) |

DCSync via Mimikatz and via Impacket [@impacket-secretsdump] share the *output* (domain takeover via credential theft) and the *default detection* (the wire signature is the same). Their pre-conditions are identical. The Hacker Recipes documents Impacket's invocation surface for both pass-the-hash and pass-the-ticket modes [@hacker-recipes-dcsync]. This is why Impacket's `secretsdump.py -just-dc` has become the universal red-team and IR tool, while Mimikatz remains the reference implementation that every blue-team detection still names.

The Generation-4 row is the interesting one. Its pre-condition is dramatically cheaper than the other three (*any* ACE that leads to the triad, rather than the triad in hand). Its detection is by graph traversal, not by wire signature. This is why the 2024-2026 frontier in this space has been the graph layer: the attack-side cost asymmetry favors the chain-finding problem, so the defense-side investment has landed there.

<Aside label="What about Zerologon?">
A reader who has internalized the "no DC check" observation will naturally ask: surely the obvious architectural fix is to add a caller-machine-identity check? CVE-2020-1472, popularly known as Zerologon, is the counterexample. Secura's September 2020 whitepaper, published approximately one month after Microsoft's August 11, 2020 patch, documented that the Netlogon protocol's `ComputeNetlogonCredential` AES-CFB8 implementation used an all-zero initialization vector. An attacker who sends an all-zero `ClientChallenge` exploits the IV bug to authenticate with roughly 1-in-256 probability per attempt, then calls `NetrServerPasswordSet2` to reset the DC's machine account password (commonly to all zeros) and becomes that DC for protocol purposes [@secura-zerologon-whitepaper]. After Zerologon-class compromise, the attacker *is* a DC. Any architectural caller-machine-identity check on `IDL_DRSGetNCChanges` would have been satisfied. Zerologon is the canonical worked proof that promoting the access-check from "rights" to "rights and DC identity" raises the bar but does not change the class.
</Aside>

> **Note:** Every detection layer in this space has a class of cases it cannot see. Mature defenders run all four (posture, behavioral, network, graph) because each layer is the only answer for a specific class of inputs, and accept that the un-shipped fifth layer (architectural) would only narrow the gap, not close it. Single-layer deployments produce confident but incomplete coverage.

If even the hypothetical perfect architectural fix would not close the class, what are the actual theoretical limits of any possible defense?

## 8. Why the Protocol Cannot Be Fixed

Two structural ceilings bound any conceivable MS-DRSR amendment. Both are provable from the specification's own definition of what it must do; both have been corroborated by the post-2018 industry consensus that *detection* (not prevention) is the only viable defender posture.

**Ceiling 1: replicated secret material is the data the protocol exists to carry.** MS-DRSR is the mechanism by which Active Directory's multi-master replication invariant holds. If `IDL_DRSGetNCChanges` did not return secret attributes -- `unicodePwd`, `dBCSPwd`, `ntPwdHistory`, `lmPwdHistory`, `supplementalCredentials` -- then a password change on DC-A would not converge to DC-B within the replication interval. AD's *"any DC accepts any write and the others catch up within minutes"* property would collapse [@ms-drsr-spec]. The protocol cannot stop returning secrets without ceasing to be the protocol. This is why "disable MS-DRSR" appears on every list of options that look attractive in a slide deck and break replication in production within minutes of being applied.

**Ceiling 2: a machine-identity check on the caller would shift the attack class, not close it.** Suppose Microsoft amended the access check on `IDL_DRSGetNCChanges` to require, in addition to the rights triad, a cryptographic proof that the caller's machine account is in the `Domain Controllers` group -- for example, that the call was made under a Kerberos service ticket issued for a DC SPN. This would defeat the Mimikatz-from-workstation case. It would also defeat every legitimate integration that today holds the rights triad on a non-DC service account: the Entra ID Connect MSOL_ account, third-party HR identity-management connectors, every backup and disaster-recovery tool that integrates at the AD level.

Worse, the new check would shift the attack class to *compromising a DC's machine account* -- CVE-2020-1472 Zerologon being the canonical worked example (see the §7 Aside for the mechanism). After a Zerologon-class compromise the attacker *is* a DC, so promoting the speaker check from (rights) to (rights and DC-identity) raises the bar but does not change the class [@secura-zerologon-whitepaper].

<Mermaid caption="The two structural ceilings on any MS-DRSR fix. Either branch -- removing the secret-return path or adding a caller-machine-identity check -- breaks the protocol or shifts the attack class without closing it.">
flowchart TD
    Fix[Proposed MS-DRSR fix]
    Fix --> A[Stop returning secrets<br/>in IDL_DRSGetNCChanges]
    Fix --> B[Add machine-identity check<br/>on the caller]
    A --> A1[AD multi-master replication breaks<br/>password changes do not propagate]
    B --> B1[Legitimate integrations break<br/>MSOL_ account, HR IDM, backup tools]
    B --> B2[Attack shifts to compromised DC<br/>machine accounts e.g. Zerologon<br/>CVE-2020-1472]
</Mermaid>

The honest structural fix would require a different replication architecture: a [TPM](/blog/the-tpm-in-windows-one-primitive-twenty-five-years-and-the-c/)- or HSM-attested DC machine identity, bound to a sealed replication key, with secret attributes encrypted under that key on the wire. No caller without the sealed key (or its hardware-bound equivalent on a different DC) could ever decrypt the response.

Microsoft has not announced any such architecture. Its closest published precedent in the Windows security stack is the [LSAIso trustlet](/blog/vbs-trustlets-what-actually-runs-in-the-secure-kernel/) that Credential Guard uses for LSASS isolation -- a per-host isolation primitive applied to a per-host secret store. Applying the same idea to a multi-party wire protocol that must interoperate with twenty-five years of installed identity-sync tooling is a different engineering problem at a different scale. Microsoft has not committed to it.

> **Key idea:** The replication attack class is structurally permanent. The honest defender response is detection-and-response, not prevention.

This is the article's humility moment. The reader who arrived at §5 thinking "this is fixable" should now understand why eleven years of attack/defense iteration have produced detection layers, not protocol revisions. The four-layer detection architecture is not a placeholder while we wait for Microsoft to ship the real fix. It is the real fix, conditional on the constraint that the protocol's job description does not change.

If the protocol is structurally unfixable, what exactly does 2026 still not solve operationally?

## 9. What 2026 Still Cannot Do

Five problems sit on the open-questions register in 2026. Each is documented in the literature. None has a satisfying answer.

**The DCShadow gap window.** MDI's External ID 2029 alert fires on the rogue DC's replication request, which is structurally *after* the rogue `nTDSDSA` registration has been committed. The alert documentation describes the detection as firing after the fact [@mdi-alerts-classic]. An attacker who completes the register-replicate-deregister cycle inside the alert's batch interval commits the persistence write before any SOC responder sees the alert. External ID 2028 (rogue promotion) fires earlier in the kill chain and partially closes the gap, but the gap is structural to the alert-batch model. The directory write that DCShadow lands -- a SID-history injection, an AdminSDHolder ACE re-grant -- survives the alert.

**Encrypted-channel DCSync.** DRSUAPI clients that negotiate `AUTH_LEVEL_PKT_PRIVACY` on the RPC binding (the modern hardened-DC default) encrypt the request and response bodies on the wire. Passive NDR sensors that depend on parsing the `IDL_DRSGetNCChanges` request to determine which principal is being targeted lose per-principal granularity.

The interface-bind packet is still in clear, so the existence of a DRSUAPI call is still visible, but the payload is not. The Microsoft channel-binding rollout that began in late 2023 (targeting LDAP rather than DRSUAPI, but cementing the broader trend toward encrypted directory traffic) makes this gap permanent on the wire side [@microsoft-ldap-channel-binding-kb4520412]. Detection moves into the DC itself via the MDI sensor model.

**The legitimate-principal-compromise non-detection.** A hijacked Domain Admin session that uses its rightful DCSync ability triggers no layer. The posture layer sees a default principal. The behavioral layer sees a request from a DC or admin workstation that the Replication Allow List baseline accepts. The network layer sees the same. The graph layer sees the principal as a default Tier Zero member. The MDI alert is explicit: the trigger is *"a computer that isn't a domain controller"* -- a compromised legitimate principal acting from a legitimately-baselined workstation does not fire it [@mdi-alerts-classic].

<Sidenote>This is the failure mode that catches mature SOCs. The attacker who already has Domain Admin does not need to attack DCSync detection because DCSync detection is not designed for legitimate principal abuse. UEBA-style per-principal anomaly detection (*"this DA has not run DCSync in 90 days; this DA running DCSync at 03:00 from a workstation it has not used before is anomalous"*) is the partial answer. No production product currently delivers it with low enough false-positive rates to be operationally useful for already-Tier-Zero principals.</Sidenote>

**Cross-forest replication abuse is under-instrumented.** The interaction of `DS-Replication-Get-Changes-In-Filtered-Set` with the SPN-suffix routing matrix in multi-forest environments is poorly covered in public detection guidance. Large enterprises with M&A history hold dozens of trusts; the cross-trust edges are the least-audited surface in their identity architecture. BloodHound's SharpHound collector can enumerate cross-trust data, and v6.0's wildcard-principal fix improves the picture, but no fully automated detection pattern exists [@bloodhound-v6-0-release-notes].

**Delegation-drift residual long tail.** Even with the MDI Accounts security posture assessment perfectly tuned, the long tail of forgotten ACE delegations across a twenty-five-year-old forest with mergers, acquisitions, decommissioned products, and migrations remains the canonical entry point. Christopher Keim frames it unambiguously [@keim-dcsync-rights]:

<PullQuote>
"The defaults aren't the problem. The problem is delegation drift, backup agents, identity sync products, and application service accounts accumulate these rights over time, often with no documentation and no review." -- Christopher Keim, *"DCSync Attack: Finding and Fixing Replication Rights in Active Directory"* (2025) [@keim-dcsync-rights]
</PullQuote>

The posture-layer detection is necessary but not sufficient; the human-process loop -- documented ownership, periodic review, removal of unjustified ACEs -- is what closes the residual. Most enterprise SOCs are not staffed to run this loop at the cadence the residual requires.

| Open problem | Why it matters | Current best partial result |
|---|---|---|
| DCShadow gap window | Persistence write commits before SOC sees the alert | Configure MDI to surface External ID 2028 (rogue promotion) with automated investigation and response to block RPC traffic from the suspected source [@mdi-credential-access-alerts] |
| Encrypted-channel DCSync | Passive NDR loses per-principal granularity | Hybrid deployment: NDR for cross-DC visibility, MDI on-DC sensor for per-principal granularity [@trellix-silent-domain-hijack] |
| Legitimate-principal compromise non-detection | The Tier Zero principal who already has DCSync rights triggers nothing | Reduce the count of DCSync-capable principals to a number a human can monitor; surface their DCSync activity to a high-severity review queue |
| Cross-forest replication abuse | Cross-trust DCSync paths are not enumerated by default | SharpHound trust-collection methods; manual BloodHound inspection of foreign-domain principals |
| Delegation-drift residual long tail | Posture surfaces the principals; humans still have to decide which are legitimate | Quarterly posture review with documented justification per non-default principal |

What can a defender actually do on Monday morning, given all of the above?

## 10. What a Defender Does on Monday Morning

Three action lanes, in priority order.

### Lane 1: inventory the rights triad

Read the Domain NC root's ACL. Filter on the three rights GUIDs. Subtract the four default principal sets plus any legitimately delegated identity-sync product (Entra ID Connect's MSOL_ account is the canonical exclusion). Every entry that remains gets a documented owner and a documented justification, or the ACE gets removed.

<RunnableCode lang="python" title="Production-shape inventory of rights-triad holders, with operator commentary">{`
# Operator-facing inventory script. The browser-runnable demo uses a
# hardcoded SAMPLE_ACL; in production, replace the SAMPLE_ACL with output
# from one of:
#   PowerShell:  Get-Acl "AD:$( (Get-ADDomain).DistinguishedName )"
#   python-ldap: ldap_search(ncroot_dn, attr='nTSecurityDescriptor')

GET_CHANGES          = '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2'
GET_CHANGES_ALL      = '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2'
GET_CHANGES_FILTERED = '89e95b76-444d-4c62-991a-0facbeda640c'
TRIAD = {GET_CHANGES, GET_CHANGES_ALL, GET_CHANGES_FILTERED}

DEFAULT_OK = {
    'BUILTIN\\\\Administrators',
    'CONTOSO\\\\Domain Controllers',
    'CONTOSO\\\\Domain Admins',
    'CONTOSO\\\\Enterprise Admins',
    'NT AUTHORITY\\\\ENTERPRISE DOMAIN CONTROLLERS',
}

# MSOL_ accounts: legitimate Entra ID Connect sync principals.
# Exclude by prefix, never by exact name (the suffix is random).
def is_known_legitimate(principal):
    return principal in DEFAULT_OK or '\\\\MSOL_' in principal

SAMPLE_ACL = [
    {'principal': 'CONTOSO\\\\Domain Admins',     'right': GET_CHANGES_ALL},
    {'principal': 'CONTOSO\\\\MSOL_a1b2c3d4',     'right': GET_CHANGES_ALL},
    {'principal': 'CONTOSO\\\\backup_svc_2017',   'right': GET_CHANGES_ALL},
    {'principal': 'CONTOSO\\\\hr_idm_connector',  'right': GET_CHANGES},
    {'principal': 'CONTOSO\\\\fileserver_old$',   'right': GET_CHANGES_ALL},
]

findings = []
for ace in SAMPLE_ACL:
    if ace['right'] not in TRIAD:
        continue
    if is_known_legitimate(ace['principal']):
        continue
    findings.append(ace['principal'])

print("Principals to investigate:")
for p in sorted(set(findings)):
    print(f"  - {p}  ->  document owner or remove ACE")
`}</RunnableCode>

Anything in the *Principals to investigate* output is either a legitimately delegated service (document the owner and add to your exclusions; treat as Tier Zero) or a forgotten ACE from a project nobody remembers (remove it). Christopher Keim's framing is the operationally useful one: every common culprit is a backup tool, an identity-governance tool, or a service account from a long-dead migration [@keim-dcsync-rights].

<Aside label="The MSOL_ exclusion in hybrid environments">
The Microsoft Entra ID Connect synchronization service account, created on-premises during Entra Connect installation with an `MSOL_` prefix and a random hex suffix, legitimately holds the rights triad on the Domain NC root. It must -- it has to replicate password hashes to the cloud directory so that Entra ID can validate cloud logons against on-premises credentials. Removing its ACE breaks Entra ID password hash sync within a single replication interval, and your help desk will know about it.

The right answer is not to remove the ACE. It is to treat the MSOL_ account as Tier Zero. Dedicated host (the Entra Connect server itself, hardened as a DC-tier asset). No interactive logon. Multi-factor authentication on any privileged use. Conditional access policies that block sign-in from anything other than the Entra Connect service identity. The MDI Hybrid security posture-assessment family documents the surrounding controls [@mdi-security-posture-hybrid].
</Aside>

### Lane 2: enable the canonical alerts and audit

Three configuration items.

First, ensure Microsoft Defender for Identity (or an equivalent identity-threat-detection product) is deployed with a sensor on every DC. The un-sensored-DC gap that the MDI alert documentation explicitly warns about creates a structural blind spot that an attacker will preferentially target [@mdi-alerts-classic].

Second, enable Advanced Security Audit policy `Audit Directory Services Access` under `DS Access` and apply SACLs on the Domain NC root that audit the three replication rights against `Everyone`, `Domain Computers`, and `Domain Controllers`. This is what makes Event ID 4662 fire on the request, which is what the Sigma 611eab06 and Splunk 51307514 rules consume [@sigma-rule-dcsync][@splunk-research-dcsync]. A fresh-install AD does not have these SACLs by default; the most common reason a SIEM dashboard for DCSync is silent is that the SACL never got applied.

Third, deploy or configure NDR coverage on the inter-DC subnet, with a rule that fires on DRSUAPI bind requests originating from source IPs outside the legitimate-DC baseline. Trellix NDR, Microsoft's MDI sensor, CrowdStrike Falcon, and community Zeek/Suricata rulesets all implement this [@trellix-silent-domain-hijack]. Where commercial NDR is out of budget, Sysmon with the SwiftOnSecurity or Olaf Hartong modular configuration surfaces Event ID 3 (NetworkConnect) and Event ID 22 (DnsQuery) outbound from non-DC hosts to DC RPC endpoints; a SIEM correlation rule can combine this endpoint-side signal with Event ID 4662 on the DC to approximate the network-plus-host signature without an appliance budget.

### Lane 3: run BloodHound on the domain quarterly

Collect with SharpHound at minimum quarterly. Continuous collection if BloodHound Enterprise is available. Run the canonical query for the `DCSync` edge into the domain node. Trace inbound paths. Close the longest path first -- the longest paths are the ones a human operator is least likely to have noticed and most likely to have been delegated decades ago for a reason nobody remembers.

The v6.0 wildcard-principal fix is particularly worth a re-run on any forest that has been operated since before 2003: legacy `Authenticated Users` or `Everyone` ACEs on the domain root are exactly the kind of thing that survived a Server 2003 upgrade silently and never showed up in any subsequent audit [@bloodhound-v6-0-release-notes]. The v6.3 Butterfly algorithm lets you query the inverse view -- *which targets fall if this principal is compromised?* -- which is the right question to ask about any newly-discovered non-default DCSync holder [@specterops-butterfly-blog][@bloodhound-v6-3-release-notes].

### What does not work

Four common misbeliefs are worth naming.

> **Note:** See §1 -- Credential Guard does not stop DCSync. The secret transits remote-to-remote (a DC's NTDS.dit to the attacker's process), never local-to-LSASS, so the trustlet's isolation boundary has no jurisdiction over the call [@credential-guard-considerations]. Credential Guard is the right control for the local-memory attack surface and the wrong control for the network-protocol attack surface.

> **Note:** After a confirmed DCSync of `krbtgt`, rotate the `krbtgt` account password twice, with at least ten hours between rotations. The first rotation invalidates the old key after the current Kerberos ticket lifetime expires. The second rotation invalidates the *previous* old key, which the directory stores alongside the current key for compatibility during replication convergence. Rotating only once leaves Golden Tickets forged from the dumped key valid for the duration of the second key. Rotating twice ten hours apart is what closes the window [@microsoft-new-krbtgtkeys]. (And neither rotation removes the ACE that allowed the dump in the first place: come back to Lane 1.)

Renaming `krbtgt` does nothing. The account's Relative Identifier (RID 502) is fixed by AD's design and is what the TGT signing key derives against, not the `sAMAccountName` [@microsoft-well-known-sids]. Renaming it to `krbtgt-old-do-not-use` confuses operators, not attackers.

Disabling MS-DRSR is not an option. The protocol is what makes AD replication work. Blocking opnum 3 at the RPC layer or refusing the DRSUAPI bind stops DCSync and stops every DC in the forest from talking to every other DC. Replication grinds. Password changes do not propagate. Domain joins fail. Within hours, the directory is split-brain across DCs, and within days, it is unrecoverable without DR-grade restore from backup: Microsoft's own AD-replication troubleshooting documentation walks the lingering-object pathology that produces exactly this split-brain when DCs stop replicating for longer than the tombstone lifetime [@microsoft-ad-lingering-objects].

<Spoiler kind="hint" label="A diagnostic one-liner for the SACL precondition">
On any DC, run `auditpol /get /subcategory:"Directory Service Access"` from an elevated prompt. If the output reads `No Auditing`, your Sigma / Splunk SACL-event detection will not fire because Event ID 4662 is not being generated. Enable with `auditpol /set /subcategory:"Directory Service Access" /success:enable /failure:enable`, then apply the SACL on the domain root as described in the Splunk rule's implementation notes [@splunk-research-dcsync].
</Spoiler>

The six FAQ items in the next section cover the misconceptions that did not fit into any single lane.

## 11. Frequently Asked Questions

<FAQ title="Frequently asked questions">

<FAQItem question="Doesn't Replicating Directory Changes All alone let you DCSync?">
No. The access check on `IDL_DRSGetNCChanges` requires both the baseline `DS-Replication-Get-Changes` right *and* `DS-Replication-Get-Changes-All` to read secret attributes [@ad-schema-get-changes][@ad-schema-get-changes-all]. The *All* suffix unlocks the secret attribute set given the baseline right; it is not a self-sufficient gate. Christopher Keim's PowerShell pattern filters on both GUIDs together [@keim-dcsync-rights]. Confidential-flag attributes additionally require `DS-Replication-Get-Changes-In-Filtered-Set`. Some detection rules (Sigma 611eab06 included) accept either GUID in the SACL event because the operational cost of a false positive on the broader filter is lower than the risk of missing one of the two required ACEs being present without the other [@sigma-rule-dcsync].
</FAQItem>

<FAQItem question="Doesn't Credential Guard protect me?">
No. Credential Guard isolates LSASS-resident secrets in a separate virtual trust level so that local-memory attacks (Mimikatz `sekurlsa::logonpasswords`, comsvcs.dll mini-dumps of `lsass.exe`, and similar) cannot read cached credentials. DCSync does not touch the attacker's LSASS at all. The secret transits from a remote DC's NTDS.dit, over an encrypted MS-DRSR session, into the attacker's process memory. Microsoft's Credential Guard documentation lists the scenarios Credential Guard does and does not cover; MS-DRSR-based network credential extraction is not in scope [@credential-guard-considerations]. This is one of the clearest examples in the Windows security model of a control that is right for one attack surface and orthogonal to another.
</FAQItem>

<FAQItem question="Isn't DCShadow undetectable?">
Not anymore. The BlueHat IL 2018 "your million-dollar SIEM goes blind" framing was correct in 2018 against SIEMs that monitored only object-modification events: the replication writes are SACL-silent. By July 24, 2018, Tali Ash announced Azure ATP's two new preview detections, which fired on the rogue-DC promotion fingerprint (creating an `nTDSDSA` object in the Configuration NC) and the replication request from the rogue, respectively [@tali-ash-azure-atp-dcshadow]. Those detections carried forward as Microsoft Defender for Identity External IDs 2028 and 2029 [@mdi-alerts-classic]. The writes themselves remain SACL-silent; the *registration* fingerprint that has to precede them is not. The honest contemporary statement is "DCShadow's writes are silent, but the rogue-DC scaffolding is not, and a sensored DC catches the scaffolding." A skilled attacker who completes the register-replicate-deregister cycle inside the alert batch interval may still commit the persistence write before SOC response.
</FAQItem>

<FAQItem question="Did Microsoft fix this in some patch?">
No. The protocol's design is the issue. Microsoft has not announced any MS-DRSR amendment that would change the `IDL_DRSGetNCChanges` access check, because amending the access check breaks legitimate non-DC consumers (the Entra ID Connect MSOL_ account is the most prominent) and shifts the attack class to compromised DC machine accounts (Zerologon is the worked example [@secura-zerologon-whitepaper]). The "fixes" that have shipped since 2015 are all detection: ATA 1.7 (April 2017) [@ata-v17-release-notes], Azure ATP (2018) [@tali-ash-azure-atp-dcshadow], Microsoft Defender for Identity (post-Ignite 2020 rebrand) [@rcpmag-defender-rebrand][@mdi-whats-new], and the posture-assessment families that surface non-default rights triad holders [@mdi-security-posture-accounts]. The protocol itself is the same protocol it was in Windows 2000 Server.
</FAQItem>

<FAQItem question="What does this mean for hybrid (Entra ID Connect) environments?">
The MSOL_ sync account legitimately holds the rights triad on the Domain NC root because it must replicate password hashes to Entra ID. Treat it as Tier Zero with the hardening profile listed in §10's MSOL_ Aside (dedicated hardened host, no interactive logon, MFA on privileged use, conditional access restricted to the Entra Connect service identity) [@mdi-security-posture-hybrid]. Critically, do *not* remove the ACE from the Domain NC root: doing so breaks Entra ID password hash sync within one replication interval, and your help desk will know about it within hours.
</FAQItem>

<FAQItem question="Is this the same as DCSync-over-LDAP?">
No. DCSync is over DRSUAPI/MS-DRSR, not over LDAP. The directory's LDAP service refuses to return `unicodePwd` and related secret-attribute values regardless of caller privilege, because the attribute is marked confidential and the LDAP read path does not honor the replication extended rights. There is no "DCSync over LDAP" technique because LDAP simply does not return the data; MITRE T1003.006 names DRSUAPI explicitly as the protocol vector [@mitre-t1003-006]. Operators occasionally confuse this with LDAPS (LDAP over TLS) or with the November 2023 LDAP signing and channel-binding rollout, both of which are channel-protection concerns rather than credential-read concerns.
</FAQItem>

</FAQ>

<StudyGuide slug="dcsync-dcshadow-and-the-domain-replication-attack-class" keyTerms={[
  { term: "MS-DRSR", definition: "Directory Replication Service Remote Protocol; the RPC interface by which any AD domain controller can replicate any object including secret attributes from any other DC." },
  { term: "IDL_DRSGetNCChanges", definition: "MS-DRSR's opnum-3 method that returns changed objects within a naming context; the protocol method DCSync invokes." },
  { term: "Extended Right", definition: "A schema-defined access-control right keyed by GUID rather than by standard ACL bit. Granted via ACE; checked at runtime by the operation that requires it." },
  { term: "Naming Context", definition: "A top-level replication partition of the Active Directory database. DCSync operates against the Domain NC root." },
  { term: "Rights Triad", definition: "DS-Replication-Get-Changes, DS-Replication-Get-Changes-All, and DS-Replication-Get-Changes-In-Filtered-Set extended rights on a naming-context root." },
  { term: "NTDS.dit", definition: "The on-disk Extensible Storage Engine database holding every AD object including secret attributes." },
  { term: "SACL-silent", definition: "A directory operation that does not generate the Event ID 4662/4738/5136 events normally emitted by Domain Services Auditing. Legitimate DC-to-DC replication is SACL-silent by design." },
  { term: "Tier Zero", definition: "Principals and assets whose compromise yields domain-wide control. KRBTGT, Domain Admins, the MSOL_ account, and any principal holding the rights triad are all Tier Zero." },
  { term: "MSOL_ account", definition: "The Entra ID Connect synchronization service account; legitimately holds the rights triad to replicate password hashes to the cloud directory." },
  { term: "Replication Allow List", definition: "MDI's internal baseline of which computers in the domain legitimately speak DRSUAPI to which DCs." }
]} flashcards={[
  { front: "What does MS-DRSR §4.1.10 check on IDL_DRSGetNCChanges?", back: "Only that the calling principal holds the rights triad on the naming-context root. It does not check whether the caller is a domain controller." },
  { front: "What is the Mimikatz commit hash and date for DCSync's introduction?", back: "Commit 7717b7a7173fa6a6b6566bbbc3e7372b464d988f, authored by Benjamin DELPY on 2015-08-11 01:27:13 +0200, subject 'DCSync in mimikatz & for XP/2003'." },
  { front: "What are MDI's three DCSync/DCShadow alert IDs?", back: "External ID 2006 (DCSync), 2028 (DCShadow promotion), 2029 (DCShadow replication request)." },
  { front: "Why can't Microsoft patch DCSync?", back: "Two structural ceilings: stopping the protocol from returning secrets breaks AD replication; adding a machine-identity check shifts the attack class to compromised DC machine accounts (Zerologon)." },
  { front: "What is the BloodHound v6.3 'Butterfly' algorithm?", back: "Bi-directional impact analysis: in addition to 'which principals can reach this target?', also computes 'which targets fall if this principal is compromised?'." }
]} questions={[
  { q: "Why does adding a caller-machine-identity check to MS-DRSR not close the attack class?", a: "Because compromising a DC's machine account (CVE-2020-1472 Zerologon being the canonical worked example) satisfies the new check while still enabling the original attack." },
  { q: "Why is Credential Guard the wrong control for DCSync?", a: "Credential Guard isolates LSASS-resident secrets on the local machine. DCSync reads secrets from a remote DC's NTDS.dit over MS-DRSR; the secret never transits the attacker's LSASS." },
  { q: "Why must the krbtgt password be rotated twice after a confirmed DCSync?", a: "Each AD account stores both the current and previous password. Rotating once invalidates only the older of the two keys; the most recently dumped key remains valid. Rotating a second time, after the first replication interval has converged, invalidates the dumped key." },
  { q: "What does each of the four defense layers miss?", a: "Posture misses transitive paths. Behavioral misses pre-attack staging and compromised-DC speakers. Network misses encrypted RPC payloads. Graph misses net-new ACEs created after the last collection." },
  { q: "Why is the DCShadow gap window structural?", a: "MDI External ID 2029 fires on the rogue's replication request after registration. An attacker who completes register-replicate-deregister inside the alert batch interval commits the persistence write before SOC response." }
]} />

A final observation, since the closing should add something new. The protocol that this article calls structurally unfixable is not unusual. Most Microsoft security primitives that survive long enough enter the same regime -- the LSASS surface, the Kerberos delegation surface, the SMB authentication surface -- where the only honest answer is detection in depth because the protocol's job description and its abuse surface are the same surface viewed from different chairs.

The thing that makes MS-DRSR notable is the *clarity* with which the structural error is visible. Read §4.1.10 once and you are done. Everything from §6 onward is the industry's slow accumulation of detection layers around a gate that cannot be moved. Twenty-five years in, the gate is still where it was on February 17, 2000, and the four layers around it are still under active engineering.
