From bd31cfd9470f29cfc01c162ba96759ef2939aba1 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 22 Nov 2021 18:23:37 +0530 Subject: [PATCH 01/18] Agent: Add IMaster --- monkey/infection_monkey/i_master.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 monkey/infection_monkey/i_master.py diff --git a/monkey/infection_monkey/i_master.py b/monkey/infection_monkey/i_master.py new file mode 100644 index 000000000..b045941df --- /dev/null +++ b/monkey/infection_monkey/i_master.py @@ -0,0 +1,27 @@ +import abc + + +class IMaster(metaclass=abc.ABCMeta): + @abc.abstractmethod + def start(self) -> None: + """ + With the help of the puppet, starts and instructs the Agent to + perform various actions like scanning or exploiting a specific host. + """ + pass + + @abc.abstractmethod + def terminate(self) -> None: + """ + Effectively marks the Agent as dead, telling all actions being + performed by the Agent to stop. + """ + pass + + @abc.abstractmethod + def cleanup(self) -> None: + """ + With the help of the puppet, instructs the Agent to cleanup whatever + is required since the Agent was killed. + """ + pass From 082f034d58eff19161364b6b997def7793afe66a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 22 Nov 2021 13:02:09 -0500 Subject: [PATCH 02/18] Agent: Change the method docstrings for IMaster --- monkey/infection_monkey/i_master.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/i_master.py b/monkey/infection_monkey/i_master.py index b045941df..9caa71a4d 100644 --- a/monkey/infection_monkey/i_master.py +++ b/monkey/infection_monkey/i_master.py @@ -5,23 +5,18 @@ class IMaster(metaclass=abc.ABCMeta): @abc.abstractmethod def start(self) -> None: """ - With the help of the puppet, starts and instructs the Agent to - perform various actions like scanning or exploiting a specific host. + Run the control logic that will instruct the Puppet to perform various actions like scanning + or exploiting a specific host. """ - pass @abc.abstractmethod def terminate(self) -> None: """ - Effectively marks the Agent as dead, telling all actions being - performed by the Agent to stop. + Stop the master and interrupt any actions that are currently being executed. """ - pass @abc.abstractmethod def cleanup(self) -> None: """ - With the help of the puppet, instructs the Agent to cleanup whatever - is required since the Agent was killed. + Revert any changes that the master has directly or indirectly caused to the system. """ - pass From 612668f43b99cb432595ed55711632ef0d780504 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 22 Nov 2021 14:46:19 -0500 Subject: [PATCH 03/18] Agent: Add partially completed MockMaster --- monkey/infection_monkey/master/__init__.py | 0 monkey/infection_monkey/master/mock_master.py | 62 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 monkey/infection_monkey/master/__init__.py create mode 100644 monkey/infection_monkey/master/mock_master.py diff --git a/monkey/infection_monkey/master/__init__.py b/monkey/infection_monkey/master/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/infection_monkey/master/mock_master.py b/monkey/infection_monkey/master/mock_master.py new file mode 100644 index 000000000..a322cacd3 --- /dev/null +++ b/monkey/infection_monkey/master/mock_master.py @@ -0,0 +1,62 @@ +from infection_monkey.i_master import IMaster +from infection_monkey.i_puppet import IPuppet, PortScanData, PortStatus +from infection_monkey.model.host import VictimHost +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger +from infection_monkey.telemetry.scan_telem import ScanTelem +from infection_monkey.telemetry.system_info_telem import SystemInfoTelem + + +class MockMaster(IMaster): + def __init__(self, puppet: IPuppet, telemetry_messenger: ITelemetryMessenger): + self._puppet = puppet + self._telemetry_messenger = telemetry_messenger + + def start(self) -> None: + self._run_sys_info_collectors() + self._run_pbas() + self._scan_victims() + + def _run_sys_info_collectors(self): + system_info_telemetry = {} + system_info_telemetry["ProcessListCollector"] = self._puppet.run_sys_info_collector( + "ProcessListCollector" + ) + self._telemetry_messenger.send_telemetry( + SystemInfoTelem({"collectors": system_info_telemetry}) + ) + system_info = self._puppet.run_sys_info_collector("LinuxInfoCollector") + self._telemetry_messenger.send_telemetry(SystemInfoTelem(system_info)) + + def _run_pbas(self): + self._puppet.run_pba("AccountDiscovery", {}) + self._puppet.run_pba("CommunicateAsBackdoorUser", {}) + + def _scan_victims(self): + # TODO: The telemetry must be malformed somehow, or something else is wrong. This causes the + # Island to raise an error when reports are viewed. + ips = ["10.0.0.1", "10.0.0.2", "10.0.0.3"] + ports = [22, 445, 3389, 8008] + for ip in ips: + h = VictimHost(ip) + + (response_received, os) = self._puppet.ping(ip) + h.icmp = response_received + if os is not None: + h.os["type"] = os + + for p in ports: + port_scan_data = self._puppet.scan_tcp_port(ip, p) + if port_scan_data.status == PortStatus.OPEN: + h.services[port_scan_data.service] = {} + h.services[port_scan_data.service]["display_name"] = "unknown(TCP)" + h.services[port_scan_data.service]["port"] = port_scan_data.port + if port_scan_data.banner is not None: + h.services[port_scan_data.service]["banner"] = port_scan_data.banner + + self._telemetry_messenger.send_telemetry(ScanTelem(h)) + + def terminate(self) -> None: + pass + + def cleanup(self) -> None: + pass From 357f74955748df3619a43f39e82ef1b7b8fe39fb Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 23 Nov 2021 14:48:52 +0100 Subject: [PATCH 04/18] Agent: Fix typo in puppet ping function that messed with node states --- monkey/infection_monkey/puppet/mock_puppet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/puppet/mock_puppet.py b/monkey/infection_monkey/puppet/mock_puppet.py index 0652c109b..674908c14 100644 --- a/monkey/infection_monkey/puppet/mock_puppet.py +++ b/monkey/infection_monkey/puppet/mock_puppet.py @@ -154,7 +154,7 @@ class MockPuppet(IPuppet): return (False, None) if host == DOT_3: - return (True, "Linux") + return (True, "linux") if host == DOT_4: return (False, None) From ea8be28a72dbcf015174cdc842e965e9c665b895 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 23 Nov 2021 11:51:09 -0500 Subject: [PATCH 05/18] Agent: Log a message in MockMaster.terminate() --- monkey/infection_monkey/master/mock_master.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/master/mock_master.py b/monkey/infection_monkey/master/mock_master.py index a322cacd3..e3592841b 100644 --- a/monkey/infection_monkey/master/mock_master.py +++ b/monkey/infection_monkey/master/mock_master.py @@ -1,3 +1,5 @@ +import logging + from infection_monkey.i_master import IMaster from infection_monkey.i_puppet import IPuppet, PortScanData, PortStatus from infection_monkey.model.host import VictimHost @@ -5,6 +7,8 @@ from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemet from infection_monkey.telemetry.scan_telem import ScanTelem from infection_monkey.telemetry.system_info_telem import SystemInfoTelem +logger = logging.getLogger() + class MockMaster(IMaster): def __init__(self, puppet: IPuppet, telemetry_messenger: ITelemetryMessenger): @@ -56,7 +60,7 @@ class MockMaster(IMaster): self._telemetry_messenger.send_telemetry(ScanTelem(h)) def terminate(self) -> None: - pass + logger.info("Terminating MockMaster") def cleanup(self) -> None: pass From b48ddd055a1fa597971dcc40b87cd7ed2ea2a371 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Tue, 23 Nov 2021 19:32:37 +0530 Subject: [PATCH 06/18] Agent: Progress implementing MockMaster --- monkey/infection_monkey/master/mock_master.py | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/master/mock_master.py b/monkey/infection_monkey/master/mock_master.py index e3592841b..b23712f71 100644 --- a/monkey/infection_monkey/master/mock_master.py +++ b/monkey/infection_monkey/master/mock_master.py @@ -1,8 +1,10 @@ import logging from infection_monkey.i_master import IMaster -from infection_monkey.i_puppet import IPuppet, PortScanData, PortStatus +from infection_monkey.i_puppet import IPuppet, PortStatus from infection_monkey.model.host import VictimHost +from infection_monkey.telemetry.exploit_telem import ExploitTelem +from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.telemetry.scan_telem import ScanTelem from infection_monkey.telemetry.system_info_telem import SystemInfoTelem @@ -19,6 +21,9 @@ class MockMaster(IMaster): self._run_sys_info_collectors() self._run_pbas() self._scan_victims() + self._fingerprint() + self._exploit() + self._run_payload() def _run_sys_info_collectors(self): system_info_telemetry = {} @@ -59,6 +64,34 @@ class MockMaster(IMaster): self._telemetry_messenger.send_telemetry(ScanTelem(h)) + def _fingerprint(self): + machine_1 = VictimHost("10.0.0.1") + machine_3 = VictimHost("10.0.0.3") + + self._puppet.fingerprint("SMBFinger", machine_1) + self._telemetry_messenger.send_telemetry(ScanTelem(machine_1)) + + self._puppet.fingerprint("SMBFinger", machine_3) + self._telemetry_messenger.send_telemetry(ScanTelem(machine_3)) + + self._puppet.fingerprint("HTTPFinger", machine_3) + self._telemetry_messenger.send_telemetry(ScanTelem(machine_3)) + + def _exploit(self): + # TODO: modify what ExploitTelem gets + self._telemetry_messenger.send_telemetry( + ExploitTelem(self._puppet.exploit_host("PowerShellExploiter", "10.0.0.1", {}, None)) + ) + self._telemetry_messenger.send_telemetry( + ExploitTelem(self._puppet.exploit_host("SSHExploiter", "10.0.0.3", {}, None)) + ) + + def _run_payload(self): + # TODO: modify what FileEncryptionTelem gets + self._telemetry_messenger.send_telemetry( + FileEncryptionTelem(self._run_payload("RansomwarePayload", {}, None)) + ) + def terminate(self) -> None: logger.info("Terminating MockMaster") From 7b0f08ee54eba0b265c2eb2b09abb56461fc481b Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 24 Nov 2021 00:12:41 +0530 Subject: [PATCH 07/18] Agent: Finish implementing MockMaster Also modified ExploitTelem and PostBreachTelem internals, and MockPuppet. --- monkey/infection_monkey/i_puppet.py | 11 +++-- monkey/infection_monkey/master/mock_master.py | 39 +++++++++++----- monkey/infection_monkey/puppet/mock_puppet.py | 45 +++++++++++++++---- .../telemetry/exploit_telem.py | 28 ++++++++---- .../telemetry/post_breach_telem.py | 19 ++++---- vulture_allowlist.py | 1 + 6 files changed, 103 insertions(+), 40 deletions(-) diff --git a/monkey/infection_monkey/i_puppet.py b/monkey/infection_monkey/i_puppet.py index c10731d8f..d9d225b7b 100644 --- a/monkey/infection_monkey/i_puppet.py +++ b/monkey/infection_monkey/i_puppet.py @@ -10,7 +10,9 @@ class PortStatus(Enum): CLOSED = 2 +ExploiterResultData = namedtuple("ExploiterResultData", ["result", "info", "attempts"]) PortScanData = namedtuple("PortScanData", ["port", "status", "banner", "service"]) +PostBreachData = namedtuple("PostBreachData", ["command", "result"]) class IPuppet(metaclass=abc.ABCMeta): @@ -24,11 +26,12 @@ class IPuppet(metaclass=abc.ABCMeta): """ @abc.abstractmethod - def run_pba(self, name: str, options: Dict) -> None: + def run_pba(self, name: str, options: Dict) -> PostBreachData: """ Runs a post-breach action (PBA) :param str name: The name of the post-breach action to run :param Dict options: A dictionary containing options that modify the behavior of the PBA + :rtype: PostBreachData """ @abc.abstractmethod @@ -63,7 +66,9 @@ class IPuppet(metaclass=abc.ABCMeta): """ @abc.abstractmethod - def exploit_host(self, name: str, host: str, options: Dict, interrupt: threading.Event) -> bool: + def exploit_host( + self, name: str, host: str, options: Dict, interrupt: threading.Event + ) -> ExploiterResultData: """ Runs an exploiter against a remote host :param str name: The name of the exploiter to run @@ -71,7 +76,7 @@ class IPuppet(metaclass=abc.ABCMeta): :param Dict options: A dictionary containing options that modify the behavior of the exploiter :return: True if exploitation was successful, False otherwise - :rtype: bool + :rtype: ExploiterResultData """ @abc.abstractmethod diff --git a/monkey/infection_monkey/master/mock_master.py b/monkey/infection_monkey/master/mock_master.py index b23712f71..f73bcd120 100644 --- a/monkey/infection_monkey/master/mock_master.py +++ b/monkey/infection_monkey/master/mock_master.py @@ -6,6 +6,7 @@ from infection_monkey.model.host import VictimHost from infection_monkey.telemetry.exploit_telem import ExploitTelem from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger +from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.telemetry.scan_telem import ScanTelem from infection_monkey.telemetry.system_info_telem import SystemInfoTelem @@ -16,6 +17,12 @@ class MockMaster(IMaster): def __init__(self, puppet: IPuppet, telemetry_messenger: ITelemetryMessenger): self._puppet = puppet self._telemetry_messenger = telemetry_messenger + self._hosts = { + "10.0.0.1": VictimHost("10.0.0.1"), + "10.0.0.2": VictimHost("10.0.0.2"), + "10.0.0.3": VictimHost("10.0.0.3"), + "10.0.0.4": VictimHost("10.0.0.4"), + } def start(self) -> None: self._run_sys_info_collectors() @@ -37,8 +44,13 @@ class MockMaster(IMaster): self._telemetry_messenger.send_telemetry(SystemInfoTelem(system_info)) def _run_pbas(self): - self._puppet.run_pba("AccountDiscovery", {}) - self._puppet.run_pba("CommunicateAsBackdoorUser", {}) + name = "AccountDiscovery" + command, result = self._puppet.run_pba(name, {}) + self._telemetry_messenger.send_telemetry(PostBreachTelem(name, command, result)) + + name = "CommunicateAsBackdoorUser" + command, result = self._puppet.run_pba(name, {}) + self._telemetry_messenger.send_telemetry(PostBreachTelem(name, command, result)) def _scan_victims(self): # TODO: The telemetry must be malformed somehow, or something else is wrong. This causes the @@ -46,7 +58,7 @@ class MockMaster(IMaster): ips = ["10.0.0.1", "10.0.0.2", "10.0.0.3"] ports = [22, 445, 3389, 8008] for ip in ips: - h = VictimHost(ip) + h = self._hosts[ip] (response_received, os) = self._puppet.ping(ip) h.icmp = response_received @@ -65,8 +77,8 @@ class MockMaster(IMaster): self._telemetry_messenger.send_telemetry(ScanTelem(h)) def _fingerprint(self): - machine_1 = VictimHost("10.0.0.1") - machine_3 = VictimHost("10.0.0.3") + machine_1 = self._hosts["10.0.0.1"] + machine_3 = self._hosts["10.0.0.3"] self._puppet.fingerprint("SMBFinger", machine_1) self._telemetry_messenger.send_telemetry(ScanTelem(machine_1)) @@ -78,19 +90,22 @@ class MockMaster(IMaster): self._telemetry_messenger.send_telemetry(ScanTelem(machine_3)) def _exploit(self): - # TODO: modify what ExploitTelem gets - self._telemetry_messenger.send_telemetry( - ExploitTelem(self._puppet.exploit_host("PowerShellExploiter", "10.0.0.1", {}, None)) + result, info, attempts = self._puppet.exploit_host( + "PowerShellExploiter", "10.0.0.1", {}, None ) self._telemetry_messenger.send_telemetry( - ExploitTelem(self._puppet.exploit_host("SSHExploiter", "10.0.0.3", {}, None)) + ExploitTelem("PowerShellExploiter", self._hosts["10.0.0.1"], result, info, attempts) + ) + + result, info, attempts = self._puppet.exploit_host("SSHExploiter", "10.0.0.3", {}, None) + self._telemetry_messenger.send_telemetry( + ExploitTelem("SSHExploiter", self._hosts["10.0.0.3"], result, info, attempts) ) def _run_payload(self): # TODO: modify what FileEncryptionTelem gets - self._telemetry_messenger.send_telemetry( - FileEncryptionTelem(self._run_payload("RansomwarePayload", {}, None)) - ) + path, success, error = self._puppet.run_payload("RansomwarePayload", {}, None) + self._telemetry_messenger.send_telemetry(FileEncryptionTelem(path, success, error)) def terminate(self) -> None: logger.info("Terminating MockMaster") diff --git a/monkey/infection_monkey/puppet/mock_puppet.py b/monkey/infection_monkey/puppet/mock_puppet.py index 674908c14..92eeea70e 100644 --- a/monkey/infection_monkey/puppet/mock_puppet.py +++ b/monkey/infection_monkey/puppet/mock_puppet.py @@ -1,8 +1,14 @@ import logging import threading -from typing import Dict, Optional, Tuple +from typing import Dict, List, Optional, Tuple -from infection_monkey.i_puppet import IPuppet, PortScanData, PortStatus +from infection_monkey.i_puppet import ( + ExploiterResultData, + IPuppet, + PortScanData, + PortStatus, + PostBreachData, +) DOT_1 = "10.0.0.1" DOT_2 = "10.0.0.2" @@ -141,9 +147,15 @@ class MockPuppet(IPuppet): return {} - def run_pba(self, name: str, options: Dict) -> None: + def run_pba(self, name: str, options: Dict) -> List[Tuple[str, bool]]: logger.debug(f"run_pba({name}, {options})") - return None + result_1 = PostBreachData("pba command 1", "pba result 1") + result_2 = PostBreachData("pba command 2", "pba result 2") + + return [ + (result_1.command, result_1.result, True), + (result_2.command, result_2.result, False), + ] def ping(self, host: str) -> Tuple[bool, Optional[str]]: logger.debug(f"run_ping({host})") @@ -208,13 +220,30 @@ class MockPuppet(IPuppet): def exploit_host(self, name: str, host: str, options: Dict, interrupt: threading.Event) -> bool: logger.debug(f"exploit_hosts({name}, {host}, {options})") - successful_exploiters = {DOT_1: {"PowerShellExploiter"}, DOT_3: {"SSHExploiter"}} + successful_exploiters = { + DOT_1: { + "PowerShellExploiter": ExploiterResultData( + True, {"info": "important success stuff"}, ["attempt 1"] + ) + }, + DOT_3: { + "SSHExploiter": ExploiterResultData( + False, {"info": "important failure stuff"}, ["attempt 2"] + ) + }, + } - return name in successful_exploiters.get(host, {}) + return ( + successful_exploiters[host][name].result, + successful_exploiters[host][name].info, + successful_exploiters[host][name].attempts, + ) - def run_payload(self, name: str, options: Dict, interrupt: threading.Event) -> None: + def run_payload( + self, name: str, options: Dict, interrupt: threading.Event + ) -> Tuple[None, bool, str]: logger.debug(f"run_payload({name}, {options})") - return None + return (None, True, "") def cleanup(self) -> None: print("Cleanup called!") diff --git a/monkey/infection_monkey/telemetry/exploit_telem.py b/monkey/infection_monkey/telemetry/exploit_telem.py index e181b0243..a34b4e861 100644 --- a/monkey/infection_monkey/telemetry/exploit_telem.py +++ b/monkey/infection_monkey/telemetry/exploit_telem.py @@ -1,25 +1,35 @@ +from typing import Dict, List + from common.common_consts.telem_categories import TelemCategoryEnum +from infection_monkey.model.host import VictimHost from infection_monkey.telemetry.base_telem import BaseTelem class ExploitTelem(BaseTelem): - def __init__(self, exploiter, result): + def __init__(self, name: str, host: VictimHost, result: bool, info: Dict, attempts: List): """ Default exploit telemetry constructor - :param exploiter: The instance of exploiter used - :param result: The result from the 'exploit_host' method. + :param name: The name of exploiter used + :param host: The host machine + :param result: The result from the 'exploit_host' method + :param info: Information about the exploiter + :param attempts: Information about the exploiter's attempts """ super(ExploitTelem, self).__init__() - self.exploiter = exploiter + + self.name = name + self.host = host.__dict__ self.result = result + self.info = info + self.attempts = attempts telem_category = TelemCategoryEnum.EXPLOIT - def get_data(self): + def get_data(self) -> Dict: return { "result": self.result, - "machine": self.exploiter.host.__dict__, - "exploiter": self.exploiter.__class__.__name__, - "info": self.exploiter.exploit_info, - "attempts": self.exploiter.exploit_attempts, + "machine": self.host, + "exploiter": self.name, + "info": self.info, + "attempts": self.attempts, } diff --git a/monkey/infection_monkey/telemetry/post_breach_telem.py b/monkey/infection_monkey/telemetry/post_breach_telem.py index 4c6607b9c..e4f93e30d 100644 --- a/monkey/infection_monkey/telemetry/post_breach_telem.py +++ b/monkey/infection_monkey/telemetry/post_breach_telem.py @@ -1,4 +1,5 @@ import socket +from typing import Dict, Tuple from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.telemetry.base_telem import BaseTelem @@ -6,31 +7,33 @@ from infection_monkey.utils.environment import is_windows_os class PostBreachTelem(BaseTelem): - def __init__(self, pba, result): + def __init__(self, name: str, command: str, result: str) -> None: """ Default post breach telemetry constructor - :param pba: Post breach action which was used + :param name: Name of post breach action + :param command: Command used as PBA :param result: Result of PBA """ super(PostBreachTelem, self).__init__() - self.pba = pba + self.name = name + self.command = command self.result = result self.hostname, self.ip = PostBreachTelem._get_hostname_and_ip() telem_category = TelemCategoryEnum.POST_BREACH - def get_data(self): + def get_data(self) -> Dict: return { - "command": self.pba.command, + "command": self.command, "result": self.result, - "name": self.pba.name, + "name": self.name, "hostname": self.hostname, "ip": self.ip, "os": PostBreachTelem._get_os(), } @staticmethod - def _get_hostname_and_ip(): + def _get_hostname_and_ip() -> Tuple[str, str]: try: hostname = socket.gethostname() ip = socket.gethostbyname(hostname) @@ -40,5 +43,5 @@ class PostBreachTelem(BaseTelem): return hostname, ip @staticmethod - def _get_os(): + def _get_os() -> str: return "Windows" if is_windows_os() else "Linux" diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 4f67c9860..9ad0ccc68 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -212,3 +212,4 @@ MockPuppet ControlChannel should_agent_stop get_credentials_for_propagation +MockMaster From 8c2eab4c2a93c42cf163c1a3a314d7f3dbb35458 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 24 Nov 2021 00:16:14 +0530 Subject: [PATCH 08/18] Agent: Remove stray issue comment in MockMaster which was solved --- monkey/infection_monkey/master/mock_master.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/infection_monkey/master/mock_master.py b/monkey/infection_monkey/master/mock_master.py index f73bcd120..9d44f5d32 100644 --- a/monkey/infection_monkey/master/mock_master.py +++ b/monkey/infection_monkey/master/mock_master.py @@ -53,8 +53,6 @@ class MockMaster(IMaster): self._telemetry_messenger.send_telemetry(PostBreachTelem(name, command, result)) def _scan_victims(self): - # TODO: The telemetry must be malformed somehow, or something else is wrong. This causes the - # Island to raise an error when reports are viewed. ips = ["10.0.0.1", "10.0.0.2", "10.0.0.3"] ports = [22, 445, 3389, 8008] for ip in ips: From 57b710fb10547801ae0839f842b09d518745cec5 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 24 Nov 2021 00:20:28 +0530 Subject: [PATCH 09/18] UT: Modify unit tests for ExploitTelem and PostBreachTelem based on previous changes --- .../infection_monkey/telemetry/test_exploit_telem.py | 3 +-- .../infection_monkey/telemetry/test_post_breach_telem.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py index 6ecfeba1a..982299947 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py @@ -19,7 +19,6 @@ HOST_AS_DICT = { "default_tunnel": None, "default_server": None, } -EXPLOITER = SSHExploiter(HOST) EXPLOITER_NAME = "SSHExploiter" EXPLOITER_INFO = { "display_name": SSHExploiter._EXPLOITED_SERVICE, @@ -35,7 +34,7 @@ RESULT = False @pytest.fixture def exploit_telem_test_instance(): - return ExploitTelem(EXPLOITER, RESULT) + return ExploitTelem(EXPLOITER_NAME, HOST, RESULT, EXPLOITER_INFO, EXPLOITER_ATTEMPTS) def test_exploit_telem_send(exploit_telem_test_instance, spy_send_telemetry): diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_post_breach_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_post_breach_telem.py index e880b3fc9..d71a82e2a 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_post_breach_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_post_breach_telem.py @@ -20,10 +20,9 @@ class StubSomePBA: @pytest.fixture def post_breach_telem_test_instance(monkeypatch): - PBA = StubSomePBA() monkeypatch.setattr(PostBreachTelem, "_get_hostname_and_ip", lambda: (HOSTNAME, IP)) monkeypatch.setattr(PostBreachTelem, "_get_os", lambda: OS) - return PostBreachTelem(PBA, RESULT) + return PostBreachTelem(PBA_NAME, PBA_COMMAND, RESULT) def test_post_breach_telem_send(post_breach_telem_test_instance, spy_send_telemetry): From d0b9fca4d74ba70558b2247ef371b188f0368f8d Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 24 Nov 2021 13:20:53 +0530 Subject: [PATCH 10/18] Agent: Fix return types and statements in mock puppet for PBA and exploiters --- monkey/infection_monkey/puppet/mock_puppet.py | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/monkey/infection_monkey/puppet/mock_puppet.py b/monkey/infection_monkey/puppet/mock_puppet.py index 92eeea70e..e203d9cec 100644 --- a/monkey/infection_monkey/puppet/mock_puppet.py +++ b/monkey/infection_monkey/puppet/mock_puppet.py @@ -1,6 +1,6 @@ import logging import threading -from typing import Dict, List, Optional, Tuple +from typing import Dict, Optional, Tuple from infection_monkey.i_puppet import ( ExploiterResultData, @@ -147,15 +147,13 @@ class MockPuppet(IPuppet): return {} - def run_pba(self, name: str, options: Dict) -> List[Tuple[str, bool]]: + def run_pba(self, name: str, options: Dict) -> PostBreachData: logger.debug(f"run_pba({name}, {options})") - result_1 = PostBreachData("pba command 1", "pba result 1") - result_2 = PostBreachData("pba command 2", "pba result 2") - return [ - (result_1.command, result_1.result, True), - (result_2.command, result_2.result, False), - ] + if name == "AccountDiscovery": + return PostBreachData("pba command 1", "pba result 1") + else: + return PostBreachData("pba command 2", "pba result 2") def ping(self, host: str) -> Tuple[bool, Optional[str]]: logger.debug(f"run_ping({host})") @@ -218,7 +216,9 @@ class MockPuppet(IPuppet): return {} - def exploit_host(self, name: str, host: str, options: Dict, interrupt: threading.Event) -> bool: + def exploit_host( + self, name: str, host: str, options: Dict, interrupt: threading.Event + ) -> ExploiterResultData: logger.debug(f"exploit_hosts({name}, {host}, {options})") successful_exploiters = { DOT_1: { @@ -233,11 +233,7 @@ class MockPuppet(IPuppet): }, } - return ( - successful_exploiters[host][name].result, - successful_exploiters[host][name].info, - successful_exploiters[host][name].attempts, - ) + return successful_exploiters[host][name] def run_payload( self, name: str, options: Dict, interrupt: threading.Event From e00fd64530d9a73a9aebd4007a6fe6f4e57e050c Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 24 Nov 2021 13:47:33 +0530 Subject: [PATCH 11/18] Agent: Fix PBA return value --- monkey/infection_monkey/puppet/mock_puppet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/puppet/mock_puppet.py b/monkey/infection_monkey/puppet/mock_puppet.py index e203d9cec..6996d4d7c 100644 --- a/monkey/infection_monkey/puppet/mock_puppet.py +++ b/monkey/infection_monkey/puppet/mock_puppet.py @@ -151,9 +151,9 @@ class MockPuppet(IPuppet): logger.debug(f"run_pba({name}, {options})") if name == "AccountDiscovery": - return PostBreachData("pba command 1", "pba result 1") + return PostBreachData("pba command 1", ["pba result 1", True]) else: - return PostBreachData("pba command 2", "pba result 2") + return PostBreachData("pba command 2", ["pba result 2", False]) def ping(self, host: str) -> Tuple[bool, Optional[str]]: logger.debug(f"run_ping({host})") From 9d36f20b4238db1afe9c1a89f3dbf95e66d51142 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 23 Nov 2021 15:27:09 +0200 Subject: [PATCH 12/18] Agent: register signal handlers Agent will now handle interrupt and break signals on linux and windows --- monkey/infection_monkey/monkey.py | 3 +++ .../infection_monkey/utils/signal_handler.py | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 monkey/infection_monkey/utils/signal_handler.py diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 4160a36e0..4c5584558 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -38,6 +38,7 @@ from infection_monkey.utils.monkey_dir import ( remove_monkey_dir, ) from infection_monkey.utils.monkey_log_path import get_monkey_log_path +from infection_monkey.utils.signal_handler import register_signal_handlers from infection_monkey.windows_upgrader import WindowsUpgrader MAX_DEPTH_REACHED_MESSAGE = "Reached max depth, skipping propagation phase." @@ -107,6 +108,8 @@ class InfectionMonkey(object): logger.info("Monkey is starting...") logger.debug("Starting the setup phase.") + register_signal_handlers() + input() # Sets island's IP and port for monkey to communicate to self.set_default_server() self.set_default_port() diff --git a/monkey/infection_monkey/utils/signal_handler.py b/monkey/infection_monkey/utils/signal_handler.py new file mode 100644 index 000000000..32efa078b --- /dev/null +++ b/monkey/infection_monkey/utils/signal_handler.py @@ -0,0 +1,21 @@ +import logging +import signal + +from infection_monkey.utils.environment import is_windows_os +from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException + +logger = logging.getLogger(__name__) + + +def stop_signal_handler(_, __): + # IMaster.cleanup() + logger.debug("Some kind of interrupt signal was sent to the Monkey Agent") + raise PlannedShutdownException("Monkey Agent got an interrupt signal") + + +def register_signal_handlers(): + signal.signal(signal.SIGINT, stop_signal_handler) + signal.signal(signal.SIGTERM, stop_signal_handler) + + if is_windows_os(): + signal.signal(signal.SIGBREAK, stop_signal_handler) From 27ef06c546234e5e4f8526a7c9afb23cd0231e29 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 23 Nov 2021 12:17:17 -0500 Subject: [PATCH 13/18] Agent: Call IMaster.terminate() from signal handler --- monkey/infection_monkey/monkey.py | 8 +++++++- monkey/infection_monkey/utils/signal_handler.py | 16 +++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 4c5584558..2fde8cf43 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -13,18 +13,23 @@ from common.version import get_version from infection_monkey.config import WormConfiguration from infection_monkey.control import ControlClient from infection_monkey.exploit.HostExploiter import HostExploiter +from infection_monkey.master.mock_master import MockMaster from infection_monkey.model import DELAY_DELETE_CMD from infection_monkey.network.firewall import app as firewall from infection_monkey.network.HostFinger import HostFinger from infection_monkey.network.network_scanner import NetworkScanner from infection_monkey.network.tools import get_interface_to_target, is_running_on_island from infection_monkey.post_breach.post_breach_handler import PostBreach +from infection_monkey.puppet.mock_puppet import MockPuppet from infection_monkey.ransomware.ransomware_payload_builder import build_ransomware_payload from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from infection_monkey.telemetry.attack.t1107_telem import T1107Telem from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem +from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter import ( + LegacyTelemetryMessengerAdapter, +) from infection_monkey.telemetry.scan_telem import ScanTelem from infection_monkey.telemetry.state_telem import StateTelem from infection_monkey.telemetry.system_info_telem import SystemInfoTelem @@ -108,7 +113,8 @@ class InfectionMonkey(object): logger.info("Monkey is starting...") logger.debug("Starting the setup phase.") - register_signal_handlers() + mock_master = MockMaster(MockPuppet(), LegacyTelemetryMessengerAdapter()) + register_signal_handlers(mock_master) input() # Sets island's IP and port for monkey to communicate to self.set_default_server() diff --git a/monkey/infection_monkey/utils/signal_handler.py b/monkey/infection_monkey/utils/signal_handler.py index 32efa078b..d125ad98d 100644 --- a/monkey/infection_monkey/utils/signal_handler.py +++ b/monkey/infection_monkey/utils/signal_handler.py @@ -1,19 +1,25 @@ import logging import signal +from infection_monkey.i_master import IMaster from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException logger = logging.getLogger(__name__) -def stop_signal_handler(_, __): - # IMaster.cleanup() - logger.debug("Some kind of interrupt signal was sent to the Monkey Agent") - raise PlannedShutdownException("Monkey Agent got an interrupt signal") +class StopSignalHandler: + def __init__(self, master: IMaster): + self._master = master + + def __call__(self, _, __): + self._master.terminate() + logger.debug("Some kind of interrupt signal was sent to the Monkey Agent") + raise PlannedShutdownException("Monkey Agent got an interrupt signal") -def register_signal_handlers(): +def register_signal_handlers(master: IMaster): + stop_signal_handler = StopSignalHandler(master) signal.signal(signal.SIGINT, stop_signal_handler) signal.signal(signal.SIGTERM, stop_signal_handler) From 068307f0ebdd51d143e4fa16aa809fd8ef9a3ce1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 23 Nov 2021 13:09:17 -0500 Subject: [PATCH 14/18] Agent: Handle window close event on Windows --- monkey/infection_monkey/utils/signal_handler.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/utils/signal_handler.py b/monkey/infection_monkey/utils/signal_handler.py index d125ad98d..f15fded3a 100644 --- a/monkey/infection_monkey/utils/signal_handler.py +++ b/monkey/infection_monkey/utils/signal_handler.py @@ -12,7 +12,7 @@ class StopSignalHandler: def __init__(self, master: IMaster): self._master = master - def __call__(self, _, __): + def __call__(self, _, __=None): self._master.terminate() logger.debug("Some kind of interrupt signal was sent to the Monkey Agent") raise PlannedShutdownException("Monkey Agent got an interrupt signal") @@ -24,4 +24,7 @@ def register_signal_handlers(master: IMaster): signal.signal(signal.SIGTERM, stop_signal_handler) if is_windows_os(): + import win32api + signal.signal(signal.SIGBREAK, stop_signal_handler) + win32api.SetConsoleCtrlHandler(stop_signal_handler, True) From 6149ef630bc63c221fea730eff5dc5b00042297c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 23 Nov 2021 13:18:11 -0500 Subject: [PATCH 15/18] Agent: Improve signal handler log message --- monkey/infection_monkey/utils/signal_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/utils/signal_handler.py b/monkey/infection_monkey/utils/signal_handler.py index f15fded3a..a2f865ce2 100644 --- a/monkey/infection_monkey/utils/signal_handler.py +++ b/monkey/infection_monkey/utils/signal_handler.py @@ -12,9 +12,9 @@ class StopSignalHandler: def __init__(self, master: IMaster): self._master = master - def __call__(self, _, __=None): + def __call__(self, signum, __=None): + logger.info(f"The Monkey Agent received signal {signum}") self._master.terminate() - logger.debug("Some kind of interrupt signal was sent to the Monkey Agent") raise PlannedShutdownException("Monkey Agent got an interrupt signal") From 73329e9729ddc309c1c9a954f3546652e97b2fde Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 23 Nov 2021 13:28:01 -0500 Subject: [PATCH 16/18] Agent: Remove input() call in monkey.py The call to input() was used to pause the execution of the agent while testing the new signal handlers. It is no longer needed. --- monkey/infection_monkey/monkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 2fde8cf43..09eef703d 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -115,7 +115,7 @@ class InfectionMonkey(object): logger.debug("Starting the setup phase.") mock_master = MockMaster(MockPuppet(), LegacyTelemetryMessengerAdapter()) register_signal_handlers(mock_master) - input() + # Sets island's IP and port for monkey to communicate to self.set_default_server() self.set_default_port() From 3f7c4a8859b1d71cab4982f49489c8b06d309d50 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 24 Nov 2021 10:40:05 +0200 Subject: [PATCH 17/18] Agent: add a comment warning that windows will terminate the process 5s after CTRL_CLOSE_EVENT signal The comment will warn us that in case that particular signal is raised, the cleanup shouldn't take longer than 5s --- monkey/infection_monkey/utils/signal_handler.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/utils/signal_handler.py b/monkey/infection_monkey/utils/signal_handler.py index a2f865ce2..d75b08f10 100644 --- a/monkey/infection_monkey/utils/signal_handler.py +++ b/monkey/infection_monkey/utils/signal_handler.py @@ -12,7 +12,7 @@ class StopSignalHandler: def __init__(self, master: IMaster): self._master = master - def __call__(self, signum, __=None): + def __call__(self, signum, _=None): logger.info(f"The Monkey Agent received signal {signum}") self._master.terminate() raise PlannedShutdownException("Monkey Agent got an interrupt signal") @@ -27,4 +27,7 @@ def register_signal_handlers(master: IMaster): import win32api signal.signal(signal.SIGBREAK, stop_signal_handler) + + # CTRL_CLOSE_EVENT signal has a timeout of 5000ms, + # after that OS will forcefully kill the process win32api.SetConsoleCtrlHandler(stop_signal_handler, True) From 0ec8fca766f49dcc6afab6131a8c1eb29bd9523b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 24 Nov 2021 07:45:40 -0500 Subject: [PATCH 18/18] Agent: Add start/finish logging to phases of MockMaster execution --- monkey/infection_monkey/master/mock_master.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/monkey/infection_monkey/master/mock_master.py b/monkey/infection_monkey/master/mock_master.py index 9d44f5d32..4cf6dc176 100644 --- a/monkey/infection_monkey/master/mock_master.py +++ b/monkey/infection_monkey/master/mock_master.py @@ -33,6 +33,7 @@ class MockMaster(IMaster): self._run_payload() def _run_sys_info_collectors(self): + logging.info("Running system info collectors") system_info_telemetry = {} system_info_telemetry["ProcessListCollector"] = self._puppet.run_sys_info_collector( "ProcessListCollector" @@ -42,8 +43,10 @@ class MockMaster(IMaster): ) system_info = self._puppet.run_sys_info_collector("LinuxInfoCollector") self._telemetry_messenger.send_telemetry(SystemInfoTelem(system_info)) + logging.info("Finished running system info collectors") def _run_pbas(self): + logging.info("Running post breach actions") name = "AccountDiscovery" command, result = self._puppet.run_pba(name, {}) self._telemetry_messenger.send_telemetry(PostBreachTelem(name, command, result)) @@ -51,8 +54,10 @@ class MockMaster(IMaster): name = "CommunicateAsBackdoorUser" command, result = self._puppet.run_pba(name, {}) self._telemetry_messenger.send_telemetry(PostBreachTelem(name, command, result)) + logging.info("Finished running post breach actions") def _scan_victims(self): + logging.info("Scanning network for potential victims") ips = ["10.0.0.1", "10.0.0.2", "10.0.0.3"] ports = [22, 445, 3389, 8008] for ip in ips: @@ -73,8 +78,10 @@ class MockMaster(IMaster): h.services[port_scan_data.service]["banner"] = port_scan_data.banner self._telemetry_messenger.send_telemetry(ScanTelem(h)) + logging.info("Finished scanning network for potential victims") def _fingerprint(self): + logging.info("Running fingerprinters on potential victims") machine_1 = self._hosts["10.0.0.1"] machine_3 = self._hosts["10.0.0.3"] @@ -86,8 +93,10 @@ class MockMaster(IMaster): self._puppet.fingerprint("HTTPFinger", machine_3) self._telemetry_messenger.send_telemetry(ScanTelem(machine_3)) + logging.info("Finished running fingerprinters on potential victims") def _exploit(self): + logging.info("Exploiting victims") result, info, attempts = self._puppet.exploit_host( "PowerShellExploiter", "10.0.0.1", {}, None ) @@ -99,11 +108,14 @@ class MockMaster(IMaster): self._telemetry_messenger.send_telemetry( ExploitTelem("SSHExploiter", self._hosts["10.0.0.3"], result, info, attempts) ) + logging.info("Finished exploiting victims") def _run_payload(self): + logging.info("Running payloads") # TODO: modify what FileEncryptionTelem gets path, success, error = self._puppet.run_payload("RansomwarePayload", {}, None) self._telemetry_messenger.send_telemetry(FileEncryptionTelem(path, success, error)) + logging.info("Finished running payloads") def terminate(self) -> None: logger.info("Terminating MockMaster")