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"""
7DCE/RPC client as per [MS-RPCE]
8"""
9
10import collections
11import uuid
12import socket
13
14from scapy.config import conf
15from scapy.error import log_runtime
16
17from scapy.layers.dcerpc import (
18 _DCE_RPC_ERROR_CODES,
19 ComInterface,
20 CommonAuthVerifier,
21 DCE_C_AUTHN_LEVEL,
22 DCERPC_Transport,
23 DceRpc5,
24 DceRpc5AbstractSyntax,
25 DceRpc5AlterContext,
26 DceRpc5AlterContextResp,
27 DceRpc5Auth3,
28 DceRpc5Bind,
29 DceRpc5BindAck,
30 DceRpc5BindNak,
31 DceRpc5Context,
32 DceRpc5Fault,
33 DceRpc5Request,
34 DceRpc5Response,
35 DceRpc5TransferSyntax,
36 DceRpcInterface,
37 DceRpcSecVT,
38 DceRpcSecVTCommand,
39 DceRpcSecVTPcontext,
40 DceRpcSession,
41 DceRpcSocket,
42 find_dcerpc_interface,
43 NDRContextHandle,
44 NDRPointer,
45 RPC_C_IMP_LEVEL,
46)
47from scapy.layers.gssapi import (
48 SSP,
49 GSS_S_FAILURE,
50 GSS_S_COMPLETE,
51 GSS_S_CONTINUE_NEEDED,
52 GSS_C_FLAGS,
53)
54from scapy.layers.smbclient import (
55 SMB_RPC_SOCKET,
56)
57from scapy.layers.windows.erref import STATUS_ERREF
58
59# RPC
60from scapy.layers.msrpce.ept import (
61 ept_map_Request,
62 ept_map_Response,
63 twr_p_t,
64 protocol_tower_t,
65 prot_and_addr_t,
66 UUID,
67)
68
69# Typing
70from typing import (
71 Optional,
72 Union,
73)
74
75
76class DCERPC_Client(object):
77 """
78 A basic DCE/RPC client
79
80 :param transport: the transport to use.
81 :param ndr64: should ask for NDR64 when binding (default conf.ndr64)
82 :param ndrendian: the endianness to use (default little)
83 :param verb: enable verbose logging (default True)
84 :param auth_level: the DCE_C_AUTHN_LEVEL to use
85 :param impersonation_type: the RPC_C_IMP_LEVEL to use
86 """
87
88 def __init__(
89 self,
90 transport: DCERPC_Transport,
91 ndr64: Optional[bool] = None,
92 ndrendian: str = "little",
93 verb: bool = True,
94 auth_level: Optional[DCE_C_AUTHN_LEVEL] = None,
95 impersonation_type: RPC_C_IMP_LEVEL = RPC_C_IMP_LEVEL.DEFAULT,
96 **kwargs,
97 ):
98 self.sock = None
99 self.transport = transport
100 assert isinstance(
101 transport, DCERPC_Transport
102 ), "transport must be from DCERPC_Transport"
103
104 # Counters
105 self.call_ids = collections.defaultdict(lambda: 0) # by assoc_group_id
106 self.next_cont_id = 0 # next available context id
107 self.next_auth_contex_id = 0 # next available auth context id
108
109 # Session parameters
110 if ndr64 is None:
111 ndr64 = conf.ndr64
112 self.ndr64: bool = ndr64
113 self.ndrendian = ndrendian
114 self.verb = verb
115 self.host: str = None
116 self.port: int = -1
117 self.ssp = kwargs.pop("ssp", None) # type: SSP
118 self.sspcontext = None
119 if auth_level is not None:
120 self.auth_level = auth_level
121 elif self.ssp is not None:
122 self.auth_level = DCE_C_AUTHN_LEVEL.CONNECT
123 else:
124 self.auth_level = DCE_C_AUTHN_LEVEL.NONE
125 if impersonation_type == RPC_C_IMP_LEVEL.DEFAULT:
126 # Same default as windows
127 impersonation_type = RPC_C_IMP_LEVEL.IDENTIFY
128 self.impersonation_type = impersonation_type
129 self._first_time_on_interface = True
130 self.contexts = {}
131 self.dcesockargs = kwargs
132 self.dcesockargs["transport"] = self.transport
133
134 @classmethod
135 def from_smblink(cls, smbcli, smb_kwargs={}, **kwargs):
136 """
137 Build a DCERPC_Client from a SMB_Client.smblink directly
138 """
139 client = DCERPC_Client(DCERPC_Transport.NCACN_NP, **kwargs)
140 sock = client.smbrpcsock = SMB_RPC_SOCKET(smbcli, **smb_kwargs)
141 client.sock = DceRpcSocket(
142 sock,
143 DceRpc5,
144 ssp=client.ssp,
145 auth_level=client.auth_level,
146 **client.dcesockargs,
147 )
148 return client
149
150 @property
151 def session(self) -> DceRpcSession:
152 try:
153 return self.sock.session
154 except AttributeError:
155 raise ValueError("Client is not connected ! Please connect()")
156
157 def connect(
158 self,
159 host,
160 endpoint: Union[int, str] = None,
161 port: Optional[int] = None,
162 interface=None,
163 timeout=5,
164 smb_kwargs={},
165 ):
166 """
167 Initiate a connection.
168
169 :param host: the host to connect to
170 :param endpoint: (optional) the port/smb pipe to connect to
171 :param interface: (optional) if endpoint isn't provided, uses the endpoint
172 mapper to find the appropriate endpoint for that interface.
173 :param timeout: (optional) the connection timeout (default 5)
174 :param port: (optional) the port to connect to. (useful for SMB)
175 """
176 self.sspcontext = None
177 smb_kwargs.setdefault("HOST", host)
178 if endpoint is None and interface is not None:
179 # Figure out the endpoint using the endpoint mapper
180
181 if self.transport == DCERPC_Transport.NCACN_IP_TCP and port is None:
182 # IP/TCP
183 # ask the endpoint mapper (port 135) for the IP:PORT
184 endpoints = get_endpoint(
185 host,
186 interface,
187 ndrendian=self.ndrendian,
188 verb=self.verb,
189 )
190 if endpoints:
191 _, endpoint = endpoints[0]
192 else:
193 raise ValueError(
194 "Could not find an available endpoint for that interface !"
195 )
196 elif self.transport == DCERPC_Transport.NCACN_NP:
197 # SMB
198 # ask the endpoint mapper (over SMB) for the namedpipe
199 endpoints = get_endpoint(
200 host,
201 interface,
202 transport=self.transport,
203 ndrendian=self.ndrendian,
204 verb=self.verb,
205 ssp=self.ssp,
206 smb_kwargs=smb_kwargs,
207 )
208 if endpoints:
209 endpoint = endpoints[0].lstrip("\\pipe\\")
210 else:
211 return
212
213 # Assign the default port if no port is provided
214 if port is None:
215 if self.transport == DCERPC_Transport.NCACN_IP_TCP: # IP/TCP
216 port = endpoint or 135
217 elif self.transport == DCERPC_Transport.NCACN_NP: # SMB
218 port = 445
219 else:
220 raise ValueError(
221 "Can't guess the port for transport: %s" % self.transport
222 )
223
224 # Start socket and connect
225 self.host = host
226 self.port = port
227 sock = socket.socket()
228 sock.settimeout(timeout)
229 if self.verb:
230 print(
231 "\u2503 Connecting to %s on port %s via %s..."
232 % (host, port, repr(self.transport))
233 )
234 sock.connect((host, port))
235 if self.verb:
236 print(
237 conf.color_theme.green(
238 "\u2514 Connected from %s" % repr(sock.getsockname())
239 )
240 )
241
242 if self.transport == DCERPC_Transport.NCACN_NP: # SMB
243 if self.auth_level >= DCE_C_AUTHN_LEVEL.PKT_PRIVACY:
244 smb_kwargs.setdefault("REQUIRE_ENCRYPTION", True)
245 elif self.auth_level >= DCE_C_AUTHN_LEVEL.PKT_INTEGRITY:
246 smb_kwargs.setdefault("REQUIRE_SIGNATURE", True)
247
248 # We pack the socket into a SMB_RPC_SOCKET
249 sock = self.smbrpcsock = SMB_RPC_SOCKET.from_tcpsock(
250 sock,
251 ssp=self.ssp,
252 **smb_kwargs,
253 )
254
255 # If the endpoint is provided, connect to it.
256 if endpoint is not None:
257 self.open_smbpipe(endpoint)
258
259 self.sock = DceRpcSocket(
260 sock,
261 DceRpc5,
262 **self.dcesockargs,
263 )
264 elif self.transport == DCERPC_Transport.NCACN_IP_TCP:
265 self.sock = DceRpcSocket(
266 sock,
267 DceRpc5,
268 ssp=self.ssp,
269 auth_level=self.auth_level,
270 **self.dcesockargs,
271 )
272
273 def close(self):
274 """
275 Close the DCE/RPC client.
276 """
277 if self.verb:
278 print("X Connection closed\n")
279 self.sock.close()
280
281 def sr1(self, pkt, **kwargs):
282 """
283 Send/Receive a DCE/RPC message.
284
285 The DCE/RPC header is added automatically.
286 """
287 self.call_ids[self.session.assoc_group_id] += 1
288 pkt = (
289 DceRpc5(
290 call_id=self.call_ids[self.session.assoc_group_id],
291 pfc_flags="PFC_FIRST_FRAG+PFC_LAST_FRAG",
292 endian=self.ndrendian,
293 auth_verifier=kwargs.pop("auth_verifier", None),
294 vt_trailer=kwargs.pop("vt_trailer", None),
295 )
296 / pkt
297 )
298 if "pfc_flags" in kwargs:
299 pkt.pfc_flags = kwargs.pop("pfc_flags")
300 if "objectuuid" in kwargs:
301 pkt.pfc_flags += "PFC_OBJECT_UUID"
302 pkt.object = kwargs.pop("objectuuid")
303 return self.sock.sr1(pkt, verbose=0, **kwargs)
304
305 def send(self, pkt, **kwargs):
306 """
307 Send a DCE/RPC message.
308
309 The DCE/RPC header is added automatically.
310 """
311 self.call_ids[self.session.assoc_group_id] += 1
312 pkt = (
313 DceRpc5(
314 call_id=self.call_ids[self.session.assoc_group_id],
315 pfc_flags="PFC_FIRST_FRAG+PFC_LAST_FRAG",
316 endian=self.ndrendian,
317 auth_verifier=kwargs.pop("auth_verifier", None),
318 vt_trailer=kwargs.pop("vt_trailer", None),
319 )
320 / pkt
321 )
322 if "pfc_flags" in kwargs:
323 pkt.pfc_flags = kwargs.pop("pfc_flags")
324 if "objectuuid" in kwargs:
325 pkt.pfc_flags += "PFC_OBJECT_UUID"
326 pkt.object = kwargs.pop("objectuuid")
327 return self.sock.send(pkt, **kwargs)
328
329 def sr1_req(self, pkt, **kwargs):
330 """
331 Send/Receive a DCE/RPC request.
332
333 :param pkt: the inner DCE/RPC message, without any header.
334 """
335 if self.verb:
336 if "objectuuid" in kwargs:
337 # COM
338 print(
339 conf.color_theme.opening(
340 ">> REQUEST (COM): %s" % pkt.payload.__class__.__name__
341 )
342 )
343 else:
344 print(
345 conf.color_theme.opening(">> REQUEST: %s" % pkt.__class__.__name__)
346 )
347
348 # Add sectrailer if first time talking on this interface
349 vt_trailer = b""
350 if (
351 self._first_time_on_interface
352 and self.transport != DCERPC_Transport.NCACN_NP
353 ):
354 # In the first request after a bind, Windows sends a trailer to verify
355 # that the negotiated transfer/interface wasn't altered.
356 self._first_time_on_interface = False
357 vt_trailer = DceRpcSecVT(
358 commands=[
359 DceRpcSecVTCommand(SEC_VT_COMMAND_END=1)
360 / DceRpcSecVTPcontext(
361 InterfaceId=self.session.rpc_bind_interface.uuid,
362 Version=self.session.rpc_bind_interface.if_version,
363 TransferSyntax="NDR64" if self.ndr64 else "NDR 2.0",
364 TransferVersion=1 if self.ndr64 else 2,
365 )
366 ]
367 )
368
369 # Optional: force opnum
370 opnum = {}
371 if "opnum" in kwargs:
372 opnum["opnum"] = kwargs.pop("opnum")
373
374 # Set NDR64
375 pkt.ndr64 = self.ndr64
376
377 # Send/receive
378 resp = self.sr1(
379 DceRpc5Request(
380 cont_id=self.session.cont_id,
381 alloc_hint=len(pkt) + len(vt_trailer),
382 **opnum,
383 )
384 / pkt,
385 vt_trailer=vt_trailer,
386 **kwargs,
387 )
388
389 # Parse result
390 result = None
391 if DceRpc5Response in resp:
392 if self.verb:
393 if "objectuuid" in kwargs:
394 # COM
395 print(
396 conf.color_theme.success(
397 "<< RESPONSE (COM): %s"
398 % (resp[DceRpc5Response].payload.payload.__class__.__name__)
399 )
400 )
401 else:
402 print(
403 conf.color_theme.success(
404 "<< RESPONSE: %s"
405 % (resp[DceRpc5Response].payload.__class__.__name__)
406 )
407 )
408 result = resp[DceRpc5Response].payload
409 elif DceRpc5Fault in resp:
410 if self.verb:
411 print(conf.color_theme.success("<< FAULT"))
412 # If [MS-EERR] is loaded, show the extended info
413 if resp[DceRpc5Fault].payload and not isinstance(
414 resp[DceRpc5Fault].payload, conf.raw_layer
415 ):
416 resp[DceRpc5Fault].payload.show()
417 result = resp
418
419 if self.verb and getattr(resp, "status", 0) != 0:
420 if resp.status in _DCE_RPC_ERROR_CODES:
421 print(conf.color_theme.fail(f"! {_DCE_RPC_ERROR_CODES[resp.status]}"))
422 elif resp.status in STATUS_ERREF:
423 print(conf.color_theme.fail(f"! {STATUS_ERREF[resp.status]}"))
424 else:
425 print(conf.color_theme.fail("! Failure"))
426 resp.show()
427 return result
428
429 def _get_bind_context(self, interface):
430 """
431 Internal: get the bind DCE/RPC context.
432 """
433 if interface in self.contexts:
434 # We have already found acceptable contexts for this interface,
435 # reuse that.
436 return self.contexts[interface]
437
438 # NDR 2.0
439 contexts = [
440 DceRpc5Context(
441 cont_id=self.next_cont_id,
442 abstract_syntax=DceRpc5AbstractSyntax(
443 if_uuid=interface.uuid,
444 if_version=interface.if_version,
445 ),
446 transfer_syntaxes=[
447 DceRpc5TransferSyntax(
448 # NDR 2.0 32-bit
449 if_uuid="NDR 2.0",
450 if_version=2,
451 )
452 ],
453 ),
454 ]
455 self.next_cont_id += 1
456
457 # NDR64
458 if self.ndr64:
459 contexts.append(
460 DceRpc5Context(
461 cont_id=self.next_cont_id,
462 abstract_syntax=DceRpc5AbstractSyntax(
463 if_uuid=interface.uuid,
464 if_version=interface.if_version,
465 ),
466 transfer_syntaxes=[
467 DceRpc5TransferSyntax(
468 # NDR64
469 if_uuid="NDR64",
470 if_version=1,
471 )
472 ],
473 )
474 )
475 self.next_cont_id += 1
476
477 # BindTimeFeatureNegotiationBitmask
478 contexts.append(
479 DceRpc5Context(
480 cont_id=self.next_cont_id,
481 abstract_syntax=DceRpc5AbstractSyntax(
482 if_uuid=interface.uuid,
483 if_version=interface.if_version,
484 ),
485 transfer_syntaxes=[
486 DceRpc5TransferSyntax(
487 if_uuid=uuid.UUID("6cb71c2c-9812-4540-0300-000000000000"),
488 if_version=1,
489 )
490 ],
491 )
492 )
493 self.next_cont_id += 1
494
495 # Store contexts for this interface
496 self.contexts[interface] = contexts
497
498 return contexts
499
500 def _check_bind_context(self, interface, contexts) -> bool:
501 """
502 Internal: check the answer DCE/RPC bind context, and update them.
503 """
504 for i, ctx in enumerate(contexts):
505 if ctx.result == 0:
506 # Context was accepted. Remove all others from cache
507 if len(self.contexts[interface]) != 1:
508 self.contexts[interface] = [self.contexts[interface][i]]
509 return True
510
511 return False
512
513 def _bind(
514 self,
515 interface: Union[DceRpcInterface, ComInterface],
516 reqcls,
517 respcls,
518 target_name: Optional[str] = None,
519 ) -> bool:
520 """
521 Internal: used to send a bind/alter request
522 """
523 # Build a security context: [MS-RPCE] 3.3.1.5.2
524 if self.verb:
525 print(
526 conf.color_theme.opening(
527 ">> %s on %s" % (reqcls.__name__, interface)
528 + (" (with %s)" % self.ssp.__class__.__name__ if self.ssp else "")
529 )
530 )
531
532 # Do we need an authenticated bind
533 if not self.ssp or (
534 self.sspcontext is not None
535 or self.transport == DCERPC_Transport.NCACN_NP
536 and self.auth_level < DCE_C_AUTHN_LEVEL.PKT_INTEGRITY
537 ):
538 # NCACN_NP = SMB without INTEGRITY/PRIVACY does not bind the RPC securely,
539 # again as it has already authenticated during the SMB Session Setup
540 resp = self.sr1(
541 reqcls(context_elem=self._get_bind_context(interface)),
542 auth_verifier=None,
543 )
544 status = GSS_S_COMPLETE
545 else:
546 # Perform authentication
547 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context(
548 self.sspcontext,
549 req_flags=(
550 # SSPs need to be instantiated with some special flags
551 # for DCE/RPC usages.
552 GSS_C_FLAGS.GSS_C_DCE_STYLE
553 | GSS_C_FLAGS.GSS_C_REPLAY_FLAG
554 | GSS_C_FLAGS.GSS_C_SEQUENCE_FLAG
555 | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG
556 | (
557 GSS_C_FLAGS.GSS_C_INTEG_FLAG
558 if self.auth_level >= DCE_C_AUTHN_LEVEL.PKT_INTEGRITY
559 else 0
560 )
561 | (
562 GSS_C_FLAGS.GSS_C_CONF_FLAG
563 if self.auth_level >= DCE_C_AUTHN_LEVEL.PKT_PRIVACY
564 else 0
565 )
566 | (
567 GSS_C_FLAGS.GSS_C_IDENTIFY_FLAG
568 if self.impersonation_type <= RPC_C_IMP_LEVEL.IDENTIFY
569 else 0
570 )
571 | (
572 GSS_C_FLAGS.GSS_C_DELEG_FLAG
573 if self.impersonation_type == RPC_C_IMP_LEVEL.DELEGATE
574 else 0
575 )
576 ),
577 target_name=target_name or ("host/" + self.host),
578 )
579
580 if status not in [GSS_S_CONTINUE_NEEDED, GSS_S_COMPLETE]:
581 # Authentication failed.
582 self.sspcontext.clifailure()
583 return False
584
585 resp = self.sr1(
586 reqcls(context_elem=self._get_bind_context(interface)),
587 auth_verifier=(
588 None
589 if not self.sspcontext
590 else CommonAuthVerifier(
591 auth_type=self.ssp.auth_type,
592 auth_level=self.auth_level,
593 auth_context_id=self.session.auth_context_id,
594 auth_value=token,
595 )
596 ),
597 pfc_flags=(
598 "PFC_FIRST_FRAG+PFC_LAST_FRAG"
599 + (
600 # If the SSP supports "Header Signing", advertise it
601 "+PFC_SUPPORT_HEADER_SIGN"
602 if self.ssp is not None and self.session.support_header_signing
603 else ""
604 )
605 ),
606 )
607
608 # Check that the answer looks valid and contexts were accepted
609 if respcls not in resp or not self._check_bind_context(
610 interface, resp.results
611 ):
612 token = None
613 status = GSS_S_FAILURE
614 else:
615 # Call the underlying SSP
616 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context(
617 self.sspcontext,
618 input_token=resp.auth_verifier.auth_value,
619 target_name=target_name or ("host/" + self.host),
620 )
621
622 if status in [GSS_S_CONTINUE_NEEDED, GSS_S_COMPLETE]:
623 # Authentication should continue, in two ways:
624 # - through DceRpc5Auth3 (e.g. NTLM)
625 # - through DceRpc5AlterContext (e.g. Kerberos)
626 if token and self.ssp.LegsAmount(self.sspcontext) % 2 == 1:
627 # AUTH 3 for certain SSPs (e.g. NTLM)
628 # "The server MUST NOT respond to an rpc_auth_3 PDU"
629 self.send(
630 DceRpc5Auth3(),
631 auth_verifier=CommonAuthVerifier(
632 auth_type=self.ssp.auth_type,
633 auth_level=self.auth_level,
634 auth_context_id=self.session.auth_context_id,
635 auth_value=token,
636 ),
637 )
638 status = GSS_S_COMPLETE
639 else:
640 while token:
641 respcls = DceRpc5AlterContextResp
642 resp = self.sr1(
643 DceRpc5AlterContext(
644 context_elem=self._get_bind_context(interface)
645 ),
646 auth_verifier=CommonAuthVerifier(
647 auth_type=self.ssp.auth_type,
648 auth_level=self.auth_level,
649 auth_context_id=self.session.auth_context_id,
650 auth_value=token,
651 ),
652 )
653 if respcls not in resp:
654 status = GSS_S_FAILURE
655 break
656 if resp.auth_verifier is None:
657 status = GSS_S_COMPLETE
658 break
659 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context(
660 self.sspcontext,
661 input_token=resp.auth_verifier.auth_value,
662 target_name=target_name or ("host/" + self.host),
663 )
664 else:
665 log_runtime.error("GSS_Init_sec_context failed with %s !" % status)
666
667 # Check context acceptance
668 if (
669 status == GSS_S_COMPLETE
670 and respcls in resp
671 and self._check_bind_context(interface, resp.results)
672 ):
673 port = resp.sec_addr.port_spec.decode()
674 ndr = self.session.ndr64 and "NDR64" or "NDR32"
675 self.ndr64 = self.session.ndr64
676 if self.verb:
677 print(
678 conf.color_theme.success(
679 f"<< {respcls.__name__} port '{port}' using {ndr}"
680 )
681 )
682 self.session.sspcontext = self.sspcontext
683 self._first_time_on_interface = True
684 return True
685 else:
686 if self.verb:
687 if DceRpc5BindNak in resp:
688 err_msg = resp.sprintf(
689 "reject_reason: %DceRpc5BindNak.provider_reject_reason%"
690 )
691 print(conf.color_theme.fail("! Bind_nak (%s)" % err_msg))
692 if DceRpc5BindNak in resp:
693 if resp[DceRpc5BindNak].payload and not isinstance(
694 resp[DceRpc5BindNak].payload, conf.raw_layer
695 ):
696 resp[DceRpc5BindNak].payload.show()
697 elif DceRpc5Fault in resp:
698 if getattr(resp, "status", 0) != 0:
699 if resp.status in _DCE_RPC_ERROR_CODES:
700 print(
701 conf.color_theme.fail(
702 f"! {_DCE_RPC_ERROR_CODES[resp.status]}"
703 )
704 )
705 elif resp.status in STATUS_ERREF:
706 print(
707 conf.color_theme.fail(f"! {STATUS_ERREF[resp.status]}")
708 )
709 else:
710 print(conf.color_theme.fail("! Failure"))
711 resp.show()
712 if resp[DceRpc5Fault].payload and not isinstance(
713 resp[DceRpc5Fault].payload, conf.raw_layer
714 ):
715 resp[DceRpc5Fault].payload.show()
716 else:
717 print(conf.color_theme.fail("! Failure"))
718 resp.show()
719 return False
720
721 def bind(
722 self,
723 interface: Union[DceRpcInterface, ComInterface],
724 target_name: Optional[str] = None,
725 ) -> bool:
726 """
727 Bind the client to an interface
728
729 :param interface: the DceRpcInterface object
730 """
731 return self._bind(
732 interface,
733 DceRpc5Bind,
734 DceRpc5BindAck,
735 target_name=target_name,
736 )
737
738 def alter_context(
739 self,
740 interface: Union[DceRpcInterface, ComInterface],
741 target_name: Optional[str] = None,
742 ) -> bool:
743 """
744 Alter context: post-bind context negotiation
745
746 :param interface: the DceRpcInterface object
747 """
748 return self._bind(
749 interface,
750 DceRpc5AlterContext,
751 DceRpc5AlterContextResp,
752 target_name=target_name,
753 )
754
755 def bind_or_alter(
756 self,
757 interface: Union[DceRpcInterface, ComInterface],
758 target_name: Optional[str] = None,
759 ) -> bool:
760 """
761 Bind the client to an interface or alter the context if already bound
762
763 :param interface: the DceRpcInterface object
764 """
765 if not self.session.rpc_bind_interface:
766 # No interface is bound
767 return self.bind(interface, target_name=target_name)
768 elif self.session.rpc_bind_interface != interface:
769 # An interface is already bound
770 return self.alter_context(interface, target_name=target_name)
771 return True
772
773 def open_smbpipe(self, name: str):
774 """
775 Open a certain filehandle with the SMB automaton.
776
777 :param name: the name of the pipe
778 """
779 self.ipc_tid = self.smbrpcsock.tree_connect("IPC$")
780 self.smbrpcsock.open_pipe(name)
781
782 def close_smbpipe(self):
783 """
784 Close the previously opened pipe
785 """
786 self.smbrpcsock.set_TID(self.ipc_tid)
787 self.smbrpcsock.close_pipe()
788 self.smbrpcsock.tree_disconnect()
789
790 def connect_and_bind(
791 self,
792 host: str,
793 interface: DceRpcInterface,
794 port: Optional[int] = None,
795 timeout: int = 5,
796 smb_kwargs={},
797 ):
798 """
799 Asks the Endpoint Mapper what address to use to connect to the interface,
800 then uses connect() followed by a bind()
801
802 :param host: the host to connect to
803 :param interface: the DceRpcInterface object
804 :param port: (optional, NCACN_NP only) the port to connect to
805 :param timeout: (optional) the connection timeout (default 5)
806 """
807 # Connect to the interface using the endpoint mapper
808 self.connect(
809 host=host,
810 interface=interface,
811 port=port,
812 timeout=timeout,
813 smb_kwargs=smb_kwargs,
814 )
815
816 # Bind in RPC
817 if not self.bind(interface):
818 raise ValueError("DCE/RPC bind failed")
819
820 def epm_map(self, interface):
821 """
822 Calls ept_map (the EndPoint Manager)
823 """
824 if self.ndr64:
825 ndr_uuid = "NDR64"
826 ndr_version = 1
827 else:
828 ndr_uuid = "NDR 2.0"
829 ndr_version = 2
830 pkt = self.sr1_req(
831 ept_map_Request(
832 obj=NDRPointer(
833 referent_id=1,
834 value=UUID(
835 Data1=0,
836 Data2=0,
837 Data3=0,
838 Data4=None,
839 ),
840 ),
841 map_tower=NDRPointer(
842 referent_id=2,
843 value=twr_p_t(
844 tower_octet_string=bytes(
845 protocol_tower_t(
846 floors=[
847 prot_and_addr_t(
848 lhs_length=19,
849 protocol_identifier=0xD,
850 uuid=interface.uuid,
851 version=interface.major_version,
852 rhs_length=2,
853 rhs=interface.minor_version,
854 ),
855 prot_and_addr_t(
856 lhs_length=19,
857 protocol_identifier=0xD,
858 uuid=ndr_uuid,
859 version=ndr_version,
860 rhs_length=2,
861 rhs=0,
862 ),
863 prot_and_addr_t(
864 lhs_length=1,
865 protocol_identifier="RPC connection-oriented protocol", # noqa: E501
866 rhs_length=2,
867 rhs=0,
868 ),
869 {
870 DCERPC_Transport.NCACN_IP_TCP: (
871 prot_and_addr_t(
872 lhs_length=1,
873 protocol_identifier="NCACN_IP_TCP",
874 rhs_length=2,
875 rhs=135,
876 )
877 ),
878 DCERPC_Transport.NCACN_NP: (
879 prot_and_addr_t(
880 lhs_length=1,
881 protocol_identifier="NCACN_NP",
882 rhs_length=2,
883 rhs=b"0\x00",
884 )
885 ),
886 }[self.transport],
887 {
888 DCERPC_Transport.NCACN_IP_TCP: (
889 prot_and_addr_t(
890 lhs_length=1,
891 protocol_identifier="IP",
892 rhs_length=4,
893 rhs="0.0.0.0",
894 )
895 ),
896 DCERPC_Transport.NCACN_NP: (
897 prot_and_addr_t(
898 lhs_length=1,
899 protocol_identifier="NCACN_NB",
900 rhs_length=10,
901 rhs=b"127.0.0.1\x00",
902 )
903 ),
904 }[self.transport],
905 ],
906 )
907 ),
908 ),
909 ),
910 entry_handle=NDRContextHandle(
911 attributes=0,
912 uuid=b"\x00" * 16,
913 ),
914 max_towers=500,
915 ndr64=self.ndr64,
916 ndrendian=self.ndrendian,
917 )
918 )
919 if pkt and ept_map_Response in pkt:
920 status = pkt[ept_map_Response].status
921 # [MS-RPCE] sect 2.2.1.2.5
922 if status == 0x00000000:
923 towers = [
924 protocol_tower_t(x.value.tower_octet_string)
925 for x in pkt[ept_map_Response].ITowers.value[0].value
926 ]
927 # Let's do some checks to know we know what we're doing
928 endpoints = []
929 for t in towers:
930 if t.floors[0].uuid != interface.uuid:
931 if self.verb:
932 print(
933 conf.color_theme.fail(
934 "! Server answered with a different interface."
935 )
936 )
937 raise ValueError
938 if t.floors[1].sprintf("%uuid%") != ndr_uuid:
939 if self.verb:
940 print(
941 conf.color_theme.fail(
942 "! Server answered with a different NDR version."
943 )
944 )
945 raise ValueError
946 if self.transport == DCERPC_Transport.NCACN_IP_TCP:
947 endpoints.append((t.floors[4].rhs, t.floors[3].rhs))
948 elif self.transport == DCERPC_Transport.NCACN_NP:
949 endpoints.append(t.floors[3].rhs.rstrip(b"\x00").decode())
950 return endpoints
951 elif status == 0x16C9A0D6:
952 if self.verb:
953 print(
954 conf.color_theme.fail(
955 "! Server errored: 'There are no elements that satisfy"
956 " the specified search criteria'."
957 )
958 )
959 raise ValueError
960 print(conf.color_theme.fail("! Failure."))
961 if pkt:
962 pkt.show()
963 raise ValueError("EPM Map failed")
964
965
966def get_endpoint(
967 ip,
968 interface,
969 transport=DCERPC_Transport.NCACN_IP_TCP,
970 ndrendian="little",
971 verb=True,
972 ssp=None,
973 smb_kwargs={},
974):
975 """
976 Call the endpoint mapper on a remote IP to find an interface
977
978 :param ip:
979 :param interface:
980 :param mode:
981 :param verb:
982 :param ssp:
983
984 :return: a list of connection tuples for this interface
985 """
986 client = DCERPC_Client(
987 transport,
988 # EPM only works with NDR32
989 ndr64=False,
990 ndrendian=ndrendian,
991 verb=verb,
992 ssp=ssp,
993 )
994
995 if transport == DCERPC_Transport.NCACN_IP_TCP:
996 endpoint = 135
997 elif transport == DCERPC_Transport.NCACN_NP:
998 endpoint = "epmapper"
999 else:
1000 raise ValueError("Unknown transport value !")
1001
1002 client.connect(ip, endpoint=endpoint, smb_kwargs=smb_kwargs)
1003
1004 client.bind(find_dcerpc_interface("ept"))
1005 try:
1006 endpoints = client.epm_map(interface)
1007 finally:
1008 client.close()
1009
1010 return endpoints