Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DCE/RPC: defragment should happen after integrity check/decryption #4664

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
322 changes: 185 additions & 137 deletions scapy/layers/dcerpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2614,31 +2614,60 @@ def _up_pkt(self, pkt):
# Since the connection-oriented transport guarantees sequentiality, the receiver
# will always receive the fragments in order.

def _defragment(self, pkt):
def _defragment(self, pkt, body=None):
"""
Function to defragment DCE/RPC packets.
"""
uid = pkt.call_id
if pkt.pfc_flags.PFC_FIRST_FRAG and pkt.pfc_flags.PFC_LAST_FRAG:
# Not fragmented
return pkt
return body
if pkt.pfc_flags.PFC_FIRST_FRAG or uid in self.frags:
# Packet is fragmented
self.frags[uid] += pkt[DceRpc5].payload.payload.original
if body is None:
body = pkt[DceRpc5].payload.payload.original
self.frags[uid] += body
if pkt.pfc_flags.PFC_LAST_FRAG:
pkt[DceRpc5].payload.remove_payload()
pkt[DceRpc5].payload /= self.frags[uid]
return pkt
return self.frags[uid]
else:
# Not fragmented
return pkt
return body

# C706 sect 12.5.2.15 - PDU Body Length
# "The maximum PDU body size is 65528 bytes."
MAX_PDU_BODY_SIZE = 4176

def _fragment(self, pkt):
def _fragment(self, pkt, body):
"""
Function to fragment DCE/RPC packets.
"""
# unimplemented
pass
uid = pkt.call_id
if len(body) > self.MAX_PDU_BODY_SIZE:
# Clear any PFC_*_FRAG flag
pkt.pfc_flags &= 0xFC

# Iterate through fragments
cur = None
while body:
# Create a fragment
pkt_frag = pkt.copy()

if cur is None:
# It's the first one
pkt_frag.pfc_flags += "PFC_FIRST_FRAG"

# Split
cur, body = (
body[: self.MAX_PDU_BODY_SIZE],
body[self.MAX_PDU_BODY_SIZE :],
)

if not body:
# It's the last one
pkt_frag.pfc_flags += "PFC_LAST_FRAG"
yield pkt_frag, cur
else:
yield pkt, body

# [MS-RPCE] sect 3.3.1.5.2.2

Expand All @@ -2656,12 +2685,6 @@ def _fragment(self, pkt):
# Similarly the signature output SHOULD be ignored.

def in_pkt(self, pkt):
# Defragment
pkt = self._defragment(pkt)
if not pkt:
return
# Get opnum and options
opnum, opts = self._up_pkt(pkt)
# Check for encrypted payloads
body = None
if conf.raw_layer in pkt.payload:
Expand Down Expand Up @@ -2783,6 +2806,13 @@ def in_pkt(self, pkt):
if pkt.vt_trailer:
vtlen = len(pkt.vt_trailer)
body, pkt.vt_trailer = body[:-vtlen], body[-vtlen:]
# Defragment
if body:
body = self._defragment(pkt, body)
if not body:
return
# Get opnum and options
opnum, opts = self._up_pkt(pkt)
# Try to parse the payload
if opnum is not None and self.rpc_bind_interface:
# use opnum to parse the payload
Expand Down Expand Up @@ -2811,128 +2841,141 @@ def in_pkt(self, pkt):
def out_pkt(self, pkt):
assert DceRpc5 in pkt
self._up_pkt(pkt)
if pkt.auth_verifier is not None:
# Verifier already set
return [pkt]
if self.sspcontext and isinstance(
pkt.payload, (DceRpc5Request, DceRpc5Response)
):

