~/posts/web/dcsync-dcshadow-rpc-context-detection.md

DCSync and DCShadow: RPC context determines the alert

Correlate interface negotiation, context IDs, and opnums in directory replication. Examine flowbit blind spots, DCShadow's reverse pull, byte order, protocol-aware rules, and deployment regression cases.

date[31:24]
read[23:16]
8 min
cat[15:8]
Web security
Contents
  1. 0x00Define roles before alerts
  2. 0x01Negotiation sits between the decisive packets
  3. 0x02Interpret opnum from the PDU boundary
  4. 0x03A flowbit approximates state, not interface negotiation
  5. 0x04DCShadow reverses the replication direction
  6. 0x05Byte order comes from the packet
  7. 0x06Protocol rules still need regression testing
  8. 0x07Official references

A DsGetNCChanges request in Active Directory may be ordinary domain-controller replication or a directory read from another host holding replication rights. Detection hinges on who invoked which operation on which negotiated RPC interface, not on a tool name.

The following analysis reconstructs that chain from 2021 DCSync and DCShadow network observations. Interface negotiation, an operation request, a response, and the actual directory effect are different levels of evidence. Seeing the request alone does not prove that sensitive data was read or changed.

Define roles before alerts

DCSync uses directory replication privileges to request updates. DCShadow additionally arranges a temporary replication source and initiates directory changes. Neither is a protocol flaw that grants domain privileges merely through network reachability; legitimate controllers use the same interface.

WORKSTATIONS and DCS should be maintained asset sets, not guessed subnets. Non-controller services with delegated replication tasks need business-scope review, while controller status does not make every operation appropriate. A sensor needs coverage of internal replication paths; an Internet-egress capture usually is not enough.

flowchart TD
  A["Connection endpoints and host roles"] --> B["Bind / Alter Context"]
  B --> C["Accepted context → interface"]
  C --> D["Request: context ID + opnum"]
  D --> E["Response and directory effect"]

Operation numbers have meaning only within an interface. Distinguish three contexts: p_cont_id selects a presentation context, call_id correlates RPC requests and responses, and the DRS_HANDLE returned by IDL_DRSBind belongs to the DRS application interface. An RPC Bind PDU is not an IDL_DRSBind method call.

Negotiation sits between the decisive packets

DCSync — TCP stream 4pcap
No.SourceDestinationProtocolLengthInfo
28192.0.2.16192.0.2.5DCERPC1794Bind, call_id=2
30192.0.2.5192.0.2.16DCERPC314Bind_ack
31192.0.2.16192.0.2.5DCERPC274Alter_context
32192.0.2.5192.0.2.16DCERPC159Alter_context_resp
41192.0.2.16192.0.2.5DRSUAPI498DsGetNCChanges request, context_id=0, opnum=3
45192.0.2.5192.0.2.16DRSUAPI822DsGetNCChanges response
6 packets

Addresses are replaced with documentation addresses; packet numbers and lengths are retained. In TCP stream 4, frame 28 proposes the interface, frame 41 requests DsGetNCChanges, and frame 45 is its response. Bind_ack and Alter_context also occur in between. A detector should correlate proposals with acceptance, not treat every Bind as a usable interface.

The UUID is e3514235-4b06-11d1-ab04-00c04fc2dcd2, with interface version 4.0 shown in the record. Its little-endian representation is:

DRSUAPI UUID — little-endian representation
00000000354251E3064BD111AB0400C04FC2DCD2
  1. Data10x00–0x03
  2. Data20x04–0x05
  3. Data30x06–0x07
  4. Data40x08–0x0F

The first three fields follow their respective field widths, while Data4 retains its byte sequence; this is not a reversal of the whole UUID. UUID(...).bytes_le provides an independent check. Binding also includes a transfer syntax, whose identifier must not be confused with the interface UUID.

Interpret opnum from the PDU boundary

DCE/RPC request — fixed headerLE
OffsetNameTypeSizeValue
0x00rpc_vers15
0x01rpc_vers_minor10
0x02ptype10
0x03pfc_flags1
0x04packed_drep4
0x08frag_length2
0x0aauth_length2
0x0ccall_id4
0x10alloc_hint4
0x14p_cont_id2
0x16opnum23
size = 0x18 (24 bytes)

