diff --git a/capa/render/default.py b/capa/render/default.py index 10379a93..b374883e 100644 --- a/capa/render/default.py +++ b/capa/render/default.py @@ -36,6 +36,34 @@ def render_meta(doc, ostream): ostream.write("\n") +def find_subrule_matches(doc): + """ + collect the rule names that have been matched as a subrule match. + this way we can avoid displaying entries for things that are too specific. + """ + matches = set([]) + + def rec(node): + if not node["success"]: + # there's probably a bug here for rules that do `not: match: ...` + # but we don't have any examples of this yet + return + + elif node["node"]["type"] == "statement": + for child in node["children"]: + rec(child) + + elif node["node"]["type"] == "feature": + if node["node"]["feature"]["type"] == "match": + matches.add(node["node"]["feature"]["match"]) + + for rule in rutils.capability_rules(doc): + for node in rule["matches"].values(): + rec(node) + + return matches + + def render_capabilities(doc, ostream): """ example:: @@ -48,8 +76,16 @@ def render_capabilities(doc, ostream): | ... | ... | +-------------------------------------------------------+-------------------------------------------------+ """ + subrule_matches = find_subrule_matches(doc) + rows = [] for rule in rutils.capability_rules(doc): + if rule["meta"]["name"] in subrule_matches: + # rules that are also matched by other rules should not get rendered by default. + # this cuts down on the amount of output while giving approx the same detail. + # see #224 + continue + count = len(rule["matches"]) if count == 1: capability = rutils.bold(rule["meta"]["name"]) diff --git a/tests/test_main.py b/tests/test_main.py index 6b8eaf57..7eb84883 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -320,3 +320,29 @@ def test_fix262(pma16_01_extractor, capsys): std = capsys.readouterr() assert "HTTP/1.0" in std.out assert "www.practicalmalwareanalysis.com" not in std.out + + +@pytest.mark.xfail(sys.version_info >= (3, 0), reason="vivsect only works on py2") +def test_not_render_rules_also_matched(z9324d_extractor, capsys): + # rules that are also matched by other rules should not get rendered by default. + # this cuts down on the amount of output while giving approx the same detail. + # see #224 + path = z9324d_extractor.path + + # `act as TCP client` matches on + # `connect TCP client` matches on + # `create TCP socket` + # + # so only `act as TCP client` should be displayed + assert capa.main.main([path]) == 0 + std = capsys.readouterr() + assert "act as TCP client" in std.out + assert "connect TCP socket" not in std.out + assert "create TCP socket" not in std.out + + # this strategy only applies to the default renderer, not any verbose renderer + assert capa.main.main([path, "-v"]) == 0 + std = capsys.readouterr() + assert "act as TCP client" in std.out + assert "connect TCP socket" in std.out + assert "create TCP socket" in std.out