mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-08-20 21:52:21 +02:00
152 lines
8.0 KiB
Python
152 lines
8.0 KiB
Python
"""This module attempts to automatically stack layers.
|
|
|
|
This automagic module fulfills :class:`~volatility.framework.interfaces.configuration.TranslationLayerRequirement` that are not already fulfilled, by attempting to
|
|
stack as many layers on top of each other as possible. The base/lowest layer is derived from the
|
|
"automagic.general.single_location" configuration path. Layers are then attempting in likely height order, and
|
|
once a layer successfully stacks on top of the existing layers, it is removed from the possible choices list
|
|
(so no layer type can exist twice in the layer stack).
|
|
"""
|
|
|
|
import logging
|
|
import traceback
|
|
import typing
|
|
|
|
import volatility
|
|
from volatility.framework import configuration, interfaces, constants, validity
|
|
from volatility.framework.automagic import construct_layers
|
|
from volatility.framework.configuration import requirements
|
|
from volatility.framework.layers import physical
|
|
|
|
vollog = logging.getLogger(__name__)
|
|
|
|
|
|
class LayerStacker(interfaces.automagic.AutomagicInterface):
|
|
"""Builds up layers in a single stack
|
|
|
|
This class mimics the volatility 2 style of stacking address spaces. It builds up various layers based on
|
|
separate :class:`~volatility.framework.interfaces.automagic.StackerLayerInterface` classes. These classes are
|
|
built up based on a `stack_order` class variable each has.
|
|
|
|
This has a high priority to provide other automagic modules as complete a context/configuration tree as possible.
|
|
Upon completion it will re-call the :class:`~volatility.framework.automagic.construct_layers.ConstructionMagic`,
|
|
so that any stacked layers are actually constructed and added to the context.
|
|
"""
|
|
# Most important automagic, must happen first!
|
|
priority = 10
|
|
page_map_offset = None
|
|
|
|
def __call__(self,
|
|
context: interfaces.context.ContextInterface,
|
|
config_path: str,
|
|
requirement: interfaces.configuration.ConstructableRequirementInterface,
|
|
progress_callback: validity.ProgressCallback = None) \
|
|
-> typing.Optional[typing.List[str]]:
|
|
"""Runs the automagic over the configurable"""
|
|
|
|
# Quick exit if we're not needed
|
|
if not requirement.unsatisfied(context, config_path):
|
|
return None
|
|
|
|
# Bow out quickly if the UI hasn't provided a single_location
|
|
unsatisfied = self.unsatisfied(self.context, self.config_path)
|
|
if unsatisfied:
|
|
vollog.info("Unable to run LayerStacker, unsatisfied requirement: {}".format(unsatisfied))
|
|
return unsatisfied
|
|
if not self.config or not self.config.get('single_location', None):
|
|
raise ValueError("Unable to run LayerStacker, single_location parameter not provided")
|
|
location = self.config['single_location']
|
|
self._check_type(location, str)
|
|
self._check_type(requirement, interfaces.configuration.RequirementInterface)
|
|
|
|
# Setup the local copy of the resource
|
|
new_context = context.clone()
|
|
current_layer_name = context.memory.free_layer_name("FileLayer")
|
|
current_config_path = interfaces.configuration.path_join(config_path, "stack", current_layer_name)
|
|
# This must be specific to get us started, setup the config and run
|
|
new_context.config[interfaces.configuration.path_join(current_config_path, "location")] = location
|
|
|
|
physical_layer = physical.FileLayer(new_context, current_config_path, current_layer_name)
|
|
new_context.add_layer(physical_layer)
|
|
|
|
# Repeatedly apply "determine what this is" code and build as much up as possible
|
|
stacked = True
|
|
stacked_layers = [current_layer_name]
|
|
stack_set = sorted(volatility.framework.class_subclasses(interfaces.automagic.StackerLayerInterface),
|
|
key = lambda x: x.stack_order)
|
|
while stacked:
|
|
stacked = False
|
|
new_layer = None
|
|
stacker_cls = None
|
|
for stacker_cls in stack_set:
|
|
stacker = stacker_cls()
|
|
try:
|
|
new_layer = stacker.stack(new_context, current_layer_name, progress_callback)
|
|
if new_layer:
|
|
new_context.memory.add_layer(new_layer)
|
|
break
|
|
except Exception as excp:
|
|
# Stacking exceptions are likely only of interest to developers, so the lowest level of logging
|
|
fulltrace = traceback.TracebackException.from_exception(excp).format(chain = True)
|
|
vollog.log(constants.LOGLEVEL_VVV, "Exception during stacking: {}".format(str(excp)))
|
|
vollog.log(constants.LOGLEVEL_VVVV, "\n".join(fulltrace))
|
|
else:
|
|
stacked = False
|
|
if new_layer and stacker_cls:
|
|
stacked_layers = [new_layer.name] + stacked_layers
|
|
current_layer_name = new_layer.name
|
|
stacked = True
|
|
stack_set.remove(stacker_cls)
|
|
|
|
vollog.debug("Stacked layers: {}".format(stacked_layers))
|
|
|
|
if stacked_layers:
|
|
|
|
result = self.find_suitable_requirements(stacked_layers, requirement, new_context, config_path)
|
|
if result:
|
|
path, layer = result
|
|
# splice in the new configuration into the original context
|
|
context.config.merge(path, new_context.memory[layer].build_configuration())
|
|
# Call the construction magic now we may have new things to construct
|
|
constructor = construct_layers.ConstructionMagic(context,
|
|
interfaces.configuration.path_join(self.config_path,
|
|
"ConstructionMagic"))
|
|
constructor(context, config_path, requirement)
|
|
return None
|
|
|
|
def find_suitable_requirements(self,
|
|
stacked_layers: typing.List,
|
|
requirement: interfaces.configuration.RequirementInterface,
|
|
context: interfaces.context.ContextInterface,
|
|
config_path: str) -> typing.Optional[typing.Tuple[str, str]]:
|
|
"""Looks for translation layer requirements and attempts to apply the stacked layers to it. If it succeeds
|
|
it returns the configuration path and layer name where the stacked nodes were spliced into the tree.
|
|
|
|
:return: A tuple of a configuration path and layer name for the top of the stacked layers
|
|
:rtype: (str, str)"""
|
|
child_config_path = interfaces.configuration.path_join(config_path, requirement.name)
|
|
if isinstance(requirement, interfaces.configuration.TranslationLayerRequirement):
|
|
if requirement.unsatisfied(context, config_path):
|
|
original_setting = context.config.get(child_config_path, None)
|
|
for layer_name in stacked_layers:
|
|
context.config[child_config_path] = layer_name
|
|
if not requirement.unsatisfied(context, config_path):
|
|
return child_config_path, layer_name
|
|
else:
|
|
# Clean-up to restore the config
|
|
if original_setting:
|
|
context.config[child_config_path] = original_setting
|
|
else:
|
|
del context.config[child_config_path]
|
|
for req_name, req in requirement.requirements.items():
|
|
result = self.find_suitable_requirements(stacked_layers, req, context, child_config_path)
|
|
if result:
|
|
return result
|
|
return None
|
|
|
|
@classmethod
|
|
def get_requirements(cls) -> typing.List[interfaces.configuration.RequirementInterface]:
|
|
# This is not optional for the stacker to run, so optional must be marked as False
|
|
return [requirements.StringRequirement("single_location",
|
|
description = "Specifies a base location on which to stack",
|
|
optional = True)]
|