mirror of
https://github.com/mandiant/capa.git
synced 2025-12-22 07:10:29 -08:00
parse att&ck for output doc
This commit is contained in:
@@ -110,7 +110,7 @@ It includes many new rules, including all new techniques introduced in MITRE ATT
|
|||||||
- linter: summarize results at the end #571 @williballenthin
|
- linter: summarize results at the end #571 @williballenthin
|
||||||
- meta: added `library_functions` field, `feature_counts.functions` does not include library functions any more #562 @mr-tz
|
- meta: added `library_functions` field, `feature_counts.functions` does not include library functions any more #562 @mr-tz
|
||||||
- linter: check for `or` with always true child statement, e.g. `optional`, colors #348 @mr-tz
|
- linter: check for `or` with always true child statement, e.g. `optional`, colors #348 @mr-tz
|
||||||
- json: breaking change in results document; now contains parsed MBC fields instead of canonical representation #526 @mr-tz
|
- json: breaking change in results document; now contains parsed ATT&CK and MBC fields instead of canonical representation #526 @mr-tz
|
||||||
- json: breaking change: record all matching strings for regex #159 @williballenthin
|
- json: breaking change: record all matching strings for regex #159 @williballenthin
|
||||||
- main: implement file limitations via rules not code #390 @williballenthin
|
- main: implement file limitations via rules not code #390 @williballenthin
|
||||||
|
|
||||||
|
|||||||
@@ -203,11 +203,44 @@ def convert_match_to_result_document(rules, capabilities, result):
|
|||||||
|
|
||||||
|
|
||||||
def convert_meta_to_result_document(meta):
|
def convert_meta_to_result_document(meta):
|
||||||
|
attacks = meta.get("att&ck", [])
|
||||||
|
meta["att&ck"] = [parse_canonical_attack(attack) for attack in attacks]
|
||||||
mbcs = meta.get("mbc", [])
|
mbcs = meta.get("mbc", [])
|
||||||
meta["mbc"] = [parse_canonical_mbc(mbc) for mbc in mbcs]
|
meta["mbc"] = [parse_canonical_mbc(mbc) for mbc in mbcs]
|
||||||
return meta
|
return meta
|
||||||
|
|
||||||
|
|
||||||
|
def parse_canonical_attack(attck):
|
||||||
|
"""
|
||||||
|
parse capa's canonical ATT&CK representation: `Tactic::Technique::Subtechnique [Identifier]`
|
||||||
|
"""
|
||||||
|
id = ""
|
||||||
|
tactic = ""
|
||||||
|
technique = ""
|
||||||
|
subtechnique = ""
|
||||||
|
parts = attck.split("::")
|
||||||
|
if len(parts) > 0:
|
||||||
|
last = parts.pop()
|
||||||
|
last, _, id = last.rpartition(" ")
|
||||||
|
id = id.lstrip("[").rstrip("]")
|
||||||
|
parts.append(last)
|
||||||
|
|
||||||
|
if len(parts) > 0:
|
||||||
|
tactic = parts[0]
|
||||||
|
if len(parts) > 1:
|
||||||
|
technique = parts[1]
|
||||||
|
if len(parts) > 2:
|
||||||
|
subtechnique = parts[2]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"parts": parts,
|
||||||
|
"id": id,
|
||||||
|
"tactic": tactic,
|
||||||
|
"technique": technique,
|
||||||
|
"subtechnique": subtechnique,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def parse_canonical_mbc(mbc):
|
def parse_canonical_mbc(mbc):
|
||||||
"""
|
"""
|
||||||
parse capa's canonical MBC representation: `Objective::Behavior::Method [Identifier]`
|
parse capa's canonical MBC representation: `Objective::Behavior::Method [Identifier]`
|
||||||
|
|||||||
@@ -123,14 +123,10 @@ def render_attack(doc, ostream):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
for attack in rule["meta"]["att&ck"]:
|
for attack in rule["meta"]["att&ck"]:
|
||||||
tactic, _, rest = attack.partition("::")
|
if attack.get("subtechnique"):
|
||||||
if "::" in rest:
|
tactics[attack["tactic"]].add((attack["technique"], attack["subtechnique"], attack["id"]))
|
||||||
technique, _, rest = rest.partition("::")
|
|
||||||
subtechnique, _, id = rest.rpartition(" ")
|
|
||||||
tactics[tactic].add((technique, subtechnique, id))
|
|
||||||
else:
|
else:
|
||||||
technique, _, id = rest.rpartition(" ")
|
tactics[attack["tactic"]].add((attack["technique"], attack["id"]))
|
||||||
tactics[tactic].add((technique, id))
|
|
||||||
|
|
||||||
rows = []
|
rows = []
|
||||||
for tactic, techniques in sorted(tactics.items()):
|
for tactic, techniques in sorted(tactics.items()):
|
||||||
|
|||||||
@@ -29,8 +29,11 @@ def hex(n):
|
|||||||
return "0x%X" % n
|
return "0x%X" % n
|
||||||
|
|
||||||
|
|
||||||
def format_mbc(mbc):
|
def format_parts_id(data):
|
||||||
return "%s [%s]" % ("::".join(mbc["parts"]), mbc["id"])
|
"""
|
||||||
|
format canonical representation of ATT&CK/MBC parts and ID
|
||||||
|
"""
|
||||||
|
return "%s [%s]" % ("::".join(data["parts"]), data["id"])
|
||||||
|
|
||||||
|
|
||||||
def capability_rules(doc):
|
def capability_rules(doc):
|
||||||
|
|||||||
@@ -219,8 +219,8 @@ def render_rules(ostream, doc):
|
|||||||
if not v:
|
if not v:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if key == "mbc":
|
if key in ("att&ck", "mbc"):
|
||||||
v = [rutils.format_mbc(mbc) for mbc in v]
|
v = [rutils.format_parts_id(vv) for vv in v]
|
||||||
|
|
||||||
if isinstance(v, list) and len(v) == 1:
|
if isinstance(v, list) and len(v) == 1:
|
||||||
v = v[0]
|
v = v[0]
|
||||||
|
|||||||
@@ -579,8 +579,9 @@ class Rule(object):
|
|||||||
raise InvalidRule("{:s} is not a supported scope".format(scope))
|
raise InvalidRule("{:s} is not a supported scope".format(scope))
|
||||||
|
|
||||||
meta = d["rule"]["meta"]
|
meta = d["rule"]["meta"]
|
||||||
mbcs = meta.get("mbc", [])
|
if not isinstance(meta.get("att&ck", []), list):
|
||||||
if not isinstance(mbcs, list):
|
raise InvalidRule("ATT&CK mapping must be a list")
|
||||||
|
if not isinstance(meta.get("mbc", []), list):
|
||||||
raise InvalidRule("MBC mapping must be a list")
|
raise InvalidRule("MBC mapping must be a list")
|
||||||
|
|
||||||
return cls(name, scope, build_statements(statements[0], scope), meta, definition)
|
return cls(name, scope, build_statements(statements[0], scope), meta, definition)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
import json
|
import json
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
import pytest
|
|
||||||
from fixtures import *
|
from fixtures import *
|
||||||
|
|
||||||
import capa.main
|
import capa.main
|
||||||
@@ -361,8 +360,8 @@ def test_not_render_rules_also_matched(z9324d_extractor, capsys):
|
|||||||
assert "create TCP socket" in std.out
|
assert "create TCP socket" in std.out
|
||||||
|
|
||||||
|
|
||||||
# It tests main works with different backends
|
|
||||||
def test_backend_option(capsys):
|
def test_backend_option(capsys):
|
||||||
|
# tests that main works with different backends
|
||||||
path = get_data_path_by_name("pma16-01")
|
path = get_data_path_by_name("pma16-01")
|
||||||
assert capa.main.main([path, "-j", "-b", capa.main.BACKEND_VIV]) == 0
|
assert capa.main.main([path, "-j", "-b", capa.main.BACKEND_VIV]) == 0
|
||||||
std = capsys.readouterr()
|
std = capsys.readouterr()
|
||||||
|
|||||||
71
tests/test_render.py
Normal file
71
tests/test_render.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import textwrap
|
||||||
|
|
||||||
|
import capa.rules
|
||||||
|
from capa.render import convert_meta_to_result_document
|
||||||
|
from capa.render.utils import format_parts_id
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_meta_attack():
|
||||||
|
# Persistence::Boot or Logon Autostart Execution::Registry Run Keys / Startup Folder [T1547.001]
|
||||||
|
id = "T1543.003"
|
||||||
|
tactic = "Persistence"
|
||||||
|
technique = "Create or Modify System Process"
|
||||||
|
subtechnique = "Windows Service"
|
||||||
|
canonical = "{:s}::{:s}::{:s} [{:s}]".format(tactic, technique, subtechnique, id)
|
||||||
|
|
||||||
|
rule = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
rule:
|
||||||
|
meta:
|
||||||
|
name: test rule
|
||||||
|
att&ck:
|
||||||
|
- {:s}
|
||||||
|
features:
|
||||||
|
- number: 1
|
||||||
|
""".format(
|
||||||
|
canonical
|
||||||
|
)
|
||||||
|
)
|
||||||
|
r = capa.rules.Rule.from_yaml(rule)
|
||||||
|
rule_meta = convert_meta_to_result_document(r.meta)
|
||||||
|
attack = rule_meta["att&ck"][0]
|
||||||
|
|
||||||
|
assert attack["id"] == id
|
||||||
|
assert attack["tactic"] == tactic
|
||||||
|
assert attack["technique"] == technique
|
||||||
|
assert attack["subtechnique"] == subtechnique
|
||||||
|
|
||||||
|
assert format_parts_id(attack) == canonical
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_meta_mbc():
|
||||||
|
# Defense Evasion::Disable or Evade Security Tools::Heavens Gate [F0004.008]
|
||||||
|
id = "F0004.008"
|
||||||
|
objective = "Defense Evasion"
|
||||||
|
behavior = "Disable or Evade Security Tools"
|
||||||
|
method = "Heavens Gate"
|
||||||
|
canonical = "{:s}::{:s}::{:s} [{:s}]".format(objective, behavior, method, id)
|
||||||
|
|
||||||
|
rule = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
rule:
|
||||||
|
meta:
|
||||||
|
name: test rule
|
||||||
|
mbc:
|
||||||
|
- {:s}
|
||||||
|
features:
|
||||||
|
- number: 1
|
||||||
|
""".format(
|
||||||
|
canonical
|
||||||
|
)
|
||||||
|
)
|
||||||
|
r = capa.rules.Rule.from_yaml(rule)
|
||||||
|
rule_meta = convert_meta_to_result_document(r.meta)
|
||||||
|
attack = rule_meta["mbc"][0]
|
||||||
|
|
||||||
|
assert attack["id"] == id
|
||||||
|
assert attack["objective"] == objective
|
||||||
|
assert attack["behavior"] == behavior
|
||||||
|
assert attack["method"] == method
|
||||||
|
|
||||||
|
assert format_parts_id(attack) == canonical
|
||||||
@@ -399,6 +399,34 @@ def test_invalid_rules():
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# att&ck and mbc must be lists
|
||||||
|
with pytest.raises(capa.rules.InvalidRule):
|
||||||
|
r = capa.rules.Rule.from_yaml(
|
||||||
|
textwrap.dedent(
|
||||||
|
"""
|
||||||
|
rule:
|
||||||
|
meta:
|
||||||
|
name: test rule
|
||||||
|
att&ck: Tactic::Technique::Subtechnique [Identifier]
|
||||||
|
features:
|
||||||
|
- number: 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
with pytest.raises(capa.rules.InvalidRule):
|
||||||
|
r = capa.rules.Rule.from_yaml(
|
||||||
|
textwrap.dedent(
|
||||||
|
"""
|
||||||
|
rule:
|
||||||
|
meta:
|
||||||
|
name: test rule
|
||||||
|
mbc: Objective::Behavior::Method [Identifier]
|
||||||
|
features:
|
||||||
|
- number: 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_number_symbol():
|
def test_number_symbol():
|
||||||
rule = textwrap.dedent(
|
rule = textwrap.dedent(
|
||||||
|
|||||||
Reference in New Issue
Block a user