Active Directory 中出现 DsGetNCChanges,既可能是正常域控同步,也可能是持有复制权限的其他主机读取目录。检测的核心不是工具名称,而是谁在已协商的哪个 RPC 接口上调用了什么操作。
以下依据 2021 年 DCSync、DCShadow 的网络记录恢复这条证据链。接口绑定、操作调用、服务响应和实际目录效果是不同层级的证据;只识别到请求,不代表敏感数据已被读取或修改成功。
先定义角色,再定义告警
DCSync 借用目录复制权限请求更新;DCShadow 还涉及临时组织复制来源并推动目录变化。它们不是仅靠网络可达就能获得域权限的协议漏洞,正常域控也广泛使用同一接口。
WORKSTATIONS 与 DCS 应来自维护中的资产集合,而不是临时猜测的网段。对被委派复制任务的非域控服务还要核对业务范围;域控身份本身也不保证一次操作合理。传感器需覆盖内部复制路径,互联网出口抓包通常不足以回答这些问题。
flowchart TD A["连接端点与主机角色"] --> B["Bind / Alter Context"] B --> C["已接受的 context → 接口"] C --> D["Request:context ID + opnum"] D --> E["响应与目录效果"]
操作编号只在接口内部有意义。还要区分三种上下文:p_cont_id 选择呈现上下文,call_id 关联 RPC 请求与响应,IDL_DRSBind 返回的 DRS_HANDLE 属于 DRS 业务接口。RPC Bind PDU 与 IDL_DRSBind 调用也不是同一件事。
两个数据包之间还有协商状态
| 序号 | 源地址 | 目的地址 | 协议 | 长度 | 信息 |
|---|---|---|---|---|---|
| 28 | 192.0.2.16 | 192.0.2.5 | DCERPC | 1794 | Bind, call_id=2 |
| 30 | 192.0.2.5 | 192.0.2.16 | DCERPC | 314 | Bind_ack |
| 31 | 192.0.2.16 | 192.0.2.5 | DCERPC | 274 | Alter_context |
| 32 | 192.0.2.5 | 192.0.2.16 | DCERPC | 159 | Alter_context_resp |
| 41 | 192.0.2.16 | 192.0.2.5 | DRSUAPI | 498 | DsGetNCChanges request, context_id=0, opnum=3 |
| 45 | 192.0.2.5 | 192.0.2.16 | DRSUAPI | 822 | DsGetNCChanges response |
地址已替换为文档地址,包号与长度保留。TCP stream 4 的第 28 帧声明接口,第 41 帧请求 DsGetNCChanges,第 45 帧是对应响应;中间还有 Bind_ack 与 Alter_context。检测器应关联提议和接受结果,而不是看到任意 Bind 就视为接口已可用。
接口 UUID 为 e3514235-4b06-11d1-ab04-00c04fc2dcd2,记录显示接口版本 4.0。小端数据表示下,UUID 的字节是:
Data10x00–0x03Data20x04–0x05Data30x06–0x07Data40x08–0x0F
前三个字段分别按各自字段宽度处理字节序,Data4 保持字节顺序,并非整体倒序。独立核对可用 UUID(...).bytes_le;绑定中还存在 transfer syntax,接口标识和传输语法不要混为一个 UUID。
从 PDU 起点解释 opnum
| 偏移 | 名称 | 类型 | 大小 | 值 |
|---|---|---|---|---|
| 0x00 | rpc_vers | 1 | 5 | |
| 0x01 | rpc_vers_minor | 1 | 0 | |
| 0x02 | ptype | 1 | 0 | |
| 0x03 | pfc_flags | 1 | ||
| 0x04 | packed_drep | 4 | ||
| 0x08 | frag_length | 2 | ||
| 0x0a | auth_length | 2 | ||
| 0x0c | call_id | 4 | ||
| 0x10 | alloc_hint | 4 | ||
| 0x14 | p_cont_id | 2 | ||
| 0x16 | opnum | 2 | 3 |
这是固定请求头布局,value 仅标注此处讨论的版本、PDU 类型和操作号,不表示补齐了整份报文。所有偏移相对于 RPC PDU 起点,不是以太网帧或 TCP 段起点;可选对象 UUID、认证尾部和 stub 数据仍需后续解析。
Bind 前缀是 05 00 0B,Request 前缀是 05 00 00。第 41 帧的 context ID = 0,偏移 22 的小端 03 00 对应 IDL_DRSGetNCChanges,其语义是从服务端命名上下文副本复制更新。Microsoft DRSGetNCChanges
该请求显示 SPNEGO packet privacy。请求头的操作类型仍可见,不代表传感器能读取受保护的全部参数;响应帧存在,也不自动说明返回成功或包含哪些属性。
flowbit 是近似状态,不是接口状态机
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;)这组规则仅用于说明原始字节匹配:假设检测缓冲区从 PDU 起点开始,且采用小端表示。第一条设置流标记并抑制自身告警;第二条在同一流已标记后检查请求编号。它们没有经过此环境中的 Suricata 加载或 PCAP 回放测试。
流标记省掉了重复 UUID 搜索,却丢失了重要维度。一个连接可以有多个 presentation context,drs_seen 并不证明之后的每个 opnum=3 都属于 DRSUAPI。完整实现至少维护 连接 + context ID → 已接受接口及传输语法,并处理上下文变更、失败协商和连接结束。
TCP 重分段、多 PDU 合并、RPC 分片和抓包缺失都会影响原始偏移。若只捕获到请求而没看到协商,应记录“上下文未知”,而不是静默当作可信,也不是把 opnum 强行解释成 DRS 操作。
DCShadow 的复制方向会翻转
样本使用两个执行上下文:一个准备本地 RPC 服务,另一个持有所需目录管理权限并推动注册、同步和注销。控制台中可见域控功能级别 WIN2016;这不等于完整 Windows 构建号。
RPC bind registered
RPC Server is waiting!
1 object(s) pushed
RPC bind unregistered
stopping RPC server
RPC server stopped上方只保留服务状态行。域名、主机、账户标识、进程号以及会话密钥不进入公开记录;1 object(s) pushed 是工具输出,实际属性效果仍应由独立目录查询核对。
| 序号 | 源地址 | 目的地址 | 协议 | 长度 | 信息 |
|---|---|---|---|---|---|
| 74 | 192.0.2.16 | 192.0.2.5 | DCERPC | 2026 | stream=7, Bind |
| 93 | 192.0.2.16 | 192.0.2.5 | DRSUAPI | 434 | stream=7, DRSUAPI_REPLICA_ADD request, opnum=5 |
| 118 | 192.0.2.5 | 192.0.2.16 | DCERPC | 468 | stream=10, Bind |
| 125 | 192.0.2.5 | 192.0.2.16 | DRSUAPI | 150 | stream=10, DsGetNCChanges request |
| 127 | 192.0.2.16 | 192.0.2.5 | DRSUAPI | 1298 | stream=10, DsGetNCChanges response |
| 130 | 192.0.2.5 | 192.0.2.16 | DRSUAPI | 178 | stream=7, DRSUAPI_REPLICA_ADD response |
| 131 | 192.0.2.16 | 192.0.2.5 | DRSUAPI | 354 | stream=7, DRSUAPI_REPLICA_DEL request |
第 93 帧从 192.0.2.16 向域控 192.0.2.5 请求添加复制来源,opnum=5。Microsoft 将 IDL_DRSReplicaAdd 定义为给指定命名上下文增加复制来源引用,而不是直接把它等同于一次属性写入。Microsoft DRSReplicaAdd
随后 stream 10 的第 125 帧方向反转:域控向该端点发送 DsGetNCChanges,第 127 帧由端点返回数据。正常 AD 复制本身也是接收方拉取来源的更新,方向应结合角色解释。Microsoft 复制过程
因此,只部署“工作站 → 域控、opnum=3”的规则会遗漏这条反向拉取。应另行关注域控对非基线复制来源的 DRS 请求,并把它与 stream 7 中的来源添加、返回及删除操作关联;这不是把不同流的 flowbit 混用。
字节序来自报文,不来自操作系统假设
packed_drep 第一个字节的高半字节表示整数编码:1 为小端,0 为大端。于是操作号三可表示为 03 00 或 00 03;context、call ID 和其他多字节字段也应遵循同一表示。
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))
这个函数只读完整的固定请求头,未验证整份片段长度、认证数据、对象 UUID 或 TCP 重组,也未执行接口绑定。它是独立字段核对工具,不是生产解析器。
本地以合成请求头分别检查大小端的 opnum 三和五,再对照一个预先声明为“已接受”的 context 映射。错误接口、其他流与无关操作号都不匹配,实际输出如下:
$ 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只给请求规则增加大端 opnum、而绑定规则仍只搜索小端 UUID,不构成完整的大端支持。字段读取与接口协商应统一处理。
协议规则仍需要回归验收
部署语法对照采用 Suricata 8.0.3 文档中的关键字;这是明确版本的规则示例,不是声称 2021 年抓包使用该版本引擎。只对编号三和五做精确集合匹配,不依赖比较运算符。
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 和 dcerpc.opnum 将接口与操作匹配交给协议解析器;实际支持的封装、可见性与重组仍取决于引擎配置。上面的方向仅覆盖工作站到域控,DCShadow 的反向链应以单独的资产策略补充。OISF 关键字文档
| 输入变化 | 验收目标 |
|---|---|
| 完整协商与请求 | 能回到具体流、context 和帧号 |
| 仅有请求 | 明确标识缺失上下文 |
| 相同 opnum、不同接口 | 不借用其他 context 的接口身份 |
| 被拒绝的 Bind / Alter Context | 不建立已接受映射 |
| 改变 TCP 切片、合并 PDU | 重组后的语义保持一致 |
| 改变整数表示 | UUID 与请求字段一致解码 |
| 域控向非基线端点拉取 | 不被单向源角色规则遗漏 |
| 正常复制及批准的业务例外 | 保留可解释的基线 |
目前完成的是 UUID、请求头和关联逻辑的离线模型,没有重放 PCAP,也没有执行目录变更。吞吐、丢包、分片、绑定拒绝及封装回归属于部署验收项,不记作已通过。
最终告警应给出主机角色、接口、context ID、opnum、请求与响应帧,再关联目录审计。识别敏感操作是调查起点,不是操作成功或攻击归因的终点。