text: #!/usr/bin/python3
  # SECUREAUTH LABS. Copyright 2020 SecureAuth Corporation. All rights reserved.
  #
  # This software is provided under a slightly modified version
  # of the Apache Software License. See the accompanying LICENSE file
  # for more information.
  #
  # Scan for listening MSRPC interfaces
  #
  # This binds to the MGMT interface and gets a list of interface UUIDs.
  # If the MGMT interface is not available, it takes a list of interface UUIDs
  # seen in the wild and tries to bind to each interface.
  #
  # If -brute-opnums is specified, the script tries to call each of the first N
  # operation numbers for each UUID in turn and reports the outcome of each call.
  #
  # This can generate a burst of connections to the given endpoint!
  #
  # Authors:
  #  Catalin Patulea <cat@vv.carleton.ca>
  #  Arseniy Sharoglazov <mohemiv@gmail.com> / Positive Technologies (https://www.ptsecurity.com/)
  #
  # TODO:
  #  [ ] The rpcmap.py connections are never closed. We need to close them.
  #      This will require changing SMB and RPC libraries.
  #
  
  from __future__ import division
  from __future__ import print_function
  import re
  import sys
  import logging
  import argparse
  
  from impacket.http import AUTH_BASIC
  from impacket.examples import logger, rpcdatabase
  from impacket import uuid, version
  from impacket.dcerpc.v5.epm import KNOWN_UUIDS
  from impacket.dcerpc.v5 import transport, rpcrt, epm
  from impacket.dcerpc.v5.rpcrt import DCERPCException
  from impacket.dcerpc.v5.transport import DCERPCStringBinding, \
      SMBTransport
  from impacket.dcerpc.v5 import mgmt
  from impacket.dcerpc.v5.rpch import RPC_PROXY_CONN_A1_401_ERR, \
      RPC_PROXY_INVALID_RPC_PORT_ERR, RPC_PROXY_HTTP_IN_DATA_401_ERR, \
      RPC_PROXY_CONN_A1_0X6BA_ERR, RPC_PROXY_CONN_A1_404_ERR, \
      RPC_PROXY_RPC_OUT_DATA_404_ERR
  
  class RPCMap():
      def __init__(self, stringbinding='', authLevel=6, bruteUUIDs=False, uuids=(),
          bruteOpnums=False, opnumMax=64, bruteVersions=False, versionMax=64):
          try:
              self.__stringbinding = DCERPCStringBinding(stringbinding)
          except:
              raise Exception("Provided stringbinding is not correct")
  
          # Empty network address is used to specify that the network address
          # must be obtained from NTLMSSP of RPC proxy.
          if self.__stringbinding.get_network_address() == '' and \
             not self.__stringbinding.is_option_set("RpcProxy"):
              raise Exception("Provided stringbinding is not correct")
  
          self.__authLevel      = authLevel
          self.__brute_uuids    = bruteUUIDs
          self.__uuids          = uuids
          self.__brute_opnums   = bruteOpnums
          self.__opnum_max      = opnumMax
          self.__brute_versions = bruteVersions
          self.__version_max    = versionMax
  
          self.__msrpc_lockout_protection = False
          self.__rpctransport   = transport.DCERPCTransportFactory(stringbinding)
          self.__dce            = self.__rpctransport.get_dce_rpc()
  
      def get_rpc_transport(self):
          return self.__rpctransport
  
      def set_transport_credentials(self, username, password, domain='', hashes=None):
          if hashes is not None:
              lmhash, nthash = hashes.split(':')
          else:
              lmhash = ''
              nthash = ''
  
          if hasattr(self.__rpctransport, 'set_credentials'):
              self.__rpctransport.set_credentials(username, password, domain, lmhash, nthash)
  
      def set_rpc_credentials(self, username, password, domain='', hashes=None):
          if hashes is not None:
              lmhash, nthash = hashes.split(':')
          else:
              lmhash = ''
              nthash = ''
  
          if hasattr(self.__dce, 'set_credentials'):
              self.__dce.set_credentials(username, password, domain, lmhash, nthash)
  
          if username != '' or password != '' or hashes != '':
              self.__msrpc_lockout_protection = True
  
      def set_smb_info(self, smbhost=None, smbport=None):
          if isinstance(self.__rpctransport, SMBTransport):
              if smbhost:
                  self.__rpctransport.setRemoteHost(smbhost)
              if smbport:
                  self.__rpctransport.set_dport(smbport)
  
      def connect(self):
          self.__dce.set_auth_level(self.__authLevel)
          self.__dce.connect()
  
      def disconnect(self):
          self.__dce.disconnect()
  
      def do(self):
          try:
              # Connecting to MGMT interface
              self.__dce.bind(mgmt.MSRPC_UUID_MGMT)
  
              # Retrieving interfaces UUIDs from the MGMT interface
              ifids = mgmt.hinq_if_ids(self.__dce)
  
              # If -brute-uuids is set, bruteforcing UUIDs instead of parsing ifids
              # We must do it after mgmt.hinq_if_ids to prevent a specified account from being locked out
              if self.__brute_uuids:
                  self.bruteforce_uuids()
                  return
  
              uuidtups = set(
                  uuid.bin_to_uuidtup(ifids['if_id_vector']['if_id'][index]['Data'].getData())
                  for index in range(ifids['if_id_vector']['count'])
                )
  
              # Adding MGMT interface itself
              uuidtups.add(('AFA8BD80-7D8A-11C9-BEF4-08002B102989', '1.0'))
  
              for tup in sorted(uuidtups):
                  self.handle_discovered_tup(tup)
          except DCERPCException as e:
              # nca_s_unk_if for Windows SMB
              # reason_not_specified for Samba 4
              # abstract_syntax_not_supported for Samba 3
              if str(e).find('nca_s_unk_if') >= 0 or \
                 str(e).find('reason_not_specified') >= 0 or \
                 str(e).find('abstract_syntax_not_supported') >= 0:
                  logging.info("Target MGMT interface not available")
                  logging.info("Bruteforcing UUIDs. The result may not be complete.")
                  self.bruteforce_uuids()
              elif str(e).find('rpc_s_access_denied') and self.__msrpc_lockout_protection == False:
                  logging.info("Target MGMT interface requires authentication, but no credentials provided.")
                  logging.info("Bruteforcing UUIDs. The result may not be complete.")
                  self.bruteforce_uuids()
              else:
                  raise
  
      def bruteforce_versions(self, interface_uuid):
          results = []
  
          for i in range(self.__version_max + 1):
              binuuid = uuid.uuidtup_to_bin((interface_uuid, "%d.0" % i))
              # Is there a way to test multiple opnums in a single rpc channel?
              self.__dce.connect()
  
              try:
                  self.__dce.bind(binuuid)
              except Exception as e:
                  if str(e).find("abstract_syntax_not_supported") >= 0:
                      results.append("abstract_syntax_not_supported (version not supported)")
                  else:
                      results.append(str(e))
              else:
                  results.append("success")
  
          if len(results) > 1 and results[-1] == results[-2]:
              suffix = results[-1]
              while results and results[-1] == suffix:
                  results.pop()
  
              for i, result in enumerate(results):
                  print("Versions %d: %s" % (i, result))
  
              print("Versions %d-%d: %s" % (len(results), self.__version_max, suffix))
          else:
              for i, result in enumerate(results):
                  print("Versions %d: %s" % (i, result))
  
      def bruteforce_opnums(self, binuuid):
          results = []
  
          for i in range(self.__opnum_max + 1):
              # Is there a way to test multiple opnums in a single rpc channel?
              self.__dce.connect()
              self.__dce.bind(binuuid)
              self.__dce.call(i, b"")
  
              try:
                  self.__dce.recv()
              except Exception as e:
                  if str(e).find("nca_s_op_rng_error") >= 0:
                      results.append("nca_s_op_rng_error (opnum not found)")
                  else:
                      results.append(str(e))
              else:
                  results.append("success")
  
          if len(results) > 1 and results[-1] == results[-2]:
              suffix = results[-1]
              while results and results[-1] == suffix:
                  results.pop()
  
              for i, result in enumerate(results):
                  print("Opnum %d: %s" % (i, result))
  
              print("Opnums %d-%d: %s" % (len(results), self.__opnum_max, suffix))
          else:
              for i, result in enumerate(results):
                  print("Opnum %d: %s" % (i, result))
  
      def bruteforce_uuids(self):
          for tup in sorted(self.__uuids):
              # Is there a way to test multiple UUIDs in a single rpc channel?
              self.__dce.connect()
              binuuid = uuid.uuidtup_to_bin(tup)
  
              try:
                  self.__dce.bind(binuuid)
              except rpcrt.DCERPCException as e:
                  # For Windows SMB
                  if str(e).find('abstract_syntax_not_supported') >= 0:
                     continue
                  # For Samba
                  if str(e).find('nca_s_proto_error') >= 0:
                     continue
                  # For Samba
                  if str(e).find('reason_not_specified') >= 0:
                     continue
  
              self.handle_discovered_tup(tup)
  
          logging.info("Tested %d UUID(s)", len(self.__uuids))
  
      def handle_discovered_tup(self, tup):
          if tup[0] in epm.KNOWN_PROTOCOLS:
              print("Protocol: %s" % (epm.KNOWN_PROTOCOLS[tup[0]]))
          else:
              print("Procotol: N/A")
  
          if uuid.uuidtup_to_bin(tup)[: 18] in KNOWN_UUIDS:
              print("Provider: %s" % (KNOWN_UUIDS[uuid.uuidtup_to_bin(tup)[:18]]))
          else:
              print("Provider: N/A")
  
          print("UUID: %s v%s" % (tup[0], tup[1]))
  
          if self.__brute_versions:
              self.bruteforce_versions(tup[0])
  
          if self.__brute_opnums:
              try:
                  self.bruteforce_opnums(uuid.uuidtup_to_bin(tup))
              except DCERPCException as e:
                  if str(e).find('abstract_syntax_not_supported') >= 0:
                      print("Listening: False")
                  else:
                      raise
          print()
  
  if __name__ == '__main__':
      # Init the example's logger theme
      logger.init()
      print(version.BANNER)
  
      class SmartFormatter(argparse.HelpFormatter):
          def _split_lines(self, text, width):
              if text.startswith('R|'):
                  return text[2:].splitlines()  
              else:
                  return argparse.HelpFormatter._split_lines(self, text, width)
  
      parser = argparse.ArgumentParser(add_help=True, formatter_class=SmartFormatter, description="Lookups listening MSRPC interfaces.")
      parser.add_argument('stringbinding', help='R|String binding to connect to MSRPC interface, for example:\n'
                                                'ncacn_ip_tcp:192.168.0.1[135]\n'
                                                'ncacn_np:192.168.0.1[\\pipe\\spoolss]\n'
                                                'ncacn_http:192.168.0.1[593]\n'
                                                'ncacn_http:[6001,RpcProxy=exchange.contoso.com:443]\n'
                                                'ncacn_http:localhost[3388,RpcProxy=rds.contoso:443]'
                                                 )
      parser.add_argument('-brute-uuids', action='store_true', help='Bruteforce UUIDs even if MGMT interface is available')
      parser.add_argument('-brute-opnums', action='store_true', help='Bruteforce opnums for found UUIDs')
      parser.add_argument('-brute-versions', action='store_true', help='Bruteforce major versions of found UUIDs')
      parser.add_argument('-opnum-max', action='store', type=int, default=64, help='Bruteforce opnums from 0 to N, default 64')
      parser.add_argument('-version-max', action='store', type=int, default=64, help='Bruteforce versions from 0 to N, default 64')
      parser.add_argument('-auth-level', action='store', type=int, default=6, help='MS-RPCE auth level, from 1 to 6, default 6 '
                                                                                   '(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)')
      parser.add_argument('-uuid', action='store', help='Test only this UUID')
      parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
  
      group = parser.add_argument_group('ncacn-np-details')
  
      group.add_argument('-target-ip', action='store', metavar="ip address", help='IP Address of the target machine. '
                         'If omitted it will use whatever was specified as target. This is useful when target is the '
                         'NetBIOS name and you cannot resolve it')
      group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port",
                         help='Destination port to connect to SMB Server')
  
      group = parser.add_argument_group('authentication')
      group.add_argument('-auth-rpc', action='store', default='', help='[domain/]username[:password]')
      group.add_argument('-auth-transport', action='store', default='', help='[domain/]username[:password]')
      group.add_argument('-hashes-rpc', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
      group.add_argument('-hashes-transport', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
      group.add_argument('-no-pass', action="store_true", help='don\'t ask for passwords')
  
      if len(sys.argv)==1:
          parser.print_help()
          sys.exit(1)
   
      options = parser.parse_args()
  
      if options.debug is True:
          logging.getLogger().setLevel(logging.DEBUG)
          # Print the Library's installation path
          logging.debug(version.getInstallationPath())
      else:
          logging.getLogger().setLevel(logging.INFO)
  
      rpcdomain, rpcuser, rpcpass = re.compile('(?:(?:([^/:]*)/)?([^:]*)(?::(.*))?)?').match(options.auth_rpc).groups('')
      transportdomain, transportuser, transportpass = re.compile('(?:(?:([^/:]*)/)?([^:]*)(?::(.*))?)?').match(options.auth_transport).groups('')
  
      if options.brute_opnums and options.brute_versions:
         logging.error("Specify only -brute-opnums or -brute-versions")
         sys.exit(1)
  
      if rpcdomain is None:
          rpcdomain = ''
  
      if transportdomain is None:
          transportdomain = ''
  
      if rpcpass == '' and rpcuser != '' and options.hashes_rpc is None and options.no_pass is False:
           from getpass import getpass
           rpcpass = getpass("Password for MSRPC communication:")
  
      if transportpass == '' and transportuser != '' and options.hashes_transport is None and options.no_pass is False:
          from getpass import getpass
          transportpass = getpass("Password for RPC transport (SMB or HTTP):")
  
      if options.uuid is not None:
          uuids = [uuid.string_to_uuidtup(options.uuid)]
          options.brute_uuids = True
      else:
          uuids = rpcdatabase.uuid_database
  
      try:
          lookuper = RPCMap(options.stringbinding, options.auth_level, options.brute_uuids, uuids,
                            options.brute_opnums, options.opnum_max, options.brute_versions, options.version_max)
          lookuper.set_rpc_credentials(rpcuser, rpcpass, rpcdomain, options.hashes_rpc)
          lookuper.set_transport_credentials(transportuser, transportpass, transportdomain, options.hashes_transport)
          lookuper.set_smb_info(options.target_ip, options.port)
          lookuper.connect()
          lookuper.do()
          lookuper.disconnect()
      except Exception as e:
          #raise
  
          # This may contain UTF-8
          error_text = 'Protocol failed: %s' % e
          logging.critical(error_text)
  
          # Exchange errors
          if RPC_PROXY_INVALID_RPC_PORT_ERR in error_text:
              logging.critical("This usually means the target is a MS Exchange Server, "
                               "and connections to this rpc port on this host are not allowed (try port 6001)")
  
          if RPC_PROXY_RPC_OUT_DATA_404_ERR in error_text or \
             RPC_PROXY_CONN_A1_404_ERR in error_text:
              logging.critical("This usually means the target is a MS Exchange Server, "
                               "and connections to the specified RPC server are not allowed")
  
          # Other errors
          if RPC_PROXY_CONN_A1_0X6BA_ERR in error_text:
              logging.critical("This usually means the target has no ACL to connect to this endpoint using RpcProxy")
  
          if RPC_PROXY_HTTP_IN_DATA_401_ERR in error_text or RPC_PROXY_CONN_A1_401_ERR in error_text:
             if lookuper.get_rpc_transport().get_auth_type() == AUTH_BASIC and transportdomain == '':
                  logging.critical("RPC proxy basic authentication might require you to specify the domain. "
                                   "Your domain is empty!")
  
          if RPC_PROXY_CONN_A1_401_ERR in error_text or \
             RPC_PROXY_CONN_A1_404_ERR in error_text:
              logging.info("A proxy in front of the target server detected (may be WAF / SIEM)")
  
          if 'rpc_s_access_denied' in error_text:
               logging.critical("This usually means the credentials on the MSRPC level are invalid!")
  