From d5ae2ffd9148c41be71b9c4246e387a4c369d593 Mon Sep 17 00:00:00 2001 From: Yacine Elhamer Date: Fri, 20 Oct 2023 10:15:20 +0200 Subject: [PATCH] capa.capabilities: move `has_file_limitations()` from capa.main to the capabilities module --- capa/capabilities/common.py | 28 +++++++++++++++++++++++- capa/ghidra/capa_ghidra.py | 4 ++-- capa/ida/plugin/form.py | 2 +- capa/main.py | 28 +----------------------- scripts/show-capabilities-by-function.py | 2 +- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/capa/capabilities/common.py b/capa/capabilities/common.py index 6098f789..0563b538 100644 --- a/capa/capabilities/common.py +++ b/capa/capabilities/common.py @@ -11,7 +11,7 @@ import itertools import collections from typing import Any, Tuple -from capa.rules import Scope, RuleSet +from capa.rules import Rule, Scope, RuleSet from capa.engine import FeatureSet, MatchResults from capa.features.address import NO_ADDRESS from capa.features.extractors.base_extractor import FeatureExtractor, StaticFeatureExtractor, DynamicFeatureExtractor @@ -40,6 +40,32 @@ def find_file_capabilities(ruleset: RuleSet, extractor: FeatureExtractor, functi return matches, len(file_features) +def is_file_limitation_rule(rule: Rule) -> bool: + return rule.meta.get("namespace", "") == "internal/limitation/file" + + +def has_file_limitation(rules: RuleSet, capabilities: MatchResults, is_standalone=True) -> bool: + file_limitation_rules = list(filter(is_file_limitation_rule, rules.rules.values())) + + for file_limitation_rule in file_limitation_rules: + if file_limitation_rule.name not in capabilities: + continue + + logger.warning("-" * 80) + for line in file_limitation_rule.meta.get("description", "").split("\n"): + logger.warning(" %s", line) + logger.warning(" Identified via rule: %s", file_limitation_rule.name) + if is_standalone: + logger.warning(" ") + logger.warning(" Use -v or -vv if you really want to see the capabilities identified by capa.") + logger.warning("-" * 80) + + # bail on first file limitation + return True + + return False + + def find_capabilities( ruleset: RuleSet, extractor: FeatureExtractor, disable_progress=None, **kwargs ) -> Tuple[MatchResults, Any]: diff --git a/capa/ghidra/capa_ghidra.py b/capa/ghidra/capa_ghidra.py index 72eae7cf..70b98df5 100644 --- a/capa/ghidra/capa_ghidra.py +++ b/capa/ghidra/capa_ghidra.py @@ -80,7 +80,7 @@ def run_headless(): meta.analysis.library_functions = counts["library_functions"] meta.analysis.layout = capa.main.compute_layout(rules, extractor, capabilities) - if capa.main.has_file_limitation(rules, capabilities, is_standalone=True): + if capa.capabilities.common.has_file_limitation(rules, capabilities, is_standalone=True): logger.info("capa encountered warnings during analysis") if args.json: @@ -130,7 +130,7 @@ def run_ui(): meta.analysis.library_functions = counts["library_functions"] meta.analysis.layout = capa.main.compute_layout(rules, extractor, capabilities) - if capa.main.has_file_limitation(rules, capabilities, is_standalone=False): + if capa.capabilities.common.has_file_limitation(rules, capabilities, is_standalone=False): logger.info("capa encountered warnings during analysis") if verbose == "vverbose": diff --git a/capa/ida/plugin/form.py b/capa/ida/plugin/form.py index f0a4e13e..4e1bd572 100644 --- a/capa/ida/plugin/form.py +++ b/capa/ida/plugin/form.py @@ -811,7 +811,7 @@ class CapaExplorerForm(idaapi.PluginForm): capa.ida.helpers.inform_user_ida_ui("capa encountered file type warnings during analysis") - if capa.main.has_file_limitation(ruleset, capabilities, is_standalone=False): + if capa.capabilities.common.has_file_limitation(ruleset, capabilities, is_standalone=False): capa.ida.helpers.inform_user_ida_ui("capa encountered file limitation warnings during analysis") except Exception as e: logger.exception("Failed to check for file limitations (error: %s)", e) diff --git a/capa/main.py b/capa/main.py index 8a6a398a..54052433 100644 --- a/capa/main.py +++ b/capa/main.py @@ -84,7 +84,7 @@ from capa.features.common import ( FORMAT_RESULT, ) from capa.features.address import Address -from capa.capabilities.common import find_capabilities, find_file_capabilities +from capa.capabilities.common import find_capabilities, has_file_limitation, find_file_capabilities from capa.features.extractors.base_extractor import ( SampleHashes, FeatureExtractor, @@ -144,32 +144,6 @@ def is_internal_rule(rule: Rule) -> bool: return rule.meta.get("namespace", "").startswith("internal/") -def is_file_limitation_rule(rule: Rule) -> bool: - return rule.meta.get("namespace", "") == "internal/limitation/file" - - -def has_file_limitation(rules: RuleSet, capabilities: MatchResults, is_standalone=True) -> bool: - file_limitation_rules = list(filter(is_file_limitation_rule, rules.rules.values())) - - for file_limitation_rule in file_limitation_rules: - if file_limitation_rule.name not in capabilities: - continue - - logger.warning("-" * 80) - for line in file_limitation_rule.meta.get("description", "").split("\n"): - logger.warning(" %s", line) - logger.warning(" Identified via rule: %s", file_limitation_rule.name) - if is_standalone: - logger.warning(" ") - logger.warning(" Use -v or -vv if you really want to see the capabilities identified by capa.") - logger.warning("-" * 80) - - # bail on first file limitation - return True - - return False - - def is_supported_format(sample: Path) -> bool: """ Return if this is a supported file based on magic header values diff --git a/scripts/show-capabilities-by-function.py b/scripts/show-capabilities-by-function.py index e987b680..421c6c7e 100644 --- a/scripts/show-capabilities-by-function.py +++ b/scripts/show-capabilities-by-function.py @@ -192,7 +192,7 @@ def main(argv=None): meta = capa.main.collect_metadata(argv, args.sample, format_, args.os, args.rules, extractor, counts) meta.analysis.layout = capa.main.compute_layout(rules, extractor, capabilities) - if capa.main.has_file_limitation(rules, capabilities): + if capa.capabilities.common.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):