diff --git a/cme/cmedb.py b/cme/cmedb.py index ab9fdc41..366cb9a3 100644 --- a/cme/cmedb.py +++ b/cme/cmedb.py @@ -16,12 +16,12 @@ requests.packages.urllib3.disable_warnings() class CMEDatabaseNavigator(cmd.Cmd): - def __init__(self): + def __init__(self, db_path): cmd.Cmd.__init__(self) self.prompt = 'cmedb > ' try: # set the database connection to autocommit w/ isolation level - conn = sqlite3.connect('data/cme.db', check_same_thread=False) + conn = sqlite3.connect(db_path, check_same_thread=False) conn.text_factory = str conn.isolation_level = None self.db = CMEDatabase(conn) @@ -300,15 +300,18 @@ class CMEDatabaseNavigator(cmd.Cmd): def main(): parser = argparse.ArgumentParser() - parser.add_argument("path", nargs='?', type=str, default='data/cme.db', help="path to CME database (default: data/cme.db)") + parser.add_argument("path", nargs='?', type=str, default=None, help="path to CME database (default: data/cme.db)") args = parser.parse_args() - if not os.path.exists(args.path): + db_path = os.path.join(os.path.expanduser('~/.cme'), 'cme.db') + + if args.path: + db_path = os.path.expanduser(args.path) print 'Path to CME database invalid' sys.exit(1) try: - cmedbnav = CMEDatabaseNavigator() + cmedbnav = CMEDatabaseNavigator(db_path) cmedbnav.cmdloop() except KeyboardInterrupt: pass \ No newline at end of file diff --git a/cme/cmeserver.py b/cme/cmeserver.py index 64914317..5bc336be 100644 --- a/cme/cmeserver.py +++ b/cme/cmeserver.py @@ -1,6 +1,9 @@ import BaseHTTPServer import threading import ssl +import os +import sys +from getpass import getuser from BaseHTTPServer import BaseHTTPRequestHandler from logging import getLogger from gevent import sleep @@ -36,7 +39,11 @@ class RequestHandler(BaseHTTPRequestHandler): class CMEServer(threading.Thread): - def __init__(self, module, context, srv_host, port, server_type='https'): + def __init__(self, module, context, logger, srv_host, port, server_type='https'): + + if port <= 1024 and os.geteuid() != 0: + logger.error("I'm sorry {}, I'm afraid I can't let you do that".format(getuser())) + sys.exit(1) try: threading.Thread.__init__(self) @@ -46,12 +53,19 @@ class CMEServer(threading.Thread): self.server.module = module self.server.context = context self.server.log = context.log + self.cert_path = os.path.join(os.path.expanduser('~/.cme'), 'cme.pem') if server_type == 'https': - self.server.socket = ssl.wrap_socket(self.server.socket, certfile='data/cme.pem', server_side=True) + self.server.socket = ssl.wrap_socket(self.server.socket, certfile=self.cert_path, server_side=True) except Exception as e: - print 'Error starting CME Server: {}'.format(e) + errno, message = e.args + if errno == 98 and message == 'Address already in use': + logger.error('Error starting CME server: the port is already in use, try specifying a diffrent port using --server-port') + else: + logger.error('Error starting CME server: {}'.format(message)) + + sys.exit(1) def base_server(self): return self.server diff --git a/cme/crackmapexec.py b/cme/crackmapexec.py index 8140117e..fb335f63 100644 --- a/cme/crackmapexec.py +++ b/cme/crackmapexec.py @@ -43,7 +43,7 @@ def main(): @pentestgeek's smbexec https://github.com/pentestgeek/smbexec {}: {} - {}: {} + {}: {} """.format(highlight('Version', 'red'), highlight(VERSION), highlight('Codename', 'red'), @@ -63,8 +63,8 @@ def main(): 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('--module-info', action='store_true', dest='module_info', help='Display module info') - parser.add_argument('--list-modules', action='store_true', help='List available modules') + 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)') @@ -190,14 +190,16 @@ def main(): modules = loader.get_modules() if args.list_modules: - for module in modules: - print module + for m in modules: + logger.info('{:<20} {}'.format(m, modules[m]['description'])) - elif args.modules: - for module in modules.keys(): - if args.module.lower() == module.lower(): - module, context, server = loader.init_module(modules[module]['path']) - break + 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: ''' diff --git a/cme/credentials/secretsdump.py b/cme/credentials/secretsdump.py index e46d9430..63775e97 100644 --- a/cme/credentials/secretsdump.py +++ b/cme/credentials/secretsdump.py @@ -7,6 +7,7 @@ from cme.credentials.lsa import LSASecrets from cme.credentials.ntds import NTDSHashes from impacket.dcerpc.v5.rpcrt import DCERPCException import traceback +import os import logging class DumpSecrets: @@ -28,7 +29,7 @@ class DumpSecrets: self.__history = False self.__noLMHash = True self.__isRemote = True - self.__outputFileName = 'logs/{}_{}'.format(connection.hostname, connection.host) + self.__outputFileName = os.path.join(os.path.expanduser('~/.cme'), 'logs/{}_{}'.format(connection.hostname, connection.host)) self.__doKerberos = False self.__justDC = False self.__justDCNTLM = False diff --git a/cme/helpers.py b/cme/helpers.py index 53a74d62..86552a0e 100644 --- a/cme/helpers.py +++ b/cme/helpers.py @@ -1,6 +1,8 @@ import random import string import re +import cme +import os from base64 import b64encode from termcolor import colored @@ -14,6 +16,14 @@ def validate_ntlm(data): else: return False +def get_ps_script(path): + return os.path.join(os.path.dirname(cme.__file__), 'data', 'PowerSploit', path) + +def write_log(data, log_name): + logs_dir = os.path.join(os.path.expanduser('~/.cme'), 'logs') + with open(os.path.join(logs_dir, log_name), 'w') as mimikatz_output: + mimikatz_output.write(data) + def obfs_ps_script(script, function_name=None): """ Strip block comments, line comments, empty lines, verbose statements, diff --git a/cme/moduleloader.py b/cme/moduleloader.py index 9b724436..1083d109 100644 --- a/cme/moduleloader.py +++ b/cme/moduleloader.py @@ -2,7 +2,6 @@ import imp import os import sys import cme -from getpass import getuser from logging import getLogger from cme.context import Context from cme.logger import CMEAdapter @@ -24,9 +23,13 @@ class ModuleLoader: self.logger.error('{} missing the name variable'.format(module_path)) module_error = True + elif not hasattr(module, 'description'): + self.logger.error('{} missing the description variable'.format(module_path)) + module_error = True + elif not hasattr(module, 'options'): self.logger.error('{} missing the options function'.format(module_path)) - module_error = True + module_error = True elif not hasattr(module, 'on_login') and not (module, 'on_admin_login'): self.logger.error('{} missing the on_login/on_admin_login function(s)'.format(module_path)) @@ -48,19 +51,19 @@ class ModuleLoader: modules_path = os.path.join(os.path.dirname(cme.__file__), 'modules') for module in os.listdir(modules_path): - if module[-3:] == '.py': + if module[-3:] == '.py' and module != 'example_module.py': module_path = os.path.join(modules_path, module) m = self.load_module(os.path.join(modules_path, module)) if m: - modules[m.name] = {'path': module, 'description': m.__doc__, 'options': m.options.__doc__} + modules[m.name] = {'path': module_path, 'description': m.description, 'options': m.options.__doc__} modules_path = os.path.join(self.cme_path, 'modules') for module in os.listdir(modules_path): - if module[-3:] == '.py': + if module[-3:] == '.py' and module != 'example_module.py': module_path = os.path.join(modules_path, module) m = self.load_module(module_path) if m: - modules[m.name] = {'path': module_path, 'description': m.__doc__, 'options': m.options.__doc__} + modules[m.name] = {'path': module_path, 'description': m.description, 'options': m.options.__doc__} return modules @@ -90,13 +93,9 @@ class ModuleLoader: args.server = getattr(module, 'required_server') if not self.server_port: - if self.args.server_port <= 1024 and os.geteuid() is not 0: - self.logger.error("I'm sorry {}, I'm afraid I can't let you do that".format(getuser())) - sys.exit(1) - self. server_port = self.args.server_port - server = CMEServer(module, context, self.args.server_host, self.server_port, self.args.server) + server = CMEServer(module, context, self.logger, self.args.server_host, self.server_port, self.args.server) server.start() return module, context, server \ No newline at end of file diff --git a/cme/modules/com_exec.py b/cme/modules/com_exec.py index de97a212..87cadc9e 100644 --- a/cme/modules/com_exec.py +++ b/cme/modules/com_exec.py @@ -15,6 +15,8 @@ class CMEModule: name='com_exec' + description = 'Executes a command using a COM scriptlet to bypass whitelisting' + required_server='http' def options(self, context, module_options): diff --git a/cme/modules/empire_agent_exec.py b/cme/modules/empire_agent_exec.py index 6c87256d..57bc59f9 100644 --- a/cme/modules/empire_agent_exec.py +++ b/cme/modules/empire_agent_exec.py @@ -11,7 +11,9 @@ class CMEModule: Module by @byt3bl33d3r ''' - name='Empire_Exec' + name='empire_exec' + + description = "Uses Empire's RESTful API to generate a launcher for the specified listener and executes it" def options(self, context, module_options): ''' diff --git a/cme/modules/example_module.py b/cme/modules/example_module.py index 04a927df..d5b1ca44 100644 --- a/cme/modules/example_module.py +++ b/cme/modules/example_module.py @@ -5,7 +5,9 @@ class CMEModule: ''' - name = 'Example' + name = 'example module' + + description = 'Something Something' def options(self, context, module_options): '''Required. Module options get parsed here. Additionally, put the modules usage here as well''' diff --git a/cme/modules/get_computers.py b/cme/modules/get_computers.py index 815c311f..f25234ce 100644 --- a/cme/modules/get_computers.py +++ b/cme/modules/get_computers.py @@ -1,4 +1,4 @@ -from cme.helpers import create_ps_command, obfs_ps_script +from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script, write_log from StringIO import StringIO from datetime import datetime @@ -8,7 +8,9 @@ class CMEModule: Module by @byt3bl33d3r ''' - name = 'GetComputers' + name = 'getcomputers' + + description = "Wrapper for PowerView's Get-NetGroup function" def options(self, context, module_options): ''' @@ -71,7 +73,7 @@ class CMEModule: request.send_response(200) request.end_headers() - with open('data/PowerSploit/Recon/PowerView.ps1', 'r') as ps_script: + with open(get_ps_script('Recon/PowerView.ps1'), 'r') as ps_script: ps_script = obfs_ps_script(ps_script.read()) request.wfile.write(ps_script) @@ -97,6 +99,5 @@ class CMEModule: print_post_data(data) log_name = 'Computers-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S")) - with open('logs/' + log_name, 'w') as log_file: - log_file.write(data) + write_log(data, log_name) context.log.info("Saved output to {}".format(log_name)) \ No newline at end of file diff --git a/cme/modules/get_group_members.py b/cme/modules/get_group_members.py index 9b855dae..5fce16ac 100644 --- a/cme/modules/get_group_members.py +++ b/cme/modules/get_group_members.py @@ -1,4 +1,4 @@ -from cme.helpers import create_ps_command, obfs_ps_script +from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script, write_log from StringIO import StringIO from datetime import datetime @@ -8,7 +8,9 @@ class CMEModule: Module by @byt3bl33d3r ''' - name = 'GetGroupMembers' + name = 'getgroupmembers' + + description = "Wrapper for PowerView's Get-NetGroupMember function" def options(self, context, module_options): ''' @@ -63,7 +65,7 @@ class CMEModule: request.send_response(200) request.end_headers() - with open('data/PowerSploit/Recon/PowerView.ps1', 'r') as ps_script: + with open(get_ps_script('Recon/PowerView.ps1'), 'r') as ps_script: ps_script = obfs_ps_script(ps_script.read()) request.wfile.write(ps_script) @@ -89,6 +91,5 @@ class CMEModule: print_post_data(data) log_name = 'GroupMembers-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S")) - with open('logs/' + log_name, 'w') as log_file: - log_file.write(data) + write_log(data, log_name) context.log.info("Saved output to {}".format(log_name)) \ No newline at end of file diff --git a/cme/modules/get_groups.py b/cme/modules/get_groups.py index dc94fb9b..5ce0fef6 100644 --- a/cme/modules/get_groups.py +++ b/cme/modules/get_groups.py @@ -1,4 +1,4 @@ -from cme.helpers import create_ps_command, obfs_ps_script +from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script, write_log from StringIO import StringIO from datetime import datetime @@ -8,7 +8,9 @@ class CMEModule: Module by @byt3bl33d3r ''' - name = 'GetGroups' + name = 'getgroups' + + description = "Wrapper for PowerView's Get-NetGroup function" def options(self, context, module_options): ''' @@ -63,7 +65,7 @@ class CMEModule: request.send_response(200) request.end_headers() - with open('data/PowerSploit/Recon/PowerView.ps1', 'r') as ps_script: + with open(get_ps_script('Recon/PowerView.ps1'), 'r') as ps_script: ps_script = obfs_ps_script(ps_script.read()) request.wfile.write(ps_script) @@ -89,6 +91,5 @@ class CMEModule: print_post_data(data) log_name = 'Groups-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S")) - with open('logs/' + log_name, 'w') as log_file: - log_file.write(data) + write_log(data, log_name) context.log.info("Saved output to {}".format(log_name)) \ No newline at end of file diff --git a/cme/modules/meterpreter_inject.py b/cme/modules/meterpreter_inject.py index 557cdc12..255eab6e 100644 --- a/cme/modules/meterpreter_inject.py +++ b/cme/modules/meterpreter_inject.py @@ -1,4 +1,4 @@ -from cme.helpers import gen_random_string, create_ps_command, obfs_ps_script +from cme.helpers import gen_random_string, create_ps_command, obfs_ps_script, get_ps_script from sys import exit class CMEModule: @@ -6,7 +6,9 @@ class CMEModule: Downloads the Meterpreter stager and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script Module by @byt3bl33d3r ''' - name = 'MetInject' + name = 'metinject' + + description = "Downloads the Meterpreter stager and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script" def options(self, context, module_options): ''' @@ -73,7 +75,7 @@ class CMEModule: request.send_response(200) request.end_headers() - with open('data/PowerSploit/CodeExecution/Invoke-Shellcode.ps1', 'r') as ps_script: + with open(get_ps_script('CodeExecution/Invoke-Shellcode.ps1'), 'r') as ps_script: ps_script = obfs_ps_script(ps_script.read(), self.obfs_name) request.wfile.write(ps_script) diff --git a/cme/modules/mimikatz.py b/cme/modules/mimikatz.py index 6ce30e1e..98cbdf70 100644 --- a/cme/modules/mimikatz.py +++ b/cme/modules/mimikatz.py @@ -1,4 +1,4 @@ -from cme.helpers import create_ps_command, obfs_ps_script, gen_random_string, validate_ntlm +from cme.helpers import create_ps_command, get_ps_script, obfs_ps_script, gen_random_string, validate_ntlm, write_log from datetime import datetime import re @@ -8,7 +8,9 @@ class CMEModule: Module by @byt3bl33d3r ''' - name = 'Mimikatz' + name = 'mimikatz' + + description = "Executes PowerSploit's Invoke-Mimikatz.ps1 script" def options(self, context, module_options): ''' @@ -53,7 +55,7 @@ class CMEModule: request.send_response(200) request.end_headers() - with open('data/PowerSploit/Exfiltration/Invoke-Mimikatz.ps1', 'r') as ps_script: + with open(get_ps_script('Exfiltration/Invoke-Mimikatz.ps1'), 'r') as ps_script: ps_script = obfs_ps_script(ps_script.read(), self.obfs_name) request.wfile.write(ps_script) @@ -213,6 +215,5 @@ class CMEModule: context.log.highlight('{}\\{}:{}'.format(domain, username, password)) log_name = 'Mimikatz-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S")) - with open('logs/' + log_name, 'w') as mimikatz_output: - mimikatz_output.write(data) + write_log(data, log_name) context.log.info("Saved Mimikatz's output to {}".format(log_name)) \ No newline at end of file diff --git a/cme/modules/pe_dll_inject.py b/cme/modules/pe_dll_inject.py index bf5bf39f..f5d89693 100644 --- a/cme/modules/pe_dll_inject.py +++ b/cme/modules/pe_dll_inject.py @@ -1,4 +1,4 @@ -from cme.helpers import gen_random_string, create_ps_command, obfs_ps_script +from cme.helpers import gen_random_string, create_ps_command, obfs_ps_script, get_ps_script from sys import exit import os @@ -7,7 +7,9 @@ class CMEModule: Downloads the specified DLL/EXE and injects it into memory using PowerSploit's Invoke-ReflectivePEInjection.ps1 script Module by @byt3bl33d3r ''' - name = 'PEInject' + name = 'peinject' + + description = "Downloads the specified DLL/EXE and injects it into memory using PowerSploit's Invoke-ReflectivePEInjection.ps1 script" def options(self, context, module_options): ''' @@ -64,7 +66,7 @@ class CMEModule: request.send_response(200) request.end_headers() - with open('data/PowerSploit/CodeExecution/Invoke-ReflectivePEInjection.ps1', 'r') as ps_script: + with open(get_ps_script('CodeExecution/Invoke-ReflectivePEInjection.ps1'), 'r') as ps_script: ps_script = obfs_ps_script(ps_script.read(), self.obfs_name) request.wfile.write(ps_script) diff --git a/cme/modules/shellcode_inject.py b/cme/modules/shellcode_inject.py index 6440ef3a..89a147b9 100644 --- a/cme/modules/shellcode_inject.py +++ b/cme/modules/shellcode_inject.py @@ -1,5 +1,5 @@ import os -from cme.helpers import gen_random_string, create_ps_command, obfs_ps_script +from cme.helpers import gen_random_string, create_ps_command, obfs_ps_script, get_ps_script from sys import exit class CMEModule: @@ -7,7 +7,9 @@ class CMEModule: Downloads the specified raw shellcode and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script Module by @byt3bl33d3r ''' - name = 'ShellInject' + name = 'shellinject' + + description = "Downloads the specified raw shellcode and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script" def options(self, context, module_options): ''' @@ -56,7 +58,7 @@ class CMEModule: request.send_response(200) request.end_headers() - with open('data/PowerSploit/CodeExecution/Invoke-Shellcode.ps1' ,'r') as ps_script: + with open(get_ps_script('CodeExecution/Invoke-Shellcode.ps1') ,'r') as ps_script: ps_script = obfs_ps_script(ps_script.read(), self.obfs_name) request.wfile.write(ps_script) diff --git a/cme/modules/token_rider.py b/cme/modules/token_rider.py index 4c6cfcce..a1591c80 100644 --- a/cme/modules/token_rider.py +++ b/cme/modules/token_rider.py @@ -1,5 +1,5 @@ from StringIO import StringIO -from cme.helpers import create_ps_command, gen_random_string, obfs_ps_script +from cme.helpers import create_ps_command, gen_random_string, obfs_ps_script, get_ps_script from base64 import b64encode import sys import os @@ -16,7 +16,9 @@ class CMEModule: Module by @byt3bl33d3r ''' - name = 'TokenRider' + name = 'tokenrider' + + description = 'Allows for automatic token enumeration, impersonation and mass lateral spread using privileges instead of dumped credentials' def options(self, context, module_options): ''' @@ -124,7 +126,7 @@ class CMEModule: request.send_response(200) request.end_headers() - with open('data/PowerSploit/Exfiltration/Invoke-TokenManipulation.ps1', 'r') as ps_script: + with open(get_ps_script('Exfiltration/Invoke-TokenManipulation.ps1'), 'r') as ps_script: ps_script = obfs_ps_script(ps_script.read(), self.obfs_name) request.wfile.write(ps_script) diff --git a/cme/modules/tokens.py b/cme/modules/tokens.py index 101a68bd..592aa7d9 100644 --- a/cme/modules/tokens.py +++ b/cme/modules/tokens.py @@ -1,4 +1,4 @@ -from cme.helpers import create_ps_command, obfs_ps_script, gen_random_string +from cme.helpers import create_ps_command, obfs_ps_script, gen_random_string, get_ps_script from datetime import datetime from StringIO import StringIO import os @@ -10,7 +10,9 @@ class CMEModule: Module by @byt3bl33d3r ''' - name = 'Tokens' + name = 'tokens' + + description = "Enumerates available tokens using Powersploit's Invoke-TokenManipulation" def options(self, context, module_options): ''' @@ -67,7 +69,7 @@ class CMEModule: request.send_response(200) request.end_headers() - with open('data/PowerSploit/Exfiltration/Invoke-TokenManipulation.ps1', 'r') as ps_script: + with open(get_ps_script('Exfiltration/Invoke-TokenManipulation.ps1'), 'r') as ps_script: ps_script = obfs_ps_script(ps_script.read(), self.obfs_name) request.wfile.write(ps_script) diff --git a/setup.py b/setup.py index b53bd956..ea69ff1d 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup(name='crackmapexec', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python :: 2.7', ], - keywords='pentesting tool security windows smb active-directory', + keywords='pentesting security windows smb active-directory', url='http://github.com/byt3bl33d3r/CrackMapExec', author='byt3bl33d3r', author_email='byt3bl33d3r@gmail.com',