From 7da364058747b35b39312e8e6da5f321664942f9 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 9 Jun 2022 09:25:37 +0200 Subject: [PATCH 1/4] Island: Refactor agent-binaries resource --- .../cc/resources/agent_binaries.py | 56 ++++++++----------- .../cc/services/run_local_monkey.py | 30 +++++++++- 2 files changed, 51 insertions(+), 35 deletions(-) diff --git a/monkey/monkey_island/cc/resources/agent_binaries.py b/monkey/monkey_island/cc/resources/agent_binaries.py index ff4e061c1..aced17c43 100644 --- a/monkey/monkey_island/cc/resources/agent_binaries.py +++ b/monkey/monkey_island/cc/resources/agent_binaries.py @@ -1,48 +1,40 @@ import logging -from pathlib import Path -from flask import make_response, send_from_directory +from flask import make_response, send_file +from monkey_island.cc.repository import AgentRetrievalError, IAgentBinaryRepository from monkey_island.cc.resources.AbstractResource import AbstractResource -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH logger = logging.getLogger(__name__) -AGENTS = { - "linux": "monkey-linux-64", - "windows": "monkey-windows-64.exe", -} - - -class UnsupportedOSError(Exception): - pass - class AgentBinaries(AbstractResource): urls = ["/api/agent-binaries/"] + def __init__(self, agent_binary_repository: IAgentBinaryRepository): + self._agent_binary_repository = agent_binary_repository + # Used by monkey. can't secure. def get(self, os): + """ + Gets the agent binary based on the OS + + :param os: Operating systems. Supported OS are: 'linux' and 'windows' + :return: file-like object with a filename same as the OS + """ try: - path = get_agent_executable_path(os) - return send_from_directory(path.parent, path.name) - except UnsupportedOSError as ex: - logger.error(ex) - return make_response({"error": str(ex)}, 404) + agent_binaries = { + "linux": self._agent_binary_repository.get_linux_binary, + "windows": self._agent_binary_repository.get_windows_binary, + } + file = agent_binaries[os]() -def get_agent_executable_path(os: str) -> Path: - try: - agent_path = get_executable_full_path(AGENTS[os]) - logger.debug(f'Local path for {os} executable is "{agent_path}"') - if not agent_path.is_file(): - logger.error(f"File {agent_path} not found") - - return agent_path - except KeyError: - logger.warning(f"No monkey executables could be found for the host os: {os}") - raise UnsupportedOSError(f'No Agents are available for unsupported operating system "{os}"') - - -def get_executable_full_path(executable_filename: str) -> Path: - return Path(MONKEY_ISLAND_ABS_PATH) / "cc" / "binaries" / executable_filename + return send_file(file, mimetype="application/octet-stream") + except AgentRetrievalError as err: + logger.error(err) + return make_response({"error": str(err)}, 500) + except KeyError as err: + error_msg = f'No Agents are available for unsupported operating system "{os}": {err}' + logger.error(error_msg) + return make_response({"error": error_msg}, 404) diff --git a/monkey/monkey_island/cc/services/run_local_monkey.py b/monkey/monkey_island/cc/services/run_local_monkey.py index 1f022c0e4..f78868a37 100644 --- a/monkey/monkey_island/cc/services/run_local_monkey.py +++ b/monkey/monkey_island/cc/services/run_local_monkey.py @@ -6,12 +6,17 @@ import subprocess from pathlib import Path from shutil import copyfile -from monkey_island.cc.resources.agent_binaries import get_agent_executable_path -from monkey_island.cc.server_utils.consts import ISLAND_PORT +from monkey_island.cc.server_utils.consts import ISLAND_PORT, MONKEY_ISLAND_ABS_PATH from monkey_island.cc.services.utils.network_utils import local_ip_addresses logger = logging.getLogger(__name__) +AGENTS = {"linux": "monkey-linux-64", "windows": "monkey-windows-64.exe"} + + +class UnsupportedOSError(Exception): + pass + class LocalMonkeyRunService: DATA_DIR = None @@ -27,7 +32,7 @@ class LocalMonkeyRunService: def run_local_monkey(): # get the monkey executable suitable to run on the server try: - src_path = get_agent_executable_path(platform.system().lower()) + src_path = LocalMonkeyRunService._get_agent_executable_path(platform.system().lower()) except Exception as ex: logger.error(f"Error running agent from island: {ex}") return False, str(ex) @@ -55,3 +60,22 @@ class LocalMonkeyRunService: return False, "popen failed: %s" % exc return True, "" + + @staticmethod + def _get_agent_executable_path(os: str) -> Path: + try: + agent_path = LocalMonkeyRunService._get_executable_full_path(AGENTS[os]) + logger.debug(f'Local path for {os} executable is "{agent_path}"') + if not agent_path.is_file(): + logger.error(f"File {agent_path} not found") + + return agent_path + except KeyError: + logger.warning(f"No monkey executable could be found for the host os: {os}") + raise UnsupportedOSError( + f'No Agents are available for unsupported operating system "{os}"' + ) + + @staticmethod + def _get_executable_full_path(executable_filename: str) -> Path: + return Path(MONKEY_ISLAND_ABS_PATH) / "cc" / "binaries" / executable_filename From 152b486edeee6f37efe55d2aeac4c2083be1681c Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 9 Jun 2022 13:23:45 +0200 Subject: [PATCH 2/4] UI: Remove dropper config schema key Dropper configurations are removed entirely. --- .../ui/src/components/configuration-components/InternalConfig.js | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/InternalConfig.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/InternalConfig.js index 42a86dbff..89632d926 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/InternalConfig.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/InternalConfig.js @@ -6,7 +6,6 @@ const sectionOrder = [ 'network', 'island_server', 'exploits', - 'dropper', 'classes', 'general', 'testing' From 0b152942fb027a25f3451a2501769aee81732a14 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 9 Jun 2022 19:04:27 +0200 Subject: [PATCH 3/4] Island: Fix small docstrings --- monkey/monkey_island/cc/resources/agent_binaries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/agent_binaries.py b/monkey/monkey_island/cc/resources/agent_binaries.py index aced17c43..8d960b6a4 100644 --- a/monkey/monkey_island/cc/resources/agent_binaries.py +++ b/monkey/monkey_island/cc/resources/agent_binaries.py @@ -17,10 +17,10 @@ class AgentBinaries(AbstractResource): # Used by monkey. can't secure. def get(self, os): """ - Gets the agent binary based on the OS + Gets the agent binary for the specified OS :param os: Operating systems. Supported OS are: 'linux' and 'windows' - :return: file-like object with a filename same as the OS + :return: an agent binary file """ try: agent_binaries = { From 2415ddcea733b7902d4f7c66fa0798eed40c0053 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 9 Jun 2022 19:10:28 +0200 Subject: [PATCH 4/4] Island: Refactor LocalMonkerRunService to use AgentBinaryRepository * Register the data_dir in the DI container * Construct LocalRun resource with the LocalMonkeyRunService --- .../monkey_island/cc/resources/local_run.py | 5 +- .../monkey_island/cc/services/initialize.py | 7 +- .../cc/services/run_local_monkey.py | 80 ++++++++----------- 3 files changed, 45 insertions(+), 47 deletions(-) diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index 4e918e6c7..1bd68f6fb 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -12,6 +12,9 @@ from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService class LocalRun(AbstractResource): urls = ["/api/local-monkey"] + def __init__(self, local_monkey_run_service: LocalMonkeyRunService): + self._local_monkey_run_service = local_monkey_run_service + # API Spec: Both of these methods should be separated to their own resources # API Spec: This should be a REST endpoint, /api/monkeys or something @@ -30,7 +33,7 @@ class LocalRun(AbstractResource): def post(self): body = json.loads(request.data) if body.get("action") == "run": - local_run = LocalMonkeyRunService.run_local_monkey() + local_run = self._local_monkey_run_service.run_local_monkey() # API Spec: Feels weird to return "error_text" even when "is_running" is True return jsonify(is_running=local_run[0], error_text=local_run[1]) diff --git a/monkey/monkey_island/cc/services/initialize.py b/monkey/monkey_island/cc/services/initialize.py index f4e4c5b97..8722db760 100644 --- a/monkey/monkey_island/cc/services/initialize.py +++ b/monkey/monkey_island/cc/services/initialize.py @@ -26,6 +26,11 @@ AGENT_BINARIES_PATH = Path(MONKEY_ISLAND_ABS_PATH) / "cc" / "binaries" def initialize_services(data_dir: Path) -> DIContainer: container = DIContainer() + + # TODO: everything that is build with DI and expects Path in the constructor + # will use the same data_dir. Come up with a better way to inject + # the data_dir in the things that needed + container.register_instance(Path, data_dir) container.register_instance(AWSInstance, AWSInstance()) container.register_instance( @@ -33,10 +38,10 @@ def initialize_services(data_dir: Path) -> DIContainer: ) container.register_instance(AWSService, container.resolve(AWSService)) container.register_instance(IAgentBinaryRepository, _build_agent_binary_repository()) + container.register_instance(LocalMonkeyRunService, container.resolve(LocalMonkeyRunService)) # This is temporary until we get DI all worked out. PostBreachFilesService.initialize(container.resolve(IFileRepository)) - LocalMonkeyRunService.initialize(data_dir) AuthenticationService.initialize(data_dir, JsonFileUserDatastore(data_dir)) ReportService.initialize(container.resolve(AWSService)) diff --git a/monkey/monkey_island/cc/services/run_local_monkey.py b/monkey/monkey_island/cc/services/run_local_monkey.py index f78868a37..a54e2c6b9 100644 --- a/monkey/monkey_island/cc/services/run_local_monkey.py +++ b/monkey/monkey_island/cc/services/run_local_monkey.py @@ -1,49 +1,58 @@ import logging -import os import platform import stat import subprocess from pathlib import Path -from shutil import copyfile +from shutil import copyfileobj -from monkey_island.cc.server_utils.consts import ISLAND_PORT, MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.repository import AgentRetrievalError, IAgentBinaryRepository +from monkey_island.cc.server_utils.consts import ISLAND_PORT from monkey_island.cc.services.utils.network_utils import local_ip_addresses logger = logging.getLogger(__name__) -AGENTS = {"linux": "monkey-linux-64", "windows": "monkey-windows-64.exe"} - - -class UnsupportedOSError(Exception): - pass +AGENT_NAMES = {"linux": "monkey-linux-64", "windows": "monkey-windows-64.exe"} class LocalMonkeyRunService: - DATA_DIR = None + def __init__(self, data_dir: Path, agent_binary_repository: IAgentBinaryRepository): + self._data_dir = data_dir + self._agent_binary_repository = agent_binary_repository - # TODO: A number of these services should be instance objects instead of - # static/singleton hybrids. At the moment, this requires invasive refactoring that's - # not a priority. - @classmethod - def initialize(cls, data_dir: Path): - cls.DATA_DIR = data_dir - - @staticmethod - def run_local_monkey(): + def run_local_monkey(self): # get the monkey executable suitable to run on the server + operating_system = platform.system().lower() try: - src_path = LocalMonkeyRunService._get_agent_executable_path(platform.system().lower()) - except Exception as ex: - logger.error(f"Error running agent from island: {ex}") - return False, str(ex) + agents = { + "linux": self._agent_binary_repository.get_linux_binary, + "windows": self._agent_binary_repository.get_windows_binary, + } - dest_path = LocalMonkeyRunService.DATA_DIR / src_path.name + agent_binary = agents[platform.system().lower()]() + except AgentRetrievalError as err: + logger.error( + f"No Agent can be retrieved for the specified operating system" + f'"{operating_system}"' + ) + return False, str(err) + except KeyError as err: + logger.error( + f"No Agents are available for unsupported operating system" f'"{operating_system}"' + ) + return False, str(err) + except Exception as err: + logger.error(f"Error running agent from island: {err}") + return False, str(err) + + dest_path = self._data_dir / AGENT_NAMES[operating_system] # copy the executable to temp path (don't run the monkey from its current location as it may # delete itself) try: - copyfile(src_path, dest_path) - os.chmod(dest_path, stat.S_IRWXU | stat.S_IRWXG) + with open(dest_path, "wb") as dest_agent: + copyfileobj(agent_binary, dest_agent) + + dest_path.chmod(stat.S_IRWXU | stat.S_IRWXG) except Exception as exc: logger.error("Copy file failed", exc_info=True) return False, "Copy file failed: %s" % exc @@ -54,28 +63,9 @@ class LocalMonkeyRunService: port = ISLAND_PORT args = [str(dest_path), "m0nk3y", "-s", f"{ip}:{port}"] - subprocess.Popen(args, cwd=LocalMonkeyRunService.DATA_DIR) + subprocess.Popen(args, cwd=self._data_dir) except Exception as exc: logger.error("popen failed", exc_info=True) return False, "popen failed: %s" % exc return True, "" - - @staticmethod - def _get_agent_executable_path(os: str) -> Path: - try: - agent_path = LocalMonkeyRunService._get_executable_full_path(AGENTS[os]) - logger.debug(f'Local path for {os} executable is "{agent_path}"') - if not agent_path.is_file(): - logger.error(f"File {agent_path} not found") - - return agent_path - except KeyError: - logger.warning(f"No monkey executable could be found for the host os: {os}") - raise UnsupportedOSError( - f'No Agents are available for unsupported operating system "{os}"' - ) - - @staticmethod - def _get_executable_full_path(executable_filename: str) -> Path: - return Path(MONKEY_ISLAND_ABS_PATH) / "cc" / "binaries" / executable_filename