Skip to content

Commit

Permalink
Merge pull request #35 from CybercentreCanada/feature_add_auto_mitre_…
Browse files Browse the repository at this point in the history
…software_code

Feature add auto mitre software code
  • Loading branch information
cccs-gm authored Sep 11, 2020
2 parents 1f38f96 + 0819629 commit 1c3c83a
Show file tree
Hide file tree
Showing 5 changed files with 312 additions and 25 deletions.
12 changes: 10 additions & 2 deletions CCCS_YARA.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
# - child: name of a child metadata (mutually exclusive with parent)
# - parent: name of a parent metadata (mutually exclusive with child)
# - <custom parameter>: 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.
Expand Down Expand Up @@ -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'
Expand All @@ -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
Expand Down
93 changes: 84 additions & 9 deletions yara-validator/validator_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()

Expand All @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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]
Expand All @@ -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]
Expand All @@ -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):
Expand All @@ -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()
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 ''

Loading

0 comments on commit 1c3c83a

Please sign in to comment.