diff --git a/volatility/cli/__init__.py b/volatility/cli/__init__.py index 3433b201d..55de75816 100644 --- a/volatility/cli/__init__.py +++ b/volatility/cli/__init__.py @@ -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: diff --git a/volatility/framework/configuration/requirements.py b/volatility/framework/configuration/requirements.py index eff817a69..b7fe6167a 100644 --- a/volatility/framework/configuration/requirements.py +++ b/volatility/framework/configuration/requirements.py @@ -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 [] diff --git a/volatility/framework/interfaces/automagic.py b/volatility/framework/interfaces/automagic.py index 4483044d2..02437a2b2 100644 --- a/volatility/framework/interfaces/automagic.py +++ b/volatility/framework/interfaces/automagic.py @@ -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") diff --git a/volatility/framework/interfaces/configuration.py b/volatility/framework/interfaces/configuration.py index 34a1a47b9..8f9268c8c 100644 --- a/volatility/framework/interfaces/configuration.py +++ b/volatility/framework/interfaces/configuration.py @@ -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 diff --git a/volatility/framework/layers/intel.py b/volatility/framework/layers/intel.py index a14c353cd..c08bc3f84 100644 --- a/volatility/framework/layers/intel.py +++ b/volatility/framework/layers/intel.py @@ -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',