DCSync activity hunt

Hypothesis: an attacker is abusing the MS-DRSR replication protocol to extract credential hashes from a domain controller without running code on the DC itself.

Domain controllers generate Event ID 4662 on the domain NC object continuously during normal replication, with computer accounts (accounts ending in $) as the subject. The anomaly is a user or service account appearing as the subject in the same event with the DS-Replication-Get-Changes-All GUID in the Properties field. This hunt returns nothing if the domain object SACL is not configured; in that case, the SACL configuration itself is the remediation item.

Data sources: domain controller Security event log; Event ID 4662 on the domain NC object; requires “Audit Directory Service Access” enabled and a SACL on the domain object auditing DS-Replication-Get-Changes-All ({1131f6ad-9c07-11d1-f79f-00c04fc2dcd2}).

$startTime = (Get-Date).AddHours(-24)
$replGuid  = '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2'

$events = Get-WinEvent -ComputerName DC_NAME -FilterHashtable @{
    LogName   = 'Security'
    Id        = 4662
    StartTime = $startTime
}

$events | ForEach-Object {
    # Property indices for Event 4662 are inconsistent across Windows versions;
    # named XML parsing is the reliable path
    $xml  = [xml]$_.ToXml()
    $data = $xml.Event.EventData.Data

    $subject    = ($data | Where-Object Name -eq 'SubjectUserName').'#text'
    $properties = ($data | Where-Object Name -eq 'Properties').'#text'

    # skip computer accounts (DC-to-DC replication)
    if ($subject -like '*$') { return }
    if ($properties -notmatch $replGuid) { return }

    [PSCustomObject]@{
        Time          = $_.TimeCreated
        SubjectUser   = $subject
        SubjectDomain = ($data | Where-Object Name -eq 'SubjectDomainName').'#text'
        ObjectDN      = ($data | Where-Object Name -eq 'ObjectName').'#text'
        LogonId       = ($data | Where-Object Name -eq 'SubjectLogonId').'#text'
    }
} | Sort-Object Time

Any result after filtering computer accounts is unusual. A user account exercising DS-Replication-Get-Changes-All has either been explicitly granted that right (administrative oversight question) or is being used by an attacker who compromised an account with that right.

The LogonId field in the output links to a corresponding Event ID 4624 logon event on the same DC, which records the source IP address. Querying Event ID 4624 with the matching TargetLogonId value identifies the host from which the replication call originated.

# correlate LogonId with Event 4624 to get source IP
$logonId = '0x...'  # value from DCSync hunt output

Get-WinEvent -ComputerName DC_NAME -FilterHashtable @{
    LogName   = 'Security'
    Id        = 4624
    StartTime = $startTime
} | Where-Object {
    $xml  = [xml]$_.ToXml()
    $data = $xml.Event.EventData.Data
    ($data | Where-Object Name -eq 'TargetLogonId').'#text' -eq $logonId
} | ForEach-Object {
    $xml  = [xml]$_.ToXml()
    $data = $xml.Event.EventData.Data
    [PSCustomObject]@{
        Time      = $_.TimeCreated
        Account   = ($data | Where-Object Name -eq 'TargetUserName').'#text'
        IpAddress = ($data | Where-Object Name -eq 'IpAddress').'#text'
        LogonType = ($data | Where-Object Name -eq 'LogonType').'#text'
    }
}

A source IP that belongs to a workstation or member server, rather than a domain controller, confirms the DCSync pattern. Accounts legitimately granted replication rights (such as Azure AD Connect sync accounts in hybrid environments) appear in the first query; their source IPs are the expected sync server addresses and distinguish legitimate operations from attacker activity.