Skip to content

Commit a9eed2d

Browse files
authored
Add some [MS-DRSR] parts (#4572)
1 parent 28287eb commit a9eed2d

File tree

5 files changed

+380
-0
lines changed

5 files changed

+380
-0
lines changed

scapy/layers/msrpce/all.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
# Low-level RPC definitions
3333
"msrpce.raw.ept",
3434
"msrpce.raw.ms_dcom",
35+
"msrpce.raw.ms_drsr",
3536
"msrpce.raw.ms_nrpc",
3637
"msrpce.raw.ms_samr",
3738
"msrpce.raw.ms_srvs",

scapy/layers/msrpce/msdrsr.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# SPDX-License-Identifier: GPL-2.0-or-later
2+
# This file is part of Scapy
3+
# See https://scapy.net/ for more information
4+
# Copyright (C) Gabriel Potter
5+
6+
"""
7+
[MS-DRSR] Directory Replication Service (DRS) Remote Protocol
8+
"""
9+
10+
import uuid
11+
from scapy.packet import Packet
12+
from scapy.fields import LEIntField, FlagsField, UUIDField, UTCTimeField
13+
14+
from scapy.layers.msrpce.raw.ms_drsr import UUID
15+
from scapy.layers.msrpce.raw.ms_drsr import * # noqa: F403,F401
16+
17+
# [MS-DRSR] sect 5.39 DRS_EXTENSIONS_INT
18+
19+
20+
class DRS_EXTENSIONS_INT(Packet):
21+
fields_desc = [
22+
FlagsField(
23+
"dwFlags",
24+
0,
25+
-32,
26+
{
27+
0x00000001: "BASE",
28+
0x00000002: "ASYNCREPL",
29+
0x00000004: "REMOVEAPI",
30+
0x00000008: "MOVEREQ_V2",
31+
0x00000010: "GETCHG_DEFLATE",
32+
0x00000020: "DCINFO_V1",
33+
0x00000040: "RESTORE_USN_OPTIMIZATION",
34+
0x00000080: "ADDENTRY",
35+
0x00000100: "KCC_EXECUTE",
36+
0x00000200: "ADDENTRY_V2",
37+
0x00000400: "LINKED_VALUE_REPLICATION",
38+
0x00000800: "DCINFO_V2",
39+
0x00001000: "INSTANCE_TYPE_NOT_REQ_ON_MOD",
40+
0x00002000: "CRYPTO_BIND",
41+
0x00004000: "GET_REPL_INFO",
42+
0x00008000: "STRONG_ENCRYPTION",
43+
0x00010000: "DCINFO_VFFFFFFFF",
44+
0x00020000: "TRANSITIVE_MEMBERSHIP",
45+
0x00040000: "ADD_SID_HISTORY",
46+
0x00080000: "POST_BETA3",
47+
0x00100000: "GETCHGREQ_V5",
48+
0x00200000: "GETMEMBERSHIPS2",
49+
0x00400000: "GETCHGREQ_V6",
50+
0x00800000: "NONDOMAIN_NCS",
51+
0x01000000: "GETCHGREQ_V8",
52+
0x02000000: "GETCHGREPLY_V5",
53+
0x04000000: "GETCHGREPLY_V6",
54+
0x08000000: "WHISTLER_BETA3",
55+
0x10000000: "W2K3_DEFLATE",
56+
0x20000000: "GETCHGREQ_V10",
57+
0x40000000: "R2",
58+
0x80000000: "R3",
59+
},
60+
),
61+
UUIDField("SiteObjGuid", None, uuid_fmt=UUIDField.FORMAT_LE),
62+
LEIntField("Pid", 0),
63+
UTCTimeField("dwReplEpoch", None, fmt="<I"),
64+
FlagsField(
65+
"dwFlagsExt",
66+
0,
67+
-32,
68+
{
69+
0x00000001: "ADAM",
70+
0x00000002: "LH_BETA2",
71+
0x00000004: "RECYCLE_BIN",
72+
0x00000100: "GETCHGREPLY_V9",
73+
0x00000400: "RPC_CORRELATIONID_1",
74+
},
75+
),
76+
UUIDField("ConfigObjGuid", None, uuid_fmt=UUIDField.FORMAT_LE),
77+
LEIntField("dwExtCaps", 0),
78+
]
79+
80+
81+
# [MS-DRSR] sect 5.138 NTDSAPI_CLIENT_GUID
82+
83+
NTDSAPI_CLIENT_GUID = UUID(uuid.UUID("{e24d201a-4fd6-11d1-a3da-0000f875ae0d}").bytes_le)

scapy/layers/msrpce/raw/ms_drsr.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
# This file is part of Scapy
3+
# See https://scapy.net/ for more information
4+
# Copyright (C) Gabriel Potter
5+
6+
"""
7+
Very partial RPC definitions for the following interfaces:
8+
- drsuapi (v4.0): e3514235-4b06-11d1-ab04-00c04fc2dcd2
9+
"""
10+
11+
from enum import IntEnum
12+
import uuid
13+
14+
from scapy.fields import StrFixedLenField
15+
from scapy.layers.dcerpc import (
16+
NDRPacket,
17+
DceRpcOp,
18+
NDRByteField,
19+
NDRConfFieldListField,
20+
NDRConfPacketListField,
21+
NDRConfStrLenField,
22+
NDRConfStrLenFieldUtf16,
23+
NDRConfVarFieldListField,
24+
NDRConfVarStrNullField,
25+
NDRConfVarStrNullFieldUtf16,
26+
NDRContextHandle,
27+
NDRFullPointerField,
28+
NDRInt3264EnumField,
29+
NDRIntField,
30+
NDRLongField,
31+
NDRPacketField,
32+
NDRRecursiveField,
33+
NDRRefEmbPointerField,
34+
NDRShortField,
35+
NDRSignedByteField,
36+
NDRSignedIntField,
37+
NDRSignedLongField,
38+
NDRUnionField,
39+
NDRVarStrLenField,
40+
NDRVarStrLenFieldUtf16,
41+
register_dcerpc_interface,
42+
)
43+
44+
45+
class UUID(NDRPacket):
46+
ALIGNMENT = (4, 4)
47+
fields_desc = [
48+
NDRIntField("Data1", 0),
49+
NDRShortField("Data2", 0),
50+
NDRShortField("Data3", 0),
51+
StrFixedLenField("Data4", "", length=8),
52+
]
53+
54+
55+
class DRS_EXTENSIONS(NDRPacket):
56+
ALIGNMENT = (4, 8)
57+
DEPORTED_CONFORMANTS = ["rgb"]
58+
fields_desc = [
59+
NDRIntField("cb", None, size_of="rgb"),
60+
NDRConfStrLenField(
61+
"rgb", "", size_is=lambda pkt: pkt.cb, conformant_in_struct=True
62+
),
63+
]
64+
65+
66+
class IDL_DRSBind_Request(NDRPacket):
67+
fields_desc = [
68+
NDRFullPointerField(NDRPacketField("puuidClientDsa", UUID(), UUID)),
69+
NDRFullPointerField(
70+
NDRPacketField("pextClient", DRS_EXTENSIONS(), DRS_EXTENSIONS)
71+
),
72+
]
73+
74+
75+
class IDL_DRSBind_Response(NDRPacket):
76+
fields_desc = [
77+
NDRFullPointerField(
78+
NDRPacketField("ppextServer", DRS_EXTENSIONS(), DRS_EXTENSIONS)
79+
),
80+
NDRPacketField("phDrs", NDRContextHandle(), NDRContextHandle),
81+
NDRIntField("status", 0),
82+
]
83+
84+
85+
class IDL_DRSUnbind_Request(NDRPacket):
86+
fields_desc = [NDRPacketField("phDrs", NDRContextHandle(), NDRContextHandle)]
87+
88+
89+
class IDL_DRSUnbind_Response(NDRPacket):
90+
fields_desc = [
91+
NDRPacketField("phDrs", NDRContextHandle(), NDRContextHandle),
92+
NDRIntField("status", 0),
93+
]
94+
95+
96+
class DRS_MSG_CRACKREQ_V1(NDRPacket):
97+
ALIGNMENT = (4, 8)
98+
fields_desc = [
99+
NDRIntField("CodePage", 0),
100+
NDRIntField("LocaleId", 0),
101+
NDRIntField("dwFlags", 0),
102+
NDRIntField("formatOffered", 0),
103+
NDRIntField("formatDesired", 0),
104+
NDRIntField("cNames", None, size_of="rpNames"),
105+
NDRFullPointerField(
106+
NDRConfFieldListField(
107+
"rpNames",
108+
[],
109+
NDRFullPointerField(
110+
NDRConfVarStrNullFieldUtf16("rpNames", ""), deferred=True
111+
),
112+
size_is=lambda pkt: pkt.cNames,
113+
),
114+
deferred=True,
115+
),
116+
]
117+
118+
119+
class PDS_NAME_RESULT_ITEMW(NDRPacket):
120+
ALIGNMENT = (4, 8)
121+
fields_desc = [
122+
NDRIntField("status", 0),
123+
NDRFullPointerField(NDRConfVarStrNullFieldUtf16("pDomain", ""), deferred=True),
124+
NDRFullPointerField(NDRConfVarStrNullFieldUtf16("pName", ""), deferred=True),
125+
]
126+
127+
128+
class DS_NAME_RESULTW(NDRPacket):
129+
ALIGNMENT = (4, 8)
130+
fields_desc = [
131+
NDRIntField("cItems", None, size_of="rItems"),
132+
NDRFullPointerField(
133+
NDRConfPacketListField(
134+
"rItems",
135+
[PDS_NAME_RESULT_ITEMW()],
136+
PDS_NAME_RESULT_ITEMW,
137+
size_is=lambda pkt: pkt.cItems,
138+
),
139+
deferred=True,
140+
),
141+
]
142+
143+
144+
class DRS_MSG_CRACKREPLY_V1(NDRPacket):
145+
ALIGNMENT = (4, 8)
146+
fields_desc = [
147+
NDRFullPointerField(
148+
NDRPacketField("pResult", DS_NAME_RESULTW(), DS_NAME_RESULTW), deferred=True
149+
)
150+
]
151+
152+
153+
class IDL_DRSCrackNames_Request(NDRPacket):
154+
fields_desc = [
155+
NDRPacketField("hDrs", NDRContextHandle(), NDRContextHandle),
156+
NDRIntField("dwInVersion", 0),
157+
NDRUnionField(
158+
[
159+
(
160+
NDRPacketField(
161+
"pmsgIn", DRS_MSG_CRACKREQ_V1(), DRS_MSG_CRACKREQ_V1
162+
),
163+
(
164+
(lambda pkt: getattr(pkt, "dwInVersion", None) == 1),
165+
(lambda _, val: val.tag == 1),
166+
),
167+
)
168+
],
169+
StrFixedLenField("pmsgIn", "", length=0),
170+
align=(4, 8),
171+
switch_fmt=("L", "L"),
172+
),
173+
]
174+
175+
176+
class IDL_DRSCrackNames_Response(NDRPacket):
177+
fields_desc = [
178+
NDRIntField("pdwOutVersion", 0),
179+
NDRUnionField(
180+
[
181+
(
182+
NDRPacketField(
183+
"pmsgOut", DRS_MSG_CRACKREPLY_V1(), DRS_MSG_CRACKREPLY_V1
184+
),
185+
(
186+
(lambda pkt: getattr(pkt, "pdwOutVersion", None) == 1),
187+
(lambda _, val: val.tag == 1),
188+
),
189+
)
190+
],
191+
StrFixedLenField("pmsgOut", "", length=0),
192+
align=(4, 8),
193+
switch_fmt=("L", "L"),
194+
),
195+
NDRIntField("status", 0),
196+
]
197+
198+
199+
DRSUAPI_OPNUMS = {
200+
0: DceRpcOp(IDL_DRSBind_Request, IDL_DRSBind_Response),
201+
1: DceRpcOp(IDL_DRSUnbind_Request, IDL_DRSUnbind_Response),
202+
12: DceRpcOp(IDL_DRSCrackNames_Request, IDL_DRSCrackNames_Response),
203+
}
204+
register_dcerpc_interface(
205+
name="drsuapi",
206+
uuid=uuid.UUID("e3514235-4b06-11d1-ab04-00c04fc2dcd2"),
207+
version="4.0",
208+
opnums=DRSUAPI_OPNUMS,
209+
)
3.47 KB
Binary file not shown.

test/scapy/layers/msdrsr.uts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
% MS-DRSR tests
2+
3+
+ [MS-DRSR] test vectors
4+
5+
+ Dissect DRSR Crack_Names exchange
6+
7+
= [EXCH] - Load MSDRSR exchange and decrypt (SPNEGOSSP/NTLMSSP)
8+
9+
load_layer("msrpce")
10+
bind_layers(TCP, DceRpc5, sport=49685) # the DCE/RPC port
11+
bind_layers(TCP, DceRpc5, dport=49685)
12+
13+
conf.dcerpc_session_enable = True
14+
conf.winssps_passive = [
15+
SPNEGOSSP(
16+
[
17+
NTLMSSP(
18+
IDENTITIES={
19+
"Administrator": MD4le("Password123!"),
20+
},
21+
)
22+
]
23+
)
24+
]
25+
pkts = sniff(offline=scapy_path('test/pcaps/dcerpc_msdrsr_cracknames.pcapng.gz'), session=TCPSession)
26+
conf.dcerpc_session_enable = False
27+
28+
= [EXCH] - Check IDL_DRSBind_Request
29+
30+
from scapy.layers.msrpce.msdrsr import DRS_EXTENSIONS_INT
31+
32+
bindreq = pkts[7]
33+
assert IDL_DRSBind_Request in bindreq
34+
ext = DRS_EXTENSIONS_INT(bindreq[IDL_DRSBind_Request].valueof("pextClient").rgb)
35+
assert ext.Pid == 1234
36+
assert ext.dwReplEpoch == 1729468809
37+
38+
= [EXCH] - Check IDL_DRSBind_Response
39+
40+
import uuid
41+
42+
bindresp = pkts[8]
43+
assert IDL_DRSBind_Response in bindresp
44+
assert bindresp[IDL_DRSBind_Response].phDrs.uuid == b'\xf4$I\xf5\xde\x0c\xfcO\x8b\xfa\xb0Y\x87\xf4\x11i'
45+
ext = DRS_EXTENSIONS_INT(bindresp[IDL_DRSBind_Response].valueof("ppextServer").rgb)
46+
assert ext.dwFlags.GETCHGREQ_V10
47+
assert ext.dwFlags == 0x3fffff7f
48+
assert ext.Pid == 696
49+
assert ext.ConfigObjGuid == uuid.UUID('14ea64e0-3470-48e6-9ace-77012d8d474f')
50+
51+
= [EXCH] - Check IDL_DRSCrackNames_Request
52+
53+
cnreq = pkts[9]
54+
assert IDL_DRSCrackNames_Request in cnreq
55+
56+
crackreq = cnreq[IDL_DRSCrackNames_Request].valueof("pmsgIn")
57+
assert crackreq.formatOffered == 11
58+
assert crackreq.formatDesired == 0xfffffff2
59+
60+
assert crackreq.valueof("rpNames") == [
61+
b'S-1-5-21-1924137214-3718646274-40215721-522',
62+
b'S-1-5-21-1924137214-3718646274-40215721-498',
63+
b'S-1-5-21-1924137214-3718646274-40215721-516',
64+
b'S-1-5-21-1924137214-3718646274-40215721-526',
65+
b'S-1-5-21-1924137214-3718646274-40215721-527',
66+
b'S-1-5-21-1924137214-3718646274-40215721-512',
67+
b'S-1-5-21-1924137214-3718646274-40215721-519',
68+
b'S-1-5-21-1924137214-3718646274-40215721-513',
69+
]
70+
71+
= [EXCH] - Check IDL_DRSCrackNames_Response
72+
73+
cnresp = pkts[10]
74+
assert IDL_DRSCrackNames_Response in cnresp
75+
76+
crackresp = cnresp[IDL_DRSCrackNames_Response].valueof("pmsgOut")
77+
assert [x.valueof("pName") for x in crackresp.valueof("pResult").valueof("rItems")] == [
78+
b'Cloneable Domain Controllers@DOMAIN',
79+
b'Enterprise Read-only Domain Controllers@DOMAIN',
80+
b'Domain Controllers@DOMAIN',
81+
b'Key Admins@DOMAIN',
82+
b'Enterprise Key Admins@DOMAIN',
83+
b'Domain Admins@DOMAIN',
84+
b'Enterprise Admins@DOMAIN',
85+
b'Domain Users@DOMAIN',
86+
]
87+

0 commit comments

Comments
 (0)