From 95713fc7acdab0ae177b2c63476c59d063e43b2c Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 19 Sep 2021 01:30:21 +0100 Subject: [PATCH] Core: Change Module to ConfigurableInterface --- volatility3/framework/automagic/module.py | 2 +- volatility3/framework/contexts/__init__.py | 75 ++++++++++++------- .../framework/interfaces/configuration.py | 2 +- volatility3/framework/interfaces/context.py | 46 +++++++----- volatility3/framework/plugins/linux/kmsg.py | 2 +- volatility3/framework/plugins/windows/ssdt.py | 12 +-- 6 files changed, 83 insertions(+), 56 deletions(-) diff --git a/volatility3/framework/automagic/module.py b/volatility3/framework/automagic/module.py index 315164ec7..0c2b58f3c 100644 --- a/volatility3/framework/automagic/module.py +++ b/volatility3/framework/automagic/module.py @@ -22,7 +22,7 @@ class KernelModule(interfaces.automagic.AutomagicInterface): # The requirement is unfulfilled and is a ModuleRequirement context.config[interfaces.configuration.path_join( - new_config_path, 'class')] = 'volatility3.framework.contexts.ConfigurableModule' + new_config_path, 'class')] = 'volatility3.framework.contexts.Module' for req in requirement.requirements: if requirement.requirements[req].unsatisfied(context, new_config_path) and req != 'offset': diff --git a/volatility3/framework/contexts/__init__.py b/volatility3/framework/contexts/__init__.py index e39c2d4ec..3b15c5d0e 100644 --- a/volatility3/framework/contexts/__init__.py +++ b/volatility3/framework/contexts/__init__.py @@ -144,17 +144,17 @@ class Context(interfaces.context.ContextInterface): size: The size, in bytes, that the module occupys from offset location within the layer named layer_name """ 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) + return SizedModule.create(self, + module_name = module_name, + layer_name = layer_name, + offset = offset, + size = size, + native_layer_name = native_layer_name) + return Module.create(self, + module_name = module_name, + layer_name = layer_name, + offset = offset, + native_layer_name = native_layer_name) def get_module_wrapper(method: str) -> Callable: @@ -179,6 +179,33 @@ def get_module_wrapper(method: str) -> Callable: class Module(interfaces.context.ModuleInterface): + @classmethod + def create(cls, + context: interfaces.context.ContextInterface, + module_name: str, + layer_name: str, + offset: int, + **kwargs) -> 'Module': + pathjoin = interfaces.configuration.path_join + # Check if config_path is None + config_path = kwargs.get('config_path', None) + if config_path is None: + config_path = pathjoin('temporary', 'modules') + # Populate the configuration + context.config[pathjoin(config_path, 'layer_name')] = layer_name + context.config[pathjoin(config_path, 'offset')] = offset + # This is important, since the module_name may be changed in case it is already in use + if 'symbol_table_name' not in kwargs: + kwargs['symbol_table_name'] = module_name + for arg in kwargs: + context.config[pathjoin(config_path, arg)] = kwargs.get(arg, None) + # Construct the object + return_val = cls(context, config_path, context.modules.free_module_name(module_name)) + context.add_module(return_val) + context.config[config_path] = return_val.name + # Add the module to the context modules collection + return return_val + def object(self, object_type: str, offset: int = None, @@ -280,26 +307,11 @@ class Module(interfaces.context.ModuleInterface): 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 + size = self.config.get('size', 0) + return size or 0 @property # type: ignore # FIXME: mypy #5107 @functools.lru_cache() @@ -346,6 +358,13 @@ class ModuleCollection(interfaces.context.ModuleContainer): seen.add(mod.hash) # type: ignore # FIXME: mypy #5107 return ModuleCollection(new_modules) + def free_module_name(self, prefix: str = "module") -> str: + """Returns an unused module name""" + count = 1 + while prefix + str(count) in self: + count += 1 + return prefix + str(count) + @property def modules(self) -> 'ModuleCollection': """A name indexed dictionary of modules using that name in this diff --git a/volatility3/framework/interfaces/configuration.py b/volatility3/framework/interfaces/configuration.py index 6331e6683..7dc046a3e 100644 --- a/volatility3/framework/interfaces/configuration.py +++ b/volatility3/framework/interfaces/configuration.py @@ -192,7 +192,7 @@ class HierarchicalDict(collections.abc.Mapping): elif value is None: return None else: - raise TypeError("Invalid type stored in configuration") + raise TypeError(f"Invalid type stored in configuration: {type(value)}") def __delitem__(self, key: str) -> None: """Deletes an item from the hierarchical dict.""" diff --git a/volatility3/framework/interfaces/context.py b/volatility3/framework/interfaces/context.py index e42753363..c52e1aaa5 100644 --- a/volatility3/framework/interfaces/context.py +++ b/volatility3/framework/interfaces/context.py @@ -136,7 +136,7 @@ class ContextInterface(metaclass = ABCMeta): """ -class ModuleInterface(metaclass = ABCMeta): +class ModuleInterface(interfaces.configuration.ConfigurableInterface): """Maintains state concerning a particular loaded module in memory. This object is OS-independent. @@ -144,31 +144,35 @@ class ModuleInterface(metaclass = ABCMeta): def __init__(self, context: ContextInterface, - module_name: str, - layer_name: str, - offset: int, - symbol_table_name: Optional[str] = None, - native_layer_name: Optional[str] = None) -> None: + config_path: str, + name: str) -> None: """Constructs a new os-independent module. Args: context: The context within which this module will exist + config_path: The path within the context's configuration tree name: The name of the module - layer_name: The layer within the context in which the module exists - offset: The offset at which the module exists in the layer - symbol_table_name: The name of an associated symbol table - native_layer_name: The default native layer for objects constructed by the module """ - self._context = context - self._module_name = module_name - self._layer_name = layer_name - self._offset = offset - # TODO: Figure out about storing/requesting the native_layer_name for a module in the configuration - # The current module requirement does not ask for nor act upon this information - self._native_layer_name = native_layer_name or layer_name - self._symbol_table_name = symbol_table_name or self._module_name + super().__init__(context, config_path) + self._module_name = name - def build_configuration(self) -> 'configuration.HierarchicalDict': + @property + def _layer_name(self) -> str: + return self.config['layer_name'] + + @property + def _offset(self) -> int: + return self.config['offset'] + + @property + def _native_layer_name(self) -> str: + return self.config.get('native_layer_name', self._layer_name) + + @property + def _symbol_table_name(self) -> str: + return self.config.get('symbol_table_name', self._module_name) + + def build_configuration(self) -> 'interfaces.configuration.HierarchicalDict': """Builds the configuration dictionary for this specific Module""" config = super().build_configuration() @@ -319,6 +323,10 @@ class ModuleContainer(collections.abc.Mapping): def __iter__(self): return iter(self._modules) + def free_module_name(self, prefix: str = "module") -> str: + """Returns an unused table name to ensure no collision occurs when + inserting a symbol table.""" + def get_modules_by_symbol_tables(self, symbol_table: str) -> Iterable[str]: """Returns the modules which use the specified symbol table name""" for module_name in self._modules: diff --git a/volatility3/framework/plugins/linux/kmsg.py b/volatility3/framework/plugins/linux/kmsg.py index 3ec53cdcf..d982956d0 100644 --- a/volatility3/framework/plugins/linux/kmsg.py +++ b/volatility3/framework/plugins/linux/kmsg.py @@ -60,7 +60,7 @@ class ABCKmsg(ABC): vmlinux = context.modules[self._config['kernel']] self.layer_name = vmlinux.layer_name # type: ignore symbol_table_name = vmlinux.symbol_table_name # type: ignore - self.vmlinux = contexts.Module(context, symbol_table_name, self.layer_name, 0) # type: ignore + self.vmlinux = contexts.Module.create(context, symbol_table_name, self.layer_name, 0) # type: ignore self.long_unsigned_int_size = self.vmlinux.get_type('long unsigned int').size @classmethod diff --git a/volatility3/framework/plugins/windows/ssdt.py b/volatility3/framework/plugins/windows/ssdt.py index b5b3e40c0..7203d415b 100644 --- a/volatility3/framework/plugins/windows/ssdt.py +++ b/volatility3/framework/plugins/windows/ssdt.py @@ -60,12 +60,12 @@ class SSDT(plugins.PluginInterface): if module_name in constants.windows.KERNEL_MODULE_NAMES: symbol_table_name = symbol_table - context_module = contexts.SizedModule(context, - module_name, - layer_name, - mod.DllBase, - mod.SizeOfImage, - symbol_table_name = symbol_table_name) + context_module = contexts.SizedModule.create(context = context, + module_name = module_name, + layer_name = layer_name, + offset = mod.DllBase, + size = mod.SizeOfImage, + symbol_table_name = symbol_table_name) context_modules.append(context_module)