# If it's a request / response, we can frag it
if isinstance(pkt.payload, (DceRpc5Request, DceRpc5Response)):
# The list of packet responses
pkts = []
# Take the body payload, and eventually split it
body = bytes(pkt.payload.payload)
signature = None
if self.auth_level in (
RPC_C_AUTHN_LEVEL.PKT_INTEGRITY,
RPC_C_AUTHN_LEVEL.PKT_PRIVACY,
):
# Account for padding when computing checksum/encryption
if pkt.auth_padding is None:
padlen = (-len(body)) % _COMMON_AUTH_PAD # authdata padding
pkt.auth_padding = b"\x00" * padlen
else:
padlen = len(pkt.auth_padding)
# Remember that vt_trailer is included in the PDU
if pkt.vt_trailer:
body += bytes(pkt.vt_trailer)
# Remember that padding IS SIGNED & ENCRYPTED
body += pkt.auth_padding
# Add the auth_verifier
pkt.auth_verifier = CommonAuthVerifier(
auth_type=self.ssp.auth_type,
auth_level=self.auth_level,
auth_context_id=self.auth_context_id,
auth_pad_length=padlen,
# Note: auth_value should have the correct length because when
# using PFC_SUPPORT_HEADER_SIGN, auth_len (and frag_len) is
# included in the token.. but this creates a dependency loop as
# you'd need to know the token length to compute the token.
# Windows solves this by setting the 'Maximum Signature Length'
# (or something similar) beforehand, instead of the real length.
# See `gensec_sig_size` in samba.
auth_value=b"\x00"
* self.ssp.MaximumSignatureLength(self.sspcontext),
)
# Build pdu_header and sec_trailer
pdu_header = pkt.copy()
pdu_header.auth_len = len(pdu_header.auth_verifier) - 8
pdu_header.frag_len = len(pdu_header)
sec_trailer = pdu_header.auth_verifier
# sec_trailer: include the sec_trailer but not the Authentication token
authval_len = len(sec_trailer.auth_value)
# sec_trailer.auth_value = None
# Discard everything out of the header
pdu_header.auth_padding = None
pdu_header.auth_verifier = None
pdu_header.payload.payload = NoPayload()
pdu_header.vt_trailer = None
signature = None
# [MS-RPCE] sect 2.2.2.12
if self.auth_level == RPC_C_AUTHN_LEVEL.PKT_PRIVACY:
_msgs, signature = self.ssp.GSS_WrapEx(
self.sspcontext,
[
# "PDU header"
SSP.WRAP_MSG(
conf_req_flag=False,
sign=self.header_sign,
data=bytes(pdu_header),
),
# "PDU body"
SSP.WRAP_MSG(
conf_req_flag=True,
sign=True,
data=body,
),
# "sec_trailer"
SSP.WRAP_MSG(
conf_req_flag=False,
sign=self.header_sign,
data=bytes(sec_trailer)[:-authval_len],
),
],
)
s = _msgs[1].data # PDU body
elif self.auth_level == RPC_C_AUTHN_LEVEL.PKT_INTEGRITY:
signature = self.ssp.GSS_GetMICEx(
self.sspcontext,
[
# "PDU header"
SSP.MIC_MSG(
sign=self.header_sign,
data=bytes(pdu_header),
),
# "PDU body"
SSP.MIC_MSG(
sign=True,
data=body,
),
# "sec_trailer"
SSP.MIC_MSG(
sign=self.header_sign,
data=bytes(sec_trailer)[:-authval_len],
),
],
pkt.auth_verifier.auth_value,
)
s = body
else:
raise ValueError("Impossible")
# Put padding back in the header
if padlen:
s, pkt.auth_padding = s[:-padlen], s[-padlen:]
# Put back vt_trailer into the header
if pkt.vt_trailer:
vtlen = len(pkt.vt_trailer)
s, pkt.vt_trailer = s[:-vtlen], s[-vtlen:]
else:
s = body

# now inject the encrypted payload into the packet
pkt.payload.payload = conf.raw_layer(load=s)
# and the auth_value
if signature:
pkt.auth_verifier.auth_value = signature
else:
pkt.auth_verifier = None
return [pkt]
for pkt, body in self._fragment(pkt, body):
if pkt.auth_verifier is not None:
# Verifier already set
pkts.append(pkt)
continue

