mirror of
https://github.com/fortra/impacket
synced 2026-06-08 14:15:13 +00:00
34cdb1b972
* Fixing findDelegation.py - wrong ldap_login parameter name * Fixing GetUserSPNs.py - wrong parse_identity parameter value * Fixing rpcmap.py - wrong parse_identity parameter value
381 lines
16 KiB
Python
Executable File
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!")
|