def __init__(self, source, domain='enterprise', resource=None):
     """
     Initialize the Generator
     :param source: Which source to use for data (local, taxii [server], or [remote] ATT&CK Workbench)
     :param domain: Which matrix to use during generation
     :param resource: string path to local STIX data (local) or url of workbench to reach out to (remote)
     """
     self.matrix_handle = MatrixGen(source, resource)
     self.domain = domain
     try:
         self.source_handle = self.matrix_handle.collections[domain]
     except KeyError:
         print(f"[UsageGenerator] - unable to load collection {domain} (current source = {source}).")
         raise BadInput
     self.full_matrix = self.matrix_handle.get_matrix(self.domain)
     self.sources = self.source_handle.query([Filter('type', '=', 'x-mitre-data-source')])
     self.components = self.source_handle.query([Filter('type', '=', 'x-mitre-data-component')])
     self.source_mapping = build_data_strings(self.sources, self.components)
Пример #2
0
    def __init__(self, source='taxii', resource=None, domain='enterprise'):
        """
            Initialization - Creates a ExcelTemplate object

            :param source: Source to use when compiling the matrix
            :param resource: string path to local cache of stix data (local) or url of workbench to reach out
                                to (remote)
            :param domain: The domain to utilize
        """
        muse = domain
        if muse.startswith('mitre-'):
            muse = domain[6:]
        if muse.endswith('-attack'):
            muse = domain[:-7]
        if muse in ['enterprise', 'mobile']:
            self.mode = muse
            self.h = MatrixGen(source=source, resource=resource)
        else:
            raise BadTemplateException
    def __init__(self, source='taxii', domain='enterprise', resource=None):
        """
            Initialization - Creates a SvgTemplate object

            :param domain: Which domain to utilize for the underlying matrix layout
            :param source: Use the taxii server or local data
            :param resource: string path to local cache of stix data (local) or url of workbench to reach out
                                to (remote)
        """
        muse = domain
        if muse.startswith('mitre-'):
            muse = domain[6:]
        if muse.endswith('-attack'):
            muse = domain[:-7]
        if muse in ['enterprise', 'mobile']:
            self.mode = muse
            self.h = MatrixGen(source=source, resource=resource)
            self.lhandle = None
        else:
            raise BadTemplateException
