From 2286571a72030d4497470c3c9611c3ee1e5e9a44 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 20 Jan 2020 17:12:12 +0200 Subject: [PATCH] Refactored process list collector --- .../data/system_info_collectors_names.py | 1 + .../infection_monkey/system_info/__init__.py | 32 ------------ .../collectors/process_list_collector.py | 50 +++++++++++++++++++ .../cc/services/config_schema.py | 23 ++++++--- .../telemetry/processing/system_info.py | 4 +- .../system_info_telemetry_dispatcher.py | 6 ++- .../zero_trust_tests/antivirus_existence.py | 50 +++++++++---------- 7 files changed, 98 insertions(+), 68 deletions(-) create mode 100644 monkey/infection_monkey/system_info/collectors/process_list_collector.py diff --git a/monkey/common/data/system_info_collectors_names.py b/monkey/common/data/system_info_collectors_names.py index 8bdf757c7..831bbe142 100644 --- a/monkey/common/data/system_info_collectors_names.py +++ b/monkey/common/data/system_info_collectors_names.py @@ -1,3 +1,4 @@ AWS_COLLECTOR = "AwsCollector" HOSTNAME_COLLECTOR = "HostnameCollector" ENVIRONMENT_COLLECTOR = "EnvironmentCollector" +PROCESS_LIST_COLLECTOR = "ProcessListCollector" diff --git a/monkey/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py index 66056828e..889b558a1 100644 --- a/monkey/infection_monkey/system_info/__init__.py +++ b/monkey/infection_monkey/system_info/__init__.py @@ -62,44 +62,12 @@ class InfoCollector(object): def get_info(self): # Collect all hardcoded - self.get_process_list() self.get_network_info() self.get_azure_info() # Collect all plugins SystemInfoCollectorsHandler().execute_all_configured() - def get_process_list(self): - """ - Adds process information from the host to the system information. - Currently lists process name, ID, parent ID, command line - and the full image path of each process. - :return: None. Updates class information - """ - LOG.debug("Reading process list") - processes = {} - for process in psutil.process_iter(): - try: - processes[process.pid] = {"name": process.name(), - "pid": process.pid, - "ppid": process.ppid(), - "cmdline": " ".join(process.cmdline()), - "full_image_path": process.exe(), - } - except (psutil.AccessDenied, WindowsError): - # we may be running as non root - # and some processes are impossible to acquire in Windows/Linux - # in this case we'll just add what we can - processes[process.pid] = {"name": "null", - "pid": process.pid, - "ppid": process.ppid(), - "cmdline": "ACCESS DENIED", - "full_image_path": "null", - } - continue - - self.info['process_list'] = processes - def get_network_info(self): """ Adds network information from the host to the system information. diff --git a/monkey/infection_monkey/system_info/collectors/process_list_collector.py b/monkey/infection_monkey/system_info/collectors/process_list_collector.py new file mode 100644 index 000000000..93f836376 --- /dev/null +++ b/monkey/infection_monkey/system_info/collectors/process_list_collector.py @@ -0,0 +1,50 @@ +import logging +import psutil + +from common.data.system_info_collectors_names import PROCESS_LIST_COLLECTOR +from infection_monkey.system_info.system_info_collector import SystemInfoCollector + +logger = logging.getLogger(__name__) + +# Linux doesn't have WindowsError +try: + WindowsError +except NameError: + # noinspection PyShadowingBuiltins + WindowsError = psutil.AccessDenied + + +class ProcessListCollector(SystemInfoCollector): + def __init__(self): + super(ProcessListCollector, self).__init__(name=PROCESS_LIST_COLLECTOR) + + def collect(self) -> dict: + """ + Adds process information from the host to the system information. + Currently lists process name, ID, parent ID, command line + and the full image path of each process. + """ + logger.debug("Reading process list") + processes = {} + for process in psutil.process_iter(): + try: + processes[process.pid] = { + "name": process.name(), + "pid": process.pid, + "ppid": process.ppid(), + "cmdline": " ".join(process.cmdline()), + "full_image_path": process.exe(), + } + except (psutil.AccessDenied, WindowsError): + # we may be running as non root and some processes are impossible to acquire in Windows/Linux. + # In this case we'll just add what we know. + processes[process.pid] = { + "name": "null", + "pid": process.pid, + "ppid": process.ppid(), + "cmdline": "ACCESS DENIED", + "full_image_path": "null", + } + continue + + return {'process_list': processes} diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 86e6225e0..e7d599cc5 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -1,3 +1,5 @@ +from common.data.system_info_collectors_names import * + WARNING_SIGN = " \u26A0" SCHEMA = { @@ -106,7 +108,7 @@ SCHEMA = { { "type": "string", "enum": [ - "EnvironmentCollector" + ENVIRONMENT_COLLECTOR ], "title": "Collect which environment this machine is on (on prem/cloud)", "attack_techniques": [] @@ -114,7 +116,7 @@ SCHEMA = { { "type": "string", "enum": [ - "AwsCollector" + AWS_COLLECTOR ], "title": "If on AWS, collect more information about the instance", "attack_techniques": [] @@ -122,11 +124,19 @@ SCHEMA = { { "type": "string", "enum": [ - "HostnameCollector" + HOSTNAME_COLLECTOR ], "title": "Collect the machine's hostname", "attack_techniques": [] }, +{ + "type": "string", + "enum": [ + PROCESS_LIST_COLLECTOR + ], + "title": "Collect running processes on the machine", + "attack_techniques": [] + }, ], }, "post_breach_acts": { @@ -471,9 +481,10 @@ SCHEMA = { "$ref": "#/definitions/system_info_collectors_classes" }, "default": [ - "EnvironmentCollector", - "AwsCollector", - "HostnameCollector" + ENVIRONMENT_COLLECTOR, + AWS_COLLECTOR, + HOSTNAME_COLLECTOR, + PROCESS_LIST_COLLECTOR ], "description": "Determines which system information collectors will collect information." }, diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py index c490b1d69..b923b6395 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py @@ -6,7 +6,6 @@ from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \ SystemInfoTelemetryDispatcher -from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import test_antivirus_existence from monkey_island.cc.services.wmi_handler import WMIHandler logger = logging.getLogger(__name__) @@ -18,8 +17,7 @@ def process_system_info_telemetry(telemetry_json): process_ssh_info, process_credential_info, process_mimikatz_and_wmi_info, - test_antivirus_existence, - dispatcher.dispatch_to_relevant_collectors + dispatcher.dispatch_collector_results_to_relevant_processors ] # Calling safe_process_telemetry so if one of the stages fail, we log and move on instead of failing the rest of diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py index e20231d8b..d67979e8d 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py @@ -5,6 +5,7 @@ from common.data.system_info_collectors_names import * from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import process_environment_telemetry from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import process_hostname_telemetry +from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import test_antivirus_existence logger = logging.getLogger(__name__) @@ -12,6 +13,7 @@ SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = { AWS_COLLECTOR: [process_aws_telemetry], ENVIRONMENT_COLLECTOR: [process_environment_telemetry], HOSTNAME_COLLECTOR: [process_hostname_telemetry], + PROCESS_LIST_COLLECTOR: [test_antivirus_existence] } @@ -32,9 +34,9 @@ class SystemInfoTelemetryDispatcher(object): :param telemetry_json: Telemetry sent from the Monkey """ if "collectors" in telemetry_json["data"]: - self.dispatch_each_result_to_relevant_processors(telemetry_json) + self.dispatch_single_result_to_relevant_processor(telemetry_json) - def dispatch_each_result_to_relevant_processors(self, telemetry_json): + def dispatch_single_result_to_relevant_processor(self, telemetry_json): relevant_monkey_guid = telemetry_json['monkey_guid'] for collector_name, collector_results in telemetry_json["data"]["collectors"].items(): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py index ddc1af65b..1916291e2 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py @@ -7,36 +7,36 @@ from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.services.telemetry.zero_trust_tests.known_anti_viruses import ANTI_VIRUS_KNOWN_PROCESS_NAMES -def test_antivirus_existence(telemetry_json): - current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) - if 'process_list' in telemetry_json['data']: - process_list_event = Event.create_event( - title="Process list", - message="Monkey on {} scanned the process list".format(current_monkey.hostname), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL) - events = [process_list_event] +def test_antivirus_existence(process_list_json, monkey_guid): + current_monkey = Monkey.get_single_monkey_by_guid(monkey_guid) - av_processes = filter_av_processes(telemetry_json) + process_list_event = Event.create_event( + title="Process list", + message="Monkey on {} scanned the process list".format(current_monkey.hostname), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL) + events = [process_list_event] - for process in av_processes: - events.append(Event.create_event( - title="Found AV process", - message="The process '{}' was recognized as an Anti Virus process. Process " - "details: {}".format(process[1]['name'], json.dumps(process[1])), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL - )) + av_processes = filter_av_processes(process_list_json["process_list"]) - if len(av_processes) > 0: - test_status = zero_trust_consts.STATUS_PASSED - else: - test_status = zero_trust_consts.STATUS_FAILED - AggregateFinding.create_or_add_to_existing( - test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events - ) + for process in av_processes: + events.append(Event.create_event( + title="Found AV process", + message="The process '{}' was recognized as an Anti Virus process. Process " + "details: {}".format(process[1]['name'], json.dumps(process[1])), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL + )) + + if len(av_processes) > 0: + test_status = zero_trust_consts.STATUS_PASSED + else: + test_status = zero_trust_consts.STATUS_FAILED + AggregateFinding.create_or_add_to_existing( + test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events + ) -def filter_av_processes(telemetry_json): - all_processes = list(telemetry_json['data']['process_list'].items()) +def filter_av_processes(process_list): + all_processes = list(process_list.items()) av_processes = [] for process in all_processes: process_name = process[1]['name']