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