Files
Gabriel Gonzalez 34cdb1b972 Techdebt examples bootstrapping fixes (#1935)
* Fixing findDelegation.py - wrong ldap_login parameter name

* Fixing GetUserSPNs.py - wrong parse_identity parameter value

* Fixing rpcmap.py - wrong parse_identity parameter value
2025-03-31 14:01:20 -03:00

381 lines
16 KiB
Python
Executable File

#!/usr/bin/env python
# Impacket - Collection of Python classes for working with network protocols.
#
# Copyright Fortra, LLC and its affiliated companies
#
# 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.
#
# Description:
# 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.examples.utils import parse_identity
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__':
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')
parser.add_argument('-ts', action='store_true', help='Adds timestamp to every logging output')
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()
# Init the example's logger theme
logger.init(options.ts, options.debug)
rpcdomain, rpcuser, rpcpass, _, _, _ = parse_identity(options.auth_rpc, options.hashes_rpc, options.no_pass, getpass_msg='Password for MSRPC communication:')
transportdomain, transportuser, transportpass, _, _, _ = parse_identity(options.auth_transport, options.hashes_transport, options.no_pass, getpass_msg='Password for RPC transport (SMB or HTTP):')
if options.brute_opnums and options.brute_versions:
logging.error("Specify only -brute-opnums or -brute-versions")
sys.exit(1)
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!")