Пример #4
0
class ExcelTemplates:
    def __init__(self, source='taxii', resource=None, domain='enterprise'):
        """
            Initialization - Creates a ExcelTemplate object

            :param source: Source to use when compiling the matrix
            :param resource: string path to local cache of stix data (local) or url of workbench to reach out
                                to (remote)
            :param domain: The domain to utilize
        """
        muse = domain
        if muse.startswith('mitre-'):
            muse = domain[6:]
        if muse.endswith('-attack'):
            muse = domain[:-7]
        if muse in ['enterprise', 'mobile']:
            self.mode = muse
            self.h = MatrixGen(source=source, resource=resource)
        else:
            raise BadTemplateException

    def _build_raw(self,
                   showName=True,
                   showID=False,
                   sort=0,
                   scores=[],
                   subtechs=[],
                   exclude=[]):
        """
            INTERNAL - builds a raw, not-yet-marked-up excel document based on the specifications

            :param showName: Whether or not to display names for each entry
            :param showID: Whether or not to display Technique IDs for each entry
            :param sort: The sort mode to use
            :param subtechs: List of all visible subtechniques
            :param exclude: List of of techniques to exclude from the matrix
            :return: a openpyxl workbook object containing the raw matrix
        """
        self.codex = self.h._adjust_ordering(self.codex, sort, scores)
        template, joins = self.h._construct_panop(self.codex, subtechs,
                                                  exclude)
        self.template = template
        wb = openpyxl.Workbook()

        sheet = wb.active

        header_template_f = Font(name='Calibri', bold=True)
        header_template_a = Alignment(horizontal='center', vertical='bottom')
        header_template_b = Border(bottom=Side(border_style='thin'))
        header_template_c = PatternFill(patternType='solid',
                                        start_color='DDDDDD',
                                        end_color='DDDDDD')

        for entry in template:
            c = sheet.cell(row=entry[0], column=entry[1])
            write_val = ''
            if showName and showID:
                write_val = self.h._get_ID(
                    self.codex, template[entry]) + ': ' + template[entry]
            elif showName:
                write_val = template[entry]
            elif showID:
                write_val = self.h._get_ID(self.codex, template[entry])
            c.value = write_val
            if entry[0] == 1:
                c.font = header_template_f
                c.alignment = header_template_a
                c.border = header_template_b
                c.fill = header_template_c

        # patch widths
        dims = {}
        sheet_handle = wb.active
        for row in sheet_handle:
            for cell in row:
                if cell.value:
                    dims[cell.column_letter] = max(
                        (dims.get(cell.column_letter,
                                  0), len(str(cell.value))))
        for col, value in dims.items():
            sheet_handle.column_dimensions[col].width = value

        merge_border_thickness = 'thin'
        merge_template_l = Border(
            bottom=Side(border_style=merge_border_thickness),
            left=Side(border_style=merge_border_thickness),
            top=Side(border_style=merge_border_thickness))
        merge_template_r = Border(
            bottom=Side(border_style=merge_border_thickness),
            right=Side(border_style=merge_border_thickness),
            top=Side(border_style=merge_border_thickness))

        for marker in joins:
            sheet_handle.merge_cells(start_row=marker[0],
                                     start_column=marker[1],
                                     end_row=marker[0] + marker[2] - 1,
                                     end_column=marker[1])
            for block in range(marker[0], marker[0] + marker[2]):
                sheet_handle[block][marker[1]].border = merge_template_r
                sheet_handle[block][marker[1] - 1].border = merge_template_l
            sheet_handle.merge_cells(start_row=1,
                                     start_column=marker[1],
                                     end_row=1,
                                     end_column=marker[1] + 1)
            adjust = sheet_handle.cell(row=marker[0], column=marker[1])
            adjust.alignment = Alignment(vertical='top')

        return wb

    def export(self,
               showName,
               showID,
               filters=None,
               sort=0,
               scores=[],
               subtechs=[],
               exclude=[]):
        """
            Export a raw customized excel template


            :param showName: Whether or not to display names for each entry
            :param showID: Whether or not to display Technique IDs for each entry
            :param filters: Filter/Platform object to use to filter the matrix
            :param sort: The sort mode to utilize
            :param scores: List of scores to utilize (extracted previously from layer)
            :param subtechs: List of all visible subtechniques
            :param exclude: List of of techniques to exclude from the matrix
            return: a openpyxl workbook object containing the raw matrix
        """
        self.codex = self.h.get_matrix(self.mode, filters=filters)
        return self._build_raw(showName, showID, sort, scores, subtechs,
                               exclude)

    def retrieve_coords(self, techniqueID, tactic=None):
        """
            Locate the openpyxl coordinates of the provided technique for the currently loaded matrix

            :param techniqueID: The ID of the technique to locate
            :param tactic: Optional parameter to isolate the technique to a specific tactic
            :return: A tuple representing the (row, column) of the target element in the workbook
        """
        listing = []
        match = self.h._get_name(self.codex, techniqueID)
        for entry in self.template:
            if self.template[entry] == match:
                if tactic is not None:
                    try:
                        if self.template[(1,
                                          entry[1])] != self.h.convert(tactic):
                            continue
                    except KeyError:
                        # account for subtechniques when scanning
                        if self.template[(1, entry[1] -
                                          1)] != self.h.convert(tactic):
                            continue
                listing.append(entry)
        if listing:
            if '.' in techniqueID:
                parent = self.retrieve_coords(
                    techniqueID.split('.')[0], tactic)
                if not parent:
                    return 'HIDDEN'
        return listing