This is the fixed request-header layout. Values mark only the version, PDU type, and operation discussed here; they do not reconstruct a complete packet. Offsets start at the RPC PDU, not at the Ethernet frame or TCP segment. Optional object UUIDs, authentication trailers, and stub data require additional parsing.

Bind starts with 05 00 0B and Request with 05 00 00. Frame 41 uses context ID = 0; little-endian 03 00 at offset 22 identifies IDL_DRSGetNCChanges, which replicates updates from a naming-context replica on the server. Microsoft DRSGetNCChanges

This request shows SPNEGO packet privacy. The visible request header identifies the operation but does not expose all protected arguments. A response frame also does not, by itself, establish successful completion or identify the returned attributes.

A flowbit approximates state, not interface negotiation

raw-drs.rulestext
alert tcp $WORKSTATIONS any -> $DCS any (msg:"DRS bind candidate"; flow:established,to_server; content:"|05 00 0b|"; depth:3; content:"|35 42 51 e3 06 4b d1 11 ab 04 00 c0 4f c2 dc d2|"; depth:100; flowbits:set,drs_seen; flowbits:noalert; sid:9001000; rev:1;)
alert tcp $WORKSTATIONS any -> $DCS any (msg:"DRS GetNCChanges candidate"; flow:established,to_server; flowbits:isset,drs_seen; content:"|05 00 00|"; depth:3; content:"|03 00|"; offset:22; depth:2; sid:9001001; rev:1;)

These rules illustrate raw-byte matching only: they assume a detection buffer aligned to the PDU start and little-endian representation. The first sets a flow marker without alerting; the second checks the operation after that marker exists on the same flow. They have not been loaded into Suricata or tested through PCAP replay in this environment.

The marker avoids repeated UUID searches but discards important dimensions. A connection can have multiple presentation contexts, and drs_seen does not prove that every later opnum=3 belongs to DRSUAPI. A complete design at least maintains connection + context ID → accepted interface and transfer syntax, handling context changes, rejected negotiation, and connection termination.

TCP segmentation, combined PDUs, RPC fragmentation, and capture loss affect raw offsets. A request observed without its negotiation should carry an unknown-context status, rather than being silently trusted or assigned a DRS meaning from its opnum alone.

DCShadow reverses the replication direction

The sample uses two execution contexts: one prepares a local RPC service, while another has the directory-management rights needed for registration, synchronization, and removal. The console reports WIN2016 controller functionality, which is not a complete Windows build number.

RPC service: selected lineslog
RPC bind registered
RPC Server is waiting!
1 object(s) pushed
RPC bind unregistered
stopping RPC server
RPC server stopped

Only service-state lines are retained above. Domains, hosts, account identifiers, process IDs, and the session key are omitted from the public record. 1 object(s) pushed is tool output; an independent directory query is still needed to verify the attribute effect.

DCShadow — selected streamspcap
No.SourceDestinationProtocolLengthInfo
74192.0.2.16192.0.2.5DCERPC2026stream=7, Bind
93192.0.2.16192.0.2.5DRSUAPI434stream=7, DRSUAPI_REPLICA_ADD request, opnum=5
118192.0.2.5192.0.2.16DCERPC468stream=10, Bind
125192.0.2.5192.0.2.16DRSUAPI150stream=10, DsGetNCChanges request
127192.0.2.16192.0.2.5DRSUAPI1298stream=10, DsGetNCChanges response
130192.0.2.5192.0.2.16DRSUAPI178stream=7, DRSUAPI_REPLICA_ADD response
131192.0.2.16192.0.2.5DRSUAPI354stream=7, DRSUAPI_REPLICA_DEL request
7 packets

Frame 93 goes from 192.0.2.16 to controller 192.0.2.5 and requests a replication source with opnum=5. Microsoft defines IDL_DRSReplicaAdd as adding a replication-source reference for the specified naming context, not as an attribute write in itself. Microsoft DRSReplicaAdd

