From 94e8b5e4b50dc3f6e23c08a18a97b9236b40426d Mon Sep 17 00:00:00 2001 From: Marcus Bakker Date: Sat, 31 Oct 2020 21:01:09 +0100 Subject: [PATCH] Removed support for PRE-ATT&CK from the Group mode --- data_source_mapping.py | 2 +- dettect.py | 26 ++++++++++++-------------- generic.py | 28 +++++++++++----------------- group_mapping.py | 35 +++++++++++++++-------------------- interactive_menu.py | 24 +++++++++--------------- technique_mapping.py | 8 ++++---- 6 files changed, 52 insertions(+), 71 deletions(-) diff --git a/data_source_mapping.py b/data_source_mapping.py index 7ad7608..b0b793a 100644 --- a/data_source_mapping.py +++ b/data_source_mapping.py @@ -26,7 +26,7 @@ def generate_data_sources_layer(filename, output_filename, layer_name, platform= if not layer_name: layer_name = 'Data sources ' + name - layer = get_layer_template_data_sources(layer_name, 'description', 'attack', platform) + layer = get_layer_template_data_sources(layer_name, 'description', platform) layer['techniques'] = my_techniques json_string = simplejson.dumps(layer).replace('}, ', '},\n') diff --git a/dettect.py b/dettect.py index de084dd..dab7743 100644 --- a/dettect.py +++ b/dettect.py @@ -25,7 +25,7 @@ def _init_menu(): 'group, generic} --help', metavar='', dest='subparser') parser_editor = subparsers.add_parser('editor', aliases=['e'], help='DeTT&CT Editor', - description='Start the DeTT&CT Editor for easy editing the YAML administration files.') + description='Start the DeTT&CT Editor for easy editing the YAML administration files') parser_editor.add_argument('-p', '--port', help='port where the webserver listens on (default is 8080)', required=False, default=8080) # create the data source parser @@ -69,9 +69,9 @@ def _init_menu(): parser_data_sources.add_argument('-ln', '--layer-name', help='set the name of the Navigator layer') parser_data_sources.add_argument('--health', help='check the YAML file(s) for errors', action='store_true') parser_data_sources.add_argument('--local-stix-path', help='path to a local STIX repository to use DeTT&CT offline ' - 'or to use a specific version of STIX objects.') + 'or to use a specific version of STIX objects') parser_data_sources.add_argument('--update-to-sub-techniques', help='Update the technique administration YAML file ' - 'to ATT&CK with sub-techniques.', action='store_true') + 'to ATT&CK with sub-techniques', action='store_true') # create the visibility parser parser_visibility = subparsers.add_parser('visibility', aliases=['v'], @@ -107,9 +107,9 @@ def _init_menu(): parser_visibility.add_argument('-ln', '--layer-name', help='set the name of the Navigator layer') parser_visibility.add_argument('--health', help='check the YAML file for errors', action='store_true') parser_visibility.add_argument('--local-stix-path', help='path to a local STIX repository to use DeTT&CT offline ' - 'or to use a specific version of STIX objects.') + 'or to use a specific version of STIX objects') parser_visibility.add_argument('--update-to-sub-techniques', help='Update the technique administration YAML file ' - 'to ATT&CK with sub-techniques.', action='store_true') + 'to ATT&CK with sub-techniques', action='store_true') # create the detection parser parser_detection = subparsers.add_parser('detection', aliases=['d'], @@ -147,9 +147,9 @@ def _init_menu(): parser_detection.add_argument('-ln', '--layer-name', help='set the name of the Navigator layer') parser_detection.add_argument('--health', help='check the YAML file(s) for errors', action='store_true') parser_detection.add_argument('--local-stix-path', help='path to a local STIX repository to use DeTT&CT offline ' - 'or to use a specific version of STIX objects.') + 'or to use a specific version of STIX objects') parser_detection.add_argument('--update-to-sub-techniques', help='Update the technique administration YAML file ' - 'to ATT&CK with sub-techniques.', action='store_true') + 'to ATT&CK with sub-techniques', action='store_true') # create the group parser parser_group = subparsers.add_parser('group', aliases=['g'], @@ -159,7 +159,7 @@ def _init_menu(): parser_group.add_argument('-g', '--groups', help='specify the ATT&CK Groups to include. Group can be its ID, ' 'name or alias (default is all groups). Multiple Groups can be ' 'provided with extra \'-g/--group\' arguments. Another option is ' - 'to provide a YAML file with a custom group(s).', + 'to provide a YAML file with a custom group(s)', default=None, action='append') parser_group.add_argument('-o', '--overlay', help='specify what to overlay on the group(s) (provided using the ' 'arguments \-g/--groups\): group(s), visibility or detection. ' @@ -180,8 +180,6 @@ def _init_menu(): 'can be provided with extra \'-p/--platform\' arguments', choices=['all'] + list(PLATFORMS.values()), default=None, action='append', type=_platform_lookup()) - parser_group.add_argument('-s', '--stage', help='specify the stage (default = attack)', - choices=['attack', 'pre-attack'], default='attack') parser_group.add_argument('-sd', '--search-detection', help='only include detection objects which match the ' 'provided EQL query') parser_group.add_argument('-sv', '--search-visibility', help='only include visibility objects which match the ' @@ -194,9 +192,9 @@ def _init_menu(): parser_group.add_argument('-ln', '--layer-name', help='set the name of the Navigator layer') parser_group.add_argument('--health', help='check the YAML file(s) for errors', action='store_true') parser_group.add_argument('--local-stix-path', help='path to a local STIX repository to use DeTT&CT offline ' - 'or to use a specific version of STIX objects.') + 'or to use a specific version of STIX objects') parser_group.add_argument('--update-to-sub-techniques', help='Update the technique administration YAML file ' - 'to ATT&CK with sub-techniques.', action='store_true') + 'to ATT&CK with sub-techniques', action='store_true') # create the generic parser parser_generic = subparsers.add_parser('generic', description='Generic functions which will output to stdout.', @@ -216,7 +214,7 @@ def _init_menu(): 'date (default = modified)', choices=['modified', 'created'], default='modified') parser_generic.add_argument('--local-stix-path', help='path to a local STIX repository to use DeTT&CT offline ' - 'or to use a specific version of STIX objects.') + 'or to use a specific version of STIX objects') return menu_parser @@ -289,7 +287,7 @@ def _menu(menu_parser): # TODO add search capabilities elif args.subparser in ['group', 'g']: - generate_group_heat_map(args.groups, args.overlay, args.overlay_type, args.stage, args.platform, + generate_group_heat_map(args.groups, args.overlay, args.overlay_type, args.platform, args.software_group, args.search_visibility, args.search_detection, args.health, args.output_filename, args.layer_name, include_all_score_objs=args.all_scores) diff --git a/generic.py b/generic.py index 525ad01..9f34e27 100644 --- a/generic.py +++ b/generic.py @@ -206,13 +206,12 @@ def init_yaml(): return _yaml -def _get_base_template(name, description, stage, platform, sorting): +def _get_base_template(name, description, platform, sorting): """ Prepares a base template for the json layer file that can be loaded into the MITRE ATT&CK Navigator. More information on the layer format can be found here: https://github.com/mitre/attack-navigator/blob/master/layers/ :param name: name :param description: description - :param stage: stage (act | prepare) :param platform: platform :param sorting: sorting :return: layer template dictionary @@ -236,19 +235,18 @@ def _get_base_template(name, description, stage, platform, sorting): return layer -def get_layer_template_groups(name, max_count, description, stage, platform, overlay_type): +def get_layer_template_groups(name, max_count, description, platform, overlay_type): """ Prepares a base template for the json layer file that can be loaded into the MITRE ATT&CK Navigator. More information on the layer format can be found here: https://github.com/mitre/attack-navigator/blob/master/layers/ :param name: name :param max_count: the sum of all count values :param description: description - :param stage: stage (act | prepare) :param platform: platform :param overlay_type: group, visibility or detection :return: layer template dictionary """ - layer = _get_base_template(name, description, stage, platform, 3) + layer = _get_base_template(name, description, platform, 3) layer['gradient'] = {'colors': [COLOR_GRADIENT_MIN, COLOR_GRADIENT_MAX], 'minValue': 0, 'maxValue': max_count} layer['legendItems'] = [] layer['legendItems'].append({'label': 'Tech. not often used', 'color': COLOR_GRADIENT_MIN}) @@ -285,17 +283,16 @@ def get_layer_template_groups(name, max_count, description, stage, platform, ove return layer -def get_layer_template_detections(name, description, stage, platform): +def get_layer_template_detections(name, description, platform): """ Prepares a base template for the json layer file that can be loaded into the MITRE ATT&CK Navigator. More information on the layer format can be found here: https://github.com/mitre/attack-navigator/blob/master/layers/ :param name: name :param description: description - :param stage: stage (act | prepare) :param platform: platform :return: layer template dictionary """ - layer = _get_base_template(name, description, stage, platform, 0) + layer = _get_base_template(name, description, platform, 0) layer['legendItems'] = \ [ {'label': 'Detection score 0: Forensics/Context', 'color': COLOR_D_0}, @@ -308,17 +305,16 @@ def get_layer_template_detections(name, description, stage, platform): return layer -def get_layer_template_data_sources(name, description, stage, platform): +def get_layer_template_data_sources(name, description, platform): """ Prepares a base template for the json layer file that can be loaded into the MITRE ATT&CK Navigator. More information on the layer format can be found here: https://github.com/mitre/attack-navigator/blob/master/layers/ :param name: name :param description: description - :param stage: stage (act | prepare) :param platform: platform :return: layer template dictionary """ - layer = _get_base_template(name, description, stage, platform, 0) + layer = _get_base_template(name, description, platform, 0) layer['legendItems'] = \ [ {'label': '1-25% of data sources available', 'color': COLOR_DS_25p}, @@ -330,17 +326,16 @@ def get_layer_template_data_sources(name, description, stage, platform): return layer -def get_layer_template_visibility(name, description, stage, platform): +def get_layer_template_visibility(name, description, platform): """ Prepares a base template for the json layer file that can be loaded into the MITRE ATT&CK Navigator. More information on the layer format can be found here: https://github.com/mitre/attack-navigator/blob/master/layers/ :param name: name :param description: description - :param stage: stage (act | prepare) :param platform: platform :return: layer template dictionary """ - layer = _get_base_template(name, description, stage, platform, 0) + layer = _get_base_template(name, description, platform, 0) layer['legendItems'] = \ [ {'label': 'Visibility score 1: Minimal', 'color': COLOR_V_1}, @@ -351,17 +346,16 @@ def get_layer_template_visibility(name, description, stage, platform): return layer -def get_layer_template_layered(name, description, stage, platform): +def get_layer_template_layered(name, description, platform): """ Prepares a base template for the json layer file that can be loaded into the MITRE ATT&CK Navigator. More information on the layer format can be found here: https://github.com/mitre/attack-navigator/blob/master/layers/ :param name: name :param description: description - :param stage: stage (act | prepare) :param platform: platform :return: layer template dictionary """ - layer = _get_base_template(name, description, stage, platform, 0) + layer = _get_base_template(name, description, platform, 0) layer['legendItems'] = \ [ {'label': 'Visibility and detection', 'color': COLOR_OVERLAY_BOTH}, diff --git a/group_mapping.py b/group_mapping.py index 8c1ded2..b772638 100644 --- a/group_mapping.py +++ b/group_mapping.py @@ -44,19 +44,18 @@ def _are_groups_found(groups_found, argument_groups): if not group_id: # the group that has been provided through the command line cannot be found in ATT&CK print('[!] Unknown ATT&CK group: ' + group_arg) group_found = False - elif group_id not in groups_found: # group not present in filtered data sate (i.e. platform and stage) + elif group_id not in groups_found: # group not present in filtered (platform) data set print('[!] Group not part of the data set: ' + group_arg) group_found = False return group_found -def _get_software_techniques(groups, stage, platform): +def _get_software_techniques(groups, platform): """ Get all techniques (in a dict) from the provided list of groups in relation to the software these groups use, and hence techniques they support. :param groups: ATT&CK groups - :param stage: attack or pre-attack :param platform: one or multiple values from PLATFORMS constant :return: dictionary with info on groups """ @@ -108,7 +107,7 @@ def _get_software_techniques(groups, stage, platform): # software matches the ATT&CK Matrix and platform # and the group is a group we are interested in if s['x_mitre_platforms']: # there is software that do not have a platform, skip those - if s['matrix'] == 'mitre-' + stage and len(set(s['x_mitre_platforms']).intersection(set(platform))) > 0 and \ + if s['matrix'] == 'mitre-attack' and len(set(s['x_mitre_platforms']).intersection(set(platform))) > 0 and \ (groups[0] == 'all' or s['group_id'].lower() in groups or _is_in_group(s['aliases'], groups)): if s['group_id'] not in groups_dict: groups_dict[s['group_id']] = {'group_name': s['name']} @@ -149,11 +148,10 @@ def _generate_group_id(group_name, campaign): return CG_GROUPS[group_name + campaign] -def _get_group_techniques(groups, stage, platform, file_type): +def _get_group_techniques(groups, platform, file_type): """ Get all techniques (in a dict) from the provided list of groups :param groups: group ID, group name/alias or a YAML file with group(s) data - :param stage: attack or pre-attack :param platform: one or multiple values from PLATFORMS constant :param file_type: the file type of the YAML file as present in the key 'file_type' :return: returns dictionary with all techniques from the provided list of groups or -1 when group is not found @@ -196,7 +194,7 @@ def _get_group_techniques(groups, stage, platform, file_type): platforms = ['Windows'] # group matches the: matrix/stage, platform and the group(s) we are interested in - if gr['matrix'] == 'mitre-' + stage and len(set(platforms).intersection(set(platform))) > 0 and \ + if gr['matrix'] == 'mitre-attack' and len(set(platforms).intersection(set(platform))) > 0 and \ (groups[0] == 'all' or gr['group_id'].lower() in groups or _is_in_group(gr['aliases'], groups)): if gr['group_id'] not in groups_dict: groups_found.add(gr['group_id']) @@ -477,7 +475,7 @@ def _get_group_list(groups, file_type): return groups -def generate_group_heat_map(groups, overlay, overlay_type, stage, platform, software_groups, search_visibility, +def generate_group_heat_map(groups, overlay, overlay_type, platform, software_groups, search_visibility, search_detection, health_is_called, output_filename, layer_name, include_all_score_objs=False): """ Calls all functions that are necessary for the generation of the heat map and write a json layer to disk. @@ -485,7 +483,6 @@ def generate_group_heat_map(groups, overlay, overlay_type, stage, platform, soft :param overlay: group(s), visibility or detections to overlay (group ID, group name/alias, YAML file with group(s), detections or visibility) :param overlay_type: group, visibility or detection - :param stage: attack or pre-attack :param platform: one or multiple the values from PLATFORMS constant or None (default = Windows) :param software_groups: specify if techniques from related software should be included :param search_visibility: visibility EQL search query @@ -567,11 +564,11 @@ def generate_group_heat_map(groups, overlay, overlay_type, stage, platform, soft # we are not overlaying visibility or detection, overlay group will therefore contain information on another group elif len(overlay) > 0: - overlay_dict = _get_group_techniques(overlay, stage, platform, overlay_file_type) + overlay_dict = _get_group_techniques(overlay, platform, overlay_file_type) if overlay_dict == -1: return None # returns None when the provided Group(s) to be overlaid, contains Groups not part of ATT&CK - groups_dict = _get_group_techniques(groups, stage, platform, groups_file_type) + groups_dict = _get_group_techniques(groups, platform, groups_file_type) if groups_dict == -1: return None # returns None when the provided Group contains Groups not part of ATT&CK if len(groups_dict) == 0: @@ -582,9 +579,9 @@ def generate_group_heat_map(groups, overlay, overlay_type, stage, platform, soft if software_groups and overlay: if overlay_type not in [OVERLAY_TYPE_VISIBILITY, OVERLAY_TYPE_DETECTION]: # if a group overlay is provided, get the software techniques for the overlay - groups_software_dict = _get_software_techniques(overlay, stage, platform) + groups_software_dict = _get_software_techniques(overlay, platform) elif software_groups: - groups_software_dict = _get_software_techniques(groups, stage, platform) + groups_software_dict = _get_software_techniques(groups, platform) technique_count, max_count = _get_technique_count(groups_dict, overlay_dict, groups_software_dict, overlay_type, all_techniques) technique_layer = _get_technique_layer(technique_count, groups_dict, overlay_dict, groups_software_dict, @@ -597,26 +594,24 @@ def generate_group_heat_map(groups, overlay, overlay_type, stage, platform, soft groups_list = _get_group_list(groups_dict, groups_file_type) overlay_list = _get_group_list(overlay_dict, overlay_file_type) - desc = 'stage: ' + stage + ' | platform(s): ' + platform_to_name(platform, separator=', ') + ' | group(s): ' \ + desc = 'stage: attack | platform(s): ' + platform_to_name(platform, separator=', ') + ' | group(s): ' \ + ', '.join(groups_list) + ' | overlay group(s): ' + ', '.join(overlay_list) if not layer_name: - layer_name = stage[0].upper() + stage[1:] + ' - ' + platform_to_name(platform, separator=', ') + layer_name = 'Attack - ' + platform_to_name(platform, separator=', ') - layer = get_layer_template_groups(layer_name, max_count, desc, stage, platform, overlay_type) + layer = get_layer_template_groups(layer_name, max_count, desc, platform, overlay_type) layer['techniques'] = technique_layer json_string = simplejson.dumps(layer).replace('}, ', '},\n') if not output_filename: - if stage == 'pre-attack': - filename = '_'.join(groups_list) - elif overlay: + if overlay: filename = platform_to_name(platform) + '_' + '_'.join(groups_list) + '-overlay_' + '_'.join(overlay_list) else: filename = platform_to_name(platform) + '_' + '_'.join(groups_list) - filename = create_output_filename(stage, filename) + filename = create_output_filename('attack', filename) write_file(filename, json_string) else: write_file(output_filename, json_string) diff --git a/interactive_menu.py b/interactive_menu.py index b30ac12..0515d1f 100644 --- a/interactive_menu.py +++ b/interactive_menu.py @@ -7,7 +7,6 @@ from eql_yaml import * groups = 'all' software_group = False default_platform = ['Windows'] -default_stage = 'attack' default_matrix = 'enterprise' groups_overlay = '' overlay_type = 'group' @@ -460,7 +459,7 @@ def _menu_groups(): Prints and handles the Threat actor group mapping functionality. :return: """ - global groups, software_group, default_platform, default_stage, groups_overlay, overlay_type, eql_all_scores, \ + global groups, software_group, default_platform, groups_overlay, overlay_type, eql_all_scores, \ eql_query_detection, eql_query_visibility _clear() print('Menu: %s' % MENU_NAME_THREAT_ACTOR_GROUP_MAPPING) @@ -468,12 +467,11 @@ def _menu_groups(): print('Options:') print('1. Software group: %s' % str(software_group)) print('2. Platform: %s' % ','.join(default_platform)) - print('3. Stage: %s' % default_stage) - print('4. Groups: %s' % groups) - print('5. Overlay: ') + print('3. Groups: %s' % groups) + print('4. Overlay: ') print(' - %s: %s' % ('File' if os.path.exists(groups_overlay) else 'Groups', groups_overlay)) print(' - Type: %s' % overlay_type) - print('6. EQL search: ') + print('5. EQL search: ') eql_d_str = '' if not eql_query_detection else eql_query_detection eql_v_str = '' if not eql_query_visibility else eql_query_visibility print(' - Only include detection objects which match the EQL query: ' + eql_d_str) @@ -481,7 +479,7 @@ def _menu_groups(): print(' - Include all \'score\' objects from the \'score_logbook\' in the EQL search: ' + str(eql_all_scores)) print('') print('Select what you want to do:') - print('7. Generate a heat map layer.') + print('6. Generate a heat map layer.') print('9. Back to main menu.') choice = _ask_input() if choice == '1': @@ -491,15 +489,11 @@ def _menu_groups(): p = _ask_input().lower() default_platform = [PLATFORMS[p]] if p in PLATFORMS.keys() else ['all'] elif choice == '3': - print('Specify stage (pre-attack, attack):') - s = _ask_input().lower() - default_stage = 'pre-attack' if s == 'pre-attack' else 'attack' - elif choice == '4': print('Specify the groups to include separated using commas. Group can be their ID, name or alias ' '(default is all groups). Other option is to provide a YAML file with a custom group(s)') g = _ask_input() groups = g if g != '' else 'all' - elif choice == '5': + elif choice == '4': print('') print('1. Overlay with groups.') print('2. Overlay with detections.') @@ -521,7 +515,7 @@ def _menu_groups(): elif choice == '4': overlay_type = '' groups_overlay = '' - elif choice == '6': + elif choice == '5': print('') print('1. Only include detection objects which match the EQL query: ' + eql_d_str) print('2. Only include visibility objects which match the EQL query: ' + eql_v_str) @@ -537,8 +531,8 @@ def _menu_groups(): elif choice == '3': eql_all_scores = not eql_all_scores - elif choice == '7': - generate_group_heat_map(groups, groups_overlay, overlay_type, default_stage, default_platform, + elif choice == '6': + generate_group_heat_map(groups, groups_overlay, overlay_type, default_platform, software_group, eql_query_visibility, eql_query_detection, False, None, None, include_all_score_objs=eql_all_scores) _wait() diff --git a/technique_mapping.py b/technique_mapping.py index 0ad220c..87140f7 100644 --- a/technique_mapping.py +++ b/technique_mapping.py @@ -23,14 +23,14 @@ def generate_detection_layer(filename_techniques, filename_data_sources, overlay mapped_techniques_detection = _map_and_colorize_techniques_for_detections(my_techniques) if not layer_name: layer_name = 'Detections ' + name - layer_detection = get_layer_template_detections(layer_name, 'description', 'attack', platform) + layer_detection = get_layer_template_detections(layer_name, 'description', platform) _write_layer(layer_detection, mapped_techniques_detection, 'detection', name, output_filename) else: my_data_sources = _load_data_sources(filename_data_sources) mapped_techniques_both = _map_and_colorize_techniques_for_overlaid(my_techniques, my_data_sources, platform) if not layer_name: layer_name = 'Visibility and Detection ' + name - layer_both = get_layer_template_layered(layer_name, 'description', 'attack', platform) + layer_both = get_layer_template_layered(layer_name, 'description', platform) _write_layer(layer_both, mapped_techniques_both, 'visibility_and_detection', name, output_filename) @@ -53,13 +53,13 @@ def generate_visibility_layer(filename_techniques, filename_data_sources, overla mapped_techniques_visibility = _map_and_colorize_techniques_for_visibility(my_techniques, my_data_sources, platform) if not layer_name: layer_name = 'Visibility ' + name - layer_visibility = get_layer_template_visibility(layer_name, 'description', 'attack', platform) + layer_visibility = get_layer_template_visibility(layer_name, 'description', platform) _write_layer(layer_visibility, mapped_techniques_visibility, 'visibility', name, output_filename) else: mapped_techniques_both = _map_and_colorize_techniques_for_overlaid(my_techniques, my_data_sources, platform) if not layer_name: layer_name = 'Visibility and Detection ' + name - layer_both = get_layer_template_layered(layer_name, 'description', 'attack', platform) + layer_both = get_layer_template_layered(layer_name, 'description', platform) _write_layer(layer_both, mapped_techniques_both, 'visibility_and_detection', name, output_filename)