diff --git a/capa/features/__init__.py b/capa/features/__init__.py index db04b717..21d3dc30 100644 --- a/capa/features/__init__.py +++ b/capa/features/__init__.py @@ -15,6 +15,11 @@ def bytes_to_str(b): return codecs.encode(b, "hex") +def hex_string(h): + """ render hex string e.g. "0a40b1" as "0A 40 B1" """ + return ' '.join(h[i:i + 2] for i in range(0, len(h), 2)).upper() + + class Feature(object): def __init__(self, args, description=None): super(Feature, self).__init__() @@ -104,7 +109,7 @@ class Bytes(Feature): return capa.engine.Result(False, self, []) def get_args_str(self): - return bytes_to_str(self.value).upper() + return hex_string(bytes_to_str(self.value)) def freeze_serialize(self): return (self.__class__.__name__, [bytes_to_str(x).upper() for x in self.args]) diff --git a/capa/render/utils.py b/capa/render/utils.py index 91d88293..794f9904 100644 --- a/capa/render/utils.py +++ b/capa/render/utils.py @@ -17,11 +17,6 @@ def hex(n): return "0x%X" % n -def hex_string(h): - """ render hex string e.g. "0a40b1" as "0A 40 B1" """ - return " ".join(h[i : i + 2] for i in range(0, len(h), 2)).upper() - - def capability_rules(doc): """enumerate the rules in (namespace, name) order that are 'capability' rules (not lib/subscope/disposition/etc).""" for (_, _, rule) in sorted( diff --git a/capa/render/vverbose.py b/capa/render/vverbose.py index c0edc234..0d8b0a93 100644 --- a/capa/render/vverbose.py +++ b/capa/render/vverbose.py @@ -40,28 +40,11 @@ def render_statement(ostream, match, statement, indent=0): # there's no additional logic in the feature part, just the existence of a feature. # so, we have to inline some of the feature rendering here. - child = statement["child"] - if child["type"] in ( - "string", - "api", - "mnemonic", - "basic block", - "export", - "import", - "section", - "match", - "characteristic", - ): - value = rutils.bold2(child[child["type"]]) - elif child["type"] in ("number", "offset"): - value = rutils.bold2(rutils.hex(child[child["type"]])) - elif child["type"] == "bytes": - value = rutils.bold2(rutils.hex_string(child[child["type"]])) - else: - raise RuntimeError("unexpected feature type: " + str(child)) + child = statement['child'] + value = rutils.bold2(child[child['type']]) - if child["description"]: - ostream.write("count(%s(%s = %s)): " % (child["type"], value, child["description"])) + if child.get('description'): + ostream.write('count(%s(%s = %s)): ' % (child['type'], value, child['description'])) else: ostream.write("count(%s(%s)): " % (child["type"], value)) @@ -89,34 +72,11 @@ def render_statement(ostream, match, statement, indent=0): def render_feature(ostream, match, feature, indent=0): - ostream.write(" " * indent) + ostream.write(' ' * indent) - if feature["type"] in ( - "string", - "api", - "mnemonic", - "basic block", - "export", - "import", - "section", - "match", - "characteristic", - ): - ostream.write(feature["type"]) - ostream.write(": ") - ostream.write(rutils.bold2(feature[feature["type"]])) - elif feature["type"] in ("number", "offset"): - ostream.write(feature["type"]) - ostream.write(": ") - ostream.write(rutils.bold2(rutils.hex(feature[feature["type"]]))) - elif feature["type"] == "bytes": - ostream.write("bytes: ") - # bytes is the uppercase, hex-encoded string. - # it should always be an even number of characters (its hex). - ostream.write(rutils.bold2(rutils.hex_string(feature[feature["type"]]))) - # note that regex is found in `render_statement` - else: - raise RuntimeError("unexpected feature type: " + str(feature)) + ostream.write(feature['type']) + ostream.write(': ') + ostream.write(rutils.bold2(feature[feature['type']])) if "description" in feature: ostream.write(" = ") diff --git a/tests/test_main.py b/tests/test_main.py index 1c60356b..c434e414 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -10,8 +10,11 @@ from capa.engine import * def test_main(sample_9324d1a8ae37a36ae560c37448c9705a): - # tests rules can be loaded successfully - assert capa.main.main([sample_9324d1a8ae37a36ae560c37448c9705a.path, "-v"]) == 0 + # tests rules can be loaded successfully and all output modes + assert capa.main.main([sample_9324d1a8ae37a36ae560c37448c9705a.path, '-vv']) == 0 + assert capa.main.main([sample_9324d1a8ae37a36ae560c37448c9705a.path, '-v']) == 0 + assert capa.main.main([sample_9324d1a8ae37a36ae560c37448c9705a.path, '-j']) == 0 + assert capa.main.main([sample_9324d1a8ae37a36ae560c37448c9705a.path]) == 0 def test_main_single_rule(sample_9324d1a8ae37a36ae560c37448c9705a, tmpdir): @@ -32,7 +35,10 @@ def test_main_single_rule(sample_9324d1a8ae37a36ae560c37448c9705a, tmpdir): def test_main_shellcode(sample_499c2a85f6e8142c3f48d4251c9c7cd6_raw32): - assert capa.main.main([sample_499c2a85f6e8142c3f48d4251c9c7cd6_raw32.path, "-v", "-f", "sc32"]) == 0 + assert capa.main.main([sample_499c2a85f6e8142c3f48d4251c9c7cd6_raw32.path, '-vv', '-f', 'sc32']) == 0 + assert capa.main.main([sample_499c2a85f6e8142c3f48d4251c9c7cd6_raw32.path, '-v', '-f', 'sc32']) == 0 + assert capa.main.main([sample_499c2a85f6e8142c3f48d4251c9c7cd6_raw32.path, '-j', '-f', 'sc32']) == 0 + assert capa.main.main([sample_499c2a85f6e8142c3f48d4251c9c7cd6_raw32.path, '-f', 'sc32']) == 0 def test_ruleset():