Refactor requirements to support a list of swap layers.

This commit is contained in:
Mike Auty
2018-01-28 14:48:50 +00:00
parent bf8173f9a9
commit c203663f0b
5 changed files with 97 additions and 81 deletions
+3 -2
View File
@@ -15,6 +15,7 @@ import logging
import sys
import volatility.framework
import volatility.framework.configuration.requirements
import volatility.plugins
from volatility.framework import automagic, constants, contexts, interfaces
from volatility.framework.configuration import requirements
@@ -226,7 +227,7 @@ class CommandLine(object):
additional["action"] = "store_true"
if "type" in additional:
del additional["type"]
elif isinstance(requirement, interfaces.configuration.ListRequirement):
elif isinstance(requirement, volatility.framework.configuration.requirements.ListRequirement):
if requirement.min_elements != requirement.max_elements:
if requirement.min_elements > 0:
additional["nargs"] = "+"
@@ -236,7 +237,7 @@ class CommandLine(object):
else:
additional["nargs"] = requirement.max_elements
additional["type"] = requirement.element_type.instance_type
elif isinstance(requirement, interfaces.configuration.ChoiceRequirement):
elif isinstance(requirement, volatility.framework.configuration.requirements.ChoiceRequirement):
additional["type"] = str
additional["choices"] = requirement.choices
else:
@@ -8,7 +8,9 @@ etc) as well as indicating what they expect to be in the context (such as partic
import logging
import typing
from volatility.framework import interfaces, constants
from volatility.framework.interfaces import configuration as interfaces_configuration
from volatility.framework.interfaces.configuration import RequirementInterface, InstanceRequirement, vollog, path_join
vollog = logging.getLogger(__name__)
@@ -49,3 +51,77 @@ class StringRequirement(interfaces_configuration.InstanceRequirement):
class BytesRequirement(interfaces_configuration.InstanceRequirement):
"""A requirement type that contains a byte string"""
instance_type = bytes # type: typing.ClassVar[typing.Type]
class ListRequirement(RequirementInterface):
"""Allows for a list of a specific type of requirement (all of which must be met for this requirement to be met) to be specified
This roughly correlates to allowing a number of arguments to follow a command line parameter,
such as a list of integers or a list of strings.
It is distinct from a multi-requirement which stores the subrequirements in a dictionary, not a list,
and does not allow for a dynamic number of values.
"""
def __init__(self,
element_type: InstanceRequirement,
max_elements: int,
min_elements: int, *args, **kwargs) -> None:
"""Constructs the object
:param element_type: The (requirement) type of each element within the list
:type element_type: InstanceRequirement
:param max_elements; The maximum number of acceptable elements this list can contain
:type max_elements: int
:param min_elements: The minimum number of acceptable elements this list can contain
:type min_elements: int
"""
super().__init__(*args, **kwargs)
if not isinstance(element_type, InstanceRequirement):
raise TypeError("ListRequirements can only contain simple InstanceRequirements")
self.element_type = element_type
self.min_elements = min_elements # type: int
self.max_elements = max_elements # type: int
def unsatisfied(self, context: interfaces.context.ContextInterface, config_path: str) -> typing.List[str]:
"""Check the types on each of the returned values and their number and then call the element type's check for each one"""
value = self.config_value(context, config_path)
if not isinstance(value, list):
# TODO: Check this is the correct response for an error
raise ValueError("")
if not (self.min_elements <= len(value) <= self.max_elements):
vollog.log(constants.LOGLEVEL_V, "TypeError - List option provided more or less elements than allowed.")
return [path_join(config_path, self.name)]
if not all([self._check_type(element, self.element_type.instance_type) for element in value]):
vollog.log(constants.LOGLEVEL_V, "TypeError - At least one element in the list is not of the correct type.")
return [path_join(config_path, self.name)]
result = []
for element in value:
if isinstance(element, str):
subresult = self.element_type.unsatisfied(context, element)
for subvalue in subresult:
result.append(subvalue)
return result
class ChoiceRequirement(RequirementInterface):
"""Allows one from a choice of strings"""
def __init__(self, choices: typing.List[str], *args, **kwargs) -> None:
"""Constructs the object
:param choices: A list of possible string options that can be chosen from
:type choices: list of str
"""
super().__init__(*args, **kwargs)
if not isinstance(choices, list) or any([not isinstance(choice, str) for choice in choices]):
raise TypeError("ChoiceRequirement takes a list of strings as choices")
self.choices = choices
def unsatisfied(self, context: interfaces.context.ContextInterface, config_path: str) -> typing.List[str]:
"""Validates the provided value to ensure it is one of the available choices"""
value = self.config_value(context, config_path)
if value not in self.choices:
vollog.log(constants.LOGLEVEL_V, "ValueError - Value is not within the set of available choices")
return [path_join(config_path, self.name)]
return []
+3 -2
View File
@@ -5,6 +5,7 @@ Automagic objects attempt to automatically fill configuration values that a user
import typing
from abc import ABCMeta, abstractmethod
import volatility.framework.configuration.requirements
from volatility.framework import validity, interfaces
from volatility.framework.interfaces import configuration as interfaces_configuration
@@ -41,8 +42,8 @@ class AutomagicInterface(interfaces_configuration.ConfigurableInterface, metacla
super().__init__(context, config_path)
for requirement in self.get_requirements():
if not isinstance(requirement, (interfaces_configuration.InstanceRequirement,
interfaces_configuration.ChoiceRequirement,
interfaces_configuration.ListRequirement)):
volatility.framework.configuration.requirements.ChoiceRequirement,
volatility.framework.configuration.requirements.ListRequirement)):
raise ValueError(
"Automagic requirements must be an InstanceRequirement, ChoiceRequirement or ListRequirement")
@@ -664,77 +664,3 @@ class SymbolRequirement(ConstructableRequirementInterface):
if obj is not None:
context.symbol_space.append(obj)
return None
class ChoiceRequirement(RequirementInterface):
"""Allows one from a choice of strings"""
def __init__(self, choices: typing.List[str], *args, **kwargs) -> None:
"""Constructs the object
:param choices: A list of possible string options that can be chosen from
:type choices: list of str
"""
super().__init__(*args, **kwargs)
if not isinstance(choices, list) or any([not isinstance(choice, str) for choice in choices]):
raise TypeError("ChoiceRequirement takes a list of strings as choices")
self.choices = choices
def unsatisfied(self, context: interfaces.context.ContextInterface, config_path: str) -> typing.List[str]:
"""Validates the provided value to ensure it is one of the available choices"""
value = self.config_value(context, config_path)
if value not in self.choices:
vollog.log(constants.LOGLEVEL_V, "ValueError - Value is not within the set of available choices")
return [path_join(config_path, self.name)]
return []
class ListRequirement(RequirementInterface):
"""Allows for a list of a specific type of requirement (all of which must be met for this requirement to be met) to be specified
This roughly correlates to allowing a number of arguments to follow a command line parameter,
such as a list of integers or a list of strings.
It is distinct from a multi-requirement which stores the subrequirements in a dictionary, not a list,
and does not allow for a dynamic number of values.
"""
def __init__(self,
element_type: InstanceRequirement,
max_elements: int,
min_elements: int, *args, **kwargs) -> None:
"""Constructs the object
:param element_type: The (requirement) type of each element within the list
:type element_type: InstanceRequirement
:param max_elements; The maximum number of acceptable elements this list can contain
:type max_elements: int
:param min_elements: The minimum number of acceptable elements this list can contain
:type min_elements: int
"""
super().__init__(*args, **kwargs)
if not isinstance(element_type, InstanceRequirement):
raise TypeError("ListRequirements can only contain simple InstanceRequirements")
self.element_type = element_type
self.min_elements = min_elements # type: int
self.max_elements = max_elements # type: int
def unsatisfied(self, context: interfaces.context.ContextInterface, config_path: str) -> typing.List[str]:
"""Check the types on each of the returned values and their number and then call the element type's check for each one"""
value = self.config_value(context, config_path)
if not isinstance(value, list):
# TODO: Check this is the correct response for an error
raise ValueError("")
if not (self.min_elements <= len(value) <= self.max_elements):
vollog.log(constants.LOGLEVEL_V, "TypeError - List option provided more or less elements than allowed.")
return [path_join(config_path, self.name)]
if not all([self._check_type(element, self.element_type.instance_type) for element in value]):
vollog.log(constants.LOGLEVEL_V, "TypeError - At least one element in the list is not of the correct type.")
return [path_join(config_path, self.name)]
result = []
for element in value:
if isinstance(element, str):
subresult = self.element_type.unsatisfied(context, element)
for subvalue in subresult:
result.append(subvalue)
return result
+15 -3
View File
@@ -43,6 +43,12 @@ class Intel(interfaces.layers.TranslationLayerInterface):
name: str) -> None:
super().__init__(context, config_path, name)
self._base_layer = self._check_type(self.config["memory_layer"], str)
self._swap_layers = []
self._check_type(self.config.get("swap_layers", []), list)
for layer_name in self.config.get("swap_layers", []):
self._check_type(layer_name, str)
if layer_name in context.memory:
self._swap_layers.append(layer_name)
self._page_map_offset = self._check_type(self.config["page_map_offset"], int)
self._optimize_scan = False
@@ -169,14 +175,20 @@ class Intel(interfaces.layers.TranslationLayerInterface):
def dependencies(self) -> typing.List[str]:
"""Returns a list of the lower layer names that this layer is dependent upon"""
# TODO: Add in the whole buffalo
return [self._base_layer]
return [self._base_layer] + self._swap_layers
@classmethod
def get_requirements(cls) -> typing.List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'memory_layer',
optional = False),
requirements.TranslationLayerRequirement(name = 'swap_layer',
optional = True),
requirements.ListRequirement(name = 'swap_layers',
element_type = requirements.StringRequirement(
name = 'layer_name',
optional = False
),
min_elements = 0,
max_elements = 100,
optional = True),
requirements.IntRequirement(name = 'page_map_offset',
optional = False),
requirements.IntRequirement(name = 'kernel_virtual_offset',