|
| 1 | +# layers |
| 2 | + |
| 3 | +This folder contains modules and scripts for working with ATT&CK Navigator layers. ATT&CK Navigator Layers are a set of annotations overlayed on top of the ATT&CK Matrix. For more about ATT&CK Navigator layers, visit the ATT&CK Navigator repository. The core module allows users to load, validate, manipulate, and save ATT&CK layers. A brief overview of the components can be found below. All scripts adhere to the MITRE ATT&CK Navigator Layer file format, [version 3.0](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md). |
| 4 | + |
| 5 | +#### Core Modules |
| 6 | +| script | description | |
| 7 | +|:-------|:------------| |
| 8 | +| [filter](core/filter.py) | Implements a basic [filter object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#filter-object-properties). | |
| 9 | +| [gradient](core/gradient.py) | Implements a basic [gradient object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#gradient-object-properties). | |
| 10 | +| [layer](core/layer.py) | Provides an interface for interacting with core module's layer representation. A further breakdown can be found in the corresponding section below. | |
| 11 | +| [layout](core/layout.py) | Implements a basic [layout object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#layout-object-properties). | |
| 12 | +| [legenditem](core/legenditem.py) | Implements a basic [legenditem object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#legenditem-object-properties). | |
| 13 | +| [metadata](core/metadata.py) | Implements a basic [metadata object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#metadata-object-properties). | |
| 14 | +| [technique](core/technique.py) | Implements a basic [technique object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#technique-object-properties). | |
| 15 | + |
| 16 | +#### Manipulator Scripts |
| 17 | +| script | description | |
| 18 | +|:-------|:------------| |
| 19 | +| [layerops](manipulators/layerops.py) | Provides a means by which to combine multiple ATT&CK layer objects in customized ways. A further breakdown can be found in the corresponding section below. | |
| 20 | + |
| 21 | +## Layer |
| 22 | +The Layer class provides format validation and read/write capabilities to aid in working with ATT&CK Navigator Layers in python. It is the primary interface through which other Layer-related classes defined in the core module should be used. The Layer class API and a usage example are below. |
| 23 | + |
| 24 | +| method [x = Layer()]| description | |
| 25 | +|:-------|:------------| |
| 26 | +| x.from_str(_input_) | Loads an ATT&CK layer from a string representation of a json layer. | |
| 27 | +| x.from_dict(_input_) | Loads an ATT&CK layer from a dictionary. | |
| 28 | +| x.from_file(_filepath_) | Loads an ATT&CK layer from a file location specified by the _filepath_. | |
| 29 | +| x.to_file(_filepath_) | Saves the current state of the loaded ATT&CK layer to a json file denoted by the _filepath_. | |
| 30 | +| x.to_dict() | Returns a representation of the current ATT&CK layer object as a dictionary. | |
| 31 | +| x.to_str() | Returns a representation of the current ATT&CK layer object as a string representation of a dictionary. | |
| 32 | + |
| 33 | +#### Example Usage |
| 34 | + |
| 35 | +```python |
| 36 | +example_layer_dict = { |
| 37 | + "name": "example layer", |
| 38 | + "version": "3.0", |
| 39 | + "domain": "mitre-enterprise" |
| 40 | +} |
| 41 | + |
| 42 | +example_layer_location = "/path/to/layer/file.json" |
| 43 | +example_layer_out_location = "/path/to/new/layer/file.json" |
| 44 | + |
| 45 | +from layers.core import Layer |
| 46 | + |
| 47 | +layer1 = Layer(example_layer_dict) # Create a new layer and load existing data |
| 48 | +layer1.to_file(example_layer_out_location) # Write out the loaded layer to the specified file |
| 49 | + |
| 50 | +layer2 = Layer() # Create a new layer object |
| 51 | +layer2.from_dict(example_layer_dict) # Load layer data into existing layer object |
| 52 | +print(layer2.to_dict()) # Retrieve the loaded layer's data as a dictionary, and print it |
| 53 | + |
| 54 | +layer3 = Layer() # Create a new layer object |
| 55 | +layer3.from_file(example_layer_location) # Load layer data from a file into existing layer object |
| 56 | +``` |
| 57 | + |
| 58 | +## layerops.py |
| 59 | +Layerops.py provides the LayerOps class, which is a way to combine layer files in an automated way, using user defined lambda functions. Each LayerOps instance, when created, ingests the provided lambda functions, and stores them for use. An existing LayerOps class can be used to combine layer files according to the initialized lambda using the process method. The breakdown of this two step process is documented in the table below, while examples of both the list and dictionary modes of operation can be found below. |
| 60 | + |
| 61 | +##### LayerOps() |
| 62 | +```python |
| 63 | + x = LayerOps(score=score, comment=comment, enabled=enabled, colors=colors, metadata=metadata, name=name, desc=desc, default_values=default_values) |
| 64 | +``` |
| 65 | + |
| 66 | + Each of the _inputs_ takes a lambda function that will be used to combine technique object fields matching the parameter. The one exception to this is _default_values_, which is an optional dictionary argument containing default values to provide the lambda functions if techniques of the combined layers are missing them. |
| 67 | + |
| 68 | +##### .process() Method |
| 69 | +```python |
| 70 | +x.process(data, default_values=default_values) |
| 71 | +``` |
| 72 | +The process method applies the lambda functions stored during initialization to the layer objects in _data_. _data_ must be either a list or a dictionary of Layer objects, and is expected to match the format of the lambda equations provided during initialization. default_values is an optional dictionary argument that overrides the currently stored default |
| 73 | + values with new ones for this specific processing operation. |
| 74 | + |
| 75 | +#### Example Usage |
| 76 | +```python |
| 77 | +from layers.manipulators.layerops import LayerOps |
| 78 | +from layers.core.layer import Layer |
| 79 | + |
| 80 | +demo = Layer() |
| 81 | +demo.from_file("C:\Users\attack\Downloads\layer.json") |
| 82 | +demo2 = Layer() |
| 83 | +demo2.from_file("C:\Users\attack\Downloads\layer2.json") |
| 84 | +demo3 = Layer() |
| 85 | +demo3.from_file("C:\Users\attack\Downloads\layer3.json") |
| 86 | + |
| 87 | +# Example 1) Build a LayerOps object that takes a list and averages scores across the layers |
| 88 | +lo = LayerOps(score=lambda x: sum(x) / len(x), |
| 89 | + name=lambda x: x[1], |
| 90 | + desc=lambda x: "This is an list example") # Build LayerOps object |
| 91 | +out_layer = lo.process([demo, demo2]) # Trigger processing on a list of demo and demo2 layers |
| 92 | +out_layer.to_file("C:\demo_layer1.json") # Save averaged layer to file |
| 93 | +out_layer2 = lo.process([demo, demo2, demo3]) # Trigger processing on a list of demo, demo2, demo3 |
| 94 | +visual_aid = out_layer2.to_dict() # Retrieve dictionary representation of processed layer |
| 95 | + |
| 96 | +# Example 2) Build a LayerOps object that takes a dictionary and averages scores across the layers |
| 97 | +lo2 = LayerOps(score=lambda x: sum([x[y] for y in x]) / len([x[y] for y in x]), |
| 98 | + color=lambda x: x['b'], |
| 99 | + desc=lambda x: "This is a dict example") # Build LayerOps object, with lambda |
| 100 | +out_layer3 = lo2.process({'a': demo, 'b': demo2}) # Trigger processing on a dictionary of demo and demo2 |
| 101 | +dict_layer = out_layer3.to_dict() # Retrieve dictionary representation of processed layer |
| 102 | +print(dict_layer) # Display retrieved dictionary |
| 103 | +out_layer4 = lo2.process({'a': demo, 'b': demo2, 'c': demo3})# Trigger processing on a dictionary of demo, demo2, demo3 |
| 104 | +out_layer4.to_file("C:\demo_layer4.json") # Save averaged layer to file |
| 105 | + |
| 106 | +# Example 3) Build a LayerOps object that takes a single element dictionary and inverts the score |
| 107 | +lo3 = LayerOps(score=lambda x: 100 - x['a'], |
| 108 | + desc= lambda x: "This is a simple example") # Build LayerOps object to invert score (0-100 scale) |
| 109 | +out_layer5 = lo3.process({'a': demo}) # Trigger processing on dictionary of demo |
| 110 | +print(out_layer5.to_dict()) # Display processed layer in dictionary form |
| 111 | +out_layer5.to_file("C:\demo_layer5.json") # Save inverted score layer to file |
| 112 | + |
| 113 | +# Example 4) Build a LayerOps object that combines the comments from elements in the list, with custom defaults |
| 114 | +lo4 = LayerOps(score=lambda x: '; '.join(x), |
| 115 | + default_values= { |
| 116 | + "comment": "This was an example of new default values" |
| 117 | + }, |
| 118 | + desc= lambda x: "This is a defaults example") # Build LayerOps object to combine descriptions, defaults |
| 119 | +out_layer6 = lo4.process([demo2, demo3]) # Trigger processing on a list of demo2 and demo0 |
| 120 | +out_layer6.to_file("C:\demo_layer6.json") # Save combined comment layer to file |
| 121 | +``` |
0 commit comments