diff --git a/capa/render/default.py b/capa/render/default.py index 52488011..a30ab07f 100644 --- a/capa/render/default.py +++ b/capa/render/default.py @@ -179,18 +179,15 @@ def render_mbc(doc, ostream): if not rule["meta"].get("mbc"): continue - mbcs = rule["meta"]["mbc"] - if not isinstance(mbcs, list): - raise ValueError("invalid rule: MBC mapping is not a list") + for mbc in rule["meta"]["mbc"]: + id = mbc.get("id") + objective = mbc.get("objective") + behavior = mbc.get("behavior") + method = mbc.get("method") - for mbc in mbcs: - objective, _, rest = mbc.partition("::") - if "::" in rest: - behavior, _, rest = rest.partition("::") - method, _, id = rest.rpartition(" ") + if method: objectives[objective].add((behavior, method, id)) else: - behavior, _, id = rest.rpartition(" ") objectives[objective].add((behavior, id)) rows = [] @@ -199,10 +196,10 @@ def render_mbc(doc, ostream): for spec in sorted(behaviors): if len(spec) == 2: behavior, id = spec - inner_rows.append("%s %s" % (rutils.bold(behavior), id)) + inner_rows.append("%s [%s]" % (rutils.bold(behavior), id)) elif len(spec) == 3: behavior, method, id = spec - inner_rows.append("%s::%s %s" % (rutils.bold(behavior), method, id)) + inner_rows.append("%s::%s [%s]" % (rutils.bold(behavior), method, id)) else: raise RuntimeError("unexpected MBC spec format") rows.append( diff --git a/capa/rules.py b/capa/rules.py index 493b2f07..53743710 100644 --- a/capa/rules.py +++ b/capa/rules.py @@ -281,6 +281,42 @@ def parse_description(s, value_type, description=None): return value, description +def parse_meta(meta): + """ + further process meta items such as MBC + """ + mbcs = meta.get("mbc", []) + if not isinstance(mbcs, list): + raise InvalidRule("MBC mapping must be a list") + + if mbcs: + meta["mbc"] = [parse_canonical_mbc(mbc) for mbc in mbcs] + + # TODO att&ck + + return meta + + +def parse_canonical_mbc(mbc): + """ + parse capa's canonical MBC representation: `OBJECTIVE::Behavior::Method [Identifier]` + """ + objective, _, rest = mbc.partition("::") + if "::" in rest: + behavior, _, rest = rest.partition("::") + method, _, id = rest.rpartition(" ") + else: + behavior, _, id = rest.rpartition(" ") + method = "" + return { + "id": id.lstrip("[").rstrip("]"), + "objective": objective, + "behavior": behavior, + "method": method, + # TODO "micro-behavior": "", + } + + def pop_statement_description_entry(d): """ extracts the description for statements and removes the description entry from the document @@ -577,7 +613,9 @@ class Rule(object): if scope not in SUPPORTED_FEATURES.keys(): raise InvalidRule("{:s} is not a supported scope".format(scope)) - return cls(name, scope, build_statements(statements[0], scope), d["rule"]["meta"], definition) + meta = parse_meta(d["rule"]["meta"]) + + return cls(name, scope, build_statements(statements[0], scope), meta, definition) @staticmethod @lru_cache()