Skip to content

Commit 180c64d

Browse files
authored
chore: add object counter
1 parent bf4b78a commit 180c64d

1 file changed

Lines changed: 186 additions & 0 deletions

File tree

examples/attack-object-counter.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
"""Print comprehensive ATT&CK statistics across all domains."""
2+
3+
import os
4+
from dataclasses import dataclass
5+
6+
from mitreattack.stix20 import MitreAttackData
7+
8+
# Get STIX base directory from environment or use default
9+
STIX_BASE_DIR = os.environ.get("STIX_BASE_DIR", "attack-releases/stix-2.0/v17.1")
10+
11+
12+
@dataclass
13+
class DomainStatistics:
14+
"""Statistics for a single ATT&CK domain."""
15+
16+
name: str
17+
tactics: int
18+
techniques: int
19+
subtechniques: int
20+
groups: int
21+
software: int
22+
campaigns: int
23+
mitigations: int
24+
datasources: int
25+
assets: int = 0
26+
27+
def format_output(self) -> str:
28+
"""
29+
Format domain statistics as a string.
30+
31+
Returns
32+
-------
33+
str
34+
Formatted statistics string for display.
35+
"""
36+
# Define all possible statistics with their labels
37+
stats = [
38+
(self.tactics, "Tactics"),
39+
(self.techniques, "Techniques"),
40+
(self.subtechniques, "Sub-Techniques"),
41+
(self.groups, "Groups"),
42+
(self.software, "Pieces of Software"),
43+
(self.campaigns, "Campaigns"),
44+
(self.mitigations, "Mitigations"),
45+
(self.assets, "Assets"),
46+
(self.datasources, "Data Sources"),
47+
]
48+
49+
# Build parts list, only including items with count > 0
50+
parts = [f"{count} {label}" for count, label in stats if count > 0]
51+
52+
# Join all parts with proper formatting
53+
return f"- {self.name}: {', '.join(parts[:-1])}, and {parts[-1]}"
54+
55+
56+
def load_domain_data() -> dict[str, MitreAttackData]:
57+
"""
58+
Load STIX data for all ATT&CK domains.
59+
60+
Returns
61+
-------
62+
dict of str to MitreAttackData
63+
Mapping of domain names to loaded MitreAttackData objects.
64+
"""
65+
domains = {
66+
"enterprise": "enterprise-attack.json",
67+
"mobile": "mobile-attack.json",
68+
"ics": "ics-attack.json",
69+
}
70+
71+
return {
72+
domain: MitreAttackData(stix_filepath=os.path.join(STIX_BASE_DIR, filename))
73+
for domain, filename in domains.items()
74+
}
75+
76+
77+
def collect_domain_statistics(data: MitreAttackData, domain_name: str) -> DomainStatistics:
78+
"""
79+
Collect statistics for a single domain.
80+
81+
Parameters
82+
----------
83+
data : MitreAttackData
84+
The MitreAttackData object for the domain.
85+
domain_name : str
86+
Display name of the domain.
87+
88+
Returns
89+
-------
90+
DomainStatistics
91+
Statistics for the domain.
92+
"""
93+
# Get all object types, removing revoked and deprecated
94+
tactics = data.get_tactics(remove_revoked_deprecated=True)
95+
techniques = data.get_techniques(include_subtechniques=False, remove_revoked_deprecated=True)
96+
subtechniques = data.get_subtechniques(remove_revoked_deprecated=True)
97+
groups = data.get_groups(remove_revoked_deprecated=True)
98+
software = data.get_software(remove_revoked_deprecated=True)
99+
campaigns = data.get_campaigns(remove_revoked_deprecated=True)
100+
mitigations = data.get_mitigations(remove_revoked_deprecated=True)
101+
datasources = data.get_datasources(remove_revoked_deprecated=True)
102+
103+
# ICS domain has assets
104+
assets = 0
105+
if domain_name == "ICS":
106+
assets = len(data.get_assets(remove_revoked_deprecated=True))
107+
108+
return DomainStatistics(
109+
name=domain_name,
110+
tactics=len(tactics),
111+
techniques=len(techniques),
112+
subtechniques=len(subtechniques),
113+
groups=len(groups),
114+
software=len(software),
115+
campaigns=len(campaigns),
116+
mitigations=len(mitigations),
117+
datasources=len(datasources),
118+
assets=assets,
119+
)
120+
121+
122+
def collect_unique_object_counts(domain_data: dict[str, MitreAttackData]) -> dict[str, int]:
123+
"""
124+
Collect counts of unique objects across all domains.
125+
126+
Some objects (Software, Groups, Campaigns) may appear in multiple domains.
127+
This function counts unique objects to avoid double-counting.
128+
129+
Parameters
130+
----------
131+
domain_data : dict of str to MitreAttackData
132+
Mapping of domain names to MitreAttackData objects.
133+
134+
Returns
135+
-------
136+
dict of str to int
137+
Counts of unique software, groups, and campaigns.
138+
"""
139+
all_software_ids = set()
140+
all_groups_ids = set()
141+
all_campaigns_ids = set()
142+
143+
for data in domain_data.values():
144+
software = data.get_software(remove_revoked_deprecated=True)
145+
groups = data.get_groups(remove_revoked_deprecated=True)
146+
campaigns = data.get_campaigns(remove_revoked_deprecated=True)
147+
148+
all_software_ids.update(obj["id"] for obj in software)
149+
all_groups_ids.update(obj["id"] for obj in groups)
150+
all_campaigns_ids.update(obj["id"] for obj in campaigns)
151+
152+
return {
153+
"software": len(all_software_ids),
154+
"groups": len(all_groups_ids),
155+
"campaigns": len(all_campaigns_ids),
156+
}
157+
158+
159+
def main():
160+
"""Print ATT&CK statistics for all domains."""
161+
# Load data for all domains
162+
domain_data = load_domain_data()
163+
164+
# Collect unique object counts across all domains
165+
unique_counts = collect_unique_object_counts(domain_data)
166+
167+
# Collect statistics for each domain
168+
enterprise_stats = collect_domain_statistics(domain_data["enterprise"], "Enterprise")
169+
mobile_stats = collect_domain_statistics(domain_data["mobile"], "Mobile")
170+
ics_stats = collect_domain_statistics(domain_data["ics"], "ICS")
171+
172+
# Print summary output
173+
print(
174+
f"This version of ATT&CK contains {unique_counts['software']} Pieces of Software, "
175+
f"{unique_counts['groups']} Groups, and {unique_counts['campaigns']} Campaigns"
176+
)
177+
print("Broken out by domain:\n")
178+
179+
# Print domain statistics
180+
print(enterprise_stats.format_output())
181+
print(mobile_stats.format_output())
182+
print(ics_stats.format_output())
183+
184+
185+
if __name__ == "__main__":
186+
main()

0 commit comments

Comments
 (0)