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