From 126208972ee7842d444d720d1a25a3f81a9a4ba1 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 11 May 2023 20:13:07 +0000 Subject: [PATCH 1/7] Agent: Extract function file_exists_at_destination --- monkey/infection_monkey/dropper.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 7fdd6859c87..e602c463919 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -39,6 +39,13 @@ MOVEFILE_DELAY_UNTIL_REBOOT = 4 +def file_exists_at_destination(source_path, destination_path) -> bool: + try: + return filecmp.cmp(source_path, destination_path) + except OSError: + return False + + class MonkeyDrops(object): def __init__(self, args): arg_parser = argparse.ArgumentParser() @@ -61,17 +68,16 @@ def start(self): logger.error("No destination path specified") return False - # we copy/move only in case path is different - try: - file_moved = filecmp.cmp(self._config["source_path"], self._config["destination_path"]) - except OSError: - file_moved = False + source_path = self._config["source_path"] + destination_path = self._config["destination_path"] - if not file_moved and os.path.exists(self._config["destination_path"]): - os.remove(self._config["destination_path"]) + # we copy/move only in case path is different + file_exists = file_exists_at_destination(source_path, destination_path) + if not file_exists and os.path.exists(destination_path): + os.remove(destination_path) # always try to move the file first - if not file_moved: + if not file_exists: try: shutil.move(self._config["source_path"], self._config["destination_path"]) @@ -81,7 +87,7 @@ def start(self): self._config["destination_path"], ) - file_moved = True + file_exists = True except (WindowsError, IOError, OSError) as exc: logger.debug( "Error moving source file '%s' into '%s': %s", @@ -91,7 +97,7 @@ def start(self): ) # if file still need to change path, copy it - if not file_moved: + if not file_exists: try: shutil.copy(self._config["source_path"], self._config["destination_path"]) @@ -182,7 +188,6 @@ def cleanup(self): if self._config["source_path"].lower() != self._config[ "destination_path" ].lower() and os.path.exists(self._config["source_path"]): - # try removing the file first try: os.remove(self._config["source_path"]) From 1bcdc4516e4e6b4567408cb5e0fec856d2f4aa99 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 11 May 2023 20:21:35 +0000 Subject: [PATCH 2/7] Agent: Extract method _move_file --- monkey/infection_monkey/dropper.py | 35 +++++++++++++----------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index e602c463919..6f216ddb73d 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -77,27 +77,9 @@ def start(self): os.remove(destination_path) # always try to move the file first - if not file_exists: - try: - shutil.move(self._config["source_path"], self._config["destination_path"]) - - logger.info( - "Moved source file '%s' into '%s'", - self._config["source_path"], - self._config["destination_path"], - ) - - file_exists = True - except (WindowsError, IOError, OSError) as exc: - logger.debug( - "Error moving source file '%s' into '%s': %s", - self._config["source_path"], - self._config["destination_path"], - exc, - ) + if not file_exists and not self._move_file(source_path, destination_path): + # if file still need to change path, copy it - # if file still need to change path, copy it - if not file_exists: try: shutil.copy(self._config["source_path"], self._config["destination_path"]) @@ -181,6 +163,19 @@ def start(self): if monkey_process.poll() is not None: logger.warning("Seems like monkey died too soon") + def _move_file(self, source_path, destination_path) -> bool: + try: + shutil.move(source_path, destination_path) + logger.info(f"Moved source file '{source_path}' into '{destination_path}'") + except (WindowsError, IOError, OSError) as exc: + logger.debug( + f"Error moving source file '{source_path}' into '{destination_path}': {exc}" + ) + + return False + + return True + def cleanup(self): logger.info("Cleaning up the dropper") From eb207c702828c75c74d4e04939af5b2da244a9d9 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 11 May 2023 20:24:04 +0000 Subject: [PATCH 3/7] Agent: Extract method _copy_file --- monkey/infection_monkey/dropper.py | 40 ++++++++++++++---------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 6f216ddb73d..073dfaad640 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -76,27 +76,12 @@ def start(self): if not file_exists and os.path.exists(destination_path): os.remove(destination_path) - # always try to move the file first - if not file_exists and not self._move_file(source_path, destination_path): - # if file still need to change path, copy it - - try: - shutil.copy(self._config["source_path"], self._config["destination_path"]) - - logger.info( - "Copied source file '%s' into '%s'", - self._config["source_path"], - self._config["destination_path"], - ) - except (WindowsError, IOError, OSError) as exc: - logger.error( - "Error copying source file '%s' into '%s': %s", - self._config["source_path"], - self._config["destination_path"], - exc, - ) - - return False + if ( + not file_exists + and not self._move_file(source_path, destination_path) + and not self._copy_file(source_path, destination_path) + ): + return False if sys.platform == "win32": dropper_date_reference_path = DATE_REFERENCE_PATH_WINDOWS @@ -176,6 +161,19 @@ def _move_file(self, source_path, destination_path) -> bool: return True + def _copy_file(self, source_path, destination_path) -> bool: + try: + shutil.copy(source_path, destination_path) + logger.info(f"Copied source file '{source_path}' into '{destination_path}'") + except (WindowsError, IOError, OSError) as exc: + logger.debug( + f"Error copying source file '{source_path}' into '{destination_path}': {exc}" + ) + + return False + + return True + def cleanup(self): logger.info("Cleaning up the dropper") From f2781fe3e27b35576ee2e37a1ae600b38b38b7f0 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 11 May 2023 20:27:25 +0000 Subject: [PATCH 4/7] Agent: Extract function get_date_reference_path --- monkey/infection_monkey/dropper.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 073dfaad640..d6733936f6a 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -9,8 +9,9 @@ import time from pathlib import PosixPath, WindowsPath +from common import OperatingSystem from common.utils.argparse_types import positive_int -from common.utils.environment import is_windows_os +from common.utils.environment import get_os, is_windows_os from infection_monkey.utils.commands import ( build_monkey_commandline_explicitly, get_monkey_commandline_linux, @@ -20,11 +21,8 @@ if "win32" == sys.platform: from win32process import DETACHED_PROCESS - - DATE_REFERENCE_PATH_WINDOWS = os.path.expandvars(WindowsPath(r"%windir%\system32\kernel32.dll")) else: DETACHED_PROCESS = 0 - DATE_REFERENCE_PATH_LINUX = PosixPath("/bin/sh") # Linux doesn't have WindowsError try: @@ -46,6 +44,13 @@ def file_exists_at_destination(source_path, destination_path) -> bool: return False +def get_date_reference_path(): + if get_os() == OperatingSystem.WINDOWS: + return os.path.expandvars(WindowsPath(r"%windir%\system32\kernel32.dll")) + else: + return PosixPath("/bin/sh") + + class MonkeyDrops(object): def __init__(self, args): arg_parser = argparse.ArgumentParser() @@ -83,10 +88,7 @@ def start(self): ): return False - if sys.platform == "win32": - dropper_date_reference_path = DATE_REFERENCE_PATH_WINDOWS - else: - dropper_date_reference_path = DATE_REFERENCE_PATH_LINUX + dropper_date_reference_path = get_date_reference_path() try: ref_stat = os.stat(dropper_date_reference_path) From 66e32133ed6606a143ca905b55af38834db56824 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 11 May 2023 20:32:12 +0000 Subject: [PATCH 5/7] Agent: Extract method _try_update_access_time --- monkey/infection_monkey/dropper.py | 31 ++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index d6733936f6a..b36f081b1b7 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -88,20 +88,7 @@ def start(self): ): return False - dropper_date_reference_path = get_date_reference_path() - - try: - ref_stat = os.stat(dropper_date_reference_path) - except OSError: - logger.warning( - "Cannot set reference date using '%s', file not found", - dropper_date_reference_path, - ) - else: - try: - os.utime(self._config["destination_path"], (ref_stat.st_atime, ref_stat.st_mtime)) - except OSError: - logger.warning("Cannot set reference date to destination file") + MonkeyDrops._try_update_access_time(destination_path) monkey_options = build_monkey_commandline_explicitly( parent=self.opts.parent, @@ -176,6 +163,22 @@ def _copy_file(self, source_path, destination_path) -> bool: return True + @staticmethod + def _try_update_access_time(destination_path): + dropper_date_reference_path = get_date_reference_path() + + try: + ref_stat = os.stat(dropper_date_reference_path) + except OSError: + logger.warning( + f"Cannot set reference date using '{dropper_date_reference_path}', file not found" + ) + else: + try: + os.utime(destination_path, (ref_stat.st_atime, ref_stat.st_mtime)) + except OSError: + logger.warning("Cannot set reference date to destination file") + def cleanup(self): logger.info("Cleaning up the dropper") From 58dbdeabc1c241faf64014c5068f298ec49c57ea Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 11 May 2023 20:35:56 +0000 Subject: [PATCH 6/7] Agent: Extract method _run_monkey --- monkey/infection_monkey/dropper.py | 93 ++++++++++++++---------------- 1 file changed, 44 insertions(+), 49 deletions(-) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index b36f081b1b7..4eb4a574ef9 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -11,7 +11,7 @@ from common import OperatingSystem from common.utils.argparse_types import positive_int -from common.utils.environment import get_os, is_windows_os +from common.utils.environment import get_os from infection_monkey.utils.commands import ( build_monkey_commandline_explicitly, get_monkey_commandline_linux, @@ -19,11 +19,6 @@ ) from infection_monkey.utils.file_utils import mark_file_for_deletion_on_windows -if "win32" == sys.platform: - from win32process import DETACHED_PROCESS -else: - DETACHED_PROCESS = 0 - # Linux doesn't have WindowsError try: WindowsError @@ -89,49 +84,7 @@ def start(self): return False MonkeyDrops._try_update_access_time(destination_path) - - monkey_options = build_monkey_commandline_explicitly( - parent=self.opts.parent, - servers=self.opts.servers, - depth=self.opts.depth, - location=None, - ) - - if is_windows_os(): - monkey_commandline = get_monkey_commandline_windows( - self._config["destination_path"], monkey_options - ) - - monkey_process = subprocess.Popen( - monkey_commandline, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - close_fds=True, - creationflags=DETACHED_PROCESS, - ) - else: - dest_path = self._config["destination_path"] - # In Linux, we need to change the directory first, which is done - # using thw `cwd` argument in `subprocess.Popen` below - - monkey_commandline = get_monkey_commandline_linux(dest_path, monkey_options) - - monkey_process = subprocess.Popen( - monkey_commandline, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - close_fds=True, - cwd="/".join(dest_path.split("/")[0:-1]), - creationflags=DETACHED_PROCESS, - ) - - logger.info( - "Executed monkey process (PID=%d) with command line: %s", - monkey_process.pid, - " ".join(monkey_commandline), - ) + monkey_process = self._run_monkey(destination_path) time.sleep(3) if monkey_process.poll() is not None: @@ -179,6 +132,48 @@ def _try_update_access_time(destination_path): except OSError: logger.warning("Cannot set reference date to destination file") + def _run_monkey(self, destination_path) -> subprocess.Popen: + monkey_options = build_monkey_commandline_explicitly( + parent=self.opts.parent, + servers=self.opts.servers, + depth=self.opts.depth, + location=None, + ) + + if get_os() == OperatingSystem.WINDOWS: + from win32process import DETACHED_PROCESS + + monkey_commandline = get_monkey_commandline_windows(destination_path, monkey_options) + + monkey_process = subprocess.Popen( + monkey_commandline, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True, + creationflags=DETACHED_PROCESS, + ) + else: + # In Linux, we need to change the directory first, which is done + # using thw `cwd` argument in `subprocess.Popen` below + + monkey_commandline = get_monkey_commandline_linux(destination_path, monkey_options) + + monkey_process = subprocess.Popen( + monkey_commandline, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True, + cwd="/".join(destination_path.split("/")[0:-1]), + ) + + logger.info( + f"Executed monkey process (PID={monkey_process.pid}) " + f"with command line: {' '.join(monkey_commandline)}" + ) + return monkey_process + def cleanup(self): logger.info("Cleaning up the dropper") From 5e16bee25d9bad8c90f39467f2ecb44d7cc8a9e0 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 11 May 2023 20:40:57 +0000 Subject: [PATCH 7/7] Agent: Extract method _remove_file --- monkey/infection_monkey/dropper.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 4eb4a574ef9..09a2ec7fd08 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -177,20 +177,22 @@ def _run_monkey(self, destination_path) -> subprocess.Popen: def cleanup(self): logger.info("Cleaning up the dropper") + source_path = self._config["source_path"] + try: - if self._config["source_path"].lower() != self._config[ - "destination_path" - ].lower() and os.path.exists(self._config["source_path"]): - # try removing the file first - try: - os.remove(self._config["source_path"]) - except Exception as exc: - logger.debug( - "Error removing source file '%s': %s", self._config["source_path"], exc - ) - - # mark the file for removal on next boot - mark_file_for_deletion_on_windows(WindowsPath(self._config["source_path"])) + if source_path.lower() != self._config["destination_path"].lower() and os.path.exists( + source_path + ): + self._remove_file(source_path) logger.info("Dropper cleanup complete") except AttributeError: logger.error("Invalid configuration options. Failing") + + def _remove_file(self, path): + try: + os.remove(path) + except Exception as exc: + logger.debug(f"Error removing source file '{path}': {exc}") + + # mark the file for removal on next boot + mark_file_for_deletion_on_windows(WindowsPath(path))