From 4ff034f366ea28e0dad0685e4847625e49e5ce9f Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 7 May 2017 21:16:18 -0600 Subject: [PATCH] Added enum_avproducts module, fixed module logging - Modules now do not print output of commands called from their protocol - Added the enum_avproducts module - Fixed the mimikatz_enum_vault_creds to not display creds with invalid passwords - Added an export command to the SMB protocols DB navigator (as suggested by @hatredshapedlikeaman) - Misc output fixes --- cme/cmedb.py | 2 +- cme/crackmapexec.py | 4 +-- cme/helpers/misc.py | 11 ++++++++ cme/logger.py | 19 ++++++++++++++ cme/modules/enum_avproducts.py | 30 ++++++++++++++++++++++ cme/modules/gpp_autologin.py | 5 ++++ cme/modules/gpp_password.py | 5 ++++ cme/modules/mimikatz_enum_vault_creds.py | 3 ++- cme/modules/slinky.py | 1 + cme/protocols/smb/db_navigator.py | 32 ++++++++++++++++++++++++ 10 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 cme/modules/enum_avproducts.py diff --git a/cme/cmedb.py b/cme/cmedb.py index 2c1f173e..69871034 100755 --- a/cme/cmedb.py +++ b/cme/cmedb.py @@ -102,7 +102,7 @@ class CMEDatabaseNavigator(cmd.Cmd): self.write_configfile() self.workspace = line - self.prompt = 'cmedb ({}) >'.format(line) + self.prompt = 'cmedb ({}) > '.format(line) def do_exit(self, line): sys.exit(0) diff --git a/cme/crackmapexec.py b/cme/crackmapexec.py index 59e4c5ad..ea5074d8 100755 --- a/cme/crackmapexec.py +++ b/cme/crackmapexec.py @@ -146,12 +146,12 @@ def main(): exit(1) if getattr(module, 'opsec_safe') is False: - ans = raw_input(highlight('[!] Module is not opsec safe, are you sure you want to run this? [Y/n]', 'red')) + ans = raw_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 = raw_input(highlight("[!] Running this module on multiple hosts doesn't really make any sense, are you sure you want to continue? [Y/n]", 'red')) + ans = raw_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) diff --git a/cme/helpers/misc.py b/cme/helpers/misc.py index c4dfc779..b0d9a1e7 100755 --- a/cme/helpers/misc.py +++ b/cme/helpers/misc.py @@ -1,6 +1,7 @@ import random import string import re +import inspect def gen_random_string(length=10): return ''.join(random.sample(string.ascii_letters, int(length))) @@ -11,3 +12,13 @@ def validate_ntlm(data): return True else: return False + +def called_from_cmd_args(): + for stack in inspect.stack(): + if stack[3] == 'print_host_info': + return True + if stack[3] == 'plaintext_login' or stack[3] == 'hash_login': + return True + if stack[3] == 'call_cmd_args': + return True + return False diff --git a/cme/logger.py b/cme/logger.py index 83100f08..ac505660 100755 --- a/cme/logger.py +++ b/cme/logger.py @@ -1,6 +1,7 @@ import logging import sys import re +from cme.helpers.misc import called_from_cmd_args from termcolor import colored from datetime import datetime @@ -59,6 +60,12 @@ class CMEAdapter(logging.LoggerAdapter): msg), kwargs def info(self, msg, *args, **kwargs): + try: + if 'protocol' in self.extra.keys() and not called_from_cmd_args(): + return + except AttributeError: + pass + msg, kwargs = self.process(u'{} {}'.format(colored("[*]", 'blue', attrs=['bold']), msg), kwargs) self.logger.info(msg, *args, **kwargs) @@ -70,10 +77,22 @@ class CMEAdapter(logging.LoggerAdapter): pass def success(self, msg, *args, **kwargs): + try: + if 'protocol' in self.extra.keys() and not called_from_cmd_args(): + return + except AttributeError: + pass + msg, kwargs = self.process(u'{} {}'.format(colored("[+]", 'green', attrs=['bold']), msg), kwargs) self.logger.info(msg, *args, **kwargs) def highlight(self, msg, *args, **kwargs): + try: + if 'protocol' in self.extra.keys() and not called_from_cmd_args(): + return + except AttributeError: + pass + msg, kwargs = self.process(u'{}'.format(colored(msg, 'yellow', attrs=['bold'])), kwargs) self.logger.info(msg, *args, **kwargs) diff --git a/cme/modules/enum_avproducts.py b/cme/modules/enum_avproducts.py new file mode 100644 index 00000000..87f95daa --- /dev/null +++ b/cme/modules/enum_avproducts.py @@ -0,0 +1,30 @@ +class CMEModule: + ''' + Uses WMI to gather information on all endpoint protection solutions installed on the the remote host(s) + Module by @byt3bl33d3r + + ''' + + name = 'enum_avproducts' + description = 'Gathers information on all endpoint protection solutions installed on the the remote host(s) via WMI' + supported_protocols = ['smb'] + opsec_safe= True + multiple_hosts = True + + def options(self, context, module_options): + pass + + def on_admin_login(self, context, connection): + output = connection.wmi('Select * From AntiSpywareProduct', 'root\\SecurityCenter2') + if output: + context.log.success('Found Anti-Spyware product:') + for entry in output: + for k,v in entry.iteritems(): + context.log.highlight('{} => {}'.format(k,v['value'])) + + output = connection.wmi('Select * from AntiVirusProduct', 'root\\SecurityCenter2') + if output: + context.log.success('Found Anti-Virus product:') + for entry in output: + for k,v in entry.iteritems(): + context.log.highlight('{} => {}'.format(k,v['value'])) \ No newline at end of file diff --git a/cme/modules/gpp_autologin.py b/cme/modules/gpp_autologin.py index b33a197f..fe5f9311 100644 --- a/cme/modules/gpp_autologin.py +++ b/cme/modules/gpp_autologin.py @@ -22,9 +22,14 @@ class CMEModule: for share in shares: if share['name'] == 'SYSVOL' and 'READ' in share['access']: + context.log.success('Found SYSVOL share') + context.log.info('Searching for Registry.xml') + paths = connection.spider('SYSVOL', pattern=['Registry.xml']) for path in paths: + context.log.info('Found {}'.format(path)) + buf = StringIO() connection.conn.getFile('SYSVOL', path, buf.write) xml = ET.fromstring(buf.getvalue()) diff --git a/cme/modules/gpp_password.py b/cme/modules/gpp_password.py index 5134812f..96d21550 100644 --- a/cme/modules/gpp_password.py +++ b/cme/modules/gpp_password.py @@ -25,9 +25,14 @@ class CMEModule: for share in shares: if share['name'] == 'SYSVOL' and 'READ' in share['access']: + context.log.success('Found SYSVOL share') + context.log.info('Searching for potential XML files containing passwords') + paths = connection.spider('SYSVOL', pattern=['Groups.xml','Services.xml','Scheduledtasks.xml','DataSources.xml','Printers.xml','Drives.xml']) for path in paths: + context.log.info('Found {}'.format(path)) + buf = StringIO() connection.conn.getFile('SYSVOL', path, buf.write) xml = ET.fromstring(buf.getvalue()) diff --git a/cme/modules/mimikatz_enum_vault_creds.py b/cme/modules/mimikatz_enum_vault_creds.py index 3355b52a..351a4485 100644 --- a/cme/modules/mimikatz_enum_vault_creds.py +++ b/cme/modules/mimikatz_enum_vault_creds.py @@ -76,7 +76,8 @@ class CMEModule: user = buf[i+1].split(':', 1)[1].strip().replace('[STRING]', '') passw = buf[i+4].split(':', 1)[1].strip().replace('[STRING]', '') - creds.append({'url': url, 'user': user, 'passw': passw}) + if '[BYTE*]' not in passw: + creds.append({'url': url, 'user': user, 'passw': passw}) i += 1 except: diff --git a/cme/modules/slinky.py b/cme/modules/slinky.py index dee85b4d..455dc94f 100644 --- a/cme/modules/slinky.py +++ b/cme/modules/slinky.py @@ -49,6 +49,7 @@ class CMEModule: shares = connection.shares() for share in shares: if 'WRITE' in share['access'] and share['name'] not in ['C$', 'ADMIN$']: + context.log.success('Found writable share: {}'.format(share['name'])) if not self.cleanup: with open(self.lnk_path, 'rb') as lnk: try: diff --git a/cme/protocols/smb/db_navigator.py b/cme/protocols/smb/db_navigator.py index b5846d40..4e675aac 100644 --- a/cme/protocols/smb/db_navigator.py +++ b/cme/protocols/smb/db_navigator.py @@ -1,4 +1,5 @@ import requests +import os from requests import ConnectionError #The following disables the InsecureRequests warning and the 'Starting new HTTPS connection' log message from requests.packages.urllib3.exceptions import InsecureRequestWarning @@ -26,6 +27,28 @@ class navigator(cmd.Cmd): def do_exit(self, line): exit(0) + def do_export(self, line): + if not line: + return + + line = line.split() + + if len(line) < 3: + return + + if line[0].lower() == 'creds': + if line[1].lower() == 'plaintext': + creds = self.db.get_credentials(credtype="plaintext") + elif line[1].lower()== 'hashes': + creds = self.db.get_credentials(credtype="hash") + else: + return + + with open(os.path.expanduser(line[2]), 'w') as export_file: + for cred in creds: + _,_,_,password,_,_ = cred + export_file.write('{}\n'.format(password)) + def do_import(self, line): if not line: @@ -437,3 +460,12 @@ class navigator(cmd.Cmd): mline = line.partition(' ')[2] offs = len(mline) - len(text) return [s[offs:] for s in commands if s.startswith(mline)] + + def complete_export(self, text, line, begidx, endidx): + "Tab-complete 'creds' commands." + + commands = [ "creds"] + + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in commands if s.startswith(mline)]