Removed support for PRE-ATT&CK from the Group mode

master
Marcus Bakker 2020-10-31 21:01:09 +01:00
parent b3705c782e
commit 94e8b5e4b5
6 changed files with 52 additions and 71 deletions

View File

@ -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')

View File

@ -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)

View File

@ -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},

View File

@ -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)

View File

@ -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()

View File

@ -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)