From e7fcea08518a75790e9d5928bdef0f034b6cc4eb Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Fri, 17 Mar 2023 09:07:21 -0400 Subject: [PATCH] feat(modules): allow multiple modules to be ran --- cme/cli.py | 2 +- cme/connection.py | 36 ++++++++++++----------- cme/crackmapexec.py | 70 ++++++++++++++++++++++++--------------------- 3 files changed, 58 insertions(+), 50 deletions(-) diff --git a/cme/cli.py b/cme/cli.py index 672ddc40..50deb2bd 100755 --- a/cme/cli.py +++ b/cme/cli.py @@ -63,7 +63,7 @@ def gen_cli_args(): module_parser = argparse.ArgumentParser(add_help=False) mgroup = module_parser.add_mutually_exclusive_group() - mgroup.add_argument("-M", "--module", metavar='MODULE', help='module to use') + mgroup.add_argument("-M", "--module", action='append', metavar='MODULE', help='module to use') #mgroup.add_argument('-MC','--module-chain', metavar='CHAIN_COMMAND', help='Payload module chain command string to run') module_parser.add_argument('-o', metavar='MODULE_OPTION', nargs='+', default=[], dest='module_options', help='module options') module_parser.add_argument('-L', '--list-modules', action='store_true', help='list available modules') diff --git a/cme/connection.py b/cme/connection.py index a0190b12..b84775a6 100755 --- a/cme/connection.py +++ b/cme/connection.py @@ -119,28 +119,30 @@ class connection(object): r = getattr(self, k)() def call_modules(self): - module_logger = CMEAdapter(extra={ - 'module': self.module.name.upper(), - 'host': self.host, - 'port': self.args.port, - 'hostname': self.hostname - }) + for module in self.module: + logging.debug(f"Loading module {module}") + module_logger = CMEAdapter(extra={ + 'module': module.name.upper(), + 'host': self.host, + 'port': self.args.port, + 'hostname': self.hostname + }) - context = Context(self.db, module_logger, self.args) - context.localip = self.local_ip + context = Context(self.db, module_logger, self.args) + context.localip = self.local_ip - if hasattr(self.module, 'on_request') or hasattr(self.module, 'has_response'): - self.server.connection = self - self.server.context.localip = self.local_ip + if hasattr(module, 'on_request') or hasattr(module, 'has_response'): + self.server.connection = self + self.server.context.localip = self.local_ip - if hasattr(self.module, 'on_login'): - self.module.on_login(context, self) + if hasattr(module, 'on_login'): + module.on_login(context, self) - if self.admin_privs and hasattr(self.module, 'on_admin_login'): - self.module.on_admin_login(context, self) + if self.admin_privs and hasattr(module, 'on_admin_login'): + module.on_admin_login(context, self) - if (not hasattr(self.module, 'on_request') and not hasattr(self.module, 'has_response')) and hasattr(self.module, 'on_shutdown'): - self.module.on_shutdown(context, self) + if (not hasattr(module, 'on_request') and not hasattr(module, 'has_response')) and hasattr(module,'on_shutdown'): + module.on_shutdown(context, self) def inc_failed_login(self, username): global global_failed_logins diff --git a/cme/crackmapexec.py b/cme/crackmapexec.py index 3008cd1e..91323ec0 100755 --- a/cme/crackmapexec.py +++ b/cme/crackmapexec.py @@ -241,55 +241,61 @@ def main(): if hasattr(args, 'module'): loader = module_loader(args, db, logger) - if args.list_modules: - modules = loader.get_modules() + modules = loader.get_modules() + if args.list_modules: for name, props in sorted(modules.items()): logger.info('{:<25} {}'.format(name, props['description'])) sys.exit(0) - elif args.module and args.show_module_options: - - modules = loader.get_modules() for name, props in modules.items(): if args.module.lower() == name.lower(): logger.info('{} module options:\n{}'.format(name, props['options'])) sys.exit(0) - elif args.module: - modules = loader.get_modules() - for name, props in modules.items(): - if args.module.lower() == name.lower(): - module = loader.init_module(props['path']) - setattr(protocol_object, 'module', module) - break + logging.debug(f"Modules to be Loaded: {args.module}, {type(args.module)}") + for m in args.module: + if m not in modules: + logger.error(f"Module not found: {m}") + exit(1) - if not module: - logger.error('Module not found') - exit(1) + logging.debug(f"Loading module {m} at path {modules[m]['path']}") + module = loader.init_module(modules[m]['path']) - if getattr(module, 'opsec_safe') is False: - ans = input(highlight('[!] Module is not opsec safe, are you sure you want to run this? [Y/n] ', 'red')) - if ans.lower() not in ['y', 'yes', '']: - sys.exit(1) + if not module.opsec_safe: + ans = input( + highlight('[!] Module is not opsec safe, are you sure you want to run this? [Y/n] ', 'red')) + if ans.lower() not in ['y', 'yes', '']: + sys.exit(1) - if getattr(module, 'multiple_hosts') is False and len(targets) > 1: - ans = input(highlight("[!] Running this module on multiple hosts doesn't really make any sense, are you sure you want to continue? [Y/n] ", 'red')) - if ans.lower() not in ['y', 'yes', '']: - sys.exit(1) + if not module.multiple_hosts and len(targets) > 1: + ans = input(highlight("[!] Running this module on multiple hosts doesn't really make any sense, are you sure you want to continue? [Y/n] ", 'red')) + if ans.lower() not in ['y', 'yes', '']: + sys.exit(1) - if hasattr(module, 'on_request') or hasattr(module, 'has_response'): + if hasattr(module, 'on_request') or hasattr(module, 'has_response'): + if hasattr(module, 'required_server'): + args.server = module.required_server - if hasattr(module, 'required_server'): - args.server = getattr(module, 'required_server') + if not args.server_port: + args.server_port = server_port_dict[args.server] - if not args.server_port: - args.server_port = server_port_dict[args.server] + # loading a module server multiple times will obviously fail + try: + context = Context(db, logger, args) + module_server = CMEServer(module, context, logger, args.server_host, args.server_port, args.server) + module_server.start() + protocol_object.server = module_server.server + except Exception as e: + logging.debug(f"Error loading module server for {module}: {e}") - context = Context(db, logger, args) - module_server = CMEServer(module, context, logger, args.server_host, args.server_port, args.server) - module_server.start() - setattr(protocol_object, 'server', module_server.server) + logging.debug(f"proto_object: {protocol_object}, type: {type(protocol_object)}") + logging.debug(f"proto object dir: {dir(protocol_object)}") + # get currently set modules, otherwise default to empty list + current_modules = getattr(protocol_object, 'module', []) + current_modules.append(module) + setattr(protocol_object, 'module', current_modules) + logging.debug(f"proto object module after adding: {protocol_object.module}") if hasattr(args, 'ntds') and args.ntds and not args.userntds: ans = input(highlight('[!] Dumping the ntds can crash the DC on Windows Server 2019. Use the option --user to dump a specific user safely [Y/n] ', 'red'))