From 4e1c1c4de1100f8139452255f1a410ae7735a1a8 Mon Sep 17 00:00:00 2001 From: cccs-gm <56171373+cccs-gm@users.noreply.github.com> Date: Fri, 17 Jul 2020 14:42:14 -0400 Subject: [PATCH 1/5] Start of the new functionality - adding some of the changes required for the generation of the MITRE ATT&CK software codes - refactoring YaraFileProcessor function names and adding deprecation messages to old function names --- CCCS_YARA.yml | 11 ++ yara-validator/validator_functions.py | 46 ++++++++ yara-validator/yara_file_processor.py | 147 ++++++++++++++++++++++++++ yara_validator_cli.py | 4 +- 4 files changed, 206 insertions(+), 2 deletions(-) diff --git a/CCCS_YARA.yml b/CCCS_YARA.yml index 49451ab..5a5465f 100644 --- a/CCCS_YARA.yml +++ b/CCCS_YARA.yml @@ -189,6 +189,7 @@ info|exploit|technique|tool|malware: validator: valid_category_type argument: parent: category + child: mitre_software malware_type: description: 'Give an indication as to what the capability/purpose of the malware is' @@ -207,6 +208,16 @@ mitre_att: optional: Yes validator: valid_mitre_att +mitre_software: + description: 'Autogenerated MITRE ATT&CK software code if the value of info|exploit|technique|tool|malware + is found in the "https://attack.mitre.org/groups/" standard' + format: 'uppercase' + unique: Yes + optional: Yes + validator: mitre_software_generator + argument: + parent: info|exploit|technique|tool|malware + actor_type: description: 'The type of the actor' format: 'uppercase' diff --git a/yara-validator/validator_functions.py b/yara-validator/validator_functions.py index e30652a..834b3a8 100644 --- a/yara-validator/validator_functions.py +++ b/yara-validator/validator_functions.py @@ -562,7 +562,53 @@ def mitre_group_generator(self, rule_to_generate_group, metadata_index, metadata return self.required_fields[place_holder].valid + def mitre_software_generator(self, rule_to_generate_software, metadata_index, metadata_key): + """ + This will only be looked for if the info|exploit|technique|tool|malware metadata value has already been + processed. Current functionality is not to check the value of an existing mitre_group metadata value and + just overwrite it as this is automatically filled out. Also if no alias is found it will be removed. + :param rule_to_generate_software: the plyara parsed rule that is being validated + :param metadata_index: used to reference what the array index of the mitre_group metadata value is + :param metadata_key: the name of the metadata value that is being processed + :return: This should return True all the time as there will always be a return from self.get_group_from_alias + """ + ACTOR = 'actor' + place_holder = self.required_fields[ACTOR].argument.get('child_place_holder') + if self.required_fields.get(metadata_key): # if child place holder is passed as metadata_key + MITRE_GROUP = self.required_fields[self.required_fields[metadata_key].argument['parent']].argument['child'] + else: + MITRE_GROUP = metadata_key + mitre_group = str(Helper.get_group_from_alias(self.mitre_group_alias)).upper() + rule_group = {MITRE_GROUP: mitre_group} + if Helper.valid_metadata_index(rule_to_generate_group, metadata_index): + if list(rule_to_generate_group[METADATA][metadata_index].keys())[0] == MITRE_GROUP: + if mitre_group: + rule_to_generate_group[METADATA][metadata_index] = rule_group + self.required_fields[place_holder].attributefound() + self.required_fields[place_holder].attributevalid() + self.required_fields_index[self.required_fields[place_holder].position].increment_count() + else: + rule_to_generate_group[METADATA].pop(metadata_index) + return True + else: + if mitre_group: + rule_to_generate_group[METADATA].insert(metadata_index, rule_group) + self.required_fields[place_holder].attributefound() + self.required_fields[place_holder].attributevalid() + self.required_fields_index[self.required_fields[place_holder].position].increment_count() + else: + return True + else: + if mitre_group: + rule_to_generate_group[METADATA].append(rule_group) + self.required_fields[place_holder].attributefound() + self.required_fields[place_holder].attributevalid() + self.required_fields_index[self.required_fields[place_holder].position].increment_count() + else: + return True + + return self.required_fields[place_holder].valid class Helper: diff --git a/yara-validator/yara_file_processor.py b/yara-validator/yara_file_processor.py index 248c7bf..ef1cded 100644 --- a/yara-validator/yara_file_processor.py +++ b/yara-validator/yara_file_processor.py @@ -209,6 +209,32 @@ def return_file_error_state(self): return error_state def return_rule_errors(self): + """ + Returns the any file errors and loops through the self.yara_rules array and returns a string for any errors + :return: error_string, a string of all of the errors concatenated together + """ + import warnings + warnings.warn( + 'YaraFileProcessor.return_rule_errors() is deprecated, use YaraFileProcessor.return_file_errors() instead', + DeprecationWarning + ) + error_string = '' + + if self.file_errors: + error_string = self.__build_return_string(self.errors) + + for rule in self.yara_rules: + if rule.rule_return: + if isinstance(rule.rule_return, YaraReturn): + if rule.return_error(): + error_string = error_string + rule.return_errors() + else: + if not rule.rule_return.rule_validity: + error_string = error_string + rule.return_errors() + + return error_string + + def return_file_errors(self): """ Returns the any file errors and loops through the self.yara_rules array and returns a string for any errors :return: error_string, a string of all of the errors concatenated together @@ -230,6 +256,38 @@ def return_rule_errors(self): return error_string def return_rule_errors_for_cmlt(self): + """ + Loops throught the self.yara_rules array and returns a string for of errors in cmlt format + :return: + """ + import warnings + warnings.warn( + 'YaraFileProcessor.return_rule_errors_for_cmlt() is deprecated, ' + 'use YaraFileProcessor.return_file_errors_for_cmlt() instead', + DeprecationWarning + ) + error_string = '' + + if self.file_errors: + error_string = self.__build_return_string_cmlt(self.errors) + + for rule in self.yara_rules: + if rule.rule_return: + if isinstance(rule.rule_return, YaraReturn): + if rule.return_error(): + error_string = error_string + "{indent:>8} {name:10}".format(indent="- ", + name=rule.get_rule_name() + ":\n") + error_string = error_string + rule.rule_plyara["rule_name"] + "\n" + error_string = error_string + rule.return_errors_for_cmlt() + else: + if not rule.rule_return.rule_validity: + error_string = error_string + "{indent:>8} {name:10}".format(indent="- ", + name=rule.get_rule_name() + ":\n") + error_string = error_string + rule.return_errors_for_cmlt() + + return error_string + + def return_file_errors_for_cmlt(self): """ Loops throught the self.yara_rules array and returns a string for of errors in cmlt format :return: @@ -256,6 +314,26 @@ def return_rule_errors_for_cmlt(self): return error_string def return_rule_warning_state(self): + """ + Loops through the self.yara_rules array and returns true if any of the rules are in a warning state + :return: + """ + import warnings + warnings.warn( + 'YaraFileProcessor.return_rule_warning_state() is deprecated, ' + 'use YaraFileProcessor.return_file_warning_state() instead', + DeprecationWarning + ) + warning_state = False + for rule in self.yara_rules: + if rule.rule_return: + if rule.return_warning(): + warning_state = True + break + + return warning_state + + def return_file_warning_state(self): """ Loops through the self.yara_rules array and returns true if any of the rules are in a warning state :return: @@ -270,6 +348,26 @@ def return_rule_warning_state(self): return warning_state def return_rule_warnings(self): + """ + Loops throught the self.yara_rules array and returns a string for of warnings + :return: + """ + import warnings + warnings.warn( + 'YaraFileProcessor.return_rule_warnings() is deprecated, ' + 'use YaraFileProcessor.return_file_warnings() instead', + DeprecationWarning + ) + warning_string = '' + + for rule in self.yara_rules: + if rule.rule_return: + if rule.return_warning(): + warning_string = warning_string + rule.return_warnings() + + return warning_string + + def return_file_warnings(self): """ Loops throught the self.yara_rules array and returns a string for of warnings :return: @@ -284,6 +382,28 @@ def return_rule_warnings(self): return warning_string def return_rule_warnings_for_cmlt(self): + """ + Loops throught the self.yara_rules array and returns a string for of warnings in cmlt format + :return: + """ + import warnings + warnings.warn( + 'YaraFileProcessor.return_rule_warnings_for_cmlt() is deprecated, ' + 'use YaraFileProcessor.return_file_warnings_for_cmlt() instead', + DeprecationWarning + ) + warning_string = '' + + for rule in self.yara_rules: + if rule.rule_return: + if rule.return_warning(): + warning_string = warning_string + "{indent:>8} {name:10}".format(indent="- ", + name=rule.get_rule_name() + ":\n") + warning_string = warning_string + rule.return_warnings_for_cmlt() + + return warning_string + + def return_file_warnings_for_cmlt(self): """ Loops throught the self.yara_rules array and returns a string for of warnings in cmlt format :return: @@ -300,6 +420,19 @@ def return_rule_warnings_for_cmlt(self): return warning_string def return_original_rule(self): + """ + Returns the original rule string + :return: + """ + import warnings + warnings.warn( + 'YaraFileProcessor.return_original_rule() is deprecated, ' + 'use YaraFileProcessor.return_original_file() instead', + DeprecationWarning + ) + return self.original_rule_string + + def return_original_file(self): """ Returns the original rule string :return: @@ -307,6 +440,19 @@ def return_original_rule(self): return self.original_rule_string def return_edited_rule(self): + """ + Returns the edited rule string + :return: + """ + import warnings + warnings.warn( + 'YaraFileProcessor.return_original_rule() is deprecated, ' + 'use YaraFileProcessor.return_original_file() instead', + DeprecationWarning + ) + return self.edited_rule_string + + def return_edited_file(self): """ Returns the edited rule string :return: @@ -314,6 +460,7 @@ def return_edited_rule(self): return self.edited_rule_string + class YaraRule: """ YaraRule objects contain a string representation of a rule, a plyara representation of the rule and the RuleReturn diff --git a/yara_validator_cli.py b/yara_validator_cli.py index a955b5d..d978b78 100644 --- a/yara_validator_cli.py +++ b/yara_validator_cli.py @@ -118,7 +118,7 @@ def print_errors(yara_file_processor, options): def print_warnings(yara_file_processor, options): - if yara_file_processor.return_rule_warning_state and not options.warnings: + if yara_file_processor.return_rule_warning_state() and not options.warnings: print(colored.yellow('{indent:>7}{message}'.format(indent='- ', message='Warnings:'))) print(colored.white(yara_file_processor.return_rule_warnings_for_cmlt())) @@ -211,7 +211,7 @@ def __call_validator(options): print_errors(yara_file_processor, options) print_warnings(yara_file_processor, options) - elif yara_file_processor.return_rule_warning_state() and not options.warnings: + elif yara_file_processor.return_file_warning_state() and not options.warnings: # The rule is valid, has warnings and warning are turned on all_warning_rule_returns.append((yara_rule_path, yara_file_processor)) From 46544e36759b276bb30ad4d07ec882de30adc26d Mon Sep 17 00:00:00 2001 From: cccs-gm <56171373+cccs-gm@users.noreply.github.com> Date: Fri, 28 Aug 2020 14:22:31 -0400 Subject: [PATCH 2/5] Added Features and Warnings - Added a feature were malware metadata values can be used to query the MITRE ATTC&K database to find the associated software codes. Any that are not already present as will be added as mitre_att metadata values. This will not remove existing mitre_att metadata with software values. - Added a warning check for when info|exploit|technique|tool|malware key is matching the category key value is not present. While not required it is a best practice to include the metadata with the additional information i.e. malware: "malware name" - Added a warning check for metadata keys that are similarly named to keys in the standard, this is to try and help catch things like hash1, hash2, or hash_256. This is a first pass and should be updated with more common items over time. --- CCCS_YARA.yml | 13 +-- yara-validator/validator_functions.py | 127 ++++++++++++++++---------- yara-validator/yara_validator.py | 65 +++++++++++-- 3 files changed, 139 insertions(+), 66 deletions(-) diff --git a/CCCS_YARA.yml b/CCCS_YARA.yml index 5a5465f..3fbfcbe 100644 --- a/CCCS_YARA.yml +++ b/CCCS_YARA.yml @@ -189,7 +189,8 @@ info|exploit|technique|tool|malware: validator: valid_category_type argument: parent: category - child: mitre_software + ifKeyGenMitreAtt: malware + malwareIdsFound: malware_type: description: 'Give an indication as to what the capability/purpose of the malware is' @@ -207,16 +208,8 @@ mitre_att: unique: No optional: Yes validator: valid_mitre_att - -mitre_software: - description: 'Autogenerated MITRE ATT&CK software code if the value of info|exploit|technique|tool|malware - is found in the "https://attack.mitre.org/groups/" standard' - format: 'uppercase' - unique: Yes - optional: Yes - validator: mitre_software_generator argument: - parent: info|exploit|technique|tool|malware + softCodesFound: actor_type: description: 'The type of the actor' diff --git a/yara-validator/validator_functions.py b/yara-validator/validator_functions.py index 834b3a8..b9b1468 100644 --- a/yara-validator/validator_functions.py +++ b/yara-validator/validator_functions.py @@ -74,6 +74,7 @@ def __init__(self): self.required_fields_children = None self.category_types = None self.mitre_group_alias = None + self.mitre_software_code = None self.names = { 'None': self.valid_none, 'valid_regex': self.valid_regex, @@ -350,7 +351,8 @@ def valid_source(self, rule_to_source_check, metadata_index, metadata_key): def valid_mitre_att(self, rule_to_validate_mitre_att, metadata_index, metadata_key): """ - Pulls the value of the mitre_att metadata value and passes it to validate_mitre_att_by_id + Pulls the value of the mitre_att metadata value and passes it to validate_mitre_att_by_id. This also stores + any found MITRE ATT&CK software codes to be compared with ones generated by malware metadata values :param rule_to_validate_mitre_att: the plyara parsed rule that is being validated :param metadata_index: used to reference what the array index of the mitre_att metadata value is :param metadata_key: the name of the metadata value that is being processed @@ -368,6 +370,15 @@ def valid_mitre_att(self, rule_to_validate_mitre_att, metadata_index, metadata_k else: self.required_fields[MITRE_ATT].attributeinvalid() + if self.required_fields[MITRE_ATT].valid and mitre_att_to_validate.startswith('S'): + soft_codes_found = self.required_fields[MITRE_ATT].argument.get('softCodesFound') + if not soft_codes_found: + soft_codes_found = [mitre_att_to_validate] + else: + soft_codes_found.append(mitre_att_to_validate) + + self.required_fields[MITRE_ATT].argument.update({'softCodesFound': soft_codes_found}) + return self.required_fields[MITRE_ATT].valid def valid_al_config_dumper(self, rule_to_validate_al_config_d, metadata_index, metadata_key): @@ -415,7 +426,8 @@ def valid_category(self, rule_to_validate_category, metadata_index, metadata_key Pulls the value of the category metadata value and checks if it is a valid category type. Valid options are stored in self.category_types. If the category value is valid and a new metadata metadata with a name the same as the category value is added to be searched for. - This new metadata value links to the same object as the initially created self.required_fields[CATEGORY_TYPE]. + This new metadata value links to the same object as the initially created + self.required_fields[CATEGORY_TYPE]. :param rule_to_validate_category: the plyara parsed rule that is being validated :param metadata_index: used to reference what the array index of the category metadata value is :param metadata_key: the name of the metadata value that is being processed @@ -447,8 +459,10 @@ def valid_category(self, rule_to_validate_category, metadata_index, metadata_key def valid_category_type(self, rule_to_validate_type, metadata_index, metadata_key): """ - This will be called by the new metadata created by the valid_category function. Because it references the same object - as that initialized as CATEGORY_TYPE we can use that to reference the reqired metadata in this function. + This will be called by the new metadata created by the valid_category function. Because it references the same + object as that initialized as CATEGORY_TYPE we can use that to reference the reqired metadata in this + function. This also generates and stores any MITRE ATT&CK software codes to be checked against found + mitre_att software codes :param rule_to_validate_type: the plyara parsed rule that is being validated :param metadata_index: used to reference what the array index of the category_type metadata value is :param metadata_key: the name of the metadata value that is being processed @@ -458,6 +472,8 @@ def valid_category_type(self, rule_to_validate_type, metadata_index, metadata_ke child_metadata_place_holder = self.required_fields[CATEGORY].argument.get('child_place_holder') self.required_fields[child_metadata_place_holder].attributefound() self.required_fields_index[self.required_fields[child_metadata_place_holder].position].increment_count() + key_gen_mitre_att = r'^' + self.required_fields[child_metadata_place_holder].argument.get('ifKeyGenMitreAtt')\ + + '$' metadata = rule_to_validate_type[METADATA] rule_category_key_to_check = list(metadata[metadata_index].keys())[0] @@ -471,6 +487,19 @@ def valid_category_type(self, rule_to_validate_type, metadata_index, metadata_ke else: self.required_fields[child_metadata_place_holder].attributeinvalid() + rule_category_value_to_check = list(metadata[metadata_index].values())[0] + if self.required_fields[child_metadata_place_holder].valid and re.fullmatch(key_gen_mitre_att, + rule_category_key_to_check): + malware_id = Helper.get_software_id_by_name(rule_category_value_to_check) + if malware_id: + malware_ids_found = self.required_fields[child_metadata_place_holder].argument.get('malwareIdsFound') + if not malware_ids_found: + malware_ids_found = [malware_id] + else: + malware_ids_found.append(malware_id) + + self.required_fields[child_metadata_place_holder].argument.update({'malwareIdsFound': malware_ids_found}) + return self.required_fields[child_metadata_place_holder].valid def valid_actor(self, rule_to_validate_actor, metadata_index, metadata_key): @@ -562,53 +591,33 @@ def mitre_group_generator(self, rule_to_generate_group, metadata_index, metadata return self.required_fields[place_holder].valid - def mitre_software_generator(self, rule_to_generate_software, metadata_index, metadata_key): + def mitre_software_generator(self, rule_to_generate_mitre_att, category_key, mitre_key): """ - This will only be looked for if the info|exploit|technique|tool|malware metadata value has already been - processed. Current functionality is not to check the value of an existing mitre_group metadata value and - just overwrite it as this is automatically filled out. Also if no alias is found it will be removed. - :param rule_to_generate_software: the plyara parsed rule that is being validated - :param metadata_index: used to reference what the array index of the mitre_group metadata value is - :param metadata_key: the name of the metadata value that is being processed - :return: This should return True all the time as there will always be a return from self.get_group_from_alias + This is called to generate any required mitre_att software codes. This is determined by comparing software + codes generated from the value of malware metadata keys and any already found mitre_att keys containing + software codes. Any of the generated values that are not found are added to the metadata of the rule + as mitre_att + :param rule_to_generate_mitre_att: rule that we want to add any missing mitre_att software codes + :param category_key: the key for category, which is used to find the generated software codes + :param mitre_key: the key used to find the found software codes + :return: """ - ACTOR = 'actor' - place_holder = self.required_fields[ACTOR].argument.get('child_place_holder') - if self.required_fields.get(metadata_key): # if child place holder is passed as metadata_key - MITRE_GROUP = self.required_fields[self.required_fields[metadata_key].argument['parent']].argument['child'] - else: - MITRE_GROUP = metadata_key + CATEGORY = category_key + MITRE_ATT = mitre_key + child_metadata_place_holder = self.required_fields[CATEGORY].argument.get('child_place_holder') - mitre_group = str(Helper.get_group_from_alias(self.mitre_group_alias)).upper() - rule_group = {MITRE_GROUP: mitre_group} - if Helper.valid_metadata_index(rule_to_generate_group, metadata_index): - if list(rule_to_generate_group[METADATA][metadata_index].keys())[0] == MITRE_GROUP: - if mitre_group: - rule_to_generate_group[METADATA][metadata_index] = rule_group - self.required_fields[place_holder].attributefound() - self.required_fields[place_holder].attributevalid() - self.required_fields_index[self.required_fields[place_holder].position].increment_count() - else: - rule_to_generate_group[METADATA].pop(metadata_index) - return True - else: - if mitre_group: - rule_to_generate_group[METADATA].insert(metadata_index, rule_group) - self.required_fields[place_holder].attributefound() - self.required_fields[place_holder].attributevalid() - self.required_fields_index[self.required_fields[place_holder].position].increment_count() - else: - return True - else: - if mitre_group: - rule_to_generate_group[METADATA].append(rule_group) - self.required_fields[place_holder].attributefound() - self.required_fields[place_holder].attributevalid() - self.required_fields_index[self.required_fields[place_holder].position].increment_count() - else: - return True + malware_ids_found = self.required_fields[child_metadata_place_holder].argument.get('malwareIdsFound') + soft_codes_found = self.required_fields[MITRE_ATT].argument.get('softCodesFound') + if not soft_codes_found: + soft_codes_found = [] - return self.required_fields[place_holder].valid + for malware_id_found in malware_ids_found: + if malware_id_found not in soft_codes_found: + mitre_att_to_add = {MITRE_ATT: malware_id_found} + rule_to_generate_mitre_att[METADATA].append(mitre_att_to_add) + self.required_fields[MITRE_ATT].attributefound() + self.required_fields[MITRE_ATT].attributevalid() + self.required_fields_index[self.required_fields[MITRE_ATT].position].increment_count() class Helper: @@ -913,3 +922,27 @@ def get_group_from_alias(alias): return '' return group_from_alias[0][MITRE_GROUP_NAME] + + @staticmethod + def get_software_id_by_name(software_name): + """ + Used to find the software id with the software_name + :param software_name: The value of the malware name + :return: The return of the query of the MITRE ATT&CK database, null if there are no matches + """ + malware_return = Helper.fs.query([ + Filter('type', '=', 'malware'), + FilterCasefold('name', 'casefold', software_name) + ]) + + tool_return = Helper.fs.query([ + Filter('type', '=', 'tool'), + FilterCasefold('name', 'casefold', software_name) + ]) + + if malware_return: + return malware_return[0]['external_references'][0]['external_id'] + elif tool_return: + return tool_return[0]['external_references'][0]['external_id'] + else: + return '' diff --git a/yara-validator/yara_validator.py b/yara-validator/yara_validator.py index a7cd639..9f64f1e 100644 --- a/yara-validator/yara_validator.py +++ b/yara-validator/yara_validator.py @@ -28,6 +28,9 @@ HASH = 'hash' ACTOR = 'actor' AUTHOR = 'author' +CATEGORY = 'category' +CATEGORY_TYPE = 'info|exploit|technique|tool|malware' +MITRE_ATT = 'mitre_att' VALUE = 'value' STRING_ENCODING = 'string_encoding' WHITE_SPACE_REPLACEMENT = 'white_space_replacement' @@ -355,6 +358,8 @@ def __init__(self, stix_data_path, validator_yaml, validator_yaml_values): self.validators = Validators() self.required_fields = {} + self.metadata_keys_regex = r'' + self.metadata_keys_filter = r'^malware_type$' self.import_yara_cfg() self.required_fields_index = [Positional(i) for i in range(len(self.required_fields))] @@ -366,7 +371,9 @@ def __init__(self, stix_data_path, validator_yaml, validator_yaml_values): self.warning_functions = [ self.warning_author_no_report_check, self.warning_author_no_hash_check, - self.warning_actor_no_mitre_group + self.warning_actor_no_mitre_group, + self.warning_no_category_type, + self.warning_common_metadata_errors ] previous_position_values = None @@ -480,9 +487,9 @@ def validation(self, rule_to_validate, rule_to_validate_string, generate_values= metadata_index_and_metadata = {key: metadata_index} meta_not_initially_found.append(metadata_index_and_metadata) - meta_not_initially_found.reverse() - for metadata_to_check in meta_not_initially_found: - if len(metadata_to_check.keys()) == 1: + while meta_not_initially_found: + no_children_keys_found = True + for metadata_to_check in list(meta_not_initially_found): key_to_match = list(metadata_to_check.keys())[0] metadata_index = list(metadata_to_check.values())[0] @@ -492,11 +499,16 @@ def validation(self, rule_to_validate, rule_to_validate_string, generate_values= value = list(metadata.values())[0] if key in self.required_fields_children: + no_children_keys_found = False + meta_not_initially_found.remove(metadata_to_check) validity, rule_response = self.process_key(key, self.required_fields_children, rule_to_validate, metadata_index) if not validity: valid.update_validity(validity, key, rule_response) + if no_children_keys_found: + meta_not_initially_found = [] + for empty_metadata in sorted(index_of_empty_metadata, reverse=True): if list(rule_to_validate[METADATA][empty_metadata].values())[0] == '': metadata_vals.pop(empty_metadata) @@ -571,8 +583,32 @@ def warning_actor_no_mitre_group(self, rule_to_check, valid): warning_message = 'Actor: {!r} was not found in MITRE ATT&CK dataset.'.format(value) valid.update_warning(True, ACTOR, warning_message) + def warning_no_category_type(self, rule_to_check, valid): + category_child_place_holder = self.required_fields[CATEGORY].argument.get('child_place_holder') + if self.required_fields.get(CATEGORY).found and not self.required_fields.get(category_child_place_holder).found: + metadata_values = rule_to_check[METADATA] + for value in metadata_values: + if len(value.keys()) == 1: + key = list(value.keys())[0] + value = list(value.values())[0] + if key == CATEGORY: + warning_message = 'Category: {!r} was selected but there is no associated metadate with more' \ + 'information i.e. malware: "name of the malware".'.format(value) + valid.update_warning(True, CATEGORY_TYPE, warning_message) + + def warning_common_metadata_errors(self, rule_to_check, valid): + metadata_values = rule_to_check[METADATA] + for value in metadata_values: + if len(value.keys()) == 1: + key = list(value.keys())[0] + value = list(value.values())[0] + if re.fullmatch(self.metadata_keys_regex, key) and not re.fullmatch(self.metadata_keys_filter, key): + warning_message = 'Key: {!r} has a similar name to a key in the standard but was not validated' \ + 'because it did not match the standard.'.format(key) + valid.update_warning(True, key, warning_message) + def generate_required_optional_metadata(self, rule_to_validate): - req_optional_keys = self.return_req_optional() + req_optional_keys = self.return_req_optional(rule_to_validate) for key in req_optional_keys: if not self.required_fields[key].found: @@ -582,7 +618,7 @@ def generate_required_optional_metadata(self, rule_to_validate): self.required_fields[key].function(rule_to_validate, self.required_fields_index[ self.required_fields[key].position].index(), key) - def return_req_optional(self): + def return_req_optional(self, rule_to_validate): keys_to_return = [] for key in self.required_fields.keys(): if self.required_fields[key].optional == MetadataOpt.REQ_OPTIONAL: @@ -590,7 +626,12 @@ def return_req_optional(self): keys_to_return.append(key) if self.__mitre_group_alias() and self.required_fields[ACTOR].found: - keys_to_return.append(self.required_fields[ACTOR].argument.get("child_place_holder")) + keys_to_return.append(self.required_fields[ACTOR].argument.get('child_place_holder')) + + category_type = self.required_fields[CATEGORY].argument.get('child_place_holder') + if self.required_fields[category_type].argument.get('malwareIdsFound'): + self.validators.mitre_software_generator(rule_to_validate, CATEGORY, MITRE_ATT) + return keys_to_return def __mitre_group_alias(self): @@ -627,7 +668,8 @@ def handle_child_parent_metadata(self, metadata, params, metadata_in_child_paren if argument.get('parent'): self.required_fields[metadata + place_holder] = self.required_fields.pop(metadata) metadata_in_child_parent_relationship.append(argument.get('parent')) - elif argument.get('child'): + + if argument.get('child'): child_metadata = argument['child'] argument.update({'child_place_holder': child_metadata + place_holder}) metadata_in_child_parent_relationship.append(argument.get('child')) @@ -787,11 +829,16 @@ def import_yara_cfg(self): for index, item in enumerate(self.yara_config.items()): # python 3.6+ dictionary preserves the insertion order cfg_metadata = item[0] cfg_params = item[1] # {parameter : value} - + if cfg_metadata == 'info|exploit|technique|tool|malware': + self.metadata_keys_regex = self.metadata_keys_regex\ + + '^info.+|^exploit.+|^technique.+|^tool.+|^malware.+|' + else: + self.metadata_keys_regex = self.metadata_keys_regex + '^' + cfg_metadata + '.+|' self.required_fields[cfg_metadata] = self.read_yara_cfg(cfg_metadata, cfg_params, index) # add a new MetadataAttributes instance self.handle_child_parent_metadata(cfg_metadata, cfg_params, metadata_in_child_parent_relationship) # replace the name of child metadata with its place holder self.validate_child_parent_metadata(self.yara_config, metadata_in_child_parent_relationship) # check if any metadata in child-parent relationship are missing + self.metadata_keys_regex = self.metadata_keys_regex[:-1] From 8fb7377bc8b6e364e7234417f29f4e9a78895c30 Mon Sep 17 00:00:00 2001 From: cccs-gm <56171373+cccs-gm@users.noreply.github.com> Date: Mon, 31 Aug 2020 11:48:28 -0400 Subject: [PATCH 3/5] added support for tool autogeneration and name changes - Changed the names of some of the .yml variables - ifKeyGenMitreAtt -> if_key_gen_mitre_att, also changed it to be a regex expression for all valid keys that can currently be used to autogenerate mire_att software ids - malwareIdsFound -> mitre_softwareid_gen, also changed it to be predefined as an empty list - softCodesFound -> mitre_softwareid_found, also changed it to be predefined as an empty list - changed descriptions of info|exploit|technique|tool|malware and mitre_att to add details about the extra arguments and their purpose - added a note to the - : section in the documentation at the top --- CCCS_YARA.yml | 17 ++++++++++++----- yara-validator/validator_functions.py | 26 ++++++++++---------------- yara-validator/yara_validator.py | 4 ++-- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/CCCS_YARA.yml b/CCCS_YARA.yml index 3fbfcbe..262cc46 100644 --- a/CCCS_YARA.yml +++ b/CCCS_YARA.yml @@ -37,6 +37,8 @@ # - child: name of a child metadata (mutually exclusive with parent) # - parent: name of a parent metadata (mutually exclusive with child) # - : any custom parameter that will be passed to the validator as a part of "argument" +# (info|exploit|technique|tool|malware and mitre_att below have examples of some arguments +# that are passed into facilitate some more complex behaviors) # # --NOTE-- # 1. "unique" and "optional" are required fields. @@ -182,15 +184,19 @@ category: child: info|exploit|technique|tool|malware info|exploit|technique|tool|malware: - description: 'Specific information about the category' + description: 'Specific information about the category. + if_key_gen_mitre_att: is used to define which keys will been used to generate mitre_att software ids + mitre_softwareid_gen: is an array that will be used during runtime to store software ids generated, + this will be compared with mitre_softwareid_found and values not found there + will be generated' format: 'uppercase' unique: No optional: Yes validator: valid_category_type argument: parent: category - ifKeyGenMitreAtt: malware - malwareIdsFound: + if_key_gen_mitre_att: ^tool$|^malware$ + mitre_softwareid_gen: [] malware_type: description: 'Give an indication as to what the capability/purpose of the malware is' @@ -203,13 +209,14 @@ malware_type: valueName: malware_types mitre_att: - description: 'Any valid MITRE ATT&CK ID codes' + description: 'Any valid MITRE ATT&CK ID codes + mitre_softwareid_found: is an array that will be used during runtime to store software ids found' format: "'TA####'|'T####'|'M####'|'G####'|'S####'" unique: No optional: Yes validator: valid_mitre_att argument: - softCodesFound: + mitre_softwareid_found: [] actor_type: description: 'The type of the actor' diff --git a/yara-validator/validator_functions.py b/yara-validator/validator_functions.py index b9b1468..8985de8 100644 --- a/yara-validator/validator_functions.py +++ b/yara-validator/validator_functions.py @@ -371,13 +371,10 @@ def valid_mitre_att(self, rule_to_validate_mitre_att, metadata_index, metadata_k self.required_fields[MITRE_ATT].attributeinvalid() if self.required_fields[MITRE_ATT].valid and mitre_att_to_validate.startswith('S'): - soft_codes_found = self.required_fields[MITRE_ATT].argument.get('softCodesFound') - if not soft_codes_found: - soft_codes_found = [mitre_att_to_validate] - else: - soft_codes_found.append(mitre_att_to_validate) + soft_codes_found = self.required_fields[MITRE_ATT].argument.get('mitre_softwareid_found') + soft_codes_found.append(mitre_att_to_validate) - self.required_fields[MITRE_ATT].argument.update({'softCodesFound': soft_codes_found}) + self.required_fields[MITRE_ATT].argument.update({'mitre_softwareid_found': soft_codes_found}) return self.required_fields[MITRE_ATT].valid @@ -472,8 +469,8 @@ def valid_category_type(self, rule_to_validate_type, metadata_index, metadata_ke child_metadata_place_holder = self.required_fields[CATEGORY].argument.get('child_place_holder') self.required_fields[child_metadata_place_holder].attributefound() self.required_fields_index[self.required_fields[child_metadata_place_holder].position].increment_count() - key_gen_mitre_att = r'^' + self.required_fields[child_metadata_place_holder].argument.get('ifKeyGenMitreAtt')\ - + '$' + key_gen_mitre_att = r'' +\ + self.required_fields[child_metadata_place_holder].argument.get('if_key_gen_mitre_att') metadata = rule_to_validate_type[METADATA] rule_category_key_to_check = list(metadata[metadata_index].keys())[0] @@ -492,13 +489,10 @@ def valid_category_type(self, rule_to_validate_type, metadata_index, metadata_ke rule_category_key_to_check): malware_id = Helper.get_software_id_by_name(rule_category_value_to_check) if malware_id: - malware_ids_found = self.required_fields[child_metadata_place_holder].argument.get('malwareIdsFound') - if not malware_ids_found: - malware_ids_found = [malware_id] - else: - malware_ids_found.append(malware_id) + malware_ids_found = self.required_fields[child_metadata_place_holder].argument.get('mitre_softwareid_gen') + malware_ids_found.append(malware_id) - self.required_fields[child_metadata_place_holder].argument.update({'malwareIdsFound': malware_ids_found}) + self.required_fields[child_metadata_place_holder].argument.update({'mitre_softwareid_gen': malware_ids_found}) return self.required_fields[child_metadata_place_holder].valid @@ -606,8 +600,8 @@ def mitre_software_generator(self, rule_to_generate_mitre_att, category_key, mit MITRE_ATT = mitre_key child_metadata_place_holder = self.required_fields[CATEGORY].argument.get('child_place_holder') - malware_ids_found = self.required_fields[child_metadata_place_holder].argument.get('malwareIdsFound') - soft_codes_found = self.required_fields[MITRE_ATT].argument.get('softCodesFound') + malware_ids_found = self.required_fields[child_metadata_place_holder].argument.get('mitre_softwareid_gen') + soft_codes_found = self.required_fields[MITRE_ATT].argument.get('mitre_softwareid_found') if not soft_codes_found: soft_codes_found = [] diff --git a/yara-validator/yara_validator.py b/yara-validator/yara_validator.py index 9f64f1e..547c62d 100644 --- a/yara-validator/yara_validator.py +++ b/yara-validator/yara_validator.py @@ -359,7 +359,7 @@ def __init__(self, stix_data_path, validator_yaml, validator_yaml_values): self.validators = Validators() self.required_fields = {} self.metadata_keys_regex = r'' - self.metadata_keys_filter = r'^malware_type$' + self.metadata_keys_filter = r'^malware_type$|^actor_type$' self.import_yara_cfg() self.required_fields_index = [Positional(i) for i in range(len(self.required_fields))] @@ -629,7 +629,7 @@ def return_req_optional(self, rule_to_validate): keys_to_return.append(self.required_fields[ACTOR].argument.get('child_place_holder')) category_type = self.required_fields[CATEGORY].argument.get('child_place_holder') - if self.required_fields[category_type].argument.get('malwareIdsFound'): + if self.required_fields[category_type].argument.get('mitre_softwareid_gen'): self.validators.mitre_software_generator(rule_to_validate, CATEGORY, MITRE_ATT) return keys_to_return From 261cf2c7319c8cba2e4d6ef49d86aa77f0c063ca Mon Sep 17 00:00:00 2001 From: cccs-gm <56171373+cccs-gm@users.noreply.github.com> Date: Mon, 7 Sep 2020 21:16:49 -0400 Subject: [PATCH 4/5] Final Changes for feature_add_auto_mitre_software_code - removed the mitre_softwareid_gen and mitre_softwareid_found from the CCCS_YARA.yml file as they were empty arrays. - added Helper.check_argument_list_var() - it take a required_fields variable, should be self.required_fields - metadata, which should be the metadata you want to find the are argument value of - variable_name, the name of the argument variable you want returned - the function ensures there is an argument list variable of the name if it does not exist and returns it otherwise --- CCCS_YARA.yml | 5 +-- yara-validator/validator_functions.py | 45 +++++++++++++++++---------- yara-validator/yara_validator.py | 20 ++++++------ 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/CCCS_YARA.yml b/CCCS_YARA.yml index 262cc46..626992c 100644 --- a/CCCS_YARA.yml +++ b/CCCS_YARA.yml @@ -195,8 +195,7 @@ info|exploit|technique|tool|malware: validator: valid_category_type argument: parent: category - if_key_gen_mitre_att: ^tool$|^malware$ - mitre_softwareid_gen: [] + generate_mitre_att_from: ^tool$|^malware$ malware_type: description: 'Give an indication as to what the capability/purpose of the malware is' @@ -215,8 +214,6 @@ mitre_att: unique: No optional: Yes validator: valid_mitre_att - argument: - mitre_softwareid_found: [] actor_type: description: 'The type of the actor' diff --git a/yara-validator/validator_functions.py b/yara-validator/validator_functions.py index 8985de8..e852e37 100644 --- a/yara-validator/validator_functions.py +++ b/yara-validator/validator_functions.py @@ -17,7 +17,7 @@ BASE62_REGEX = r'^[0-9a-zA-z]+$' UNIVERSAL_REGEX = r'^[^a-z]*$' MITRE_GROUP_NAME = 'name' - +CHILD_PLACE_HOLDER = 'child_place_holder' # potential values of MetadataAttributes.optional variable class MetadataOpt(Enum): @@ -359,6 +359,7 @@ def valid_mitre_att(self, rule_to_validate_mitre_att, metadata_index, metadata_k :return: True if the value was found in the MITRE ATT&CK database and False if it was not found """ MITRE_ATT = metadata_key + MITRE_SOFTWAREID_FOUND = 'mitre_softwareid_found' self.required_fields[MITRE_ATT].attributefound() self.required_fields_index[self.required_fields[MITRE_ATT].position].increment_count() @@ -371,11 +372,9 @@ def valid_mitre_att(self, rule_to_validate_mitre_att, metadata_index, metadata_k self.required_fields[MITRE_ATT].attributeinvalid() if self.required_fields[MITRE_ATT].valid and mitre_att_to_validate.startswith('S'): - soft_codes_found = self.required_fields[MITRE_ATT].argument.get('mitre_softwareid_found') + soft_codes_found = Helper.check_argument_list_var(self.required_fields, MITRE_ATT, MITRE_SOFTWAREID_FOUND) soft_codes_found.append(mitre_att_to_validate) - self.required_fields[MITRE_ATT].argument.update({'mitre_softwareid_found': soft_codes_found}) - return self.required_fields[MITRE_ATT].valid def valid_al_config_dumper(self, rule_to_validate_al_config_d, metadata_index, metadata_key): @@ -433,7 +432,7 @@ def valid_category(self, rule_to_validate_category, metadata_index, metadata_key CATEGORY = metadata_key self.required_fields[CATEGORY].attributefound() self.required_fields_index[self.required_fields[CATEGORY].position].increment_count() - child_metadata_place_holder = self.required_fields[CATEGORY].argument.get('child_place_holder') + child_metadata_place_holder = self.required_fields[CATEGORY].argument.get(CHILD_PLACE_HOLDER) metadata = rule_to_validate_category[METADATA] rule_category_to_check = metadata[metadata_index][CATEGORY] @@ -466,11 +465,13 @@ def valid_category_type(self, rule_to_validate_type, metadata_index, metadata_ke :return: True if the value matches the Regex expression and False if it was not found """ CATEGORY = 'category' - child_metadata_place_holder = self.required_fields[CATEGORY].argument.get('child_place_holder') + GENERATE_MITRE_ATT_FROM = 'generate_mitre_att_from' + MITRE_SOFTWAREID_GEN = 'mitre_softwareid_gen' + child_metadata_place_holder = self.required_fields[CATEGORY].argument.get(CHILD_PLACE_HOLDER) self.required_fields[child_metadata_place_holder].attributefound() self.required_fields_index[self.required_fields[child_metadata_place_holder].position].increment_count() key_gen_mitre_att = r'' +\ - self.required_fields[child_metadata_place_holder].argument.get('if_key_gen_mitre_att') + self.required_fields[child_metadata_place_holder].argument.get(GENERATE_MITRE_ATT_FROM) metadata = rule_to_validate_type[METADATA] rule_category_key_to_check = list(metadata[metadata_index].keys())[0] @@ -489,11 +490,10 @@ def valid_category_type(self, rule_to_validate_type, metadata_index, metadata_ke rule_category_key_to_check): malware_id = Helper.get_software_id_by_name(rule_category_value_to_check) if malware_id: - malware_ids_found = self.required_fields[child_metadata_place_holder].argument.get('mitre_softwareid_gen') + malware_ids_found = Helper.check_argument_list_var(self.required_fields, child_metadata_place_holder, + MITRE_SOFTWAREID_GEN) malware_ids_found.append(malware_id) - self.required_fields[child_metadata_place_holder].argument.update({'mitre_softwareid_gen': malware_ids_found}) - return self.required_fields[child_metadata_place_holder].valid def valid_actor(self, rule_to_validate_actor, metadata_index, metadata_key): @@ -510,7 +510,7 @@ def valid_actor(self, rule_to_validate_actor, metadata_index, metadata_key): ACTOR = metadata_key ACTOR_TYPE = self.required_fields[ACTOR].argument.get('required') child_metadata = self.required_fields[ACTOR].argument.get('child') - child_metadata_place_holder = self.required_fields[ACTOR].argument.get('child_place_holder') + child_metadata_place_holder = self.required_fields[ACTOR].argument.get(CHILD_PLACE_HOLDER) mitre_group_alias_regex = r'^[^a-z]+$' self.required_fields[ACTOR].attributefound() @@ -548,7 +548,7 @@ def mitre_group_generator(self, rule_to_generate_group, metadata_index, metadata :return: This should return True all the time as there will always be a return from self.get_group_from_alias """ ACTOR = 'actor' - place_holder = self.required_fields[ACTOR].argument.get('child_place_holder') + place_holder = self.required_fields[ACTOR].argument.get(CHILD_PLACE_HOLDER) if self.required_fields.get(metadata_key): # if child place holder is passed as metadata_key MITRE_GROUP = self.required_fields[self.required_fields[metadata_key].argument['parent']].argument['child'] else: @@ -598,12 +598,12 @@ def mitre_software_generator(self, rule_to_generate_mitre_att, category_key, mit """ CATEGORY = category_key MITRE_ATT = mitre_key - child_metadata_place_holder = self.required_fields[CATEGORY].argument.get('child_place_holder') + MITRE_SOFTWAREID_GEN = 'mitre_softwareid_gen' + MITRE_SOFTWAREID_FOUND = 'mitre_softwareid_found' + child_metadata_place_holder = self.required_fields[CATEGORY].argument.get(CHILD_PLACE_HOLDER) - malware_ids_found = self.required_fields[child_metadata_place_holder].argument.get('mitre_softwareid_gen') - soft_codes_found = self.required_fields[MITRE_ATT].argument.get('mitre_softwareid_found') - if not soft_codes_found: - soft_codes_found = [] + malware_ids_found = Helper.check_argument_list_var(self.required_fields, child_metadata_place_holder, MITRE_SOFTWAREID_GEN) + soft_codes_found = Helper.check_argument_list_var(self.required_fields, MITRE_ATT, MITRE_SOFTWAREID_FOUND) for malware_id_found in malware_ids_found: if malware_id_found not in soft_codes_found: @@ -940,3 +940,14 @@ def get_software_id_by_name(software_name): return tool_return[0]['external_references'][0]['external_id'] else: return '' + + @staticmethod + def check_argument_list_var(required_fields, metadata, variable_name): + if not required_fields[metadata].argument: + required_fields[metadata].argument = {variable_name: []} + elif not required_fields[metadata].argument.get(variable_name): + required_fields[metadata].argument.update({variable_name: []}) + elif not isinstance(required_fields[metadata].argument.get(variable_name), list): + required_fields[metadata].argument.update({variable_name: []}) + + return required_fields[metadata].argument.get(variable_name) diff --git a/yara-validator/yara_validator.py b/yara-validator/yara_validator.py index 547c62d..0d12575 100644 --- a/yara-validator/yara_validator.py +++ b/yara-validator/yara_validator.py @@ -8,7 +8,7 @@ # for querying the MITRE ATT&CK data from stix2 import FileSystemSource -from validator_functions import Validators, MetadataOpt, StringEncoding, check_encoding +from validator_functions import Validators, MetadataOpt, StringEncoding, check_encoding, Helper from yara_file_processor import YaraFileProcessor, YaraRule # set current working directory @@ -31,6 +31,8 @@ CATEGORY = 'category' CATEGORY_TYPE = 'info|exploit|technique|tool|malware' MITRE_ATT = 'mitre_att' +CHILD_PLACE_HOLDER = 'child_place_holder' +MITRE_SOFTWAREID_GEN = 'mitre_softwareid_gen' VALUE = 'value' STRING_ENCODING = 'string_encoding' WHITE_SPACE_REPLACEMENT = 'white_space_replacement' @@ -571,8 +573,8 @@ def warning_author_no_hash_check(self, rule_to_check, valid): valid.update_warning(True, HASH, 'Rule is authored by the CCCS but no hash is referenced.') def warning_actor_no_mitre_group(self, rule_to_check, valid): - if self.required_fields.get(ACTOR) and self.required_fields[ACTOR].argument.get('child_place_holder'): - place_holder = self.required_fields[ACTOR].argument.get('child_place_holder') + if self.required_fields.get(ACTOR) and self.required_fields[ACTOR].argument.get(CHILD_PLACE_HOLDER): + place_holder = self.required_fields[ACTOR].argument.get(CHILD_PLACE_HOLDER) if self.required_fields[ACTOR].found and not self.required_fields[place_holder].found: metadata_values = rule_to_check[METADATA] for value in metadata_values: @@ -584,7 +586,7 @@ def warning_actor_no_mitre_group(self, rule_to_check, valid): valid.update_warning(True, ACTOR, warning_message) def warning_no_category_type(self, rule_to_check, valid): - category_child_place_holder = self.required_fields[CATEGORY].argument.get('child_place_holder') + category_child_place_holder = self.required_fields[CATEGORY].argument.get(CHILD_PLACE_HOLDER) if self.required_fields.get(CATEGORY).found and not self.required_fields.get(category_child_place_holder).found: metadata_values = rule_to_check[METADATA] for value in metadata_values: @@ -592,7 +594,7 @@ def warning_no_category_type(self, rule_to_check, valid): key = list(value.keys())[0] value = list(value.values())[0] if key == CATEGORY: - warning_message = 'Category: {!r} was selected but there is no associated metadate with more' \ + warning_message = 'Category: {!r} was selected but there is no associated metadate with more ' \ 'information i.e. malware: "name of the malware".'.format(value) valid.update_warning(True, CATEGORY_TYPE, warning_message) @@ -626,10 +628,10 @@ def return_req_optional(self, rule_to_validate): keys_to_return.append(key) if self.__mitre_group_alias() and self.required_fields[ACTOR].found: - keys_to_return.append(self.required_fields[ACTOR].argument.get('child_place_holder')) + keys_to_return.append(self.required_fields[ACTOR].argument.get(CHILD_PLACE_HOLDER)) - category_type = self.required_fields[CATEGORY].argument.get('child_place_holder') - if self.required_fields[category_type].argument.get('mitre_softwareid_gen'): + category_type = self.required_fields[CATEGORY].argument.get(CHILD_PLACE_HOLDER) + if Helper.check_argument_list_var(self.required_fields, category_type, MITRE_SOFTWAREID_GEN): self.validators.mitre_software_generator(rule_to_validate, CATEGORY, MITRE_ATT) return keys_to_return @@ -671,7 +673,7 @@ def handle_child_parent_metadata(self, metadata, params, metadata_in_child_paren if argument.get('child'): child_metadata = argument['child'] - argument.update({'child_place_holder': child_metadata + place_holder}) + argument.update({CHILD_PLACE_HOLDER: child_metadata + place_holder}) metadata_in_child_parent_relationship.append(argument.get('child')) def validate_child_parent_metadata(self, configuration, metadata_in_child_parent_relationship): From 08196295729e4f5b64ab5eef185fd820f206bdb3 Mon Sep 17 00:00:00 2001 From: cccs-gm <56171373+cccs-gm@users.noreply.github.com> Date: Thu, 10 Sep 2020 15:43:39 -0400 Subject: [PATCH 5/5] Moved the check_argument_list_var() funcitonality - removed it from the Helper class and added it to MetadataAttributes --- yara-validator/validator_functions.py | 21 ++++++--------------- yara-validator/yara_validator.py | 12 ++++++++++-- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/yara-validator/validator_functions.py b/yara-validator/validator_functions.py index e852e37..047afdb 100644 --- a/yara-validator/validator_functions.py +++ b/yara-validator/validator_functions.py @@ -372,7 +372,7 @@ def valid_mitre_att(self, rule_to_validate_mitre_att, metadata_index, metadata_k self.required_fields[MITRE_ATT].attributeinvalid() if self.required_fields[MITRE_ATT].valid and mitre_att_to_validate.startswith('S'): - soft_codes_found = Helper.check_argument_list_var(self.required_fields, MITRE_ATT, MITRE_SOFTWAREID_FOUND) + soft_codes_found = self.required_fields[MITRE_ATT].check_argument_list_var(MITRE_SOFTWAREID_FOUND) soft_codes_found.append(mitre_att_to_validate) return self.required_fields[MITRE_ATT].valid @@ -490,8 +490,8 @@ def valid_category_type(self, rule_to_validate_type, metadata_index, metadata_ke rule_category_key_to_check): malware_id = Helper.get_software_id_by_name(rule_category_value_to_check) if malware_id: - malware_ids_found = Helper.check_argument_list_var(self.required_fields, child_metadata_place_holder, - MITRE_SOFTWAREID_GEN) + malware_ids_found = self.required_fields[child_metadata_place_holder] \ + .check_argument_list_var(MITRE_SOFTWAREID_GEN) malware_ids_found.append(malware_id) return self.required_fields[child_metadata_place_holder].valid @@ -602,8 +602,9 @@ def mitre_software_generator(self, rule_to_generate_mitre_att, category_key, mit MITRE_SOFTWAREID_FOUND = 'mitre_softwareid_found' child_metadata_place_holder = self.required_fields[CATEGORY].argument.get(CHILD_PLACE_HOLDER) - malware_ids_found = Helper.check_argument_list_var(self.required_fields, child_metadata_place_holder, MITRE_SOFTWAREID_GEN) - soft_codes_found = Helper.check_argument_list_var(self.required_fields, MITRE_ATT, MITRE_SOFTWAREID_FOUND) + malware_ids_found = self.required_fields[child_metadata_place_holder]\ + .check_argument_list_var(MITRE_SOFTWAREID_GEN) + soft_codes_found = self.required_fields[MITRE_ATT].check_argument_list_var(MITRE_SOFTWAREID_FOUND) for malware_id_found in malware_ids_found: if malware_id_found not in soft_codes_found: @@ -941,13 +942,3 @@ def get_software_id_by_name(software_name): else: return '' - @staticmethod - def check_argument_list_var(required_fields, metadata, variable_name): - if not required_fields[metadata].argument: - required_fields[metadata].argument = {variable_name: []} - elif not required_fields[metadata].argument.get(variable_name): - required_fields[metadata].argument.update({variable_name: []}) - elif not isinstance(required_fields[metadata].argument.get(variable_name), list): - required_fields[metadata].argument.update({variable_name: []}) - - return required_fields[metadata].argument.get(variable_name) diff --git a/yara-validator/yara_validator.py b/yara-validator/yara_validator.py index 0d12575..212aefa 100644 --- a/yara-validator/yara_validator.py +++ b/yara-validator/yara_validator.py @@ -8,7 +8,7 @@ # for querying the MITRE ATT&CK data from stix2 import FileSystemSource -from validator_functions import Validators, MetadataOpt, StringEncoding, check_encoding, Helper +from validator_functions import Validators, MetadataOpt, StringEncoding, check_encoding from yara_file_processor import YaraFileProcessor, YaraRule # set current working directory @@ -304,6 +304,14 @@ def attributereset(self): self.found = False self.valid = False + def check_argument_list_var(self, variable_name): + if not self.argument or not isinstance(self.argument, dict): + self.argument = {variable_name: []} + elif not self.argument.get(variable_name) or not isinstance(self.argument.get(variable_name), list): + self.argument.update({variable_name: []}) + + return self.argument.get(variable_name) + class Positional: """ @@ -631,7 +639,7 @@ def return_req_optional(self, rule_to_validate): keys_to_return.append(self.required_fields[ACTOR].argument.get(CHILD_PLACE_HOLDER)) category_type = self.required_fields[CATEGORY].argument.get(CHILD_PLACE_HOLDER) - if Helper.check_argument_list_var(self.required_fields, category_type, MITRE_SOFTWAREID_GEN): + if self.required_fields[category_type].check_argument_list_var(MITRE_SOFTWAREID_GEN): self.validators.mitre_software_generator(rule_to_validate, CATEGORY, MITRE_ATT) return keys_to_return