1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
use std::{
collections::{BTreeSet, HashMap},
fmt,
};
use attck::{AttackPattern, Matrix, Node, Tactic};
use stix::Id;
struct DisplayMatrix<'a> {
/// The tactic columns of the MITRE ATT&CK matrix and their associated techniques.
/// Subtechniques are not included, but parent technique `Node` instances are preserved
/// so that the `subtechnique_attack_patterns` method can be used to retrieve those.
///
/// Tactics are in declaration order; techniques are in order of their declaration in
/// the STIX bundle.
columns: Vec<(&'a Tactic, Vec<Node<'a, AttackPattern>>)>,
dangling_ids: BTreeSet<&'a Id>,
}
impl<'a> DisplayMatrix<'a> {
pub fn new(matrix: Node<'a, Matrix>) -> Self {
let collection = matrix.collection();
// Annoyingly, MITRE does not publish SROs from tactics to techniques and instead relies on
// the kill_chain_phases property. The `stix` crate doesn't know about this relationship, so
// we do one pass to gather all the tactics and visit every attack_pattern to build the grid.
let mut tactics_by_shortname = HashMap::new();
// Cells are ordered to preserve the ordering from the Matrix declaration.
let mut columns = vec![];
let mut dangling_ids = BTreeSet::new();
for tactic_ref in &matrix.data().tactic_refs {
if let Some(tactic) = collection.get::<Tactic>(tactic_ref) {
// Use data() because &tactic.shortname doesn't live long enough
tactics_by_shortname.insert(&tactic.data().shortname, columns.len());
// Insert _after_ capturing the index of the value we just added or else our
// indices will be off by one.
columns.push((tactic.data(), vec![]));
} else {
dangling_ids.insert(tactic_ref);
}
}
for attack_pattern in matrix
.collection()
.attack_patterns()
.filter(|ap| ap.mitre.is_subtechnique == Some(false))
{
for phase in attack_pattern
.base
.kill_chain_phases
.iter()
.filter(|phase| phase.kill_chain_name == "mitre-attack")
{
if let Some(cell_index) = tactics_by_shortname.get(&phase.phase_name).copied() {
columns[cell_index].1.push(attack_pattern.clone());
}
}
}
Self {
columns,
dangling_ids,
}
}
}
impl fmt::Display for DisplayMatrix<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (tactic, techniques) in &self.columns {
write!(f, "{} ({}) | ", tactic.name, tactic.mitre_id().unwrap())?;
if techniques.is_empty() {
writeln!(f, "No techniques")?;
continue;
}
write!(f, "{}", techniques[0].name())?;
for technique in &techniques[1..3] {
write!(f, ", {}", technique.name())?;
}
if techniques.len() > 4 {
writeln!(f, " +{} more", techniques.len() - 4)?;
} else {
writeln!(f)?;
}
}
Ok(())
}
}
fn main() {
let enterprise = attck::enterprise();
for matrix in enterprise.matrices() {
println!("===== {} =====", matrix.name);
let disp = DisplayMatrix::new(matrix);
println!("{}", disp);
for id in &disp.dangling_ids {
eprintln!("Found reference to {} but it was not in the collection", id);
}
}
}