diff --git a/.swm/AzD8XysWg1BBXCjCDkfq.swm b/.swm/AzD8XysWg1BBXCjCDkfq.swm index 5383db0b7..29ad78526 100644 --- a/.swm/AzD8XysWg1BBXCjCDkfq.swm +++ b/.swm/AzD8XysWg1BBXCjCDkfq.swm @@ -17,7 +17,7 @@ "type": "snippet", "path": "monkey/infection_monkey/config.py", "comments": [], - "firstLineNumber": 126, + "firstLineNumber": 124, "lines": [ " exploiter_classes = []", " system_info_collector_classes = []", @@ -33,19 +33,18 @@ "type": "snippet", "path": "monkey/infection_monkey/monkey.py", "comments": [], - "firstLineNumber": 159, + "firstLineNumber": 220, "lines": [ + " if not self._keep_running or not WormConfiguration.alive:", + " break", " ", - " if not self._keep_running or not WormConfiguration.alive:", - " break", - "*", - "* machines = self._network.get_victim_machines(", - "* max_find=WormConfiguration.victims_max_find,", - "* stop_callback=ControlClient.check_for_stop,", - "* )", - " is_empty = True", - " for machine in machines:", - " if ControlClient.check_for_stop():" + "* machines = self._network.get_victim_machines(", + "* max_find=WormConfiguration.victims_max_find,", + "* stop_callback=ControlClient.check_for_stop,", + "* )", + " is_empty = True", + " for machine in machines:", + " if ControlClient.check_for_stop():" ] }, { @@ -77,11 +76,11 @@ "symbols": {}, "file_version": "2.0.1", "meta": { - "app_version": "0.4.1-1", + "app_version": "0.4.9-1", "file_blobs": { - "monkey/infection_monkey/config.py": "ffdea551eb1ae2b65d4700db896c746771e7954c", - "monkey/infection_monkey/monkey.py": "c81a6251746e3af4e93eaa7d50af44d33debe05c", - "monkey/monkey_island/cc/services/config_schema/internal.py": "d03527b89c21dfb832a15e4f7d55f4027d83b453" + "monkey/infection_monkey/config.py": "0bede1c57949987f5c8025bd9b8f7aa29d02a6af", + "monkey/infection_monkey/monkey.py": "89d2fa8452dee70f6d2985a9bb452f0159ea8219", + "monkey/monkey_island/cc/services/config_schema/internal.py": "1ce1c864b1df332b65e16b4ce9ed533affd73f9c" } } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d3d3bb37..f4146a696 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Attempted to delete a directory when monkey config reset was called. #1054 - An errant space in the windows commands to run monkey manually. #1153 - gevent tracebacks in console output. #859 +- Crash and failure to run PBAs if max depth reached. #1374 ### Security - Address minor issues discovered by Dlint. #1075 diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index b71e27a5e..109110498 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -22,7 +22,6 @@ from infection_monkey.config import GUID, WormConfiguration from infection_monkey.network.info import check_internet_access, local_ips from infection_monkey.transport.http import HTTPConnectProxy from infection_monkey.transport.tcp import TcpProxy -from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException requests.packages.urllib3.disable_warnings() @@ -401,10 +400,8 @@ class ControlClient(object): and ControlClient.can_island_see_port(vulnerable_port) and WormConfiguration.started_on_island ): - raise PlannedShutdownException( - "Monkey shouldn't run on current machine " - "(it will be exploited later with more depth)." - ) + return False + return True @staticmethod diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 6059c291a..741635c6e 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -40,7 +40,7 @@ from infection_monkey.utils.monkey_dir import ( from infection_monkey.utils.monkey_log_path import get_monkey_log_path from infection_monkey.windows_upgrader import WindowsUpgrader -MAX_DEPTH_REACHED_MESSAGE = "Reached max depth, shutting down" +MAX_DEPTH_REACHED_MESSAGE = "Reached max depth, skipping propagation phase." LOG = logging.getLogger(__name__) @@ -62,6 +62,8 @@ class InfectionMonkey(object): self._default_server_port = None self._opts = None self._upgrading_to_64 = False + self._monkey_tunnel = None + self._post_breach_phase = None def initialize(self): LOG.info("Monkey is initializing...") @@ -125,124 +127,36 @@ class InfectionMonkey(object): if is_running_on_island(): WormConfiguration.started_on_island = True ControlClient.report_start_on_island() - ControlClient.should_monkey_run(self._opts.vulnerable_port) + + if not ControlClient.should_monkey_run(self._opts.vulnerable_port): + raise PlannedShutdownException( + "Monkey shouldn't run on current machine " + "(it will be exploited later with more depth)." + ) if firewall.is_enabled(): firewall.add_firewall_rule() - monkey_tunnel = ControlClient.create_control_tunnel() - if monkey_tunnel: - monkey_tunnel.start() + self._monkey_tunnel = ControlClient.create_control_tunnel() + if self._monkey_tunnel: + self._monkey_tunnel.start() StateTelem(is_done=False, version=get_version()).send() TunnelTelem().send() LOG.debug("Starting the post-breach phase asynchronously.") - post_breach_phase = Thread(target=self.start_post_breach_phase) - post_breach_phase.start() + self._post_breach_phase = Thread(target=self.start_post_breach_phase) + self._post_breach_phase.start() - LOG.debug("Starting the propagation phase.") - self.shutdown_by_max_depth_reached() + if not InfectionMonkey.max_propagation_depth_reached(): + LOG.info("Starting the propagation phase.") + LOG.debug("Running with depth: %d" % WormConfiguration.depth) + self.propagate() + else: + LOG.info("Maximum propagation depth has been reached; monkey will not propagate.") + TraceTelem(MAX_DEPTH_REACHED_MESSAGE).send() - for iteration_index in range(WormConfiguration.max_iterations): - ControlClient.keepalive() - ControlClient.load_control_config() - - self._network.initialize() - - self._fingerprint = HostFinger.get_instances() - - self._exploiters = HostExploiter.get_classes() - - if not self._keep_running or not WormConfiguration.alive: - break - - machines = self._network.get_victim_machines( - max_find=WormConfiguration.victims_max_find, - stop_callback=ControlClient.check_for_stop, - ) - is_empty = True - for machine in machines: - if ControlClient.check_for_stop(): - break - - is_empty = False - for finger in self._fingerprint: - LOG.info( - "Trying to get OS fingerprint from %r with module %s", - machine, - finger.__class__.__name__, - ) - try: - finger.get_host_fingerprint(machine) - except BaseException as exc: - LOG.error( - "Failed to run fingerprinter %s, exception %s" - % finger.__class__.__name__, - str(exc), - ) - - ScanTelem(machine).send() - - # skip machines that we've already exploited - if machine in self._exploited_machines: - LOG.debug("Skipping %r - already exploited", machine) - continue - elif machine in self._fail_exploitation_machines: - if WormConfiguration.retry_failed_explotation: - LOG.debug("%r - exploitation failed before, trying again", machine) - else: - LOG.debug("Skipping %r - exploitation failed before", machine) - continue - - if monkey_tunnel: - monkey_tunnel.set_tunnel_for_host(machine) - if self._default_server: - if self._network.on_island(self._default_server): - machine.set_default_server( - get_interface_to_target(machine.ip_addr) - + ( - ":" + self._default_server_port - if self._default_server_port - else "" - ) - ) - else: - machine.set_default_server(self._default_server) - LOG.debug( - "Default server for machine: %r set to %s" - % (machine, machine.default_server) - ) - - # Order exploits according to their type - self._exploiters = sorted( - self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value - ) - host_exploited = False - for exploiter in [exploiter(machine) for exploiter in self._exploiters]: - if self.try_exploiting(machine, exploiter): - host_exploited = True - VictimHostTelem("T1210", ScanStatus.USED, machine=machine).send() - if exploiter.RUNS_AGENT_ON_SUCCESS: - break # if adding machine to exploited, won't try other exploits - # on it - if not host_exploited: - self._fail_exploitation_machines.add(machine) - VictimHostTelem("T1210", ScanStatus.SCANNED, machine=machine).send() - if not self._keep_running: - break - - InfectionMonkey.run_ransomware() - - if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1): - time_to_sleep = WormConfiguration.timeout_between_iterations - LOG.info("Sleeping %d seconds before next life cycle iteration", time_to_sleep) - time.sleep(time_to_sleep) - - if self._keep_running and WormConfiguration.alive: - LOG.info("Reached max iterations (%d)", WormConfiguration.max_iterations) - elif not WormConfiguration.alive: - LOG.info("Marked not alive from configuration") + InfectionMonkey.run_ransomware() # if host was exploited, before continue to closing the tunnel ensure the exploited # host had its chance to @@ -254,12 +168,6 @@ class InfectionMonkey(object): ) time.sleep(time_to_sleep) - if monkey_tunnel: - monkey_tunnel.stop() - monkey_tunnel.join() - - post_breach_phase.join() - except PlannedShutdownException: LOG.info( "A planned shutdown of the Monkey occurred. Logging the reason and finishing " @@ -267,16 +175,21 @@ class InfectionMonkey(object): ) LOG.exception("Planned shutdown, reason:") + finally: + if self._monkey_tunnel: + self._monkey_tunnel.stop() + self._monkey_tunnel.join() + + if self._post_breach_phase: + self._post_breach_phase.join() + def start_post_breach_phase(self): self.collect_system_info_if_configured() PostBreach().execute_all_configured() - def shutdown_by_max_depth_reached(self): - if 0 == WormConfiguration.depth: - TraceTelem(MAX_DEPTH_REACHED_MESSAGE).send() - raise PlannedShutdownException(MAX_DEPTH_REACHED_MESSAGE) - else: - LOG.debug("Running with depth: %d" % WormConfiguration.depth) + @staticmethod + def max_propagation_depth_reached(): + return 0 == WormConfiguration.depth def collect_system_info_if_configured(self): LOG.debug("Calling for system info collection") @@ -291,6 +204,101 @@ class InfectionMonkey(object): if not WormConfiguration.alive: raise PlannedShutdownException("Marked 'not alive' from configuration.") + def propagate(self): + for iteration_index in range(WormConfiguration.max_iterations): + ControlClient.keepalive() + ControlClient.load_control_config() + + self._network.initialize() + + self._fingerprint = HostFinger.get_instances() + + self._exploiters = HostExploiter.get_classes() + + if not self._keep_running or not WormConfiguration.alive: + break + + machines = self._network.get_victim_machines( + max_find=WormConfiguration.victims_max_find, + stop_callback=ControlClient.check_for_stop, + ) + is_empty = True + for machine in machines: + if ControlClient.check_for_stop(): + break + + is_empty = False + for finger in self._fingerprint: + LOG.info( + "Trying to get OS fingerprint from %r with module %s", + machine, + finger.__class__.__name__, + ) + try: + finger.get_host_fingerprint(machine) + except BaseException as exc: + LOG.error( + "Failed to run fingerprinter %s, exception %s" + % finger.__class__.__name__, + str(exc), + ) + + ScanTelem(machine).send() + + # skip machines that we've already exploited + if machine in self._exploited_machines: + LOG.debug("Skipping %r - already exploited", machine) + continue + elif machine in self._fail_exploitation_machines: + if WormConfiguration.retry_failed_explotation: + LOG.debug("%r - exploitation failed before, trying again", machine) + else: + LOG.debug("Skipping %r - exploitation failed before", machine) + continue + + if self._monkey_tunnel: + self._monkey_tunnel.set_tunnel_for_host(machine) + if self._default_server: + if self._network.on_island(self._default_server): + machine.set_default_server( + get_interface_to_target(machine.ip_addr) + + (":" + self._default_server_port if self._default_server_port else "") + ) + else: + machine.set_default_server(self._default_server) + LOG.debug( + "Default server for machine: %r set to %s" + % (machine, machine.default_server) + ) + + # Order exploits according to their type + self._exploiters = sorted( + self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value + ) + host_exploited = False + for exploiter in [exploiter(machine) for exploiter in self._exploiters]: + if self.try_exploiting(machine, exploiter): + host_exploited = True + VictimHostTelem("T1210", ScanStatus.USED, machine=machine).send() + if exploiter.RUNS_AGENT_ON_SUCCESS: + break # if adding machine to exploited, won't try other exploits + # on it + if not host_exploited: + self._fail_exploitation_machines.add(machine) + VictimHostTelem("T1210", ScanStatus.SCANNED, machine=machine).send() + if not self._keep_running: + break + + if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1): + time_to_sleep = WormConfiguration.timeout_between_iterations + LOG.info("Sleeping %d seconds before next life cycle iteration", time_to_sleep) + time.sleep(time_to_sleep) + + if self._keep_running and WormConfiguration.alive: + LOG.info("Reached max iterations (%d)", WormConfiguration.max_iterations) + elif not WormConfiguration.alive: + LOG.info("Marked not alive from configuration") + def upgrade_to_64_if_needed(self): if WindowsUpgrader.should_upgrade(): self._upgrading_to_64 = True