diff --git a/cme/cmedb.py b/cme/cmedb.py index 49c0a55a..59287af4 100644 --- a/cme/cmedb.py +++ b/cme/cmedb.py @@ -5,8 +5,11 @@ import cmd import configparser import csv import os +from os import listdir +from os.path import exists +from os.path import join as path_join import shutil -import sqlite3 +from sqlite3 import connect import sys from textwrap import dedent @@ -70,7 +73,7 @@ def complete_import(text, line): """ Tab-complete 'import' commands """ - commands = ["empire", "metasploit"] + commands = ("empire", "metasploit") mline = line.partition(" ")[2] offs = len(mline) - len(text) return [s[offs:] for s in commands if s.startswith(mline)] @@ -80,7 +83,7 @@ def complete_export(text, line): """ Tab-complete 'creds' commands. """ - commands = [ + commands = ( "creds", "plaintext", "hashes", @@ -88,7 +91,7 @@ def complete_export(text, line): "local_admins", "signing", "keys", - ] + ) mline = line.partition(" ")[2] offs = len(mline) - len(text) return [s[offs:] for s in commands if s.startswith(mline)] @@ -111,7 +114,8 @@ class DatabaseNavigator(cmd.Cmd): self.db.shutdown_db() sys.exit() - def help_exit(self): + @staticmethod + def help_exit(): help_string = """ Exits """ @@ -138,14 +142,14 @@ class DatabaseNavigator(cmd.Cmd): filename = line[2] creds = self.db.get_credentials() - csv_header = [ + csv_header = ( "id", "domain", "username", "password", "credtype", "pillaged_from", - ] + ) if line[1].lower() == "simple": write_csv(filename, csv_header, creds) @@ -178,7 +182,7 @@ class DatabaseNavigator(cmd.Cmd): ) return - csv_header_simple = [ + csv_header_simple = ( "id", "ip", "hostname", @@ -187,8 +191,8 @@ class DatabaseNavigator(cmd.Cmd): "dc", "smbv1", "signing", - ] - csv_header_detailed = [ + ) + csv_header_detailed = ( "id", "ip", "hostname", @@ -200,7 +204,7 @@ class DatabaseNavigator(cmd.Cmd): "spooler", "zerologon", "petitpotam", - ] + ) filename = line[2] if line[1].lower() == "simple": @@ -228,7 +232,7 @@ class DatabaseNavigator(cmd.Cmd): return shares = self.db.get_shares() - csv_header = ["id", "host", "userid", "name", "remark", "read", "write"] + csv_header = ("id", "host", "userid", "name", "remark", "read", "write") filename = line[2] if line[1].lower() == "simple": @@ -240,7 +244,7 @@ class DatabaseNavigator(cmd.Cmd): for share in shares: user = self.db.get_users(share[2])[0] - entry = [ + entry = ( share[0], # shareID self.db.get_hosts(share[1])[0][2], # hosts f"{user[1]}\{user[2]}", # userID @@ -248,7 +252,7 @@ class DatabaseNavigator(cmd.Cmd): share[4], # remark bool(share[5]), # read bool(share[6]), # write - ] + ) formatted_shares.append(entry) write_csv(filename, csv_header, formatted_shares) else: @@ -265,7 +269,7 @@ class DatabaseNavigator(cmd.Cmd): # These values don't change between simple and detailed local_admins = self.db.get_admin_relations() - csv_header = ["id", "userid", "host"] + csv_header = ("id", "userid", "host") filename = line[2] if line[1].lower() == "simple": @@ -275,11 +279,11 @@ class DatabaseNavigator(cmd.Cmd): for entry in local_admins: user = self.db.get_users(filter_term=entry[1])[0] - formatted_entry = [ + formatted_entry = ( entry[0], # Entry ID f"{user[1]}/{user[2]}", # DOMAIN/Username self.db.get_hosts(filter_term=entry[2])[0][2], # Hostname - ] + ) # Can't modify a tuple which is what self.db.get_admin_relations() returns formatted_local_admins.append(formatted_entry) write_csv(filename, csv_header, formatted_local_admins) @@ -296,7 +300,7 @@ class DatabaseNavigator(cmd.Cmd): # These values don't change between simple and detailed dpapi_secrets = self.db.get_dpapi_secrets() - csv_header = [ + csv_header = ( "id", "host", "dpapi_type", @@ -304,7 +308,7 @@ class DatabaseNavigator(cmd.Cmd): "username", "password", "url", - ] + ) filename = line[2] if line[1].lower() == "simple": @@ -312,7 +316,7 @@ class DatabaseNavigator(cmd.Cmd): elif line[1].lower() == "detailed": formatted_dpapi_secret = [] for entry in dpapi_secrets: - formatted_entry = [ + formatted_entry = ( entry[0], # Entry ID self.db.get_hosts(filter_term=entry[1])[0][2], # Hostname entry[2], # DPAPI type @@ -320,12 +324,12 @@ class DatabaseNavigator(cmd.Cmd): entry[4], # Username entry[5], # Password entry[6], # URL - ] + ) # Can't modify a tuple which is what self.db.get_admin_relations() returns formatted_dpapi_secret.append(formatted_entry) write_csv(filename, csv_header, formatted_dpapi_secret) else: - print("[-] No such export option: %s" % line[1]) + print(f"[-] No such export option: {line[1]}") return print("[+] DPAPI secrets exported") elif command == "keys": @@ -341,7 +345,8 @@ class DatabaseNavigator(cmd.Cmd): "[-] Invalid argument, specify creds, hosts, local_admins, shares or dpapi" ) - def help_export(self): + @staticmethod + def help_export(): help_string = """ export [creds|hosts|local_admins|shares|signing|keys] [simple|detailed|*] [filename] Exports information to a specified file @@ -436,8 +441,8 @@ class CMEDBMenu(cmd.Cmd): if not proto: return - proto_db_path = os.path.join(WORKSPACE_DIR, self.workspace, proto + ".db") - if os.path.exists(proto_db_path): + proto_db_path = path_join(WORKSPACE_DIR, self.workspace, f"{proto}.db") + if exists(proto_db_path): self.conn = create_db_engine(proto_db_path) db_nav_object = self.p_loader.load_protocol(self.protocols[proto]["nvpath"]) db_object = self.p_loader.load_protocol(self.protocols[proto]["dbpath"]) @@ -451,7 +456,8 @@ class CMEDBMenu(cmd.Cmd): except UserExitedProto: pass - def help_proto(self): + @staticmethod + def help_proto(): help_string = """ proto [smb|mssql|winrm] *unimplemented protocols: ftp, rdp, ldap, ssh @@ -474,27 +480,30 @@ class CMEDBMenu(cmd.Cmd): self.do_workspace(new_workspace) elif subcommand == "list": print("[*] Enumerating Workspaces") - for workspace in os.listdir(os.path.join(WORKSPACE_DIR)): + for workspace in listdir(path_join(WORKSPACE_DIR)): if workspace == self.workspace: print("==> " + workspace) else: print(workspace) - elif os.path.exists(os.path.join(WORKSPACE_DIR, line)): + elif exists(path_join(WORKSPACE_DIR, line)): self.config.set("CME", "workspace", line) self.write_configfile() self.workspace = line self.prompt = f"cmedb ({line}) > " - def help_workspace(self): + @staticmethod + def help_workspace(): help_string = """ workspace [create | workspace list | workspace ] """ print_help(help_string) - def do_exit(self, line): + @staticmethod + def do_exit(line): sys.exit() - def help_exit(self): + @staticmethod + def help_exit(): help_string = """ Exits """ @@ -502,18 +511,15 @@ class CMEDBMenu(cmd.Cmd): def create_workspace(workspace_name, p_loader, protocols): - os.mkdir(os.path.join(WORKSPACE_DIR, workspace_name)) + os.mkdir(path_join(WORKSPACE_DIR, workspace_name)) for protocol in protocols.keys(): - try: - protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"]) - except KeyError: - continue - proto_db_path = os.path.join(WORKSPACE_DIR, workspace_name, protocol + ".db") + protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"]) + proto_db_path = path_join(WORKSPACE_DIR, workspace_name, f"{protocol}.db") - if not os.path.exists(proto_db_path): + if not exists(proto_db_path): print(f"[*] Initializing {protocol.upper()} protocol database") - conn = sqlite3.connect(proto_db_path) + conn = connect(proto_db_path) c = conn.cursor() # try to prevent some weird sqlite I/O errors @@ -528,27 +534,23 @@ def create_workspace(workspace_name, p_loader, protocols): def delete_workspace(workspace_name): - shutil.rmtree(os.path.join(WORKSPACE_DIR, workspace_name)) + shutil.rmtree(path_join(WORKSPACE_DIR, workspace_name)) def initialize_db(logger): - if not os.path.exists(os.path.join(WS_PATH, "default")): + if not exists(path_join(WS_PATH, "default")): logger.debug("Creating default workspace") - os.mkdir(os.path.join(WS_PATH, "default")) + os.mkdir(path_join(WS_PATH, "default")) p_loader = ProtocolLoader() protocols = p_loader.get_protocols() for protocol in protocols.keys(): - try: - protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"]) - except KeyError: - continue + protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"]) + proto_db_path = path_join(WS_PATH, "default", f"{protocol}.db") - proto_db_path = os.path.join(WS_PATH, "default", protocol + ".db") - - if not os.path.exists(proto_db_path): + if not exists(proto_db_path): logger.debug(f"Initializing {protocol.upper()} protocol database") - conn = sqlite3.connect(proto_db_path) + conn = connect(proto_db_path) c = conn.cursor() # try to prevent some weird sqlite I/O errors c.execute( @@ -564,7 +566,7 @@ def initialize_db(logger): def main(): - if not os.path.exists(CONFIG_PATH): + if not exists(CONFIG_PATH): print("[-] Unable to find config file") sys.exit(1) try: diff --git a/cme/connection.py b/cme/connection.py index 7e87b585..74c67088 100755 --- a/cme/connection.py +++ b/cme/connection.py @@ -3,7 +3,8 @@ import random import socket -import sys +from socket import AF_INET, AF_INET6, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME +from socket import getaddrinfo from os.path import isfile from threading import BoundedSemaphore from functools import wraps @@ -21,23 +22,23 @@ user_failed_logins = {} def gethost_addrinfo(hostname): try: - for res in socket.getaddrinfo( + for res in getaddrinfo( hostname, None, - socket.AF_INET6, - socket.SOCK_DGRAM, - socket.IPPROTO_IP, - socket.AI_CANONNAME, + AF_INET6, + SOCK_DGRAM, + IPPROTO_IP, + AI_CANONNAME, ): af, socktype, proto, canonname, sa = res except socket.gaierror: - for res in socket.getaddrinfo( + for res in getaddrinfo( hostname, None, - socket.AF_INET, - socket.SOCK_DGRAM, - socket.IPPROTO_IP, - socket.AI_CANONNAME, + AF_INET, + SOCK_DGRAM, + IPPROTO_IP, + AI_CANONNAME, ): af, socktype, proto, canonname, sa = res if canonname == "": diff --git a/cme/crackmapexec.py b/cme/crackmapexec.py index 15b0aa31..a939d448 100755 --- a/cme/crackmapexec.py +++ b/cme/crackmapexec.py @@ -15,16 +15,17 @@ from cme.paths import CME_PATH, DATA_PATH from cme.console import cme_console from cme.logger import cme_logger from cme.config import cme_config, cme_workspace, config_log, ignore_opsec -from concurrent.futures import ThreadPoolExecutor +from concurrent.futures import ThreadPoolExecutor, as_completed import asyncio import cme.helpers.powershell as powershell import shutil import webbrowser import random import os -import sys +from os.path import exists +from os.path import join as path_join +from sys import exit import logging -import concurrent.futures import sqlalchemy from rich.progress import Progress @@ -34,7 +35,7 @@ except: print( "Incompatible python version, try with another python version or another binary 3.8 / 3.9 / 3.10 / 3.11 that match your python version (python -V)" ) - sys.exit(1) + exit(1) def create_db_engine(db_path): @@ -64,7 +65,7 @@ async def start_run(protocol_obj, args, db, targets): executor.submit(protocol_obj, args, db, target) for target in targets ] - for future in concurrent.futures.as_completed(futures): + for future in as_completed(futures): current += 1 progress.update(tasks, completed=current) @@ -84,7 +85,8 @@ def main(): cme_logger.logger.setLevel(logging.ERROR) root_logger.setLevel(logging.ERROR) - # if these are the same, it might double log to file (two FileHandlers will be added), but this should never happen by accident + # if these are the same, it might double log to file (two FileHandlers will be added) + # but this should never happen by accident if config_log: cme_logger.add_file_log() if hasattr(args, "log") and args.log: @@ -94,16 +96,14 @@ def main(): if args.darrell: links = ( - open(os.path.join(DATA_PATH, "videos_for_darrell.harambe")) - .read() - .splitlines() + open(path_join(DATA_PATH, "videos_for_darrell.harambe")).read().splitlines() ) try: webbrowser.open(random.choice(links)) - sys.exit(1) + exit(1) except Exception as e: cme_logger.error(f"Error opening le dank meme: {e}") - sys.exit(1) + exit(1) if args.protocol == "ssh": if args.key_file: @@ -111,11 +111,11 @@ def main(): cme_logger.fail( f"Password is required, even if a key file is used - if no passphrase for key, use `-p ''`" ) - sys.exit(1) + exit(1) if args.use_kcache and not os.environ.get("KRB5CCNAME"): cme_logger.error("KRB5CCNAME environment variable is not set") - sys.exit(1) + exit(1) module_server = None targets = [] @@ -131,11 +131,11 @@ def main(): args.cred_id.remove(cred_id) except Exception as e: cme_logger.error(f"Error parsing database credential id: {e}") - sys.exit(1) + exit(1) if hasattr(args, "target") and args.target: for target in args.target: - if os.path.exists(target): + if exists(target): target_file_type = identify_target_file(target) if target_file_type == "nmap": targets.extend(parse_nmap_xml(target, args.protocol)) @@ -169,7 +169,7 @@ def main(): protocol_db_object = getattr(p_loader.load_protocol(protocol_db_path), "database") cme_logger.debug(f"Protocol DB Object: {protocol_db_object}") - db_path = os.path.join(CME_PATH, "workspaces", cme_workspace, args.protocol + ".db") + db_path = path_join(CME_PATH, "workspaces", cme_workspace, f"{args.protocol}.db") cme_logger.debug(f"DB Path: {db_path}") db_engine = create_db_engine(db_path) @@ -186,13 +186,13 @@ def main(): for name, props in sorted(modules.items()): if args.protocol in props["supported_protocols"]: cme_logger.display(f"{name:<25} {props['description']}") - sys.exit(0) + exit(0) elif args.module and args.show_module_options: for module in args.module: cme_logger.display( f"{module} module options:\n{modules[module]['options']}" ) - sys.exit(0) + exit(0) elif args.module: cme_logger.debug(f"Modules to be Loaded: {args.module}, {type(args.module)}") for m in map(str.lower, args.module): @@ -219,7 +219,7 @@ def main(): ) ) if ans.lower() not in ["y", "yes", ""]: - sys.exit(1) + exit(1) if not module.multiple_hosts and len(targets) > 1: ans = input( @@ -229,7 +229,7 @@ def main(): ) ) if ans.lower() not in ["y", "yes", ""]: - sys.exit(1) + exit(1) if hasattr(module, "on_request") or hasattr(module, "has_response"): if hasattr(module, "required_server"): @@ -274,7 +274,7 @@ def main(): ) ) if ans.lower() not in ["y", "yes", ""]: - sys.exit(1) + exit(1) try: asyncio.run(start_run(protocol_object, args, db, targets)) diff --git a/cme/first_run.py b/cme/first_run.py index 8159ef7e..d9b7d8b3 100755 --- a/cme/first_run.py +++ b/cme/first_run.py @@ -2,6 +2,9 @@ # -*- coding: utf-8 -*- import os +from os import mkdir +from os.path import exists +from os.path import join as path_join import shutil import configparser from configparser import NoSectionError, NoOptionError @@ -11,32 +14,32 @@ from cme.logger import cme_logger def first_run_setup(logger=cme_logger): - if not os.path.exists(TMP_PATH): - os.mkdir(TMP_PATH) + if not exists(TMP_PATH): + mkdir(TMP_PATH) - if not os.path.exists(CME_PATH): + if not exists(CME_PATH): logger.display("First time use detected") logger.display("Creating home directory structure") - os.mkdir(CME_PATH) + mkdir(CME_PATH) - folders = [ + folders = ( "logs", "modules", "protocols", "workspaces", "obfuscated_scripts", "screenshots", - ] + ) for folder in folders: - if not os.path.exists(os.path.join(CME_PATH, folder)): + if not exists(path_join(CME_PATH, folder)): logger.display(f"Creating missing folder {folder}") - os.mkdir(os.path.join(CME_PATH, folder)) + mkdir(path_join(CME_PATH, folder)) initialize_db(logger) - if not os.path.exists(CONFIG_PATH): + if not exists(CONFIG_PATH): logger.display("Copying default configuration file") - default_path = os.path.join(DATA_PATH, "cme.conf") + default_path = path_join(DATA_PATH, "cme.conf") shutil.copy(default_path, CME_PATH) else: # This is just a quick check to make sure the config file isn't the old 3.x format @@ -52,10 +55,10 @@ def first_run_setup(logger=cme_logger): logger.display( "Old configuration file detected, replacing with new version" ) - default_path = os.path.join(DATA_PATH, "cme.conf") + default_path = path_join(DATA_PATH, "cme.conf") shutil.copy(default_path, CME_PATH) - # if not os.path.exists(CERT_PATH): + # if not exists(CERT_PATH): # logger.display('Generating SSL certificate') # try: # check_output(['openssl', 'help'], stderr=PIPE) @@ -66,7 +69,7 @@ def first_run_setup(logger=cme_logger): # except OSError as e: # if e.errno == errno.ENOENT: # logger.error('OpenSSL command line utility is not installed, could not generate certificate, using default certificate') - # default_path = os.path.join(DATA_PATH, 'default.pem') + # default_path = path_join(DATA_PATH, 'default.pem') # shutil.copy(default_path, CERT_PATH) # else: # logger.error('Error while generating SSL certificate: {}'.format(e)) diff --git a/cme/loaders/moduleloader.py b/cme/loaders/moduleloader.py index cdd81879..5ef486b1 100755 --- a/cme/loaders/moduleloader.py +++ b/cme/loaders/moduleloader.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- import importlib -import os +from os import listdir +from os.path import dirname +from os.path import join as path_join import sys import cme @@ -130,14 +132,14 @@ class ModuleLoader: """ modules = {} modules_paths = [ - os.path.join(os.path.dirname(cme.__file__), "modules"), - os.path.join(CME_PATH, "modules"), + path_join(dirname(cme.__file__), "modules"), + path_join(CME_PATH, "modules"), ] for path in modules_paths: - for module in os.listdir(path): + for module in listdir(path): if module[-3:] == ".py" and module != "example_module.py": - module_path = os.path.join(path, module) + module_path = path_join(path, module) module_data = self.get_module_info(module_path) modules.update(module_data) return modules diff --git a/cme/loaders/protocolloader.py b/cme/loaders/protocolloader.py index a6a0017d..d5ad25aa 100755 --- a/cme/loaders/protocolloader.py +++ b/cme/loaders/protocolloader.py @@ -2,13 +2,15 @@ # -*- coding: utf-8 -*- import types from importlib.machinery import SourceFileLoader -import os +from os import listdir +from os.path import join as path_join +from os.path import dirname, exists, expanduser import cme class ProtocolLoader: def __init__(self): - self.cme_path = os.path.expanduser("~/.cme") + self.cme_path = expanduser("~/.cme") def load_protocol(self, protocol_path): loader = SourceFileLoader("protocol", protocol_path) @@ -19,23 +21,23 @@ class ProtocolLoader: def get_protocols(self): protocols = {} protocol_paths = [ - os.path.join(os.path.dirname(cme.__file__), "protocols"), - os.path.join(self.cme_path, "protocols"), + path_join(dirname(cme.__file__), "protocols"), + path_join(self.cme_path, "protocols"), ] for path in protocol_paths: - for protocol in os.listdir(path): + for protocol in listdir(path): if protocol[-3:] == ".py" and protocol[:-3] != "__init__": - protocol_path = os.path.join(path, protocol) + protocol_path = path_join(path, protocol) protocol_name = protocol[:-3] protocols[protocol_name] = {"path": protocol_path} - db_file_path = os.path.join(path, protocol_name, "database.py") - db_nav_path = os.path.join(path, protocol_name, "db_navigator.py") - if os.path.exists(db_file_path): + db_file_path = path_join(path, protocol_name, "database.py") + db_nav_path = path_join(path, protocol_name, "db_navigator.py") + if exists(db_file_path): protocols[protocol_name]["dbpath"] = db_file_path - if os.path.exists(db_nav_path): + if exists(db_nav_path): protocols[protocol_name]["nvpath"] = db_nav_path return protocols diff --git a/cme/logger.py b/cme/logger.py index f4953bd6..7c1d0023 100755 --- a/cme/logger.py +++ b/cme/logger.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- import logging +from logging import LogRecord from logging.handlers import RotatingFileHandler import os.path import sys @@ -148,10 +149,10 @@ class CMEAdapter(logging.LoggerAdapter): if self.logger.getEffectiveLevel() >= logging.INFO: # will be 0 if it's just the console output, so only do this if we actually have file loggers if len(self.logger.handlers): - for handler in self.logger.handlers: - try: + try: + for handler in self.logger.handlers: handler.handle( - logging.LogRecord( + LogRecord( "cme", 20, "", @@ -161,10 +162,8 @@ class CMEAdapter(logging.LoggerAdapter): exc_info=None, ) ) - except Exception as e: - self.logger.fail( - f"Issue while trying to custom print handler: {e}" - ) + except Exception as e: + self.logger.fail(f"Issue while trying to custom print handler: {e}") else: self.logger.info(text) diff --git a/cme/protocols/mssql/db_navigator.py b/cme/protocols/mssql/db_navigator.py index 814d937c..ae7bd361 100644 --- a/cme/protocols/mssql/db_navigator.py +++ b/cme/protocols/mssql/db_navigator.py @@ -10,22 +10,15 @@ class navigator(DatabaseNavigator): data = [["CredID", "Admin On", "CredType", "Domain", "UserName", "Password"]] for cred in creds: - cred_id = cred[0] - credtype = cred[1] - domain = cred[2] - username = cred[3] - password = cred[4] - # pillaged_from = cred[5] - - links = self.db.get_admin_relations(user_id=cred_id) + links = self.db.get_admin_relations(user_id=cred[0]) data.append( [ - cred_id, + cred[0], # cred_id str(len(links)) + " Host(s)", - credtype, - domain, - username, - password, + cred[1], # cred_type + cred[2], # domain + cred[3], # username + cred[4], # password ] ) print_table(data, title="Credentials") @@ -33,24 +26,16 @@ class navigator(DatabaseNavigator): def display_hosts(self, hosts): data = [["HostID", "Admins", "IP", "Hostname", "Domain", "OS", "DB Instances"]] for host in hosts: - host_id = host[0] - ip = host[1] - hostname = host[2] - domain = host[3] - os = host[4] - instances = host[5] - - links = self.db.get_admin_relations(host_id=host_id) - + links = self.db.get_admin_relations(host_id=host[0]) data.append( [ - host_id, + host[0], str(len(links)) + " Cred(s)", - ip, - hostname, - domain, - os, - instances, + host[1], + host[2], + host[3], + host[4], + host[5], ] ) print_table(data, title="Hosts") @@ -71,15 +56,8 @@ class navigator(DatabaseNavigator): host_id_list = [] for host in hosts: - host_id = host[0] - host_id_list.append(host_id) - - ip = host[1] - hostname = host[2] - domain = host[3] - os = host[4] - - data.append([host_id, ip, hostname, domain, os]) + host_id_list.append(host[0]) + data.append([host[0], host[1], host[2], host[3], host[4]]) print_table(data, title="Host(s)") @@ -92,14 +70,7 @@ class navigator(DatabaseNavigator): creds = self.db.get_credentials(filter_term=cred_id) for cred in creds: - cred_id = cred[0] - domain = cred[1] - username = cred[2] - password = cred[3] - credtype = cred[4] - # pillaged_from = cred[5] - - data.append([cred_id, credtype, domain, username, password]) + data.append([cred[0], cred[4], cred[1], cred[2], cred[3]]) print_table(data, title="Credential(s) with Admin Access") def do_creds(self, line): @@ -141,15 +112,8 @@ class navigator(DatabaseNavigator): cred_id_list = [] for cred in creds: - cred_id = cred[0] - cred_id_list.append(cred_id) - - credType = cred[1] - domain = cred[2] - username = cred[3] - password = cred[4] - - data.append([cred_id, credType, domain, username, password]) + cred_id_list.append(cred[0]) + data.append([cred[0], cred[1], cred[2], cred[3], cred[4]]) print_table(data, title="Credential(s)") @@ -162,13 +126,7 @@ class navigator(DatabaseNavigator): hosts = self.db.get_hosts(host_id) for host in hosts: - host_id = host[0] - ip = host[1] - hostname = host[2] - domain = host[3] - os = host[4] - - data.append([host_id, ip, hostname, domain, os]) + data.append([host[0], host[1], host[2], host[3], host[4]]) print_table(data, title="Admin Access to Host(s)") def do_clear_database(self, line): @@ -180,7 +138,8 @@ class navigator(DatabaseNavigator): ): self.db.clear_database() - def help_clear_database(self): + @staticmethod + def help_clear_database(): help_string = """ clear_database THIS COMPLETELY DESTROYS ALL DATA IN THE CURRENTLY CONNECTED DATABASE @@ -192,8 +151,7 @@ class navigator(DatabaseNavigator): """ Tab-complete 'creds' commands """ - commands = ["add", "remove"] - + commands = ("add", "remove") mline = line.partition(" ")[2] offs = len(mline) - len(text) return [s[offs:] for s in commands if s.startswith(mline)] @@ -202,8 +160,7 @@ class navigator(DatabaseNavigator): """ Tab-complete 'creds' commands """ - commands = ["add", "remove", "hash", "plaintext"] - + 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)] diff --git a/cme/protocols/rdp.py b/cme/protocols/rdp.py index 631a3c7e..2cf4769f 100644 --- a/cme/protocols/rdp.py +++ b/cme/protocols/rdp.py @@ -20,23 +20,6 @@ from asyauth.common.credentials.kerberos import KerberosCredential from asyauth.common.constants import asyauthSecret from asysocks.unicomm.common.target import UniTarget, UniProto -rdp_error_status = { - "0xc0000071": "STATUS_PASSWORD_EXPIRED", - "0xc0000234": "STATUS_ACCOUNT_LOCKED_OUT", - "0xc0000072": "STATUS_ACCOUNT_DISABLED", - "0xc0000193": "STATUS_ACCOUNT_EXPIRED", - "0xc000006E": "STATUS_ACCOUNT_RESTRICTION", - "0xc000006F": "STATUS_INVALID_LOGON_HOURS", - "0xc0000070": "STATUS_INVALID_WORKSTATION", - "0xc000015B": "STATUS_LOGON_TYPE_NOT_GRANTED", - "0xc0000224": "STATUS_PASSWORD_MUST_CHANGE", - "0xc0000022": "STATUS_ACCESS_DENIED", - "0xc000006d": "STATUS_LOGON_FAILURE", - "0xc000006a": "STATUS_WRONG_PASSWORD ", - "KDC_ERR_CLIENT_REVOKED": "KDC_ERR_CLIENT_REVOKED", - "KDC_ERR_PREAUTH_FAILED": "KDC_ERR_PREAUTH_FAILED", -} - class rdp(connection): def __init__(self, args, db, host): @@ -77,6 +60,23 @@ class rdp(connection): self.target = None self.auth = None + self.rdp_error_status = { + "0xc0000071": "STATUS_PASSWORD_EXPIRED", + "0xc0000234": "STATUS_ACCOUNT_LOCKED_OUT", + "0xc0000072": "STATUS_ACCOUNT_DISABLED", + "0xc0000193": "STATUS_ACCOUNT_EXPIRED", + "0xc000006E": "STATUS_ACCOUNT_RESTRICTION", + "0xc000006F": "STATUS_INVALID_LOGON_HOURS", + "0xc0000070": "STATUS_INVALID_WORKSTATION", + "0xc000015B": "STATUS_LOGON_TYPE_NOT_GRANTED", + "0xc0000224": "STATUS_PASSWORD_MUST_CHANGE", + "0xc0000022": "STATUS_ACCESS_DENIED", + "0xc000006d": "STATUS_LOGON_FAILURE", + "0xc000006a": "STATUS_WRONG_PASSWORD ", + "KDC_ERR_CLIENT_REVOKED": "KDC_ERR_CLIENT_REVOKED", + "KDC_ERR_PREAUTH_FAILED": "KDC_ERR_PREAUTH_FAILED", + } + connection.__init__(self, args, db, host) @staticmethod @@ -349,11 +349,7 @@ class rdp(connection): if not self.config.get("CME", "audit_mode") else self.config.get("CME", "audit_mode") * 8 ), - highlight( - f'({self.config.get("CME", "pwn3d_label")})' - if self.admin_privs - else "" - ), + self.mark_pwned(), ) ) if not self.args.local_auth: @@ -364,9 +360,9 @@ class rdp(connection): except Exception as e: if "KDC_ERR" in str(e): reason = None - for word in rdp_error_status.keys(): + for word in self.rdp_error_status.keys(): if word in str(e): - reason = rdp_error_status[word] + reason = self.rdp_error_status[word] self.logger.fail( f"{domain}\\{username}{' from ccache' if useCache else ':%s' % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8)} {f'({reason})' if reason else str(e)}", color="magenta" @@ -378,24 +374,15 @@ class rdp(connection): ) elif "Authentication failed!" in str(e): self.logger.success( - "{}\\{}:{} {}".format( - domain, - username, - password, - highlight( - f'({self.config.get("CME", "pwn3d_label")})' - if self.admin_privs - else "" - ), - ) + f"{domain}\\{username}:{password} {self.mark_pwned()}" ) elif "No such file" in str(e): self.logger.fail(e) else: reason = None - for word in rdp_error_status.keys(): + for word in self.rdp_error_status.keys(): if word in str(e): - reason = rdp_error_status[word] + reason = self.rdp_error_status[word] if "cannot unpack non-iterable NoneType object" == str(e): reason = "User valid but cannot connect" self.logger.fail( @@ -423,18 +410,7 @@ class rdp(connection): asyncio.run(self.connect_rdp()) self.admin_privs = True - self.logger.success( - "{}\\{}:{} {}".format( - domain, - username, - password, - highlight( - f'({self.config.get("CME", "pwn3d_label")})' - if self.admin_privs - else "" - ), - ) - ) + self.logger.success(f"{domain}\\{username}:{password} {self.mark_pwned()}") if not self.args.local_auth: add_user_bh(username, domain, self.logger, self.config) if not self.args.continue_on_success: @@ -442,22 +418,13 @@ class rdp(connection): except Exception as e: if "Authentication failed!" in str(e): self.logger.success( - "{}\\{}:{} {}".format( - domain, - username, - password, - highlight( - f'({self.config.get("CME", "pwn3d_label")})' - if self.admin_privs - else "" - ), - ) + f"{domain}\\{username}:{password} {self.mark_pwned()}" ) else: reason = None - for word in rdp_error_status.keys(): + for word in self.rdp_error_status.keys(): if word in str(e): - reason = rdp_error_status[word] + reason = self.rdp_error_status[word] if "cannot unpack non-iterable NoneType object" == str(e): reason = "User valid but cannot connect" self.logger.fail( @@ -486,16 +453,7 @@ class rdp(connection): self.admin_privs = True self.logger.success( - "{}\\{}:{} {}".format( - self.domain, - username, - ntlm_hash, - highlight( - f'({self.config.get("CME", "pwn3d_label")})' - if self.admin_privs - else "" - ), - ) + f"{self.domain}\\{username}:{ntlm_hash} {self.mark_pwned()}" ) if not self.args.local_auth: add_user_bh(username, domain, self.logger, self.config) @@ -504,22 +462,13 @@ class rdp(connection): except Exception as e: if "Authentication failed!" in str(e): self.logger.success( - "{}\\{}:{} {}".format( - domain, - username, - ntlm_hash, - highlight( - f'({self.config.get("CME", "pwn3d_label")})' - if self.admin_privs - else "" - ), - ) + f"{domain}\\{username}:{ntlm_hash} {self.mark_pwned()}" ) else: reason = None - for word in rdp_error_status.keys(): + for word in self.rdp_error_status.keys(): if word in str(e): - reason = rdp_error_status[word] + reason = self.rdp_error_status[word] if "cannot unpack non-iterable NoneType object" == str(e): reason = "User valid but cannot connect" diff --git a/cme/protocols/smb.py b/cme/protocols/smb.py index ef84f6d4..8e04bc4e 100755 --- a/cme/protocols/smb.py +++ b/cme/protocols/smb.py @@ -60,6 +60,7 @@ from datetime import datetime from functools import wraps from traceback import format_exc import logging +from json import loads smb_share_name = gen_random_string(5).upper() smb_server = None @@ -642,11 +643,8 @@ class smb(connection): self.logger.fail( "LAPS password is encrypted and currently CrackMapExec doesn't support the decryption..." ) - return False elif "mslaps-password" in values: - from json import loads - r = loads(values["mslaps-password"]) msMCSAdmPwd = r["p"] username = r["n"] @@ -1746,7 +1744,7 @@ class smb(connection): sids = list() for i in range(so_far, so_far + sids_to_check): - sids.append(domain_sid + "-%d" % i) + sids.append(f"{domain_sid}-{i:d}") try: lsat.hLsarLookupSids( dce, policy_handle, sids, lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta diff --git a/cme/protocols/smb/db_navigator.py b/cme/protocols/smb/db_navigator.py index 87a83cd4..fcdfd0f7 100644 --- a/cme/protocols/smb/db_navigator.py +++ b/cme/protocols/smb/db_navigator.py @@ -175,16 +175,11 @@ class navigator(DatabaseNavigator): data = [["ShareID", "Name", "Remark"], [share_id, name, remark]] print_table(data, title="Share") host = self.db.get_hosts(filter_term=host_id)[0] - data = [["HostID", "IP", "Hostname", "Domain", "OS", "DC"]] + data = [ + ["HostID", "IP", "Hostname", "Domain", "OS", "DC"], + [host[0], host[1], host[2], host[3], host[4], host[5]], + ] - host_id = host[0] - ip = host[1] - hostname = host[2] - domain = host[3] - os = host[4] - dc = host[5] - - data.append([host_id, ip, hostname, domain, os, dc]) print_table(data, title="Share Location") if users_r_access: @@ -194,12 +189,7 @@ class navigator(DatabaseNavigator): creds = self.db.get_credentials(filter_term=userid) for cred in creds: - cred_id = cred[0] - domain = cred[1] - username = cred[2] - password = cred[3] - credtype = cred[4] - data.append([cred_id, credtype, domain, username, password]) + data.append([cred[0], cred[4], cred[1], cred[2], cred[3]]) print_table(data, title="Users(s) with Read Access") if users_w_access: @@ -209,13 +199,7 @@ class navigator(DatabaseNavigator): creds = self.db.get_credentials(filter_term=userid) for cred in creds: - cred_id = cred[0] - domain = cred[1] - username = cred[2] - password = cred[3] - credtype = cred[4] - - data.append([cred_id, credtype, domain, username, password]) + data.append([cred[0], cred[4], cred[1], cred[2], cred[3]]) print_table(data, title="Users(s) with Write Access") def help_shares(self): @@ -251,23 +235,15 @@ class navigator(DatabaseNavigator): ] for group in groups: - group_id = group[0] - domain = group[1] - name = group[2] - rid = group[3] - members = len(self.db.get_group_relations(group_id=group_id)) - ad_members = group[4] - last_query_time = group[5] - data.append( [ - group_id, - domain, - name, - rid, - members, - ad_members, - last_query_time, + group[0], + group[1], + group[2], + group[3], + len(self.db.get_group_relations(group_id=group_id)), + group[4], + group[5], ] ) print_table(data, title="Group") @@ -290,21 +266,14 @@ class navigator(DatabaseNavigator): creds = self.db.get_credentials(filter_term=userid) for cred in creds: - cred_id = cred[0] - domain = cred[1] - username = cred[2] - password = cred[3] - credtype = cred[4] - pillaged_from = cred[5] - data.append( [ - cred_id, - credtype, - pillaged_from, - domain, - username, - password, + cred[0], + cred[4], + cred[5], + cred[1], + cred[2], + cred[3], ] ) print_table(data, title="Member(s)") @@ -402,13 +371,8 @@ class navigator(DatabaseNavigator): creds = self.db.get_credentials(filter_term=cred_id) for cred in creds: - cred_id = cred[0] - domain = cred[1] - username = cred[2] - password = cred[3] - credtype = cred[4] - # pillaged_from = cred[5] - data.append([cred_id, credtype, domain, username, password]) + data.append([cred[0], cred[4], cred[1], cred[2], cred[3]]) + print_table(data, title="Credential(s) with Admin Access") def help_hosts(self): @@ -632,17 +596,9 @@ class navigator(DatabaseNavigator): cred_id_list = [] for cred in creds: - cred_id = cred[0] - cred_id_list.append(cred_id) - domain = cred[1] - username = cred[2] - password = cred[3] - credtype = cred[4] - pillaged_from = cred[5] + cred_id_list.append(cred[0]) + data.append([cred[0], cred[4], cred[5], cred[1], cred[2], cred[3]]) - data.append( - [cred_id, credtype, pillaged_from, domain, username, password] - ) print_table(data, title="Credential(s)") data = [["GroupID", "Domain", "Name"]] @@ -670,13 +626,8 @@ class navigator(DatabaseNavigator): hosts = self.db.get_hosts(host_id) for host in hosts: - host_id = host[0] - ip = host[1] - hostname = host[2] - domain = host[3] - os = host[4] + data.append([host[0], host[1], host[2], host[3], host[4]]) - data.append([host_id, ip, hostname, domain, os]) print_table(data, title="Admin Access to Host(s)") def help_creds(self): @@ -720,7 +671,7 @@ class navigator(DatabaseNavigator): """ Tab-complete 'hosts' commands. """ - commands = ["add", "remove", "dc"] + commands = ("add", "remove", "dc") mline = line.partition(" ")[2] offs = len(mline) - len(text) @@ -730,7 +681,7 @@ class navigator(DatabaseNavigator): """ Tab-complete 'creds' commands. """ - commands = ["add", "remove", "hash", "plaintext"] + commands = ("add", "remove", "hash", "plaintext") mline = line.partition(" ")[2] offs = len(mline) - len(text) diff --git a/cme/protocols/smb/firefox.py b/cme/protocols/smb/firefox.py index fde19504..9942ef97 100644 --- a/cme/protocols/smb/firefox.py +++ b/cme/protocols/smb/firefox.py @@ -31,7 +31,7 @@ class FirefoxTriage: firefox_generic_path = "Users\\{}\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles" share = "C$" - false_positive = [ + false_positive = ( ".", "..", "desktop.ini", @@ -39,7 +39,7 @@ class FirefoxTriage: "Default", "Default User", "All Users", - ] + ) def __init__(self, target, logger, conn: DPLootSMBConnection = None): self.target = target @@ -127,23 +127,21 @@ class FirefoxTriage: except Exception as e: if "STATUS_OBJECT_PATH_NOT_FOUND" in str(e): continue - print(e) - self.logger.debug(e) + self.logger.exception(e) return firefox_data def get_login_data(self, logins_data): - logins = [] json_logins = json.loads(logins_data) if "logins" not in json_logins: - return logins # No logins key in logins.json file - for row in json_logins["logins"]: - logins.append( - ( - self.decode_login_data(row["encryptedUsername"]), - self.decode_login_data(row["encryptedPassword"]), - row["hostname"], - ) + return [] # No logins key in logins.json file + logins = [ + ( + self.decode_login_data(row["encryptedUsername"]), + self.decode_login_data(row["encryptedPassword"]), + row["hostname"], ) + for row in json_logins["logins"] + ] return logins def get_key(self, key4_data, master_password=b""): @@ -204,10 +202,10 @@ class FirefoxTriage: directories = self.conn.listPath( shareName=self.share, path=ntpath.normpath(users_dir_path) ) + for d in directories: if d.get_longname() not in self.false_positive and d.is_directory() > 0: users.append(d.get_longname()) - return users @staticmethod diff --git a/cme/protocols/smb/mmcexec.py b/cme/protocols/smb/mmcexec.py index 8bf89cda..8ffb0d89 100644 --- a/cme/protocols/smb/mmcexec.py +++ b/cme/protocols/smb/mmcexec.py @@ -28,7 +28,7 @@ # import logging -import os +from os.path import join as path_join from time import sleep from cme.helpers.misc import gen_random_string @@ -241,7 +241,7 @@ class MMCEXEC: while True: try: with open( - os.path.join("/tmp", "cme_hosted", self.__output), "r" + path_join("/tmp", "cme_hosted", self.__output), "r" ) as output: self.output_callback(output.read()) break diff --git a/cme/protocols/smb/smbexec.py b/cme/protocols/smb/smbexec.py index fa08437a..bb94a064 100755 --- a/cme/protocols/smb/smbexec.py +++ b/cme/protocols/smb/smbexec.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- import os +from os.path import join as path_join from time import sleep from impacket.dcerpc.v5 import transport, scmr from cme.helpers.misc import gen_random_string @@ -120,9 +121,7 @@ class SMBEXEC: else: command = self.__shell + data - with open( - os.path.join("/tmp", "cme_hosted", self.__batchFile), "w" - ) as batch_file: + with open(path_join("/tmp", "cme_hosted", self.__batchFile), "w") as batch_file: batch_file.write(command) self.logger.debug("Hosting batch file with command: " + command) @@ -168,7 +167,7 @@ class SMBEXEC: sleep(2) pass else: - logger.debug(e) + self.logger.debug(e) pass self.__smbconnection.deleteFile(self.__share, self.__output) @@ -187,9 +186,7 @@ class SMBEXEC: else: command = self.__shell + data - with open( - os.path.join("/tmp", "cme_hosted", self.__batchFile), "w" - ) as batch_file: + with open(path_join("/tmp", "cme_hosted", self.__batchFile), "w") as batch_file: batch_file.write(command) self.logger.debug("Hosting batch file with command: " + command) @@ -227,7 +224,7 @@ class SMBEXEC: while True: try: with open( - os.path.join("/tmp", "cme_hosted", self.__output), "rb" + path_join("/tmp", "cme_hosted", self.__output), "rb" ) as output: self.output_callback(output.read()) break diff --git a/cme/servers/smb.py b/cme/servers/smb.py index 57da9355..2befa078 100755 --- a/cme/servers/smb.py +++ b/cme/servers/smb.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- import threading +from threading import enumerate from sys import exit from impacket import smbserver @@ -46,7 +47,7 @@ class CMESMBServer(threading.Thread): def shutdown(self): # TODO: should fine the proper way # make sure all the threads are killed - for thread in threading.enumerate(): + for thread in enumerate(): if thread.is_alive(): try: self._stop()