class UsageLayerGenerator:
    """Generates a Layer that shows techniques mapped to an input group, software or mitigation"""
    def __init__(self, source, domain='enterprise', resource=None):
        """
        Initialize the Generator
        :param source: Which source to use for data (local, taxii [server], or [remote] ATT&CK Workbench)
        :param domain: Which matrix to use during generation
        :param resource: string path to local STIX data (local) or url of workbench to reach out to (remote)
        """
        self.matrix_handle = MatrixGen(source, resource)
        self.domain = domain
        try:
            self.source_handle = self.matrix_handle.collections[domain]
        except KeyError:
            print(f"[UsageGenerator] - unable to load collection {domain} (current source = {source}).")
            raise BadInput
        self.full_matrix = self.matrix_handle.get_matrix(self.domain)
        self.sources = self.source_handle.query([Filter('type', '=', 'x-mitre-data-source')])
        self.components = self.source_handle.query([Filter('type', '=', 'x-mitre-data-component')])
        self.source_mapping = build_data_strings(self.sources, self.components)

    def get_stix_object(self, match):
        """
        Retrieve the stix object for a given string
        :param match: The string to match on - can be a name, alias, or ATT&CK ID
        :return: the corresponding stix object
        """
        filts = [
            [Filter('name', '=', match)],
            [Filter(match, 'in', 'aliases')],
            [Filter(match, 'in', 'x_mitre_aliases')],
            [Filter('external_references.external_id', '=', match)],
            [Filter('id', '=', match)]  # Support data component type objects from sum generator
        ]
        data = list(chain.from_iterable(self.source_handle.query(f) for f in filts))
        data = remove_revoked_depreciated(data)
        if len(data):
            if len(data) > 1:
                print(f"[Usage Generator] - WARNING! Multiple matches found for {match}: [{data}]. Selecting the first "
                      f"one as default.")
            return data[0]
        raise UnableToFindStixObject

    def get_matrix_data(self, match_pattern):
        """
        Retrieve a list of attack-pattern (technique) objects that map to a group, software or mitigation.
        :param match_pattern: Name, associated group/software (alias), or ATT&CK ID.
                              Techniques mapped to the object matching this pattern are returned.```
        """
        obj = self.get_stix_object(match_pattern)
        if obj['type'] == 'course-of-action':
            verb = 'mitigates'
        elif obj['type'] == 'x-mitre-data-component' or obj['type'] == 'x-mitre-data-source':
            verb = 'detects'
        else:
            verb = 'uses'
        related = self.source_handle.relationships(obj['id'], verb, source_only=True)

        out = self.source_handle.query([
            Filter('type', '=', 'attack-pattern'),
            Filter('id', 'in', [r.target_ref for r in related])
        ])
        return remove_revoked_depreciated(out), obj

    def generate_technique_data(self, raw_matches):
        """
        Generate technique list of dictionary objects (dictionary form of technique listing for a layer)
        :param raw_matches: matching attack-pattern objects
        :return: list of dictionary objects for every technique: score=0 if not in raw_matches, 1 otherwise,
                    description in comments
        """
        shortlist = []
        for match in raw_matches:
            xid = ''
            xphase = ''
            for ref in match.external_references:
                if ref.source_name == 'mitre-attack':
                    xid = ref.external_id
            for phase in match.kill_chain_phases:
                if phase.kill_chain_name == 'mitre-attack':
                    xphase = phase.phase_name
            shortlist.append((xid, xphase, match.description))
        full_matrix_listing = copy.deepcopy(self.full_matrix)
        construct = list()
        for tactic in full_matrix_listing:
            for tech in tactic.techniques:
                construct.append(dict(techniqueID=tech.id, score=0,
                                      tactic=self.matrix_handle.convert(tactic.tactic.name)))
            for tech_key in tactic.subtechniques:
                for subtech in tactic.subtechniques[tech_key]:
                    construct.append(dict(techniqueID=subtech.id, score=0,
                                          tactic=self.matrix_handle.convert(tactic.tactic.name)))
        for entry in shortlist:
            for tac in construct:
                if entry[0] == tac['techniqueID'] and (entry[1] == '' or entry[1] == tac['tactic']):
                    tac['score'] = 1
                    tac['comment'] = entry[2]
        return construct

    def generate_layer(self, match):
        """
        Generate a layer
        :param match: the pattern to match
        :return: layer object with annotated techniques
        """
        typeChecker(type(self).__name__, match, str, "match")
        raw_data, matched_obj = self.get_matrix_data(match)
        if matched_obj['type'] not in ["course-of-action", 'tool', 'malware', 'intrusion-set',
                                       'x-mitre-data-source', 'x-mitre-data-component']:
            print(f"Warning: The input match {match} corresponds with an ATT&CK Technique, which is not supported. "
                  f"Please provide a group, software, or mitigation instead.")
            raise StixObjectIsNotValid
        a_id = get_attack_id(matched_obj)
        processed_listing = self.generate_technique_data(raw_data)
        raw_layer = dict(name=f"{matched_obj['name']} ({matched_obj['id']})", domain=self.domain + '-attack')
        raw_layer['techniques'] = processed_listing
        output_layer = Layer(raw_layer)
        if matched_obj['type'] != 'x-mitre-data-component':
            name = matched_obj['name']
        else:
            name = self.source_mapping[matched_obj['id']]
        output_layer.description = f"{self.domain.capitalize() if len(self.domain) > 3 else self.domain.upper()} " \
                                   f"techniques used by {name}, ATT&CK {matched_obj['type']} {a_id}"
        return output_layer
