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