mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-30 13:34:53 +02:00
Note: creation of enumerations can be done using Context.object, which should allow reference of enumeration members by normal type objects. If it turns out enumerations are never referenced from types, we can easily remove the try/except in the Module.object method.
331 lines
14 KiB
Python
331 lines
14 KiB
Python
# This file was contributed to the Volatility Framework Version 3.
|
|
# Copyright (C) 2018 Volatility Foundation.
|
|
#
|
|
# THE LICENSED WORK IS PROVIDED UNDER THE TERMS OF THE Volatility Contributors
|
|
# Public License V1.0("LICENSE") AS FIRST COMPLETED BY: Volatility Foundation,
|
|
# Inc. ANY USE, PUBLIC DISPLAY, PUBLIC PERFORMANCE, REPRODUCTION OR DISTRIBUTION
|
|
# OF, OR PREPARATION OF SUBSEQUENT WORKS, DERIVATIVE WORKS OR DERIVED WORKS BASED
|
|
# ON, THE LICENSED WORK CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS LICENSE AND ITS
|
|
# TERMS, WHETHER OR NOT SUCH RECIPIENT READS THE TERMS OF THE LICENSE. "LICENSED
|
|
# WORK,” “RECIPIENT" AND “DISTRIBUTOR" ARE DEFINED IN THE LICENSE. A COPY OF THE
|
|
# LICENSE IS LOCATED IN THE TEXT FILE ENTITLED "LICENSE.txt" ACCOMPANYING THE
|
|
# CONTENTS OF THIS FILE. IF A COPY OF THE LICENSE DOES NOT ACCOMPANY THIS FILE, A
|
|
# COPY OF THE LICENSE MAY ALSO BE OBTAINED AT THE FOLLOWING WEB SITE:
|
|
# https://www.volatilityfoundation.org/license/vcpl_v1.0
|
|
#
|
|
# Software distributed under the License is distributed on an "AS IS" basis,
|
|
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
|
|
# specific language governing rights and limitations under the License.
|
|
#
|
|
"""A `Context` maintains the accumulated state required for various plugins and framework functions.
|
|
|
|
This has been made an object to allow quick swapping and changing of contexts, to allow a plugin
|
|
to act on multiple different contexts without them interfering eith each other.
|
|
"""
|
|
import functools
|
|
import hashlib
|
|
from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple, Union
|
|
|
|
from volatility.framework import constants, interfaces, symbols, exceptions
|
|
|
|
|
|
class Context(interfaces.context.ContextInterface):
|
|
"""Maintains the context within which to construct objects
|
|
|
|
The context object is the main method of carrying around state that's been constructed for the purposes of
|
|
investigating memory. It contains a symbol_space of all the symbols that can be accessed by plugins using the
|
|
context. It also contains the memory made up of data and translation layers, and it contains a factory method
|
|
for creating new objects.
|
|
|
|
Other context objects can be constructed as long as they support the
|
|
:class:`~volatility.framework.interfaces.context.ContextInterface`. This is the primary context object to be used
|
|
in the volatility framework. It maintains the
|
|
"""
|
|
|
|
def __init__(self) -> None:
|
|
"""Initializes the context."""
|
|
super().__init__()
|
|
self._symbol_space = symbols.SymbolSpace()
|
|
self._memory = interfaces.layers.LayerContainer()
|
|
self._config = interfaces.configuration.HierarchicalDict()
|
|
|
|
# ## Symbol Space Functions
|
|
|
|
@property
|
|
def config(self) -> interfaces.configuration.HierarchicalDict:
|
|
"""Returns a mutable copy of the configuration, but does not allow the whole configuration to be altered"""
|
|
return self._config
|
|
|
|
@config.setter
|
|
def config(self, value: interfaces.configuration.HierarchicalDict) -> None:
|
|
if not isinstance(value, interfaces.configuration.HierarchicalDict):
|
|
raise TypeError("Config must be of type HierarchicalDict")
|
|
self._config = value
|
|
|
|
@property
|
|
def symbol_space(self) -> interfaces.symbols.SymbolSpaceInterface:
|
|
"""The space of all symbols that can be accessed within this context.
|
|
"""
|
|
return self._symbol_space
|
|
|
|
@property
|
|
def layers(self) -> interfaces.layers.LayerContainer:
|
|
"""A LayerContainer object, allowing access to all data and translation layers currently available within the context"""
|
|
return self._memory
|
|
|
|
# ## Translation Layer Functions
|
|
|
|
def add_layer(self, layer: interfaces.layers.DataLayerInterface) -> None:
|
|
"""Adds a named translation layer to the context
|
|
|
|
Args:
|
|
layer: The layer to be added to the memory
|
|
|
|
Raises:
|
|
volatility.framework.exceptions.LayerException: if the layer is already present, or has
|
|
unmet dependencies
|
|
"""
|
|
self._memory.add_layer(layer)
|
|
|
|
# ## Object Factory Functions
|
|
|
|
def object(self,
|
|
symbol: Union[str, interfaces.objects.Template],
|
|
layer_name: str,
|
|
offset: int,
|
|
native_layer_name: Optional[str] = None,
|
|
**arguments) -> interfaces.objects.ObjectInterface:
|
|
"""Object factory, takes a context, symbol, offset and optional layername
|
|
|
|
Looks up the layername in the context, finds the object template based on the symbol,
|
|
and constructs an object using the object template on the layer at the offset.
|
|
|
|
Args:
|
|
symbol: The name (or template) of the symbol type on which to construct the object. If this is a name, it should contain an explicit table name.
|
|
layer_name: The name of the layer on which to construct the object
|
|
offset: The offset within the layer at which the data used to create the object lives
|
|
|
|
|
|
Returns:
|
|
A fully constructed object
|
|
"""
|
|
if not isinstance(symbol, interfaces.objects.Template):
|
|
try:
|
|
object_template = self._symbol_space.get_type(symbol)
|
|
except exceptions.SymbolError:
|
|
object_template = self._symbol_space.get_enumeration(symbol)
|
|
else:
|
|
object_template = symbol
|
|
# Ensure that if a pre-constructed type is provided we just instantiate it
|
|
arguments.update(object_template.vol)
|
|
|
|
object_template = object_template.clone()
|
|
object_template.update_vol(**arguments)
|
|
return object_template(
|
|
context = self,
|
|
object_info = interfaces.objects.ObjectInformation(
|
|
layer_name = layer_name, offset = offset, native_layer_name = native_layer_name))
|
|
|
|
def module(self,
|
|
module_name: str,
|
|
layer_name: str,
|
|
offset: int,
|
|
native_layer_name: Optional[str] = None,
|
|
size: Optional[int] = None) -> interfaces.context.ModuleInterface:
|
|
"""Creates a module object"""
|
|
if size:
|
|
return SizedModule(
|
|
self,
|
|
module_name = module_name,
|
|
layer_name = layer_name,
|
|
offset = offset,
|
|
size = size,
|
|
native_layer_name = native_layer_name)
|
|
return Module(
|
|
self,
|
|
module_name = module_name,
|
|
layer_name = layer_name,
|
|
offset = offset,
|
|
native_layer_name = native_layer_name)
|
|
|
|
|
|
def get_module_wrapper(method: str) -> Callable:
|
|
"""Returns a symbol using the symbol_table_name of the Module"""
|
|
|
|
def wrapper(self, name: str) -> Callable:
|
|
if constants.BANG not in name:
|
|
name = self._module_name + constants.BANG + name
|
|
else:
|
|
raise ValueError("Cannot reference another module when calling {}".format(method))
|
|
return getattr(self._context.symbol_space, method)(name)
|
|
|
|
for entry in ['__annotations__', '__doc__', '__module__', '__name__', '__qualname__']:
|
|
proxy_interface = getattr(interfaces.context.ModuleInterface, method)
|
|
if hasattr(proxy_interface, entry):
|
|
setattr(wrapper, entry, getattr(proxy_interface, entry))
|
|
|
|
return wrapper
|
|
|
|
|
|
class Module(interfaces.context.ModuleInterface):
|
|
|
|
def object(self,
|
|
symbol: str,
|
|
offset: int = None,
|
|
native_layer_name: Optional[str] = None,
|
|
absolute: bool = False,
|
|
**kwargs) -> 'interfaces.objects.ObjectInterface':
|
|
"""Returns an object created using the symbol_table_name and layer_name of the Module
|
|
|
|
Args:
|
|
symbol: Name of the type/symbol/enumeration (within the module) to construct
|
|
offset: The location of the object, ignored when symbol_type is SYMBOL
|
|
native_layer_name: Name of the layer in which constructed objects are made (for pointers)
|
|
"""
|
|
if constants.BANG not in symbol:
|
|
symbol = self.symbol_table_name + constants.BANG + symbol
|
|
else:
|
|
raise ValueError("Cannot reference another module when constructing an object")
|
|
|
|
if offset is None:
|
|
raise ValueError("Offset must not be None for non-symbol objects")
|
|
|
|
if not absolute:
|
|
offset += self._offset
|
|
|
|
# Ensure we don't use a layer_name other than the module's, why would anyone do that?
|
|
if 'layer_name' in kwargs:
|
|
del kwargs['layer_name']
|
|
return self._context.object(
|
|
symbol = symbol,
|
|
layer_name = self._layer_name,
|
|
offset = offset,
|
|
native_layer_name = native_layer_name or self._native_layer_name,
|
|
**kwargs)
|
|
|
|
def object_from_symbol(self, symbol: str, native_layer_name: Optional[str] = None, absolute: bool = False,
|
|
**kwargs) -> 'interfaces.objects.ObjectInterface':
|
|
if constants.BANG not in symbol:
|
|
symbol = self.symbol_table_name + constants.BANG + symbol
|
|
else:
|
|
raise ValueError("Cannot reference another module when constructing an object")
|
|
|
|
# Only set the offset if type is Symbol and we were given a name, not a template
|
|
symbol_val = self._context.symbol_space.get_symbol(symbol)
|
|
offset = symbol_val.address
|
|
|
|
if not absolute:
|
|
offset += self._offset
|
|
|
|
if symbol_val.type is None:
|
|
raise ValueError("Symbol {} has no associated type".format(symbol_val.name))
|
|
|
|
# Ensure we don't use a layer_name other than the module's, why would anyone do that?
|
|
if 'layer_name' in kwargs:
|
|
del kwargs['layer_name']
|
|
|
|
# Since type may be a template, we don't just call our own module method
|
|
return self._context.object(
|
|
symbol = symbol_val.type,
|
|
layer_name = self._layer_name,
|
|
offset = offset,
|
|
native_layer_name = native_layer_name or self._native_layer_name,
|
|
**kwargs)
|
|
|
|
get_symbol = get_module_wrapper('get_symbol')
|
|
get_type = get_module_wrapper('get_type')
|
|
get_enumeration = get_module_wrapper('get_enumeration')
|
|
has_symbol = get_module_wrapper('has_symbol')
|
|
has_type = get_module_wrapper('has_type')
|
|
has_enumeration = get_module_wrapper('has_enumeration')
|
|
|
|
|
|
class SizedModule(Module):
|
|
|
|
def __init__(self,
|
|
context: interfaces.context.ContextInterface,
|
|
module_name: str,
|
|
layer_name: str,
|
|
offset: int,
|
|
size: int,
|
|
symbol_table_name: Optional[str] = None,
|
|
native_layer_name: Optional[str] = None) -> None:
|
|
super().__init__(
|
|
context,
|
|
module_name = module_name,
|
|
layer_name = layer_name,
|
|
offset = offset,
|
|
native_layer_name = native_layer_name,
|
|
symbol_table_name = symbol_table_name)
|
|
self._size = size
|
|
|
|
@property
|
|
def size(self) -> int:
|
|
"""Returns the size of the module (0 for unknown size)"""
|
|
return self._size
|
|
|
|
@property # type: ignore # FIXME: mypy #5107
|
|
@functools.lru_cache()
|
|
def hash(self) -> str:
|
|
"""Hashes the module for equality checks
|
|
|
|
The mapping should be sorted and should be quicker than reading the data
|
|
We turn it into JSON to make a common string and use a quick hash, because collissions are unlikely"""
|
|
layer = self._context.layers[self.layer_name]
|
|
if not isinstance(layer, interfaces.layers.TranslationLayerInterface):
|
|
raise TypeError("Hashing modules on non-TranslationLayers is not allowed")
|
|
return hashlib.md5(bytes(str(list(layer.mapping(self.offset, self.size, ignore_errors = True))),
|
|
'utf-8')).hexdigest()
|
|
|
|
def get_symbols_by_absolute_location(self, offset: int, size: int = 0) -> List[str]:
|
|
"""Returns the symbols within this module that live at the specified absolute offset provided"""
|
|
if size < 0:
|
|
raise ValueError("Size must be strictly non-negative")
|
|
if offset > self._offset + self.size:
|
|
return []
|
|
return list(
|
|
self._context.symbol_space.get_symbols_by_location(
|
|
offset = offset - self._offset, size = size, table_name = self.symbol_table_name))
|
|
|
|
|
|
class ModuleCollection:
|
|
"""Class to contain a collection of SizedModules and reason about their contents"""
|
|
|
|
def __init__(self, modules: List[SizedModule]) -> None:
|
|
self._modules = modules
|
|
|
|
def deduplicate(self) -> 'ModuleCollection':
|
|
"""Returns a new deduplicated ModuleCollection featuring no repeated modules (based on data hash)
|
|
|
|
All 0 sized modules will have identical hashes and are therefore included in the deduplicated version
|
|
"""
|
|
new_modules = []
|
|
seen = set() # type: Set[str]
|
|
for mod in self._modules:
|
|
if mod.hash not in seen or mod.size == 0:
|
|
new_modules.append(mod)
|
|
seen.add(mod.hash) # type: ignore # FIXME: mypy #5107
|
|
return ModuleCollection(new_modules)
|
|
|
|
@property
|
|
def modules(self) -> Dict[str, List[SizedModule]]:
|
|
"""A name indexed dictionary of modules using that name in this collection"""
|
|
return self._generate_module_dict(self._modules)
|
|
|
|
@classmethod
|
|
def _generate_module_dict(cls, modules: List[SizedModule]) -> Dict[str, List[SizedModule]]:
|
|
result = {} # type: Dict[str, List[SizedModule]]
|
|
for module in modules:
|
|
modlist = result.get(module.name, [])
|
|
modlist.append(module)
|
|
result[module.name] = modlist
|
|
return result
|
|
|
|
def get_module_symbols_by_absolute_location(self, offset: int, size: int = 0) -> Iterable[Tuple[str, List[str]]]:
|
|
"""Returns a tuple of (module_name, list_of_symbol_names) for each module, where symbols live at the absolute offset in memory provided"""
|
|
if size < 0:
|
|
raise ValueError("Size must be strictly non-negative")
|
|
for module in self._modules:
|
|
if (offset <= module.offset + module.size) and (offset + size >= module.offset):
|
|
yield (module.name, module.get_symbols_by_absolute_location(offset, size))
|