def main(argv=None): if argv is None: argv = sys.argv[1:] parser = argparse.ArgumentParser( description="detect capabilities in programs.") capa.main.install_common_args( parser, wanted={"format", "backend", "sample", "signatures", "rules", "tag"}) args = parser.parse_args(args=argv) capa.main.handle_common_args(args) try: taste = get_file_taste(args.sample) except IOError as e: logger.error("%s", str(e)) return -1 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 try: sig_paths = capa.main.get_signatures(args.signatures) except (IOError) as e: logger.error("%s", str(e)) return -1 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 should_save_workspace = os.environ.get("CAPA_SAVE_WORKSPACE") not in ( "0", "no", "NO", "n", None) try: extractor = capa.main.get_extractor(args.sample, args.format, args.backend, sig_paths, should_save_workspace) except capa.exceptions.UnsupportedFormatError: capa.helpers.log_unsupported_format_error() return -1 except capa.exceptions.UnsupportedRuntimeError: capa.helpers.log_unsupported_runtime_error() return -1 meta = capa.main.collect_metadata(argv, args.sample, args.rules, extractor) capabilities, counts = capa.main.find_capabilities(rules, extractor) meta["analysis"].update(counts) meta["analysis"]["layout"] = capa.main.compute_layout( rules, extractor, capabilities) 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.result_document.convert_capabilities_to_result_document( meta, rules, capabilities) print(render_matches_by_function(doc)) colorama.deinit() return 0
def main(argv=None): if argv is None: argv = sys.argv[1:] parser = argparse.ArgumentParser( description="detect capabilities in programs.") capa.main.install_common_args(parser, wanted={"format", "sample", "rules", "tag"}) args = parser.parse_args(args=argv) capa.main.handle_common_args(args) try: taste = get_file_taste(args.sample) except IOError as e: logger.error("%s", str(e)) return -1 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 -r ./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 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, args.rules, 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
def main(argv=None): if sys.version_info < (3, 7): raise UnsupportedRuntimeError("This version of capa can only be used with Python 3.7+") if argv is None: argv = sys.argv[1:] desc = "The FLARE team's open-source tool to identify capabilities in executable files." epilog = textwrap.dedent( """ By default, capa uses a default set of embedded rules. You can see the rule set here: https://github.com/mandiant/capa-rules To provide your own rule set, use the `-r` flag: capa --rules /path/to/rules suspicious.exe capa -r /path/to/rules suspicious.exe examples: identify capabilities in a binary capa suspicious.exe identify capabilities in 32-bit shellcode, see `-f` for all supported formats capa -f sc32 shellcode.bin report match locations capa -v suspicious.exe report all feature match details capa -vv suspicious.exe filter rules by meta fields, e.g. rule name or namespace capa -t "create TCP socket" suspicious.exe """ ) parser = argparse.ArgumentParser( description=desc, epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter ) install_common_args(parser, {"sample", "format", "backend", "signatures", "rules", "tag"}) parser.add_argument("-j", "--json", action="store_true", help="emit JSON instead of text") args = parser.parse_args(args=argv) ret = handle_common_args(args) if ret is not None and ret != 0: return ret try: _ = get_file_taste(args.sample) except IOError as e: # per our research there's not a programmatic way to render the IOError with non-ASCII filename unless we # handle the IOError separately and reach into the args logger.error("%s", e.args[0]) return E_MISSING_FILE format_ = args.format if format_ == FORMAT_AUTO: try: format_ = get_auto_format(args.sample) except UnsupportedFormatError: log_unsupported_format_error() return E_INVALID_FILE_TYPE try: rules = get_rules(args.rules, disable_progress=args.quiet) rules = capa.rules.RuleSet(rules) logger.debug( "successfully loaded %s rules", # during the load of the RuleSet, we extract subscope statements into their own rules # that are subsequently `match`ed upon. this inflates the total rule count. # so, filter out the subscope rules when reporting total number of loaded rules. len([i for i in filter(lambda r: "capa/subscope-rule" not in r.meta, rules.rules.values())]), ) if args.tag: rules = rules.filter_rules_by_meta(args.tag) logger.debug("selected %d rules", len(rules)) for i, r in enumerate(rules.rules, 1): # TODO don't display subscope rules? logger.debug(" %d. %s", i, r) except (IOError, capa.rules.InvalidRule, capa.rules.InvalidRuleSet) as e: logger.error("%s", str(e)) return E_INVALID_RULE # file feature extractors are pretty lightweight: they don't do any code analysis. # so we can fairly quickly determine if the given file has "pure" file-scope rules # that indicate a limitation (like "file is packed based on section names") # and avoid doing a full code analysis on difficult/impossible binaries. # # this pass can inspect multiple file extractors, e.g., dotnet and pe to identify # various limitations try: file_extractors = get_file_extractors(args.sample, format_) except PEFormatError as e: logger.error("Input file '%s' is not a valid PE file: %s", args.sample, str(e)) return E_CORRUPT_FILE except (ELFError, OverflowError) as e: logger.error("Input file '%s' is not a valid ELF file: %s", args.sample, str(e)) return E_CORRUPT_FILE for file_extractor in file_extractors: try: pure_file_capabilities, _ = find_file_capabilities(rules, file_extractor, {}) except PEFormatError as e: logger.error("Input file '%s' is not a valid PE file: %s", args.sample, str(e)) return E_CORRUPT_FILE except (ELFError, OverflowError) as e: logger.error("Input file '%s' is not a valid ELF file: %s", args.sample, str(e)) return E_CORRUPT_FILE # file limitations that rely on non-file scope won't be detected here. # nor on FunctionName features, because pefile doesn't support this. if has_file_limitation(rules, pure_file_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): logger.debug("file limitation short circuit, won't analyze fully.") return E_FILE_LIMITATION if isinstance(file_extractor, capa.features.extractors.dotnetfile.DotnetFileFeatureExtractor): format_ = FORMAT_DOTNET if format_ == FORMAT_FREEZE: with open(args.sample, "rb") as f: extractor = capa.features.freeze.load(f.read()) else: try: if format_ == FORMAT_PE: sig_paths = get_signatures(args.signatures) else: sig_paths = [] logger.debug("skipping library code matching: only have native PE signatures") except IOError as e: logger.error("%s", str(e)) return E_INVALID_SIG should_save_workspace = os.environ.get("CAPA_SAVE_WORKSPACE") not in ("0", "no", "NO", "n", None) try: extractor = get_extractor( args.sample, format_, args.backend, sig_paths, should_save_workspace, disable_progress=args.quiet ) except UnsupportedFormatError: log_unsupported_format_error() return E_INVALID_FILE_TYPE except UnsupportedArchError: log_unsupported_arch_error() return E_INVALID_FILE_ARCH except UnsupportedOSError: log_unsupported_os_error() return E_INVALID_FILE_OS meta = collect_metadata(argv, args.sample, args.rules, extractor) capabilities, counts = find_capabilities(rules, extractor, disable_progress=args.quiet) meta["analysis"].update(counts) meta["analysis"]["layout"] = compute_layout(rules, extractor, capabilities) if 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 E_FILE_LIMITATION if args.json: print(capa.render.json.render(meta, rules, capabilities)) elif args.vverbose: print(capa.render.vverbose.render(meta, rules, capabilities)) elif args.verbose: print(capa.render.verbose.render(meta, rules, capabilities)) else: print(capa.render.default.render(meta, rules, capabilities)) colorama.deinit() logger.debug("done.") return 0