diff --git a/test/plugins/test_yarascan_options.py b/test/plugins/test_yarascan_options.py new file mode 100644 index 0000000000..a955d207f9 --- /dev/null +++ b/test/plugins/test_yarascan_options.py @@ -0,0 +1,64 @@ +import logging +import pathlib + +import pytest + +import volatility3.plugins # noqa: F401 - registers the plugins namespace + +yarascan = pytest.importorskip("volatility3.plugins.yarascan") + + +@pytest.fixture +def rule_file(tmp_path): + path = tmp_path / "rules.yar" + path.write_text('rule t { strings: $a = "abc" condition: $a }') + return pathlib.Path(path).as_uri() + + +def test_yara_string_builds_rules(): + rules = yarascan.YaraScan.process_yara_options({"yara_string": "abc"}) + assert rules is not None + + +def test_no_rule_source_returns_none(caplog): + with caplog.at_level(logging.ERROR): + rules = yarascan.YaraScan.process_yara_options({}) + assert rules is None + assert "No yara rules" in caplog.text + + +def test_yara_file_builds_rules(rule_file): + rules = yarascan.YaraScan.process_yara_options({"yara_file": rule_file}) + assert rules is not None + + +def test_multiple_sources_warns_and_prefers_string(caplog, rule_file): + with caplog.at_level(logging.WARNING): + rules = yarascan.YaraScan.process_yara_options( + {"yara_string": "abc", "yara_file": rule_file} + ) + assert rules is not None + assert "Multiple yara rule sources" in caplog.text + + +def test_string_options_ignored_for_file_warns(caplog, rule_file): + with caplog.at_level(logging.WARNING): + rules = yarascan.YaraScan.process_yara_options( + {"yara_file": rule_file, "insensitive": True, "wide": True} + ) + assert rules is not None + assert "insensitive, wide" in caplog.text + + +def test_file_without_string_options_does_not_warn(caplog, rule_file): + with caplog.at_level(logging.WARNING): + yarascan.YaraScan.process_yara_options({"yara_file": rule_file}) + assert "only apply to yara_string" not in caplog.text + + +def test_string_with_options_does_not_warn(caplog): + with caplog.at_level(logging.WARNING): + yarascan.YaraScan.process_yara_options( + {"yara_string": "abc", "insensitive": True, "wide": True} + ) + assert "only apply to yara_string" not in caplog.text diff --git a/volatility3/framework/plugins/yarascan.py b/volatility3/framework/plugins/yarascan.py index 910ded1091..09e10c6d3c 100644 --- a/volatility3/framework/plugins/yarascan.py +++ b/volatility3/framework/plugins/yarascan.py @@ -175,9 +175,32 @@ def get_yarascan_option_requirements( def yara_returns_instances(cls) -> bool: return not tuple(int(x) for x in yara.__version__.split(".")) < (4, 3) + @classmethod + def _warn_ignored_string_options(cls, config: Dict[str, Any]) -> None: + """Warns when options that only affect yara_string rules are set at the + same time as a rules file, where they have no effect.""" + ignored = [name for name in ("insensitive", "wide") if config.get(name, False)] + if ignored: + vollog.warning( + f"These options only apply to yara_string rules and are ignored when a rules file is supplied: {', '.join(ignored)}" + ) + @classmethod def process_yara_options(cls, config: Dict[str, Any]): rules = None + + # Only one rule source is ever used, so warn rather than silently pick one + # when the user supplies more than one. + rule_sources = [ + name + for name in ("yara_string", "yara_file", "yara_compiled_file") + if config.get(name) is not None + ] + if len(rule_sources) > 1: + vollog.warning( + f"Multiple yara rule sources were supplied ({', '.join(rule_sources)}); only {rule_sources[0]} will be used" + ) + if config.get("yara_string") is not None: rule = config["yara_string"] if rule[0] not in ["{", "/"]: @@ -188,9 +211,11 @@ def process_yara_options(cls, config: Dict[str, Any]): rule += " wide ascii" rules = YaraScanner.get_rule(rule) elif config.get("yara_file") is not None: + cls._warn_ignored_string_options(config) vollog.debug(f"Plain file: {config['yara_file']} - yara-x: {USE_YARA_X}") rules = YaraScanner.from_file(config["yara_file"]) elif config.get("yara_compiled_file") is not None: + cls._warn_ignored_string_options(config) vollog.debug( f"Compiled file: {config['yara_compiled_file']} - yara-x: {USE_YARA_X}" )