# Sign / Encrypt
if self.sspcontext:
signature = None
if self.auth_level in (
RPC_C_AUTHN_LEVEL.PKT_INTEGRITY,
RPC_C_AUTHN_LEVEL.PKT_PRIVACY,
):
# Account for padding when computing checksum/encryption
if pkt.auth_padding is None:
padlen = (-len(body)) % _COMMON_AUTH_PAD # authdata padding
pkt.auth_padding = b"\x00" * padlen
else:
padlen = len(pkt.auth_padding)
# Remember that vt_trailer is included in the PDU
if pkt.vt_trailer:
body += bytes(pkt.vt_trailer)
# Remember that padding IS SIGNED & ENCRYPTED
body += pkt.auth_padding
# Add the auth_verifier
pkt.auth_verifier = CommonAuthVerifier(
auth_type=self.ssp.auth_type,
auth_level=self.auth_level,
auth_context_id=self.auth_context_id,
auth_pad_length=padlen,
# Note: auth_value should have the correct length because when
# using PFC_SUPPORT_HEADER_SIGN, auth_len (and frag_len) is
# included in the token.. but this creates a dependency loop as
# you'd need to know the token length to compute the token.
# Windows solves this by setting the 'Maximum Signature Length'
# (or something similar) beforehand, instead of the real length.
# See `gensec_sig_size` in samba.
auth_value=b"\x00"
* self.ssp.MaximumSignatureLength(self.sspcontext),
)
# Build pdu_header and sec_trailer
pdu_header = pkt.copy()
pdu_header.auth_len = len(pdu_header.auth_verifier) - 8
pdu_header.frag_len = len(pdu_header)
sec_trailer = pdu_header.auth_verifier
# sec_trailer: include the sec_trailer but not the Authentication token
authval_len = len(sec_trailer.auth_value)
# sec_trailer.auth_value = None
# Discard everything out of the header
pdu_header.auth_padding = None
pdu_header.auth_verifier = None
pdu_header.payload.payload = NoPayload()
pdu_header.vt_trailer = None
signature = None
# [MS-RPCE] sect 2.2.2.12
if self.auth_level == RPC_C_AUTHN_LEVEL.PKT_PRIVACY:
_msgs, signature = self.ssp.GSS_WrapEx(
self.sspcontext,
[
# "PDU header"
SSP.WRAP_MSG(
conf_req_flag=False,
sign=self.header_sign,
data=bytes(pdu_header),
),
# "PDU body"
SSP.WRAP_MSG(
conf_req_flag=True,
sign=True,
data=body,
),
# "sec_trailer"
SSP.WRAP_MSG(
conf_req_flag=False,
sign=self.header_sign,
data=bytes(sec_trailer)[:-authval_len],
),
],
)
s = _msgs[1].data # PDU body
elif self.auth_level == RPC_C_AUTHN_LEVEL.PKT_INTEGRITY:
signature = self.ssp.GSS_GetMICEx(
self.sspcontext,
[
# "PDU header"
SSP.MIC_MSG(
sign=self.header_sign,
data=bytes(pdu_header),
),
# "PDU body"
SSP.MIC_MSG(
sign=True,
data=body,
),
# "sec_trailer"
SSP.MIC_MSG(
sign=self.header_sign,
data=bytes(sec_trailer)[:-authval_len],
),
],
pkt.auth_verifier.auth_value,
)
s = body
else:
raise ValueError("Impossible")
# Put padding back in the header
if padlen:
s, pkt.auth_padding = s[:-padlen], s[-padlen:]
# Put back vt_trailer into the header
if pkt.vt_trailer:
vtlen = len(pkt.vt_trailer)
s, pkt.vt_trailer = s[:-vtlen], s[-vtlen:]
else:
s = body

# now inject the encrypted payload into the packet
pkt.payload.payload = conf.raw_layer(load=s)
# and the auth_value
if signature:
pkt.auth_verifier.auth_value = signature
else:
pkt.auth_verifier = None
# Add to the list
pkts.append(pkt)
return pkts
else:
return [pkt]

def process(self, pkt: Packet) -> Optional[Packet]:
pkt = super(DceRpcSession, self).process(pkt)
Expand All @@ -2947,6 +2990,7 @@ class DceRpcSocket(StreamSocket):
"""

def __init__(self, *args, **kwargs):
self.transport = kwargs.pop("transport", None)
self.session = DceRpcSession(
ssp=kwargs.pop("ssp", None),
auth_level=kwargs.pop("auth_level", None),
Expand All @@ -2957,7 +3001,11 @@ def __init__(self, *args, **kwargs):

def send(self, x, **kwargs):
for pkt in self.session.out_pkt(x):
return super(DceRpcSocket, self).send(pkt, **kwargs)
if self.transport == DCERPC_Transport.NCACN_NP:
# In this case DceRpcSocket wraps a SMB_RPC_SOCKET, call it directly.
self.ins.send(pkt, **kwargs)
else:
super(DceRpcSocket, self).send(pkt, **kwargs)

def recv(self, x=None):
pkt = super(DceRpcSocket, self).recv(x)
Expand Down
1 change: 1 addition & 0 deletions scapy/layers/msrpce/rpcclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def __init__(self, transport, ndr64=False, ndrendian="little", verb=True, **kwar
self.ssp = kwargs.pop("ssp", None) # type: SSP
self.sspcontext = None
self.dcesockargs = kwargs
self.dcesockargs["transport"] = self.transport

@classmethod
def from_smblink(cls, smbcli, smb_kwargs={}, **kwargs):
Expand Down
Loading
Loading