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