From 2e48d9ead9e944902725ad1ec985f868a4a25237 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Fri, 25 Mar 2022 14:11:04 +0530 Subject: [PATCH 01/15] Agent: Return PostBreachData in PBA's run() instead of sending PostBreachTelem --- monkey/infection_monkey/post_breach/pba.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index 1ee4c3cdc..ab3a004f0 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -2,8 +2,8 @@ import logging import subprocess from common.utils.attack_utils import ScanStatus +from infection_monkey.i_puppet.i_puppet import PostBreachData from infection_monkey.telemetry.attack.t1064_telem import T1064Telem -from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.utils.environment import is_windows_os logger = logging.getLogger(__name__) @@ -35,7 +35,7 @@ class PBA: T1064Telem( ScanStatus.USED, f"Scripts were used to execute {self.name} post breach action." ).send() - PostBreachTelem(self, result).send() + return PostBreachData(self.name, self.command, result) else: logger.debug(f"No command available for PBA '{self.name}' on current OS, skipping.") From ee24538407fd8cb80526d11a25b5c73e97d6260c Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Fri, 25 Mar 2022 14:21:15 +0530 Subject: [PATCH 02/15] Agent: Modify clear command history PBA to return PostBreachData --- .../post_breach/actions/clear_command_history.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/clear_command_history.py b/monkey/infection_monkey/post_breach/actions/clear_command_history.py index f4aa5ad7b..03cb77be0 100644 --- a/monkey/infection_monkey/post_breach/actions/clear_command_history.py +++ b/monkey/infection_monkey/post_breach/actions/clear_command_history.py @@ -1,11 +1,11 @@ import subprocess from common.common_consts.post_breach_consts import POST_BREACH_CLEAR_CMD_HISTORY +from infection_monkey.i_puppet.i_puppet import PostBreachData from infection_monkey.post_breach.clear_command_history.clear_command_history import ( get_commands_to_clear_command_history, ) from infection_monkey.post_breach.pba import PBA -from infection_monkey.telemetry.post_breach_telem import PostBreachTelem class ClearCommandHistory(PBA): @@ -15,7 +15,8 @@ class ClearCommandHistory(PBA): def run(self): results = [pba.run() for pba in self.clear_command_history_PBA_list()] if results: - PostBreachTelem(self, results).send() + # Note: `self.command` is empty here + return PostBreachData(self.name, self.command, results) def clear_command_history_PBA_list(self): return self.CommandHistoryPBAGenerator().get_clear_command_history_pbas() From 24ba5e37da6843da7725eeb5f5820577f68effb7 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Fri, 25 Mar 2022 20:48:54 +0530 Subject: [PATCH 03/15] Agent: Modify collect running processes PBA to return PostBreachData --- .../post_breach/actions/collect_processes_list.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/collect_processes_list.py b/monkey/infection_monkey/post_breach/actions/collect_processes_list.py index 181fd5988..260d4bf18 100644 --- a/monkey/infection_monkey/post_breach/actions/collect_processes_list.py +++ b/monkey/infection_monkey/post_breach/actions/collect_processes_list.py @@ -3,6 +3,7 @@ import logging import psutil from common.common_consts.post_breach_consts import POST_BREACH_PROCESS_LIST_COLLECTION +from infection_monkey.i_puppet.i_puppet import PostBreachData from infection_monkey.post_breach.pba import PBA logger = logging.getLogger(__name__) @@ -16,9 +17,6 @@ except NameError: class ProcessListCollection(PBA): - # TODO: (?) Move all PBA consts into their classes - display_name = POST_BREACH_PROCESS_LIST_COLLECTION - def __init__(self): super().__init__(POST_BREACH_PROCESS_LIST_COLLECTION) @@ -54,4 +52,5 @@ class ProcessListCollection(PBA): } continue - return self.command, (processes, success_state) + # No command here; used psutil + return PostBreachData(self.name, "", (processes, success_state)) From 5a8e8850a584756c69f94781ed0be3562bc7021e Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Fri, 25 Mar 2022 21:00:24 +0530 Subject: [PATCH 04/15] Agent: Modify schedule jobs PBA to return PostBreachData --- monkey/infection_monkey/post_breach/actions/schedule_jobs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py index e7845968a..7bffce8a2 100644 --- a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py +++ b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py @@ -21,5 +21,6 @@ class ScheduleJobs(PBA): ) def run(self): - super(ScheduleJobs, self).run() + post_breach_data = super(ScheduleJobs, self).run() remove_scheduled_jobs() + return post_breach_data From 0b2ac96deef7dcda6b80a24e52358a767afdccb9 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Fri, 25 Mar 2022 21:00:57 +0530 Subject: [PATCH 05/15] Agent: Modify use signed scripts PBA to return PostBreachData --- .../infection_monkey/post_breach/actions/use_signed_scripts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py index 4f0c6bd90..085b73bb9 100644 --- a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py +++ b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py @@ -24,7 +24,7 @@ class SignedScriptProxyExecution(PBA): original_comspec = subprocess.check_output( # noqa: DUO116 "if defined COMSPEC echo %COMSPEC%", shell=True ).decode() - super().run() + return super().run() except Exception as e: logger.warning( f"An exception occurred on running PBA " From 29d40f8e9d913c175e95157ad240d697addc706a Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 28 Mar 2022 13:22:46 +0530 Subject: [PATCH 06/15] Agent: Modify communicates as backdoor user PBA to return PostBreachData --- .../actions/communicate_as_backdoor_user.py | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py index 03126dec0..d93be17e1 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py @@ -5,8 +5,8 @@ import string import subprocess from common.common_consts.post_breach_consts import POST_BREACH_COMMUNICATE_AS_BACKDOOR_USER +from infection_monkey.i_puppet.i_puppet import PostBreachData from infection_monkey.post_breach.pba import PBA -from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.utils.auto_new_user_factory import create_auto_new_user from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.new_user_error import NewUserError @@ -49,11 +49,16 @@ class CommunicateAsBackdoorUser(PBA): ) ) exit_status = new_user.run_as(http_request_commandline) - self.send_result_telemetry(exit_status, http_request_commandline, username) + result = self._get_result_for_telemetry( + exit_status, http_request_commandline, username + ) + # `command` is empty here; we could get the command from `new_user` but that + # doesn't work either since Windows doesn't use a command, it uses win32 modules + return PostBreachData(self.name, "", result) except subprocess.CalledProcessError as e: - PostBreachTelem(self, (e.output.decode(), False)).send() + return PostBreachData(self.name, "", (e.output.decode(), False)) except NewUserError as e: - PostBreachTelem(self, (str(e), False)).send() + return PostBreachData(self.name, "", (str(e), False)) @staticmethod def get_random_new_user_name(): @@ -79,28 +84,25 @@ class CommunicateAsBackdoorUser(PBA): format_string = "wget -O/dev/null -q {url} --method=HEAD --timeout=10" return format_string.format(url=url) - def send_result_telemetry(self, exit_status, commandline, username): + def _get_result_for_telemetry(self, exit_status, commandline, username): """ - Parses the result of the command and sends telemetry accordingly. + Parses the result of the command and returns it to be sent as telemetry from the master. :param exit_status: In both Windows and Linux, 0 exit code indicates success. :param commandline: Exact commandline which was executed, for reporting back. :param username: Username from which the command was executed, for reporting back. """ if exit_status == 0: - PostBreachTelem( - self, (CREATED_PROCESS_AS_USER_SUCCESS_FORMAT.format(commandline, username), True) - ).send() + result = (CREATED_PROCESS_AS_USER_SUCCESS_FORMAT.format(commandline, username), True) else: - PostBreachTelem( - self, - ( - CREATED_PROCESS_AS_USER_FAILED_FORMAT.format( - commandline, username, exit_status, twos_complement(exit_status) - ), - False, + result = ( + CREATED_PROCESS_AS_USER_FAILED_FORMAT.format( + commandline, username, exit_status, twos_complement(exit_status) ), - ).send() + False, + ) + + return result def twos_complement(exit_status): From 8418a5ce771d9271ea121c2f39905a3a0356d334 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 28 Mar 2022 13:56:18 +0530 Subject: [PATCH 07/15] Agent: Modify modify shell startup files PBA to return PostBreachData --- .../post_breach/actions/modify_shell_startup_files.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py index 3283bcc94..ebaf9dfc1 100644 --- a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py +++ b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py @@ -1,11 +1,11 @@ import subprocess from common.common_consts.post_breach_consts import POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION +from infection_monkey.i_puppet.i_puppet import PostBreachData from infection_monkey.post_breach.pba import PBA from infection_monkey.post_breach.shell_startup_files.shell_startup_files_modification import ( get_commands_to_modify_shell_startup_files, ) -from infection_monkey.telemetry.post_breach_telem import PostBreachTelem class ModifyShellStartupFiles(PBA): @@ -27,7 +27,9 @@ class ModifyShellStartupFiles(PBA): False, ) ] - PostBreachTelem(self, results).send() + # `command` is empty here since multiple commands were run and the results + # were aggregated to send the telemetry just once + return PostBreachData(self.name, "", results).send() def modify_shell_startup_PBA_list(self): return self.ShellStartupPBAGenerator().get_modify_shell_startup_pbas() From 28ff1128722478019b44247caef806a12561bd47 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 28 Mar 2022 17:15:06 +0530 Subject: [PATCH 08/15] Agent: Modify hide files PBA to return PostBreachData --- monkey/infection_monkey/master/automated_master.py | 10 ++++++++-- .../infection_monkey/post_breach/actions/hide_files.py | 8 +++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index c1ced257c..f70d90b46 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -198,8 +198,14 @@ class AutomatedMaster(IMaster): name = pba[0] options = pba[1] - result = self._puppet.run_pba(name, options) - self._telemetry_messenger.send_telemetry(PostBreachTelem(result)) + # TEMPORARY; TO AVOID ERRORS SINCE THIS ISN'T IMPLEMENTED YET + if name == "Custom": + return + + for pba_data in self._puppet.run_pba(name, options): + self._telemetry_messenger.send_telemetry( + PostBreachTelem(pba_data.display_name, pba_data.command, pba_data.result) + ) def _can_propagate(self) -> bool: return True diff --git a/monkey/infection_monkey/post_breach/actions/hide_files.py b/monkey/infection_monkey/post_breach/actions/hide_files.py index c6e1d1a6b..6bbeefa68 100644 --- a/monkey/infection_monkey/post_breach/actions/hide_files.py +++ b/monkey/infection_monkey/post_breach/actions/hide_files.py @@ -1,6 +1,6 @@ from common.common_consts.post_breach_consts import POST_BREACH_HIDDEN_FILES +from infection_monkey.i_puppet.i_puppet import PostBreachData from infection_monkey.post_breach.pba import PBA -from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.hidden_files import ( cleanup_hidden_files, @@ -29,10 +29,12 @@ class HiddenFiles(PBA): linux_cmd=" ".join(linux_cmds), windows_cmd=windows_cmds, ) - super(HiddenFiles, self).run() + yield super(HiddenFiles, self).run() + if is_windows_os(): # use winAPI result, status = get_winAPI_to_hide_files() - PostBreachTelem(self, (result, status)).send() + # no command here, used WinAPI + yield PostBreachData(self.name, "", (result, status)) # cleanup hidden files and folders cleanup_hidden_files(is_windows_os()) From ec2b2beca5b15aee5a91b77b27431dc94bdaddf7 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 28 Mar 2022 17:16:50 +0530 Subject: [PATCH 09/15] Agent: Modify PBAs to yield PostBreachData instead of returning it This is done mainly because of the hide files PBA which needs to send telemetry two times. It also makes more sense to do it this way so that it's easier to send telemetry multiple times in any PBA. --- .../post_breach/actions/clear_command_history.py | 2 +- .../post_breach/actions/collect_processes_list.py | 2 +- .../post_breach/actions/communicate_as_backdoor_user.py | 6 +++--- .../post_breach/actions/modify_shell_startup_files.py | 2 +- monkey/infection_monkey/post_breach/pba.py | 2 +- monkey/infection_monkey/puppet/mock_puppet.py | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/clear_command_history.py b/monkey/infection_monkey/post_breach/actions/clear_command_history.py index 03cb77be0..9baa3dc67 100644 --- a/monkey/infection_monkey/post_breach/actions/clear_command_history.py +++ b/monkey/infection_monkey/post_breach/actions/clear_command_history.py @@ -16,7 +16,7 @@ class ClearCommandHistory(PBA): results = [pba.run() for pba in self.clear_command_history_PBA_list()] if results: # Note: `self.command` is empty here - return PostBreachData(self.name, self.command, results) + yield PostBreachData(self.name, self.command, results) def clear_command_history_PBA_list(self): return self.CommandHistoryPBAGenerator().get_clear_command_history_pbas() diff --git a/monkey/infection_monkey/post_breach/actions/collect_processes_list.py b/monkey/infection_monkey/post_breach/actions/collect_processes_list.py index 260d4bf18..782c771dc 100644 --- a/monkey/infection_monkey/post_breach/actions/collect_processes_list.py +++ b/monkey/infection_monkey/post_breach/actions/collect_processes_list.py @@ -53,4 +53,4 @@ class ProcessListCollection(PBA): continue # No command here; used psutil - return PostBreachData(self.name, "", (processes, success_state)) + yield PostBreachData(self.name, "", (processes, success_state)) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py index d93be17e1..36c96b126 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py @@ -54,11 +54,11 @@ class CommunicateAsBackdoorUser(PBA): ) # `command` is empty here; we could get the command from `new_user` but that # doesn't work either since Windows doesn't use a command, it uses win32 modules - return PostBreachData(self.name, "", result) + yield PostBreachData(self.name, "", result) except subprocess.CalledProcessError as e: - return PostBreachData(self.name, "", (e.output.decode(), False)) + yield PostBreachData(self.name, "", (e.output.decode(), False)) except NewUserError as e: - return PostBreachData(self.name, "", (str(e), False)) + yield PostBreachData(self.name, "", (str(e), False)) @staticmethod def get_random_new_user_name(): diff --git a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py index ebaf9dfc1..75b2e1a55 100644 --- a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py +++ b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py @@ -29,7 +29,7 @@ class ModifyShellStartupFiles(PBA): ] # `command` is empty here since multiple commands were run and the results # were aggregated to send the telemetry just once - return PostBreachData(self.name, "", results).send() + yield PostBreachData(self.name, "", results).send() def modify_shell_startup_PBA_list(self): return self.ShellStartupPBAGenerator().get_modify_shell_startup_pbas() diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index ab3a004f0..449c06186 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -35,7 +35,7 @@ class PBA: T1064Telem( ScanStatus.USED, f"Scripts were used to execute {self.name} post breach action." ).send() - return PostBreachData(self.name, self.command, result) + yield PostBreachData(self.name, self.command, result) else: logger.debug(f"No command available for PBA '{self.name}' on current OS, skipping.") diff --git a/monkey/infection_monkey/puppet/mock_puppet.py b/monkey/infection_monkey/puppet/mock_puppet.py index 0196076ad..5f707acd7 100644 --- a/monkey/infection_monkey/puppet/mock_puppet.py +++ b/monkey/infection_monkey/puppet/mock_puppet.py @@ -53,9 +53,9 @@ class MockPuppet(IPuppet): logger.debug(f"run_pba({name}, {options})") if name == "AccountDiscovery": - return PostBreachData(name, "pba command 1", ["pba result 1", True]) + yield PostBreachData(name, "pba command 1", ["pba result 1", True]) else: - return PostBreachData(name, "pba command 2", ["pba result 2", False]) + yield PostBreachData(name, "pba command 2", ["pba result 2", False]) def ping(self, host: str, timeout: float = 1) -> PingScanData: logger.debug(f"run_ping({host}, {timeout})") From 778f2305898bfa12251299818ba241eb3e067656 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 28 Mar 2022 20:26:03 +0530 Subject: [PATCH 10/15] Agent: Modify remaining PBAs to yield PostBreachData --- monkey/infection_monkey/post_breach/actions/schedule_jobs.py | 2 +- .../infection_monkey/post_breach/actions/use_signed_scripts.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py index 7bffce8a2..8846efcf9 100644 --- a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py +++ b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py @@ -23,4 +23,4 @@ class ScheduleJobs(PBA): def run(self): post_breach_data = super(ScheduleJobs, self).run() remove_scheduled_jobs() - return post_breach_data + yield post_breach_data diff --git a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py index 085b73bb9..984fc0f66 100644 --- a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py +++ b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py @@ -24,7 +24,7 @@ class SignedScriptProxyExecution(PBA): original_comspec = subprocess.check_output( # noqa: DUO116 "if defined COMSPEC echo %COMSPEC%", shell=True ).decode() - return super().run() + yield super().run() except Exception as e: logger.warning( f"An exception occurred on running PBA " From 61ff95b5682a97b12923cecf830f77c256d179ec Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Tue, 29 Mar 2022 11:13:33 +0530 Subject: [PATCH 11/15] Agent: Modify PBAs to return Iterable[PostBreachData] --- .../post_breach/actions/clear_command_history.py | 5 +++-- .../post_breach/actions/collect_processes_list.py | 3 ++- .../post_breach/actions/communicate_as_backdoor_user.py | 8 +++++--- monkey/infection_monkey/post_breach/actions/hide_files.py | 6 ++++-- .../post_breach/actions/modify_shell_startup_files.py | 3 ++- .../infection_monkey/post_breach/actions/schedule_jobs.py | 4 ++-- .../post_breach/actions/use_signed_scripts.py | 3 ++- monkey/infection_monkey/post_breach/pba.py | 7 +++++-- 8 files changed, 25 insertions(+), 14 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/clear_command_history.py b/monkey/infection_monkey/post_breach/actions/clear_command_history.py index 9baa3dc67..036c32d25 100644 --- a/monkey/infection_monkey/post_breach/actions/clear_command_history.py +++ b/monkey/infection_monkey/post_breach/actions/clear_command_history.py @@ -15,8 +15,9 @@ class ClearCommandHistory(PBA): def run(self): results = [pba.run() for pba in self.clear_command_history_PBA_list()] if results: - # Note: `self.command` is empty here - yield PostBreachData(self.name, self.command, results) + # `self.command` is empty here + self.pba_data.append(PostBreachData(self.name, self.command, results)) + return self.pba_data def clear_command_history_PBA_list(self): return self.CommandHistoryPBAGenerator().get_clear_command_history_pbas() diff --git a/monkey/infection_monkey/post_breach/actions/collect_processes_list.py b/monkey/infection_monkey/post_breach/actions/collect_processes_list.py index 782c771dc..5f18e0e33 100644 --- a/monkey/infection_monkey/post_breach/actions/collect_processes_list.py +++ b/monkey/infection_monkey/post_breach/actions/collect_processes_list.py @@ -53,4 +53,5 @@ class ProcessListCollection(PBA): continue # No command here; used psutil - yield PostBreachData(self.name, "", (processes, success_state)) + self.pba_data.append(PostBreachData(self.name, "", (processes, success_state))) + return self.pba_data diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py index 36c96b126..73ef0fa3b 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py @@ -54,11 +54,13 @@ class CommunicateAsBackdoorUser(PBA): ) # `command` is empty here; we could get the command from `new_user` but that # doesn't work either since Windows doesn't use a command, it uses win32 modules - yield PostBreachData(self.name, "", result) + self.pba_data.append(PostBreachData(self.name, "", result)) except subprocess.CalledProcessError as e: - yield PostBreachData(self.name, "", (e.output.decode(), False)) + self.pba_data.append(PostBreachData(self.name, "", (e.output.decode(), False))) except NewUserError as e: - yield PostBreachData(self.name, "", (str(e), False)) + self.pba_data.append(PostBreachData(self.name, "", (str(e), False))) + finally: + return self.pba_data @staticmethod def get_random_new_user_name(): diff --git a/monkey/infection_monkey/post_breach/actions/hide_files.py b/monkey/infection_monkey/post_breach/actions/hide_files.py index 6bbeefa68..1a2f3472d 100644 --- a/monkey/infection_monkey/post_breach/actions/hide_files.py +++ b/monkey/infection_monkey/post_breach/actions/hide_files.py @@ -29,12 +29,14 @@ class HiddenFiles(PBA): linux_cmd=" ".join(linux_cmds), windows_cmd=windows_cmds, ) - yield super(HiddenFiles, self).run() + super(HiddenFiles, self).run() if is_windows_os(): # use winAPI result, status = get_winAPI_to_hide_files() # no command here, used WinAPI - yield PostBreachData(self.name, "", (result, status)) + self.pba_data.append(PostBreachData(self.name, "", (result, status))) # cleanup hidden files and folders cleanup_hidden_files(is_windows_os()) + + return self.pba_data diff --git a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py index 75b2e1a55..bb1a653f8 100644 --- a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py +++ b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py @@ -29,7 +29,8 @@ class ModifyShellStartupFiles(PBA): ] # `command` is empty here since multiple commands were run and the results # were aggregated to send the telemetry just once - yield PostBreachData(self.name, "", results).send() + self.pba_data.append(PostBreachData(self.name, "", results)) + return self.pba_data def modify_shell_startup_PBA_list(self): return self.ShellStartupPBAGenerator().get_modify_shell_startup_pbas() diff --git a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py index 8846efcf9..37649488b 100644 --- a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py +++ b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py @@ -21,6 +21,6 @@ class ScheduleJobs(PBA): ) def run(self): - post_breach_data = super(ScheduleJobs, self).run() + super(ScheduleJobs, self).run() remove_scheduled_jobs() - yield post_breach_data + return self.pba_data diff --git a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py index 984fc0f66..f6066fecb 100644 --- a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py +++ b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py @@ -24,7 +24,8 @@ class SignedScriptProxyExecution(PBA): original_comspec = subprocess.check_output( # noqa: DUO116 "if defined COMSPEC echo %COMSPEC%", shell=True ).decode() - yield super().run() + super().run() + return self.pba_data except Exception as e: logger.warning( f"An exception occurred on running PBA " diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index 449c06186..8b50f08ba 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -1,5 +1,6 @@ import logging import subprocess +from typing import Iterable from common.utils.attack_utils import ScanStatus from infection_monkey.i_puppet.i_puppet import PostBreachData @@ -23,8 +24,9 @@ class PBA: """ self.command = PBA.choose_command(linux_cmd, windows_cmd) self.name = name + self.pba_data = [] - def run(self): + def run(self) -> Iterable[PostBreachData]: """ Runs post breach action command """ @@ -35,7 +37,8 @@ class PBA: T1064Telem( ScanStatus.USED, f"Scripts were used to execute {self.name} post breach action." ).send() - yield PostBreachData(self.name, self.command, result) + self.pba_data.append(PostBreachData(self.name, self.command, result)) + return self.pba_data else: logger.debug(f"No command available for PBA '{self.name}' on current OS, skipping.") From 1f2867a70a75360a38ba894986b2795bc34a5a08 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Tue, 29 Mar 2022 11:16:51 +0530 Subject: [PATCH 12/15] Project: Add ProcessListCollection to Vulture's allowlist --- vulture_allowlist.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 63094f3ae..687a9b497 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -92,6 +92,7 @@ AccountDiscovery # unused class (monkey/infection_monkey/post_breach/actions/di ModifyShellStartupFiles # unused class (monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py:11) Timestomping # unused class (monkey/infection_monkey/post_breach/actions/timestomping.py:6) SignedScriptProxyExecution # unused class (monkey/infection_monkey/post_breach/actions/use_signed_scripts.py:15) +ProcessListCollection # unused class (monkey/infection_monkey/post_breach/actions/collect_processes_list.py:19) EnvironmentCollector # unused class (monkey/infection_monkey/system_info/collectors/environment_collector.py:19) HostnameCollector # unused class (monkey/infection_monkey/system_info/collectors/hostname_collector.py:10) _.representations # unused attribute (monkey/monkey_island/cc/app.py:180) From ba49e4d23ec56dbcc4f8e395ad3c80901f69e8f5 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 29 Mar 2022 14:17:50 +0300 Subject: [PATCH 13/15] Agent: Small style improvements in PBA code --- .../post_breach/actions/collect_processes_list.py | 2 +- .../actions/communicate_as_backdoor_user.py | 13 ++++++++----- .../post_breach/actions/hide_files.py | 2 +- .../actions/modify_shell_startup_files.py | 14 ++++++++------ .../post_breach/actions/use_signed_scripts.py | 2 +- 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/collect_processes_list.py b/monkey/infection_monkey/post_breach/actions/collect_processes_list.py index 5f18e0e33..d0a5c5e0d 100644 --- a/monkey/infection_monkey/post_breach/actions/collect_processes_list.py +++ b/monkey/infection_monkey/post_breach/actions/collect_processes_list.py @@ -53,5 +53,5 @@ class ProcessListCollection(PBA): continue # No command here; used psutil - self.pba_data.append(PostBreachData(self.name, "", (processes, success_state))) + self.pba_data.append(PostBreachData(self.name, self.command, (processes, success_state))) return self.pba_data diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py index 73ef0fa3b..4dca6ac06 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py @@ -49,16 +49,18 @@ class CommunicateAsBackdoorUser(PBA): ) ) exit_status = new_user.run_as(http_request_commandline) - result = self._get_result_for_telemetry( + result = CommunicateAsBackdoorUser._get_result_for_telemetry( exit_status, http_request_commandline, username ) # `command` is empty here; we could get the command from `new_user` but that # doesn't work either since Windows doesn't use a command, it uses win32 modules - self.pba_data.append(PostBreachData(self.name, "", result)) + self.pba_data.append(PostBreachData(self.name, self.command, result)) except subprocess.CalledProcessError as e: - self.pba_data.append(PostBreachData(self.name, "", (e.output.decode(), False))) + self.pba_data.append( + PostBreachData(self.name, self.command, (e.output.decode(), False)) + ) except NewUserError as e: - self.pba_data.append(PostBreachData(self.name, "", (str(e), False))) + self.pba_data.append(PostBreachData(self.name, self.command, (str(e), False))) finally: return self.pba_data @@ -86,7 +88,8 @@ class CommunicateAsBackdoorUser(PBA): format_string = "wget -O/dev/null -q {url} --method=HEAD --timeout=10" return format_string.format(url=url) - def _get_result_for_telemetry(self, exit_status, commandline, username): + @staticmethod + def _get_result_for_telemetry(exit_status, commandline, username): """ Parses the result of the command and returns it to be sent as telemetry from the master. diff --git a/monkey/infection_monkey/post_breach/actions/hide_files.py b/monkey/infection_monkey/post_breach/actions/hide_files.py index 1a2f3472d..e3123192c 100644 --- a/monkey/infection_monkey/post_breach/actions/hide_files.py +++ b/monkey/infection_monkey/post_breach/actions/hide_files.py @@ -34,7 +34,7 @@ class HiddenFiles(PBA): if is_windows_os(): # use winAPI result, status = get_winAPI_to_hide_files() # no command here, used WinAPI - self.pba_data.append(PostBreachData(self.name, "", (result, status))) + self.pba_data.append(PostBreachData(self.name, self.command, (result, status))) # cleanup hidden files and folders cleanup_hidden_files(is_windows_os()) diff --git a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py index bb1a653f8..5a966e92d 100644 --- a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py +++ b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py @@ -29,14 +29,16 @@ class ModifyShellStartupFiles(PBA): ] # `command` is empty here since multiple commands were run and the results # were aggregated to send the telemetry just once - self.pba_data.append(PostBreachData(self.name, "", results)) + self.pba_data.append(PostBreachData(self.name, self.command, results)) return self.pba_data - def modify_shell_startup_PBA_list(self): - return self.ShellStartupPBAGenerator().get_modify_shell_startup_pbas() + @classmethod + def modify_shell_startup_PBA_list(cls): + return cls.ShellStartupPBAGenerator.get_modify_shell_startup_pbas() class ShellStartupPBAGenerator: - def get_modify_shell_startup_pbas(self): + @classmethod + def get_modify_shell_startup_pbas(cls): (cmds_for_linux, shell_startup_files_for_linux, usernames_for_linux), ( cmds_for_windows, shell_startup_files_per_user_for_windows, @@ -46,14 +48,14 @@ class ModifyShellStartupFiles(PBA): for startup_file_per_user in shell_startup_files_per_user_for_windows: windows_cmds = " ".join(cmds_for_windows).format(startup_file_per_user) - pbas.append(self.ModifyShellStartupFile(linux_cmds="", windows_cmds=windows_cmds)) + pbas.append(cls.ModifyShellStartupFile(linux_cmds="", windows_cmds=windows_cmds)) for username in usernames_for_linux: for shell_startup_file in shell_startup_files_for_linux: linux_cmds = ( " ".join(cmds_for_linux).format(shell_startup_file).format(username) ) - pbas.append(self.ModifyShellStartupFile(linux_cmds=linux_cmds, windows_cmds="")) + pbas.append(cls.ModifyShellStartupFile(linux_cmds=linux_cmds, windows_cmds="")) return pbas diff --git a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py index f6066fecb..75ede03ee 100644 --- a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py +++ b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py @@ -18,8 +18,8 @@ class SignedScriptProxyExecution(PBA): super().__init__(POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC, windows_cmd=" ".join(windows_cmds)) def run(self): + original_comspec = "" try: - original_comspec = "" if is_windows_os(): original_comspec = subprocess.check_output( # noqa: DUO116 "if defined COMSPEC echo %COMSPEC%", shell=True From 70186a40f60b98fd22ab31d17599521c495fd75c Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Tue, 29 Mar 2022 17:13:44 +0530 Subject: [PATCH 14/15] Agent: Remove comment from function in backdoor user PBA since the code is self-explanatory --- .../post_breach/actions/communicate_as_backdoor_user.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py index 4dca6ac06..e4523f0fd 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py @@ -90,13 +90,6 @@ class CommunicateAsBackdoorUser(PBA): @staticmethod def _get_result_for_telemetry(exit_status, commandline, username): - """ - Parses the result of the command and returns it to be sent as telemetry from the master. - - :param exit_status: In both Windows and Linux, 0 exit code indicates success. - :param commandline: Exact commandline which was executed, for reporting back. - :param username: Username from which the command was executed, for reporting back. - """ if exit_status == 0: result = (CREATED_PROCESS_AS_USER_SUCCESS_FORMAT.format(commandline, username), True) else: From 246a72c940fe52d3f94e02782cb39aefaef22588 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Tue, 29 Mar 2022 17:16:17 +0530 Subject: [PATCH 15/15] Agent: Modify comment in shell startup PBA to make more sense --- .../post_breach/actions/modify_shell_startup_files.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py index 5a966e92d..5d3c3c5ea 100644 --- a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py +++ b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py @@ -27,8 +27,8 @@ class ModifyShellStartupFiles(PBA): False, ) ] - # `command` is empty here since multiple commands were run and the results - # were aggregated to send the telemetry just once + # `command` is empty here since multiple commands were run through objects of the nested + # class. The results of each of those were aggregated to send the telemetry just once. self.pba_data.append(PostBreachData(self.name, self.command, results)) return self.pba_data