diff --git a/CCCS_YARA.yml b/CCCS_YARA.yml index 49451ab..626992c 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,13 +184,18 @@ 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 + generate_mitre_att_from: ^tool$|^malware$ malware_type: description: 'Give an indication as to what the capability/purpose of the malware is' @@ -201,7 +208,8 @@ 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 diff --git a/yara-validator/validator_functions.py b/yara-validator/validator_functions.py index e30652a..047afdb 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): @@ -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,13 +351,15 @@ 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 :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() @@ -368,6 +371,10 @@ 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].check_argument_list_var(MITRE_SOFTWAREID_FOUND) + soft_codes_found.append(mitre_att_to_validate) + 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 +422,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 @@ -424,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] @@ -447,17 +455,23 @@ 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 :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(GENERATE_MITRE_ATT_FROM) metadata = rule_to_validate_type[METADATA] rule_category_key_to_check = list(metadata[metadata_index].keys())[0] @@ -471,6 +485,15 @@ 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] \ + .check_argument_list_var(MITRE_SOFTWAREID_GEN) + malware_ids_found.append(malware_id) + return self.required_fields[child_metadata_place_holder].valid def valid_actor(self, rule_to_validate_actor, metadata_index, metadata_key): @@ -487,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() @@ -525,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: @@ -562,7 +585,34 @@ 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_mitre_att, category_key, mitre_key): + """ + 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: + """ + CATEGORY = category_key + MITRE_ATT = mitre_key + 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]\ + .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: + 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: @@ -867,3 +917,28 @@ 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_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/yara_validator.py b/yara-validator/yara_validator.py index a7cd639..212aefa 100644 --- a/yara-validator/yara_validator.py +++ b/yara-validator/yara_validator.py @@ -28,6 +28,11 @@ HASH = 'hash' ACTOR = 'actor' AUTHOR = 'author' +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' @@ -299,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: """ @@ -355,6 +368,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$|^actor_type$' self.import_yara_cfg() self.required_fields_index = [Positional(i) for i in range(len(self.required_fields))] @@ -366,7 +381,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 +497,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 +509,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) @@ -559,8 +581,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: @@ -571,8 +593,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 +628,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 +636,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].check_argument_list_var(MITRE_SOFTWAREID_GEN): + self.validators.mitre_software_generator(rule_to_validate, CATEGORY, MITRE_ATT) + return keys_to_return def __mitre_group_alias(self): @@ -627,9 +678,10 @@ 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}) + 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): @@ -787,11 +839,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] diff --git a/yara_validator_cli.py b/yara_validator_cli.py index 3192dc0..413f8a7 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))