mirror of
https://github.com/mandiant/capa.git
synced 2026-02-04 11:07:53 -08:00
linter: add mbc data extractor and linter
This commit is contained in:
@@ -223,37 +223,47 @@ class ExampleFileDNE(Lint):
|
||||
return not found
|
||||
|
||||
|
||||
class InvalidAttckTechnique(Lint):
|
||||
name = "att&ck technique is malformed or does not exist"
|
||||
class InvalidAttckOrMbcTechnique(Lint):
|
||||
name = "att&ck/mbc entry is malformed or does not exist"
|
||||
recommendation = """
|
||||
The att&ck field must respect the following format:
|
||||
<Tactic>::<Technique> [<TXXXX>]
|
||||
The att&ck and mbc fields must respect the following format:
|
||||
<Tactic/Objective>::<Technique/Behavior> [<ID>]
|
||||
OR
|
||||
<Tactic>::<Technique>::<Subtechnique> [<TXXXX.XXX>]
|
||||
<Tactic/Objective>::<Technique/Behavior>::<Subtechnique/Method> [<ID.SubID>]
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(InvalidAttckTechnique, self).__init__()
|
||||
super(InvalidAttckOrMbcTechnique, self).__init__()
|
||||
|
||||
# This regex match the format defined in the recommandation attribute
|
||||
self.reg = re.compile("^([a-zA-Z| ]+)::(.*) \[(T\d+\.?\d*)\]$")
|
||||
with open("scripts/linter-data.json", "r") as jf:
|
||||
self.techniques = json.load(jf)
|
||||
self.reg = re.compile("^([a-zA-Z| ]+)::(.*) \[([A-Za-z0-9.]+)\]$")
|
||||
with open("scripts/linter-data.json", "r") as fd:
|
||||
self.data = json.load(fd)
|
||||
|
||||
def _entry_check(self, framework, category, entry, eid):
|
||||
if category not in self.data[framework].keys():
|
||||
self.recommendation = f'Unknown category: "{category}"'
|
||||
return True
|
||||
if eid not in self.data[framework][category].keys():
|
||||
self.recommendation = f"Unknown entry ID: {eid}"
|
||||
return True
|
||||
if self.data[framework][category][eid] != entry:
|
||||
self.recommendation = (
|
||||
f'{eid} should be associated to entry "{self.data[framework][category][eid]}" instead of "{entry}"'
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_rule(self, ctx: Context, rule: Rule):
|
||||
if "att&ck" in rule.meta.keys():
|
||||
for r in rule.meta["att&ck"]:
|
||||
m = self.reg.match(r)
|
||||
if m:
|
||||
tactic, technique, tid = m.group(1, 2, 3)
|
||||
if tactic not in self.techniques.keys():
|
||||
self.recommendation = f'Unknown tactic: "{tactic}"'
|
||||
for framework in ["mbc"]:
|
||||
if framework in rule.meta.keys():
|
||||
for r in rule.meta[framework]:
|
||||
m = self.reg.match(r)
|
||||
if m is None:
|
||||
return True
|
||||
if tid not in self.techniques[tactic].keys():
|
||||
self.recommendation = f"Unknown technique ID: {tid}"
|
||||
return True
|
||||
if self.techniques[tactic][tid] != technique:
|
||||
self.recommendation = f'{tid} should be associated to technique "{self.techniques[tactic][tid]}" instead of "{technique}"'
|
||||
|
||||
args = m.group(1, 2, 3)
|
||||
if self._entry_check(framework, *args):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -684,7 +694,7 @@ META_LINTS = (
|
||||
UnusualMetaField(),
|
||||
LibRuleNotInLibDirectory(),
|
||||
LibRuleHasNamespace(),
|
||||
InvalidAttckTechnique(),
|
||||
InvalidAttckOrMbcTechnique(),
|
||||
)
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,10 +5,14 @@ import requests
|
||||
from stix2 import Filter, MemoryStore, AttackPattern
|
||||
|
||||
|
||||
class StixExtractor:
|
||||
class MitreExtractor:
|
||||
url = ""
|
||||
kill_chain_name = ""
|
||||
|
||||
def __init__(self):
|
||||
if self.kill_chain_name == "":
|
||||
raise ValueError(f"Kill chain name not specified in class {self.__class__.__name__}")
|
||||
|
||||
if self.url == "":
|
||||
raise ValueError(f"URL not specified in class {self.__class__.__name__}")
|
||||
|
||||
@@ -25,10 +29,6 @@ class StixExtractor:
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class AttckStixExtractor(StixExtractor):
|
||||
url = "https://raw.githubusercontent.com/mitre-attack/attack-stix-data/master/enterprise-attack/enterprise-attack.json"
|
||||
|
||||
def _get_tactics(self) -> list[dict]:
|
||||
# Only one matrix for enterprise att&ck framework
|
||||
matrix = self._remove_deprecated_objetcs(
|
||||
@@ -47,7 +47,7 @@ class AttckStixExtractor(StixExtractor):
|
||||
Filter("type", "=", "attack-pattern"),
|
||||
Filter("kill_chain_phases.phase_name", "=", tactic),
|
||||
Filter( # kill chain name for enterprise att&ck
|
||||
"kill_chain_phases.kill_chain_name", "=", "mitre-attack"
|
||||
"kill_chain_phases.kill_chain_name", "=", self.kill_chain_name
|
||||
),
|
||||
]
|
||||
)
|
||||
@@ -72,23 +72,37 @@ class AttckStixExtractor(StixExtractor):
|
||||
data[tactic["name"]] = {}
|
||||
for technique in self._get_techniques_from_tactic(tactic["x_mitre_shortname"]):
|
||||
tid = technique["external_references"][0]["external_id"]
|
||||
technique_name = technique["name"].split("::")[0]
|
||||
if technique["x_mitre_is_subtechnique"]:
|
||||
parent_technique = self._get_parent_technique_from_subtechnique(technique)
|
||||
data[tactic["name"]][tid] = f"{parent_technique['name']}::{technique['name']}"
|
||||
data[tactic["name"]][tid] = f"{parent_technique['name']}::{technique_name}"
|
||||
else:
|
||||
data[tactic["name"]][tid] = technique["name"]
|
||||
data[tactic["name"]][tid] = technique_name
|
||||
return data
|
||||
|
||||
|
||||
class MbcStixExtractor(StixExtractor):
|
||||
...
|
||||
class AttckExtractor(MitreExtractor):
|
||||
url = "https://raw.githubusercontent.com/mitre-attack/attack-stix-data/master/enterprise-attack/enterprise-attack.json"
|
||||
kill_chain_name = "mitre-attack"
|
||||
|
||||
|
||||
class MbcExtractor(MitreExtractor):
|
||||
url = "https://raw.githubusercontent.com/MBCProject/mbc-stix2/master/mbc/mbc.json"
|
||||
kill_chain_name = "mitre-mbc"
|
||||
|
||||
def _get_tactics(self) -> list[dict]:
|
||||
tactics = super(MbcExtractor, self)._get_tactics()
|
||||
# We don't want the Micro-objective string inside objective names
|
||||
for tactic in tactics:
|
||||
tactic["name"] = tactic["name"].replace(" Micro-objective", "")
|
||||
return tactics
|
||||
|
||||
|
||||
def main():
|
||||
s = AttckStixExtractor()
|
||||
r = s.run()
|
||||
data = {"att&ck": AttckExtractor().run(), "mbc": MbcExtractor().run()}
|
||||
|
||||
with open(f"{dirname(__file__)}/linter-data.json", "w") as jf:
|
||||
json.dump(r, jf, indent=2)
|
||||
json.dump(data, jf, indent=2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user