class SvgTemplates:
    def __init__(self, source='taxii', domain='enterprise', resource=None):
        """
            Initialization - Creates a SvgTemplate object

            :param domain: Which domain to utilize for the underlying matrix layout
            :param source: Use the taxii server or local data
            :param resource: string path to local cache of stix data (local) or url of workbench to reach out
                                to (remote)
        """
        muse = domain
        if muse.startswith('mitre-'):
            muse = domain[6:]
        if muse.endswith('-attack'):
            muse = domain[:-7]
        if muse in ['enterprise', 'mobile']:
            self.mode = muse
            self.h = MatrixGen(source=source, resource=resource)
            self.lhandle = None
        else:
            raise BadTemplateException

    def _build_headers(self,
                       name,
                       config,
                       domain='Enterprise',
                       version='8',
                       desc=None,
                       filters=None,
                       gradient=None):
        """
            Internal - build the header blocks for the svg

            :param name: The name of the layer being exported
            :param config: SVG Config object
            :param domain: The layer's domain
            :param version: The layer's version
            :param desc: Description of the layer being exported
            :param filters: Any filters applied to the layer being exported
            :param gradient: Gradient information included with the layer
            :return: Instantiated SVG header
        """
        max_x = convertToPx(config.width, config.unit)
        max_y = convertToPx(config.height, config.unit)
        header_height = convertToPx(config.headerHeight, config.unit)
        ff = config.font
        d = draw.Drawing(max_x, max_y, origin=(0, -max_y), displayInline=False)
        psych = 0
        overlay = None
        if config.showHeader:
            border = convertToPx(config.border, config.unit)
            root = G(tx=border, ty=border, style='font-family: {}'.format(ff))

            header = G()
            root.append(header)
            b1 = G()
            header.append(b1)

            header_count = 0
            showAgg = False
            if config.showAbout:
                header_count += 1
            if config.showFilters:
                header_count += 1
            if config.showDomain:
                header_count += 1
            if config.showLegend and gradient is not False and config.legendDocked:
                header_count += 1
            if self.lhandle.layout:
                if self.lhandle.layout.showAggregateScores:
                    showAgg = True
                    header_count += 1
                if config.showFilters:
                    header_count -= 1

            operation_x = (max_x - border) - (1.5 * border *
                                              (header_count - 1)) - border
            if header_count > 0:
                header_width = operation_x / header_count
                if config.showAbout:
                    if desc is not None:
                        g = SVG_HeaderBlock().build(height=header_height,
                                                    width=header_width,
                                                    label='about',
                                                    t1text=name,
                                                    t2text=desc,
                                                    config=config)
                    else:
                        g = SVG_HeaderBlock().build(height=header_height,
                                                    width=header_width,
                                                    label='about',
                                                    t1text=name,
                                                    config=config)
                    b1.append(g)
                    psych += 1
                if config.showDomain:
                    if domain.startswith('mitre-'):
                        domain = domain[6:].capitalize()
                    if domain.endswith('-attack'):
                        domain = domain[:-7].capitalize()
                    tag = domain + ' ATT&CK v' + version
                    if config.showFilters and showAgg:
                        fi = filters
                        if fi is None:
                            fi = Filter()
                            fi.platforms = ["Windows", "Linux", "macOS"]
                        gD = SVG_HeaderBlock().build(
                            height=header_height,
                            width=header_width,
                            label='domain & platforms',
                            t1text=tag,
                            t2text=', '.join(fi.platforms),
                            config=config)
                    else:
                        gD = SVG_HeaderBlock().build(height=header_height,
                                                     width=header_width,
                                                     label='domain',
                                                     t1text=tag,
                                                     config=config)
                    bD = G(tx=operation_x / header_count * psych +
                           1.5 * border * psych)
                    header.append(bD)
                    bD.append(gD)
                    psych += 1
                if config.showFilters and not showAgg:
                    fi = filters
                    if fi is None:
                        fi = Filter()
                        fi.platforms = ["Windows", "Linux", "macOS"]
                    g2 = SVG_HeaderBlock().build(height=header_height,
                                                 width=header_width,
                                                 label='filters',
                                                 t1text=', '.join(
                                                     fi.platforms),
                                                 config=config)
                    b2 = G(tx=operation_x / header_count * psych +
                           1.5 * border * psych)
                    header.append(b2)
                    b2.append(g2)
                    psych += 1
                if showAgg:
                    t1 = f"showing aggregate scores using the {self.lhandle.layout.aggregateFunction} " \
                         f"aggregate function"
                    stub = "does not include"
                    if self.lhandle.layout.countUnscored:
                        stub = "includes"
                    t2 = f"{stub} unscored techniques as having a score of 0"
                    gA = SVG_HeaderBlock().build(height=header_height,
                                                 width=header_width,
                                                 label='aggregate',
                                                 t1text=t1,
                                                 t2text=t2,
                                                 config=config)
                    bA = G(tx=operation_x / header_count * psych +
                           1.5 * border * psych)
                    header.append(bA)
                    bA.append(gA)
                    psych += 1
                if config.showLegend and gradient is not False:
                    gr = gradient
                    if gr is None:
                        gr = Gradient(colors=["#ff6666", "#ffe766", "#8ec843"],
                                      minValue=1,
                                      maxValue=100)
                    colors = []
                    div = round(
                        (gr.maxValue - gr.minValue) / (len(gr.colors) * 2 - 1))
                    for i in range(0, len(gr.colors) * 2 - 1):
                        colors.append(
                            (gr.compute_color(int(gr.minValue + div * i)),
                             gr.minValue + div * i))
                    colors.append((gr.compute_color(gr.maxValue), gr.maxValue))
                    if config.legendDocked:
                        b3 = G(tx=operation_x / header_count * psych +
                               1.5 * border * psych)
                        g3 = SVG_HeaderBlock().build(height=header_height,
                                                     width=header_width,
                                                     label='legend',
                                                     variant='graphic',
                                                     colors=colors,
                                                     config=config)
                        header.append(b3)
                        b3.append(g3)
                        psych += 1
                    else:
                        adjusted_height = convertToPx(config.legendHeight,
                                                      config.unit)
                        adjusted_width = convertToPx(config.legendWidth,
                                                     config.unit)
                        g3 = SVG_HeaderBlock().build(height=adjusted_height,
                                                     width=adjusted_width,
                                                     label='legend',
                                                     variant='graphic',
                                                     colors=colors,
                                                     config=config)
                        lx = convertToPx(config.legendX, config.unit)
                        if not lx:
                            lx = max_x - adjusted_width - convertToPx(
                                config.border, config.unit)
                        ly = convertToPx(config.legendY, config.unit)
                        if not ly:
                            ly = max_y - adjusted_height - convertToPx(
                                config.border, config.unit)
                        overlay = G(tx=lx, ty=ly)
                        if (ly + adjusted_height) > max_y or (
                                lx + adjusted_width) > max_x:
                            print(
                                "[WARNING] - Floating legend will render partly out of view..."
                            )
                        overlay.append(g3)
            d.append(root)
        return d, psych, overlay

    def get_tactic(self,
                   tactic,
                   height,
                   width,
                   config,
                   colors=[],
                   scores=[],
                   subtechs=[],
                   exclude=[],
                   mode=(True, False)):
        """
            Build a 'tactic column' svg object

            :param tactic: The corresponding tactic for this column
            :param height: A technique block's allocated height
            :param width: A technique blocks' allocated width
            :param config: A SVG Config object
            :param colors: Default color data in case of no score
            :param scores: Score values for the dataset
            :param subtechs: List of visible subtechniques
            :param exclude: List of excluded techniques
            :param mode: Tuple describing text for techniques (Show Name, Show ID)
            :return: Instantiated tactic column (or none if no techniques were found)
        """
        offset = 0
        column = G(ty=2)
        for a in tactic.subtechniques:
            self._copy_scores(tactic.subtechniques[a], scores,
                              tactic.tactic.name, exclude)
        for x in tactic.techniques:
            if any(x.id == y[0] and (
                    y[1] == self.h.convert(tactic.tactic.name) or not y[1])
                   for y in exclude):
                continue
            self._copy_scores([x], scores, tactic.tactic.name, exclude)
            if any(x.id == y[0] and (
                    y[1] == self.h.convert(tactic.tactic.name) or not y[1])
                   for y in subtechs):
                a, offset = self.get_tech(
                    offset,
                    mode,
                    x,
                    tactic=self.h.convert(tactic.tactic.name),
                    subtechniques=tactic.subtechniques.get(x.id, []),
                    colors=colors,
                    config=config,
                    height=height,
                    width=width,
                    subscores=tactic.subtechniques.get(x.id, []))
            else:
                a, offset = self.get_tech(
                    offset,
                    mode,
                    x,
                    tactic=self.h.convert(tactic.tactic.name),
                    subtechniques=[],
                    colors=colors,
                    config=config,
                    height=height,
                    width=width,
                    subscores=tactic.subtechniques.get(x.id, []))
            column.append(a)
        if len(column.children) == 0:
            return None
        return column

    def get_tech(self,
                 offset,
                 mode,
                 technique,
                 tactic,
                 config,
                 height,
                 width,
                 subtechniques=[],
                 colors=[],
                 subscores=[]):
        """
            Retrieve a svg object for a single technique

            :param offset: The offset in the column based on previous work
            :param mode: Tuple describing display format (Show Name, Show ID)
            :param technique: The technique to build a block for
            :param tactic: The corresponding tactic
            :param config: An SVG Config object
            :param height: The allocated height of a technique block
            :param width: The allocated width of a technique block
            :param subtechniques: A list of all visible subtechniques, some of which may apply to this one
            :param colors: A list of all color overrides in the event of no score, which may apply
            :param subscores: List of all subtechniques for the (visible or not) [includes scores]
            :return: Tuple (SVG block, new offset)
        """
        # Handle aggregate scoring (v4.2)
        if self.lhandle.layout:
            mod = self.lhandle.layout.compute_aggregate(technique, subscores)
            if mod is not None:
                technique.aggregateScore = mod
        a, b = SVG_Technique(self.lhandle.gradient).build(
            offset,
            technique,
            height,
            width,
            subtechniques=subtechniques,
            mode=mode,
            tactic=tactic,
            colors=colors,
            tBC=config.tableBorderColor)
        return a, b

    def export(self,
               showName,
               showID,
               lhandle,
               config,
               sort=0,
               scores=[],
               colors=[],
               subtechs=[],
               exclude=[]):
        """
            Export a layer object to an SVG object

            :param showName: Boolean of whether or not to show names
            :param showID:  Boolean of whether or not to show IDs
            :param lhandle: The layer object being exported
            :param config: A SVG Config object
            :param sort: The sort mode
            :param scores: List of tactic scores
            :param colors: List of tactic default colors
            :param subtechs: List of visible subtechniques
            :param exclude: List of excluded techniques
            :return:
        """
        self.codex = self.h.get_matrix(self.mode, filters=lhandle.filters)
        grad = False
        if len(scores):
            grad = lhandle.gradient
        self.lhandle = lhandle
        d, presence, overlay = self._build_headers(lhandle.name, config,
                                                   lhandle.domain,
                                                   lhandle.versions.attack,
                                                   lhandle.description,
                                                   lhandle.filters, grad)
        self.codex = self.h._adjust_ordering(self.codex, sort, scores)
        index = 0
        lengths = []
        border = convertToPx(config.border, config.unit)
        glob = G(tx=border)
        for x in self.codex:
            su = len(x.techniques)
            for enum in exclude:
                if enum[0] in [y.id for y in x.techniques]:
                    if self.h.convert(
                            enum[1]) == x.tactic.name or enum[1] is False:
                        su -= 1
            for y in x.subtechniques:
                if y in [z[0] for z in subtechs]:
                    su += len(x.subtechniques[y])
            lengths.append(su)
        tech_width = ((convertToPx(config.width, config.unit) - 2.2 * border) / sum([1 for x in lengths if x > 0])) - \
            border
        header_offset = convertToPx(config.headerHeight, config.unit)
        if presence == 0:
            header_offset = 0
        header_offset += 2.5 * border
        tech_height = (
            convertToPx(config.height, config.unit) - header_offset -
            convertToPx(config.border, config.unit)) / (max(lengths) + 1)
        incre = tech_width + 1.1 * border
        for x in self.codex:
            disp = ''
            if showName and showID:
                disp = x.tactic.id + ": " + x.tactic.name
            elif showName:
                disp = x.tactic.name
            elif showID:
                disp = x.tactic.id

            g = G(tx=index, ty=header_offset)

            fs, _ = _optimalFontSize(disp,
                                     tech_width,
                                     tech_height,
                                     maxFontSize=28)
            tx = Text(ctype='TacticName',
                      font_size=fs,
                      text=disp,
                      position='middle')
            gt = G(tx=tech_width / 2, ty=tech_height / 2)
            gt.append(tx)
            a = self.get_tactic(x,
                                tech_height,
                                tech_width,
                                colors=colors,
                                subtechs=subtechs,
                                exclude=exclude,
                                mode=(showName, showID),
                                scores=scores,
                                config=config)
            b = G(ty=tech_height)
            g.append(gt)
            b.append(a)
            g.append(b)
            if a:
                glob.append(g)
                index += incre
        d.append(glob)
        if overlay:
            d.append(overlay)
        return d

    def _copy_scores(self, listing, scores, tactic_name, exclude):
        """
            INTERNAL: Move scores over from the input object (scores) to the one used to build the svg (listing)

            :param listing: List of objects to apply scores to
            :param scores: List of scores for this tactic
            :param exclude: List of excluded techniques
            :return: None - operates on the raw object itself
        """
        for b in listing:
            if b in exclude:
                b.score = None
                continue
            found = False
            for y in scores:
                if b.id == y[0] and (y[1] == self.h.convert(tactic_name)
                                     or not y[1]):
                    b.score = y[2]
                    found = True
                    continue
            if not found:
                b.score = None
