1# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 2# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 3# 4"""This module attempts to automatically stack layers. 5 6This automagic module fulfills :class:`~volatility.framework.interfaces.configuration.TranslationLayerRequirement` that are not already fulfilled, by attempting to 7stack as many layers on top of each other as possible. The base/lowest layer is derived from the 8"automagic.general.single_location" configuration path. Layers are then attempting in likely height order, and 9once a layer successfully stacks on top of the existing layers, it is removed from the possible choices list 10(so no layer type can exist twice in the layer stack). 11""" 12 13import logging 14import sys 15import traceback 16from typing import List, Optional, Tuple 17 18from volatility import framework 19from volatility.framework import interfaces, constants, import_files 20from volatility.framework.automagic import construct_layers 21from volatility.framework.configuration import requirements 22from volatility.framework.layers import physical 23 24vollog = logging.getLogger(__name__) 25 26 27class LayerStacker(interfaces.automagic.AutomagicInterface): 28 """Builds up layers in a single stack. 29 30 This class mimics the volatility 2 style of stacking address spaces. It builds up various layers based on 31 separate :class:`~volatility.framework.interfaces.automagic.StackerLayerInterface` classes. These classes are 32 built up based on a `stack_order` class variable each has. 33 34 This has a high priority to provide other automagic modules as complete a context/configuration tree as possible. 35 Upon completion it will re-call the :class:`~volatility.framework.automagic.construct_layers.ConstructionMagic`, 36 so that any stacked layers are actually constructed and added to the context. 37 """ 38 # Most important automagic, must happen first! 39 priority = 10 40 41 def __init__(self, *args, **kwargs): 42 super().__init__(*args, **kwargs) 43 self._cached = None 44 45 def __call__(self, 46 context: interfaces.context.ContextInterface, 47 config_path: str, 48 requirement: interfaces.configuration.RequirementInterface, 49 progress_callback: constants.ProgressCallback = None) -> Optional[List[str]]: 50 """Runs the automagic over the configurable.""" 51 52 framework.import_files(sys.modules['volatility.framework.layers']) 53 54 # Quick exit if we're not needed 55 if not requirement.unsatisfied(context, config_path): 56 return None 57 58 # Bow out quickly if the UI hasn't provided a single_location 59 unsatisfied = self.unsatisfied(self.context, self.config_path) 60 if unsatisfied: 61 vollog.info("Unable to run LayerStacker, unsatisfied requirement: {}".format(unsatisfied)) 62 return list(unsatisfied) 63 if not self.config or not self.config.get('single_location', None): 64 raise ValueError("Unable to run LayerStacker, single_location parameter not provided") 65 66 # Search for suitable requirements 67 self.stack(context, config_path, requirement, progress_callback) 68 69 return None 70 71 def stack(self, context: interfaces.context.ContextInterface, config_path: str, 72 requirement: interfaces.configuration.RequirementInterface, 73 progress_callback: constants.ProgressCallback) -> None: 74 """Stacks the various layers and attaches these to a specific 75 requirement. 76 77 Args: 78 context: Context on which to operate 79 config_path: Configuration path under which to store stacking data 80 requirement: Requirement that should have layers stacked on it 81 progress_callback: Function to provide callback progress 82 """ 83 # If we're cached, find Now we need to find where to apply the stack configuration 84 if self._cached: 85 top_layer_name, subconfig = self._cached 86 result = self.find_suitable_requirements(context, config_path, requirement, [top_layer_name]) 87 if result: 88 appropriate_config_path, layer_name = result 89 context.config.merge(appropriate_config_path, subconfig) 90 context.config[appropriate_config_path] = top_layer_name 91 return 92 self._cached = None 93 94 new_context = context.clone() 95 location = self.config.get('single_location', None) 96 97 # Setup the local copy of the resource 98 current_layer_name = context.layers.free_layer_name("FileLayer") 99 current_config_path = interfaces.configuration.path_join(config_path, "stack", current_layer_name) 100 101 # This must be specific to get us started, setup the config and run 102 new_context.config[interfaces.configuration.path_join(current_config_path, "location")] = location 103 physical_layer = physical.FileLayer(new_context, current_config_path, current_layer_name) 104 new_context.add_layer(physical_layer) 105 106 # Repeatedly apply "determine what this is" code and build as much up as possible 107 stacked = True 108 stacked_layers = [current_layer_name] 109 stack_set = sorted(framework.class_subclasses(interfaces.automagic.StackerLayerInterface), 110 key = lambda x: x.stack_order) 111 while stacked: 112 stacked = False 113 new_layer = None 114 stacker_cls = None 115 for stacker_cls in stack_set: 116 stacker = stacker_cls() 117 try: 118 vollog.log(constants.LOGLEVEL_VV, "Attempting to stack using {}".format(stacker_cls.__name__)) 119 new_layer = stacker.stack(new_context, current_layer_name, progress_callback) 120 if new_layer: 121 new_context.layers.add_layer(new_layer) 122 vollog.log(constants.LOGLEVEL_VV, 123 "Stacked {} using {}".format(new_layer.name, stacker_cls.__name__)) 124 break 125 except Exception as excp: 126 # Stacking exceptions are likely only of interest to developers, so the lowest level of logging 127 fulltrace = traceback.TracebackException.from_exception(excp).format(chain = True) 128 vollog.log(constants.LOGLEVEL_VVV, "Exception during stacking: {}".format(str(excp))) 129 vollog.log(constants.LOGLEVEL_VVVV, "\n".join(fulltrace)) 130 else: 131 stacked = False 132 if new_layer and stacker_cls: 133 stacked_layers = [new_layer.name] + stacked_layers 134 current_layer_name = new_layer.name 135 stacked = True 136 stack_set.remove(stacker_cls) 137 138 if stacked_layers is not None: 139 # Applies the stacked_layers to each requirement in the requirements list 140 result = self.find_suitable_requirements(new_context, config_path, requirement, stacked_layers) 141 if result: 142 path, layer = result 143 # splice in the new configuration into the original context 144 context.config.merge(path, new_context.layers[layer].build_configuration()) 145 146 # Call the construction magic now we may have new things to construct 147 constructor = construct_layers.ConstructionMagic( 148 context, interfaces.configuration.path_join(self.config_path, "ConstructionMagic")) 149 constructor(context, config_path, requirement) 150 151 # Stash the changed config items 152 self._cached = context.config.get(path, None), context.config.branch(path) 153 154 vollog.debug("Stacked layers: {}".format(stacked_layers)) 155 156 def find_suitable_requirements(self, context: interfaces.context.ContextInterface, config_path: str, 157 requirement: interfaces.configuration.RequirementInterface, 158 stacked_layers: List[str]) -> Optional[Tuple[str, str]]: 159 """Looks for translation layer requirements and attempts to apply the 160 stacked layers to it. If it succeeds it returns the configuration path 161 and layer name where the stacked nodes were spliced into the tree. 162 163 Returns: 164 A tuple of a configuration path and layer name for the top of the stacked layers 165 or None if suitable requirements are not found 166 """ 167 child_config_path = interfaces.configuration.path_join(config_path, requirement.name) 168 if isinstance(requirement, requirements.TranslationLayerRequirement): 169 if requirement.unsatisfied(context, config_path): 170 original_setting = context.config.get(child_config_path, None) 171 for layer_name in stacked_layers: 172 context.config[child_config_path] = layer_name 173 if not requirement.unsatisfied(context, config_path): 174 return child_config_path, layer_name 175 else: 176 # Clean-up to restore the config 177 if original_setting: 178 context.config[child_config_path] = original_setting 179 else: 180 del context.config[child_config_path] 181 else: 182 return child_config_path, context.config.get(child_config_path, None) 183 for req_name, req in requirement.requirements.items(): 184 result = self.find_suitable_requirements(context, child_config_path, req, stacked_layers) 185 if result: 186 return result 187 return None 188 189 @classmethod 190 def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: 191 # This is not optional for the stacker to run, so optional must be marked as False 192 return [ 193 requirements.URIRequirement("single_location", 194 description = "Specifies a base location on which to stack", 195 optional = True) 196 ] 197