diff --git a/capa/ida/ida_capa_explorer.py b/capa/ida/ida_capa_explorer.py index 970e8a62..3230668c 100644 --- a/capa/ida/ida_capa_explorer.py +++ b/capa/ida/ida_capa_explorer.py @@ -339,7 +339,13 @@ class CapaExplorerForm(idaapi.PluginForm): rules_path = os.path.join(os.path.dirname(self.file_loc), "../..", "rules") rules = capa.main.get_rules(rules_path) rules = capa.rules.RuleSet(rules) - capabilities = capa.main.find_capabilities(rules, capa.features.extractors.ida.IdaFeatureExtractor(), True) + + meta = capa.ida.helpers.collect_metadata() + + capabilities, counts = capa.main.find_capabilities( + rules, capa.features.extractors.ida.IdaFeatureExtractor(), True + ) + meta["analysis"].update(counts) # support binary files specifically for x86/AMD64 shellcode # warn user binary file is loaded but still allow capa to process it @@ -364,7 +370,6 @@ class CapaExplorerForm(idaapi.PluginForm): logger.info("analysis completed.") - meta = capa.ida.helpers.collect_metadata() doc = capa.render.convert_capabilities_to_result_document(meta, rules, capabilities) self.model_data.render_capa_doc(doc) diff --git a/capa/main.py b/capa/main.py index 3c790f74..503f5f09 100644 --- a/capa/main.py +++ b/capa/main.py @@ -68,7 +68,7 @@ def find_function_capabilities(ruleset, extractor, f): function_features[capa.features.MatchedRule(rule_name)].add(va) _, function_matches = capa.engine.match(ruleset.function_rules, function_features, oint(f)) - return function_matches, bb_matches + return function_matches, bb_matches, len(function_features) def find_file_capabilities(ruleset, extractor, function_features): @@ -84,20 +84,25 @@ def find_file_capabilities(ruleset, extractor, function_features): if feature not in file_features: file_features[feature] = set() - logger.info("analyzed file and extracted %d features", len(file_features)) + logger.debug("analyzed file and extracted %d features", len(file_features)) file_features.update(function_features) _, matches = capa.engine.match(ruleset.file_rules, file_features, 0x0) - return matches + return matches, len(file_features) def find_capabilities(ruleset, extractor, disable_progress=None): all_function_matches = collections.defaultdict(list) all_bb_matches = collections.defaultdict(list) + meta = {"feature_counts": {"file": 0, "functions": {},}} + for f in tqdm.tqdm(extractor.get_functions(), disable=disable_progress, unit=" functions"): - function_matches, bb_matches = find_function_capabilities(ruleset, extractor, f) + function_matches, bb_matches, feature_count = find_function_capabilities(ruleset, extractor, f) + meta["feature_counts"]["functions"][f.__int__()] = feature_count + logger.debug("analyzed function 0x%x and extracted %d features", f.__int__(), feature_count) + for rule_name, res in function_matches.items(): all_function_matches[rule_name].extend(res) for rule_name, res in bb_matches.items(): @@ -110,14 +115,15 @@ def find_capabilities(ruleset, extractor, disable_progress=None): for rule_name, results in all_function_matches.items() } - all_file_matches = find_file_capabilities(ruleset, extractor, function_features) + all_file_matches, feature_count = find_file_capabilities(ruleset, extractor, function_features) + meta["feature_counts"]["file"] = feature_count matches = {} matches.update(all_bb_matches) matches.update(all_function_matches) matches.update(all_file_matches) - return matches + return matches, meta def has_rule_with_namespace(rules, capabilities, rule_cat): @@ -301,7 +307,10 @@ def get_rules(rule_path): for root, dirs, files in os.walk(rule_path): for file in files: if not file.endswith(".yml"): - logger.warning("skipping non-.yml file: %s", file) + if not (file.endswith(".md") or file.endswith(".git")): + # expect to see readme.md, format.md, and maybe a .git directory + # other things maybe are rules, but are mis-named. + logger.warning("skipping non-.yml file: %s", file) continue rule_path = os.path.join(root, file) @@ -485,7 +494,8 @@ def main(argv=None): meta = collect_metadata(argv, args.sample, format, extractor) - capabilities = find_capabilities(rules, extractor) + capabilities, counts = find_capabilities(rules, extractor) + meta["analysis"].update(counts) if has_file_limitation(rules, capabilities): # bail if capa encountered file limitation e.g. a packed binary @@ -542,12 +552,14 @@ def ida_main(): rules = get_rules(rules_path) rules = capa.rules.RuleSet(rules) - capabilities = find_capabilities(rules, capa.features.extractors.ida.IdaFeatureExtractor()) + meta = capa.ida.helpers.collect_metadata() + + capabilities, counts = find_capabilities(rules, capa.features.extractors.ida.IdaFeatureExtractor()) + meta["analysis"].update(counts) if has_file_limitation(rules, capabilities, is_standalone=False): capa.ida.helpers.inform_user_ida_ui("capa encountered warnings during analysis") - meta = capa.ida.helpers.collect_metadata() print(capa.render.render_default(meta, rules, capabilities)) diff --git a/capa/render/default.py b/capa/render/default.py index d7971124..6bdef70a 100644 --- a/capa/render/default.py +++ b/capa/render/default.py @@ -17,11 +17,10 @@ def width(s, character_count): def render_meta(doc, ostream): - rows = [] - rows.append((width("md5", 22), width(doc["meta"]["sample"]["md5"], 82))) - rows.append(("path", doc["meta"]["sample"]["path"])) - rows.append(("timestamp", doc["meta"]["timestamp"])) - rows.append(("capa version", doc["meta"]["version"])) + rows = [ + (width("md5", 22), width(doc["meta"]["sample"]["md5"], 82)), + ("path", doc["meta"]["sample"]["path"]), + ] ostream.write(tabulate.tabulate(rows, tablefmt="psql")) ostream.write("\n") @@ -48,7 +47,7 @@ def render_capabilities(doc, ostream): capability = "%s (%d matches)" % (rutils.bold(rule["meta"]["name"]), count) rows.append((capability, rule["meta"]["namespace"])) - ostream.write(tabulate.tabulate(rows, headers=[width("CAPABILITY", 40), width("NAMESPACE", 40)], tablefmt="psql")) + ostream.write(tabulate.tabulate(rows, headers=[width("CAPABILITY", 50), width("NAMESPACE", 50)], tablefmt="psql")) ostream.write("\n") diff --git a/capa/render/verbose.py b/capa/render/verbose.py index c2576155..4b096206 100644 --- a/capa/render/verbose.py +++ b/capa/render/verbose.py @@ -20,22 +20,53 @@ import capa.rules import capa.render.utils as rutils -def render_verbose(doc): - ostream = rutils.StringIO() +def render_meta(ostream, doc): + """ + like: - rows = [] - rows.append(("md5", doc["meta"]["sample"]["md5"])) - rows.append(("sha1", doc["meta"]["sample"]["sha1"])) - rows.append(("sha256", doc["meta"]["sample"]["sha256"])) - rows.append(("path", doc["meta"]["sample"]["path"])) - rows.append(("timestamp", doc["meta"]["timestamp"])) - rows.append(("capa version", doc["meta"]["version"])) - rows.append(("format", doc["meta"]["analysis"]["format"])) - rows.append(("extractor", doc["meta"]["analysis"]["extractor"])) - rows.append(("base address", hex(doc["meta"]["analysis"]["base_address"]))) + md5 84882c9d43e23d63b82004fae74ebb61 + sha1 c6fb3b50d946bec6f391aefa4e54478cf8607211 + sha256 5eced7367ed63354b4ed5c556e2363514293f614c2c2eb187273381b2ef5f0f9 + path /tmp/suspicious.dll_ + timestamp 2020-07-03T10:17:05.796933 + capa version 0.0.0 + format auto + extractor VivisectFeatureExtractor + base address 0x10000000 + function count 42 + total feature count 1918 + """ + rows = [ + ("md5", doc["meta"]["sample"]["md5"]), + ("sha1", doc["meta"]["sample"]["sha1"]), + ("sha256", doc["meta"]["sample"]["sha256"]), + ("path", doc["meta"]["sample"]["path"]), + ("timestamp", doc["meta"]["timestamp"]), + ("capa version", doc["meta"]["version"]), + ("format", doc["meta"]["analysis"]["format"]), + ("extractor", doc["meta"]["analysis"]["extractor"]), + ("base address", hex(doc["meta"]["analysis"]["base_address"])), + ("function count", len(doc["meta"]["analysis"]["feature_counts"]["functions"])), + ( + "total feature count", + doc["meta"]["analysis"]["feature_counts"]["file"] + + sum(doc["meta"]["analysis"]["feature_counts"]["functions"].values()), + ), + ] ostream.writeln(tabulate.tabulate(rows, tablefmt="plain")) - ostream.write("\n") + +def render_rules(ostream, doc): + """ + like: + + receive data (2 matches) + namespace communication + description all known techniques for receiving data from a potential C2 server + scope function + matches 0x10003A13 + 0x10003797 + """ for rule in rutils.capability_rules(doc): count = len(rule["matches"]) if count == 1: @@ -62,4 +93,14 @@ def render_verbose(doc): ostream.writeln(tabulate.tabulate(rows, tablefmt="plain")) ostream.write("\n") + +def render_verbose(doc): + ostream = rutils.StringIO() + + render_meta(ostream, doc) + ostream.write("\n") + + render_rules(ostream, doc) + ostream.write("\n") + return ostream.getvalue() diff --git a/capa/render/vverbose.py b/capa/render/vverbose.py index 342a9dd4..df204745 100644 --- a/capa/render/vverbose.py +++ b/capa/render/vverbose.py @@ -1,7 +1,10 @@ +import collections + import tabulate import capa.rules import capa.render.utils as rutils +import capa.render.verbose def render_locations(ostream, match): @@ -138,22 +141,23 @@ def render_match(ostream, match, indent=0, mode=MODE_SUCCESS): render_match(ostream, child, indent=indent + 1, mode=child_mode) -def render_vverbose(doc): - ostream = rutils.StringIO() - - rows = [] - rows.append(("md5", doc["meta"]["sample"]["md5"])) - rows.append(("sha1", doc["meta"]["sample"]["sha1"])) - rows.append(("sha256", doc["meta"]["sample"]["sha256"])) - rows.append(("path", doc["meta"]["sample"]["path"])) - rows.append(("timestamp", doc["meta"]["timestamp"])) - rows.append(("capa version", doc["meta"]["version"])) - rows.append(("format", doc["meta"]["analysis"]["format"])) - rows.append(("extractor", doc["meta"]["analysis"]["extractor"])) - rows.append(("base address", hex(doc["meta"]["analysis"]["base_address"]))) - ostream.writeln(tabulate.tabulate(rows, tablefmt="plain")) - ostream.write("\n") +def render_rules(ostream, doc): + """ + like: + ## rules + check for OutputDebugString error + namespace anti-analysis/anti-debugging/debugger-detection + author michael.hunhoff@fireeye.com + scope function + mbc Anti-Behavioral Analysis::Detect Debugger::OutputDebugString + examples Practical Malware Analysis Lab 16-02.exe_:0x401020 + function @ 0x10004706 + and: + api: kernel32.SetLastError @ 0x100047C2 + api: kernel32.GetLastError @ 0x10004A87 + api: kernel32.OutputDebugString @ 0x10004767, 0x10004787, 0x10004816, 0x10004895 + """ for rule in rutils.capability_rules(doc): count = len(rule["matches"]) if count == 1: @@ -192,7 +196,16 @@ def render_vverbose(doc): ostream.write(" @ ") ostream.writeln(rutils.hex(location)) render_match(ostream, match, indent=1) - ostream.write("\n") + +def render_vverbose(doc): + ostream = rutils.StringIO() + + capa.render.verbose.render_meta(ostream, doc) + ostream.write("\n") + + render_rules(ostream, doc) + ostream.write("\n") + return ostream.getvalue() diff --git a/scripts/lint.py b/scripts/lint.py index c0cc53df..223c3db9 100644 --- a/scripts/lint.py +++ b/scripts/lint.py @@ -180,7 +180,7 @@ class DoesntMatchExample(Lint): try: extractor = capa.main.get_extractor(path, "auto") - capabilities = capa.main.find_capabilities(ctx["rules"], extractor, disable_progress=True) + capabilities, meta = capa.main.find_capabilities(ctx["rules"], extractor, disable_progress=True) except Exception as e: logger.error("failed to extract capabilities: %s %s %s", rule.name, path, e) return True diff --git a/scripts/show-capabilities-by-function.py b/scripts/show-capabilities-by-function.py new file mode 100644 index 00000000..80b09b7b --- /dev/null +++ b/scripts/show-capabilities-by-function.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python2 +""" +show-capabilities-by-function + +Invoke capa to extract the capabilities of the given sample +and emit the results grouped by function. + +This is useful to identify "complex functions" - that is, +functions that implement a lot of different types of logic. + +Example:: + + $ python scripts/show-capabilities-by-function.py /tmp/suspicious.dll_ + function at 0x1000321A with 33 features: + - get hostname + - initialize Winsock library + function at 0x10003286 with 63 features: + - create thread + - terminate thread + function at 0x10003415 with 116 features: + - write file + - send data + - link function at runtime + - create HTTP request + - get common file path + - send HTTP request + - connect to HTTP server + function at 0x10003797 with 81 features: + - get socket status + - send data + - receive data + - create TCP socket + - send data on socket + - receive data on socket + - act as TCP client + - resolve DNS + - create UDP socket + - initialize Winsock library + - set socket configuration + - connect TCP socket + ... +""" +import os +import sys +import logging +import collections + +import argparse +import colorama + +import capa.main +import capa.rules +import capa.engine +import capa.render +import capa.features +import capa.render.utils as rutils +import capa.features.freeze +import capa.features.extractors.viv + +logger = logging.getLogger("capa.show-capabilities-by-function") + + +def render_matches_by_function(doc): + """ + like: + + function at 0x1000321a with 33 features: + - get hostname + - initialize Winsock library + function at 0x10003286 with 63 features: + - create thread + - terminate thread + function at 0x10003415 with 116 features: + - write file + - send data + - link function at runtime + - create HTTP request + - get common file path + - send HTTP request + - connect to HTTP server + """ + ostream = rutils.StringIO() + + matches_by_function = collections.defaultdict(set) + for rule in rutils.capability_rules(doc): + for va in rule["matches"].keys(): + matches_by_function[va].add(rule["meta"]["name"]) + + for va, feature_count in sorted(doc["meta"]["analysis"]["feature_counts"]["functions"].items()): + va = int(va) + if not matches_by_function.get(va, {}): + continue + ostream.writeln("function at 0x%X with %d features: " % (va, feature_count)) + for rule_name in matches_by_function[va]: + ostream.writeln(" - " + rule_name) + + ostream.write("\n") + return ostream.getvalue() + + +def main(argv=None): + if argv is None: + argv = sys.argv[1:] + + formats = [ + ("auto", "(default) detect file type automatically"), + ("pe", "Windows PE file"), + ("sc32", "32-bit shellcode"), + ("sc64", "64-bit shellcode"), + ("freeze", "features previously frozen by capa"), + ] + format_help = ", ".join(["%s: %s" % (f[0], f[1]) for f in formats]) + + parser = argparse.ArgumentParser(description="detect capabilities in programs.") + parser.add_argument("sample", type=str, help="Path to sample to analyze") + parser.add_argument( + "-r", + "--rules", + type=str, + default="(embedded rules)", + help="Path to rule file or directory, use embedded rules by default", + ) + parser.add_argument("-t", "--tag", type=str, help="Filter on rule meta field values") + parser.add_argument("-d", "--debug", action="store_true", help="Enable debugging output on STDERR") + parser.add_argument("-q", "--quiet", action="store_true", help="Disable all output but errors") + parser.add_argument( + "-f", + "--format", + choices=[f[0] for f in formats], + default="auto", + help="Select sample format, %s" % format_help, + ) + args = parser.parse_args(args=argv) + + if args.quiet: + logging.basicConfig(level=logging.ERROR) + logging.getLogger().setLevel(logging.ERROR) + elif args.debug: + logging.basicConfig(level=logging.DEBUG) + logging.getLogger().setLevel(logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + logging.getLogger().setLevel(logging.INFO) + + # disable vivisect-related logging, it's verbose and not relevant for capa users + capa.main.set_vivisect_log_level(logging.CRITICAL) + + # py2 doesn't know about cp65001, which is a variant of utf-8 on windows + # tqdm bails when trying to render the progress bar in this setup. + # because cp65001 is utf-8, we just map that codepage to the utf-8 codec. + # see #380 and: https://stackoverflow.com/a/3259271/87207 + import codecs + + codecs.register(lambda name: codecs.lookup("utf-8") if name == "cp65001" else None) + + if args.rules == "(embedded rules)": + logger.info("-" * 80) + logger.info(" Using default embedded rules.") + logger.info(" To provide your own rules, use the form `capa.exe ./path/to/rules/ /path/to/mal.exe`.") + logger.info(" You can see the current default rule set here:") + logger.info(" https://github.com/fireeye/capa-rules") + logger.info("-" * 80) + + logger.debug("detected running from source") + args.rules = os.path.join(os.path.dirname(__file__), "..", "rules") + logger.debug("default rule path (source method): %s", args.rules) + else: + logger.info("using rules path: %s", args.rules) + + try: + rules = capa.main.get_rules(args.rules) + rules = capa.rules.RuleSet(rules) + logger.info("successfully loaded %s rules", len(rules)) + if args.tag: + rules = rules.filter_rules_by_meta(args.tag) + logger.info("selected %s rules", len(rules)) + except (IOError, capa.rules.InvalidRule, capa.rules.InvalidRuleSet) as e: + logger.error("%s", str(e)) + return -1 + + with open(args.sample, "rb") as f: + taste = f.read(8) + + if (args.format == "freeze") or (args.format == "auto" and capa.features.freeze.is_freeze(taste)): + format = "freeze" + with open(args.sample, "rb") as f: + extractor = capa.features.freeze.load(f.read()) + else: + format = args.format + try: + extractor = capa.main.get_extractor(args.sample, args.format) + except capa.main.UnsupportedFormatError: + logger.error("-" * 80) + logger.error(" Input file does not appear to be a PE file.") + logger.error(" ") + logger.error( + " capa currently only supports analyzing PE files (or shellcode, when using --format sc32|sc64)." + ) + logger.error( + " If you don't know the input file type, you can try using the `file` utility to guess it." + ) + logger.error("-" * 80) + return -1 + except capa.main.UnsupportedRuntimeError: + logger.error("-" * 80) + logger.error(" Unsupported runtime or Python interpreter.") + logger.error(" ") + logger.error(" capa supports running under Python 2.7 using Vivisect for binary analysis.") + logger.error(" It can also run within IDA Pro, using either Python 2.7 or 3.5+.") + logger.error(" ") + logger.error( + " If you're seeing this message on the command line, please ensure you're running Python 2.7." + ) + logger.error("-" * 80) + return -1 + + meta = capa.main.collect_metadata(argv, args.sample, format, extractor) + capabilities, counts = capa.main.find_capabilities(rules, extractor) + meta["analysis"].update(counts) + + if capa.main.has_file_limitation(rules, capabilities): + # bail if capa encountered file limitation e.g. a packed binary + # do show the output in verbose mode, though. + if not (args.verbose or args.vverbose or args.json): + return -1 + + # colorama will detect: + # - when on Windows console, and fixup coloring, and + # - when not an interactive session, and disable coloring + # renderers should use coloring and assume it will be stripped out if necessary. + colorama.init() + doc = capa.render.convert_capabilities_to_result_document(meta, rules, capabilities) + print(render_matches_by_function(doc)) + colorama.deinit() + + logger.info("done.") + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/show-features.py b/scripts/show-features.py index e8ea988b..a6486cc8 100644 --- a/scripts/show-features.py +++ b/scripts/show-features.py @@ -1,6 +1,60 @@ #!/usr/bin/env python2 """ -show the features extracted by capa. +show-features + +Show the features that capa extracts from the given sample, +to assist with the development of rules. + +If you have a function with a capability that you'd like to detect, +you can run this tool and grep for the function/basic block/instruction addresses +to see what capa picks up. +This way, you can verify that capa successfully notices the features you'd reference. + +Example:: + + $ python scripts/show-features.py /tmp/suspicious.dll_ + ... + file: 0x10004e4d: export(__entry) + file: 0x10004706: export(Install) + file: 0x10004c2b: export(uninstallA) + file: 0x10005034: import(kernel32.GetStartupInfoA) + file: 0x10005034: import(GetStartupInfoA) + file: 0x10005048: import(kernel32.SetLastError) + file: 0x00004e10: string(Y29ubmVjdA==) + file: 0x00004e28: string(practicalmalwareanalysis.com) + file: 0x00004e68: string(serve.html) + file: 0x00004eb8: string(dW5zdXBwb3J0) + file: 0x00004ec8: string(c2xlZXA=) + func: 0x100012c2: characteristic(calls to) + func: 0x10001000: characteristic(loop) + bb : 0x10001000: basic block + insn: 0x10001000: mnemonic(push) + insn: 0x10001001: mnemonic(push) + insn: 0x10001002: mnemonic(push) + insn: 0x10001003: mnemonic(push) + insn: 0x10001004: mnemonic(push) + insn: 0x10001005: mnemonic(push) + insn: 0x10001006: mnemonic(xor) + insn: 0x10001008: number(0x1) + insn: 0x10001008: mnemonic(mov) + bb : 0x1000100a: basic block + bb : 0x1000100a: characteristic(tight loop) + insn: 0x1000100a: mnemonic(movzx) + insn: 0x1000100d: mnemonic(mov) + insn: 0x1000100f: offset(0x1000A7C8) + insn: 0x1000100f: mnemonic(mov) + insn: 0x10001015: offset(0x100075C8) + insn: 0x10001015: mnemonic(mov) + insn: 0x1000101b: mnemonic(mov) + insn: 0x1000101d: number(0x80) + insn: 0x1000101d: mnemonic(and) + insn: 0x10001020: mnemonic(neg) + insn: 0x10001022: mnemonic(sbb) + insn: 0x10001024: number(0x1B) + insn: 0x10001024: mnemonic(and) + insn: 0x10001027: number(0x1) + insn: 0x10001027: mnemonic(shl) + ... """ import sys import logging diff --git a/scripts/testbed/run_rule_on_testbed.py b/scripts/testbed/run_rule_on_testbed.py index 2e6b9ce8..aa78a830 100644 --- a/scripts/testbed/run_rule_on_testbed.py +++ b/scripts/testbed/run_rule_on_testbed.py @@ -93,7 +93,8 @@ def get_capabilities(path, rules): logger.debug("matching rules in %s", path) with open(path, "rb") as f: extractor = capa.features.freeze.load(f.read()) - return capa.main.find_capabilities(rules, extractor, disable_progress=True) + capabilities, meta = capa.main.find_capabilities(rules, extractor, disable_progress=True) + return capabilities def get_function_hits(capabilities, rule_name): diff --git a/setup.py b/setup.py index b23bc084..a01a05ca 100644 --- a/setup.py +++ b/setup.py @@ -41,13 +41,15 @@ setuptools.setup( include_package_data=True, install_requires=requirements, extras_require={ - "dev": ["pytest", - "pytest-sugar", - "pytest-instafail", - "pytest-cov", - "pycodestyle", - "black ; python_version>'3.0'", - "isort"] + "dev": [ + "pytest", + "pytest-sugar", + "pytest-instafail", + "pytest-cov", + "pycodestyle", + "black ; python_version>'3.0'", + "isort", + ] }, zip_safe=False, keywords="capa", diff --git a/tests/test_freeze.py b/tests/test_freeze.py index f81114ca..ef9fe1bd 100644 --- a/tests/test_freeze.py +++ b/tests/test_freeze.py @@ -59,7 +59,7 @@ def test_null_feature_extractor(): ), ] ) - capabilities = capa.main.find_capabilities(rules, EXTRACTOR) + capabilities, meta = capa.main.find_capabilities(rules, EXTRACTOR) assert "xor loop" in capabilities diff --git a/tests/test_main.py b/tests/test_main.py index 3010b494..c35b4b4b 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -147,7 +147,7 @@ def test_match_across_scopes_file_function(sample_9324d1a8ae37a36ae560c37448c970 extractor = capa.features.extractors.viv.VivisectFeatureExtractor( sample_9324d1a8ae37a36ae560c37448c9705a.vw, sample_9324d1a8ae37a36ae560c37448c9705a.path, ) - capabilities = capa.main.find_capabilities(rules, extractor) + capabilities, meta = capa.main.find_capabilities(rules, extractor) assert "install service" in capabilities assert ".text section" in capabilities assert ".text section and install service" in capabilities @@ -212,7 +212,7 @@ def test_match_across_scopes(sample_9324d1a8ae37a36ae560c37448c9705a): extractor = capa.features.extractors.viv.VivisectFeatureExtractor( sample_9324d1a8ae37a36ae560c37448c9705a.vw, sample_9324d1a8ae37a36ae560c37448c9705a.path ) - capabilities = capa.main.find_capabilities(rules, extractor) + capabilities, meta = capa.main.find_capabilities(rules, extractor) assert "tight loop" in capabilities assert "kill thread loop" in capabilities assert "kill thread program" in capabilities @@ -241,7 +241,7 @@ def test_subscope_bb_rules(sample_9324d1a8ae37a36ae560c37448c9705a): extractor = capa.features.extractors.viv.VivisectFeatureExtractor( sample_9324d1a8ae37a36ae560c37448c9705a.vw, sample_9324d1a8ae37a36ae560c37448c9705a.path, ) - capabilities = capa.main.find_capabilities(rules, extractor) + capabilities, meta = capa.main.find_capabilities(rules, extractor) assert "test rule" in capabilities @@ -267,7 +267,7 @@ def test_byte_matching(sample_9324d1a8ae37a36ae560c37448c9705a): extractor = capa.features.extractors.viv.VivisectFeatureExtractor( sample_9324d1a8ae37a36ae560c37448c9705a.vw, sample_9324d1a8ae37a36ae560c37448c9705a.path, ) - capabilities = capa.main.find_capabilities(rules, extractor) + capabilities, meta = capa.main.find_capabilities(rules, extractor) assert "byte match test" in capabilities @@ -294,5 +294,5 @@ def test_count_bb(sample_9324d1a8ae37a36ae560c37448c9705a): extractor = capa.features.extractors.viv.VivisectFeatureExtractor( sample_9324d1a8ae37a36ae560c37448c9705a.vw, sample_9324d1a8ae37a36ae560c37448c9705a.path, ) - capabilities = capa.main.find_capabilities(rules, extractor) + capabilities, meta = capa.main.find_capabilities(rules, extractor) assert "count bb" in capabilities