Пример #7
0
    def __init__(self, source, domain='enterprise', resource=None):
        """
        Initialize the Generator
        :param source: Which source to use for data (local, taxii [server], or [remote] ATT&CK Workbench)
        :param domain: Which domain to use during generation
        :param resource: string path to local cache of stix data (local) or url of Workbench to connect to (remote)
        """
        self.matrix_handle = MatrixGen(source, resource)
        self.domain = domain
        try:
            self.source_handle = self.matrix_handle.collections[domain]
        except KeyError:
            print(
                f"[UsageGenerator] - unable to load collection {domain} (current source = {source})."
            )
            raise BadInput
        tl = remove_revoked_depreciated(
            self.source_handle.query([Filter('type', '=', 'attack-pattern')]))
        self.mitigation_objects = self.source_handle.query(
            [Filter('type', '=', 'course-of-action')])
        complete_relationships = self.source_handle.query([
            Filter('type', '=', 'relationship'),
            Filter('relationship_type', '=', 'uses')
        ])
        complete_relationships.extend(
            self.source_handle.query([
                Filter('type', '=', 'relationship'),
                Filter('relationship_type', '=', 'mitigates')
            ]))
        complete_relationships.extend(
            self.source_handle.query([
                Filter('type', '=', 'relationship'),
                Filter('relationship_type', '=', 'detects')
            ]))

        self.sources = self.source_handle.query(
            [Filter('type', '=', 'x-mitre-data-source')])
        self.components = self.source_handle.query(
            [Filter('type', '=', 'x-mitre-data-component')])
        self.source_mapping = build_data_strings(self.sources, self.components)
        # Contains relationship mapping [stix id] -> [relationships associated with that stix id for each type]
        self.simplifier = {
            "course-of-action": dict(),
            "tool": dict(),
            "malware": dict(),
            "intrusion-set": dict(),
            "x-mitre-data-component": dict()
        }

        # Scan through all relationships to identify ones that target attack techniques (attack-pattern). Then, sort
        # these into the mapping dictionary by what kind of stix object they are (tool, course-of-action, etc.)
        for entry in complete_relationships:
            if entry['target_ref'].startswith('attack-pattern--'):
                construct_relationship_mapping(
                    self.simplifier[entry['source_ref'].split('--')[0]], entry)
        self.simplifier['software'] = self.simplifier['malware']
        self.simplifier['software'].update(
            self.simplifier['tool'])  # get combination of malware/tool
        self.simplifier['datasource'] = self.simplifier[
            'x-mitre-data-component']

        self.tech_listing = dict()
        self.tech_no_tactic_listing = dict()
        for entry in tl:
            tacs = [None]
            xid = None
            for ref in entry.external_references:
                if ref.source_name in MITRE_ATTACK_DOMAIN_STRINGS:
                    xid = ref.external_id
                    break
            for phase in entry.kill_chain_phases:
                if phase.kill_chain_name in MITRE_ATTACK_DOMAIN_STRINGS:
                    tacs.append(phase.phase_name)
            for xphase in tacs:
                self.tech_listing[(xid, xphase)] = entry
            self.tech_no_tactic_listing[xid] = entry
