diff --git a/capa/helpers.py b/capa/helpers.py index 38f94b028..48e1e8e2f 100644 --- a/capa/helpers.py +++ b/capa/helpers.py @@ -9,6 +9,7 @@ import logging import contextlib import importlib.util +import functools from typing import NoReturn from pathlib import Path @@ -126,6 +127,83 @@ def new_print(*args, **kwargs): inspect.builtins.print = old_print # type: ignore +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD +def catch_log_return_errors(func): + error_list, return_values, message_list = [(UnsupportedFormatError, E_INVALID_FILE_TYPE, + (" Input file does not appear to be a PE or ELF file.", + " capa currently only supports analyzing PE and ELF files (or shellcode, when using --format sc32|sc64).", + " If you don't know the input file type, you can try using the `file` utility to guess it.")), + + (UnsupportedArchError, E_INVALID_FILE_ARCH, + (" Input file does not appear to target a supported architecture.", + " capa currently only supports analyzing x86 (32- and 64-bit).")), + + (UnsupportedOSError, E_INVALID_FILE_OS, + (" Input file does not appear to target a supported OS.", + " capa currently only supports analyzing executables for some operating systems (including Windows and Linux)."))] + + @functools.wraps(func) + def logging_wrapper(exception): + assert(exception in error_list) + error_messages = message_list[error_list.index(exception)] + error_return_value = return_values[error_list.index(exception)] + +======= +def exceptUnsupportedError(func): + e_list = [UnsupportedFormatError, UnsupportedArchError, UnsupportedOSError] + + messsage_list = [ # UnsupportedFormatError + (" Input file does not appear to be a PE or ELF file.", + " capa currently only supports analyzing PE and ELF files (or shellcode, when using --format sc32|sc64).", + " If you don't know the input file type, you can try using the `file` utility to guess it."), + + # UnsupportedArchError + (" Input file does not appear to target a supported architecture.", + " capa currently only supports analyzing x86 (32- and 64-bit)."), + + # UnsupportedOSError + (" Input file does not appear to target a supported OS.", + " capa currently only supports analyzing executables for some operating systems (including Windows and Linux).") + ] + + def logging_wrapper(exception): + assert(exception in e_list) + e_messages = message_list[e_list.index(exception)] + +>>>>>>> parent of a9ead120 (Update helpers.py) + logger.error("-" * 80) + logger.error(f"{error_messages[0]}") + logger.error(" ") +<<<<<<< HEAD + + for i in error_messages[1:]: + logger.error(i) + + logger.error("-" * 80) +<<<<<<< HEAD + + return error_return_value + +======= +======= + + for i in e_messages[1:]: + logger.error(i) + + logger.error("-" * 80) +>>>>>>> parent of a9ead120 (Update helpers.py) + +>>>>>>> parent of a9ead120 (Update helpers.py) + if type(func(*args, **kwargs)) = ValueError: + return logging_wrapper(func(*args, **kwargs)) + + else: + return func(*args, **kwargs) +======= +======= +>>>>>>> parent of 50b4b067 (Updated handling for logging Unsupported Format, Arch, and OS) def log_unsupported_format_error(): logger.error("-" * 80) logger.error(" Input file does not appear to be a PE or ELF file.") @@ -153,6 +231,10 @@ def log_unsupported_arch_error(): logger.error(" ") logger.error(" capa currently only supports analyzing x86 (32- and 64-bit).") logger.error("-" * 80) +<<<<<<< HEAD +>>>>>>> parent of 50b4b067 (Updated handling for logging Unsupported Format, Arch, and OS) +======= +>>>>>>> parent of 50b4b067 (Updated handling for logging Unsupported Format, Arch, and OS) def log_unsupported_runtime_error(): diff --git a/capa/main.py b/capa/main.py index ae8421560..344eeb55f 100644 --- a/capa/main.py +++ b/capa/main.py @@ -517,6 +517,119 @@ def get_workspace(path: Path, format_: str, sigpaths: List[Path]): return vw +<<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> parent of d46fa26c (Incremental PR improvements) +def check_supported_format(path, os_): + if not is_supported_format(path): + raise UnsupportedFormatError() + + if not is_supported_arch(path): + raise UnsupportedArchError() + + if os_ == OS_AUTO and not is_supported_os(path): + raise UnsupportedOSError() + + + +def add_binja_to_path(): + from capa.features.extractors.binja.find_binja_api import find_binja_path + + bn_api = find_binja_path() + if bn_api.exists(): + sys.path.append(str(bn_api)) + + + +def import_binja(): + # When we are running as a standalone executable, we cannot directly import binaryninja + # We need to fist find the binja API installation path and add it into sys.path + if is_running_standalone(): + add_binja_to_path() + + try: + import binaryninja + from binaryninja import BinaryView + except ImportError: + raise RuntimeError( + "Cannot import binaryninja module. Please install the Binary Ninja Python API first: " + + "https://docs.binary.ninja/dev/batch.html#install-the-api)." + ) + + + +def handle_binja_backend(path): + import capa.features.extractors.binja.extractor + + import_binja() + + with halo.Halo(text="analyzing program", spinner="simpleDots", stream=sys.stderr, enabled=not disable_progress): + bv: BinaryView = binaryninja.load(str(path)) + if bv is None: + raise RuntimeError(f"Binary Ninja cannot open file {path}") + + return capa.features.extractors.binja.extractor.BinjaFeatureExtractor(bv) + + + +def handle_viv_backend(path, format_, sigpaths, os_): + import capa.features.extractors.viv.extractor + + with halo.Halo(text="analyzing program", spinner="simpleDots", stream=sys.stderr, enabled=not disable_progress): + vw = get_workspace(path, format_, sigpaths) + + if should_save_workspace: + logger.debug("saving workspace") + try: + vw.saveWorkspace() + except IOError: + # see #168 for discussion around how to handle non-writable directories + logger.info("source directory is not writable, won't save intermediate workspace") + else: + logger.debug("CAPA_SAVE_WORKSPACE unset, not saving workspace") + + return capa.features.extractors.viv.extractor.VivisectFeatureExtractor(vw, path, os_) + + +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD +@catch_log_return_errors +======= +def handle_pefile_backend(path: Path) -> FeatureExtractor: +======= +def handle_pefile_backend(path: Path) -> PefileFeatureExtractor: +>>>>>>> parent of 222cd6c4 (Update main.py) +======= + +def handle_pefile_backend(path): +>>>>>>> parent of d46fa26c (Incremental PR improvements) +======= + +def handle_pefile_backend(path): +>>>>>>> parent of d46fa26c (Incremental PR improvements) + import capa.features.extractors.pefile + return capa.features.extractors.pefile.PefileFeatureExtractor(path) + + +def handle_dotnet_format(format): + import capa.features.extractors.dnfile.extractor + return capa.features.extractors.dnfile.extractor.DnfileFeatureExtractor(path) + + +<<<<<<< HEAD +<<<<<<< HEAD +>>>>>>> parent of 0aab7206 (Update main.py) +======= + +>>>>>>> parent of d46fa26c (Incremental PR improvements) +======= +>>>>>>> parent of d061e0c5 (Update main.py) +======= + +>>>>>>> parent of d46fa26c (Incremental PR improvements) def get_extractor( path: Path, format_: str, @@ -543,6 +656,7 @@ def get_extractor( raise UnsupportedOSError() if format_ == FORMAT_DOTNET: +<<<<<<< HEAD import capa.features.extractors.dnfile.extractor return capa.features.extractors.dnfile.extractor.DnfileFeatureExtractor(path) @@ -574,6 +688,12 @@ def get_extractor( raise RuntimeError(f"Binary Ninja cannot open file {path}") return capa.features.extractors.binja.extractor.BinjaFeatureExtractor(bv) +======= + return handle_dotnet_format(format) + + elif backend == BACKEND_BINJA: + return handle_binja_backend(path) +>>>>>>> parent of d46fa26c (Incremental PR improvements) elif backend == BACKEND_PEFILE: import capa.features.extractors.pefile @@ -581,6 +701,7 @@ def get_extractor( return capa.features.extractors.pefile.PefileFeatureExtractor(path) elif backend == BACKEND_VIV: +<<<<<<< HEAD import capa.features.extractors.viv.extractor with halo.Halo(text="analyzing program", spinner="simpleDots", stream=sys.stderr, enabled=not disable_progress): @@ -597,11 +718,15 @@ def get_extractor( logger.debug("CAPA_SAVE_WORKSPACE unset, not saving workspace") return capa.features.extractors.viv.extractor.VivisectFeatureExtractor(vw, path, os_) +======= + return handle_viv_backend(path, format, sigpaths, os_) +>>>>>>> parent of d46fa26c (Incremental PR improvements) else: raise ValueError("unexpected backend: " + backend) + def get_file_extractors(sample: Path, format_: str) -> List[FeatureExtractor]: file_extractors: List[FeatureExtractor] = [] @@ -1257,6 +1382,28 @@ def main(argv: Optional[List[str]] = None): should_save_workspace = os.environ.get("CAPA_SAVE_WORKSPACE") not in ("0", "no", "NO", "n", None) +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD + + # Error checking and logging is performed in the get_extractor call + extractor = get_extractor( + args.sample, + format_, + args.os, + args.backend, + sig_paths, + should_save_workspace, + disable_progress=args.quiet or args.debug, + ) +======= +======= +>>>>>>> parent of bc616d07 (Update main.py) +======= +>>>>>>> parent of bc616d07 (Update main.py) +======= +>>>>>>> parent of bc616d07 (Update main.py) try: extractor = get_extractor( args.sample, @@ -1276,6 +1423,16 @@ def main(argv: Optional[List[str]] = None): except UnsupportedOSError: log_unsupported_os_error() return E_INVALID_FILE_OS +<<<<<<< HEAD +<<<<<<< HEAD +<<<<<<< HEAD +>>>>>>> parent of bc616d07 (Update main.py) +======= +>>>>>>> parent of bc616d07 (Update main.py) +======= +>>>>>>> parent of bc616d07 (Update main.py) +======= +>>>>>>> parent of bc616d07 (Update main.py) meta = collect_metadata(argv, args.sample, args.format, args.os, args.rules, extractor)