Frame 125 in stream 10 reverses direction: the controller sends DsGetNCChanges to that endpoint, which returns data in frame 127. Normal AD replication likewise has the receiving controller pull changes from a source, so direction must be interpreted with host roles. Microsoft replication behavior

A rule covering only workstation-to-controller opnum=3 misses that pull. Also monitor controller DRS requests to non-baseline replication sources, correlating them with source-addition, response, and deletion activity in stream 7. This is cross-flow event correlation, not reuse of one flow's flowbit on another.

Byte order comes from the packet

The high nibble of the first packed_drep byte selects integer representation: 1 means little-endian and 0 big-endian. Operation three can therefore appear as 03 00 or 00 03. Context, call ID, and other multibyte fields follow the same representation.

request-header.pypython
from uuid import UUID
DRS = "e3514235-4b06-11d1-ab04-00c04fc2dcd2"

def request_header(pdu):
    if len(pdu) < 24 or pdu[:3] != b"\x05\x00\x00":
        raise ValueError("not a complete fixed request header")
    rep = pdu[4] >> 4
    if rep not in (0, 1):
        raise ValueError("unsupported integer representation")
    order = "little" if rep == 1 else "big"
    return (int.from_bytes(pdu[12:16], order),
            int.from_bytes(pdu[20:22], order),
            int.from_bytes(pdu[22:24], order))

This function reads only a complete fixed request header. It does not validate the entire fragment, authentication data, optional object UUID, or TCP reassembly, and it does not negotiate interfaces. It is a field-checking helper, not a production parser.

Local tests use synthetic request headers for operations three and five in both byte orders, then consult a context map explicitly designated as already accepted. The wrong interface, another flow, and an unrelated operation do not match. Actual output:

drs-context-model.py
$ python drs-context-model.py
UUID byte order: PASS
request headers: 4 PASS
invalid headers: 4 PASS
context correlation: 5 PASS
scope: synthetic headers and accepted-context map only

Adding big-endian opnum bytes to the request rule while retaining only a little-endian UUID in the bind rule does not provide complete big-endian support. Field decoding and interface negotiation need consistent treatment.

Protocol rules still need regression testing

The deployment syntax example uses keywords documented for Suricata 8.0.3. This is an explicit rule-reference version, not a claim that the 2021 capture used that engine. It matches the exact set of operations three and five without relying on comparison operators.

drs-semantic.rulestext
alert tcp $WORKSTATIONS any -> $DCS any (msg:"Unexpected DRS replication operation"; flow:established,to_server; dcerpc.iface:e3514235-4b06-11d1-ab04-00c04fc2dcd2; dcerpc.opnum:3,5; sid:9001002; rev:1;)

dcerpc.iface and dcerpc.opnum delegate interface and operation matching to the protocol parser. Supported encapsulation, visibility, and reassembly still depend on configuration. This direction covers only workstation-to-controller traffic; a separate asset policy must cover DCShadow's reverse path. OISF keyword documentation

Pre-deployment regression matrix8 rows
Input variation Acceptance target
Full negotiation and request Traceable flow, context, and packet numbers
Request only Explicit missing-context status
Same opnum on a different interface No identity borrowed from another context
Rejected Bind / Alter Context No accepted mapping created
Different TCP splits or combined PDUs Equivalent meaning after reassembly
Different integer representation Consistent UUID and request-field decoding
Controller pull from a non-baseline endpoint Not lost to a one-way source-role rule
Legitimate replication and approved exceptions An explainable baseline

Completed work covers offline UUID, request-header, and correlation models. No PCAP replay or directory changes were performed. Throughput, loss, fragmentation, rejected-binding, and encapsulation regressions remain deployment acceptance tasks, not reported passes.

An actionable alert should retain host roles, interface, context ID, opnum, and request/response frames, then correlate directory auditing. Identifying a sensitive operation starts an investigation; it does not finish proof of success or attribution.

Official references

NORMAL~/posts/web/dcsync-dcshadow-rpc-context-detection.md§--
0%en