Source code for volatility3.framework.layers.segmented

# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from abc import ABCMeta, abstractmethod
from bisect import bisect_right
from typing import Any, Dict, Iterable, List, Optional, Tuple

from volatility3.framework import exceptions, interfaces
from volatility3.framework.configuration import requirements
from volatility3.framework.layers import linear


[docs]class NonLinearlySegmentedLayer( interfaces.layers.TranslationLayerInterface, metaclass=ABCMeta ): """A class to handle a single run-based layer-to-layer mapping. In the documentation "mapped address" or "mapped offset" refers to an offset once it has been mapped to the underlying layer """ def __init__( self, context: interfaces.context.ContextInterface, config_path: str, name: str, metadata: Optional[Dict[str, Any]] = None, ) -> None: super().__init__( context=context, config_path=config_path, name=name, metadata=metadata ) self._base_layer = self.config["base_layer"] self._segments: List[Tuple[int, int, int, int]] = [] self._minaddr: Optional[int] = None self._maxaddr: Optional[int] = None self._load_segments() @abstractmethod def _load_segments(self) -> None: """Populates the _segments variable. Segments must be (address, mapped address, length, mapped_length) and must be sorted by address when this method exits """
[docs] def is_valid(self, offset: int, length: int = 1) -> bool: """Returns whether the address offset can be translated to a valid address.""" try: base_layer = self._context.layers[self._base_layer] return all( [ base_layer.is_valid(mapped_offset) for _i, _i, mapped_offset, _i, _s in self.mapping(offset, length) ] ) except exceptions.InvalidAddressException: return False
def _find_segment( self, offset: int, next: bool = False ) -> Tuple[int, int, int, int]: """Finds the segment containing a given offset. Returns the segment tuple (offset, mapped_offset, length, mapped_length) """ if not self._segments: self._load_segments() # Find rightmost value less than or equal to x i = bisect_right( self._segments, (offset, self.context.layers[self._base_layer].maximum_address), ) if i and not next: segment = self._segments[i - 1] if segment[0] <= offset < segment[0] + segment[2]: return segment if next: if i < len(self._segments): return self._segments[i] raise exceptions.InvalidAddressException( self.name, offset, f"Invalid address at {offset:0x}" ) # Determines whether larger segments are in use and the offsets within them should be tracked linearly # When no decoding of the data occurs, this should be set to true _track_offset = False
[docs] def mapping( self, offset: int, length: int, ignore_errors: bool = False ) -> Iterable[Tuple[int, int, int, int, str]]: """Returns a sorted iterable of (offset, length, mapped_offset, mapped_length, layer) mappings.""" done = False current_offset = offset while not done: try: # Search for the appropriate segment that contains the current_offset logical_offset, mapped_offset, size, mapped_size = self._find_segment( current_offset ) # If it starts before the current_offset, bring the lower edge up to the right place if current_offset > logical_offset: difference = current_offset - logical_offset logical_offset += difference if self._track_offset: mapped_offset += difference size -= difference except exceptions.InvalidAddressException: if not ignore_errors: # If we're not ignoring errors, raise the invalid address exception raise try: # Find the next valid segment after our current_offset ( logical_offset, mapped_offset, size, mapped_size, ) = self._find_segment(current_offset, next=True) # We know that the logical_offset must be greater than current_offset so skip to that value current_offset = logical_offset # If it starts too late then we're done if logical_offset > offset + length: return None except exceptions.InvalidAddressException: return None # Crop it to the amount we need left chunk_size = min(size, length + offset - logical_offset) yield logical_offset, chunk_size, mapped_offset, mapped_size, self._base_layer current_offset += chunk_size # Terminate if we've gone (or reached) our required limit if current_offset >= offset + length: done = True
@property def minimum_address(self) -> int: if not self._segments: raise ValueError("SegmentedLayer must contain some segments") if self._minaddr is None: mapped, _, _, _ = self._segments[0] self._minaddr = mapped return self._minaddr @property def maximum_address(self) -> int: if not self._segments: raise ValueError("SegmentedLayer must contain some segments") if self._maxaddr is None: mapped, _, length, _ = self._segments[-1] self._maxaddr = mapped + length return self._maxaddr @property def dependencies(self) -> List[str]: """Returns a list of the lower layers that this layer is dependent upon.""" return [self._base_layer]
[docs] @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: return [ requirements.TranslationLayerRequirement(name="base_layer", optional=False) ]
[docs]class SegmentedLayer( NonLinearlySegmentedLayer, linear.LinearlyMappedLayer, metaclass=ABCMeta ): _track_offset = True
[docs] def mapping( self, offset: int, length: int, ignore_errors: bool = False ) -> Iterable[Tuple[int, int, int, int, str]]: # Linear mappings must return the same length of segment as that requested for offset, length, mapped_offset, mapped_length, layer in super().mapping( offset, length, ignore_errors ): yield offset, length, mapped_offset, length, layer