Skip to content

Commit 77d9498

Browse files
authored
dcerpc: fragment vt_trailer and frag_len properly (#5161)
AI-Assisted: no
1 parent 1d29984 commit 77d9498

2 files changed

Lines changed: 77 additions & 46 deletions

File tree

scapy/layers/dcerpc.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2969,6 +2969,15 @@ def _fragment(self, pkt, body):
29692969
if not body:
29702970
# It's the last one
29712971
pkt_frag.pfc_flags += "PFC_LAST_FRAG"
2972+
else:
2973+
# [MS-RPCE] sect 2.2.2.13 - Verification Trailer
2974+
# "only the last PDU of the request MUST have a verification
2975+
# trailer"
2976+
pkt_frag.vt_trailer = None
2977+
2978+
# Update payload for frag_len calculation
2979+
pkt_frag.payload.payload = conf.raw_layer(load=b"\x00" * len(cur))
2980+
29722981
yield pkt_frag, cur
29732982
else:
29742983
yield pkt, body

test/scapy/layers/dcerpc.uts

Lines changed: 68 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -65,22 +65,6 @@ f.addfield(None, b'', f.default) == hex_bytes('0123456789abcdef0123456789abcdef'
6565

6666
+ DCE/RPC v5
6767

68-
= Require a verifier for requests after packet-protection negotiation
69-
70-
protected_session = DceRpcSession(
71-
sspcontext=object(),
72-
auth_level=RPC_C_AUTHN_LEVEL.PKT_PRIVACY,
73-
)
74-
for protected_payload in (DceRpc5Request(), DceRpc5Response()):
75-
unsigned_packet = DceRpc5(
76-
pfc_flags="PFC_FIRST_FRAG+PFC_LAST_FRAG",
77-
) / protected_payload
78-
try:
79-
protected_session.in_pkt(unsigned_packet)
80-
assert False, "The unsigned packet was accepted"
81-
except ValueError as exc:
82-
assert str(exc) == "DCE/RPC packet protection is required !"
83-
8468
= Dissect DCE/RPC v5 Request with Kerberos GSSAPI/RFC1964
8569

8670
pkt = DceRpc(b"\x05\x00\x00\x03\x10\x00\x00\x00\xcd\x00-\x00\x01\x00\x00\x00x\x00\x00\x00\x00\x00\x00\x00j\x87\xb4\xa8DrE3\xfa\xc1\x1d\x9e\xb7\x8a_\xffr\xbe\x13\xc4<\x85\xf0\xf2'y\x84t%u|e\xef/\x04\xb0m\x98\xb1\xd2\x00KwW#P\x8f2\xecB\x81\x19\xf3g\xd2o[\x07L-\xb8\x89\x05\xcf?\xcf\t\xeb\xb3&&6\xb7\x84\xb6\xcd8Ao\x8c\x94\xca\x03\xe3\x0e\x86'-\xfaHj\xcez\xf0A\x83\x9dX\r\xe8\x96\x07Bs\xaf\x9c[=2\x9eS\xb1\x18\x84 \xb4y\n9\xdf\x92\x1c\xd8\xe2e\xd3^,\t\x06\x08\x00pj\x8f\x04`+\x06\t*\x86H\x86\xf7\x12\x01\x02\x02\x02\x01\x11\x00\x10\x00\xff\xffp\xc0\\m\xfe\xa4\xe1!\xf7\xdf\xbf\xa4\xad\xdf\xcb\x16\x1e\xb5+{\x97\xaf\xd5~")
@@ -171,6 +155,41 @@ assert DceRpc5Request in pkt2
171155
assert conf.padding_layer not in pkt2
172156
assert pkt2.vt_trailer.commands[1].InterfaceId == pkt2.object
173157

158+
= Build and dissect DCE/RPC - Fragment test
159+
160+
from unittest import mock
161+
from scapy.layers.msrpce.raw.ept import ept_lookup_Request
162+
163+
# 1. Build a KerberosSSP ready to encrypt
164+
ssp = KerberosSSP()
165+
ctx = KerberosSSP.CONTEXT(IsAcceptor=False, req_flags=GSS_C_FLAGS.GSS_C_CONF_FLAG)
166+
ctx.KrbSessionKey = Key(EncryptionType.AES128_CTS_HMAC_SHA1_96, key=bytes.fromhex("3705D96080C17728A0E800EAB6E0D23C"))
167+
ctx.SendSeqNum = 0x60cbacd3
168+
Confounder = bytes.fromhex("5256f3fb630cf12a")
169+
session = DceRpcSession(
170+
auth_level=RPC_C_AUTHN_LEVEL.PKT_PRIVACY,
171+
ssp=ssp,
172+
)
173+
session.sspcontext = ctx
174+
175+
# 2. Make a big packet go out, this will trigger fragmentation
176+
pkt = DceRpc5(call_id=12, vt_trailer=DceRpcSecVT(commands=[DceRpcSecVTCommand(SEC_VT_COMMAND_END=1)/DceRpcSecVTPcontext()])) / DceRpc5Request() / (b"\x11" * 5000)
177+
with mock.patch('scapy.layers.kerberos.os.urandom', side_effect=lambda x: Confounder):
178+
pkt = session.out_pkt(pkt)
179+
180+
# Checks on the fragmented packet
181+
assert len(pkt) == 2
182+
183+
assert pkt[0].pfc_flags == 1
184+
assert pkt[0].vt_trailer is None
185+
assert bytes(pkt[0].auth_verifier.auth_value) == b'\x05\x04\x06\xff\x00\x10\x00\x1c\x00\x00\x00\x00`\xcb\xac\xd3\xc3\xb9\r\xf4F\xf5\x17\xc2\x9cvA\xd2,\x15\x13\xb4\xdcI\x06\x8b^\x057\xe1\xa0\x18\xbd\t=\xba)\xa3\xe1\xa1y\x81\xc7b\xdc\xbd\xd8\xf2(\xd1q?\xd6#\x8e\xd2^\xa0'
186+
assert len(pkt[0].load) == 4176
187+
188+
assert pkt[1].pfc_flags == 2
189+
assert pkt[1].vt_trailer is not None
190+
assert bytes(pkt[1].auth_verifier.auth_value) == b'\x05\x04\x06\xff\x00\x10\x00\x1c\x00\x00\x00\x00`\xcb\xac\xd4\x9c_--ht\xc4\xc3\xfd\x8e!\x04;\x8d\xe1\xe1\x8c\xf3b\xb9\xb4\xb5%J\xff\xc5\xcb\xc1\x87\xdc]\x85S\x18\x07\x1d~e\x8f\xa6v]G\xa1q?\xd6#\x8e\xd2^\xa0'
191+
assert len(pkt[1].load) == 824
192+
174193
+ Check DCE/RPC 4 layer
175194

176195
= DCE/RPC 4 default values
@@ -617,15 +636,6 @@ assert pkts[22][ept_map_Response].valueof("ITowers")[0].tower_octet_string == b'
617636

618637
% The fact that all of this actually works is crazy to me.
619638

620-
= Server faults a request received before bind
621-
from scapy.layers.dcerpc import DCERPC_Transport, DceRpc5Fault
622-
from scapy.layers.msrpce.rpcserver import DCERPC_Server
623-
624-
server = DCERPC_Server(DCERPC_Transport.NCACN_IP_TCP, port=135, verb=False)
625-
request = hex_bytes("050000031000000018000000000000000000000000000000")
626-
server.recv(request)
627-
assert DceRpc5Fault in server.get_response()
628-
629639
= Functional: Define a MS-RPC server
630640
% Same as in dcerpc.rst
631641

@@ -670,27 +680,6 @@ class MyRPCServer(DCERPC_Server):
670680
ndr64=self.ndr64,
671681
)
672682

673-
= Auth3 cannot lower the protection level negotiated during bind
674-
675-
class TestSSP:
676-
def GSS_Accept_sec_context(self, context, token):
677-
return object(), None, 0
678-
679-
server = DCERPC_Server(
680-
DCERPC_Transport.NCACN_IP_TCP,
681-
verb=False,
682-
ssp=TestSSP(),
683-
)
684-
server.session.rpc_bind_interface = find_dcerpc_interface("wkssvc")
685-
server.session.auth_level = DCE_C_AUTHN_LEVEL.PKT_INTEGRITY
686-
auth3 = DceRpc5() / DceRpc5Auth3()
687-
auth3.auth_verifier = CommonAuthVerifier(
688-
auth_level=DCE_C_AUTHN_LEVEL.CONNECT,
689-
auth_value=Raw(b"authenticate"),
690-
)
691-
server.recv(auth3)
692-
assert server.session.auth_level == DCE_C_AUTHN_LEVEL.PKT_INTEGRITY
693-
694683
= Functional: Define wrapper over samba's rpcclient
695684
~ linux samba
696685

@@ -1025,3 +1014,36 @@ rpcserver.close()
10251014
= Restore conf.debug_dissector
10261015

10271016
conf.debug_dissector = old_debug_dissector
1017+
1018+
+ Security regression tests
1019+
1020+
= Security - Auth3 cannot lower the protection level negotiated during bind
1021+
1022+
class TestSSP:
1023+
def GSS_Accept_sec_context(self, context, token):
1024+
return object(), None, 0
1025+
1026+
server = DCERPC_Server(
1027+
DCERPC_Transport.NCACN_IP_TCP,
1028+
verb=False,
1029+
ssp=TestSSP(),
1030+
)
1031+
server.session.rpc_bind_interface = find_dcerpc_interface("wkssvc")
1032+
server.session.auth_level = DCE_C_AUTHN_LEVEL.PKT_INTEGRITY
1033+
auth3 = DceRpc5() / DceRpc5Auth3()
1034+
auth3.auth_verifier = CommonAuthVerifier(
1035+
auth_level=DCE_C_AUTHN_LEVEL.CONNECT,
1036+
auth_value=Raw(b"authenticate"),
1037+
)
1038+
server.recv(auth3)
1039+
assert server.session.auth_level == DCE_C_AUTHN_LEVEL.PKT_INTEGRITY
1040+
1041+
= Security - Server faults a request received before bind
1042+
1043+
from scapy.layers.dcerpc import DCERPC_Transport, DceRpc5Fault
1044+
from scapy.layers.msrpce.rpcserver import DCERPC_Server
1045+
1046+
server = DCERPC_Server(DCERPC_Transport.NCACN_IP_TCP, port=135, verb=False)
1047+
request = hex_bytes("050000031000000018000000000000000000000000000000")
1048+
server.recv(request)
1049+
assert DceRpc5Fault in server.get_response()

0 commit comments

Comments
 (0)