mirror of https://github.com/MISP/misp-galaxy
				
				
				
			
		
			
				
	
	
		
			209 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
			
		
		
	
	
			209 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
#!/usr/bin/env python3
 | 
						|
'''
 | 
						|
Author: Christophe Vandeplas
 | 
						|
License: AGPL v3
 | 
						|
 | 
						|
This builds an automatic mapping between the galaxy clusters of the same type.
 | 
						|
The mapping is made by using the synonyms documented in each cluster.
 | 
						|
 | 
						|
The output is saved in the cluster files themselves, if a change is done the version number is increased.
 | 
						|
(commented out) The output is saved in a file called "mapping_XXX.json".
 | 
						|
 | 
						|
# TODO add a blacklist support for blacklisted mappings
 | 
						|
'''
 | 
						|
import json
 | 
						|
import os
 | 
						|
 | 
						|
 | 
						|
# Some galaxy clusters have overlapping synonyms, while not being of the same type.
 | 
						|
# This type_mapping is there to distinguish galaxies based on their type.
 | 
						|
# Example: A galaxy of type 'actor' should not map to a galaxy of type 'tool', even if the name/synonym is the same.
 | 
						|
type_mapping = {
 | 
						|
    'ransomware': 'tool',
 | 
						|
    # 'mitre-pre-attack-relationship': '',
 | 
						|
    # 'mitre-enterprise-attack-course-of-action': '',
 | 
						|
    'mitre-enterprise-attack-intrusion-set': 'actor',
 | 
						|
    'mitre-intrusion-set': 'actor',
 | 
						|
    'rat': 'tool',
 | 
						|
    'stealer': 'tool',
 | 
						|
    'mitre-enterprise-attack-malware': 'tool',
 | 
						|
    # 'mitre-attack-pattern': '',
 | 
						|
    # 'mitre-mobile-attack-relationship': '',
 | 
						|
    # 'mitre-enterprise-attack-attack-pattern': '',
 | 
						|
    'microsoft-activity-group': 'actor',
 | 
						|
    # 'mitre-course-of-action': '',
 | 
						|
    'exploit-kit': 'tool',
 | 
						|
    'mitre-mobile-attack-tool': 'tool',
 | 
						|
    'backdoor': 'tool',
 | 
						|
    # 'mitre-pre-attack-attack-pattern': '',
 | 
						|
    'mitre-mobile-attack-intrusion-set': 'actor',
 | 
						|
    'mitre-tool': 'tool',
 | 
						|
    # 'mitre-mobile-attack-attack-pattern': '',
 | 
						|
    'mitre-mobile-attack-malware': 'tool',
 | 
						|
    'tool': 'tool',
 | 
						|
    # 'preventive-measure': '',
 | 
						|
    # 'sector': '',
 | 
						|
    'mitre-malware': 'tool',
 | 
						|
    'banker': 'tool',
 | 
						|
    # 'branded-vulnerability': '',
 | 
						|
    'botnet': 'tool',
 | 
						|
    # 'cert-eu-govsector': '',
 | 
						|
    'threat-actor': 'actor',
 | 
						|
    'mitre-enterprise-attack-tool': 'tool',
 | 
						|
    'android': 'tool',
 | 
						|
    # 'mitre-mobile-attack-course-of-action': '',
 | 
						|
    'mitre-pre-attack-intrusion-set': 'actor',
 | 
						|
    # 'mitre-enterprise-attack-relationship': '',
 | 
						|
    'tds': 'tool',
 | 
						|
    'malpedia': 'tool'
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
def loadjsons(path):
 | 
						|
    """
 | 
						|
      Find all Jsons and load them in a dict
 | 
						|
    """
 | 
						|
    files = []
 | 
						|
    data = []
 | 
						|
    for name in os.listdir(path):
 | 
						|
        if os.path.isfile(os.path.join(path, name)) and name.endswith('.json'):
 | 
						|
            files.append(name)
 | 
						|
    for jfile in files:
 | 
						|
        data.append(json.load(open("%s/%s" % (path, jfile))))
 | 
						|
    return data
 | 
						|
 | 
						|
 | 
						|
def printjson(s):
 | 
						|
    print(json.dumps(s, sort_keys=True, indent=4, separators=(',', ': ')))
 | 
						|
 | 
						|
 | 
						|
def to_tag(t, v):
 | 
						|
    return 'misp-galaxy:{}="{}"'.format(t, v)
 | 
						|
 | 
						|
 | 
						|
def get_cluster_uuid(cluster):
 | 
						|
    uuid = cluster.get('uuid')
 | 
						|
    if not uuid:  # FIXME are these bugs in the format? - mitre-tool.json
 | 
						|
        uuid = cluster['meta'].get('uuid')
 | 
						|
    if not uuid:
 | 
						|
        print(cluster)
 | 
						|
        exit("ERROR: missing UUID in cluster")
 | 
						|
    return uuid
 | 
						|
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    path = '../clusters'
 | 
						|
    jsons = loadjsons(path)
 | 
						|
    mappings = {}
 | 
						|
    for k, v in type_mapping.items():
 | 
						|
        if v not in mappings:
 | 
						|
            mappings[v] = []
 | 
						|
 | 
						|
    for djson in jsons:
 | 
						|
        galaxy = djson['type']
 | 
						|
 | 
						|
        # ignore the galaxies that are not relevant for us
 | 
						|
        if galaxy not in type_mapping:
 | 
						|
            print("Ignoring galaxy '{}' as it is not in the mapping.".format(galaxy))
 | 
						|
            continue
 | 
						|
 | 
						|
        # process the entries in each cluster
 | 
						|
        clusters = djson.get('values')
 | 
						|
        for cluster in clusters:
 | 
						|
            names = [cluster['value']]
 | 
						|
 | 
						|
            if 'meta' in cluster and 'synonyms' in cluster['meta']:
 | 
						|
                names += [s for s in cluster['meta']['synonyms']]
 | 
						|
 | 
						|
            # check if the entry is already in our mappings dict
 | 
						|
            seen_once = False
 | 
						|
            for mapping in mappings[type_mapping[galaxy]]:
 | 
						|
                seen = False
 | 
						|
                # name is known, add the synonyms and tags
 | 
						|
                for name in names:
 | 
						|
                    if name in mapping['names']:
 | 
						|
                        seen = True
 | 
						|
                        seen_once = True
 | 
						|
                # we have a match in this mapping, add name and synonyms
 | 
						|
                if seen:
 | 
						|
                    for name in names:
 | 
						|
                        if name not in mapping['names']:
 | 
						|
                            mapping['names'].append(name)
 | 
						|
                    tag = to_tag(galaxy, cluster['value'])
 | 
						|
                    if tag not in mapping['values']:
 | 
						|
                        mapping['values'].append(tag)
 | 
						|
                    uuid = get_cluster_uuid(cluster)
 | 
						|
                    if uuid not in mapping['uuids']:
 | 
						|
                        mapping['uuids'].append(uuid)
 | 
						|
 | 
						|
            # it's not in any mapping, add it
 | 
						|
            if not seen_once:
 | 
						|
                mapping = {}
 | 
						|
                mapping['names'] = names
 | 
						|
                mapping['values'] = [to_tag(galaxy, cluster['value'])]
 | 
						|
                uuid = get_cluster_uuid(cluster)
 | 
						|
                mapping['uuids'] = [uuid]
 | 
						|
                mappings[type_mapping[galaxy]].append(mapping)
 | 
						|
 | 
						|
    # We have our nice mapping.
 | 
						|
    # Now we only need to add it again in the original files.
 | 
						|
    for name in os.listdir(path):
 | 
						|
        # skip files that are not relevant
 | 
						|
        if not (os.path.isfile(os.path.join(path, name)) and name.endswith('.json')):
 | 
						|
            continue
 | 
						|
 | 
						|
        # load json
 | 
						|
        with open(os.path.join(path, name), 'r') as f_in:
 | 
						|
            file_json = json.load(f_in)
 | 
						|
        galaxy = file_json['type']
 | 
						|
 | 
						|
        # ignore the galaxies that are not relevant for us
 | 
						|
        if galaxy not in type_mapping:
 | 
						|
            continue
 | 
						|
 | 
						|
        changed = False
 | 
						|
        for cluster in file_json['values']:
 | 
						|
            for mapping in mappings[type_mapping[galaxy]]:
 | 
						|
                cluster_uuid = get_cluster_uuid(cluster)
 | 
						|
                if cluster_uuid not in mapping['uuids']:
 | 
						|
                    continue
 | 
						|
                # uuid is in the mappings
 | 
						|
                for uuid in mapping['uuids']:
 | 
						|
                    # skip self
 | 
						|
                    if uuid == cluster_uuid:
 | 
						|
                        continue
 | 
						|
                    # skip existing entries
 | 
						|
                    if 'related' in cluster:
 | 
						|
                        if any(v['dest-uuid'] == uuid for v in cluster['related']):
 | 
						|
                            continue
 | 
						|
                    # initialize array
 | 
						|
                    if 'related' not in cluster:
 | 
						|
                        cluster['related'] = []
 | 
						|
                    # automated things are set to likely
 | 
						|
                    # manual validation can upgrade to very-likely or almost-certain
 | 
						|
                    cluster['related'].append({"dest-uuid": uuid,
 | 
						|
                                               "type": "similar",
 | 
						|
                                               "tags": [
 | 
						|
                                                   "estimative-language:likelihood-probability=\"likely\""
 | 
						|
                                               ]
 | 
						|
                                               })
 | 
						|
                    changed = True
 | 
						|
        if changed:
 | 
						|
            file_json['version'] += 1
 | 
						|
 | 
						|
            # save result to the original file
 | 
						|
            with open(os.path.join(path, name), 'w') as f_out:
 | 
						|
                json.dump(file_json, f_out, indent=2, sort_keys=True, ensure_ascii=False)
 | 
						|
 | 
						|
            print("Updated file {}".format(name))
 | 
						|
    print("All done, please don't forget to ./validate_all.sh and ./jq_all_the_things.sh")
 | 
						|
 | 
						|
    # # simply dump the mapping_json to files. This is not really needed anymore
 | 
						|
    # for galaxy_type, vals in mappings.items():
 | 
						|
    #     for mapping in vals:
 | 
						|
    #         mapping['names'].sort()
 | 
						|
    #         mapping['values'].sort()
 | 
						|
    #     with open('mapping_{}.json'.format(galaxy_type), 'w') as f:
 | 
						|
    #         json.dump(vals, f, sort_keys=True, indent=4, separators=(',', ': '))
 | 
						|
    #     print("File saved as mapping_{}.json".format(galaxy_type))
 |