Skip to content
Merged
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
2 changes: 1 addition & 1 deletion doc/scapy/layers/dcerpc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ There are extensions to the :class:`~scapy.layers.msrpce.rpcclient.DCERPC_Client
client.negotiate_sessionkey(bytes.fromhex("77777777777777777777777777777777"))
client.close()

- the :class:`~scapy.layers.msrpce.msdcom.DCOM_Client` (unfinished)
- the :class:`~scapy.layers.msrpce.msdcom.DCOM_Client`. More details are available in `DCOM <dcom.html>`_

Server
------
Expand Down
19 changes: 14 additions & 5 deletions scapy/layers/dcerpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@
}
DCE_RPC_INTERFACES_NAMES = {}
DCE_RPC_INTERFACES_NAMES_rev = {}
COM_INTERFACES_NAMES = {}
COM_INTERFACES_NAMES_rev = {}


class DCERPC_Transport(IntEnum):
Expand Down Expand Up @@ -1350,6 +1352,8 @@ def register_com_interface(name, uuid, opnums):
# bind for build
for opnum, operations in opnums.items():
bind_top_down(DceRpc5Request, operations.request, opnum=opnum)
COM_INTERFACES_NAMES[uuid] = name
COM_INTERFACES_NAMES_rev[name.lower()] = uuid


def find_com_interface(name) -> ComInterface:
Expand Down Expand Up @@ -2824,6 +2828,7 @@ def __init__(self, *args, **kwargs):
self.sent_cont_ids = []
self.cont_id = 0 # Currently selected context
self.auth_context_id = 0 # Currently selected authentication context
self.assoc_group_id = 0 # Currently selected association group
self.map_callid_opnum = {}
self.frags = collections.defaultdict(lambda: b"")
self.sniffsspcontexts = {} # Unfinished contexts for passive
Expand Down Expand Up @@ -2869,6 +2874,8 @@ def _up_pkt(self, pkt):
finally:
self.sent_cont_ids = []

self.assoc_group_id = pkt.assoc_group_id

# Endianness
self.ndrendian = {0: "big", 1: "little"}[pkt[DceRpc5].endian]

Expand All @@ -2878,18 +2885,20 @@ def _up_pkt(self, pkt):
elif DceRpc5Request in pkt:
# request => match opnum with callID
opnum = pkt.opnum
uid = (self.assoc_group_id, pkt.call_id)
if self.rpc_bind_is_com:
self.map_callid_opnum[pkt.call_id] = (
self.map_callid_opnum[uid] = (
opnum,
pkt[DceRpc5Request].payload.payload,
)
else:
self.map_callid_opnum[pkt.call_id] = opnum, pkt[DceRpc5Request].payload
self.map_callid_opnum[uid] = opnum, pkt[DceRpc5Request].payload
elif DceRpc5Response in pkt:
# response => get opnum from table
uid = (self.assoc_group_id, pkt.call_id)
try:
opnum, opts["request_packet"] = self.map_callid_opnum[pkt.call_id]
del self.map_callid_opnum[pkt.call_id]
opnum, opts["request_packet"] = self.map_callid_opnum[uid]
del self.map_callid_opnum[uid]
except KeyError:
log_runtime.info("Unknown call_id %s in DCE/RPC session" % pkt.call_id)
# Bind / Alter request/response specific
Expand All @@ -2912,7 +2921,7 @@ def _defragment(self, pkt, body=None):
"""
Function to defragment DCE/RPC packets.
"""
uid = pkt.call_id
uid = (self.assoc_group_id, pkt.call_id)
if pkt.pfc_flags.PFC_FIRST_FRAG and pkt.pfc_flags.PFC_LAST_FRAG:
# Not fragmented
return body
Expand Down
2 changes: 1 addition & 1 deletion scapy/layers/ms_nrtp.py
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ def _member_type_infos_cb(pkt, lst, cur, remain):
except StopIteration:
return None
typeEnum = BinaryTypeEnum(typeEnum)
# Return BinaryTypeEnum tainted with a pre-selected type.
# Return BinaryTypeEnum tainted with a preselected type.
return functools.partial(
NRBFAdditionalInfo,
bintype=typeEnum,
Expand Down
63 changes: 57 additions & 6 deletions scapy/layers/msrpce/msdcom.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@
PadField,
StrLenField,
StrNullFieldUtf16,
UUIDEnumField,
UUIDField,
XShortField,
XStrFixedLenField,
)
from scapy.volatile import RandUUID

from scapy.layers.dcerpc import (
COM_INTERFACES_NAMES_rev,
COM_INTERFACES_NAMES,
ComInterface,
DCE_C_AUTHN_LEVEL,
DCE_RPC_PROTOCOL_IDENTIFIERS,
Expand All @@ -60,6 +63,7 @@
NDRShortField,
NDRSignedIntField,
RPC_C_AUTHN,
RPC_C_IMP_LEVEL,
)
from scapy.utils import valid_ip6, valid_ip
from scapy.layers.msrpce.rpcclient import DCERPC_Client, DCERPC_Transport
Expand Down Expand Up @@ -445,7 +449,15 @@ class OBJREF(Packet):
fields_desc = [
XStrFixedLenField("signature", b"MEOW", length=4), # :3
LEIntField("flags", 0x04),
UUIDField("iid", IID_IActivationPropertiesIn, uuid_fmt=UUIDField.FORMAT_LE),
UUIDEnumField(
"iid",
IID_IActivationPropertiesIn,
(
COM_INTERFACES_NAMES.get,
lambda x: COM_INTERFACES_NAMES_rev.get(x.lower()),
),
uuid_fmt=UUIDField.FORMAT_LE,
),
]


Expand Down Expand Up @@ -735,6 +747,7 @@ class OXID_Entry:
def __init__(self):
self.oxid: Optional[int] = None
self.bindingInfo: Optional[Tuple[str, int]] = None
self.target_name: str = None
self.authnHint: DCE_C_AUTHN_LEVEL = DCE_C_AUTHN_LEVEL.CONNECT
self.version: Optional[COMVERSION] = None
self.ipid_IRemUnknown: Optional[uuid.UUID] = None
Expand Down Expand Up @@ -777,6 +790,7 @@ def sr1_req(
iface: ComInterface,
ssp=None,
auth_level=None,
impersonation_type=None,
timeout=None,
**kwargs,
):
Expand All @@ -788,6 +802,7 @@ def sr1_req(

:param ssp: (optional) non default SSP to use to connect to the object exporter
:param auth_level: (optional) non default authn level to use
:param impersonation_type: (optional) non default impersonation type to use
:param timeout: (optional) timeout for the connection
"""
# Look for this object's entry
Expand Down Expand Up @@ -817,6 +832,7 @@ def sr1_req(
pkt=pkt,
ssp=ssp,
auth_level=auth_level,
impersonation_type=impersonation_type,
timeout=timeout,
**kwargs,
)
Expand Down Expand Up @@ -858,6 +874,12 @@ def __init__(self, cid: GUID = None, verb=True, **kwargs):
if "auth_level" not in kwargs and "ssp" in kwargs:
kwargs["auth_level"] = DCE_C_AUTHN_LEVEL.PKT_INTEGRITY

# DCOM_Client handles the activations.
# [MS-RPCE] sect 3.2.4.1.1.2 : "it MUST specify a default impersonation
# level of at leastRPC_C_IMPL_LEVEL_IMPERSONATE"
if "impersonation_type" not in kwargs and "ssp" in kwargs:
kwargs["impersonation_type"] = RPC_C_IMP_LEVEL.IMPERSONATE

super(DCOM_Client, self).__init__(
DCERPC_Transport.NCACN_IP_TCP,
ndr64=False,
Expand Down Expand Up @@ -908,7 +930,10 @@ def _RemoteCreateInstanceOrGetClassObject(
raise ValueError("Must specify at least one interface !")

# Bind IObjectExporter if not already
self.bind_or_alter(find_dcerpc_interface("IRemoteSCMActivator"))
self.bind_or_alter(
find_dcerpc_interface("IRemoteSCMActivator"),
target_name="rpcss/" + self.host,
)

# [MS-DCOM] sect 3.1.2.5.2.3.3 - Issuing the Activation Request

Expand Down Expand Up @@ -1034,8 +1059,9 @@ def _RemoteCreateInstanceOrGetClassObject(
)

# Set RPC bindings from the activation request
binds, _ = _ParseStringArray(remoteReply.valueof("pdsaOxidBindings"))
binds, secs = _ParseStringArray(remoteReply.valueof("pdsaOxidBindings"))
entry.bindingInfo = self._ChoseRPCBinding(binds)
entry.target_name = self._CalculateTargetName(secs)

if PropsOutInfo in prop:
# Information about the interfaces that the client requested
Expand Down Expand Up @@ -1225,6 +1251,21 @@ def _ChoseRPCBinding(self, bindings: List[STRINGBINDING]):
return host, port
raise ValueError("No valid bindings available !")

def _CalculateTargetName(self, secs: List[SECURITYBINDING]):
"""
3.2.4.2 ORPC Invocations - Find SPN from aPrincName
"""
if self.ssp is None or not secs:
return None

for sec in secs:
# "if the aPrincName field is nonempty"
if sec.wAuthnSvc == self.ssp.auth_type and sec.aPrincName:
return sec.aPrincName

# "if the aPrincName field is empty, the client MUST NOT specify an SPN"
return None

def UnmarshallObjectReference(
self, mifaceptr: MInterfacePointer, iid: ComInterface
):
Expand Down Expand Up @@ -1261,7 +1302,10 @@ def ResolveOxid2(
client.connect(host, port=port)

# Bind IObjectExporter if not already
client.bind_or_alter(find_dcerpc_interface("IObjectExporter"))
client.bind_or_alter(
find_dcerpc_interface("IObjectExporter"),
target_name="rpcss/" + self.host,
)

try:
# Perform ResolveOxid2
Expand Down Expand Up @@ -1293,8 +1337,9 @@ def ResolveOxid2(
)

# Set RPC bindings from the oxid request
binds, _ = _ParseStringArray(resp.valueof("ppdsaOxidBindings"))
binds, secs = _ParseStringArray(resp.valueof("ppdsaOxidBindings"))
entry.bindingInfo = self._ChoseRPCBinding(binds)
entry.target_name = self._CalculateTargetName(secs)

# Update the OXID table
if entry.oxid not in self.OXID_table:
Expand Down Expand Up @@ -1342,6 +1387,7 @@ def sr1_orpc_req(
ipid: uuid.UUID,
ssp=None,
auth_level=None,
impersonation_type=None,
timeout=5,
**kwargs,
):
Expand All @@ -1353,6 +1399,7 @@ def sr1_orpc_req(

:param ssp: (optional) non default SSP to use to connect to the object exporter
:param auth_level: (optional) non default authn level to use
:param impersonation_type: (optional) non default impersonation type to use
:param timeout: (optional) timeout for the connection
"""
# [MS-DCOM] sect 3.2.4.2
Expand Down Expand Up @@ -1385,6 +1432,7 @@ def sr1_orpc_req(
DCERPC_Transport.NCACN_IP_TCP,
ssp=ssp or self.ssp,
auth_level=auth_level or oxid_entry.authnHint,
impersonation_type=impersonation_type or self.impersonation_type,
verb=self.verb,
)

Expand All @@ -1395,7 +1443,10 @@ def sr1_orpc_req(
)

# Bind the COM interface
resolver_entry.client.bind_or_alter(ipid_entry.iface)
resolver_entry.client.bind_or_alter(
ipid_entry.iface,
target_name=oxid_entry.target_name,
)

# We need to set the NDR very late, after the bind
pkt.ndr64 = resolver_entry.client.ndr64
Expand Down
7 changes: 3 additions & 4 deletions scapy/layers/msrpce/msnrpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
PNETLOGON_CREDENTIAL,
)


if conf.crypto_valid:
from cryptography.hazmat.primitives import hashes, hmac
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
Expand All @@ -71,7 +70,6 @@
Optional,
)


# --- RFC

# [MS-NRPC] sect 3.1.4.2
Expand Down Expand Up @@ -853,11 +851,12 @@ def establish_secure_channel(
else:
self.ssp = self.sock.session.ssp = KerberosSSP(
UPN=UPN,
SPN="netlogon/" + DC_FQDN,
PASSWORD=PASSWORD,
KEY=KEY,
)
if not self.bind_or_alter(self.interface):
# [MS-NRPC] note <185> "Windows uses netlogon/<hostname>"
target_name = "netlogon/" + DC_FQDN
if not self.bind_or_alter(self.interface, target_name=target_name):
raise ValueError("Bind failed !")

# Send AuthenticateKerberos request
Expand Down
Loading
Loading