Skip to content
This repository was archived by the owner on Dec 11, 2023. It is now read-only.

Commit f618415

Browse files
committed
merging develop into master
2 parents 686478d + 6ab69a4 commit f618415

File tree

5 files changed

+65
-18
lines changed

5 files changed

+65
-18
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# v1.9.0 - 23 March 2021
2+
## Improvements
3+
- [diff_stix.py](scripts/diff_stix.py) now supports an `--unchanged` argument which adds a section listing objects which did _not_ change between releases.
4+
## Fixes
5+
- [diff_stix.py](scripts/diff_stix.py) should no longer crash when exporting layers but with only one domain specified.
6+
- Fixed a bug in the layer library where layers could be imported improperly in specific contexts.
7+
- Fixed a bug with the layer to SVG exporter which was causing sub-technique scores colors to disappear.
8+
19
# v1.8.1 - 3 March 2021
210
## Fixes
311
- Layer library should no longer throw errors when it encounters the "Network" and "PRE" platforms.

layers/exporters/svg_objects.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ def build(height, width, label, config, variant='text', t1text=None, t2text=None
260260
upper = G(tx=0, ty=2.1)
261261
internal.append(upper)
262262
if t1text is not None:
263-
bu = t2text is not None and t2text is not ""
263+
bu = t2text != None and t2text != ""
264264
theight = (height-5)
265265
if bu:
266266
theight = theight / 2

layers/exporters/svg_templates.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -170,17 +170,12 @@ def get_tactic(self, tactic, height, width, config, colors=[], scores=[], subtec
170170
"""
171171
offset = 0
172172
column = G(ty=2)
173+
for a in tactic.subtechniques:
174+
self._copy_scores(tactic.subtechniques[a],scores,tactic.tactic.name, exclude)
173175
for x in tactic.techniques:
174176
if any(x.id == y[0] and (y[1] == self.h.convert(tactic.tactic.name) or not y[1]) for y in exclude):
175177
continue
176-
found = False
177-
for y in scores:
178-
if x.id == y[0] and (y[1] == self.h.convert(tactic.tactic.name) or not y[1]):
179-
x.score = y[2]
180-
found = True
181-
continue
182-
if not found:
183-
x.score = None
178+
self._copy_scores([x], scores, tactic.tactic.name, exclude)
184179
if any(x.id == y[0] and (y[1] == self.h.convert(tactic.tactic.name) or not y[1]) for y in subtechs):
185180
a, offset = self.get_tech(offset, mode, x, tactic=self.h.convert(tactic.tactic.name),
186181
subtechniques=tactic.subtechniques.get(x.id, []), colors=colors,
@@ -286,4 +281,24 @@ def export(self, showName, showID, lhandle, config, sort=0, scores=[], colors=[]
286281
d.append(glob)
287282
if overlay:
288283
d.append(overlay)
289-
return d
284+
return d
285+
286+
287+
def _copy_scores(self, listing, scores, tactic_name, exclude):
288+
"""
289+
INTERNAL: Move scores over from the input object (scores) to the one used to build the zvg (listing)
290+
291+
:param listing: List of objects to apply scores to
292+
:param scores: List of scores for this tactic
293+
:param exclude: List of excluded techniques
294+
:return: None - operates on the raw object itself
295+
"""
296+
for b in listing:
297+
found = False
298+
for y in scores:
299+
if b.id == y[0] and (y[1] == self.h.convert(tactic_name) or not y[1]):
300+
b.score = y[2]
301+
found = True
302+
continue
303+
if not found:
304+
b.score = None

layers/exporters/to_svg.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from ..core import Layer
88
from ..exporters.svg_templates import SvgTemplates
99

10+
from layers.core import Layer as topLayer # alternative import for typechecking
11+
1012
class NoLayer(Exception):
1113
pass
1214

@@ -405,7 +407,7 @@ def to_svg(self, layer, filepath='example.svg'):
405407
:return: (meta) svg file at the targeted output location
406408
"""
407409
if layer is not None:
408-
if not isinstance(layer, Layer):
410+
if not isinstance(layer, Layer) and not isinstance(layer, topLayer):
409411
raise TypeError
410412

411413
if layer is None:

scripts/diff_stix.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,26 @@
3737
"minor_changes": "Minor {obj_type} changes",
3838
"deprecations": "{obj_type} deprecations",
3939
"revocations": "{obj_type} revocations",
40-
"deletions": "{obj_type} deletions"
40+
"deletions": "{obj_type} deletions",
41+
"unchanged": "Unchanged {obj_type}"
4142
}
4243
statusToColor = { # color key for layers
4344
"additions": "#a1d99b",
4445
"changes": "#fcf3a2",
4546
"minor_changes": "#c7c4e0",
4647
"deletions": "#ff00e1", # this will probably never show up but just in case
4748
"revocations": "#ff9000",
48-
"deprecations": "#ff6363"
49+
"deprecations": "#ff6363",
50+
"unchanged": "#ffffff"
4951
}
5052
statusDescriptions = { # explanation of modification types to data objects for legend in layer files
5153
"additions": "objects which are present in the new data and not the old",
5254
"changes": "objects which have a newer version number in the new data compared to the old",
5355
"minor_changes": "objects which have a newer last edit date in the new data than in the old, but the same version number",
5456
"revocations": "objects which are revoked in the new data but not in the old",
5557
"deprecations": "objects which are deprecated in the new data but not in the old",
56-
"deletions": "objects which are present in the old data but not the new"
58+
"deletions": "objects which are present in the old data but not the new",
59+
"unchanged": "objects which did not change between the two versions"
5760
}
5861

5962
class DiffStix(object):
@@ -66,6 +69,7 @@ def __init__(
6669
layers=None,
6770
markdown=None,
6871
minor_changes=False,
72+
unchanged=False,
6973
new='new',
7074
old='old',
7175
show_key=False,
@@ -93,6 +97,7 @@ def __init__(
9397
self.layers = layers
9498
self.markdown = markdown
9599
self.minor_changes = minor_changes
100+
self.unchanged = unchanged
96101
self.new = new
97102
self.old = old
98103
self.show_key = show_key
@@ -109,7 +114,8 @@ def __init__(
109114
# changes: [],
110115
# minor_changes: [],
111116
# revocations: [],
112-
# deprecations: []
117+
# deprecations: [],
118+
# unchanged: []
113119
# }
114120
# mobile-attack...
115121
# }
@@ -236,6 +242,7 @@ def load_taxii(new=False):
236242
minor_changes = set()
237243
revocations = set()
238244
deprecations = set()
245+
unchanged = set()
239246

240247
# find changes, revocations and deprecations
241248
for key in intersection:
@@ -281,6 +288,8 @@ def load_taxii(new=False):
281288
new_date = dateparser.parse(new["id_to_obj"][key]["modified"])
282289
if new_date > old_date:
283290
minor_changes.add(key)
291+
else :
292+
unchanged.add(key)
284293

285294
# set data
286295
if obj_type not in self.data: self.data[obj_type] = {}
@@ -291,6 +300,11 @@ def load_taxii(new=False):
291300
# only create minor_changes data if we want to display it later
292301
if self.minor_changes:
293302
self.data[obj_type][domain]["minor_changes"] = [new["id_to_obj"][key] for key in minor_changes]
303+
304+
# ditto for unchanged
305+
if self.unchanged:
306+
self.data[obj_type][domain]["unchanged"] = [new["id_to_obj"][key] for key in unchanged]
307+
294308
self.data[obj_type][domain]["revocations"] = [new["id_to_obj"][key] for key in revocations]
295309
self.data[obj_type][domain]["deprecations"] = [new["id_to_obj"][key] for key in deprecations]
296310
# only show deletions if objects were deleted
@@ -324,6 +338,8 @@ def get_md_key(self):
324338
)
325339
if self.minor_changes:
326340
key += "* Minor object changes: " + statusDescriptions['minor_changes'] + "\n"
341+
if self.unchanged:
342+
key += "* Unchanged objects: " + statusDescriptions['unchanged'] + "\n"
327343
key += (
328344
"* Object revocations: " + statusDescriptions['revocations'] + "\n"
329345
"* Object deprecations: " + statusDescriptions['deprecations']
@@ -474,7 +490,7 @@ def get_layers_dict(self):
474490
"tactic": phase['phase_name'],
475491
"enabled": True,
476492
"color": statusToColor[status],
477-
"comment": status[:-1] # trim s off end of word
493+
"comment": status[:-1] if status != "unchanged" else status # trim s off end of word
478494
})
479495
used_statuses.add(status)
480496

@@ -528,8 +544,8 @@ def layers_dict_to_files(outfiles, layers):
528544
verboseprint("writing layers dict to layer files... ", end="", flush="true")
529545

530546
# write each layer to separate files
531-
json.dump(layers['enterprise-attack'], open(outfiles[0], "w"), indent=4)
532-
json.dump(layers['mobile-attack'], open(outfiles[1], "w"), indent=4)
547+
if 'enterprise-attack' in layers: json.dump(layers['enterprise-attack'], open(outfiles[0], "w"), indent=4)
548+
if 'mobile-attack' in layers: json.dump(layers['mobile-attack'], open(outfiles[1], "w"), indent=4)
533549

534550
verboseprint("done")
535551

@@ -621,6 +637,11 @@ def layers_dict_to_files(outfiles, layers):
621637
help="show changes to objects which didn't increment the version number"
622638
)
623639

640+
parser.add_argument("--unchanged",
641+
action="store_true",
642+
help="show objects without changes in the markdown output"
643+
)
644+
624645
parser.add_argument("--use-taxii",
625646
action="store_true",
626647
help="Use content from the ATT&CK TAXII server for the -old data"
@@ -648,6 +669,7 @@ def layers_dict_to_files(outfiles, layers):
648669
layers=args.layers,
649670
markdown=args.markdown,
650671
minor_changes=args.minor_changes,
672+
unchanged=args.unchanged,
651673
new=args.new,
652674
old=args.old,
653675
show_key=args.show_key,

0 commit comments

Comments
 (0)