diff --git a/volatility/framework/configuration/requirements.py b/volatility/framework/configuration/requirements.py index b2e1a9f6a..0ad101593 100644 --- a/volatility/framework/configuration/requirements.py +++ b/volatility/framework/configuration/requirements.py @@ -380,19 +380,19 @@ class SymbolTableRequirement(interfaces.configuration.ConstructableRequirementIn return context.symbol_space[value].build_configuration() -class PluginRequirement(interfaces.configuration.RequirementInterface): +class VersionRequirement(interfaces.configuration.RequirementInterface): def __init__(self, name: str, description: str = None, default: None = None, optional: bool = False, - plugin: Type[interfaces.plugins.PluginInterface] = None, + component: Type[interfaces.configuration.VersionableInterface] = None, version: Optional[Tuple[int, ...]] = None) -> None: super().__init__(name = name, description = description, default = default, optional = optional) - if plugin is None: - raise TypeError("Plugin cannot be None") - self._plugin = plugin + if component is None: + raise TypeError("Component cannot be None") + self._component = component if version is None: raise TypeError("Version cannot be None") self._version = version @@ -401,8 +401,25 @@ class PluginRequirement(interfaces.configuration.RequirementInterface): config_path: str) -> Dict[str, interfaces.configuration.RequirementInterface]: # Mypy doesn't appreciate our classproperty implementation, self._plugin.version has no type config_path = interfaces.configuration.path_join(config_path, self.name) - if len(self._version) > 0 and self._plugin.version[0] != self._version[0]: + if len(self._version) > 0 and self._component.version[0] != self._version[0]: return {config_path: self} - if len(self._version) > 1 and self._plugin.version[1] > self._version[1]: + if len(self._version) > 1 and self._component.version[1] > self._version[1]: return {config_path: self} return {} + + +class PluginRequirement(VersionRequirement): + + def __init__(self, + name: str, + description: str = None, + default: None = None, + optional: bool = False, + plugin: Type[interfaces.plugins.PluginInterface] = None, + version: Optional[Tuple[int, ...]] = None) -> None: + super().__init__(name = name, + description = description, + default = default, + optional = optional, + component = plugin, + version = version) diff --git a/volatility/framework/interfaces/configuration.py b/volatility/framework/interfaces/configuration.py index d19800554..51ad47085 100644 --- a/volatility/framework/interfaces/configuration.py +++ b/volatility/framework/interfaces/configuration.py @@ -23,8 +23,9 @@ import random import string import sys from abc import ABCMeta, abstractmethod -from typing import Any, ClassVar, Dict, Generator, List, Optional, Type, Union +from typing import Any, ClassVar, Dict, Generator, List, Optional, Type, Union, Tuple +from volatility import classproperty from volatility.framework import constants, interfaces CONFIG_SEPARATOR = "." @@ -686,3 +687,20 @@ class ConfigurableInterface(metaclass = ABCMeta): context.config[path_join(new_config_path, k)] = v return new_config_path + + +class VersionableInterface: + _version: Tuple[int, int, int] = (0, 0, 0) + + @classproperty + def version(cls) -> Tuple[int, int, int]: + """The version of the current interface (classmethods available on the + plugin). + + It is strongly recommended that Semantic Versioning be used (and the default version verification is defined that way): + + MAJOR version when you make incompatible API changes. + MINOR version when you add functionality in a backwards compatible manner. + PATCH version when you make backwards compatible bug fixes. + """ + return cls._version diff --git a/volatility/framework/interfaces/layers.py b/volatility/framework/interfaces/layers.py index 2ab0fa728..31592d820 100644 --- a/volatility/framework/interfaces/layers.py +++ b/volatility/framework/interfaces/layers.py @@ -34,7 +34,7 @@ ProgressValue = Union['DummyProgress', managers.ValueProxy] IteratorValue = Tuple[List[Tuple[str, int, int]], int] -class ScannerInterface(metaclass = ABCMeta): +class ScannerInterface(interfaces.configuration.VersionableInterface, metaclass = ABCMeta): """Class for layer scanners that return locations of particular values from within the data. @@ -63,6 +63,7 @@ class ScannerInterface(metaclass = ABCMeta): thread_safe = False def __init__(self) -> None: + super().__init__() self.chunk_size = 0x1000000 # Default to 16Mb chunks self.overlap = 0x1000 # A page of overlap by default self._context = None # type: Optional[interfaces.context.ContextInterface] diff --git a/volatility/framework/interfaces/plugins.py b/volatility/framework/interfaces/plugins.py index 72a3963de..85df26365 100644 --- a/volatility/framework/interfaces/plugins.py +++ b/volatility/framework/interfaces/plugins.py @@ -13,7 +13,7 @@ import logging from abc import ABCMeta, abstractmethod from typing import List, Optional, Tuple -from volatility import classproperty, framework +from volatility import framework from volatility.framework import exceptions, constants, interfaces vollog = logging.getLogger(__name__) @@ -67,7 +67,9 @@ class FileConsumerInterface(object): # The plugin runs and produces a TreeGrid output -class PluginInterface(interfaces.configuration.ConfigurableInterface, metaclass = ABCMeta): +class PluginInterface(interfaces.configuration.ConfigurableInterface, + interfaces.configuration.VersionableInterface, + metaclass = ABCMeta): """Class that defines the basic interface that all Plugins must maintain. The constructor must only take a `context` and `config_path`, so @@ -77,7 +79,6 @@ class PluginInterface(interfaces.configuration.ConfigurableInterface, metaclass """ # Be careful with inheritance around this - _version = (0, 0, 0) # type: Tuple[int, int, int] _required_framework_verison = (1, 0, 0) # type: Tuple[int, int, int] """The _version variable is a quick way for plugins to define their current interface, it should follow SemVer rules""" @@ -115,19 +116,6 @@ class PluginInterface(interfaces.configuration.ConfigurableInterface, metaclass else: vollog.debug("No file consumer specified to consume: {}".format(filedata.preferred_filename)) - @classproperty - def version(cls) -> Tuple[int, int, int]: - """The version of the current interface (classmethods available on the - plugin). - - It is strongly recommended that Semantic Versioning be used (and the default version verification is defined that way): - - MAJOR version when you make incompatible API changes. - MINOR version when you add functionality in a backwards compatible manner. - PATCH version when you make backwards compatible bug fixes. - """ - return cls._version - @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: """Returns a list of Requirement objects for this plugin.""" diff --git a/volatility/framework/plugins/windows/vadyarascan.py b/volatility/framework/plugins/windows/vadyarascan.py index 86345115e..87f740fa7 100644 --- a/volatility/framework/plugins/windows/vadyarascan.py +++ b/volatility/framework/plugins/windows/vadyarascan.py @@ -7,7 +7,6 @@ from typing import Iterable, List, Tuple from volatility.framework import interfaces, renderers from volatility.framework.configuration import requirements -from volatility.framework.layers import resources from volatility.framework.renderers import format_hints from volatility.plugins import yarascan from volatility.plugins.windows import pslist @@ -44,7 +43,9 @@ class VadYaraScan(interfaces.plugins.PluginInterface): description = "Set the maximum size (default is 1GB)", optional = True), requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (1, 0, 0)), - requirements.PluginRequirement(name = 'yarascan', plugin = yarascan.YaraScan, version = (2, 0, 0)), + requirements.PluginRequirement(name = 'yarascan', plugin = yarascan.YaraScan, version = (1, 0, 0)), + requirements.VersionRequirement(name = 'yarascanner', component = yarascan.YaraScanner, + version = (2, 0, 0)), requirements.ListRequirement(name = 'pid', element_type = int, description = "Process IDs to include (all other processes are excluded)", @@ -62,10 +63,10 @@ class VadYaraScan(interfaces.plugins.PluginInterface): symbol_table = self.config['nt_symbols'], filter_func = filter_func): layer_name = task.add_process_layer() - for offset, rule_name, name, value in yarascan.YaraScan.scan(context = self.context, - layer_name = layer_name, - rules = rules, - sections = self.get_vad_maps(task)): + layer = self.context.layers[layer_name] + for offset, rule_name, name, value in layer.scan(context = self.context, + scanner = yarascan.YaraScanner(rules = rules), + sections = self.get_vad_maps(task)): yield 0, (format_hints.Hex(offset), task.UniqueProcessId, rule_name, name, value) @staticmethod diff --git a/volatility/framework/plugins/yarascan.py b/volatility/framework/plugins/yarascan.py index 91deee755..f36ab5622 100644 --- a/volatility/framework/plugins/yarascan.py +++ b/volatility/framework/plugins/yarascan.py @@ -21,6 +21,7 @@ except ImportError: class YaraScanner(interfaces.layers.ScannerInterface): + _version = (2, 0, 0) # yara.Rules isn't exposed, so we can't type this properly def __init__(self, rules) -> None: @@ -36,7 +37,7 @@ class YaraScanner(interfaces.layers.ScannerInterface): class YaraScan(plugins.PluginInterface): """Scans kernel memory using yara rules (string or file).""" - _version = (2, 0, 0) + _version = (1, 0, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -80,24 +81,12 @@ class YaraScan(plugins.PluginInterface): vollog.error("No yara rules, nor yara rules file were specified") return rules - @classmethod - def scan(cls, - context: interfaces.context.ContextInterface, - layer_name: str, - rules, - sections: Iterable[Tuple[int, int]] = None): - if rules is None: - return - layer = context.layers[layer_name] - yield from layer.scan(context = context, scanner = YaraScanner(rules = rules), sections = sections) - def _generator(self): - rules = self.process_yara_options(dict(self.config)) - for offset, rule_name, name, value in self.scan(context = self.context, layer_name = self.config['primary'], - rules = rules): - yield (0, (format_hints.Hex(offset), rule_name, name, value)) + layer = self.context.layers[self.config['primary']] + for offset, rule_name, name, value in layer.scan(context = self.context, scanner = YaraScanner(rules = rules)): + yield 0, (format_hints.Hex(offset), rule_name, name, value) def run(self): return renderers.TreeGrid([('Offset', format_hints.Hex), ('Rule', str), ('Component', str), ('Value', bytes)],