def main(): VERSION = '3.1' CODENAME = '\'Duchess\'' parser = argparse.ArgumentParser( description=""" ______ .______ ___ ______ __ ___ .___ ___. ___ .______ _______ ___ ___ _______ ______ / || _ \ / \ / || |/ / | \/ | / \ | _ \ | ____|\ \ / / | ____| / | | ,----'| |_) | / ^ \ | ,----'| ' / | \ / | / ^ \ | |_) | | |__ \ V / | |__ | ,----' | | | / / /_\ \ | | | < | |\/| | / /_\ \ | ___/ | __| > < | __| | | | `----.| |\ \----. / _____ \ | `----.| . \ | | | | / _____ \ | | | |____ / . \ | |____ | `----. \______|| _| `._____|/__/ \__\ \______||__|\__\ |__| |__| /__/ \__\ | _| |_______|/__/ \__\ |_______| \______| Swiss army knife for pentesting Windows/Active Directory environments | @byt3bl33d3r Powered by Impacket https://github.com/CoreSecurity/impacket (@agsolino) Inspired by: @ShawnDEvans's smbmap https://github.com/ShawnDEvans/smbmap @gojhonny's CredCrack https://github.com/gojhonny/CredCrack @pentestgeek's smbexec https://github.com/pentestgeek/smbexec {}: {} {}: {} """.format(highlight('Version', 'red'), highlight(VERSION), highlight('Codename', 'red'), highlight(CODENAME)), formatter_class=RawTextHelpFormatter, version='{} - {}'.format(VERSION, CODENAME), epilog='I swear I had something for this...') parser.add_argument( "target", nargs='*', type=str, help= "The target IP(s), range(s), CIDR(s), hostname(s), FQDN(s) or file(s) containg a list of targets" ) parser.add_argument( "-t", type=int, dest="threads", default=100, help="Set how many concurrent threads to use (defaults to 100)") parser.add_argument( '-id', metavar="CRED_ID", type=int, dest='cred_id', help='Database credential ID to use for authentication') parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='*', default=[], help="Username(s) or file(s) containing usernames") parser.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="Domain name") msgroup = parser.add_mutually_exclusive_group() msgroup.add_argument("-p", metavar="PASSWORD", dest='password', nargs='*', default=[], help="Password(s) or file(s) containing passwords") msgroup.add_argument( "-H", metavar="HASH", dest='hash', nargs='*', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes') parser.add_argument("-M", "--module", metavar='MODULE', dest='module', help='Payload module to use') parser.add_argument('-o', metavar='MODULE_OPTION', nargs='*', default=[], dest='module_options', help='Payload module options') parser.add_argument('-L', '--list-modules', action='store_true', help='List available modules') parser.add_argument('--show-options', action='store_true', dest='show_options', help='Display module options') parser.add_argument("--share", metavar="SHARE", dest='share', default="C$", help="Specify a share (default: C$)") parser.add_argument("--smb-port", dest='smb_port', type=int, choices={139, 445}, default=445, help="SMB port (default: 445)") parser.add_argument("--mssql-port", dest='mssql_port', default=1433, type=int, metavar='PORT', help='MSSQL port (default: 1433)') parser.add_argument("--server", choices={'http', 'https'}, default='https', help='Use the selected server (default: https)') parser.add_argument("--server-host", type=str, default='0.0.0.0', metavar='HOST', help='IP to bind the server to (default: 0.0.0.0)') parser.add_argument("--server-port", dest='server_port', metavar='PORT', type=int, help='Start the server on the specified port') parser.add_argument("--local-auth", dest='local_auth', action='store_true', help='Authenticate locally to each target') parser.add_argument( "--timeout", default=20, type=int, help='Max timeout in seconds of each thread (default: 20)') parser.add_argument("--verbose", action='store_true', dest='verbose', help="Enable verbose output") rgroup = parser.add_argument_group("Credential Gathering", "Options for gathering credentials") rgroup.add_argument("--sam", action='store_true', help='Dump SAM hashes from target systems') rgroup.add_argument("--lsa", action='store_true', help='Dump LSA secrets from target systems') rgroup.add_argument( "--ntds", choices={'vss', 'drsuapi'}, help= "Dump the NTDS.dit from target DCs using the specifed method\n(drsuapi is the fastest)" ) rgroup.add_argument("--ntds-history", action='store_true', help='Dump NTDS.dit password history') rgroup.add_argument( "--ntds-pwdLastSet", action='store_true', help='Shows the pwdLastSet attribute for each NTDS.dit account') rgroup.add_argument( "--wdigest", choices={'enable', 'disable'}, help= "Creates/Deletes the 'UseLogonCredential' registry key enabling WDigest cred dumping on Windows >= 8.1" ) egroup = parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating") egroup.add_argument("--shares", action="store_true", dest="enum_shares", help="Enumerate shares and access") egroup.add_argument('--uac', action='store_true', help='Checks UAC status') egroup.add_argument("--sessions", action='store_true', dest='enum_sessions', help='Enumerate active sessions') egroup.add_argument('--disks', action='store_true', dest='enum_disks', help='Enumerate disks') egroup.add_argument("--users", action='store_true', dest='enum_users', help='Enumerate users') egroup.add_argument( "--rid-brute", nargs='?', const=4000, metavar='MAX_RID', dest='rid_brute', help='Enumerate users by bruteforcing RID\'s (default: 4000)') egroup.add_argument("--pass-pol", action='store_true', dest='pass_pol', help='Dump password policy') egroup.add_argument("--lusers", action='store_true', dest='enum_lusers', help='Enumerate logged on users') egroup.add_argument("--wmi", metavar='QUERY', type=str, dest='wmi_query', help='Issues the specified WMI query') egroup.add_argument("--wmi-namespace", metavar='NAMESPACE', dest='wmi_namespace', default='//./root/cimv2', help='WMI Namespace (default: //./root/cimv2)') sgroup = parser.add_argument_group("Spidering", "Options for spidering shares") sgroup.add_argument("--spider", metavar='FOLDER', nargs='?', const='.', type=str, help='Folder to spider (default: root directory)') sgroup.add_argument("--content", dest='search_content', action='store_true', help='Enable file content searching') sgroup.add_argument("--exclude-dirs", type=str, metavar='DIR_LIST', default='', dest='exclude_dirs', help='Directories to exclude from spidering') esgroup = sgroup.add_mutually_exclusive_group() esgroup.add_argument( "--pattern", nargs='*', help='Pattern(s) to search for in folders, filenames and file content') esgroup.add_argument( "--regex", nargs='*', help='Regex(s) to search for in folders, filenames and file content') sgroup.add_argument("--depth", type=int, default=10, help='Spider recursion depth (default: 10)') cgroup = parser.add_argument_group("Command Execution", "Options for executing commands") cgroup.add_argument( '--exec-method', choices={"wmiexec", "smbexec", "atexec"}, default=None, help= "Method to execute the command. Ignored if in MSSQL mode (default: wmiexec)" ) cgroup.add_argument( '--force-ps32', action='store_true', help='Force the PowerShell command to run in a 32-bit process') cgroup.add_argument('--no-output', action='store_true', dest='no_output', help='Do not retrieve command output') cgroup.add_argument("-x", metavar="COMMAND", dest='command', help="Execute the specified command") cgroup.add_argument("-X", metavar="PS_COMMAND", dest='pscommand', help='Execute the specified PowerShell command') mgroup = parser.add_argument_group( "MSSQL Interaction", "Options for interacting with MSSQL DBs") mgroup.add_argument( "--mssql", action='store_true', help= 'Switches CME into MSSQL Mode. If credentials are provided will authenticate against all discovered MSSQL DBs' ) mgroup.add_argument("--mssql-query", metavar='QUERY', type=str, help='Execute the specifed query against the MSSQL DB') logger = CMEAdapter(setup_logger()) first_run_setup(logger) if len(sys.argv) == 1: parser.print_help() sys.exit(1) cme_path = os.path.expanduser('~/.cme') module = None server = None context = None targets = [] server_port_dict = {'http': 80, 'https': 443} args = parser.parse_args() if args.verbose: setup_debug_logger() if not args.server_port: args.server_port = server_port_dict[args.server] db_path = os.path.join(cme_path, 'cme.db') # set the database connection to autocommit w/ isolation level db_connection = sqlite3.connect(db_path, check_same_thread=False) db_connection.text_factory = str db_connection.isolation_level = None db = CMEDatabase(db_connection) if args.cred_id: try: c_id, credtype, domain, username, password = db.get_credentials( filterTerm=args.cred_id)[0] args.username = [username] if not args.domain: args.domain = domain if credtype == 'hash': args.hash = [password] elif credtype == 'plaintext': args.password = [password] except IndexError: logger.error("Invalid database credential ID!") sys.exit(1) else: for user in args.username: if os.path.exists(user): args.username.remove(user) args.username.append(open(user, 'r')) if args.password: for passw in args.password: if os.path.exists(passw): args.password.remove(passw) args.password.append(open(passw, 'r')) elif args.hash: for ntlm_hash in args.hash: if os.path.exists(ntlm_hash): args.hash.remove(ntlm_hash) args.hash.append(open(ntlm_hash, 'r')) for target in args.target: if os.path.exists(target): with open(target, 'r') as target_file: for target_entry in target_file: targets.extend(parse_targets(target_entry)) else: targets.extend(parse_targets(target)) loader = ModuleLoader(args, db, logger) modules = loader.get_modules() if args.list_modules: for m in modules: logger.info('{:<20} {}'.format(m, modules[m]['description'])) elif args.module: for m in modules.keys(): if args.module.lower() == m.lower(): if args.show_options: logger.info('{} module options:\n{}'.format( m, modules[m]['options'])) elif not args.show_options: module, context, server = loader.init_module( modules[m]['path']) try: ''' Open all the greenlet (as supposed to redlet??) threads Whoever came up with that name has a fetish for traffic lights ''' pool = Pool(args.threads) jobs = [ pool.spawn(connector, str(target), args, db, module, context, server) for target in targets ] #Dumping the NTDS.DIT and/or spidering shares can take a long time, so we ignore the thread timeout if args.ntds or args.spider: joinall(jobs) elif not args.ntds: for job in jobs: job.join(timeout=args.timeout) except KeyboardInterrupt: pass if server: server.shutdown() logger.info('KTHXBYE!')
class CMEDatabaseNavigator(cmd.Cmd): def __init__(self, db_path, config_path): cmd.Cmd.__init__(self) self.prompt = 'cmedb > ' try: # set the database connection to autocommit w/ isolation level conn = sqlite3.connect(db_path, check_same_thread=False) conn.text_factory = str conn.isolation_level = None self.db = CMEDatabase(conn) except Exception as e: print "[-] Could not connect to database: {}".format(e) sys.exit(1) try: self.config = ConfigParser() self.config.read(config_path) except Exception as e: print "[-] Error reading cme.conf: {}".format(e) sys.exit(1) def display_creds(self, creds): print "\nCredentials:\n" print " CredID Admin On CredType Domain UserName Password" print " ------ -------- -------- ------ -------- --------" for cred in creds: # (id, credtype, domain, username, password, host, notes, sid) credID = cred[0] credType = cred[1] domain = cred[2] username = cred[3] password = cred[4] links = self.db.get_links(credID=credID) print u" {}{}{}{}{}{}".format( '{:<8}'.format(credID), '{:<13}'.format(str(len(links)) + ' Host(s)'), '{:<12}'.format(credType), u'{:<17}'.format(domain.decode('utf-8')), u'{:<21}'.format(username.decode('utf-8')), u'{:<17}'.format(password.decode('utf-8'))) print "" def display_hosts(self, hosts): print "\nHosts:\n" print " HostID Admins IP Hostname Domain OS" print " ------ ------ -- -------- ------ --" for host in hosts: # (id, ip, hostname, domain, os) hostID = host[0] ip = host[1] hostname = host[2] domain = host[3] os = host[4] links = self.db.get_links(hostID=hostID) print u" {}{}{}{}{}{}".format( '{:<8}'.format(hostID), '{:<15}'.format(str(len(links)) + ' Cred(s)'), '{:<17}'.format(ip), u'{:<25}'.format(hostname.decode('utf-8')), u'{:<17}'.format(domain.decode('utf-8')), '{:<17}'.format(os)) print "" def do_exit(self, line): sys.exit(0) def do_import(self, line): if not line: return if line == 'empire': headers = {'Content-Type': 'application/json'} #Pull the username and password from the config file payload = { 'username': self.config.get('Empire', 'username'), 'password': self.config.get('Empire', 'password') } #Pull the host and port from the config file base_url = 'https://{}:{}'.format( self.config.get('Empire', 'api_host'), self.config.get('Empire', 'api_port')) try: r = requests.post(base_url + '/api/admin/login', json=payload, headers=headers, verify=False) if r.status_code == 200: token = r.json()['token'] url_params = {'token': token} r = requests.get(base_url + '/api/creds', headers=headers, params=url_params, verify=False) creds = r.json() for cred in creds['creds']: if cred['credtype'] == 'token' or cred[ 'credtype'] == 'krbtgt' or cred[ 'username'].endswith('$'): continue self.db.add_credential(cred['credtype'], cred['domain'], cred['username'], cred['password']) print "[+] Empire credential import successful" else: print "[-] Error authenticating to Empire's RESTful API server!" except ConnectionError as e: print "[-] Unable to connect to Empire's RESTful API server: {}".format( e) elif line == 'metasploit': msf = Msfrpc({ 'host': self.config.get('Metasploit', 'rpc_host'), 'port': self.config.get('Metasploit', 'rpc_port') }) try: msf.login('msf', self.config.get('Metasploit', 'password')) except MsfAuthError: print "[-] Error authenticating to Metasploit's MSGRPC server!" return console_id = str(msf.call('console.create')['id']) msf.call('console.write', [console_id, 'creds\n']) sleep(2) creds = msf.call('console.read', [console_id]) for entry in creds['data'].split('\n'): cred = entry.split() try: host = cred[0] port = cred[2] proto = cred[3] username = cred[4] password = cred[5] cred_type = cred[6] if proto == '(smb)' and cred_type == 'Password': self.db.add_credential('plaintext', '', username, password) except IndexError: continue msf.call('console.destroy', [console_id]) print "[+] Metasploit credential import successful" def complete_import(self, text, line, begidx, endidx): "Tab-complete 'import' commands." commands = ["empire", "metasploit"] mline = line.partition(' ')[2] offs = len(mline) - len(text) return [s[offs:] for s in commands if s.startswith(mline)] def do_hosts(self, line): filterTerm = line.strip() if filterTerm == "": hosts = self.db.get_hosts() self.display_hosts(hosts) else: hosts = self.db.get_hosts(filterTerm=filterTerm) if len(hosts) > 1: self.display_hosts(hosts) elif len(hosts) == 1: print "\nHost(s):\n" print " HostID IP Hostname Domain OS" print " ------ -- -------- ------ --" hostIDList = [] for host in hosts: hostID = host[0] hostIDList.append(hostID) ip = host[1] hostname = host[2] domain = host[3] os = host[4] print u" {}{}{}{}{}".format( '{:<8}'.format(hostID), '{:<17}'.format(ip), u'{:<25}'.format(hostname.decode('utf-8')), u'{:<17}'.format(domain.decode('utf-8')), '{:<17}'.format(os)) print "" print "\nCredential(s) with Admin Access:\n" print " CredID CredType Domain UserName Password" print " ------ -------- ------ -------- --------" for hostID in hostIDList: links = self.db.get_links(hostID=hostID) for link in links: linkID, credID, hostID = link creds = self.db.get_credentials(filterTerm=credID) for cred in creds: credID = cred[0] credType = cred[1] domain = cred[2] username = cred[3] password = cred[4] print u" {}{}{}{}{}".format( '{:<8}'.format(credID), '{:<12}'.format(credType), u'{:<17}'.format(domain.decode('utf-8')), u'{:<21}'.format(username.decode('utf-8')), u'{:<17}'.format(password.decode('utf-8'))) print "" def do_creds(self, line): filterTerm = line.strip() if filterTerm == "": creds = self.db.get_credentials() self.display_creds(creds) elif filterTerm.split()[0].lower() == "add": # add format: "domain username password <notes> <credType> <sid> args = filterTerm.split()[1:] if len(args) == 3: domain, username, password = args if validate_ntlm(password): self.db.add_credential("hash", domain, username, password) else: self.db.add_credential("plaintext", domain, username, password) else: print "[!] Format is 'add domain username password" return elif filterTerm.split()[0].lower() == "remove": args = filterTerm.split()[1:] if len(args) != 1: print "[!] Format is 'remove <credID>'" return else: self.db.remove_credentials(args) self.db.remove_links(credIDs=args) elif filterTerm.split()[0].lower() == "plaintext": creds = self.db.get_credentials(credtype="plaintext") self.display_creds(creds) elif filterTerm.split()[0].lower() == "hash": creds = self.db.get_credentials(credtype="hash") self.display_creds(creds) else: creds = self.db.get_credentials(filterTerm=filterTerm) print "\nCredential(s):\n" print " CredID CredType Pillaged From HostID Domain UserName Password" print " ------ -------- -------------------- ------ -------- --------" credIDList = [] for cred in creds: credID = cred[0] credIDList.append(credID) credType = cred[1] domain = cred[2] username = cred[3] password = cred[4] pillaged_from = cred[5] print u" {}{}{}{}{}{}".format( '{:<8}'.format(credID), '{:<12}'.format(credType), '{:<22}'.format(pillaged_from), u'{:<17}'.format(domain.decode('utf-8')), u'{:<21}'.format(username.decode('utf-8')), u'{:<17}'.format(password.decode('utf-8'))) print "" print "\nAdmin Access to Host(s):\n" print " HostID IP Hostname Domain OS" print " ------ -- -------- ------ --" for credID in credIDList: links = self.db.get_links(credID=credID) for link in links: linkID, credID, hostID = link hosts = self.db.get_hosts(hostID) for host in hosts: hostID = host[0] ip = host[1] hostname = host[2] domain = host[3] os = host[4] print u" {}{}{}{}{}".format( '{:<8}'.format(hostID), '{:<17}'.format(ip), u'{:<25}'.format(hostname.decode('utf-8')), u'{:<17}'.format(domain.decode('utf-8')), '{:<17}'.format(os)) print "" def complete_creds(self, text, line, begidx, endidx): "Tab-complete 'creds' commands." commands = ["add", "remove", "hash", "plaintext"] mline = line.partition(' ')[2] offs = len(mline) - len(text) return [s[offs:] for s in commands if s.startswith(mline)]
class CMEDatabaseNavigator(cmd.Cmd): def __init__(self, db_path, config_path): cmd.Cmd.__init__(self) self.prompt = 'cmedb > ' try: # set the database connection to autocommit w/ isolation level conn = sqlite3.connect(db_path, check_same_thread=False) conn.text_factory = str conn.isolation_level = None self.db = CMEDatabase(conn) except Exception as e: print "[-] Could not connect to database: {}".format(e) sys.exit(1) try: self.config = ConfigParser() self.config.read(config_path) except Exception as e: print "[-] Error reading cme.conf: {}".format(e) sys.exit(1) def display_creds(self, creds): print "\nCredentials:\n" print " CredID Admin On CredType Domain UserName Password" print " ------ -------- -------- ------ -------- --------" for cred in creds: # (id, credtype, domain, username, password, host, notes, sid) credID = cred[0] credType = cred[1] domain = cred[2] username = cred[3] password = cred[4] links = self.db.get_links(credID=credID) print u" {}{}{}{}{}{}".format('{:<8}'.format(credID), '{:<13}'.format(str(len(links)) + ' Host(s)'), '{:<12}'.format(credType), u'{:<17}'.format(domain.decode('utf-8')), u'{:<21}'.format(username.decode('utf-8')), u'{:<17}'.format(password.decode('utf-8'))) print "" def display_hosts(self, hosts): print "\nHosts:\n" print " HostID Admins IP Hostname Domain OS" print " ------ ------ -- -------- ------ --" for host in hosts: # (id, ip, hostname, domain, os) hostID = host[0] ip = host[1] hostname = host[2] domain = host[3] os = host[4] links = self.db.get_links(hostID=hostID) print u" {}{}{}{}{}{}".format('{:<8}'.format(hostID), '{:<15}'.format(str(len(links)) + ' Cred(s)'), '{:<17}'.format(ip), u'{:<25}'.format(hostname.decode('utf-8')), u'{:<17}'.format(domain.decode('utf-8')), '{:<17}'.format(os)) print "" def do_exit(self, line): sys.exit(0) def do_import(self, line): if not line: return if line == 'empire': headers = {'Content-Type': 'application/json'} #Pull the username and password from the config file payload = {'username': self.config.get('Empire', 'username'), 'password': self.config.get('Empire', 'password')} #Pull the host and port from the config file base_url = 'https://{}:{}'.format(self.config.get('Empire', 'api_host'), self.config.get('Empire', 'api_port')) try: r = requests.post(base_url + '/api/admin/login', json=payload, headers=headers, verify=False) if r.status_code == 200: token = r.json()['token'] url_params = {'token': token} r = requests.get(base_url + '/api/creds', headers=headers, params=url_params, verify=False) creds = r.json() for cred in creds['creds']: if cred['credtype'] == 'token' or cred['credtype'] == 'krbtgt' or cred['username'].endswith('$'): continue self.db.add_credential(cred['credtype'], cred['domain'], cred['username'], cred['password']) print "[+] Empire credential import successful" else: print "[-] Error authenticating to Empire's RESTful API server!" except ConnectionError as e: print "[-] Unable to connect to Empire's RESTful API server: {}".format(e) elif line == 'metasploit': msf = Msfrpc({'host': self.config.get('Metasploit', 'rpc_host'), 'port': self.config.get('Metasploit', 'rpc_port')}) try: msf.login('msf', self.config.get('Metasploit', 'password')) except MsfAuthError: print "[-] Error authenticating to Metasploit's MSGRPC server!" return console_id = str(msf.call('console.create')['id']) msf.call('console.write', [console_id, 'creds\n']) sleep(2) creds = msf.call('console.read', [console_id]) for entry in creds['data'].split('\n'): cred = entry.split() try: host = cred[0] port = cred[2] proto = cred[3] username = cred[4] password = cred[5] cred_type = cred[6] if proto == '(smb)' and cred_type == 'Password': self.db.add_credential('plaintext', '', username, password) except IndexError: continue msf.call('console.destroy', [console_id]) print "[+] Metasploit credential import successful" def complete_import(self, text, line, begidx, endidx): "Tab-complete 'import' commands." commands = ["empire", "metasploit"] mline = line.partition(' ')[2] offs = len(mline) - len(text) return [s[offs:] for s in commands if s.startswith(mline)] def do_hosts(self, line): filterTerm = line.strip() if filterTerm == "": hosts = self.db.get_hosts() self.display_hosts(hosts) else: hosts = self.db.get_hosts(filterTerm=filterTerm) if len(hosts) > 1: self.display_hosts(hosts) elif len(hosts) == 1: print "\nHost(s):\n" print " HostID IP Hostname Domain OS" print " ------ -- -------- ------ --" hostIDList = [] for host in hosts: hostID = host[0] hostIDList.append(hostID) ip = host[1] hostname = host[2] domain = host[3] os = host[4] print u" {}{}{}{}{}".format('{:<8}'.format(hostID), '{:<17}'.format(ip), u'{:<25}'.format(hostname.decode('utf-8')), u'{:<17}'.format(domain.decode('utf-8')), '{:<17}'.format(os)) print "" print "\nCredential(s) with Admin Access:\n" print " CredID CredType Domain UserName Password" print " ------ -------- ------ -------- --------" for hostID in hostIDList: links = self.db.get_links(hostID=hostID) for link in links: linkID, credID, hostID = link creds = self.db.get_credentials(filterTerm=credID) for cred in creds: credID = cred[0] credType = cred[1] domain = cred[2] username = cred[3] password = cred[4] print u" {}{}{}{}{}".format('{:<8}'.format(credID), '{:<12}'.format(credType), u'{:<17}'.format(domain.decode('utf-8')), u'{:<21}'.format(username.decode('utf-8')), u'{:<17}'.format(password.decode('utf-8'))) print "" def do_creds(self, line): filterTerm = line.strip() if filterTerm == "": creds = self.db.get_credentials() self.display_creds(creds) elif filterTerm.split()[0].lower() == "add": # add format: "domain username password <notes> <credType> <sid> args = filterTerm.split()[1:] if len(args) == 3: domain, username, password = args if validate_ntlm(password): self.db.add_credential("hash", domain, username, password) else: self.db.add_credential("plaintext", domain, username, password) else: print "[!] Format is 'add domain username password" return elif filterTerm.split()[0].lower() == "remove": args = filterTerm.split()[1:] if len(args) != 1 : print "[!] Format is 'remove <credID>'" return else: self.db.remove_credentials(args) self.db.remove_links(credIDs=args) elif filterTerm.split()[0].lower() == "plaintext": creds = self.db.get_credentials(credtype="plaintext") self.display_creds(creds) elif filterTerm.split()[0].lower() == "hash": creds = self.db.get_credentials(credtype="hash") self.display_creds(creds) else: creds = self.db.get_credentials(filterTerm=filterTerm) print "\nCredential(s):\n" print " CredID CredType Pillaged From HostID Domain UserName Password" print " ------ -------- -------------------- ------ -------- --------" credIDList = [] for cred in creds: credID = cred[0] credIDList.append(credID) credType = cred[1] domain = cred[2] username = cred[3] password = cred[4] pillaged_from = cred[5] print u" {}{}{}{}{}{}".format('{:<8}'.format(credID), '{:<12}'.format(credType), '{:<22}'.format(pillaged_from), u'{:<17}'.format(domain.decode('utf-8')), u'{:<21}'.format(username.decode('utf-8')), u'{:<17}'.format(password.decode('utf-8')) ) print "" print "\nAdmin Access to Host(s):\n" print " HostID IP Hostname Domain OS" print " ------ -- -------- ------ --" for credID in credIDList: links = self.db.get_links(credID=credID) for link in links: linkID, credID, hostID = link hosts = self.db.get_hosts(hostID) for host in hosts: hostID = host[0] ip = host[1] hostname = host[2] domain = host[3] os = host[4] print u" {}{}{}{}{}".format('{:<8}'.format(hostID), '{:<17}'.format(ip), u'{:<25}'.format(hostname.decode('utf-8')), u'{:<17}'.format(domain.decode('utf-8')), '{:<17}'.format(os)) print "" def complete_creds(self, text, line, begidx, endidx): "Tab-complete 'creds' commands." commands = [ "add", "remove", "hash", "plaintext"] mline = line.partition(' ')[2] offs = len(mline) - len(text) return [s[offs:] for s in commands if s.startswith(mline)]
def main(): VERSION = '3.1' CODENAME = '\'Duchess\'' parser = argparse.ArgumentParser(description=""" ______ .______ ___ ______ __ ___ .___ ___. ___ .______ _______ ___ ___ _______ ______ / || _ \ / \ / || |/ / | \/ | / \ | _ \ | ____|\ \ / / | ____| / | | ,----'| |_) | / ^ \ | ,----'| ' / | \ / | / ^ \ | |_) | | |__ \ V / | |__ | ,----' | | | / / /_\ \ | | | < | |\/| | / /_\ \ | ___/ | __| > < | __| | | | `----.| |\ \----. / _____ \ | `----.| . \ | | | | / _____ \ | | | |____ / . \ | |____ | `----. \______|| _| `._____|/__/ \__\ \______||__|\__\ |__| |__| /__/ \__\ | _| |_______|/__/ \__\ |_______| \______| Swiss army knife for pentesting Windows/Active Directory environments | @byt3bl33d3r Powered by Impacket https://github.com/CoreSecurity/impacket (@agsolino) Inspired by: @ShawnDEvans's smbmap https://github.com/ShawnDEvans/smbmap @gojhonny's CredCrack https://github.com/gojhonny/CredCrack @pentestgeek's smbexec https://github.com/pentestgeek/smbexec {}: {} {}: {} """.format(highlight('Version', 'red'), highlight(VERSION), highlight('Codename', 'red'), highlight(CODENAME)), formatter_class=RawTextHelpFormatter, version='{} - {}'.format(VERSION, CODENAME), epilog='I swear I had something for this...') parser.add_argument("target", nargs='*', type=str, help="The target IP(s), range(s), CIDR(s), hostname(s), FQDN(s) or file(s) containg a list of targets") parser.add_argument("-t", type=int, dest="threads", default=100, help="Set how many concurrent threads to use (defaults to 100)") parser.add_argument('-id', metavar="CRED_ID", type=int, dest='cred_id', help='Database credential ID to use for authentication') parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='*', default=[], help="Username(s) or file(s) containing usernames") parser.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="Domain name") msgroup = parser.add_mutually_exclusive_group() msgroup.add_argument("-p", metavar="PASSWORD", dest='password', nargs= '*', default=[], help="Password(s) or file(s) containing passwords") msgroup.add_argument("-H", metavar="HASH", dest='hash', nargs='*', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes') parser.add_argument("-M", "--module", metavar='MODULE', dest='module', help='Payload module to use') parser.add_argument('-o', metavar='MODULE_OPTION', nargs='*', default=[], dest='module_options', help='Payload module options') parser.add_argument('-L', '--list-modules', action='store_true', help='List available modules') parser.add_argument('--show-options', action='store_true', dest='show_options', help='Display module options') parser.add_argument("--share", metavar="SHARE", dest='share', default="C$", help="Specify a share (default: C$)") parser.add_argument("--smb-port", dest='smb_port', type=int, choices={139, 445}, default=445, help="SMB port (default: 445)") parser.add_argument("--mssql-port", dest='mssql_port', default=1433, type=int, metavar='PORT', help='MSSQL port (default: 1433)') parser.add_argument("--server", choices={'http', 'https'}, default='https', help='Use the selected server (default: https)') parser.add_argument("--server-host", type=str, default='0.0.0.0', metavar='HOST', help='IP to bind the server to (default: 0.0.0.0)') parser.add_argument("--server-port", dest='server_port', metavar='PORT', type=int, help='Start the server on the specified port') parser.add_argument("--local-auth", dest='local_auth', action='store_true', help='Authenticate locally to each target') parser.add_argument("--timeout", default=20, type=int, help='Max timeout in seconds of each thread (default: 20)') parser.add_argument("--verbose", action='store_true', dest='verbose', help="Enable verbose output") rgroup = parser.add_argument_group("Credential Gathering", "Options for gathering credentials") rgroup.add_argument("--sam", action='store_true', help='Dump SAM hashes from target systems') rgroup.add_argument("--lsa", action='store_true', help='Dump LSA secrets from target systems') rgroup.add_argument("--ntds", choices={'vss', 'drsuapi'}, help="Dump the NTDS.dit from target DCs using the specifed method\n(drsuapi is the fastest)") rgroup.add_argument("--ntds-history", action='store_true', help='Dump NTDS.dit password history') rgroup.add_argument("--ntds-pwdLastSet", action='store_true', help='Shows the pwdLastSet attribute for each NTDS.dit account') rgroup.add_argument("--wdigest", choices={'enable', 'disable'}, help="Creates/Deletes the 'UseLogonCredential' registry key enabling WDigest cred dumping on Windows >= 8.1") egroup = parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating") egroup.add_argument("--shares", action="store_true", dest="enum_shares", help="Enumerate shares and access") egroup.add_argument('--uac', action='store_true', help='Checks UAC status') egroup.add_argument("--sessions", action='store_true', dest='enum_sessions', help='Enumerate active sessions') egroup.add_argument('--disks', action='store_true', dest='enum_disks', help='Enumerate disks') egroup.add_argument("--users", action='store_true', dest='enum_users', help='Enumerate users') egroup.add_argument("--rid-brute", nargs='?', const=4000, metavar='MAX_RID', dest='rid_brute', help='Enumerate users by bruteforcing RID\'s (default: 4000)') egroup.add_argument("--pass-pol", action='store_true', dest='pass_pol', help='Dump password policy') egroup.add_argument("--lusers", action='store_true', dest='enum_lusers', help='Enumerate logged on users') egroup.add_argument("--wmi", metavar='QUERY', type=str, dest='wmi_query', help='Issues the specified WMI query') egroup.add_argument("--wmi-namespace", metavar='NAMESPACE', dest='wmi_namespace', default='//./root/cimv2', help='WMI Namespace (default: //./root/cimv2)') sgroup = parser.add_argument_group("Spidering", "Options for spidering shares") sgroup.add_argument("--spider", metavar='FOLDER', nargs='?', const='.', type=str, help='Folder to spider (default: root directory)') sgroup.add_argument("--content", dest='search_content', action='store_true', help='Enable file content searching') sgroup.add_argument("--exclude-dirs", type=str, metavar='DIR_LIST', default='', dest='exclude_dirs', help='Directories to exclude from spidering') esgroup = sgroup.add_mutually_exclusive_group() esgroup.add_argument("--pattern", nargs='*', help='Pattern(s) to search for in folders, filenames and file content') esgroup.add_argument("--regex", nargs='*', help='Regex(s) to search for in folders, filenames and file content') sgroup.add_argument("--depth", type=int, default=10, help='Spider recursion depth (default: 10)') cgroup = parser.add_argument_group("Command Execution", "Options for executing commands") cgroup.add_argument('--exec-method', choices={"wmiexec", "smbexec", "atexec"}, default=None, help="Method to execute the command. Ignored if in MSSQL mode (default: wmiexec)") cgroup.add_argument('--force-ps32', action='store_true', help='Force the PowerShell command to run in a 32-bit process') cgroup.add_argument('--no-output', action='store_true', dest='no_output', help='Do not retrieve command output') cgroup.add_argument("-x", metavar="COMMAND", dest='command', help="Execute the specified command") cgroup.add_argument("-X", metavar="PS_COMMAND", dest='pscommand', help='Execute the specified PowerShell command') mgroup = parser.add_argument_group("MSSQL Interaction", "Options for interacting with MSSQL DBs") mgroup.add_argument("--mssql", action='store_true', help='Switches CME into MSSQL Mode. If credentials are provided will authenticate against all discovered MSSQL DBs') mgroup.add_argument("--mssql-query", metavar='QUERY', type=str, help='Execute the specifed query against the MSSQL DB') logger = CMEAdapter(setup_logger()) first_run_setup(logger) if len(sys.argv) == 1: parser.print_help() sys.exit(1) cme_path = os.path.expanduser('~/.cme') module = None server = None context = None targets = [] server_port_dict = {'http': 80, 'https': 443} args = parser.parse_args() if args.verbose: setup_debug_logger() if not args.server_port: args.server_port = server_port_dict[args.server] db_path = os.path.join(cme_path, 'cme.db') # set the database connection to autocommit w/ isolation level db_connection = sqlite3.connect(db_path, check_same_thread=False) db_connection.text_factory = str db_connection.isolation_level = None db = CMEDatabase(db_connection) if args.cred_id: try: c_id, credtype, domain, username, password = db.get_credentials(filterTerm=args.cred_id)[0] args.username = [username] if not args.domain: args.domain = domain if credtype == 'hash': args.hash = [password] elif credtype == 'plaintext': args.password = [password] except IndexError: logger.error("Invalid database credential ID!") sys.exit(1) else: for user in args.username: if os.path.exists(user): args.username.remove(user) args.username.append(open(user, 'r')) if args.password: for passw in args.password: if os.path.exists(passw): args.password.remove(passw) args.password.append(open(passw, 'r')) elif args.hash: for ntlm_hash in args.hash: if os.path.exists(ntlm_hash): args.hash.remove(ntlm_hash) args.hash.append(open(ntlm_hash, 'r')) for target in args.target: if os.path.exists(target): with open(target, 'r') as target_file: for target_entry in target_file: targets.extend(parse_targets(target_entry)) else: targets.extend(parse_targets(target)) loader = ModuleLoader(args, db, logger) modules = loader.get_modules() if args.list_modules: for m in modules: logger.info('{:<20} {}'.format(m, modules[m]['description'])) elif args.module: for m in modules.keys(): if args.module.lower() == m.lower(): if args.show_options: logger.info('{} module options:\n{}'.format(m, modules[m]['options'])) elif not args.show_options: module, context, server = loader.init_module(modules[m]['path']) try: ''' Open all the greenlet (as supposed to redlet??) threads Whoever came up with that name has a fetish for traffic lights ''' pool = Pool(args.threads) jobs = [pool.spawn(connector, str(target), args, db, module, context, server) for target in targets] #Dumping the NTDS.DIT and/or spidering shares can take a long time, so we ignore the thread timeout if args.ntds or args.spider: joinall(jobs) elif not args.ntds: for job in jobs: job.join(timeout=args.timeout) except KeyboardInterrupt: pass if server: server.shutdown() logger.info('KTHXBYE!')