Core: Refactor versioning and associated requirements

This commit is contained in:
Mike Auty
2020-07-29 20:18:03 +01:00
committed by ikelos
parent 2c39489790
commit 3eb463475b
6 changed files with 61 additions and 47 deletions
@@ -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)
@@ -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
+2 -1
View File
@@ -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]
+4 -16
View File
@@ -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."""
@@ -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
+5 -16
View File
@@ -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)],