Пример #8
0
class OverviewLayerGenerator:
    """Generates a Layer file that provides an overview of entities related to each technique"""
    def __init__(self, source, domain='enterprise', resource=None):
        """
        Initialize the Generator
        :param source: Which source to use for data (local, taxii [server], or [remote] ATT&CK Workbench)
        :param domain: Which domain to use during generation
        :param resource: string path to local cache of stix data (local) or url of Workbench to connect to (remote)
        """
        self.matrix_handle = MatrixGen(source, resource)
        self.domain = domain
        try:
            self.source_handle = self.matrix_handle.collections[domain]
        except KeyError:
            print(
                f"[UsageGenerator] - unable to load collection {domain} (current source = {source})."
            )
            raise BadInput
        tl = remove_revoked_depreciated(
            self.source_handle.query([Filter('type', '=', 'attack-pattern')]))
        self.mitigation_objects = self.source_handle.query(
            [Filter('type', '=', 'course-of-action')])
        complete_relationships = self.source_handle.query([
            Filter('type', '=', 'relationship'),
            Filter('relationship_type', '=', 'uses')
        ])
        complete_relationships.extend(
            self.source_handle.query([
                Filter('type', '=', 'relationship'),
                Filter('relationship_type', '=', 'mitigates')
            ]))
        complete_relationships.extend(
            self.source_handle.query([
                Filter('type', '=', 'relationship'),
                Filter('relationship_type', '=', 'detects')
            ]))

        self.sources = self.source_handle.query(
            [Filter('type', '=', 'x-mitre-data-source')])
        self.components = self.source_handle.query(
            [Filter('type', '=', 'x-mitre-data-component')])
        self.source_mapping = build_data_strings(self.sources, self.components)
        # Contains relationship mapping [stix id] -> [relationships associated with that stix id for each type]
        self.simplifier = {
            "course-of-action": dict(),
            "tool": dict(),
            "malware": dict(),
            "intrusion-set": dict(),
            "x-mitre-data-component": dict()
        }

        # Scan through all relationships to identify ones that target attack techniques (attack-pattern). Then, sort
        # these into the mapping dictionary by what kind of stix object they are (tool, course-of-action, etc.)
        for entry in complete_relationships:
            if entry['target_ref'].startswith('attack-pattern--'):
                construct_relationship_mapping(
                    self.simplifier[entry['source_ref'].split('--')[0]], entry)
        self.simplifier['software'] = self.simplifier['malware']
        self.simplifier['software'].update(
            self.simplifier['tool'])  # get combination of malware/tool
        self.simplifier['datasource'] = self.simplifier[
            'x-mitre-data-component']

        self.tech_listing = dict()
        self.tech_no_tactic_listing = dict()
        for entry in tl:
            tacs = [None]
            xid = None
            for ref in entry.external_references:
                if ref.source_name in MITRE_ATTACK_DOMAIN_STRINGS:
                    xid = ref.external_id
                    break
            for phase in entry.kill_chain_phases:
                if phase.kill_chain_name in MITRE_ATTACK_DOMAIN_STRINGS:
                    tacs.append(phase.phase_name)
            for xphase in tacs:
                self.tech_listing[(xid, xphase)] = entry
            self.tech_no_tactic_listing[xid] = entry

    def get_groups(self, relationships):
        """
        Sort Groups out of relationships
        :param relationships: List of all related relationships to a given technique
        :return: length of matched groups, list of group names
        """
        list_of_groups = []
        for relationship in relationships:
            if relationship.source_ref.startswith('intrusion-set--'):
                list_of_groups.append(relationship)
        group_objects = self.source_handle.query([
            Filter('type', '=', 'intrusion-set'),
            Filter('id', 'in', [r.source_ref for r in list_of_groups])
        ])
        names = [x.name for x in group_objects]
        return len(names), names

    def get_software(self, relationships):
        """
        Sort software out of relationships
        :param relationships: List of all related relationships to a given technique
        :return: length of matched software, list of software names
        """
        list_of_softwares = []
        for relationship in relationships:
            if relationship.source_ref.startswith(
                    'malware--') or relationship.source_ref.startswith(
                        'tool--'):
                list_of_softwares.append(relationship.source_ref)
        software_listing = self.source_handle.query(
            [Filter('type', '=', 'malware'),
             Filter('type', '=', 'tool')])
        software_objects = []
        for soft in software_listing:
            if soft.id in list_of_softwares:
                software_objects.append(soft)
        names = [x.name for x in software_objects]
        return len(names), names

    def get_mitigations(self, relationships):
        """
        Sort mitigations out of relationships
        :param relationships: List of all related relationships to a given technique
        :return: length of matched mitigations, list of mitigation names
        """
        names = [
            x.name for x in self.mitigation_objects if x.id in relationships
        ]
        return len(names), names

    def get_datasources(self, relationships):
        """
        Sort datasources/datacomponents out of relationships
        :param relationships: List of all related relationships to a given technique
        :return: length of matched datasources/datacomponents, list of associated names
        """
        names = [self.source_mapping[x.source_ref] for x in relationships]
        return len(names), names

    def get_matrix_template(self):
        """
        Build the raw dictionary form matrix layer object
        :return: dictionary representing all entries in the matrix layer
        """
        construct = list()
        full_matrix_listing = self.matrix_handle.get_matrix(self.domain)
        for tactic in full_matrix_listing:
            for tech in tactic.techniques:
                construct.append(
                    dict(techniqueID=tech.id,
                         score=0,
                         tactic=self.matrix_handle.convert(
                             tactic.tactic.name)))
            for tech_key in tactic.subtechniques:
                for subtech in tactic.subtechniques[tech_key]:
                    construct.append(
                        dict(techniqueID=subtech.id,
                             score=0,
                             tactic=self.matrix_handle.convert(
                                 tactic.tactic.name)))
        return construct

    def get_technique_obj(self, techniqueID, tactic):
        """
        Extract the matching technique object from the tech_listing
        :param techniqueID: the technique object's id
        :param tactic: optional tactic for the technique object (shortname - ex. "reconnaissance")
        :return: the matching technique object (or a UnableToFindTechnique exception)
        """
        try:
            return self.tech_listing[(techniqueID, tactic)]
        except KeyError:
            pass  # didn't find a specific match for that combo, let's drop the tactic and see what we get
        try:
            return self.tech_no_tactic_listing[techniqueID]
        except KeyError:
            raise UnableToFindTechnique

    def update_template(self, obj_type, complete_tech_listing):
        """
        Update an existing dictionary of layer techniques with the appropriate matching objects
        :param obj_type: the type of object to update the data with
        :param complete_tech_listing: 'clean' technique dictionary template
        :return: Updated technique dictionary
        """
        temp = complete_tech_listing
        for entry in temp:
            tech = self.get_technique_obj(entry['techniqueID'],
                                          entry['tactic'])
            score = 0
            listing = []
            if obj_type == 'group':
                try:
                    related = self.simplifier['intrusion-set'][tech.id]
                    score, listing = self.get_groups(related)
                except KeyError:
                    pass
            elif obj_type == 'software':
                try:
                    related = self.simplifier['software'][tech.id]
                    score, listing = self.get_software(related)
                except KeyError:
                    pass
            elif obj_type == "mitigation":
                try:
                    related = self.simplifier['course-of-action'][tech.id]
                    score, listing = self.get_mitigations(related)
                except KeyError:
                    pass  # we don't have any matches for this one
            elif obj_type == "datasource":
                try:
                    related = self.simplifier['datasource'][tech.id]
                    score, listing = self.get_datasources(related)
                except KeyError:
                    pass
            entry['score'] = score
            entry['comment'] = ', '.join(listing)
        return temp

    def generate_layer(self, obj_type):
        """
        Generate a layer
        :param obj_type: the type of object data to compute over (group, software, or mitigation)
        :return: layer object with annotated techniques
        """
        typeChecker(type(self).__name__, obj_type, str, "type")
        categoryChecker(
            type(self).__name__, obj_type,
            ["group", "software", "mitigation", "datasource"], "type")
        initial_list = self.get_matrix_template()
        updated_list = self.update_template(obj_type, initial_list)
        if obj_type == "group":
            p_name = "groups"
            r_type = "using"
        elif obj_type == "software":
            p_name = "software"
            r_type = "using"
        elif obj_type == "datasource":
            p_name = "data sources"
            r_type = "detecting"
        else:  # mitigation case
            p_name = "mitigations"
            r_type = "mitigating"
        desc = f"Overview of techniques used by {p_name}. Score is the number of {p_name} " \
               f"{r_type} the technique, and comment lists the {r_type} {p_name}"
        raw_layer = dict(name=f"{p_name} overview",
                         domain='enterprise-attack',
                         description=desc)
        raw_layer['techniques'] = updated_list
        output_layer = Layer(raw_layer)
        return output_layer