mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-12 20:57:39 +02:00
Improve docstrings for all plugins, and reformat all docstrings.
This commit is contained in:
@@ -11,9 +11,10 @@ _S = TypeVar("_S")
|
||||
|
||||
|
||||
class classproperty(object):
|
||||
"""Class property decorator
|
||||
"""Class property decorator.
|
||||
|
||||
Note this will change the return type """
|
||||
Note this will change the return type
|
||||
"""
|
||||
|
||||
def __init__(self, func: Callable[[_S], _T]) -> None:
|
||||
self._func = func
|
||||
@@ -23,11 +24,13 @@ class classproperty(object):
|
||||
|
||||
|
||||
class WarningFindSpec(abc.MetaPathFinder):
|
||||
"""Checks import attempts and throws a warning if the name shouldn't be used"""
|
||||
"""Checks import attempts and throws a warning if the name shouldn't be
|
||||
used."""
|
||||
|
||||
@staticmethod
|
||||
def find_spec(fullname: str, path, target = None):
|
||||
"""Mock find_spec method that just checks the name, this must go first"""
|
||||
"""Mock find_spec method that just checks the name, this must go
|
||||
first."""
|
||||
if fullname.startswith("volatility.framework.plugins."):
|
||||
warning = "Please do not use the volatility.framework.plugins namespace directly, only use volatility.plugins"
|
||||
# Pyinstaller uses pkgutil to import, but needs to read the modules to figure out dependencies
|
||||
|
||||
+22
-18
@@ -1,14 +1,14 @@
|
||||
# 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
|
||||
#
|
||||
"""A CommandLine User Interface for the volatility framework
|
||||
"""A CommandLine User Interface for the volatility framework.
|
||||
|
||||
User interfaces make use of the framework to:
|
||||
* determine available plugins
|
||||
* request necessary information for those plugins from the user
|
||||
* determine what "automagic" modules will be used to populate information the user does not provide
|
||||
* run the plugin
|
||||
* display the results
|
||||
User interfaces make use of the framework to:
|
||||
* determine available plugins
|
||||
* request necessary information for those plugins from the user
|
||||
* determine what "automagic" modules will be used to populate information the user does not provide
|
||||
* run the plugin
|
||||
* display the results
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -39,13 +39,14 @@ vollog.addHandler(console)
|
||||
|
||||
|
||||
class PrintedProgress(object):
|
||||
"""A progress handler that prints the progress value and the description onto the command line"""
|
||||
"""A progress handler that prints the progress value and the description
|
||||
onto the command line."""
|
||||
|
||||
def __init__(self):
|
||||
self._max_message_len = 0
|
||||
|
||||
def __call__(self, progress: Union[int, float], description: str = None):
|
||||
""" A simple function for providing text-based feedback
|
||||
"""A simple function for providing text-based feedback.
|
||||
|
||||
.. warning:: Only for development use.
|
||||
|
||||
@@ -59,20 +60,21 @@ class PrintedProgress(object):
|
||||
|
||||
|
||||
class MuteProgress(PrintedProgress):
|
||||
"""A dummy progress handler that produces no output when called"""
|
||||
"""A dummy progress handler that produces no output when called."""
|
||||
|
||||
def __call__(self, progress: Union[int, float], description: str = None):
|
||||
pass
|
||||
|
||||
|
||||
class CommandLine(interfaces.plugins.FileConsumerInterface):
|
||||
"""Constructs a command-line interface object for users to run plugins"""
|
||||
"""Constructs a command-line interface object for users to run plugins."""
|
||||
|
||||
def __init__(self):
|
||||
self.output_dir = None
|
||||
|
||||
def run(self):
|
||||
"""Executes the command line module, taking the system arguments, determining the plugin to run and then running it"""
|
||||
"""Executes the command line module, taking the system arguments,
|
||||
determining the plugin to run and then running it."""
|
||||
sys.stdout.write("Volatility Framework {}\n".format(constants.PACKAGE_VERSION))
|
||||
|
||||
volatility.framework.require_interface_version(0, 0, 0)
|
||||
@@ -268,7 +270,7 @@ class CommandLine(interfaces.plugins.FileConsumerInterface):
|
||||
parser.exit(1, "Unable to validate the plugin requirements: {}\n".format([x for x in excp.unsatisfied]))
|
||||
|
||||
def process_exceptions(self, excp):
|
||||
"""Provide useful feedback if an exception occurs"""
|
||||
"""Provide useful feedback if an exception occurs."""
|
||||
# Add a blank newline
|
||||
print("")
|
||||
translation_failed = False
|
||||
@@ -295,7 +297,7 @@ class CommandLine(interfaces.plugins.FileConsumerInterface):
|
||||
def populate_config(self, context: interfaces.context.ContextInterface,
|
||||
configurables_list: Dict[str, interfaces.configuration.ConfigurableInterface],
|
||||
args: argparse.Namespace, plugin_config_path: str) -> None:
|
||||
"""Populate the context config based on the returned args
|
||||
"""Populate the context config based on the returned args.
|
||||
|
||||
We have already determined these elements must be descended from ConfigurableInterface
|
||||
|
||||
@@ -329,7 +331,7 @@ class CommandLine(interfaces.plugins.FileConsumerInterface):
|
||||
context.config[extended_path] = value
|
||||
|
||||
def consume_file(self, filedata: interfaces.plugins.FileInterface):
|
||||
"""Consumes a file as produced by a plugin"""
|
||||
"""Consumes a file as produced by a plugin."""
|
||||
if self.output_dir is None:
|
||||
raise ValueError("Output directory has not been correctly specified")
|
||||
os.makedirs(self.output_dir, exist_ok = True)
|
||||
@@ -347,7 +349,7 @@ class CommandLine(interfaces.plugins.FileConsumerInterface):
|
||||
|
||||
def populate_requirements_argparse(self, parser: Union[argparse.ArgumentParser, argparse._ArgumentGroup],
|
||||
configurable: Type[interfaces.configuration.ConfigurableInterface]):
|
||||
"""Adds the plugin's simple requirements to the provided parser
|
||||
"""Adds the plugin's simple requirements to the provided parser.
|
||||
|
||||
Args:
|
||||
parser: The parser to add the plugin's (simple) requirements to
|
||||
@@ -390,7 +392,8 @@ class CommandLine(interfaces.plugins.FileConsumerInterface):
|
||||
|
||||
# We shouldn't really steal a private member from argparse, but otherwise we're just duplicating code
|
||||
class HelpfulSubparserAction(argparse._SubParsersAction):
|
||||
"""Class to either select a unique plugin based on a substring, or identify the alternatives"""
|
||||
"""Class to either select a unique plugin based on a substring, or identify
|
||||
the alternatives."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -433,5 +436,6 @@ class HelpfulSubparserAction(argparse._SubParsersAction):
|
||||
|
||||
|
||||
def main():
|
||||
"""A convenience function for constructing and running the :class:`CommandLine`'s run method"""
|
||||
"""A convenience function for constructing and running the
|
||||
:class:`CommandLine`'s run method."""
|
||||
CommandLine().run()
|
||||
|
||||
@@ -25,7 +25,7 @@ from volatility.framework import interfaces, renderers
|
||||
|
||||
|
||||
def hex_bytes_as_text(value: bytes) -> str:
|
||||
"""Renders HexBytes as text
|
||||
"""Renders HexBytes as text.
|
||||
|
||||
Args:
|
||||
value: A series of bytes to convert to text
|
||||
@@ -80,14 +80,13 @@ def quoted_optional(func):
|
||||
|
||||
|
||||
def display_disassembly(disasm: interfaces.renderers.Disassembly) -> str:
|
||||
"""Renders a disassembly renderer type into string format
|
||||
"""Renders a disassembly renderer type into string format.
|
||||
|
||||
Args:
|
||||
disasm: Input disassembly objects
|
||||
|
||||
Returns:
|
||||
A string as rendererd by capstone where available, otherwise output as if it were just bytes
|
||||
|
||||
"""
|
||||
|
||||
if CAPSTONE_PRESENT:
|
||||
@@ -106,7 +105,7 @@ def display_disassembly(disasm: interfaces.renderers.Disassembly) -> str:
|
||||
|
||||
|
||||
class CLIRenderer(interfaces.renderers.Renderer):
|
||||
"""Class to add specific requirements for CLI renderers"""
|
||||
"""Class to add specific requirements for CLI renderers."""
|
||||
name = "unnamed"
|
||||
|
||||
|
||||
@@ -127,14 +126,12 @@ class QuickTextRenderer(CLIRenderer):
|
||||
pass
|
||||
|
||||
def render(self, grid: interfaces.renderers.TreeGrid) -> None:
|
||||
"""
|
||||
Renders each column immediately to stdout.
|
||||
"""Renders each column immediately to stdout.
|
||||
|
||||
This does not format each line's width appropriately, it merely tab separates each field
|
||||
|
||||
Args:
|
||||
grid: The TreeGrid object to render
|
||||
|
||||
"""
|
||||
# TODO: Docstrings
|
||||
# TODO: Improve text output
|
||||
@@ -180,12 +177,10 @@ class CSVRenderer(CLIRenderer):
|
||||
pass
|
||||
|
||||
def render(self, grid: interfaces.renderers.TreeGrid) -> None:
|
||||
"""
|
||||
Renders each row immediately to stdout.
|
||||
"""Renders each row immediately to stdout.
|
||||
|
||||
Args:
|
||||
grid: The TreeGrid object to render
|
||||
|
||||
"""
|
||||
outfd = sys.stdout
|
||||
|
||||
@@ -221,14 +216,12 @@ class PrettyTextRenderer(CLIRenderer):
|
||||
pass
|
||||
|
||||
def render(self, grid: interfaces.renderers.TreeGrid) -> None:
|
||||
"""
|
||||
Renders each column immediately to stdout.
|
||||
"""Renders each column immediately to stdout.
|
||||
|
||||
This does not format each line's width appropriately, it merely tab separates each field
|
||||
|
||||
Args:
|
||||
grid: The TreeGrid object to render
|
||||
|
||||
"""
|
||||
# TODO: Docstrings
|
||||
# TODO: Improve text output
|
||||
|
||||
@@ -28,17 +28,19 @@ vollog.addHandler(console)
|
||||
|
||||
|
||||
class VolShell(cli.CommandLine):
|
||||
"""Program to allow interactive interaction with a memory image
|
||||
"""Program to allow interactive interaction with a memory image.
|
||||
|
||||
This allows a memory image to be examined through an interactive python terminal with all the volatility support
|
||||
calls available."""
|
||||
This allows a memory image to be examined through an interactive
|
||||
python terminal with all the volatility support calls available.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.output_dir = None
|
||||
|
||||
def run(self):
|
||||
"""Executes the command line module, taking the system arguments, determining the plugin to run and then running it"""
|
||||
"""Executes the command line module, taking the system arguments,
|
||||
determining the plugin to run and then running it."""
|
||||
sys.stdout.write("Volshell (Volatility Framework) {}\n".format(constants.PACKAGE_VERSION))
|
||||
|
||||
framework.require_interface_version(0, 0, 0)
|
||||
@@ -219,5 +221,6 @@ class VolShell(cli.CommandLine):
|
||||
|
||||
|
||||
def main():
|
||||
"""A convenience function for constructing and running the :class:`CommandLine`'s run method"""
|
||||
"""A convenience function for constructing and running the
|
||||
:class:`CommandLine`'s run method."""
|
||||
VolShell().run()
|
||||
|
||||
@@ -11,7 +11,7 @@ from volatility.framework.configuration import requirements
|
||||
|
||||
|
||||
class Volshell(interfaces.plugins.PluginInterface):
|
||||
"""Shell environment to directly interact with a memory image"""
|
||||
"""Shell environment to directly interact with a memory image."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -21,11 +21,10 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
]
|
||||
|
||||
def run(self, additional_locals: Dict[str, Any] = None) -> interfaces.renderers.TreeGrid:
|
||||
"""Runs the interactive volshell plugin
|
||||
"""Runs the interactive volshell plugin.
|
||||
|
||||
Returns:
|
||||
Return a TreeGrid but this is always empty since the point of this plugin is to run interactively
|
||||
|
||||
"""
|
||||
|
||||
# Provide some OS-agnostic convenience elements for ease
|
||||
@@ -65,12 +64,13 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
return renderers.TreeGrid([], None)
|
||||
|
||||
def load_functions(self) -> Dict[str, Callable]:
|
||||
"""Returns a dictionary listing the functions to be added to the environment"""
|
||||
"""Returns a dictionary listing the functions to be added to the
|
||||
environment."""
|
||||
return {"dt": self.display_type}
|
||||
|
||||
@staticmethod
|
||||
def display_type(object: interfaces.objects.ObjectInterface):
|
||||
"""Display Type"""
|
||||
"""Display Type."""
|
||||
longest_member = longest_offset = 0
|
||||
for member in object.vol.members:
|
||||
relative_offset, member_type = object.vol.members[member]
|
||||
|
||||
@@ -10,7 +10,7 @@ from volatility.framework.configuration import requirements
|
||||
|
||||
|
||||
class Volshell(shellplugin.Volshell):
|
||||
"""Shell environment to directly interact with a windows memory image"""
|
||||
"""Shell environment to directly interact with a windows memory image."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -20,7 +20,7 @@ class Volshell(shellplugin.Volshell):
|
||||
])
|
||||
|
||||
def list_processes(self):
|
||||
"""Lists all the processes in the primary layer"""
|
||||
"""Lists all the processes in the primary layer."""
|
||||
|
||||
# We only use the object factory to demonstrate how to use one
|
||||
layer_name = self.config['primary']
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 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
|
||||
#
|
||||
"""Volatility 3 framework"""
|
||||
"""Volatility 3 framework."""
|
||||
import importlib
|
||||
import inspect
|
||||
import logging
|
||||
@@ -28,7 +28,7 @@ PATCH = 0 # Number of changes that do not change the interface
|
||||
|
||||
|
||||
def interface_version():
|
||||
"""Provides the so version number of the library"""
|
||||
"""Provides the so version number of the library."""
|
||||
return MAJOR, MINOR, PATCH
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def require_interface_version(*args) -> None:
|
||||
"""Checks the required version of a plugin"""
|
||||
"""Checks the required version of a plugin."""
|
||||
if len(args):
|
||||
if args[0] != interface_version()[0]:
|
||||
raise RuntimeError("Framework interface version {} is incompatible with required version {}".format(
|
||||
@@ -71,7 +71,7 @@ T = TypeVar('T')
|
||||
|
||||
|
||||
def class_subclasses(cls: Type[T]) -> Generator[Type[T], None, None]:
|
||||
"""Returns all the (recursive) subclasses of a given class"""
|
||||
"""Returns all the (recursive) subclasses of a given class."""
|
||||
if not inspect.isclass(cls):
|
||||
raise TypeError("class_subclasses parameter not a valid class: {}".format(cls))
|
||||
for clazz in cls.__subclasses__():
|
||||
@@ -83,7 +83,7 @@ def class_subclasses(cls: Type[T]) -> Generator[Type[T], None, None]:
|
||||
|
||||
|
||||
def import_files(base_module, ignore_errors = False) -> List[str]:
|
||||
"""Imports all plugins present under plugins module namespace"""
|
||||
"""Imports all plugins present under plugins module namespace."""
|
||||
failures = []
|
||||
if not isinstance(base_module.__path__, list):
|
||||
raise TypeError("[base_module].__path__ must be a list of paths")
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""Automagic modules allow the framework to populate configuration elements that a user has not provided.
|
||||
"""Automagic modules allow the framework to populate configuration elements
|
||||
that a user has not provided.
|
||||
|
||||
Automagic objects accept a `context` and a `configurable`, and will make appropriate changes to the `context` in an
|
||||
attempt to fulfill the requirements of the `configurable` object (or objects upon which that configurable may rely).
|
||||
@@ -29,7 +30,8 @@ mac_automagic = ['ConstructionMagic', 'LayerStacker', 'MacBannerCache', 'MacSymb
|
||||
|
||||
|
||||
def available(context: interfaces.context.ContextInterface) -> List[interfaces.automagic.AutomagicInterface]:
|
||||
"""Returns an ordered list of all subclasses of :class:`~volatility.framework.interfaces.automagic.AutomagicInterface`.
|
||||
"""Returns an ordered list of all subclasses of
|
||||
:class:`~volatility.framework.interfaces.automagic.AutomagicInterface`.
|
||||
|
||||
The order is based on the priority attributes of the subclasses, in order to ensure the automagics are listed in
|
||||
an appropriate order.
|
||||
@@ -47,7 +49,8 @@ def available(context: interfaces.context.ContextInterface) -> List[interfaces.a
|
||||
|
||||
|
||||
def choose_automagic(automagics, plugin):
|
||||
"""Chooses which automagics to run, maintaining the order they were handed in"""
|
||||
"""Chooses which automagics to run, maintaining the order they were handed
|
||||
in."""
|
||||
plugin_category = plugin.__module__.split('.')[2]
|
||||
vollog.info("Detected a {} category plugin".format(plugin_category))
|
||||
output = []
|
||||
@@ -73,7 +76,8 @@ def run(automagics: List[interfaces.automagic.AutomagicInterface],
|
||||
ConfigurableInterface]],
|
||||
config_path: str,
|
||||
progress_callback: constants.ProgressCallback = None) -> List[traceback.TracebackException]:
|
||||
"""Runs through the list of `automagics` in order, allowing them to make changes to the context
|
||||
"""Runs through the list of `automagics` in order, allowing them to make
|
||||
changes to the context.
|
||||
|
||||
Args:
|
||||
automagics: A list of :class:`~volatility.framework.interfaces.automagic.AutomagicInterface` objects
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# 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
|
||||
#
|
||||
"""An automagic module to use configuration data to configure and then construct classes that fulfill the descendants
|
||||
of a :class:`~volatility.framework.interfaces.configuration.ConfigurableInterface`."""
|
||||
"""An automagic module to use configuration data to configure and then
|
||||
construct classes that fulfill the descendants of a :class:`~volatility.framewo
|
||||
rk.interfaces.configuration.ConfigurableInterface`."""
|
||||
|
||||
import logging
|
||||
from typing import List
|
||||
@@ -14,7 +15,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConstructionMagic(interfaces.automagic.AutomagicInterface):
|
||||
"""Constructs underlying layers
|
||||
"""Constructs underlying layers.
|
||||
|
||||
Class to run through the requirement tree of the :class:`~volatility.framework.interfaces.configuration.ConfigurableInterface`
|
||||
and from the bottom of the tree upwards, attempt to construct all
|
||||
|
||||
@@ -15,7 +15,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LinuxBannerCache(symbol_cache.SymbolBannerCache):
|
||||
"""Caches the banners found in the Linux symbol files"""
|
||||
"""Caches the banners found in the Linux symbol files."""
|
||||
|
||||
os = "linux"
|
||||
symbol_name = "linux_banner"
|
||||
@@ -23,7 +23,7 @@ class LinuxBannerCache(symbol_cache.SymbolBannerCache):
|
||||
|
||||
|
||||
class LinuxSymbolFinder(symbol_finder.SymbolFinder):
|
||||
"""Linux symbol loader based on uname signature strings"""
|
||||
"""Linux symbol loader based on uname signature strings."""
|
||||
|
||||
banner_config_key = "kernel_banner"
|
||||
banner_cache = LinuxBannerCache
|
||||
@@ -38,7 +38,7 @@ class LintelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
context: interfaces.context.ContextInterface,
|
||||
layer_name: str,
|
||||
progress_callback: constants.ProgressCallback = None) -> Optional[interfaces.layers.DataLayerInterface]:
|
||||
"""Attempts to identify linux within this layer"""
|
||||
"""Attempts to identify linux within this layer."""
|
||||
# Bail out by default unless we can stack properly
|
||||
layer = context.layers[layer_name]
|
||||
join = interfaces.configuration.path_join
|
||||
@@ -95,7 +95,7 @@ class LintelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
|
||||
|
||||
class LinuxUtilities(object):
|
||||
"""Class with multiple useful linux functions"""
|
||||
"""Class with multiple useful linux functions."""
|
||||
|
||||
# based on __d_path from the Linux kernel
|
||||
@classmethod
|
||||
@@ -275,7 +275,8 @@ class LinuxUtilities(object):
|
||||
layer_name: str,
|
||||
progress_callback: constants.ProgressCallback = None) \
|
||||
-> Tuple[int, int]:
|
||||
"""Determines the offset of the actual DTB in physical space and its symbol offset"""
|
||||
"""Determines the offset of the actual DTB in physical space and its
|
||||
symbol offset."""
|
||||
init_task_symbol = symbol_table + constants.BANG + 'init_task'
|
||||
init_task_json_address = context.symbol_space.get_symbol(init_task_symbol).address
|
||||
swapper_signature = rb"swapper(\/0|\x00\x00)\x00\x00\x00\x00\x00\x00"
|
||||
@@ -309,7 +310,8 @@ class LinuxUtilities(object):
|
||||
|
||||
@classmethod
|
||||
def virtual_to_physical_address(cls, addr: int) -> int:
|
||||
"""Converts a virtual linux address to a physical one (does not account of ASLR)"""
|
||||
"""Converts a virtual linux address to a physical one (does not account
|
||||
of ASLR)"""
|
||||
if addr > 0xffffffff80000000:
|
||||
return addr - 0xffffffff80000000
|
||||
return addr - 0xc0000000
|
||||
|
||||
@@ -16,14 +16,14 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MacBannerCache(symbol_cache.SymbolBannerCache):
|
||||
"""Caches the banners found in the Mac symbol files"""
|
||||
"""Caches the banners found in the Mac symbol files."""
|
||||
os = "mac"
|
||||
symbol_name = "version"
|
||||
banner_path = constants.MAC_BANNERS_PATH
|
||||
|
||||
|
||||
class MacSymbolFinder(symbol_finder.SymbolFinder):
|
||||
"""Mac symbol loader based on uname signature strings"""
|
||||
"""Mac symbol loader based on uname signature strings."""
|
||||
|
||||
banner_config_key = 'kernel_banner'
|
||||
banner_cache = MacBannerCache
|
||||
@@ -38,7 +38,7 @@ class MacintelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
context: interfaces.context.ContextInterface,
|
||||
layer_name: str,
|
||||
progress_callback: constants.ProgressCallback = None) -> Optional[interfaces.layers.DataLayerInterface]:
|
||||
"""Attempts to identify mac within this layer"""
|
||||
"""Attempts to identify mac within this layer."""
|
||||
# Bail out by default unless we can stack properly
|
||||
layer = context.layers[layer_name]
|
||||
new_layer = None
|
||||
@@ -116,7 +116,7 @@ class MacintelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
|
||||
|
||||
class MacUtilities(object):
|
||||
"""Class with multiple useful mac functions"""
|
||||
"""Class with multiple useful mac functions."""
|
||||
|
||||
@classmethod
|
||||
def aslr_mask_symbol_table(cls,
|
||||
@@ -160,7 +160,8 @@ class MacUtilities(object):
|
||||
compare_banner: str = "",
|
||||
compare_banner_offset: int = 0,
|
||||
progress_callback: constants.ProgressCallback = None) -> int:
|
||||
"""Determines the offset of the actual DTB in physical space and its symbol offset"""
|
||||
"""Determines the offset of the actual DTB in physical space and its
|
||||
symbol offset."""
|
||||
version_symbol = symbol_table + constants.BANG + 'version'
|
||||
version_json_address = context.symbol_space.get_symbol(version_symbol).address
|
||||
|
||||
@@ -208,7 +209,8 @@ class MacUtilities(object):
|
||||
|
||||
@classmethod
|
||||
def virtual_to_physical_address(cls, addr: int) -> int:
|
||||
"""Converts a virtual mac address to a physical one (does not account of ASLR)"""
|
||||
"""Converts a virtual mac address to a physical one (does not account
|
||||
of ASLR)"""
|
||||
return addr - 0xffffff8000000000
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# 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
|
||||
#
|
||||
"""A module for scanning translation layers looking for Windows PDB records from loaded PE files.
|
||||
"""A module for scanning translation layers looking for Windows PDB records
|
||||
from loaded PE files.
|
||||
|
||||
This module contains a standalone scanner, and also a :class:`~volatility.framework.interfaces.layers.ScannerInterface`
|
||||
based scanner for use within the framework by calling :func:`~volatility.framework.interfaces.layers.DataLayerInterface.scan`.
|
||||
This module contains a standalone scanner, and also a :class:`~volatility.framework.interfaces.layers.ScannerInterface`
|
||||
based scanner for use within the framework by calling :func:`~volatility.framework.interfaces.layers.DataLayerInterface.scan`.
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -35,7 +36,8 @@ KernelsType = Iterable[Dict[str, Any]]
|
||||
|
||||
|
||||
class PdbSignatureScanner(interfaces.layers.ScannerInterface):
|
||||
"""A :class:`~volatility.framework.interfaces.layers.ScannerInterface` based scanner use to identify Windows PDB records
|
||||
"""A :class:`~volatility.framework.interfaces.layers.ScannerInterface`
|
||||
based scanner use to identify Windows PDB records.
|
||||
|
||||
Args:
|
||||
pdb_names: A list of bytestrings, used to match pdb signatures against the pdb names within the records.
|
||||
@@ -79,13 +81,15 @@ def scan(ctx: interfaces.context.ContextInterface,
|
||||
progress_callback: constants.ProgressCallback = None,
|
||||
start: Optional[int] = None,
|
||||
end: Optional[int] = None) -> Generator[Dict[str, Optional[Union[bytes, str, int]]], None, None]:
|
||||
"""Scans through `layer_name` at `ctx` looking for RSDS headers that indicate one of four common pdb kernel names
|
||||
(as listed in `self.pdb_names`) and returns the tuple (GUID, age, pdb_name, signature_offset, mz_offset)
|
||||
"""Scans through `layer_name` at `ctx` looking for RSDS headers that
|
||||
indicate one of four common pdb kernel names (as listed in
|
||||
`self.pdb_names`) and returns the tuple (GUID, age, pdb_name,
|
||||
signature_offset, mz_offset)
|
||||
|
||||
.. note:: This is automagical and therefore not guaranteed to provide correct results.
|
||||
.. note:: This is automagical and therefore not guaranteed to provide correct results.
|
||||
|
||||
The UI should always provide the user an opportunity to specify the
|
||||
appropriate types and PDB values themselves
|
||||
The UI should always provide the user an opportunity to specify the
|
||||
appropriate types and PDB values themselves
|
||||
"""
|
||||
min_pfn = 0
|
||||
pdb_names = [bytes(name + ".pdb", "utf-8") for name in constants.windows.KERNEL_MODULE_NAMES]
|
||||
@@ -121,7 +125,7 @@ def scan(ctx: interfaces.context.ContextInterface,
|
||||
|
||||
|
||||
class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
"""Windows symbol loader based on PDB signatures
|
||||
"""Windows symbol loader based on PDB signatures.
|
||||
|
||||
An Automagic object that looks for all Intel translation layers and scans each of them for a pdb signature.
|
||||
When found, a search for a corresponding Intermediate Format data file is carried out and if found an appropriate
|
||||
@@ -138,7 +142,8 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
|
||||
def find_virtual_layers_from_req(self, context: interfaces.context.ContextInterface, config_path: str,
|
||||
requirement: interfaces.configuration.RequirementInterface) -> List[str]:
|
||||
"""Traverses the requirement tree, rooted at `requirement` looking for virtual layers that might contain a windows PDB.
|
||||
"""Traverses the requirement tree, rooted at `requirement` looking for
|
||||
virtual layers that might contain a windows PDB.
|
||||
|
||||
Returns a list of possible layers
|
||||
|
||||
@@ -172,7 +177,8 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
context: interfaces.context.ContextInterface,
|
||||
valid_kernels: ValidKernelsType,
|
||||
progress_callback: constants.ProgressCallback = None) -> None:
|
||||
"""Fulfills the SymbolTableRequirements in `self._symbol_requirements` found by the `recurse_symbol_requirements`.
|
||||
"""Fulfills the SymbolTableRequirements in `self._symbol_requirements`
|
||||
found by the `recurse_symbol_requirements`.
|
||||
|
||||
This pass will construct any requirements that may need it in the context it was passed
|
||||
|
||||
@@ -223,7 +229,8 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
|
||||
def download_pdb_isf(self, guid: str, age: int, pdb_name: str,
|
||||
progress_callback: constants.ProgressCallback = None) -> None:
|
||||
"""Attempts to download the PDB file, convert it to an ISF file and save it to one of the symbol locations"""
|
||||
"""Attempts to download the PDB file, convert it to an ISF file and
|
||||
save it to one of the symbol locations."""
|
||||
# Check for writability
|
||||
filter_string = os.path.join(pdb_name, guid + "-" + str(age))
|
||||
for path in symbols.__path__:
|
||||
@@ -263,8 +270,9 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
|
||||
def set_kernel_virtual_offset(self, context: interfaces.context.ContextInterface,
|
||||
valid_kernels: ValidKernelsType) -> None:
|
||||
"""Traverses the requirement tree, looking for kernel_virtual_offset values that may need setting and sets
|
||||
it based on the previously identified `valid_kernels`.
|
||||
"""Traverses the requirement tree, looking for kernel_virtual_offset
|
||||
values that may need setting and sets it based on the previously
|
||||
identified `valid_kernels`.
|
||||
|
||||
Args:
|
||||
context: Context on which to operate and provide the kernel virtual offset
|
||||
@@ -326,7 +334,8 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
context: interfaces.context.ContextInterface,
|
||||
vlayer: layers.intel.Intel,
|
||||
progress_callback: constants.ProgressCallback = None) -> ValidKernelsType:
|
||||
"""Method for finding a suitable kernel offset based on a module table"""
|
||||
"""Method for finding a suitable kernel offset based on a module
|
||||
table."""
|
||||
vollog.debug("Kernel base determination - searching layer module list structure")
|
||||
valid_kernels = {} # type: ValidKernelsType
|
||||
# If we're here, chances are high we're in a Win10 x64 image with kernel base randomization
|
||||
@@ -387,7 +396,7 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
vlayer: layers.intel.Intel,
|
||||
address: int,
|
||||
progress_callback: constants.ProgressCallback = None) -> ValidKernelsType:
|
||||
"""Scans a virtual address """
|
||||
"""Scans a virtual address."""
|
||||
# Scan a few megs of the virtual space at the location to see if they're potential kernels
|
||||
|
||||
valid_kernels = {} # type: ValidKernelsType
|
||||
@@ -416,7 +425,8 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
context: interfaces.context.ContextInterface,
|
||||
potential_layers: List[str],
|
||||
progress_callback: constants.ProgressCallback = None) -> ValidKernelsType:
|
||||
"""Runs through the identified potential kernels and verifies their suitability
|
||||
"""Runs through the identified potential kernels and verifies their
|
||||
suitability.
|
||||
|
||||
This carries out a scan using the pdb_signature scanner on a physical layer. It uses the
|
||||
results of the scan to determine the virtual offset of the kernel. On early windows implementations
|
||||
|
||||
@@ -24,7 +24,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LayerStacker(interfaces.automagic.AutomagicInterface):
|
||||
"""Builds up layers in a single stack
|
||||
"""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
|
||||
@@ -46,7 +46,7 @@ class LayerStacker(interfaces.automagic.AutomagicInterface):
|
||||
config_path: str,
|
||||
requirement: interfaces.configuration.RequirementInterface,
|
||||
progress_callback: constants.ProgressCallback = None) -> Optional[List[str]]:
|
||||
"""Runs the automagic over the configurable"""
|
||||
"""Runs the automagic over the configurable."""
|
||||
|
||||
# Quick exit if we're not needed
|
||||
if not requirement.unsatisfied(context, config_path):
|
||||
@@ -68,7 +68,8 @@ class LayerStacker(interfaces.automagic.AutomagicInterface):
|
||||
def stack(self, context: interfaces.context.ContextInterface, config_path: str,
|
||||
requirement: interfaces.configuration.RequirementInterface,
|
||||
progress_callback: constants.ProgressCallback) -> None:
|
||||
"""Stacks the various layers and attaches these to a specific requirement
|
||||
"""Stacks the various layers and attaches these to a specific
|
||||
requirement.
|
||||
|
||||
Args:
|
||||
context: Context on which to operate
|
||||
@@ -152,8 +153,9 @@ class LayerStacker(interfaces.automagic.AutomagicInterface):
|
||||
def find_suitable_requirements(self, context: interfaces.context.ContextInterface, config_path: str,
|
||||
requirement: interfaces.configuration.RequirementInterface,
|
||||
stacked_layers: List[str]) -> Optional[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.
|
||||
"""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.
|
||||
|
||||
Returns:
|
||||
A tuple of a configuration path and layer name for the top of the stacked layers
|
||||
|
||||
@@ -19,7 +19,7 @@ BannersType = Dict[bytes, List[str]]
|
||||
|
||||
|
||||
class SymbolBannerCache(interfaces.automagic.AutomagicInterface):
|
||||
"""Runs through all symbols tables and caches their banners"""
|
||||
"""Runs through all symbols tables and caches their banners."""
|
||||
|
||||
# Since this is necessary for ConstructionMagic, we set a lower priority
|
||||
# The user would run it eventually either way, but running it first means it can be used that run
|
||||
@@ -70,7 +70,7 @@ class SymbolBannerCache(interfaces.automagic.AutomagicInterface):
|
||||
pickle.dump(banners, f)
|
||||
|
||||
def __call__(self, context, config_path, configurable, progress_callback = None):
|
||||
"""Runs the automagic over the configurable"""
|
||||
"""Runs the automagic over the configurable."""
|
||||
|
||||
# Bomb out if we're just the generic interface
|
||||
if self.os is None:
|
||||
|
||||
@@ -14,7 +14,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SymbolFinder(interfaces.automagic.AutomagicInterface):
|
||||
"""Symbol loader based on signature strings"""
|
||||
"""Symbol loader based on signature strings."""
|
||||
priority = 40
|
||||
|
||||
banner_config_key = "banner" # type: str
|
||||
@@ -28,7 +28,8 @@ class SymbolFinder(interfaces.automagic.AutomagicInterface):
|
||||
|
||||
@property
|
||||
def banners(self) -> symbol_cache.BannersType:
|
||||
"""Creates a cached copy of the results, but only it's been requested"""
|
||||
"""Creates a cached copy of the results, but only it's been
|
||||
requested."""
|
||||
if not self._banners:
|
||||
if not self.banner_cache:
|
||||
raise RuntimeError("Cache has not been properly defined for {}".format(self.__class__.__name__))
|
||||
@@ -40,7 +41,8 @@ class SymbolFinder(interfaces.automagic.AutomagicInterface):
|
||||
config_path: str,
|
||||
requirement: interfaces.configuration.RequirementInterface,
|
||||
progress_callback: constants.ProgressCallback = None) -> None:
|
||||
"""Searches for SymbolTableRequirements and attempt to populate them"""
|
||||
"""Searches for SymbolTableRequirements and attempt to populate
|
||||
them."""
|
||||
|
||||
# Bomb out early if our details haven't been configured
|
||||
if self.symbol_class is None:
|
||||
@@ -73,8 +75,8 @@ class SymbolFinder(interfaces.automagic.AutomagicInterface):
|
||||
requirement: interfaces.configuration.ConstructableRequirementInterface,
|
||||
layer_name: str,
|
||||
progress_callback: constants.ProgressCallback = None) -> None:
|
||||
"""Accepts a context, config_path and SymbolTableRequirement, with a constructed layer_name
|
||||
and scans the layer for banners"""
|
||||
"""Accepts a context, config_path and SymbolTableRequirement, with a
|
||||
constructed layer_name and scans the layer for banners."""
|
||||
|
||||
# Bomb out early if there's no banners
|
||||
if not self.banners:
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""Module to identify the Directory Table Base and architecture of windows memory images
|
||||
"""Module to identify the Directory Table Base and architecture of windows
|
||||
memory images.
|
||||
|
||||
This module contains a PageMapScanner that scans a physical layer to identify self-referential pointers.
|
||||
All windows versions include a self-referential pointer in their Directory Table Base's top table, in order to
|
||||
@@ -37,10 +38,12 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DtbTest:
|
||||
"""This class generically contains the tests for a page based on a set of class parameters
|
||||
"""This class generically contains the tests for a page based on a set of
|
||||
class parameters.
|
||||
|
||||
When constructed it contains all the information necessary to extract a specific index from a page
|
||||
and determine whether it points back to that page's offset.
|
||||
When constructed it contains all the information necessary to
|
||||
extract a specific index from a page and determine whether it points
|
||||
back to that page's offset.
|
||||
"""
|
||||
|
||||
def __init__(self, layer_type: Type[layers.intel.Intel], ptr_struct: str, ptr_reference: int, mask: int) -> None:
|
||||
@@ -55,7 +58,8 @@ class DtbTest:
|
||||
return struct.unpack("<" + self.ptr_struct, value)[0]
|
||||
|
||||
def __call__(self, data: bytes, data_offset: int, page_offset: int) -> Optional[Tuple[int, Any]]:
|
||||
"""Tests a specific page in a chunk of data to see if it contains a self-referential pointer.
|
||||
"""Tests a specific page in a chunk of data to see if it contains a
|
||||
self-referential pointer.
|
||||
|
||||
Args:
|
||||
data: The chunk of data that contains the page to be scanned
|
||||
@@ -82,7 +86,8 @@ class DtbTest:
|
||||
return None
|
||||
|
||||
def second_pass(self, dtb: int, data: bytes, data_offset: int) -> Optional[Tuple[int, Any]]:
|
||||
"""Re-reads over the whole page to validate other records based on the number of pages marked user vs super
|
||||
"""Re-reads over the whole page to validate other records based on the
|
||||
number of pages marked user vs super.
|
||||
|
||||
Args:
|
||||
dtb: The identified dtb that needs validating
|
||||
@@ -128,11 +133,14 @@ class DtbTestPae(DtbTest):
|
||||
layer_type = layers.intel.WindowsIntelPAE, ptr_struct = "Q", ptr_reference = 0x3, mask = 0x3FFFFFFFFFF000)
|
||||
|
||||
def second_pass(self, dtb: int, data: bytes, data_offset: int) -> Optional[Tuple[int, Any]]:
|
||||
"""PAE top level directory tables contains four entries and the self-referential pointer occurs in the second
|
||||
level of tables (so as not to use up a full quarter of the space). This is very high in the space, and occurs
|
||||
in the fourht (last quarter) second-level table. The second-level tables appear always to come sequentially
|
||||
directly after the real dtb. The value for the real DTB is therefore four page earlier (and the fourth entry
|
||||
should point back to the `dtb` parameter this function was originally passed.
|
||||
"""PAE top level directory tables contains four entries and the self-
|
||||
referential pointer occurs in the second level of tables (so as not to
|
||||
use up a full quarter of the space). This is very high in the space,
|
||||
and occurs in the fourht (last quarter) second-level table. The
|
||||
second-level tables appear always to come sequentially directly after
|
||||
the real dtb. The value for the real DTB is therefore four page
|
||||
earlier (and the fourth entry should point back to the `dtb` parameter
|
||||
this function was originally passed.
|
||||
|
||||
Args:
|
||||
dtb: The identified self-referential pointer that needs validating
|
||||
@@ -153,7 +161,8 @@ class DtbTestPae(DtbTest):
|
||||
|
||||
|
||||
class DtbSelfReferential(DtbTest):
|
||||
"""A generic DTB test which looks for a self-referential pointer at *any* index within the page."""
|
||||
"""A generic DTB test which looks for a self-referential pointer at *any*
|
||||
index within the page."""
|
||||
|
||||
def __init__(self, layer_type: Type[layers.intel.Intel], ptr_struct: str, ptr_reference: int, mask: int) -> None:
|
||||
super().__init__(layer_type = layer_type, ptr_struct = ptr_struct, ptr_reference = ptr_reference, mask = mask)
|
||||
@@ -190,7 +199,8 @@ class DtbSelfRef64bit(DtbSelfReferential):
|
||||
|
||||
|
||||
class PageMapScanner(interfaces.layers.ScannerInterface):
|
||||
"""Scans through all pages using DTB tests to determine a dtb offset and architecture"""
|
||||
"""Scans through all pages using DTB tests to determine a dtb offset and
|
||||
architecture."""
|
||||
overlap = 0x4000
|
||||
thread_safe = True
|
||||
tests = [DtbTest32bit(), DtbTest64bit(), DtbTestPae()]
|
||||
@@ -210,13 +220,14 @@ class PageMapScanner(interfaces.layers.ScannerInterface):
|
||||
|
||||
|
||||
class WintelHelper(interfaces.automagic.AutomagicInterface):
|
||||
"""Windows DTB finder based on self-referential pointers
|
||||
"""Windows DTB finder based on self-referential pointers.
|
||||
|
||||
This class adheres to the :class:`~volatility.framework.interfaces.automagic.AutomagicInterface` interface
|
||||
and both determines the directory table base of an intel layer if one hasn't been specified, and constructs
|
||||
the intel layer if necessary (for example when reconstructing a pre-existing configuration).
|
||||
|
||||
It will scan for existing TranslationLayers that do not have a DTB using the :class:`PageMapScanner`"""
|
||||
It will scan for existing TranslationLayers that do not have a DTB using the :class:`PageMapScanner`
|
||||
"""
|
||||
priority = 20
|
||||
tests = [DtbTest32bit(), DtbTest64bit(), DtbTestPae()]
|
||||
|
||||
@@ -272,12 +283,16 @@ class WintelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
context: interfaces.context.ContextInterface,
|
||||
layer_name: str,
|
||||
progress_callback: constants.ProgressCallback = None) -> Optional[interfaces.layers.DataLayerInterface]:
|
||||
"""Attempts to determine and stack an intel layer on a physical layer where possible
|
||||
"""Attempts to determine and stack an intel layer on a physical layer
|
||||
where possible.
|
||||
|
||||
Where the DTB scan fails, it attempts a heuristic of checking for the DTB within a specific range.
|
||||
New versions of windows, with randomized self-referential pointers, appear to always load their dtb within
|
||||
a small specific range (`0x1a0000` and `0x1b0000`), so instead we scan for all self-referential pointers in
|
||||
that range, and ignore any that contain multiple self-references (since the DTB is very unlikely to point to
|
||||
Where the DTB scan fails, it attempts a heuristic of checking
|
||||
for the DTB within a specific range. New versions of windows,
|
||||
with randomized self-referential pointers, appear to always load
|
||||
their dtb within a small specific range (`0x1a0000` and
|
||||
`0x1b0000`), so instead we scan for all self-referential
|
||||
pointers in that range, and ignore any that contain multiple
|
||||
self-references (since the DTB is very unlikely to point to
|
||||
itself more than once).
|
||||
"""
|
||||
base_layer = context.layers[layer_name]
|
||||
@@ -348,15 +363,15 @@ class WintelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
|
||||
|
||||
class WinSwapLayers(interfaces.automagic.AutomagicInterface):
|
||||
"""Class to read swap_layers filenames from single-swap-layers,
|
||||
create the layers and populate the single-layers swap_layers"""
|
||||
"""Class to read swap_layers filenames from single-swap-layers, create the
|
||||
layers and populate the single-layers swap_layers."""
|
||||
|
||||
def __call__(self,
|
||||
context: interfaces.context.ContextInterface,
|
||||
config_path: str,
|
||||
requirement: interfaces.configuration.RequirementInterface,
|
||||
progress_callback: constants.ProgressCallback = None) -> None:
|
||||
"""Finds translation layers that can have swap layers added"""
|
||||
"""Finds translation layers that can have swap layers added."""
|
||||
path_join = interfaces.configuration.path_join
|
||||
self._translation_requirement = self.find_requirements(
|
||||
context, config_path, requirement, requirements.TranslationLayerRequirement, shortcut = False)
|
||||
@@ -398,7 +413,7 @@ class WinSwapLayers(interfaces.automagic.AutomagicInterface):
|
||||
def find_swap_requirement(config: str,
|
||||
requirement: requirements.TranslationLayerRequirement) \
|
||||
-> Tuple[str, Optional[requirements.LayerListRequirement]]:
|
||||
"""Takes a Translation layer and returns its swap_layer requirement"""
|
||||
"""Takes a Translation layer and returns its swap_layer requirement."""
|
||||
swap_req = None
|
||||
for req_name in requirement.requirements:
|
||||
req = requirement.requirements[req_name]
|
||||
@@ -411,7 +426,7 @@ class WinSwapLayers(interfaces.automagic.AutomagicInterface):
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
"""Returns the requirements of this plugin"""
|
||||
"""Returns the requirements of this plugin."""
|
||||
return [
|
||||
requirements.ListRequirement(
|
||||
name = "single_swap_locations",
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# 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
|
||||
#
|
||||
"""Contains standard Requirement types that all adhere to the :class:`~volatility.framework.interfaces.configuration.RequirementInterface`.
|
||||
|
||||
These requirement types allow plugins to request simple information types (such as strings, integers,
|
||||
etc) as well as indicating what they expect to be in the context (such as particular layers or symboltables).
|
||||
"""Contains standard Requirement types that all adhere to the :class:`~volatili
|
||||
ty.framework.interfaces.configuration.RequirementInterface`.
|
||||
|
||||
These requirement types allow plugins to request simple information
|
||||
types (such as strings, integers, etc) as well as indicating what they
|
||||
expect to be in the context (such as particular layers or symboltables).
|
||||
"""
|
||||
import abc
|
||||
import logging
|
||||
@@ -20,7 +21,8 @@ vollog = logging.getLogger(__name__)
|
||||
class MultiRequirement(configuration.RequirementInterface):
|
||||
"""Class to hold multiple requirements.
|
||||
|
||||
Technically the Interface could handle this, but it's an interface, so this is a concrete implementation.
|
||||
Technically the Interface could handle this, but it's an interface,
|
||||
so this is a concrete implementation.
|
||||
"""
|
||||
|
||||
def unsatisfied(self, context: configuration.ContextInterface,
|
||||
@@ -29,33 +31,35 @@ class MultiRequirement(configuration.RequirementInterface):
|
||||
|
||||
|
||||
class BooleanRequirement(configuration.SimpleTypeRequirement):
|
||||
"""A requirement type that contains a boolean value"""
|
||||
"""A requirement type that contains a boolean value."""
|
||||
# Note, this must be a separate class in order to differentiate between Booleans and other instance requirements
|
||||
|
||||
|
||||
class IntRequirement(configuration.SimpleTypeRequirement):
|
||||
"""A requirement type that contains a single integer"""
|
||||
"""A requirement type that contains a single integer."""
|
||||
instance_type = int # type: ClassVar[Type]
|
||||
|
||||
|
||||
class StringRequirement(configuration.SimpleTypeRequirement):
|
||||
"""A requirement type that contains a single unicode string"""
|
||||
"""A requirement type that contains a single unicode string."""
|
||||
# TODO: Maybe add string length limits?
|
||||
instance_type = str # type: ClassVar[Type]
|
||||
|
||||
|
||||
class URIRequirement(StringRequirement):
|
||||
"""A requirement type that contains a single unicode string that is a valid URI"""
|
||||
"""A requirement type that contains a single unicode string that is a valid
|
||||
URI."""
|
||||
# TODO: Maybe a a check that to unsatisfied that the path really is a URL?
|
||||
|
||||
|
||||
class BytesRequirement(configuration.SimpleTypeRequirement):
|
||||
"""A requirement type that contains a byte string"""
|
||||
"""A requirement type that contains a byte string."""
|
||||
instance_type = bytes # type: ClassVar[Type]
|
||||
|
||||
|
||||
class ListRequirement(configuration.RequirementInterface):
|
||||
"""Allows for a list of a specific type of requirement (all of which must be met for this requirement to be met) to be specified
|
||||
"""Allows for a list of a specific type of requirement (all of which must
|
||||
be met for this requirement to be met) to be specified.
|
||||
|
||||
This roughly correlates to allowing a number of arguments to follow a command line parameter,
|
||||
such as a list of integers or a list of strings.
|
||||
@@ -70,7 +74,7 @@ class ListRequirement(configuration.RequirementInterface):
|
||||
min_elements: Optional[int] = None,
|
||||
*args,
|
||||
**kwargs) -> None:
|
||||
"""Constructs the object
|
||||
"""Constructs the object.
|
||||
|
||||
Args:
|
||||
element_type: The (requirement) type of each element within the list
|
||||
@@ -86,7 +90,8 @@ class ListRequirement(configuration.RequirementInterface):
|
||||
|
||||
def unsatisfied(self, context: interfaces.context.ContextInterface,
|
||||
config_path: str) -> Dict[str, configuration.RequirementInterface]:
|
||||
"""Check the types on each of the returned values and their number and then call the element type's check for each one"""
|
||||
"""Check the types on each of the returned values and their number and
|
||||
then call the element type's check for each one."""
|
||||
config_path = configuration.path_join(config_path, self.name)
|
||||
default = None
|
||||
value = self.config_value(context, config_path, default)
|
||||
@@ -113,10 +118,10 @@ class ListRequirement(configuration.RequirementInterface):
|
||||
|
||||
|
||||
class ChoiceRequirement(configuration.RequirementInterface):
|
||||
"""Allows one from a choice of strings"""
|
||||
"""Allows one from a choice of strings."""
|
||||
|
||||
def __init__(self, choices: List[str], *args, **kwargs) -> None:
|
||||
"""Constructs the object
|
||||
"""Constructs the object.
|
||||
|
||||
Args:
|
||||
choices: A list of possible string options that can be chosen from
|
||||
@@ -128,7 +133,8 @@ class ChoiceRequirement(configuration.RequirementInterface):
|
||||
|
||||
def unsatisfied(self, context: interfaces.context.ContextInterface,
|
||||
config_path: str) -> Dict[str, configuration.RequirementInterface]:
|
||||
"""Validates the provided value to ensure it is one of the available choices"""
|
||||
"""Validates the provided value to ensure it is one of the available
|
||||
choices."""
|
||||
config_path = configuration.path_join(config_path, self.name)
|
||||
value = self.config_value(context, config_path)
|
||||
if value not in self.choices:
|
||||
@@ -138,11 +144,12 @@ class ChoiceRequirement(configuration.RequirementInterface):
|
||||
|
||||
|
||||
class ComplexListRequirement(MultiRequirement, configuration.ConfigurableRequirementInterface, metaclass = abc.ABCMeta):
|
||||
"""Allows a variable length list of requirements"""
|
||||
"""Allows a variable length list of requirements."""
|
||||
|
||||
def unsatisfied(self, context: interfaces.context.ContextInterface,
|
||||
config_path: str) -> Dict[str, configuration.RequirementInterface]:
|
||||
"""Validates the provided value to ensure it is one of the available choices"""
|
||||
"""Validates the provided value to ensure it is one of the available
|
||||
choices."""
|
||||
config_path = configuration.path_join(config_path, self.name)
|
||||
ret_list = super().unsatisfied(context, config_path)
|
||||
if ret_list:
|
||||
@@ -162,11 +169,12 @@ class ComplexListRequirement(MultiRequirement, configuration.ConfigurableRequire
|
||||
|
||||
@abc.abstractmethod
|
||||
def construct(self, context: interfaces.context.ContextInterface, config_path: str) -> None:
|
||||
"""Method for constructing within the context any required elements from subrequirements"""
|
||||
"""Method for constructing within the context any required elements
|
||||
from subrequirements."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def new_requirement(self, index) -> configuration.RequirementInterface:
|
||||
"""Builds a new requirement based on the specified index"""
|
||||
"""Builds a new requirement based on the specified index."""
|
||||
|
||||
def build_configuration(self, context: interfaces.context.ContextInterface, config_path: str,
|
||||
_: Any) -> configuration.HierarchicalDict:
|
||||
@@ -187,10 +195,11 @@ class ComplexListRequirement(MultiRequirement, configuration.ConfigurableRequire
|
||||
|
||||
|
||||
class LayerListRequirement(ComplexListRequirement):
|
||||
"""Allows a variable length list of layers that must exist """
|
||||
"""Allows a variable length list of layers that must exist."""
|
||||
|
||||
def construct(self, context: interfaces.context.ContextInterface, config_path: str) -> None:
|
||||
"""Method for constructing within the context any required elements from subrequirements"""
|
||||
"""Method for constructing within the context any required elements
|
||||
from subrequirements."""
|
||||
new_config_path = configuration.path_join(config_path, self.name)
|
||||
num_layers_path = configuration.path_join(new_config_path, "number_of_elements")
|
||||
number_of_layers = context.config[num_layers_path]
|
||||
@@ -202,14 +211,15 @@ class LayerListRequirement(ComplexListRequirement):
|
||||
layer_req.construct(context, new_config_path)
|
||||
|
||||
def new_requirement(self, index) -> configuration.RequirementInterface:
|
||||
"""Constructs a new requirement based on the specified index"""
|
||||
"""Constructs a new requirement based on the specified index."""
|
||||
return TranslationLayerRequirement(
|
||||
name = self.name + str(index), description = "Layer for swap space", optional = False)
|
||||
|
||||
|
||||
class TranslationLayerRequirement(configuration.ConstructableRequirementInterface,
|
||||
configuration.ConfigurableRequirementInterface):
|
||||
"""Class maintaining the limitations on what sort of translation layers are acceptable"""
|
||||
"""Class maintaining the limitations on what sort of translation layers are
|
||||
acceptable."""
|
||||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
@@ -218,7 +228,7 @@ class TranslationLayerRequirement(configuration.ConstructableRequirementInterfac
|
||||
optional: bool = False,
|
||||
oses: List = None,
|
||||
architectures: List = None) -> None:
|
||||
"""Constructs a Translation Layer Requirement
|
||||
"""Constructs a Translation Layer Requirement.
|
||||
|
||||
The configuration option's value will be the name of the layer once it exists in the store
|
||||
|
||||
@@ -240,7 +250,8 @@ class TranslationLayerRequirement(configuration.ConstructableRequirementInterfac
|
||||
|
||||
def unsatisfied(self, context: interfaces.context.ContextInterface,
|
||||
config_path: str) -> Dict[str, configuration.RequirementInterface]:
|
||||
"""Validate that the value is a valid layer name and that the layer adheres to the requirements"""
|
||||
"""Validate that the value is a valid layer name and that the layer
|
||||
adheres to the requirements."""
|
||||
config_path = configuration.path_join(config_path, self.name)
|
||||
value = self.config_value(context, config_path, None)
|
||||
if isinstance(value, str):
|
||||
@@ -270,7 +281,8 @@ class TranslationLayerRequirement(configuration.ConstructableRequirementInterfac
|
||||
return {config_path: self}
|
||||
|
||||
def construct(self, context: interfaces.context.ContextInterface, config_path: str) -> None:
|
||||
"""Constructs the appropriate layer and adds it based on the class parameter"""
|
||||
"""Constructs the appropriate layer and adds it based on the class
|
||||
parameter."""
|
||||
config_path = configuration.path_join(config_path, self.name)
|
||||
|
||||
# Determine the layer name
|
||||
@@ -295,17 +307,20 @@ class TranslationLayerRequirement(configuration.ConstructableRequirementInterfac
|
||||
|
||||
def build_configuration(self, context: interfaces.context.ContextInterface, _: str,
|
||||
value: Any) -> configuration.HierarchicalDict:
|
||||
"""Builds the appropriate configuration for the specified requirement"""
|
||||
"""Builds the appropriate configuration for the specified
|
||||
requirement."""
|
||||
return context.layers[value].build_configuration()
|
||||
|
||||
|
||||
class SymbolTableRequirement(configuration.ConstructableRequirementInterface,
|
||||
configuration.ConfigurableRequirementInterface):
|
||||
"""Class maintaining the limitations on what sort of symbol spaces are acceptable"""
|
||||
"""Class maintaining the limitations on what sort of symbol spaces are
|
||||
acceptable."""
|
||||
|
||||
def unsatisfied(self, context: interfaces.context.ContextInterface,
|
||||
config_path: str) -> Dict[str, configuration.RequirementInterface]:
|
||||
"""Validate that the value is a valid within the symbol space of the provided context"""
|
||||
"""Validate that the value is a valid within the symbol space of the
|
||||
provided context."""
|
||||
config_path = configuration.path_join(config_path, self.name)
|
||||
value = self.config_value(context, config_path, None)
|
||||
if not isinstance(value, str):
|
||||
@@ -320,7 +335,8 @@ class SymbolTableRequirement(configuration.ConstructableRequirementInterface,
|
||||
return {}
|
||||
|
||||
def construct(self, context: interfaces.context.ContextInterface, config_path: str) -> None:
|
||||
"""Constructs the symbol space within the context based on the subrequirements"""
|
||||
"""Constructs the symbol space within the context based on the
|
||||
subrequirements."""
|
||||
config_path = configuration.path_join(config_path, self.name)
|
||||
# Determine the space name
|
||||
name = context.symbol_space.free_table_name(self.name)
|
||||
@@ -348,7 +364,8 @@ class SymbolTableRequirement(configuration.ConstructableRequirementInterface,
|
||||
|
||||
def build_configuration(self, context: interfaces.context.ContextInterface, _: str,
|
||||
value: Any) -> configuration.HierarchicalDict:
|
||||
"""Builds the appropriate configuration for the specified requirement"""
|
||||
"""Builds the appropriate configuration for the specified
|
||||
requirement."""
|
||||
return context.symbol_space[value].build_configuration()
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# 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
|
||||
#
|
||||
"""Volatility 3 Constants
|
||||
"""Volatility 3 Constants.
|
||||
|
||||
Stores all the constant values that are generally fixed throughout volatility
|
||||
This includes default scanning block sizes, etc."""
|
||||
Stores all the constant values that are generally fixed throughout
|
||||
volatility This includes default scanning block sizes, etc.
|
||||
"""
|
||||
import enum
|
||||
import os.path
|
||||
import sys
|
||||
@@ -61,7 +62,8 @@ ProgressCallback = Optional[Callable[[float, str], None]]
|
||||
|
||||
|
||||
class Parallelism(enum.IntEnum):
|
||||
"""An enumeration listing the different types of parallelism applied to volatility"""
|
||||
"""An enumeration listing the different types of parallelism applied to
|
||||
volatility."""
|
||||
Off = 0
|
||||
Threading = 1
|
||||
Multiprocessing = 2
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# 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
|
||||
#
|
||||
"""Volatility 3 Linux Constants
|
||||
"""Volatility 3 Linux Constants.
|
||||
|
||||
Linux-specific values that aren't found in debug symbols"""
|
||||
Linux-specific values that aren't found in debug symbols
|
||||
"""
|
||||
|
||||
# arch/x86/include/asm/page_types.h
|
||||
PAGE_SHIFT = 12
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# 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
|
||||
#
|
||||
"""Volatility 3 Linux Constants
|
||||
"""Volatility 3 Linux Constants.
|
||||
|
||||
Windows-specific values that aren't found in debug symbols"""
|
||||
Windows-specific values that aren't found in debug symbols
|
||||
"""
|
||||
|
||||
KERNEL_MODULE_NAMES = ["ntkrnlmp", "ntkrnlpa", "ntkrpamp", "ntoskrnl"]
|
||||
"""The list of names that kernel modules can have within the windows OS"""
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# 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
|
||||
#
|
||||
"""A `Context` maintains the accumulated state required for various plugins and framework functions.
|
||||
"""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.
|
||||
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
|
||||
@@ -14,7 +16,7 @@ from volatility.framework import constants, interfaces, symbols, exceptions
|
||||
|
||||
|
||||
class Context(interfaces.context.ContextInterface):
|
||||
"""Maintains the context within which to construct objects
|
||||
"""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
|
||||
@@ -37,7 +39,8 @@ class Context(interfaces.context.ContextInterface):
|
||||
|
||||
@property
|
||||
def config(self) -> interfaces.configuration.HierarchicalDict:
|
||||
"""Returns a mutable copy of the configuration, but does not allow the whole configuration to be altered"""
|
||||
"""Returns a mutable copy of the configuration, but does not allow the
|
||||
whole configuration to be altered."""
|
||||
return self._config
|
||||
|
||||
@config.setter
|
||||
@@ -48,19 +51,20 @@ class Context(interfaces.context.ContextInterface):
|
||||
|
||||
@property
|
||||
def symbol_space(self) -> interfaces.symbols.SymbolSpaceInterface:
|
||||
"""The space of all symbols that can be accessed within this context.
|
||||
"""
|
||||
"""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"""
|
||||
"""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
|
||||
"""Adds a named translation layer to the context.
|
||||
|
||||
Args:
|
||||
layer: The layer to be added to the memory
|
||||
@@ -79,7 +83,8 @@ class Context(interfaces.context.ContextInterface):
|
||||
offset: int,
|
||||
native_layer_name: Optional[str] = None,
|
||||
**arguments) -> interfaces.objects.ObjectInterface:
|
||||
"""Object factory, takes a context, symbol, offset and optional layername
|
||||
"""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.
|
||||
@@ -116,8 +121,7 @@ class Context(interfaces.context.ContextInterface):
|
||||
offset: int,
|
||||
native_layer_name: Optional[str] = None,
|
||||
size: Optional[int] = None) -> interfaces.context.ModuleInterface:
|
||||
"""
|
||||
Constructs a new os-independent module
|
||||
"""Constructs a new os-independent module.
|
||||
|
||||
Args:
|
||||
module_name: The name of the module
|
||||
@@ -143,7 +147,7 @@ class Context(interfaces.context.ContextInterface):
|
||||
|
||||
|
||||
def get_module_wrapper(method: str) -> Callable:
|
||||
"""Returns a symbol using the symbol_table_name of the Module"""
|
||||
"""Returns a symbol using the symbol_table_name of the Module."""
|
||||
|
||||
def wrapper(self, name: str) -> Callable:
|
||||
if constants.BANG not in name:
|
||||
@@ -168,7 +172,8 @@ class Module(interfaces.context.ModuleInterface):
|
||||
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
|
||||
"""Returns an object created using the symbol_table_name and layer_name
|
||||
of the Module.
|
||||
|
||||
Args:
|
||||
object_type: Name of the type/enumeration (within the module) to construct
|
||||
@@ -202,9 +207,11 @@ class Module(interfaces.context.ModuleInterface):
|
||||
native_layer_name: Optional[str] = None,
|
||||
absolute: bool = False,
|
||||
**kwargs) -> 'interfaces.objects.ObjectInterface':
|
||||
"""Returns an object based on a specific symbol (containing type and offset information) and
|
||||
the layer_name of the Module. This will throw a ValueError if the symbol does not contain an associated type,
|
||||
or if the symbol name is invalid. It will throw a SymbolError if the symbol cannot be found.
|
||||
"""Returns an object based on a specific symbol (containing type and
|
||||
offset information) and the layer_name of the Module. This will throw
|
||||
a ValueError if the symbol does not contain an associated type, or if
|
||||
the symbol name is invalid. It will throw a SymbolError if the symbol
|
||||
cannot be found.
|
||||
|
||||
Args:
|
||||
symbol_name: Name of the symbol (within the module) to construct
|
||||
@@ -273,10 +280,12 @@ class SizedModule(Module):
|
||||
@property # type: ignore # FIXME: mypy #5107
|
||||
@functools.lru_cache()
|
||||
def hash(self) -> str:
|
||||
"""Hashes the module for equality checks
|
||||
"""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"""
|
||||
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")
|
||||
@@ -284,7 +293,8 @@ class SizedModule(Module):
|
||||
'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"""
|
||||
"""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:
|
||||
@@ -295,15 +305,18 @@ class SizedModule(Module):
|
||||
|
||||
|
||||
class ModuleCollection:
|
||||
"""Class to contain a collection of SizedModules and reason about their contents"""
|
||||
"""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)
|
||||
"""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
|
||||
All 0 sized modules will have identical hashes and are therefore
|
||||
included in the deduplicated version
|
||||
"""
|
||||
new_modules = []
|
||||
seen = set() # type: Set[str]
|
||||
@@ -315,7 +328,8 @@ class ModuleCollection:
|
||||
|
||||
@property
|
||||
def modules(self) -> Dict[str, List[SizedModule]]:
|
||||
"""A name indexed dictionary of modules using that name in this collection"""
|
||||
"""A name indexed dictionary of modules using that name in this
|
||||
collection."""
|
||||
return self._generate_module_dict(self._modules)
|
||||
|
||||
@classmethod
|
||||
@@ -328,7 +342,9 @@ class ModuleCollection:
|
||||
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"""
|
||||
"""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:
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
#
|
||||
"""A list of potential exceptions that volatility can throw.
|
||||
|
||||
These include exceptions that can be thrown on errors by the symbol space or symbol tables, and by layers when
|
||||
an address is invalid. The :class:`PagedInvalidAddressException` contains information about the size of the invalid
|
||||
page.
|
||||
These include exceptions that can be thrown on errors by the symbol
|
||||
space or symbol tables, and by layers when an address is invalid. The
|
||||
:class:`PagedInvalidAddressException` contains information about the
|
||||
size of the invalid page.
|
||||
"""
|
||||
from typing import Dict
|
||||
|
||||
@@ -13,23 +14,25 @@ from volatility.framework import interfaces
|
||||
|
||||
|
||||
class VolatilityException(Exception):
|
||||
"""Class to allow filtering of all VolatilityExceptions"""
|
||||
"""Class to allow filtering of all VolatilityExceptions."""
|
||||
|
||||
|
||||
class PluginVersionException(VolatilityException):
|
||||
"""Class to allow determining that a required plugin has an invalid version"""
|
||||
"""Class to allow determining that a required plugin has an invalid
|
||||
version."""
|
||||
|
||||
|
||||
class PluginRequirementException(VolatilityException):
|
||||
"""Class to allow plugins to indicate that a requirement has not been fulfilled"""
|
||||
"""Class to allow plugins to indicate that a requirement has not been
|
||||
fulfilled."""
|
||||
|
||||
|
||||
class SymbolError(VolatilityException):
|
||||
"""Thrown when a symbol lookup has failed"""
|
||||
"""Thrown when a symbol lookup has failed."""
|
||||
|
||||
|
||||
class InvalidAddressException(VolatilityException):
|
||||
"""Thrown when an address is not valid in the space it was requested"""
|
||||
"""Thrown when an address is not valid in the space it was requested."""
|
||||
|
||||
def __init__(self, layer_name: str, invalid_address: int, *args) -> None:
|
||||
super().__init__(*args)
|
||||
@@ -38,9 +41,11 @@ class InvalidAddressException(VolatilityException):
|
||||
|
||||
|
||||
class PagedInvalidAddressException(InvalidAddressException):
|
||||
"""Thrown when an address is not valid in the paged space in which it was request
|
||||
"""Thrown when an address is not valid in the paged space in which it was
|
||||
request.
|
||||
|
||||
Includes the invalid address and the number of bits of the address that are invalid
|
||||
Includes the invalid address and the number of bits of the address
|
||||
that are invalid
|
||||
"""
|
||||
|
||||
def __init__(self, layer_name: str, invalid_address: int, invalid_bits: int, entry: int, *args) -> None:
|
||||
@@ -50,10 +55,11 @@ class PagedInvalidAddressException(InvalidAddressException):
|
||||
|
||||
|
||||
class SwappedInvalidAddressException(PagedInvalidAddressException):
|
||||
"""Thrown when an address is not valid in the paged space in which it was requested,
|
||||
but expected to be in swap space
|
||||
"""Thrown when an address is not valid in the paged space in which it was
|
||||
requested, but expected to be in swap space.
|
||||
|
||||
Includes the swap lookup"""
|
||||
Includes the swap lookup
|
||||
"""
|
||||
|
||||
def __init__(self, layer_name: str, invalid_address: int, invalid_bits: int, entry: int, swap_offset: int,
|
||||
*args) -> None:
|
||||
@@ -62,7 +68,8 @@ class SwappedInvalidAddressException(PagedInvalidAddressException):
|
||||
|
||||
|
||||
class InvalidDataException(VolatilityException):
|
||||
"""Thrown when an object contains some data known to be invalid for that structure"""
|
||||
"""Thrown when an object contains some data known to be invalid for that
|
||||
structure."""
|
||||
|
||||
def __init__(self, invalid_object: object, *args) -> None:
|
||||
super().__init__(invalid_object, *args)
|
||||
@@ -70,11 +77,11 @@ class InvalidDataException(VolatilityException):
|
||||
|
||||
|
||||
class SymbolSpaceError(VolatilityException):
|
||||
"""Thrown when an error occurs dealing with Symbols and Symbolspaces"""
|
||||
"""Thrown when an error occurs dealing with Symbols and Symbolspaces."""
|
||||
|
||||
|
||||
class LayerException(VolatilityException):
|
||||
"""Thrown when an error occurs dealing with memory and layers"""
|
||||
"""Thrown when an error occurs dealing with memory and layers."""
|
||||
|
||||
def __init__(self, layer_name: str, *args) -> None:
|
||||
super().__init__(*args)
|
||||
@@ -82,11 +89,12 @@ class LayerException(VolatilityException):
|
||||
|
||||
|
||||
class StructureException(VolatilityException):
|
||||
"""Thrown when an error occurs dealing with an expected structure type"""
|
||||
"""Thrown when an error occurs dealing with an expected structure type."""
|
||||
|
||||
|
||||
class MissingStructureException(VolatilityException):
|
||||
"""Thrown when an error occurs due to an expected structure not being present"""
|
||||
"""Thrown when an error occurs due to an expected structure not being
|
||||
present."""
|
||||
|
||||
|
||||
class UnsatisfiedException(VolatilityException):
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# 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
|
||||
#
|
||||
"""The interfaces module contains the API interface for the core volatility framework
|
||||
"""The interfaces module contains the API interface for the core volatility
|
||||
framework.
|
||||
|
||||
These interfaces should help developers attempting to write components for the main framework
|
||||
and help them understand how to use the internal components of volatility to write plugins.
|
||||
These interfaces should help developers attempting to write components
|
||||
for the main framework and help them understand how to use the internal
|
||||
components of volatility to write plugins.
|
||||
"""
|
||||
|
||||
# Import the submodules we want people to be able to use without importing them themselves
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# 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
|
||||
#
|
||||
"""Defines the automagic interfaces for populating the context before a plugin runs
|
||||
"""Defines the automagic interfaces for populating the context before a plugin
|
||||
runs.
|
||||
|
||||
Automagic objects attempt to automatically fill configuration values that a user has not filled.
|
||||
Automagic objects attempt to automatically fill configuration values
|
||||
that a user has not filled.
|
||||
"""
|
||||
from abc import ABCMeta
|
||||
from typing import Any, List, Optional, Tuple, Union, Type
|
||||
@@ -13,7 +15,8 @@ from volatility.framework.configuration import requirements
|
||||
|
||||
|
||||
class AutomagicInterface(interfaces.configuration.ConfigurableInterface, metaclass = ABCMeta):
|
||||
"""Class that defines an automagic component that can help fulfill `Requirements`
|
||||
"""Class that defines an automagic component that can help fulfill
|
||||
`Requirements`
|
||||
|
||||
These classes are callable with the following parameters:
|
||||
|
||||
@@ -47,7 +50,7 @@ class AutomagicInterface(interfaces.configuration.ConfigurableInterface, metacla
|
||||
config_path: str,
|
||||
requirement: interfaces.configuration.RequirementInterface,
|
||||
progress_callback: constants.ProgressCallback = None) -> Optional[List[Any]]:
|
||||
"""Runs the automagic over the configurable"""
|
||||
"""Runs the automagic over the configurable."""
|
||||
return []
|
||||
|
||||
# TODO: requirement_type can be made UnionType[Type[T], Tuple[Type[T], ...]]
|
||||
@@ -60,7 +63,8 @@ class AutomagicInterface(interfaces.configuration.ConfigurableInterface, metacla
|
||||
requirement_type: Union[Tuple[Type[interfaces.configuration.RequirementInterface], ...], Type[
|
||||
interfaces.configuration.RequirementInterface]],
|
||||
shortcut: bool = True) -> List[Tuple[str, interfaces.configuration.RequirementInterface]]:
|
||||
"""Determines if there is actually an unfulfilled `Requirement` waiting
|
||||
"""Determines if there is actually an unfulfilled `Requirement`
|
||||
waiting.
|
||||
|
||||
This ensures we do not carry out an expensive search when there is no need for a particular `Requirement`
|
||||
|
||||
@@ -89,10 +93,11 @@ class AutomagicInterface(interfaces.configuration.ConfigurableInterface, metacla
|
||||
|
||||
|
||||
class StackerLayerInterface(metaclass = ABCMeta):
|
||||
"""Class that takes a lower layer and attempts to build on it
|
||||
"""Class that takes a lower layer and attempts to build on it.
|
||||
|
||||
stack_order determines the order (from low to high) that stacking layers
|
||||
should be attempted lower levels should have lower `stack_orders`
|
||||
stack_order determines the order (from low to high) that stacking
|
||||
layers should be attempted lower levels should have lower
|
||||
`stack_orders`
|
||||
"""
|
||||
|
||||
stack_order = 0
|
||||
@@ -102,8 +107,8 @@ class StackerLayerInterface(metaclass = ABCMeta):
|
||||
context: interfaces.context.ContextInterface,
|
||||
layer_name: str,
|
||||
progress_callback: constants.ProgressCallback = None) -> Optional[interfaces.layers.DataLayerInterface]:
|
||||
"""
|
||||
Method to determine whether this builder can operate on the named layer. If so, modify the context appropriately.
|
||||
"""Method to determine whether this builder can operate on the named
|
||||
layer. If so, modify the context appropriately.
|
||||
|
||||
Returns the name of any new layer stacked on top of this layer or None. The stacking is therefore strictly
|
||||
linear rather than tree driven.
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
# 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
|
||||
#
|
||||
"""The configuration module contains classes and functions for interacting with the
|
||||
configuration and requirement trees.
|
||||
"""The configuration module contains classes and functions for interacting with
|
||||
the configuration and requirement trees.
|
||||
|
||||
Volatility plugins can specify a list of requirements (which may have subrequirements, thus forming a requirement tree).
|
||||
These requirement trees can contain values, which are contained in a complementary configuration tree. These two trees
|
||||
act as a protocol between the plugins and users. The plugins provide requirements that must be fulfilled, and the users
|
||||
provide configurations values that fulfill those requirements. Where the user does not provide sufficient configuration
|
||||
values, automagic modules may extend the configuration tree themselves.
|
||||
Volatility plugins can specify a list of requirements (which may have
|
||||
subrequirements, thus forming a requirement tree). These requirement
|
||||
trees can contain values, which are contained in a complementary
|
||||
configuration tree. These two trees act as a protocol between the
|
||||
plugins and users. The plugins provide requirements that must be
|
||||
fulfilled, and the users provide configurations values that fulfill
|
||||
those requirements. Where the user does not provide sufficient
|
||||
configuration values, automagic modules may extend the configuration
|
||||
tree themselves.
|
||||
"""
|
||||
|
||||
import collections.abc
|
||||
@@ -35,30 +39,29 @@ ConfigSimpleType = Union[SimpleTypes, List[SimpleTypes]]
|
||||
|
||||
|
||||
def path_join(*args) -> str:
|
||||
"""Joins configuration paths together"""
|
||||
"""Joins configuration paths together."""
|
||||
# If a path element (particularly the first) is empty, then remove it from the list
|
||||
args = tuple([arg for arg in args if arg])
|
||||
return CONFIG_SEPARATOR.join(args)
|
||||
|
||||
|
||||
def parent_path(value: str) -> str:
|
||||
"""Returns the parent configuration path from a configuration path"""
|
||||
"""Returns the parent configuration path from a configuration path."""
|
||||
return CONFIG_SEPARATOR.join(value.split(CONFIG_SEPARATOR)[:-1])
|
||||
|
||||
|
||||
def path_depth(path: str, depth: int = 1) -> str:
|
||||
"""Returns the `path` up to a certain depth
|
||||
"""Returns the `path` up to a certain depth.
|
||||
|
||||
Note that `depth` can be negative (such as `-x`) and will return all elements except for the last `x` components
|
||||
Note that `depth` can be negative (such as `-x`) and will return all
|
||||
elements except for the last `x` components
|
||||
"""
|
||||
return path_join(path.split(CONFIG_SEPARATOR)[:depth])
|
||||
|
||||
|
||||
class HierarchicalDict(collections.abc.Mapping):
|
||||
"""The core of configuration data, it is a mapping class that stores keys within itself, and also stores lower
|
||||
hierarchies.
|
||||
|
||||
"""
|
||||
"""The core of configuration data, it is a mapping class that stores keys
|
||||
within itself, and also stores lower hierarchies."""
|
||||
|
||||
def __init__(self, initial_dict: Dict[str, 'SimpleTypeRequirement'] = None,
|
||||
separator: str = CONFIG_SEPARATOR) -> None:
|
||||
@@ -83,37 +86,37 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
|
||||
@property
|
||||
def separator(self) -> str:
|
||||
"""Specifies the hierarchy separator in use in this HierarchyDict"""
|
||||
"""Specifies the hierarchy separator in use in this HierarchyDict."""
|
||||
return self._separator
|
||||
|
||||
@property
|
||||
def data(self) -> Dict:
|
||||
"""Returns just the data-containing mappings on this level of the Hierarchy"""
|
||||
"""Returns just the data-containing mappings on this level of the
|
||||
Hierarchy."""
|
||||
return self._data.copy()
|
||||
|
||||
def _key_head(self, key: str) -> str:
|
||||
"""Returns the first division of a key based on the dict separator,
|
||||
or the full key if the separator is not present
|
||||
"""
|
||||
"""Returns the first division of a key based on the dict separator, or
|
||||
the full key if the separator is not present."""
|
||||
if self.separator in key:
|
||||
return key[:key.index(self.separator)]
|
||||
else:
|
||||
return key
|
||||
|
||||
def _key_tail(self, key: str) -> str:
|
||||
"""Returns all but the first division of a key based on the dict separator,
|
||||
or None if the separator is not in the key
|
||||
"""
|
||||
"""Returns all but the first division of a key based on the dict
|
||||
separator, or None if the separator is not in the key."""
|
||||
if self.separator in key:
|
||||
return key[key.index(self.separator) + 1:]
|
||||
return ''
|
||||
|
||||
def __iter__(self):
|
||||
"""Returns an iterator object that supports the iterator protocol"""
|
||||
"""Returns an iterator object that supports the iterator protocol."""
|
||||
return self.generator()
|
||||
|
||||
def generator(self) -> Generator[str, None, None]:
|
||||
"""A generator for the data in this level and lower levels of this mapping
|
||||
"""A generator for the data in this level and lower levels of this
|
||||
mapping.
|
||||
|
||||
Returns:
|
||||
Returns each item in the top level data, and then all subkeys in a depth first order
|
||||
@@ -125,7 +128,8 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
yield subdict_key + self.separator + key
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Gets an item, traversing down the trees to get to the final value"""
|
||||
"""Gets an item, traversing down the trees to get to the final
|
||||
value."""
|
||||
try:
|
||||
if self.separator in key:
|
||||
subdict = self._subdict[self._key_head(key)]
|
||||
@@ -136,11 +140,11 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
raise KeyError(key)
|
||||
|
||||
def __setitem__(self, key: str, value: Any) -> None:
|
||||
"""Sets an item or creates a subdict and sets the item within that"""
|
||||
"""Sets an item or creates a subdict and sets the item within that."""
|
||||
self._setitem(key, value)
|
||||
|
||||
def _setitem(self, key: str, value: Any, is_data: bool = True) -> None:
|
||||
"""Set an item or appends a whole subtree at a key location"""
|
||||
"""Set an item or appends a whole subtree at a key location."""
|
||||
if self.separator in key:
|
||||
subdict = self._subdict.get(self._key_head(key), HierarchicalDict(separator = self.separator))
|
||||
subdict._setitem(self._key_tail(key), value, is_data)
|
||||
@@ -156,7 +160,8 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
self._subdict[key] = value
|
||||
|
||||
def _sanitize_value(self, value: Any) -> ConfigSimpleType:
|
||||
"""Method to ensure all values are standard values and not volatility objects containing contexts"""
|
||||
"""Method to ensure all values are standard values and not volatility
|
||||
objects containing contexts."""
|
||||
if isinstance(value, bool):
|
||||
return bool(value)
|
||||
elif isinstance(value, int):
|
||||
@@ -177,7 +182,7 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
raise TypeError("Invalid type stored in configuration")
|
||||
|
||||
def __delitem__(self, key: str) -> None:
|
||||
"""Deletes an item from the hierarchical dict"""
|
||||
"""Deletes an item from the hierarchical dict."""
|
||||
try:
|
||||
if self.separator in key:
|
||||
subdict = self._subdict[self._key_head(key)]
|
||||
@@ -188,7 +193,7 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
raise KeyError(key)
|
||||
|
||||
def __contains__(self, key: Any) -> bool:
|
||||
"""Determines whether the key is present in the hierarchy"""
|
||||
"""Determines whether the key is present in the hierarchy."""
|
||||
if self.separator in key:
|
||||
try:
|
||||
subdict = self._subdict[self._key_head(key)]
|
||||
@@ -199,11 +204,11 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
return key in self._data
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Returns the length of all items"""
|
||||
"""Returns the length of all items."""
|
||||
return len(self._data) + sum([len(subdict) for subdict in self._subdict])
|
||||
|
||||
def branch(self, key: str) -> 'HierarchicalDict':
|
||||
"""Returns the HierarchicalDict housed under the key
|
||||
"""Returns the HierarchicalDict housed under the key.
|
||||
|
||||
This differs from the data property, in that it is directed by the `key`, and all layers under that key are
|
||||
returned, not just those in that level.
|
||||
@@ -227,17 +232,18 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
return HierarchicalDict()
|
||||
|
||||
def splice(self, key: str, value: 'HierarchicalDict') -> None:
|
||||
"""Splices an existing HierarchicalDictionary under a specific key
|
||||
"""Splices an existing HierarchicalDictionary under a specific key.
|
||||
|
||||
This can be thought of as an inverse of :func:`branch`, although `branch` does not remove the requested
|
||||
hierarchy, it simply returns it.
|
||||
This can be thought of as an inverse of :func:`branch`, although
|
||||
`branch` does not remove the requested hierarchy, it simply
|
||||
returns it.
|
||||
"""
|
||||
if not isinstance(key, str) or not isinstance(value, HierarchicalDict):
|
||||
raise TypeError("Splice requires a string key and HierarchicalDict value")
|
||||
self._setitem(key, value, False)
|
||||
|
||||
def merge(self, key: str, value: 'HierarchicalDict', overwrite: bool = False) -> None:
|
||||
"""Acts similarly to splice, but maintains previous values
|
||||
"""Acts similarly to splice, but maintains previous values.
|
||||
|
||||
If overwrite is true, then entries in the new value are used over those that exist within key already
|
||||
|
||||
@@ -245,7 +251,6 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
key: The location within the hierarchy at which to merge the `value`
|
||||
value: HierarchicalDict to be merged under the key node
|
||||
overwrite: A boolean defining whether the value will be overwritten if it already exists
|
||||
|
||||
"""
|
||||
if not isinstance(key, str) or not isinstance(value, HierarchicalDict):
|
||||
raise TypeError("Splice requires a string key and HierarchicalDict value")
|
||||
@@ -257,7 +262,8 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
self[key + self._separator + item] = value[item]
|
||||
|
||||
def clone(self) -> 'HierarchicalDict':
|
||||
"""Duplicates the configuration, allowing changes without affecting the original
|
||||
"""Duplicates the configuration, allowing changes without affecting the
|
||||
original.
|
||||
|
||||
Returns:
|
||||
A duplicate HierarchicalDict of this object
|
||||
@@ -265,12 +271,12 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
return copy.deepcopy(self)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Turns the Hierarchical dict into a string representation"""
|
||||
"""Turns the Hierarchical dict into a string representation."""
|
||||
return json.dumps(dict([(key, self[key]) for key in sorted(self.generator())]), indent = 2)
|
||||
|
||||
|
||||
class RequirementInterface(metaclass = ABCMeta):
|
||||
"""Class that defines a requirement
|
||||
"""Class that defines a requirement.
|
||||
|
||||
A requirement is a means for plugins and other framework components to request specific configuration data.
|
||||
Requirements can either be simple types (such as
|
||||
@@ -308,33 +314,37 @@ class RequirementInterface(metaclass = ABCMeta):
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""The name of the Requirement. Names cannot contain CONFIG_SEPARATOR ('.' by default) since this
|
||||
is used within the configuration hierarchy."""
|
||||
"""The name of the Requirement.
|
||||
|
||||
Names cannot contain CONFIG_SEPARATOR ('.' by default) since
|
||||
this is used within the configuration hierarchy.
|
||||
"""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
"""A short description of what the Requirement is designed to affect or achieve."""
|
||||
"""A short description of what the Requirement is designed to affect or
|
||||
achieve."""
|
||||
return self._description
|
||||
|
||||
@property
|
||||
def default(self) -> Optional[ConfigSimpleType]:
|
||||
"""Returns the default value if one is set"""
|
||||
"""Returns the default value if one is set."""
|
||||
return self._default
|
||||
|
||||
@property
|
||||
def optional(self) -> bool:
|
||||
"""Whether the Requirement is optional or not"""
|
||||
"""Whether the Requirement is optional or not."""
|
||||
return self._optional
|
||||
|
||||
@optional.setter
|
||||
def optional(self, value) -> None:
|
||||
"""Sets the optional value for a requirement"""
|
||||
"""Sets the optional value for a requirement."""
|
||||
self._optional = bool(value)
|
||||
|
||||
def config_value(self, context: ContextInterface, config_path: str,
|
||||
default: ConfigSimpleType = None) -> ConfigSimpleType:
|
||||
"""Returns the value for this Requirement from its config path
|
||||
"""Returns the value for this Requirement from its config path.
|
||||
|
||||
Args:
|
||||
context: the configuration store to find the value for this requirement
|
||||
@@ -346,20 +356,20 @@ class RequirementInterface(metaclass = ABCMeta):
|
||||
# Child operations
|
||||
@property
|
||||
def requirements(self) -> Dict[str, 'RequirementInterface']:
|
||||
"""Returns a dictionary of all the child requirements, indexed by name"""
|
||||
"""Returns a dictionary of all the child requirements, indexed by
|
||||
name."""
|
||||
return self._requirements.copy()
|
||||
|
||||
def add_requirement(self, requirement: 'RequirementInterface') -> None:
|
||||
"""Adds a child to the list of requirements
|
||||
"""Adds a child to the list of requirements.
|
||||
|
||||
Args:
|
||||
requirement: The requirement to add as a child-requirement
|
||||
|
||||
"""
|
||||
self._requirements[requirement.name] = requirement
|
||||
|
||||
def remove_requirement(self, requirement: 'RequirementInterface') -> None:
|
||||
"""Removes a child from the list of requirements
|
||||
"""Removes a child from the list of requirements.
|
||||
|
||||
Args:
|
||||
requirement: The requirement to remove as a child-requirement
|
||||
@@ -367,7 +377,7 @@ class RequirementInterface(metaclass = ABCMeta):
|
||||
del self._requirements[requirement.name]
|
||||
|
||||
def unsatisfied_children(self, context: ContextInterface, config_path: str) -> Dict[str, 'RequirementInterface']:
|
||||
"""Method that will validate all child requirements
|
||||
"""Method that will validate all child requirements.
|
||||
|
||||
Args:
|
||||
context: the context containing the configuration data for this requirement
|
||||
@@ -386,33 +396,38 @@ class RequirementInterface(metaclass = ABCMeta):
|
||||
# Validation routines
|
||||
@abstractmethod
|
||||
def unsatisfied(self, context: ContextInterface, config_path: str) -> Dict[str, 'RequirementInterface']:
|
||||
"""Method to validate the value stored at config_path for the configuration object against a context
|
||||
"""Method to validate the value stored at config_path for the
|
||||
configuration object against a context.
|
||||
|
||||
Returns a list containing its own name (or multiple unsatisfied requirement names) when invalid
|
||||
Returns a list containing its own name (or multiple unsatisfied requirement names) when invalid
|
||||
|
||||
Args:
|
||||
context: The context object containing the configuration for this requirement
|
||||
config_path: The configuration path for this requirement to test satisfaction
|
||||
Args:
|
||||
context: The context object containing the configuration for this requirement
|
||||
config_path: The configuration path for this requirement to test satisfaction
|
||||
|
||||
Returns:
|
||||
A dictionary of configuration-paths to requirements that could not be satisfied
|
||||
Returns:
|
||||
A dictionary of configuration-paths to requirements that could not be satisfied
|
||||
"""
|
||||
|
||||
|
||||
class SimpleTypeRequirement(RequirementInterface):
|
||||
"""Class to represent a single simple type (such as a boolean, a string, an integer or a series of bytes)"""
|
||||
"""Class to represent a single simple type (such as a boolean, a string, an
|
||||
integer or a series of bytes)"""
|
||||
instance_type = bool # type: ClassVar[Type]
|
||||
|
||||
def add_requirement(self, requirement: RequirementInterface):
|
||||
"""Always raises a TypeError as instance requirements cannot have children"""
|
||||
"""Always raises a TypeError as instance requirements cannot have
|
||||
children."""
|
||||
raise TypeError("Instance Requirements cannot have subrequirements")
|
||||
|
||||
def remove_requirement(self, requirement: RequirementInterface):
|
||||
"""Always raises a TypeError as instance requirements cannot have children"""
|
||||
"""Always raises a TypeError as instance requirements cannot have
|
||||
children."""
|
||||
raise TypeError("Instance Requirements cannot have subrequirements")
|
||||
|
||||
def unsatisfied(self, context: ContextInterface, config_path: str) -> Dict[str, RequirementInterface]:
|
||||
"""Validates the instance requirement based upon its `instance_type`."""
|
||||
"""Validates the instance requirement based upon its
|
||||
`instance_type`."""
|
||||
config_path = path_join(config_path, self.name)
|
||||
|
||||
value = self.config_value(context, config_path, None)
|
||||
@@ -426,8 +441,12 @@ class SimpleTypeRequirement(RequirementInterface):
|
||||
|
||||
|
||||
class ClassRequirement(RequirementInterface):
|
||||
"""Requires a specific class. This is used as means to serialize specific classes for :class:`TranslationLayerRequirement`
|
||||
and :class:`SymbolTableRequirement` classes."""
|
||||
"""Requires a specific class.
|
||||
|
||||
This is used as means to serialize specific classes for
|
||||
:class:`TranslationLayerRequirement` and
|
||||
:class:`SymbolTableRequirement` classes.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -435,11 +454,12 @@ class ClassRequirement(RequirementInterface):
|
||||
|
||||
@property
|
||||
def cls(self) -> Type:
|
||||
"""Contains the actual chosen class based on the configuration value's class name"""
|
||||
"""Contains the actual chosen class based on the configuration value's
|
||||
class name."""
|
||||
return self._cls
|
||||
|
||||
def unsatisfied(self, context: ContextInterface, config_path: str) -> Dict[str, RequirementInterface]:
|
||||
"""Checks to see if a class can be recovered"""
|
||||
"""Checks to see if a class can be recovered."""
|
||||
config_path = path_join(config_path, self.name)
|
||||
|
||||
value = self.config_value(context, config_path, None)
|
||||
@@ -460,13 +480,18 @@ class ClassRequirement(RequirementInterface):
|
||||
|
||||
|
||||
class ConstructableRequirementInterface(RequirementInterface):
|
||||
"""Defines a Requirement that can be constructed based on their own requirements.
|
||||
"""Defines a Requirement that can be constructed based on their own
|
||||
requirements.
|
||||
|
||||
This effectively offers a means for serializing specific python types, to be reconstructed based on simple
|
||||
configuration data. Each constructable records a `class` requirement, which indicates the object that will be
|
||||
constructed. That class may have its own requirements (which is why validation of a ConstructableRequirement
|
||||
must happen after the class configuration value has been provided). These values are then provided to the object's
|
||||
constructor by name as arguments (as well as the standard `context` and `config_path` arguments.
|
||||
This effectively offers a means for serializing specific python
|
||||
types, to be reconstructed based on simple configuration data. Each
|
||||
constructable records a `class` requirement, which indicates the
|
||||
object that will be constructed. That class may have its own
|
||||
requirements (which is why validation of a ConstructableRequirement
|
||||
must happen after the class configuration value has been provided).
|
||||
These values are then provided to the object's constructor by name
|
||||
as arguments (as well as the standard `context` and `config_path`
|
||||
arguments.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -476,7 +501,8 @@ class ConstructableRequirementInterface(RequirementInterface):
|
||||
|
||||
@abstractmethod
|
||||
def construct(self, context: ContextInterface, config_path: str) -> None:
|
||||
"""Method for constructing within the context any required elements from subrequirements
|
||||
"""Method for constructing within the context any required elements
|
||||
from subrequirements.
|
||||
|
||||
Args:
|
||||
context: The context object containing the configuration data for the constructable
|
||||
@@ -484,12 +510,13 @@ class ConstructableRequirementInterface(RequirementInterface):
|
||||
"""
|
||||
|
||||
def _validate_class(self, context: ContextInterface, config_path: str) -> None:
|
||||
"""Method to check if the class Requirement is valid and if so populate the other requirements
|
||||
(but no need to validate, since we're invalid already)
|
||||
"""Method to check if the class Requirement is valid and if so populate
|
||||
the other requirements (but no need to validate, since we're invalid
|
||||
already)
|
||||
|
||||
Args:
|
||||
context: The context object containing the configuration data for the constructable
|
||||
config_path: The configuration path for the specific instance of this constructable
|
||||
Args:
|
||||
context: The context object containing the configuration data for the constructable
|
||||
config_path: The configuration path for the specific instance of this constructable
|
||||
"""
|
||||
class_req = self.requirements['class']
|
||||
subreq_config_path = path_join(config_path, self.name)
|
||||
@@ -507,7 +534,8 @@ class ConstructableRequirementInterface(RequirementInterface):
|
||||
|
||||
def _construct_class(self, context: ContextInterface, config_path: str,
|
||||
requirement_dict: Dict[str, object] = None) -> Optional['interfaces.objects.ObjectInterface']:
|
||||
"""Constructs the class, handing args and the subrequirements as parameters to __init__"""
|
||||
"""Constructs the class, handing args and the subrequirements as
|
||||
parameters to __init__"""
|
||||
if self.requirements["class"].unsatisfied(context, config_path):
|
||||
return None
|
||||
|
||||
@@ -533,17 +561,19 @@ class ConstructableRequirementInterface(RequirementInterface):
|
||||
|
||||
|
||||
class ConfigurableRequirementInterface(RequirementInterface):
|
||||
"""Simple Abstract class to provide build_required_config"""
|
||||
"""Simple Abstract class to provide build_required_config."""
|
||||
|
||||
def build_configuration(self, context: ContextInterface, config_path: str, value: Any) -> HierarchicalDict:
|
||||
"""Proxies to a ConfigurableInterface if necessary"""
|
||||
"""Proxies to a ConfigurableInterface if necessary."""
|
||||
|
||||
|
||||
class ConfigurableInterface(metaclass = ABCMeta):
|
||||
"""Class to allow objects to have requirements and read configuration data from the context config tree"""
|
||||
"""Class to allow objects to have requirements and read configuration data
|
||||
from the context config tree."""
|
||||
|
||||
def __init__(self, context: ContextInterface, config_path: str) -> None:
|
||||
"""Basic initializer that allows configurables to access their own config settings"""
|
||||
"""Basic initializer that allows configurables to access their own
|
||||
config settings."""
|
||||
super().__init__()
|
||||
self._context = context
|
||||
self._config_path = config_path
|
||||
@@ -551,32 +581,37 @@ class ConfigurableInterface(metaclass = ABCMeta):
|
||||
|
||||
@property
|
||||
def context(self) -> ContextInterface:
|
||||
"""The context object that this configurable belongs to/configuration is stored in"""
|
||||
"""The context object that this configurable belongs to/configuration
|
||||
is stored in."""
|
||||
return self._context
|
||||
|
||||
@property
|
||||
def config_path(self) -> str:
|
||||
"""The configuration path on which this configurable lives"""
|
||||
"""The configuration path on which this configurable lives."""
|
||||
return self._config_path
|
||||
|
||||
@config_path.setter
|
||||
def config_path(self, value: str) -> None:
|
||||
"""The configuration path on which this configurable lives"""
|
||||
"""The configuration path on which this configurable lives."""
|
||||
self._config_path = value
|
||||
self._config_cache = None
|
||||
|
||||
@property
|
||||
def config(self) -> HierarchicalDict:
|
||||
"""The Hierarchical configuration Dictionary for this Configurable object"""
|
||||
"""The Hierarchical configuration Dictionary for this Configurable
|
||||
object."""
|
||||
if not hasattr(self, "_config_cache") or self._config_cache is None:
|
||||
self._config_cache = self._context.config.branch(self._config_path)
|
||||
return self._config_cache
|
||||
|
||||
def build_configuration(self) -> HierarchicalDict:
|
||||
"""Constructs a HierarchicalDictionary of all the options required to build this component in the current context.
|
||||
"""Constructs a HierarchicalDictionary of all the options required to
|
||||
build this component in the current context.
|
||||
|
||||
Ensures that if the class has been created, it can be recreated using the configuration built
|
||||
Inheriting classes must override this to ensure any dependent classes update their configurations too
|
||||
Ensures that if the class has been created, it can be recreated
|
||||
using the configuration built Inheriting classes must override
|
||||
this to ensure any dependent classes update their configurations
|
||||
too
|
||||
"""
|
||||
result = HierarchicalDict()
|
||||
for req in self.get_requirements():
|
||||
@@ -591,12 +626,13 @@ class ConfigurableInterface(metaclass = ABCMeta):
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[RequirementInterface]:
|
||||
"""Returns a list of RequirementInterface objects required by this object"""
|
||||
"""Returns a list of RequirementInterface objects required by this
|
||||
object."""
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def unsatisfied(cls, context: ContextInterface, config_path: str) -> Dict[str, RequirementInterface]:
|
||||
"""Returns a list of the names of all unsatisfied requirements
|
||||
"""Returns a list of the names of all unsatisfied requirements.
|
||||
|
||||
Since a satisfied set of requirements will return [], it can be used in tests as follows:
|
||||
|
||||
@@ -614,9 +650,9 @@ class ConfigurableInterface(metaclass = ABCMeta):
|
||||
return result
|
||||
|
||||
def make_subconfig(self, *args, **kwargs) -> str:
|
||||
"""Convenience function to allow constructing a new randomly generated sub-configuration path,
|
||||
containing each element from kwargs
|
||||
|
||||
"""Convenience function to allow constructing a new randomly generated
|
||||
sub-configuration path, containing each element from kwargs.
|
||||
|
||||
Returns:
|
||||
str: The newly generated full configuration path
|
||||
"""
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
# 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
|
||||
#
|
||||
"""Defines an interface for contexts, which hold the core components that a plugin will operate upon when running.
|
||||
"""Defines an interface for contexts, which hold the core components that a
|
||||
plugin will operate upon when running.
|
||||
|
||||
These include a `memory` container which holds a series of forest of layers, and a `symbol_space` which contains tables
|
||||
of symbols that can be used to interpret data in a layer. The context also provides some convenience functions, most
|
||||
notably the object constructor function, `object`, which will construct a symbol on a layer at a particular offset.
|
||||
These include a `memory` container which holds a series of forest of
|
||||
layers, and a `symbol_space` which contains tables of symbols that can
|
||||
be used to interpret data in a layer. The context also provides some
|
||||
convenience functions, most notably the object constructor function,
|
||||
`object`, which will construct a symbol on a layer at a particular
|
||||
offset.
|
||||
"""
|
||||
import copy
|
||||
from abc import ABCMeta, abstractmethod
|
||||
@@ -21,19 +25,19 @@ class ContextInterface(metaclass = ABCMeta):
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initializes the context with a symbol_space"""
|
||||
"""Initializes the context with a symbol_space."""
|
||||
|
||||
# ## Symbol Space Functions
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def config(self) -> 'interfaces.configuration.HierarchicalDict':
|
||||
"""Returns the configuration object for this context"""
|
||||
"""Returns the configuration object for this context."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def symbol_space(self) -> 'interfaces.symbols.SymbolSpaceInterface':
|
||||
"""Returns the symbol_space for the context
|
||||
"""Returns the symbol_space for the context.
|
||||
|
||||
This object must support the :class:`~volatility.framework.interfaces.symbols.SymbolSpaceInterface`
|
||||
"""
|
||||
@@ -43,11 +47,11 @@ class ContextInterface(metaclass = ABCMeta):
|
||||
@property
|
||||
@abstractmethod
|
||||
def layers(self) -> 'interfaces.layers.LayerContainer':
|
||||
"""Returns the memory object for the context"""
|
||||
"""Returns the memory object for the context."""
|
||||
raise NotImplementedError("LayerContainer has not been implemented.")
|
||||
|
||||
def add_layer(self, layer: 'interfaces.layers.DataLayerInterface'):
|
||||
"""Adds a named translation layer to the context memory
|
||||
"""Adds a named translation layer to the context memory.
|
||||
|
||||
Args:
|
||||
layer: Layer object to be added to the context memory
|
||||
@@ -63,26 +67,30 @@ class ContextInterface(metaclass = ABCMeta):
|
||||
offset: int,
|
||||
native_layer_name: str = None,
|
||||
**arguments):
|
||||
"""Object factory, takes a context, symbol, offset and optional layer_name
|
||||
"""Object factory, takes a context, symbol, offset and optional
|
||||
layer_name.
|
||||
|
||||
Looks up the layer_name 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.
|
||||
Looks up the layer_name 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:
|
||||
object_type: Either a string name of the type, or a Template of the type to be constructed
|
||||
layer_name: The name of the layer on which to construct the object
|
||||
offset: The address within the layer at which to construct the object
|
||||
native_layer_name: The layer this object references (should it be a pointer or similar)
|
||||
Args:
|
||||
object_type: Either a string name of the type, or a Template of the type to be constructed
|
||||
layer_name: The name of the layer on which to construct the object
|
||||
offset: The address within the layer at which to construct the object
|
||||
native_layer_name: The layer this object references (should it be a pointer or similar)
|
||||
|
||||
Returns:
|
||||
A fully constructed object
|
||||
Returns:
|
||||
A fully constructed object
|
||||
"""
|
||||
|
||||
def clone(self) -> 'ContextInterface':
|
||||
"""Produce a clone of the context (and configuration), allowing modifications to be made without affecting
|
||||
any mutable objects in the original.
|
||||
"""Produce a clone of the context (and configuration), allowing
|
||||
modifications to be made without affecting any mutable objects in the
|
||||
original.
|
||||
|
||||
Memory constraints may become an issue for this function depending on how much is actually stored in the context"""
|
||||
Memory constraints may become an issue for this function
|
||||
depending on how much is actually stored in the context
|
||||
"""
|
||||
return copy.deepcopy(self)
|
||||
|
||||
def module(self,
|
||||
@@ -91,7 +99,7 @@ class ContextInterface(metaclass = ABCMeta):
|
||||
offset: int,
|
||||
native_layer_name: Optional[str] = None,
|
||||
size: Optional[int] = None) -> 'ModuleInterface':
|
||||
"""Create a module object
|
||||
"""Create a module object.
|
||||
|
||||
A module object is associated with a symbol table, and acts like a context, but offsets locations by a known value
|
||||
and looks up symbols, by default within the associated symbol table. It can also be sized should that information
|
||||
@@ -110,7 +118,7 @@ class ContextInterface(metaclass = ABCMeta):
|
||||
|
||||
|
||||
class ModuleInterface(metaclass = ABCMeta):
|
||||
"""Maintains state concerning a particular loaded module in memory
|
||||
"""Maintains state concerning a particular loaded module in memory.
|
||||
|
||||
This object is OS-independent.
|
||||
"""
|
||||
@@ -122,8 +130,7 @@ class ModuleInterface(metaclass = ABCMeta):
|
||||
offset: int,
|
||||
symbol_table_name: Optional[str] = None,
|
||||
native_layer_name: Optional[str] = None) -> None:
|
||||
"""
|
||||
Constructs a new os-independent module
|
||||
"""Constructs a new os-independent module.
|
||||
|
||||
Args:
|
||||
context: The context within which this module will exist
|
||||
@@ -145,22 +152,23 @@ class ModuleInterface(metaclass = ABCMeta):
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""The name of the constructed module"""
|
||||
"""The name of the constructed module."""
|
||||
return self._module_name
|
||||
|
||||
@property
|
||||
def offset(self) -> int:
|
||||
"""Returns the offset that the module resides within the layer of layer_name """
|
||||
"""Returns the offset that the module resides within the layer of
|
||||
layer_name."""
|
||||
return self._offset
|
||||
|
||||
@property
|
||||
def layer_name(self) -> str:
|
||||
"""Layer name in which the Module resides"""
|
||||
"""Layer name in which the Module resides."""
|
||||
return self._layer_name
|
||||
|
||||
@property
|
||||
def context(self) -> ContextInterface:
|
||||
"""Context that the module uses"""
|
||||
"""Context that the module uses."""
|
||||
return self._context
|
||||
|
||||
@abstractmethod
|
||||
@@ -170,7 +178,8 @@ class ModuleInterface(metaclass = ABCMeta):
|
||||
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
|
||||
"""Returns an object created using the symbol_table_name and layer_name
|
||||
of the Module.
|
||||
|
||||
Args:
|
||||
object_type: The name of object type to construct (using the module's symbol_table)
|
||||
@@ -188,7 +197,8 @@ class ModuleInterface(metaclass = ABCMeta):
|
||||
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
|
||||
"""Returns an object created using the symbol_table_name and layer_name
|
||||
of the Module.
|
||||
|
||||
Args:
|
||||
symbol_name: The name of a symbol (that must be present in the module's symbol table). The symbol's associated type will be used to construct an object at the symbol's offset.
|
||||
@@ -200,19 +210,19 @@ class ModuleInterface(metaclass = ABCMeta):
|
||||
"""
|
||||
|
||||
def get_type(self, name: str) -> 'interfaces.objects.Template':
|
||||
"""Returns a type from the module"""
|
||||
"""Returns a type from the module."""
|
||||
|
||||
def get_symbol(self, name: str) -> 'interfaces.symbols.SymbolInterface':
|
||||
"""Returns a symbol from the module"""
|
||||
"""Returns a symbol from the module."""
|
||||
|
||||
def get_enumeration(self, name: str) -> 'interfaces.objects.Template':
|
||||
"""Returns an enumeration from the module"""
|
||||
"""Returns an enumeration from the module."""
|
||||
|
||||
def has_type(self, name: str) -> bool:
|
||||
"""Determines whether a type is present in the module"""
|
||||
"""Determines whether a type is present in the module."""
|
||||
|
||||
def has_symbol(self, name: str) -> bool:
|
||||
"""Determines whether a symbol is present in the module"""
|
||||
"""Determines whether a symbol is present in the module."""
|
||||
|
||||
def has_enumeration(self, name: str) -> bool:
|
||||
"""Determines whether an enumeration is present in the module"""
|
||||
"""Determines whether an enumeration is present in the module."""
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
# 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
|
||||
#
|
||||
"""Defines layers for containing data. One layer may combine other layers, map data based on the data itself,
|
||||
or map a procedure (such as decryption) across another layer of data."""
|
||||
"""Defines layers for containing data.
|
||||
|
||||
One layer may combine other layers, map data based on the data itself,
|
||||
or map a procedure (such as decryption) across another layer of data.
|
||||
"""
|
||||
import collections.abc
|
||||
import functools
|
||||
import logging
|
||||
@@ -31,7 +34,8 @@ IteratorValue = Tuple[List[Tuple[str, int, int]], int]
|
||||
|
||||
|
||||
class ScannerInterface(metaclass = ABCMeta):
|
||||
"""Class for layer scanners that return locations of particular values from within the data
|
||||
"""Class for layer scanners that return locations of particular values from
|
||||
within the data.
|
||||
|
||||
These are designed to be given a chunk of data and return a generator which yields
|
||||
any found items. They should NOT perform complex/time-consuming tasks, these should
|
||||
@@ -69,7 +73,8 @@ class ScannerInterface(metaclass = ABCMeta):
|
||||
|
||||
@context.setter
|
||||
def context(self, ctx: 'interfaces.context.ContextInterface') -> None:
|
||||
"""Stores the context locally in case the scanner needs to access the layer"""
|
||||
"""Stores the context locally in case the scanner needs to access the
|
||||
layer."""
|
||||
self._context = ctx
|
||||
|
||||
@property
|
||||
@@ -78,22 +83,27 @@ class ScannerInterface(metaclass = ABCMeta):
|
||||
|
||||
@layer_name.setter
|
||||
def layer_name(self, layer_name: str) -> None:
|
||||
"""Stores the layer_name being scanned locally in case the scanner needs to access the layer"""
|
||||
"""Stores the layer_name being scanned locally in case the scanner
|
||||
needs to access the layer."""
|
||||
self._layer_name = layer_name
|
||||
|
||||
@abstractmethod
|
||||
def __call__(self, data: bytes, data_offset: int) -> Iterable[Any]:
|
||||
"""Searches through a chunk of data for a particular value/pattern/etc
|
||||
Always returns an iterator of the same type of object (need not be a volatility object)
|
||||
Always returns an iterator of the same type of object (need not be a
|
||||
volatility object)
|
||||
|
||||
data is the chunk of data to search through
|
||||
data_offset is the offset within the layer that the data being searched starts at
|
||||
data is the chunk of data to search through data_offset is the
|
||||
offset within the layer that the data being searched starts at
|
||||
"""
|
||||
|
||||
|
||||
class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metaclass = ABCMeta):
|
||||
"""A Layer that directly holds data (and does not translate it). This is effectively a leaf node in a layer tree.
|
||||
It directly accesses a data source and exposes it within volatility."""
|
||||
"""A Layer that directly holds data (and does not translate it).
|
||||
|
||||
This is effectively a leaf node in a layer tree. It directly
|
||||
accesses a data source and exposes it within volatility.
|
||||
"""
|
||||
|
||||
_direct_metadata = collections.ChainMap({}, {
|
||||
'architecture': 'Unknown',
|
||||
@@ -114,27 +124,29 @@ class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metacla
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Returns the layer name"""
|
||||
"""Returns the layer name."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def maximum_address(self) -> int:
|
||||
"""Returns the maximum valid address of the space"""
|
||||
"""Returns the maximum valid address of the space."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def minimum_address(self) -> int:
|
||||
"""Returns the minimum valid address of the space"""
|
||||
"""Returns the minimum valid address of the space."""
|
||||
|
||||
@property
|
||||
def address_mask(self) -> int:
|
||||
"""Returns a mask which encapsulates all the active bits of an address for this layer"""
|
||||
"""Returns a mask which encapsulates all the active bits of an address
|
||||
for this layer."""
|
||||
return (1 << int(math.ceil(math.log2(self.maximum_address)))) - 1
|
||||
|
||||
@abstractmethod
|
||||
def is_valid(self, offset: int, length: int = 1) -> bool:
|
||||
"""Returns a boolean based on whether the entire chunk of data (from offset to length) is valid or not
|
||||
"""Returns a boolean based on whether the entire chunk of data (from
|
||||
offset to length) is valid or not.
|
||||
|
||||
Args:
|
||||
offset: The address to start determining whether bytes are readable/valid
|
||||
@@ -146,39 +158,42 @@ class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metacla
|
||||
|
||||
@abstractmethod
|
||||
def read(self, offset: int, length: int, pad: bool = False) -> bytes:
|
||||
"""Reads an offset for length bytes and returns 'bytes' (not 'str') of length size
|
||||
"""Reads an offset for length bytes and returns 'bytes' (not 'str') of
|
||||
length size.
|
||||
|
||||
If there is a fault of any kind (such as a page fault), an exception will be thrown
|
||||
unless pad is set, in which case the read errors will be replaced by null characters.
|
||||
If there is a fault of any kind (such as a page fault), an exception will be thrown
|
||||
unless pad is set, in which case the read errors will be replaced by null characters.
|
||||
|
||||
Args:
|
||||
offset: The offset at which to being reading within the layer
|
||||
length: The number of bytes to read within the layer
|
||||
pad: A boolean indicating whether exceptions should be raised or bad bytes replaced with null characters
|
||||
Args:
|
||||
offset: The offset at which to being reading within the layer
|
||||
length: The number of bytes to read within the layer
|
||||
pad: A boolean indicating whether exceptions should be raised or bad bytes replaced with null characters
|
||||
|
||||
Returns:
|
||||
The bytes read from the layer, starting at offset for length bytes
|
||||
Returns:
|
||||
The bytes read from the layer, starting at offset for length bytes
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def write(self, offset: int, data: bytes) -> None:
|
||||
"""Writes a chunk of data at offset.
|
||||
|
||||
Any unavailable sections in the underlying bases will cause an exception to be thrown.
|
||||
Note: Writes are not guaranteed atomic, therefore some data may have been written, even if an exception is thrown.
|
||||
Any unavailable sections in the underlying bases will cause an exception to be thrown.
|
||||
Note: Writes are not guaranteed atomic, therefore some data may have been written, even if an exception is thrown.
|
||||
"""
|
||||
|
||||
def destroy(self) -> None:
|
||||
"""Causes a DataLayer to close any open handles, etc.
|
||||
|
||||
Systems that make use of Data Layers should call destroy when they are done with them.
|
||||
This will close all handles, and make the object unreadable
|
||||
(exceptions will be thrown using a DataLayer after destruction)"""
|
||||
Systems that make use of Data Layers should call destroy when
|
||||
they are done with them. This will close all handles, and make
|
||||
the object unreadable (exceptions will be thrown using a
|
||||
DataLayer after destruction)
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
"""Returns a list of Requirement objects for this type of layer"""
|
||||
"""Returns a list of Requirement objects for this type of layer."""
|
||||
return []
|
||||
|
||||
@property
|
||||
@@ -186,7 +201,8 @@ class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metacla
|
||||
"""A list of other layer names required by this layer.
|
||||
|
||||
Note:
|
||||
DataLayers must never define other layers"""
|
||||
DataLayers must never define other layers
|
||||
"""
|
||||
return []
|
||||
|
||||
# ## General scanning methods
|
||||
@@ -196,18 +212,18 @@ class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metacla
|
||||
scanner: ScannerInterface,
|
||||
progress_callback: constants.ProgressCallback = None,
|
||||
sections: Iterable[Tuple[int, int]] = None) -> Iterable[Any]:
|
||||
"""Scans a Translation layer by chunk
|
||||
"""Scans a Translation layer by chunk.
|
||||
|
||||
Note: this will skip missing/unmappable chunks of memory
|
||||
Note: this will skip missing/unmappable chunks of memory
|
||||
|
||||
Args:
|
||||
context: The context containing the data layer
|
||||
scanner: The constructed Scanner object to be applied
|
||||
progress_callback: Method that is called periodically during scanning to update progress
|
||||
sections: A list of (start, size) tuples defining the portions of the layer to scan
|
||||
Args:
|
||||
context: The context containing the data layer
|
||||
scanner: The constructed Scanner object to be applied
|
||||
progress_callback: Method that is called periodically during scanning to update progress
|
||||
sections: A list of (start, size) tuples defining the portions of the layer to scan
|
||||
|
||||
Returns:
|
||||
The output iterable from the scanner object having been run against the layer
|
||||
Returns:
|
||||
The output iterable from the scanner object having been run against the layer
|
||||
"""
|
||||
if progress_callback is not None and not callable(progress_callback):
|
||||
raise TypeError("Progress_callback is not callable")
|
||||
@@ -261,7 +277,8 @@ class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metacla
|
||||
"\n".join(traceback.TracebackException.from_exception(e).format(chain = True)))
|
||||
|
||||
def _coalesce_sections(self, sections: Iterable[Tuple[int, int]]) -> Iterable[Tuple[int, int]]:
|
||||
"""Take a list of (start, length) sections and coalesce any adjacent sections"""
|
||||
"""Take a list of (start, length) sections and coalesce any adjacent
|
||||
sections."""
|
||||
result = [] # type: List[Tuple[int, int]]
|
||||
position = 0
|
||||
for (start, length) in sorted(sections):
|
||||
@@ -288,11 +305,13 @@ class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metacla
|
||||
|
||||
def _scan_iterator(self, scanner: 'ScannerInterface',
|
||||
sections: Iterable[Tuple[int, int]]) -> Iterable[IteratorValue]:
|
||||
"""Iterator that indicates which blocks in the layer are to be read by for the scanning
|
||||
"""Iterator that indicates which blocks in the layer are to be read by
|
||||
for the scanning.
|
||||
|
||||
Returns a list of blocks (potentially in lower layers) that make up this chunk contiguously.
|
||||
Chunks can be no bigger than scanner.chunk_size + scanner.overlap
|
||||
DataLayers by default are assumed to have no holes
|
||||
Returns a list of blocks (potentially in lower layers) that make
|
||||
up this chunk contiguously. Chunks can be no bigger than
|
||||
scanner.chunk_size + scanner.overlap DataLayers by default are
|
||||
assumed to have no holes
|
||||
"""
|
||||
for section_start, section_length in sections:
|
||||
offset, mapped_offset, length, layer_name = section_start, section_start, section_length, self.name
|
||||
@@ -345,44 +364,51 @@ class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metacla
|
||||
|
||||
@property
|
||||
def metadata(self) -> Mapping:
|
||||
"""Returns a ReadOnly copy of the metadata published by this layer"""
|
||||
"""Returns a ReadOnly copy of the metadata published by this layer."""
|
||||
maps = [self.context.layers[layer_name].metadata for layer_name in self.dependencies]
|
||||
return interfaces.objects.ReadOnlyMapping(collections.ChainMap({}, self._direct_metadata, *maps))
|
||||
|
||||
|
||||
class TranslationLayerInterface(DataLayerInterface, metaclass = ABCMeta):
|
||||
"""Provides a layer that translates or transforms another layer or layers. Translation layers always depend on
|
||||
another layer (typically translating offsets in a virtual offset space into a smaller physical offset space).
|
||||
"""Provides a layer that translates or transforms another layer or layers.
|
||||
|
||||
Translation layers always depend on another layer (typically
|
||||
translating offsets in a virtual offset space into a smaller
|
||||
physical offset space).
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def mapping(self, offset: int, length: int, ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, str]]:
|
||||
"""Returns a sorted iterable of (offset, mapped_offset, length, layer) mappings
|
||||
"""Returns a sorted iterable of (offset, mapped_offset, length, layer)
|
||||
mappings.
|
||||
|
||||
ignore_errors will provide all available maps with gaps, but their total length may not add up to the requested length
|
||||
This allows translation layers to provide maps of contiguous regions in one layer
|
||||
ignore_errors will provide all available maps with gaps, but
|
||||
their total length may not add up to the requested length This
|
||||
allows translation layers to provide maps of contiguous regions
|
||||
in one layer
|
||||
"""
|
||||
return []
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def dependencies(self) -> List[str]:
|
||||
"""Returns a list of layer names that this layer translates onto"""
|
||||
"""Returns a list of layer names that this layer translates onto."""
|
||||
return []
|
||||
|
||||
def _decode(self, data: bytes, mapped_offset: int, offset: int) -> bytes:
|
||||
"""Decodes any necessary data"""
|
||||
"""Decodes any necessary data."""
|
||||
return data
|
||||
|
||||
def _encode(self, data: bytes, mapped_offset: int, offset: int) -> bytes:
|
||||
"""Encodes any necessary data"""
|
||||
"""Encodes any necessary data."""
|
||||
return data
|
||||
|
||||
# ## Read/Write functions for mapped pages
|
||||
|
||||
@functools.lru_cache(maxsize = 512)
|
||||
def read(self, offset: int, length: int, pad: bool = False) -> bytes:
|
||||
"""Reads an offset for length bytes and returns 'bytes' (not 'str') of length size"""
|
||||
"""Reads an offset for length bytes and returns 'bytes' (not 'str') of
|
||||
length size."""
|
||||
current_offset = offset
|
||||
output = [] # type: List[bytes]
|
||||
for (layer_offset, mapped_offset, mapped_length, layer) in self.mapping(offset, length, ignore_errors = pad):
|
||||
@@ -407,7 +433,8 @@ class TranslationLayerInterface(DataLayerInterface, metaclass = ABCMeta):
|
||||
return recovered_data + b"\x00" * (length - len(recovered_data))
|
||||
|
||||
def write(self, offset: int, value: bytes) -> None:
|
||||
"""Writes a value at offset, distributing the writing across any underlying mapping"""
|
||||
"""Writes a value at offset, distributing the writing across any
|
||||
underlying mapping."""
|
||||
current_offset = offset
|
||||
length = len(value)
|
||||
for (layer_offset, mapped_offset, mapped_length, layer) in self.mapping(offset, length):
|
||||
@@ -445,38 +472,38 @@ class TranslationLayerInterface(DataLayerInterface, metaclass = ABCMeta):
|
||||
|
||||
|
||||
class LayerContainer(collections.abc.Mapping):
|
||||
"""Container for multiple layers of data"""
|
||||
"""Container for multiple layers of data."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._layers = {} # type: Dict[str, DataLayerInterface]
|
||||
|
||||
def read(self, layer: str, offset: int, length: int, pad: bool = False) -> bytes:
|
||||
"""Reads from a particular layer at offset for length bytes
|
||||
"""Reads from a particular layer at offset for length bytes.
|
||||
|
||||
Returns 'bytes' not 'str'
|
||||
Returns 'bytes' not 'str'
|
||||
|
||||
Args:
|
||||
layer: The name of the layer to read from
|
||||
offset: Where to begin reading within the layer
|
||||
length: How many bytes to read from the layer
|
||||
pad: Whether to raise exceptions or return null bytes when errors occur
|
||||
Args:
|
||||
layer: The name of the layer to read from
|
||||
offset: Where to begin reading within the layer
|
||||
length: How many bytes to read from the layer
|
||||
pad: Whether to raise exceptions or return null bytes when errors occur
|
||||
|
||||
Returns:
|
||||
The result of reading from the requested layer
|
||||
Returns:
|
||||
The result of reading from the requested layer
|
||||
"""
|
||||
return self[layer].read(offset, length, pad)
|
||||
|
||||
def write(self, layer: str, offset: int, data: bytes) -> None:
|
||||
"""Writes to a particular layer at offset for length bytes"""
|
||||
"""Writes to a particular layer at offset for length bytes."""
|
||||
self[layer].write(offset, data)
|
||||
|
||||
def add_layer(self, layer: DataLayerInterface) -> None:
|
||||
"""Adds a layer to memory model
|
||||
"""Adds a layer to memory model.
|
||||
|
||||
This will throw an exception if the required dependencies are not met
|
||||
This will throw an exception if the required dependencies are not met
|
||||
|
||||
Args:
|
||||
layer: the layer to add to the list of layers (based on layer.name)
|
||||
Args:
|
||||
layer: the layer to add to the list of layers (based on layer.name)
|
||||
"""
|
||||
if layer.name in self._layers:
|
||||
raise exceptions.LayerException(layer.name, "Layer already exists: {}".format(layer.name))
|
||||
@@ -488,12 +515,12 @@ class LayerContainer(collections.abc.Mapping):
|
||||
self._layers[layer.name] = layer
|
||||
|
||||
def del_layer(self, name: str) -> None:
|
||||
"""Removes the layer called name
|
||||
"""Removes the layer called name.
|
||||
|
||||
This will throw an exception if other layers depend upon this layer
|
||||
This will throw an exception if other layers depend upon this layer
|
||||
|
||||
Args:
|
||||
name: The name of the layer to delete
|
||||
Args:
|
||||
name: The name of the layer to delete
|
||||
"""
|
||||
for layer in self._layers:
|
||||
depend_list = [superlayer for superlayer in self._layers if name in self._layers[layer].dependencies]
|
||||
@@ -505,13 +532,14 @@ class LayerContainer(collections.abc.Mapping):
|
||||
del self._layers[name]
|
||||
|
||||
def free_layer_name(self, prefix: str = "layer") -> str:
|
||||
"""Returns an unused layer name to ensure no collision occurs when inserting a layer
|
||||
"""Returns an unused layer name to ensure no collision occurs when
|
||||
inserting a layer.
|
||||
|
||||
Args:
|
||||
prefix: A descriptive string with which to prefix the layer name
|
||||
Args:
|
||||
prefix: A descriptive string with which to prefix the layer name
|
||||
|
||||
Returns:
|
||||
A string containing a name, prefixed with prefix, not currently in use within the LayerContainer
|
||||
Returns:
|
||||
A string containing a name, prefixed with prefix, not currently in use within the LayerContainer
|
||||
"""
|
||||
count = 1
|
||||
while prefix + str(count) in self:
|
||||
@@ -519,7 +547,7 @@ class LayerContainer(collections.abc.Mapping):
|
||||
return prefix + str(count)
|
||||
|
||||
def __getitem__(self, name: str) -> DataLayerInterface:
|
||||
"""Returns the layer of specified name"""
|
||||
"""Returns the layer of specified name."""
|
||||
return self._layers[name]
|
||||
|
||||
def __len__(self) -> int:
|
||||
@@ -529,13 +557,14 @@ class LayerContainer(collections.abc.Mapping):
|
||||
return iter(self._layers)
|
||||
|
||||
def check_cycles(self) -> None:
|
||||
"""Runs through the available layers and identifies if there are cycles in the DAG"""
|
||||
"""Runs through the available layers and identifies if there are cycles
|
||||
in the DAG."""
|
||||
# TODO: Is having a cycle check necessary?
|
||||
raise NotImplementedError("Cycle checking has not yet been implemented")
|
||||
|
||||
|
||||
class DummyProgress(object):
|
||||
"""A class to emulate Multiprocessing/threading Value objects"""
|
||||
"""A class to emulate Multiprocessing/threading Value objects."""
|
||||
|
||||
def __init__(self):
|
||||
self.value = 0
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""Objects are the core of volatility, and provide pythonic access to interpreted values of data from a layer.
|
||||
"""
|
||||
"""Objects are the core of volatility, and provide pythonic access to
|
||||
interpreted values of data from a layer."""
|
||||
import abc
|
||||
import collections
|
||||
import collections.abc
|
||||
@@ -17,16 +17,18 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ReadOnlyMapping(collections.abc.Mapping):
|
||||
"""A read-only mapping of various values that offer attribute access as well
|
||||
"""A read-only mapping of various values that offer attribute access as
|
||||
well.
|
||||
|
||||
This ensures that the data stored in the mapping should not be modified, making an immutable mapping.
|
||||
This ensures that the data stored in the mapping should not be
|
||||
modified, making an immutable mapping.
|
||||
"""
|
||||
|
||||
def __init__(self, dictionary: Mapping[str, Any]) -> None:
|
||||
self._dict = dictionary
|
||||
|
||||
def __getattr__(self, attr: str) -> Any:
|
||||
"""Returns the item as an attribute"""
|
||||
"""Returns the item as an attribute."""
|
||||
if attr == '_dict':
|
||||
return super().__getattribute__(attr)
|
||||
if attr in self._dict:
|
||||
@@ -34,20 +36,21 @@ class ReadOnlyMapping(collections.abc.Mapping):
|
||||
raise AttributeError("Object has no attribute: {}.{}".format(self.__class__.__name__, attr))
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
"""Returns the item requested"""
|
||||
"""Returns the item requested."""
|
||||
return self._dict[name]
|
||||
|
||||
def __iter__(self):
|
||||
"""Returns an iterator of the dictionary items"""
|
||||
"""Returns an iterator of the dictionary items."""
|
||||
return self._dict.__iter__()
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Returns the length of the internal dictionary"""
|
||||
"""Returns the length of the internal dictionary."""
|
||||
return len(self._dict)
|
||||
|
||||
|
||||
class ObjectInformation(ReadOnlyMapping):
|
||||
"""Contains common information useful/pertinent only to an individual object (like an instance)
|
||||
"""Contains common information useful/pertinent only to an individual
|
||||
object (like an instance)
|
||||
|
||||
This typically contains information such as the layer the object belongs to, the offset where it was constructed,
|
||||
and if it is a subordinate object, its parent.
|
||||
@@ -62,7 +65,7 @@ class ObjectInformation(ReadOnlyMapping):
|
||||
member_name: Optional[str] = None,
|
||||
parent: Optional['ObjectInterface'] = None,
|
||||
native_layer_name: Optional[str] = None):
|
||||
"""Constructs a container for basic information about an object
|
||||
"""Constructs a container for basic information about an object.
|
||||
|
||||
Args:
|
||||
layer_name: Layer from which the data for the object will be read
|
||||
@@ -81,11 +84,12 @@ class ObjectInformation(ReadOnlyMapping):
|
||||
|
||||
|
||||
class ObjectInterface(metaclass = ABCMeta):
|
||||
"""A base object required to be the ancestor of every object used in volatility"""
|
||||
"""A base object required to be the ancestor of every object used in
|
||||
volatility."""
|
||||
|
||||
def __init__(self, context: 'interfaces_context.ContextInterface', type_name: str, object_info: 'ObjectInformation',
|
||||
**kwargs) -> None:
|
||||
"""Constructs an Object adhering to the ObjectInterface
|
||||
"""Constructs an Object adhering to the ObjectInterface.
|
||||
|
||||
Args:
|
||||
context: The context associated with the object
|
||||
@@ -111,27 +115,30 @@ class ObjectInterface(metaclass = ABCMeta):
|
||||
self._context = context
|
||||
|
||||
def __getattr__(self, attr: str) -> Any:
|
||||
"""Method for ensuring volatility members can be returned"""
|
||||
"""Method for ensuring volatility members can be returned."""
|
||||
raise AttributeError
|
||||
|
||||
@property
|
||||
def vol(self) -> ReadOnlyMapping:
|
||||
"""Returns the volatility specific object information"""
|
||||
"""Returns the volatility specific object information."""
|
||||
# Wrap the outgoing vol in a read-only proxy
|
||||
return ReadOnlyMapping(self._vol)
|
||||
|
||||
@abstractmethod
|
||||
def write(self, value: Any):
|
||||
"""Writes the new value into the format at the offset the object currently resides at"""
|
||||
"""Writes the new value into the format at the offset the object
|
||||
currently resides at."""
|
||||
|
||||
def validate(self) -> bool:
|
||||
"""A method that can be overridden to validate this object. It does not return and its return value should not be used.
|
||||
"""A method that can be overridden to validate this object. It does
|
||||
not return and its return value should not be used.
|
||||
|
||||
Raises InvalidDataException on failure to validate the data correctly.
|
||||
Raises InvalidDataException on failure to validate the data
|
||||
correctly.
|
||||
"""
|
||||
|
||||
def get_symbol_table(self) -> 'interfaces.symbols.SymbolTableInterface':
|
||||
"""Returns the symbol table for this particular object
|
||||
"""Returns the symbol table for this particular object.
|
||||
|
||||
Returns none if the symbol table cannot be identified.
|
||||
"""
|
||||
@@ -143,9 +150,11 @@ class ObjectInterface(metaclass = ABCMeta):
|
||||
return self._context.symbol_space[table_name]
|
||||
|
||||
def cast(self, new_type_name: str, **additional) -> 'ObjectInterface':
|
||||
"""Returns a new object at the offset and from the layer that the current object inhabits
|
||||
"""Returns a new object at the offset and from the layer that the
|
||||
current object inhabits.
|
||||
|
||||
.. note:: If new type name does not include a symbol table, the symbol table for the current object is used
|
||||
.. note:: If new type name does not include a symbol table, the
|
||||
symbol table for the current object is used
|
||||
"""
|
||||
# TODO: Carefully consider the implications of casting and how it should work
|
||||
if constants.BANG not in new_type_name:
|
||||
@@ -163,7 +172,8 @@ class ObjectInterface(metaclass = ABCMeta):
|
||||
return object_template(context = self._context, object_info = object_info)
|
||||
|
||||
def has_member(self, member_name: str) -> bool:
|
||||
"""Returns whether the object would contain a member called member_name
|
||||
"""Returns whether the object would contain a member called
|
||||
member_name.
|
||||
|
||||
Args:
|
||||
member_name: Name to test whether a member exists within the type structure
|
||||
@@ -171,47 +181,55 @@ class ObjectInterface(metaclass = ABCMeta):
|
||||
return False
|
||||
|
||||
class VolTemplateProxy(metaclass = abc.ABCMeta):
|
||||
"""A container for proxied methods that the ObjectTemplate of this object will call. This is primarily to keep
|
||||
methods together for easy organization/management, there is no significant need for it to be a separate class.
|
||||
"""A container for proxied methods that the ObjectTemplate of this
|
||||
object will call. This is primarily to keep methods together for easy
|
||||
organization/management, there is no significant need for it to be a
|
||||
separate class.
|
||||
|
||||
The methods of this class *must* be class methods rather than standard methods, to allow for code reuse.
|
||||
Each method also takes a template since the templates may contain the necessary data about the
|
||||
yet-to-be-constructed object. It allows objects to control how their templates respond without needing to write
|
||||
new templates for each and every potental object type."""
|
||||
The methods of this class *must* be class methods rather than
|
||||
standard methods, to allow for code reuse. Each method also
|
||||
takes a template since the templates may contain the necessary
|
||||
data about the yet-to-be-constructed object. It allows objects
|
||||
to control how their templates respond without needing to write
|
||||
new templates for each and every potental object type.
|
||||
"""
|
||||
_methods = [] # type: List[str]
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def size(cls, template: 'Template') -> int:
|
||||
"""Returns the size of the template object"""
|
||||
"""Returns the size of the template object."""
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def children(cls, template: 'Template') -> List['Template']:
|
||||
"""Returns the children of the template"""
|
||||
"""Returns the children of the template."""
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def replace_child(cls, template: 'Template', old_child: 'Template', new_child: 'Template') -> None:
|
||||
"""Substitutes the old_child for the new_child"""
|
||||
"""Substitutes the old_child for the new_child."""
|
||||
raise KeyError("Template does not contain any children to replace: {}".format(template.vol.type_name))
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def relative_child_offset(cls, template: 'Template', child: str) -> int:
|
||||
"""Returns the relative offset from the head of the parent data to the child member"""
|
||||
"""Returns the relative offset from the head of the parent data to
|
||||
the child member."""
|
||||
raise KeyError("Template does not contain any children: {}".format(template.vol.type_name))
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def has_member(cls, template: 'Template', member_name: str) -> bool:
|
||||
"""Returns whether the object would contain a member called member_name"""
|
||||
"""Returns whether the object would contain a member called
|
||||
member_name."""
|
||||
return False
|
||||
|
||||
|
||||
class Template:
|
||||
"""Class for all Factories that take offsets, and data layers and produce objects
|
||||
"""Class for all Factories that take offsets, and data layers and produce
|
||||
objects.
|
||||
|
||||
This is effectively a class for currying object calls. It creates a callable that can be called with the following
|
||||
parameters:
|
||||
@@ -233,7 +251,7 @@ class Template:
|
||||
"""
|
||||
|
||||
def __init__(self, type_name: str, **arguments) -> None:
|
||||
"""Stores the keyword arguments for later object creation"""
|
||||
"""Stores the keyword arguments for later object creation."""
|
||||
# Allow the updating of template arguments whilst still in template form
|
||||
super().__init__()
|
||||
self._arguments = arguments
|
||||
@@ -242,12 +260,16 @@ class Template:
|
||||
|
||||
@property
|
||||
def vol(self) -> ReadOnlyMapping:
|
||||
"""Returns a volatility information object, much like the :class:`~volatility.framework.interfaces.objects.ObjectInformation` provides"""
|
||||
"""Returns a volatility information object, much like the
|
||||
:class:`~volatility.framework.interfaces.objects.ObjectInformation`
|
||||
provides."""
|
||||
return ReadOnlyMapping(self._vol)
|
||||
|
||||
@property
|
||||
def children(self) -> List['Template']:
|
||||
"""The children of this template (such as member types, sub-types and base-types where they are relevant).
|
||||
"""The children of this template (such as member types, sub-types and
|
||||
base-types where they are relevant).
|
||||
|
||||
Used to traverse the template tree.
|
||||
"""
|
||||
return []
|
||||
@@ -255,31 +277,36 @@ class Template:
|
||||
@property
|
||||
@abstractmethod
|
||||
def size(self) -> int:
|
||||
"""Returns the size of the template"""
|
||||
"""Returns the size of the template."""
|
||||
|
||||
@abstractmethod
|
||||
def relative_child_offset(self, child: str) -> int:
|
||||
"""Returns the relative offset of the `child` member from its parent offset"""
|
||||
"""Returns the relative offset of the `child` member from its parent
|
||||
offset."""
|
||||
|
||||
@abstractmethod
|
||||
def replace_child(self, old_child: 'Template', new_child: 'Template') -> None:
|
||||
"""Replaces `old_child` with `new_child` in the list of children"""
|
||||
"""Replaces `old_child` with `new_child` in the list of children."""
|
||||
|
||||
@abstractmethod
|
||||
def has_member(self, member_name: str) -> bool:
|
||||
"""Returns whether the object would contain a member called `member_name`"""
|
||||
"""Returns whether the object would contain a member called
|
||||
`member_name`"""
|
||||
|
||||
def clone(self) -> 'Template':
|
||||
"""Returns a copy of the original Template as constructed (without `update_vol` additions having been made)"""
|
||||
"""Returns a copy of the original Template as constructed (without
|
||||
`update_vol` additions having been made)"""
|
||||
clone = self.__class__(**self._vol.parents.new_child())
|
||||
return clone
|
||||
|
||||
def update_vol(self, **new_arguments) -> None:
|
||||
"""Updates the keyword arguments with values that will **not** be carried across to clones"""
|
||||
"""Updates the keyword arguments with values that will **not** be
|
||||
carried across to clones."""
|
||||
self._vol.update(new_arguments)
|
||||
|
||||
def __getattr__(self, attr: str) -> Any:
|
||||
"""Exposes any other values stored in ._vol as attributes (for example, enumeration choices)"""
|
||||
"""Exposes any other values stored in ._vol as attributes (for example,
|
||||
enumeration choices)"""
|
||||
if attr != '_vol':
|
||||
if attr in self._vol:
|
||||
return self._vol[attr]
|
||||
@@ -287,4 +314,4 @@ class Template:
|
||||
|
||||
def __call__(self, context: 'interfaces_context.ContextInterface',
|
||||
object_info: ObjectInformation) -> ObjectInterface:
|
||||
"""Constructs the object"""
|
||||
"""Constructs the object."""
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
#
|
||||
"""Plugins are the `functions` of the volatility framework.
|
||||
|
||||
They are called and carry out some algorithms on data stored in layers using objects constructed from symbols.
|
||||
They are called and carry out some algorithms on data stored in layers
|
||||
using objects constructed from symbols.
|
||||
"""
|
||||
|
||||
# Configuration interfaces must be imported separately, since we're part of interfaces and can't import ourselves
|
||||
@@ -21,7 +22,8 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FileInterface(metaclass = ABCMeta):
|
||||
"""Class for storing Files in the plugin as a means to output a file or files when necessary"""
|
||||
"""Class for storing Files in the plugin as a means to output a file or
|
||||
files when necessary."""
|
||||
|
||||
def __init__(self, filename: str, data: bytes = None) -> None:
|
||||
"""
|
||||
@@ -37,14 +39,16 @@ class FileInterface(metaclass = ABCMeta):
|
||||
|
||||
|
||||
class FileConsumerInterface(object):
|
||||
"""Class for consuming files potentially produced by plugins
|
||||
"""Class for consuming files potentially produced by plugins.
|
||||
|
||||
We use the producer/consumer model to ensure we can avoid running out of memory by storing every file produced.
|
||||
The downside is, we can't provide much feedback to the producer about what happened to their file (other than exceptions).
|
||||
We use the producer/consumer model to ensure we can avoid running
|
||||
out of memory by storing every file produced. The downside is, we
|
||||
can't provide much feedback to the producer about what happened to
|
||||
their file (other than exceptions).
|
||||
"""
|
||||
|
||||
def consume_file(self, file: FileInterface) -> None:
|
||||
"""Consumes a file as passed back to a UI by a plugin
|
||||
"""Consumes a file as passed back to a UI by a plugin.
|
||||
|
||||
Args:
|
||||
file: A FileInterface object with the data to write to a file
|
||||
@@ -67,9 +71,11 @@ class FileConsumerInterface(object):
|
||||
|
||||
class PluginInterface(interfaces_configuration.ConfigurableInterface, metaclass = ABCMeta):
|
||||
"""Class that defines the basic interface that all Plugins must maintain.
|
||||
The constructor must only take a `context` and `config_path`, so that plugins can be launched automatically. As
|
||||
such all configuration information must be provided through the requirements and configuration information in the
|
||||
context it is passed.
|
||||
|
||||
The constructor must only take a `context` and `config_path`, so
|
||||
that plugins can be launched automatically. As such all
|
||||
configuration information must be provided through the requirements
|
||||
and configuration information in the context it is passed.
|
||||
"""
|
||||
|
||||
# Be careful with inheritance around this
|
||||
@@ -97,11 +103,12 @@ class PluginInterface(interfaces_configuration.ConfigurableInterface, metaclass
|
||||
self._file_consumer = None # type: Optional[FileConsumerInterface]
|
||||
|
||||
def set_file_consumer(self, consumer: FileConsumerInterface) -> None:
|
||||
"""Sets the file consumer to be used by this plugin"""
|
||||
"""Sets the file consumer to be used by this plugin."""
|
||||
self._file_consumer = consumer
|
||||
|
||||
def produce_file(self, filedata: FileInterface) -> None:
|
||||
"""Adds a file to the plugin's file store and returns the chosen filename for the file"""
|
||||
"""Adds a file to the plugin's file store and returns the chosen
|
||||
filename for the file."""
|
||||
if self._file_consumer:
|
||||
self._file_consumer.consume_file(filedata)
|
||||
else:
|
||||
@@ -109,7 +116,8 @@ class PluginInterface(interfaces_configuration.ConfigurableInterface, metaclass
|
||||
|
||||
@classproperty
|
||||
def version(cls) -> Tuple[int, int, int]:
|
||||
"""The version of the current interface (classmethods available on the plugin).
|
||||
"""The version of the current interface (classmethods available on the
|
||||
plugin).
|
||||
|
||||
It is strongly recommended that Semantic Versioning be used (and the default version verification is defined that way):
|
||||
|
||||
@@ -121,12 +129,12 @@ class PluginInterface(interfaces_configuration.ConfigurableInterface, metaclass
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces_configuration.RequirementInterface]:
|
||||
"""Returns a list of Requirement objects for this plugin"""
|
||||
"""Returns a list of Requirement objects for this plugin."""
|
||||
return []
|
||||
|
||||
@abstractmethod
|
||||
def run(self) -> interfaces_renderers.TreeGrid:
|
||||
"""Executes the functionality of the code
|
||||
"""Executes the functionality of the code.
|
||||
|
||||
.. note:: This method expects `self.validate` to have been called to ensure all necessary options have been provided
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
# 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
|
||||
#
|
||||
"""All plugins output a TreeGrid object which must then be rendered (eithe by a GUI, or as text output, html output
|
||||
or in some other form. This module defines both the output format (:class:`TreeGrid`) and the renderer interface
|
||||
which can interact with a TreeGrid to produce suitable output."""
|
||||
"""All plugins output a TreeGrid object which must then be rendered (eithe by a
|
||||
GUI, or as text output, html output or in some other form.
|
||||
|
||||
This module defines both the output format (:class:`TreeGrid`) and the
|
||||
renderer interface which can interact with a TreeGrid to produce
|
||||
suitable output.
|
||||
"""
|
||||
|
||||
import collections
|
||||
import datetime
|
||||
@@ -16,19 +20,21 @@ RenderOption = Any
|
||||
|
||||
|
||||
class Renderer(metaclass = ABCMeta):
|
||||
"""Class that defines the interface that all output renderers must support"""
|
||||
"""Class that defines the interface that all output renderers must
|
||||
support."""
|
||||
|
||||
def __init__(self, options: Optional[List[RenderOption]] = None) -> None:
|
||||
"""Accepts an options object to configure the renderers"""
|
||||
"""Accepts an options object to configure the renderers."""
|
||||
# FIXME: Once the config option objects are in place, put the _type_check in place
|
||||
|
||||
@abstractmethod
|
||||
def get_render_options(self) -> List[RenderOption]:
|
||||
"""Returns a list of rendering options"""
|
||||
"""Returns a list of rendering options."""
|
||||
|
||||
@abstractmethod
|
||||
def render(self, grid: 'TreeGrid') -> None:
|
||||
"""Takes a grid object and renders it based on the object's preferences"""
|
||||
"""Takes a grid object and renders it based on the object's
|
||||
preferences."""
|
||||
|
||||
|
||||
class ColumnSortKey(metaclass = ABCMeta):
|
||||
@@ -36,52 +42,58 @@ class ColumnSortKey(metaclass = ABCMeta):
|
||||
|
||||
@abstractmethod
|
||||
def __call__(self, values: List[Any]) -> Any:
|
||||
"""The key function passed as a sort key to the TreeGrid's visit function"""
|
||||
"""The key function passed as a sort key to the TreeGrid's visit
|
||||
function."""
|
||||
|
||||
|
||||
class TreeNode(collections.Sequence, metaclass = ABCMeta):
|
||||
|
||||
def __init__(self, path, treegrid, parent, values):
|
||||
"""Initializes the TreeNode"""
|
||||
"""Initializes the TreeNode."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def values(self) -> Iterable['BaseTypes']:
|
||||
"""Returns the list of values from the particular node, based on column index"""
|
||||
"""Returns the list of values from the particular node, based on column
|
||||
index."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def path(self) -> str:
|
||||
"""Returns a path identifying string
|
||||
"""Returns a path identifying string.
|
||||
|
||||
This should be seen as opaque by external classes,
|
||||
Parsing of path locations based on this string are not guaranteed to remain stable.
|
||||
This should be seen as opaque by external classes, Parsing of
|
||||
path locations based on this string are not guaranteed to remain
|
||||
stable.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def parent(self) -> Optional['TreeNode']:
|
||||
"""Returns the parent node of this node or None"""
|
||||
"""Returns the parent node of this node or None."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def path_depth(self) -> int:
|
||||
"""Return the path depth of the current node"""
|
||||
"""Return the path depth of the current node."""
|
||||
|
||||
@abstractmethod
|
||||
def path_changed(self, path: str, added: bool = False) -> None:
|
||||
"""Updates the path based on the addition or removal of a node higher up in the tree
|
||||
"""Updates the path based on the addition or removal of a node higher
|
||||
up in the tree.
|
||||
|
||||
This should only be called by the containing TreeGrid and expects to only be called for affected nodes.
|
||||
This should only be called by the containing TreeGrid and
|
||||
expects to only be called for affected nodes.
|
||||
"""
|
||||
|
||||
|
||||
class BaseAbsentValue(object):
|
||||
"""Class that represents values which are not present for some reason"""
|
||||
"""Class that represents values which are not present for some reason."""
|
||||
|
||||
|
||||
class Disassembly(object):
|
||||
"""A class to indicate that the bytes provided should be disassembled (based on the architecture)"""
|
||||
"""A class to indicate that the bytes provided should be disassembled
|
||||
(based on the architecture)"""
|
||||
possible_architectures = ['intel', 'intel64', 'arm', 'arm64']
|
||||
|
||||
def __init__(self, data: bytes, offset: int = 0, architecture: str = 'intel64') -> None:
|
||||
@@ -120,7 +132,7 @@ class TreeGrid(object, metaclass = ABCMeta):
|
||||
base_types = (int, str, float, bytes, datetime.datetime, Disassembly) # type: ClassVar[Tuple]
|
||||
|
||||
def __init__(self, columns: ColumnsType, generator: Generator) -> None:
|
||||
"""Constructs a TreeGrid object using a specific set of columns
|
||||
"""Constructs a TreeGrid object using a specific set of columns.
|
||||
|
||||
The TreeGrid itself is a root element, that can have children but no values.
|
||||
The TreeGrid does *not* contain any information about formatting,
|
||||
@@ -134,48 +146,50 @@ class TreeGrid(object, metaclass = ABCMeta):
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def sanitize_name(text: str) -> str:
|
||||
"""Method used to sanitize column names for TreeNodes"""
|
||||
"""Method used to sanitize column names for TreeNodes."""
|
||||
|
||||
@abstractmethod
|
||||
def populate(self, func: VisitorSignature = None, initial_accumulator: Any = None) -> None:
|
||||
"""Populates the tree by consuming the TreeGrid's construction generator
|
||||
Func is called on every node, so can be used to create output on demand
|
||||
"""Populates the tree by consuming the TreeGrid's construction
|
||||
generator Func is called on every node, so can be used to create output
|
||||
on demand.
|
||||
|
||||
This is equivalent to a one-time visit.
|
||||
This is equivalent to a one-time visit.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def populated(self) -> bool:
|
||||
"""Indicates that population has completed and the tree may now be manipulated separately"""
|
||||
"""Indicates that population has completed and the tree may now be
|
||||
manipulated separately."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def columns(self) -> List[Column]:
|
||||
"""Returns the available columns and their ordering and types"""
|
||||
"""Returns the available columns and their ordering and types."""
|
||||
|
||||
@abstractmethod
|
||||
def children(self, node: TreeNode) -> List[TreeNode]:
|
||||
"""Returns the subnodes of a particular node in order"""
|
||||
"""Returns the subnodes of a particular node in order."""
|
||||
|
||||
@abstractmethod
|
||||
def values(self, node: TreeNode) -> Tuple[BaseTypes, ...]:
|
||||
"""Returns the values for a particular node
|
||||
"""Returns the values for a particular node.
|
||||
|
||||
The values returned are mutable,
|
||||
The values returned are mutable,
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def is_ancestor(self, node: TreeNode, descendant: TreeNode) -> bool:
|
||||
"""Returns true if descendent is a child, grandchild, etc of node"""
|
||||
"""Returns true if descendent is a child, grandchild, etc of node."""
|
||||
|
||||
@abstractmethod
|
||||
def max_depth(self) -> int:
|
||||
"""Returns the maximum depth of the tree"""
|
||||
"""Returns the maximum depth of the tree."""
|
||||
|
||||
@staticmethod
|
||||
def path_depth(node: TreeNode) -> int:
|
||||
"""Returns the path depth of a particular node"""
|
||||
"""Returns the path depth of a particular node."""
|
||||
return node.path_depth
|
||||
|
||||
@abstractmethod
|
||||
@@ -186,16 +200,16 @@ class TreeGrid(object, metaclass = ABCMeta):
|
||||
sort_key: ColumnSortKey = None) -> None:
|
||||
"""Visits all the nodes in a tree, calling function on each one.
|
||||
|
||||
function should have the signature function(node, accumulator) and return new_accumulator
|
||||
If accumulators are not needed, the function must still accept a second parameter.
|
||||
function should have the signature function(node, accumulator) and return new_accumulator
|
||||
If accumulators are not needed, the function must still accept a second parameter.
|
||||
|
||||
The order of that the nodes are visited is always depth first, however, the order children are traversed can
|
||||
be set based on a sort_key function which should accept a node's values and return something that can be
|
||||
sorted to receive the desired order (similar to the sort/sorted key).
|
||||
The order of that the nodes are visited is always depth first, however, the order children are traversed can
|
||||
be set based on a sort_key function which should accept a node's values and return something that can be
|
||||
sorted to receive the desired order (similar to the sort/sorted key).
|
||||
|
||||
Args:
|
||||
node: The initial node to be visited
|
||||
function: The visitor to apply to the nodes under the initial node
|
||||
initial_accumulator: An accumulator that allows data to be transfered between one visitor call to the next
|
||||
sort_key: Information about the sort order of columns in order to determine the ordering of results
|
||||
Args:
|
||||
node: The initial node to be visited
|
||||
function: The visitor to apply to the nodes under the initial node
|
||||
initial_accumulator: An accumulator that allows data to be transfered between one visitor call to the next
|
||||
sort_key: Information about the sort order of columns in order to determine the ordering of results
|
||||
"""
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# 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
|
||||
#
|
||||
"""Symbols provide structural information about a set of bytes.
|
||||
"""
|
||||
"""Symbols provide structural information about a set of bytes."""
|
||||
import bisect
|
||||
import collections.abc
|
||||
from abc import abstractmethod, ABC
|
||||
@@ -13,7 +12,7 @@ from volatility.framework.interfaces import configuration, objects, context as i
|
||||
|
||||
|
||||
class SymbolInterface:
|
||||
"""Contains information about a named location in a program's memory"""
|
||||
"""Contains information about a named location in a program's memory."""
|
||||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
@@ -40,12 +39,12 @@ class SymbolInterface:
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Returns the name of the symbol"""
|
||||
"""Returns the name of the symbol."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def type_name(self) -> Optional[str]:
|
||||
"""Returns the name of the type that the symbol represents"""
|
||||
"""Returns the name of the type that the symbol represents."""
|
||||
# Objects and ObjectTemplates should *always* get a type_name when they're constructed, so allow the IndexError
|
||||
if self.type is None:
|
||||
return None
|
||||
@@ -53,22 +52,23 @@ class SymbolInterface:
|
||||
|
||||
@property
|
||||
def type(self) -> Optional[objects.Template]:
|
||||
"""Returns the type that the symbol represents"""
|
||||
"""Returns the type that the symbol represents."""
|
||||
return self._type
|
||||
|
||||
@property
|
||||
def address(self) -> int:
|
||||
"""Returns the relative address of the symbol within the compilation unit"""
|
||||
"""Returns the relative address of the symbol within the compilation
|
||||
unit."""
|
||||
return self._address
|
||||
|
||||
@property
|
||||
def constant_data(self) -> Optional[bytes]:
|
||||
"""Returns any constant data associated with the symbol"""
|
||||
"""Returns any constant data associated with the symbol."""
|
||||
return self._constant_data
|
||||
|
||||
|
||||
class BaseSymbolTableInterface:
|
||||
"""The base interface, inherited by both NativeTables and SymbolTables
|
||||
"""The base interface, inherited by both NativeTables and SymbolTables.
|
||||
|
||||
native_types is a NativeTableInterface used for native types for the particular loaded symbol table
|
||||
table_mapping allows tables referenced by symbols to be remapped to a different table name if necessary
|
||||
@@ -104,28 +104,28 @@ class BaseSymbolTableInterface:
|
||||
# ## Required Symbol functions
|
||||
|
||||
def get_symbol(self, name: str) -> SymbolInterface:
|
||||
"""Resolves a symbol name into a symbol object
|
||||
"""Resolves a symbol name into a symbol object.
|
||||
|
||||
If the symbol isn't found, it raises a SymbolError exception
|
||||
If the symbol isn't found, it raises a SymbolError exception
|
||||
"""
|
||||
raise NotImplementedError("Abstract property get_symbol not implemented by subclass.")
|
||||
|
||||
@property
|
||||
def symbols(self) -> Iterable[str]:
|
||||
"""Returns an iterator of the Symbol names"""
|
||||
"""Returns an iterator of the Symbol names."""
|
||||
raise NotImplementedError("Abstract property symbols not implemented by subclass.")
|
||||
|
||||
# ## Required Type functions
|
||||
|
||||
@property
|
||||
def types(self) -> Iterable[str]:
|
||||
"""Returns an iterator of the Symbol type names"""
|
||||
"""Returns an iterator of the Symbol type names."""
|
||||
raise NotImplementedError("Abstract property types not implemented by subclass.")
|
||||
|
||||
def get_type(self, name: str) -> objects.Template:
|
||||
"""Resolves a symbol name into an object template
|
||||
"""Resolves a symbol name into an object template.
|
||||
|
||||
If the symbol isn't found it raises a SymbolError exception
|
||||
If the symbol isn't found it raises a SymbolError exception
|
||||
"""
|
||||
raise NotImplementedError("Abstract method get_type not implemented by subclass.")
|
||||
|
||||
@@ -133,56 +133,59 @@ class BaseSymbolTableInterface:
|
||||
|
||||
@property
|
||||
def enumerations(self) -> Iterable[Any]:
|
||||
"""Returns an iterator of the Enumeration names"""
|
||||
"""Returns an iterator of the Enumeration names."""
|
||||
raise NotImplementedError("Abstract property enumerations not implemented by subclass.")
|
||||
|
||||
# ## Native Type Handler
|
||||
|
||||
@property
|
||||
def natives(self) -> 'NativeTableInterface':
|
||||
"""Returns None or a NativeTable for handling space specific native types"""
|
||||
"""Returns None or a NativeTable for handling space specific native
|
||||
types."""
|
||||
return self._native_types
|
||||
|
||||
@natives.setter
|
||||
def natives(self, value: 'NativeTableInterface') -> None:
|
||||
"""Checks the natives value and then applies it internally
|
||||
"""Checks the natives value and then applies it internally.
|
||||
|
||||
WARNING: This allows changing the underlying size of all the other types referenced in the SymbolTable
|
||||
WARNING: This allows changing the underlying size of all the other types referenced in the SymbolTable
|
||||
"""
|
||||
self._native_types = value
|
||||
|
||||
# ## Functions for overriding classes
|
||||
|
||||
def set_type_class(self, name: str, clazz: Type[objects.ObjectInterface]) -> None:
|
||||
"""Overrides the object class for a specific Symbol type
|
||||
"""Overrides the object class for a specific Symbol type.
|
||||
|
||||
Name *must* be present in self.types
|
||||
Name *must* be present in self.types
|
||||
|
||||
Args:
|
||||
name: The name of the type to override the class for
|
||||
clazz: The actual class to override for the provided type name
|
||||
Args:
|
||||
name: The name of the type to override the class for
|
||||
clazz: The actual class to override for the provided type name
|
||||
"""
|
||||
raise NotImplementedError("Abstract method set_type_class not implemented yet.")
|
||||
|
||||
def get_type_class(self, name: str) -> Type[objects.ObjectInterface]:
|
||||
"""Returns the class associated with a Symbol type"""
|
||||
"""Returns the class associated with a Symbol type."""
|
||||
raise NotImplementedError("Abstract method get_type_class not implemented yet.")
|
||||
|
||||
def del_type_class(self, name: str) -> None:
|
||||
"""Removes the associated class override for a specific Symbol type"""
|
||||
"""Removes the associated class override for a specific Symbol type."""
|
||||
raise NotImplementedError("Abstract method del_type_class not implemented yet.")
|
||||
|
||||
# ## Convenience functions for location symbols
|
||||
|
||||
def get_symbol_type(self, name: str) -> Optional[objects.Template]:
|
||||
"""Resolves a symbol name into a symbol and then resolves the symbol's type"""
|
||||
"""Resolves a symbol name into a symbol and then resolves the symbol's
|
||||
type."""
|
||||
type_name = self.get_symbol(name).type_name
|
||||
if type_name is None:
|
||||
return None
|
||||
return self.get_type(type_name)
|
||||
|
||||
def get_symbols_by_type(self, type_name: str) -> Iterable[str]:
|
||||
"""Returns the name of all symbols in this table that have type matching type_name"""
|
||||
"""Returns the name of all symbols in this table that have type
|
||||
matching type_name."""
|
||||
for symbol_name in self.symbols:
|
||||
# This allows for searching with and without the table name (in case multiple tables contain
|
||||
# the same symbol name and we've not specifically been told which one)
|
||||
@@ -192,7 +195,8 @@ class BaseSymbolTableInterface:
|
||||
yield symbol.name
|
||||
|
||||
def get_symbols_by_location(self, offset: int, size: int = 0) -> Iterable[str]:
|
||||
"""Returns the name of all symbols in this table that live at a particular offset"""
|
||||
"""Returns the name of all symbols in this table that live at a
|
||||
particular offset."""
|
||||
if size < 0:
|
||||
raise ValueError("Size must be strictly non-negative")
|
||||
if not self._sort_symbols:
|
||||
@@ -206,50 +210,54 @@ class BaseSymbolTableInterface:
|
||||
|
||||
|
||||
class SymbolSpaceInterface(collections.abc.Mapping):
|
||||
"""An interface for the container that holds all the symbol-containing tables for use within a context"""
|
||||
"""An interface for the container that holds all the symbol-containing
|
||||
tables for use within a context."""
|
||||
|
||||
def free_table_name(self, prefix: str = "layer") -> str:
|
||||
"""Returns an unused table name to ensure no collision occurs when inserting a symbol table"""
|
||||
"""Returns an unused table name to ensure no collision occurs when
|
||||
inserting a symbol table."""
|
||||
|
||||
@abstractmethod
|
||||
def get_symbols_by_type(self, type_name: str) -> Iterable[str]:
|
||||
"""Returns all symbols based on the type of the symbol"""
|
||||
"""Returns all symbols based on the type of the symbol."""
|
||||
|
||||
@abstractmethod
|
||||
def get_symbols_by_location(self, offset: int, size: int = 0, table_name: Optional[str] = None) -> Iterable[str]:
|
||||
"""Returns all symbols that exist at a specific relative address"""
|
||||
"""Returns all symbols that exist at a specific relative address."""
|
||||
|
||||
@abstractmethod
|
||||
def get_type(self, type_name: str) -> objects.Template:
|
||||
"""Look-up a type name across all the contained symbol tables"""
|
||||
"""Look-up a type name across all the contained symbol tables."""
|
||||
|
||||
@abstractmethod
|
||||
def get_symbol(self, symbol_name: str) -> SymbolInterface:
|
||||
"""Look-up a symbol name across all the contained symbol tables"""
|
||||
"""Look-up a symbol name across all the contained symbol tables."""
|
||||
|
||||
@abstractmethod
|
||||
def get_enumeration(self, enum_name: str) -> objects.Template:
|
||||
"""Look-up an enumeration across all the contained symbol tables"""
|
||||
"""Look-up an enumeration across all the contained symbol tables."""
|
||||
|
||||
@abstractmethod
|
||||
def has_type(self, name: str) -> bool:
|
||||
"""Determines whether a type exists in the contained symbol tables"""
|
||||
"""Determines whether a type exists in the contained symbol tables."""
|
||||
|
||||
@abstractmethod
|
||||
def has_symbol(self, name: str) -> bool:
|
||||
"""Determines whether a symbol exists in the contained symbol tables"""
|
||||
"""Determines whether a symbol exists in the contained symbol
|
||||
tables."""
|
||||
|
||||
@abstractmethod
|
||||
def has_enumeration(self, name: str) -> bool:
|
||||
"""Determines whether an enumeration choice exists in the contained symbol tables"""
|
||||
"""Determines whether an enumeration choice exists in the contained
|
||||
symbol tables."""
|
||||
|
||||
@abstractmethod
|
||||
def append(self, value: BaseSymbolTableInterface) -> None:
|
||||
"""Adds a symbol_list to the end of the space"""
|
||||
"""Adds a symbol_list to the end of the space."""
|
||||
|
||||
|
||||
class SymbolTableInterface(BaseSymbolTableInterface, configuration.ConfigurableInterface, ABC):
|
||||
"""Handles a table of symbols"""
|
||||
"""Handles a table of symbols."""
|
||||
|
||||
# FIXME: native_types and table_mapping aren't recorded in the configuration
|
||||
def __init__(self,
|
||||
@@ -284,7 +292,7 @@ class SymbolTableInterface(BaseSymbolTableInterface, configuration.ConfigurableI
|
||||
|
||||
|
||||
class NativeTableInterface(BaseSymbolTableInterface):
|
||||
"""Class to distinguish NativeSymbolLists from other symbol lists"""
|
||||
"""Class to distinguish NativeSymbolLists from other symbol lists."""
|
||||
|
||||
def get_symbol(self, name: str) -> SymbolInterface:
|
||||
raise exceptions.SymbolError("NativeTables never hold symbols")
|
||||
@@ -302,8 +310,8 @@ class NativeTableInterface(BaseSymbolTableInterface):
|
||||
|
||||
|
||||
class MetadataInterface(object):
|
||||
"""Interface for accessing metadata stored within a symbol table"""
|
||||
"""Interface for accessing metadata stored within a symbol table."""
|
||||
|
||||
def __init__(self, json_data: Dict) -> None:
|
||||
"""Constructor that accepts json_data"""
|
||||
"""Constructor that accepts json_data."""
|
||||
self._json_data = json_data
|
||||
|
||||
@@ -11,13 +11,15 @@ from volatility.framework.symbols import intermed
|
||||
|
||||
|
||||
class WindowsCrashDump32FormatException(exceptions.LayerException):
|
||||
"""Thrown when an error occurs with the underlying Crash file format"""
|
||||
"""Thrown when an error occurs with the underlying Crash file format."""
|
||||
|
||||
|
||||
class WindowsCrashDump32Layer(segmented.SegmentedLayer):
|
||||
"""A Windows crash format TranslationLayer. This TranslationLayer supports
|
||||
Microsoft complete memory dump files. It currently does not support
|
||||
kernel or small memory dump files."""
|
||||
"""A Windows crash format TranslationLayer.
|
||||
|
||||
This TranslationLayer supports Microsoft complete memory dump files.
|
||||
It currently does not support kernel or small memory dump files.
|
||||
"""
|
||||
|
||||
provides = {"type": "physical"}
|
||||
priority = 23
|
||||
@@ -56,7 +58,7 @@ class WindowsCrashDump32Layer(segmented.SegmentedLayer):
|
||||
super().__init__(context, config_path, name)
|
||||
|
||||
def _load_segments(self) -> None:
|
||||
"""Loads up the segments from the meta_layer"""
|
||||
"""Loads up the segments from the meta_layer."""
|
||||
|
||||
segments = []
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Intel(linear.LinearlyMappedLayer):
|
||||
"""Translation Layer for the Intel IA32 memory mapping"""
|
||||
"""Translation Layer for the Intel IA32 memory mapping."""
|
||||
|
||||
priority = 40
|
||||
_entry_format = "<I"
|
||||
@@ -53,14 +53,16 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
|
||||
@classproperty
|
||||
def page_size(cls) -> int:
|
||||
"""Page size for the intel memory layers
|
||||
"""Page size for the intel memory layers.
|
||||
|
||||
All Intel layers work on 4096 byte pages"""
|
||||
All Intel layers work on 4096 byte pages
|
||||
"""
|
||||
return 1 << cls._page_size_in_bits
|
||||
|
||||
@classproperty
|
||||
def bits_per_register(cls) -> int:
|
||||
"""Returns the bits_per_register to determine the range of an IntelTranslationLayer"""
|
||||
"""Returns the bits_per_register to determine the range of an
|
||||
IntelTranslationLayer."""
|
||||
return cls._bits_per_register
|
||||
|
||||
@classproperty
|
||||
@@ -77,7 +79,7 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
|
||||
@staticmethod
|
||||
def _mask(value: int, high_bit: int, low_bit: int) -> int:
|
||||
"""Returns the bits of a value between highbit and lowbit inclusive"""
|
||||
"""Returns the bits of a value between highbit and lowbit inclusive."""
|
||||
high_mask = (1 << (high_bit + 1)) - 1
|
||||
low_mask = (1 << low_bit) - 1
|
||||
mask = (high_mask ^ low_mask)
|
||||
@@ -86,13 +88,15 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
|
||||
@staticmethod
|
||||
def _page_is_valid(entry: int) -> bool:
|
||||
"""Returns whether a particular page is valid based on its entry"""
|
||||
"""Returns whether a particular page is valid based on its entry."""
|
||||
return bool(entry & 1)
|
||||
|
||||
def _translate(self, offset: int) -> Tuple[int, int, str]:
|
||||
"""Translates a specific offset based on paging tables
|
||||
"""Translates a specific offset based on paging tables.
|
||||
|
||||
Returns the translated offset, the contiguous pagesize that the translated address lives in and the layer_name that the address lives in
|
||||
Returns the translated offset, the contiguous pagesize that the
|
||||
translated address lives in and the layer_name that the address
|
||||
lives in
|
||||
"""
|
||||
entry, position = self._translate_entry(offset)
|
||||
|
||||
@@ -105,9 +109,9 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
return page, 1 << (position + 1), self._base_layer
|
||||
|
||||
def _translate_entry(self, offset):
|
||||
"""Translates a specific offset based on paging tables
|
||||
"""Translates a specific offset based on paging tables.
|
||||
|
||||
Returns the translated entry value
|
||||
Returns the translated entry value
|
||||
"""
|
||||
# Setup the entry and how far we are through the offset
|
||||
# Position maintains the number of bits left to process
|
||||
@@ -149,7 +153,7 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
|
||||
@functools.lru_cache(1025)
|
||||
def _get_valid_table(self, base_address: int) -> Optional[bytes]:
|
||||
"""Extracts the table, validates it and returns it if it's valid"""
|
||||
"""Extracts the table, validates it and returns it if it's valid."""
|
||||
table = self._context.layers.read(self._base_layer, base_address, self.page_size)
|
||||
|
||||
# If the table is entirely duplicates, then mark the whole table as bad
|
||||
@@ -158,7 +162,8 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
return table
|
||||
|
||||
def is_valid(self, offset: int, length: int = 1) -> bool:
|
||||
"""Returns whether the address offset can be translated to a valid address"""
|
||||
"""Returns whether the address offset can be translated to a valid
|
||||
address."""
|
||||
try:
|
||||
# TODO: Consider reimplementing this, since calls to mapping can call is_valid
|
||||
return all([
|
||||
@@ -169,9 +174,11 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
return False
|
||||
|
||||
def mapping(self, offset: int, length: int, ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, str]]:
|
||||
"""Returns a sorted iterable of (offset, mapped_offset, length, layer) mappings
|
||||
"""Returns a sorted iterable of (offset, mapped_offset, length, layer)
|
||||
mappings.
|
||||
|
||||
This allows translation layers to provide maps of contiguous regions in one layer
|
||||
This allows translation layers to provide maps of contiguous
|
||||
regions in one layer
|
||||
"""
|
||||
if length == 0:
|
||||
try:
|
||||
@@ -208,7 +215,8 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
|
||||
@property
|
||||
def dependencies(self) -> List[str]:
|
||||
"""Returns a list of the lower layer names that this layer is dependent upon"""
|
||||
"""Returns a list of the lower layer names that this layer is dependent
|
||||
upon."""
|
||||
return [self._base_layer] + self._swap_layers
|
||||
|
||||
@classmethod
|
||||
@@ -223,7 +231,8 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
|
||||
|
||||
class IntelPAE(Intel):
|
||||
"""Class for handling Physical Address Extensions for Intel architectures"""
|
||||
"""Class for handling Physical Address Extensions for Intel
|
||||
architectures."""
|
||||
|
||||
priority = 35
|
||||
_entry_format = "<Q"
|
||||
@@ -234,7 +243,8 @@ class IntelPAE(Intel):
|
||||
|
||||
|
||||
class Intel32e(Intel):
|
||||
"""Class for handling 64-bit (32-bit extensions) for Intel architectures"""
|
||||
"""Class for handling 64-bit (32-bit extensions) for Intel
|
||||
architectures."""
|
||||
|
||||
priority = 30
|
||||
_direct_metadata = collections.ChainMap({'architecture': 'Intel64'}, Intel._direct_metadata)
|
||||
@@ -250,14 +260,14 @@ class WindowsMixin(Intel):
|
||||
|
||||
@staticmethod
|
||||
def _page_is_valid(entry: int) -> bool:
|
||||
"""Returns whether a particular page is valid based on its entry
|
||||
"""Returns whether a particular page is valid based on its entry.
|
||||
|
||||
Windows uses additional "available" bits to store flags
|
||||
These flags allow windows to determine whether a page is still valid
|
||||
Windows uses additional "available" bits to store flags
|
||||
These flags allow windows to determine whether a page is still valid
|
||||
|
||||
Bit 11 is the transition flag, and Bit 10 is the prototype flag
|
||||
Bit 11 is the transition flag, and Bit 10 is the prototype flag
|
||||
|
||||
For more information, see Windows Internals (6th Ed, Part 2, pages 268-269)
|
||||
For more information, see Windows Internals (6th Ed, Part 2, pages 268-269)
|
||||
"""
|
||||
return bool((entry & 1) or ((entry & 1 << 11) and not entry & 1 << 10))
|
||||
|
||||
|
||||
@@ -10,13 +10,15 @@ from volatility.framework.layers import segmented
|
||||
|
||||
|
||||
class LimeFormatException(exceptions.LayerException):
|
||||
"""Thrown when an error occurs with the underlying Lime file format"""
|
||||
"""Thrown when an error occurs with the underlying Lime file format."""
|
||||
|
||||
|
||||
class LimeLayer(segmented.SegmentedLayer):
|
||||
"""A Lime format TranslationLayer. Lime is generally used to store
|
||||
physical memory images where there are large holes in the physical
|
||||
layer"""
|
||||
"""A Lime format TranslationLayer.
|
||||
|
||||
Lime is generally used to store physical memory images where there
|
||||
are large holes in the physical layer
|
||||
"""
|
||||
|
||||
priority = 21
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ from volatility.framework import exceptions, interfaces
|
||||
|
||||
|
||||
class LinearlyMappedLayer(interfaces.layers.TranslationLayerInterface):
|
||||
"""Class to differentiate Linearly Mapped layers (where a => b implies that a + c => b + c)"""
|
||||
"""Class to differentiate Linearly Mapped layers (where a => b implies that
|
||||
a + c => b + c)"""
|
||||
|
||||
### Translation layer convenience function
|
||||
|
||||
@@ -29,7 +30,8 @@ class LinearlyMappedLayer(interfaces.layers.TranslationLayerInterface):
|
||||
|
||||
@functools.lru_cache(maxsize = 512)
|
||||
def read(self, offset: int, length: int, pad: bool = False) -> bytes:
|
||||
"""Reads an offset for length bytes and returns 'bytes' (not 'str') of length size"""
|
||||
"""Reads an offset for length bytes and returns 'bytes' (not 'str') of
|
||||
length size."""
|
||||
current_offset = offset
|
||||
output = [] # type: List[bytes]
|
||||
for (offset, mapped_offset, mapped_length, layer) in self.mapping(offset, length, ignore_errors = pad):
|
||||
@@ -48,7 +50,8 @@ class LinearlyMappedLayer(interfaces.layers.TranslationLayerInterface):
|
||||
return recovered_data + b"\x00" * (length - len(recovered_data))
|
||||
|
||||
def write(self, offset: int, value: bytes) -> None:
|
||||
"""Writes a value at offset, distributing the writing across any underlying mapping"""
|
||||
"""Writes a value at offset, distributing the writing across any
|
||||
underlying mapping."""
|
||||
current_offset = offset
|
||||
length = len(value)
|
||||
for (offset, mapped_offset, length, layer) in self.mapping(offset, length):
|
||||
|
||||
@@ -95,7 +95,8 @@ class PdbMultiStreamFormat(linear.LinearlyMappedLayer):
|
||||
return layer_name
|
||||
|
||||
def _check_header(self) -> Optional[Tuple[str, interfaces.objects.ObjectInterface]]:
|
||||
"""Verifies the header of the PDB file and returns the version of the file"""
|
||||
"""Verifies the header of the PDB file and returns the version of the
|
||||
file."""
|
||||
for header in self._headers:
|
||||
header_type = self.pdb_symbol_table + constants.BANG + header
|
||||
current_header = self.context.object(header_type, self._base_layer, 0)
|
||||
@@ -110,7 +111,8 @@ class PdbMultiStreamFormat(linear.LinearlyMappedLayer):
|
||||
|
||||
@property
|
||||
def dependencies(self) -> List[str]:
|
||||
"""Returns a list of the lower layers that this layer is dependent upon"""
|
||||
"""Returns a list of the lower layers that this layer is dependent
|
||||
upon."""
|
||||
return [self._base_layer]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -10,7 +10,8 @@ from volatility.framework.layers import resources
|
||||
|
||||
|
||||
class BufferDataLayer(interfaces.layers.DataLayerInterface):
|
||||
"""A DataLayer class backed by a buffer in memory, designed for testing and swift data access"""
|
||||
"""A DataLayer class backed by a buffer in memory, designed for testing and
|
||||
swift data access."""
|
||||
|
||||
priority = 10
|
||||
|
||||
@@ -25,21 +26,21 @@ class BufferDataLayer(interfaces.layers.DataLayerInterface):
|
||||
|
||||
@property
|
||||
def maximum_address(self) -> int:
|
||||
"""Returns the largest available address in the space"""
|
||||
"""Returns the largest available address in the space."""
|
||||
return len(self._buffer) - 1
|
||||
|
||||
@property
|
||||
def minimum_address(self) -> int:
|
||||
"""Returns the smallest available address in the space"""
|
||||
"""Returns the smallest available address in the space."""
|
||||
return 0
|
||||
|
||||
def is_valid(self, offset: int, length: int = 1) -> bool:
|
||||
"""Returns whether the offset is valid or not"""
|
||||
"""Returns whether the offset is valid or not."""
|
||||
return bool(self.minimum_address <= offset <= self.maximum_address
|
||||
and self.minimum_address <= offset + length - 1 <= self.maximum_address)
|
||||
|
||||
def read(self, address: int, length: int, pad: bool = False) -> bytes:
|
||||
"""Reads the data from the buffer"""
|
||||
"""Reads the data from the buffer."""
|
||||
if not self.is_valid(address, length):
|
||||
invalid_address = address
|
||||
if self.minimum_address < address <= self.maximum_address:
|
||||
@@ -49,7 +50,7 @@ class BufferDataLayer(interfaces.layers.DataLayerInterface):
|
||||
return self._buffer[address:address + length]
|
||||
|
||||
def write(self, address: int, data: bytes):
|
||||
"""Writes the data from to the buffer"""
|
||||
"""Writes the data from to the buffer."""
|
||||
self._buffer = self._buffer[:address] + data + self._buffer[address + len(data):]
|
||||
|
||||
@classmethod
|
||||
@@ -71,7 +72,7 @@ class DummyLock:
|
||||
|
||||
|
||||
class FileLayer(interfaces.layers.DataLayerInterface):
|
||||
"""a DataLayer backed by a file on the filesystem"""
|
||||
"""a DataLayer backed by a file on the filesystem."""
|
||||
|
||||
priority = 20
|
||||
|
||||
@@ -95,12 +96,13 @@ class FileLayer(interfaces.layers.DataLayerInterface):
|
||||
|
||||
@property
|
||||
def location(self) -> str:
|
||||
"""Returns the location on which this Layer abstracts"""
|
||||
"""Returns the location on which this Layer abstracts."""
|
||||
return self._location
|
||||
|
||||
@property
|
||||
def _file(self) -> IO[Any]:
|
||||
"""Property to prevent the initializer storing an unserializable open file (for context cloning)"""
|
||||
"""Property to prevent the initializer storing an unserializable open
|
||||
file (for context cloning)"""
|
||||
# FIXME: Add "+" to the mode once we've determined whether write mode is enabled
|
||||
mode = "rb"
|
||||
self._file_ = self._file_ or self._accessor.open(self._location, mode)
|
||||
@@ -108,7 +110,7 @@ class FileLayer(interfaces.layers.DataLayerInterface):
|
||||
|
||||
@property
|
||||
def maximum_address(self) -> int:
|
||||
"""Returns the largest available address in the space"""
|
||||
"""Returns the largest available address in the space."""
|
||||
# Zero based, so we return the size of the file minus 1
|
||||
if self._size:
|
||||
return self._size
|
||||
@@ -121,18 +123,18 @@ class FileLayer(interfaces.layers.DataLayerInterface):
|
||||
|
||||
@property
|
||||
def minimum_address(self) -> int:
|
||||
"""Returns the smallest available address in the space"""
|
||||
"""Returns the smallest available address in the space."""
|
||||
return 0
|
||||
|
||||
def is_valid(self, offset: int, length: int = 1) -> bool:
|
||||
"""Returns whether the offset is valid or not"""
|
||||
"""Returns whether the offset is valid or not."""
|
||||
if length <= 0:
|
||||
raise TypeError("Length must be positive")
|
||||
return bool(self.minimum_address <= offset <= self.maximum_address
|
||||
and self.minimum_address <= offset + length - 1 <= self.maximum_address)
|
||||
|
||||
def read(self, offset: int, length: int, pad: bool = False) -> bytes:
|
||||
"""Reads from the file at offset for length"""
|
||||
"""Reads from the file at offset for length."""
|
||||
if not self.is_valid(offset, length):
|
||||
invalid_address = offset
|
||||
if self.minimum_address < offset <= self.maximum_address:
|
||||
@@ -154,9 +156,9 @@ class FileLayer(interfaces.layers.DataLayerInterface):
|
||||
return data
|
||||
|
||||
def write(self, offset: int, data: bytes) -> None:
|
||||
"""Writes to the file
|
||||
"""Writes to the file.
|
||||
|
||||
This will technically allow writes beyond the extent of the file
|
||||
This will technically allow writes beyond the extent of the file
|
||||
"""
|
||||
if not self.is_valid(offset, len(data)):
|
||||
invalid_address = offset
|
||||
@@ -169,15 +171,16 @@ class FileLayer(interfaces.layers.DataLayerInterface):
|
||||
self._file.write(data)
|
||||
|
||||
def __getstate__(self) -> Dict[str, Any]:
|
||||
"""Do not store the open _file_ attribute, our property will ensure the file is open when needed
|
||||
"""Do not store the open _file_ attribute, our property will ensure the
|
||||
file is open when needed.
|
||||
|
||||
This is necessary for multi-processing
|
||||
This is necessary for multi-processing
|
||||
"""
|
||||
self._file_ = None
|
||||
return self.__dict__
|
||||
|
||||
def destroy(self) -> None:
|
||||
"""Closes the file handle"""
|
||||
"""Closes the file handle."""
|
||||
self._file.close()
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -17,11 +17,11 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RegistryFormatException(exceptions.LayerException):
|
||||
"""Thrown when an error occurs with the underlying Registry file format"""
|
||||
"""Thrown when an error occurs with the underlying Registry file format."""
|
||||
|
||||
|
||||
class RegistryInvalidIndex(exceptions.LayerException):
|
||||
"""Thrown when an index that doesn't exist or can't be found occurs"""
|
||||
"""Thrown when an index that doesn't exist or can't be found occurs."""
|
||||
|
||||
|
||||
class RegistryHive(linear.LinearlyMappedLayer):
|
||||
@@ -85,12 +85,12 @@ class RegistryHive(linear.LinearlyMappedLayer):
|
||||
|
||||
@property
|
||||
def address_mask(self) -> int:
|
||||
"""Return a mask that allows for the volatile bit to be set"""
|
||||
"""Return a mask that allows for the volatile bit to be set."""
|
||||
return super().address_mask | 0x80000000
|
||||
|
||||
@property
|
||||
def root_cell_offset(self) -> int:
|
||||
"""Returns the offset for the root cell in this hive"""
|
||||
"""Returns the offset for the root cell in this hive."""
|
||||
try:
|
||||
if self._base_block.Signature.cast("string", max_length = 4, encoding = "latin-1") == 'regf':
|
||||
return self._base_block.RootCell
|
||||
@@ -99,7 +99,7 @@ class RegistryHive(linear.LinearlyMappedLayer):
|
||||
return 0x20
|
||||
|
||||
def get_cell(self, cell_offset: int) -> 'objects.StructType':
|
||||
"""Returns the appropriate Cell value for a cell offset"""
|
||||
"""Returns the appropriate Cell value for a cell offset."""
|
||||
# This would be an _HCELL containing CELL_DATA, but to save time we skip the size of the HCELL
|
||||
cell = self._context.object(
|
||||
object_type = self._table_name + constants.BANG + "_CELL_DATA",
|
||||
@@ -108,7 +108,8 @@ class RegistryHive(linear.LinearlyMappedLayer):
|
||||
return cell
|
||||
|
||||
def get_node(self, cell_offset: int) -> 'objects.StructType':
|
||||
"""Returns the appropriate Node, interpreted from the Cell based on its Signature"""
|
||||
"""Returns the appropriate Node, interpreted from the Cell based on its
|
||||
Signature."""
|
||||
cell = self.get_cell(cell_offset)
|
||||
signature = cell.cast('string', max_length = 2, encoding = 'latin-1')
|
||||
if signature == 'nk':
|
||||
@@ -130,10 +131,11 @@ class RegistryHive(linear.LinearlyMappedLayer):
|
||||
return cell
|
||||
|
||||
def get_key(self, key: str, return_list: bool = False) -> Union[List[objects.StructType], objects.StructType]:
|
||||
"""Gets a specific registry key by key path
|
||||
"""Gets a specific registry key by key path.
|
||||
|
||||
return_list specifies whether the return result will be a single node (default) or a list of nodes from
|
||||
root to the current node (if return_list is true).
|
||||
return_list specifies whether the return result will be a single
|
||||
node (default) or a list of nodes from root to the current node
|
||||
(if return_list is true).
|
||||
"""
|
||||
node_key = [self.get_node(self.root_cell_offset)]
|
||||
if key.endswith("\\"):
|
||||
@@ -159,7 +161,8 @@ class RegistryHive(linear.LinearlyMappedLayer):
|
||||
|
||||
def visit_nodes(self, visitor: Callable[[objects.StructType], None],
|
||||
node: Optional[objects.StructType] = None) -> None:
|
||||
"""Applies a callable (visitor) to all nodes within the registry tree from a given node"""
|
||||
"""Applies a callable (visitor) to all nodes within the registry tree
|
||||
from a given node."""
|
||||
if not node:
|
||||
node = self.get_node(self.root_cell_offset)
|
||||
visitor(node)
|
||||
@@ -168,7 +171,7 @@ class RegistryHive(linear.LinearlyMappedLayer):
|
||||
|
||||
@staticmethod
|
||||
def _mask(value: int, high_bit: int, low_bit: int) -> int:
|
||||
"""Returns the bits of a value between highbit and lowbit inclusive"""
|
||||
"""Returns the bits of a value between highbit and lowbit inclusive."""
|
||||
high_mask = (2 ** (high_bit + 1)) - 1
|
||||
low_mask = (2 ** low_bit) - 1
|
||||
mask = (high_mask ^ low_mask)
|
||||
@@ -189,7 +192,8 @@ class RegistryHive(linear.LinearlyMappedLayer):
|
||||
]
|
||||
|
||||
def _translate(self, offset: int) -> int:
|
||||
"""Translates a single cell index to a cell memory offset and the suboffset within it"""
|
||||
"""Translates a single cell index to a cell memory offset and the
|
||||
suboffset within it."""
|
||||
|
||||
# Ignore the volatile bit when determining maxaddr validity
|
||||
volatile = self._mask(offset, 31, 31) >> 31
|
||||
@@ -219,11 +223,11 @@ class RegistryHive(linear.LinearlyMappedLayer):
|
||||
|
||||
@property
|
||||
def dependencies(self) -> List[str]:
|
||||
"""Returns a list of layer names that this layer translates onto"""
|
||||
"""Returns a list of layer names that this layer translates onto."""
|
||||
return [self.config['base_layer']]
|
||||
|
||||
def is_valid(self, offset: int, length: int = 1) -> bool:
|
||||
"""Returns a boolean based on whether the offset is valid or not"""
|
||||
"""Returns a boolean based on whether the offset is valid or not."""
|
||||
# TODO: Fix me
|
||||
return True
|
||||
|
||||
|
||||
@@ -37,12 +37,13 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResourceAccessor(object):
|
||||
"""Object for openning URLs as files (downloading locally first if necessary)"""
|
||||
"""Object for openning URLs as files (downloading locally first if
|
||||
necessary)"""
|
||||
|
||||
def __init__(self,
|
||||
progress_callback: Optional[constants.ProgressCallback] = None,
|
||||
context: Optional[ssl.SSLContext] = None) -> None:
|
||||
"""Creates a resource accessor
|
||||
"""Creates a resource accessor.
|
||||
|
||||
Note: context is an SSL context, not a volatility context
|
||||
"""
|
||||
@@ -54,7 +55,7 @@ class ResourceAccessor(object):
|
||||
"Available URL handlers: {}".format(", ".join([x.__name__ for x in self._handlers])))
|
||||
|
||||
def open(self, url, mode = "rb"):
|
||||
"""Returns a file-like object for a particular URL opened in mode"""
|
||||
"""Returns a file-like object for a particular URL opened in mode."""
|
||||
urllib.request.install_opener(urllib.request.build_opener(*self._handlers))
|
||||
|
||||
with contextlib.closing(urllib.request.urlopen(url, context = self._context)) as fp:
|
||||
@@ -148,7 +149,7 @@ class ResourceAccessor(object):
|
||||
|
||||
|
||||
class JarHandler(urllib.request.BaseHandler):
|
||||
"""Handles the jar scheme for URIs
|
||||
"""Handles the jar scheme for URIs.
|
||||
|
||||
Reference used for the schema syntax:
|
||||
http://docs.netkernel.org/book/view/book:mod:reference/doc:layer1:schemes:jar
|
||||
@@ -159,7 +160,7 @@ class JarHandler(urllib.request.BaseHandler):
|
||||
|
||||
@staticmethod
|
||||
def default_open(req):
|
||||
"""Handles the request if it's the jar scheme"""
|
||||
"""Handles the request if it's the jar scheme."""
|
||||
if req.type == 'jar':
|
||||
subscheme, remainder = req.full_url.split(":")[1], ":".join(req.full_url.split(":")[2:])
|
||||
if subscheme != 'file':
|
||||
|
||||
@@ -17,8 +17,8 @@ class BytesScanner(layers.ScannerInterface):
|
||||
self.needle = needle
|
||||
|
||||
def __call__(self, data: bytes, data_offset: int) -> Generator[int, None, None]:
|
||||
"""Runs through the data looking for the needle, and yields all offsets where the needle is found
|
||||
"""
|
||||
"""Runs through the data looking for the needle, and yields all offsets
|
||||
where the needle is found."""
|
||||
find_pos = data.find(self.needle)
|
||||
while find_pos >= 0:
|
||||
if find_pos < self.chunk_size:
|
||||
@@ -34,8 +34,8 @@ class RegExScanner(layers.ScannerInterface):
|
||||
self.regex = re.compile(pattern, flags)
|
||||
|
||||
def __call__(self, data: bytes, data_offset: int) -> Generator[int, None, None]:
|
||||
"""Runs through the data looking for the needle, and yields all offsets where the needle is found
|
||||
"""
|
||||
"""Runs through the data looking for the needle, and yields all offsets
|
||||
where the needle is found."""
|
||||
find_pos = self.regex.finditer(data)
|
||||
for match in find_pos:
|
||||
offset = match.start()
|
||||
@@ -54,7 +54,7 @@ class MultiStringScanner(layers.ScannerInterface):
|
||||
self._patterns.preprocess()
|
||||
|
||||
def __call__(self, data: bytes, data_offset: int) -> Generator[Tuple[int, Union[str, bytes]], None, None]:
|
||||
"""Runs through the data looking for the needles"""
|
||||
"""Runs through the data looking for the needles."""
|
||||
for offset, pattern in self._patterns.search(data):
|
||||
if offset < self.chunk_size:
|
||||
yield offset + data_offset, pattern
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import Generator, List, Tuple, Union
|
||||
|
||||
|
||||
class MultiRegexp(object):
|
||||
"""Algorithm for multi-string matching"""
|
||||
"""Algorithm for multi-string matching."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._pattern_strings = [] # type: List[bytes]
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Generator, List, Optional, Set, Tuple, Union
|
||||
|
||||
|
||||
class WuManber(object):
|
||||
"""Algorithm for multi-string matching"""
|
||||
"""Algorithm for multi-string matching."""
|
||||
|
||||
def __init__(self, block_size: int = 3) -> None:
|
||||
# Set a suitably large minimum
|
||||
@@ -29,7 +29,7 @@ class WuManber(object):
|
||||
self._patterns.append(pattern)
|
||||
|
||||
def preprocess(self) -> None:
|
||||
"""Preprocesses the patterns by populating the three arrays"""
|
||||
"""Preprocesses the patterns by populating the three arrays."""
|
||||
|
||||
if not self._patterns:
|
||||
raise ValueError("No Linux symbols/banner patterns available")
|
||||
@@ -50,15 +50,17 @@ class WuManber(object):
|
||||
self._hashes[hashval].add(pattern)
|
||||
|
||||
def _hash_function(self, value_bytes: bytes) -> int:
|
||||
"""Hash function to bucket _block_size number of bytes into sets
|
||||
"""Hash function to bucket _block_size number of bytes into sets.
|
||||
|
||||
If this hash_function changes, the maximum number of responses must be set in self._maximum_hash
|
||||
If this hash_function changes, the maximum number of responses
|
||||
must be set in self._maximum_hash
|
||||
"""
|
||||
return (value_bytes[0] << 5) + (value_bytes[1] << 3) + value_bytes[2]
|
||||
|
||||
def search(self, haystack: bytes) \
|
||||
-> Generator[Tuple[int, Union[str, bytes]], None, None]:
|
||||
"""Search through a large body of data for patterns previously added with add_pattern"""
|
||||
"""Search through a large body of data for patterns previously added
|
||||
with add_pattern."""
|
||||
if not isinstance(haystack, bytes):
|
||||
raise TypeError("Search haystack must be a byte string")
|
||||
if self._shift is None:
|
||||
|
||||
@@ -12,9 +12,10 @@ from volatility.framework.layers import linear
|
||||
|
||||
|
||||
class SegmentedLayer(linear.LinearlyMappedLayer, metaclass = ABCMeta):
|
||||
"""A class to handle a single run-based layer-to-layer mapping
|
||||
"""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
|
||||
In the documentation "mapped address" or "mapped offset" refers to
|
||||
an offset once it has been mapped to the underlying layer
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
@@ -33,13 +34,15 @@ class SegmentedLayer(linear.LinearlyMappedLayer, metaclass = ABCMeta):
|
||||
|
||||
@abstractmethod
|
||||
def _load_segments(self) -> None:
|
||||
"""Populates the _segments variable
|
||||
"""Populates the _segments variable.
|
||||
|
||||
Segments must be (address, mapped address, length) and must be sorted by address when this method exits
|
||||
Segments must be (address, mapped address, length) and must be
|
||||
sorted by address when this method exits
|
||||
"""
|
||||
|
||||
def is_valid(self, offset: int, length: int = 1) -> bool:
|
||||
"""Returns whether the address offset can be translated to a valid address"""
|
||||
"""Returns whether the address offset can be translated to a valid
|
||||
address."""
|
||||
try:
|
||||
base_layer = self._context.layers[self._base_layer]
|
||||
return all(
|
||||
@@ -48,9 +51,9 @@ class SegmentedLayer(linear.LinearlyMappedLayer, metaclass = ABCMeta):
|
||||
return False
|
||||
|
||||
def _find_segment(self, offset: int, next: bool = False) -> Tuple[int, int, int]:
|
||||
"""Finds the segment containing a given offset
|
||||
"""Finds the segment containing a given offset.
|
||||
|
||||
Returns the segment tuple (offset, mapped_offset, length)
|
||||
Returns the segment tuple (offset, mapped_offset, length)
|
||||
"""
|
||||
|
||||
if not self._segments:
|
||||
@@ -68,7 +71,8 @@ class SegmentedLayer(linear.LinearlyMappedLayer, metaclass = ABCMeta):
|
||||
raise exceptions.InvalidAddressException(self.name, offset, "Invalid address at {:0x}".format(offset))
|
||||
|
||||
def mapping(self, offset: int, length: int, ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, str]]:
|
||||
"""Returns a sorted iterable of (offset, mapped_offset, length, layer) mappings"""
|
||||
"""Returns a sorted iterable of (offset, mapped_offset, length, layer)
|
||||
mappings."""
|
||||
done = False
|
||||
current_offset = offset
|
||||
while not done:
|
||||
@@ -123,7 +127,8 @@ class SegmentedLayer(linear.LinearlyMappedLayer, metaclass = ABCMeta):
|
||||
|
||||
@property
|
||||
def dependencies(self) -> List[str]:
|
||||
"""Returns a list of the lower layers that this layer is dependent upon"""
|
||||
"""Returns a list of the lower layers that this layer is dependent
|
||||
upon."""
|
||||
return [self._base_layer]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -31,11 +31,11 @@ class VmwareLayer(segmented.SegmentedLayer):
|
||||
super().__init__(context, config_path = config_path, name = name, metadata = metadata)
|
||||
|
||||
def _load_segments(self) -> None:
|
||||
"""Loads up the segments from the meta_layer"""
|
||||
"""Loads up the segments from the meta_layer."""
|
||||
self._read_header()
|
||||
|
||||
def _read_header(self) -> None:
|
||||
"""Checks the vmware header to make sure it's valid"""
|
||||
"""Checks the vmware header to make sure it's valid."""
|
||||
if "vmware" not in self._context.symbol_space:
|
||||
self._context.symbol_space.append(native.NativeTable("vmware", native.std_ctypes))
|
||||
|
||||
@@ -100,7 +100,8 @@ class VmwareLayer(segmented.SegmentedLayer):
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
"""This vmware translation layer always requires a separate metadata layer"""
|
||||
"""This vmware translation layer always requires a separate metadata
|
||||
layer."""
|
||||
return [
|
||||
requirements.TranslationLayerRequirement(name = 'base_layer', optional = False),
|
||||
requirements.TranslationLayerRequirement(name = 'meta_layer', optional = False)
|
||||
@@ -114,7 +115,7 @@ class VmwareStacker(interfaces.automagic.StackerLayerInterface):
|
||||
context: interfaces.context.ContextInterface,
|
||||
layer_name: str,
|
||||
progress_callback: constants.ProgressCallback = None) -> Optional[interfaces.layers.DataLayerInterface]:
|
||||
"""Attempt to stack this based on the starting information"""
|
||||
"""Attempt to stack this based on the starting information."""
|
||||
memlayer = context.layers[layer_name]
|
||||
if not isinstance(memlayer, physical.FileLayer):
|
||||
return None
|
||||
|
||||
@@ -19,7 +19,7 @@ DataFormatInfo = collections.namedtuple('DataFormatInfo', ['length', 'byteorder'
|
||||
|
||||
def convert_data_to_value(data: bytes, struct_type: Type[TUnion[int, float, bytes, str, bool]],
|
||||
data_format: DataFormatInfo) -> TUnion[int, float, bytes, str, bool]:
|
||||
"""Converts a series of bytes to a particular type of value"""
|
||||
"""Converts a series of bytes to a particular type of value."""
|
||||
if struct_type == int:
|
||||
return int.from_bytes(data, byteorder = data_format.byteorder, signed = data_format.signed)
|
||||
if struct_type == bool:
|
||||
@@ -40,7 +40,7 @@ def convert_data_to_value(data: bytes, struct_type: Type[TUnion[int, float, byte
|
||||
def convert_value_to_data(value: TUnion[int, float, bytes, str, bool],
|
||||
struct_type: Type[TUnion[int, float, bytes, str, bool]],
|
||||
data_format: DataFormatInfo) -> bytes:
|
||||
"""Converts a particular value to a series of bytes"""
|
||||
"""Converts a particular value to a series of bytes."""
|
||||
if not isinstance(value, struct_type):
|
||||
raise TypeError("Written value is not of the correct type for {}".format(struct_type.__class__.__name__))
|
||||
|
||||
@@ -64,17 +64,17 @@ def convert_value_to_data(value: TUnion[int, float, bytes, str, bool],
|
||||
|
||||
|
||||
class Void(interfaces.objects.ObjectInterface):
|
||||
"""Returns an object to represent void/unknown types"""
|
||||
"""Returns an object to represent void/unknown types."""
|
||||
|
||||
class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy):
|
||||
|
||||
@classmethod
|
||||
def size(cls, template: interfaces.objects.Template) -> int:
|
||||
"""Dummy size for Void objects"""
|
||||
"""Dummy size for Void objects."""
|
||||
raise TypeError("Void types are incomplete, cannot contain data and do not have a size")
|
||||
|
||||
def write(self, value: Any) -> None:
|
||||
"""Dummy method that does nothing for Void objects"""
|
||||
"""Dummy method that does nothing for Void objects."""
|
||||
raise TypeError("Cannot write data to a void, recast as another object")
|
||||
|
||||
|
||||
@@ -83,7 +83,8 @@ class Function(interfaces.objects.ObjectInterface):
|
||||
|
||||
|
||||
class PrimitiveObject(interfaces.objects.ObjectInterface):
|
||||
"""PrimitiveObject is an interface for any objects that should simulate a Python primitive"""
|
||||
"""PrimitiveObject is an interface for any objects that should simulate a
|
||||
Python primitive."""
|
||||
_struct_type = int # type: ClassVar[Type]
|
||||
|
||||
def __init__(self, context: interfaces.context.ContextInterface, type_name: str,
|
||||
@@ -98,13 +99,15 @@ class PrimitiveObject(interfaces.objects.ObjectInterface):
|
||||
data_format: DataFormatInfo,
|
||||
new_value: TUnion[int, float, bool, bytes, str] = None,
|
||||
**kwargs) -> 'PrimitiveObject':
|
||||
"""Creates the appropriate class and returns it so that the native type is inherited
|
||||
"""Creates the appropriate class and returns it so that the native type
|
||||
is inherited.
|
||||
|
||||
The only reason the **kwargs is added, is so that the inherriting types can override __init__
|
||||
without needing to override __new__
|
||||
|
||||
We also sneak in new_value, so that we don't have to do expensive (read: impossible) context reads
|
||||
when unpickling."""
|
||||
when unpickling.
|
||||
"""
|
||||
if new_value is None:
|
||||
value = cls._unmarshall(context, data_format, object_info)
|
||||
else:
|
||||
@@ -116,7 +119,8 @@ class PrimitiveObject(interfaces.objects.ObjectInterface):
|
||||
return result
|
||||
|
||||
def __getnewargs_ex__(self):
|
||||
"""Make sure that when pickling, all appropiate parameters for new are provided"""
|
||||
"""Make sure that when pickling, all appropiate parameters for new are
|
||||
provided."""
|
||||
kwargs = {}
|
||||
for k, v in self._vol.maps[-1].items():
|
||||
if k not in ["context", "data_format", "object_info", "type_name"]:
|
||||
@@ -134,36 +138,37 @@ class PrimitiveObject(interfaces.objects.ObjectInterface):
|
||||
|
||||
@classmethod
|
||||
def size(cls, template: interfaces.objects.Template) -> int:
|
||||
"""Returns the size of the templated object"""
|
||||
"""Returns the size of the templated object."""
|
||||
return template.vol.data_format.length
|
||||
|
||||
def write(self, value: TUnion[int, float, bool, bytes, str]) -> None:
|
||||
"""Writes the object into the layer of the context at the current offset"""
|
||||
"""Writes the object into the layer of the context at the current
|
||||
offset."""
|
||||
data = convert_value_to_data(value, self._struct_type, self._data_format)
|
||||
return self._context.layers.write(self.vol.layer_name, self.vol.offset, data)
|
||||
|
||||
|
||||
class Boolean(PrimitiveObject, int):
|
||||
"""Primitive Object that handles boolean types"""
|
||||
"""Primitive Object that handles boolean types."""
|
||||
_struct_type = bool # type: ClassVar[Type]
|
||||
|
||||
|
||||
class Integer(PrimitiveObject, int):
|
||||
"""Primitive Object that handles standard numeric types"""
|
||||
"""Primitive Object that handles standard numeric types."""
|
||||
|
||||
|
||||
class Float(PrimitiveObject, float):
|
||||
"""Primitive Object that handles double or floating point numbers"""
|
||||
"""Primitive Object that handles double or floating point numbers."""
|
||||
_struct_type = float # type: ClassVar[Type]
|
||||
|
||||
|
||||
class Char(PrimitiveObject, int):
|
||||
"""Primitive Object that handles characters"""
|
||||
"""Primitive Object that handles characters."""
|
||||
_struct_type = int # type: ClassVar[Type]
|
||||
|
||||
|
||||
class Bytes(PrimitiveObject, bytes):
|
||||
"""Primitive Object that handles specific series of bytes"""
|
||||
"""Primitive Object that handles specific series of bytes."""
|
||||
_struct_type = bytes # type: ClassVar[Type]
|
||||
|
||||
def __init__(self,
|
||||
@@ -184,22 +189,24 @@ class Bytes(PrimitiveObject, bytes):
|
||||
object_info: interfaces.objects.ObjectInformation,
|
||||
length: int = 1,
|
||||
**kwargs) -> 'Bytes':
|
||||
"""Creates the appropriate class and returns it so that the native type is inherritted
|
||||
"""Creates the appropriate class and returns it so that the native type
|
||||
is inherritted.
|
||||
|
||||
The only reason the **kwargs is added, is so that the inherriting types can override __init__
|
||||
without needing to override __new__"""
|
||||
The only reason the **kwargs is added, is so that the
|
||||
inherriting types can override __init__ without needing to
|
||||
override __new__
|
||||
"""
|
||||
return cls._struct_type.__new__(
|
||||
cls,
|
||||
cls._unmarshall(context, data_format = DataFormatInfo(length, "big", False), object_info = object_info))
|
||||
|
||||
|
||||
class String(PrimitiveObject, str):
|
||||
"""Primitive Object that handles string values
|
||||
"""Primitive Object that handles string values.
|
||||
|
||||
Args:
|
||||
max_length: specifies the maximum possible length that the string could hold within memory
|
||||
(for multibyte characters, this will not be the maximum length of the string)
|
||||
|
||||
"""
|
||||
_struct_type = str # type: ClassVar[Type]
|
||||
|
||||
@@ -227,10 +234,13 @@ class String(PrimitiveObject, str):
|
||||
encoding: str = "utf-8",
|
||||
errors: str = "strict",
|
||||
**kwargs) -> 'String':
|
||||
"""Creates the appropriate class and returns it so that the native type is inherited
|
||||
"""Creates the appropriate class and returns it so that the native type
|
||||
is inherited.
|
||||
|
||||
The only reason the **kwargs is added, is so that the inherriting types can override __init__
|
||||
without needing to override __new__"""
|
||||
The only reason the **kwargs is added, is so that the
|
||||
inherriting types can override __init__ without needing to
|
||||
override __new__
|
||||
"""
|
||||
params = {}
|
||||
if encoding:
|
||||
params['encoding'] = encoding
|
||||
@@ -247,7 +257,7 @@ class String(PrimitiveObject, str):
|
||||
|
||||
|
||||
class Pointer(Integer):
|
||||
"""Pointer which points to another object"""
|
||||
"""Pointer which points to another object."""
|
||||
|
||||
def __init__(self,
|
||||
context: interfaces.context.ContextInterface,
|
||||
@@ -261,10 +271,12 @@ class Pointer(Integer):
|
||||
@classmethod
|
||||
def _unmarshall(cls, context: interfaces.context.ContextInterface, data_format: DataFormatInfo,
|
||||
object_info: ObjectInformation) -> Any:
|
||||
"""Ensure that pointer values always fall within the domain of the layer they're constructed on
|
||||
"""Ensure that pointer values always fall within the domain of the
|
||||
layer they're constructed on.
|
||||
|
||||
If there's a need for all the data within the address, the pointer should be recast. The "pointer"
|
||||
must always live within the space (even if the data provided is invalid).
|
||||
If there's a need for all the data within the address, the
|
||||
pointer should be recast. The "pointer" must always live within
|
||||
the space (even if the data provided is invalid).
|
||||
"""
|
||||
length, endian, signed = data_format
|
||||
if signed:
|
||||
@@ -275,10 +287,12 @@ class Pointer(Integer):
|
||||
return value & mask
|
||||
|
||||
def dereference(self, layer_name: Optional[str] = None) -> interfaces.objects.ObjectInterface:
|
||||
"""Dereferences the pointer
|
||||
"""Dereferences the pointer.
|
||||
|
||||
Layer_name is identifies the appropriate layer within the context that the pointer points to.
|
||||
If layer_name is None, it defaults to the same layer that the pointer is currently instantiated in.
|
||||
Layer_name is identifies the appropriate layer within the
|
||||
context that the pointer points to. If layer_name is None, it
|
||||
defaults to the same layer that the pointer is currently
|
||||
instantiated in.
|
||||
"""
|
||||
layer_name = layer_name or self.vol.native_layer_name
|
||||
mask = self._context.layers[layer_name].address_mask
|
||||
@@ -288,16 +302,18 @@ class Pointer(Integer):
|
||||
object_info = interfaces.objects.ObjectInformation(layer_name = layer_name, offset = offset, parent = self))
|
||||
|
||||
def is_readable(self, layer_name: Optional[str] = None) -> bool:
|
||||
"""Determines whether the address of this pointer can be read from memory"""
|
||||
"""Determines whether the address of this pointer can be read from
|
||||
memory."""
|
||||
layer_name = layer_name or self.vol.layer_name
|
||||
return self._context.layers[layer_name].is_valid(self)
|
||||
|
||||
def __getattr__(self, attr: str) -> Any:
|
||||
"""Convenience function to access unknown attributes by getting them from the subtype object"""
|
||||
"""Convenience function to access unknown attributes by getting them
|
||||
from the subtype object."""
|
||||
return getattr(self.dereference(), attr)
|
||||
|
||||
def has_member(self, member_name: str) -> bool:
|
||||
"""Returns whether the dereferenced type has this member"""
|
||||
"""Returns whether the dereferenced type has this member."""
|
||||
return self._vol['subtype'].has_member(member_name)
|
||||
|
||||
class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy):
|
||||
@@ -308,7 +324,7 @@ class Pointer(Integer):
|
||||
|
||||
@classmethod
|
||||
def children(cls, template: interfaces.objects.Template) -> List[interfaces.objects.Template]:
|
||||
"""Returns the children of the template"""
|
||||
"""Returns the children of the template."""
|
||||
if 'subtype' in template.vol:
|
||||
return [template.vol.subtype]
|
||||
return []
|
||||
@@ -316,7 +332,7 @@ class Pointer(Integer):
|
||||
@classmethod
|
||||
def replace_child(cls, template: interfaces.objects.Template, old_child: interfaces.objects.Template,
|
||||
new_child: interfaces.objects.Template) -> None:
|
||||
"""Substitutes the old_child for the new_child"""
|
||||
"""Substitutes the old_child for the new_child."""
|
||||
if 'subtype' in template.vol:
|
||||
if template.vol.subtype == old_child:
|
||||
template.update_vol(subtype = new_child)
|
||||
@@ -327,7 +343,8 @@ class Pointer(Integer):
|
||||
|
||||
|
||||
class BitField(interfaces.objects.ObjectInterface, int):
|
||||
"""Object containing a field which is made up of bits rather than whole bytes"""
|
||||
"""Object containing a field which is made up of bits rather than whole
|
||||
bytes."""
|
||||
|
||||
def __init__(self,
|
||||
context: interfaces.context.ContextInterface,
|
||||
@@ -363,7 +380,7 @@ class BitField(interfaces.objects.ObjectInterface, int):
|
||||
|
||||
@classmethod
|
||||
def children(cls, template: interfaces.objects.Template) -> List[interfaces.objects.Template]:
|
||||
"""Returns the children of the template"""
|
||||
"""Returns the children of the template."""
|
||||
if 'base_type' in template.vol:
|
||||
return [template.vol.base_type]
|
||||
return []
|
||||
@@ -371,14 +388,14 @@ class BitField(interfaces.objects.ObjectInterface, int):
|
||||
@classmethod
|
||||
def replace_child(cls, template: interfaces.objects.Template, old_child: interfaces.objects.Template,
|
||||
new_child: interfaces.objects.Template) -> None:
|
||||
"""Substitutes the old_child for the new_child"""
|
||||
"""Substitutes the old_child for the new_child."""
|
||||
if 'base_type' in template.vol:
|
||||
if template.vol.base_type == old_child:
|
||||
template.update_vol(base_type = new_child)
|
||||
|
||||
|
||||
class Enumeration(interfaces.objects.ObjectInterface, int):
|
||||
"""Returns an object made up of choices"""
|
||||
"""Returns an object made up of choices."""
|
||||
|
||||
def __new__(cls, context: interfaces.context.ContextInterface, type_name: str,
|
||||
object_info: interfaces.objects.ObjectInformation, base_type: interfaces.objects.Template,
|
||||
@@ -397,7 +414,7 @@ class Enumeration(interfaces.objects.ObjectInterface, int):
|
||||
|
||||
@classmethod
|
||||
def _generate_inverse_choices(cls, choices: Dict[str, int]) -> Dict[int, str]:
|
||||
"""Generates the inverse choices for the object"""
|
||||
"""Generates the inverse choices for the object."""
|
||||
inverse_choices = {} # type: Dict[int, str]
|
||||
for k, v in choices.items():
|
||||
if v in inverse_choices:
|
||||
@@ -409,7 +426,7 @@ class Enumeration(interfaces.objects.ObjectInterface, int):
|
||||
return inverse_choices
|
||||
|
||||
def lookup(self, value: int = None) -> str:
|
||||
"""Looks up an individual value and returns the associated name"""
|
||||
"""Looks up an individual value and returns the associated name."""
|
||||
if value is None:
|
||||
return self.lookup(self)
|
||||
if value in self._inverse_choices:
|
||||
@@ -418,7 +435,7 @@ class Enumeration(interfaces.objects.ObjectInterface, int):
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
"""Returns the chosen name for the value this object contains"""
|
||||
"""Returns the chosen name for the value this object contains."""
|
||||
return self.lookup(self)
|
||||
|
||||
@property
|
||||
@@ -426,7 +443,7 @@ class Enumeration(interfaces.objects.ObjectInterface, int):
|
||||
return self._vol['choices']
|
||||
|
||||
def __getattr__(self, attr: str) -> str:
|
||||
"""Returns the value for a specific name"""
|
||||
"""Returns the value for a specific name."""
|
||||
if attr in self._vol['choices']:
|
||||
return self._vol['choices'][attr]
|
||||
raise AttributeError("Unknown attribute {} for Enumeration {}".format(attr, self._vol['type_name']))
|
||||
@@ -439,7 +456,7 @@ class Enumeration(interfaces.objects.ObjectInterface, int):
|
||||
|
||||
@classmethod
|
||||
def lookup(cls, template: interfaces.objects.Template, value: int) -> str:
|
||||
"""Looks up an individual value and returns the associated name"""
|
||||
"""Looks up an individual value and returns the associated name."""
|
||||
_inverse_choices = Enumeration._generate_inverse_choices(template.vol['choices'])
|
||||
if value in _inverse_choices:
|
||||
return _inverse_choices[value]
|
||||
@@ -451,7 +468,7 @@ class Enumeration(interfaces.objects.ObjectInterface, int):
|
||||
|
||||
@classmethod
|
||||
def children(cls, template: interfaces.objects.Template) -> List[interfaces.objects.Template]:
|
||||
"""Returns the children of the template"""
|
||||
"""Returns the children of the template."""
|
||||
if 'base_type' in template.vol:
|
||||
return [template.vol.base_type]
|
||||
return []
|
||||
@@ -459,14 +476,14 @@ class Enumeration(interfaces.objects.ObjectInterface, int):
|
||||
@classmethod
|
||||
def replace_child(cls, template: interfaces.objects.Template, old_child: interfaces.objects.Template,
|
||||
new_child: interfaces.objects.Template) -> None:
|
||||
"""Substitutes the old_child for the new_child"""
|
||||
"""Substitutes the old_child for the new_child."""
|
||||
if 'base_type' in template.vol:
|
||||
if template.vol.base_type == old_child:
|
||||
template.update_vol(base_type = new_child)
|
||||
|
||||
|
||||
class Array(interfaces.objects.ObjectInterface, abc.Sequence):
|
||||
"""Object which can contain a fixed number of an object type"""
|
||||
"""Object which can contain a fixed number of an object type."""
|
||||
|
||||
def __init__(self,
|
||||
context: interfaces.context.ContextInterface,
|
||||
@@ -482,26 +499,27 @@ class Array(interfaces.objects.ObjectInterface, abc.Sequence):
|
||||
# Changing the name would be confusing (since we use count of an array everywhere else), so this is more important
|
||||
@property
|
||||
def count(self) -> int:
|
||||
"""Returns the count dynamically"""
|
||||
"""Returns the count dynamically."""
|
||||
return self.vol.count
|
||||
|
||||
@count.setter
|
||||
def count(self, value: int) -> None:
|
||||
"""Sets the count to a specific value"""
|
||||
"""Sets the count to a specific value."""
|
||||
self._vol['count'] = value
|
||||
|
||||
class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy):
|
||||
|
||||
@classmethod
|
||||
def size(cls, template: interfaces.objects.Template) -> int:
|
||||
"""Returns the size of the array, based on the count and the subtype"""
|
||||
"""Returns the size of the array, based on the count and the
|
||||
subtype."""
|
||||
if 'subtype' not in template.vol and 'count' not in template.vol:
|
||||
raise TypeError("Array ObjectTemplate must be provided a count and subtype")
|
||||
return template.vol.get('subtype', None).size * template.vol.get('count', 0)
|
||||
|
||||
@classmethod
|
||||
def children(cls, template: interfaces.objects.Template) -> List[interfaces.objects.Template]:
|
||||
"""Returns the children of the template"""
|
||||
"""Returns the children of the template."""
|
||||
if 'subtype' in template.vol:
|
||||
return [template.vol.subtype]
|
||||
return []
|
||||
@@ -509,14 +527,15 @@ class Array(interfaces.objects.ObjectInterface, abc.Sequence):
|
||||
@classmethod
|
||||
def replace_child(cls, template: interfaces.objects.Template, old_child: interfaces.objects.Template,
|
||||
new_child: interfaces.objects.Template) -> None:
|
||||
"""Substitutes the old_child for the new_child"""
|
||||
"""Substitutes the old_child for the new_child."""
|
||||
if 'subtype' in template.vol:
|
||||
if template.vol['subtype'] == old_child:
|
||||
template.update_vol(subtype = new_child)
|
||||
|
||||
@classmethod
|
||||
def relative_child_offset(cls, template: interfaces.objects.Template, child: str) -> int:
|
||||
"""Returns the relative offset from the head of the parent data to the child member"""
|
||||
"""Returns the relative offset from the head of the parent data to
|
||||
the child member."""
|
||||
if 'subtype' in template.vol and child == 'subtype':
|
||||
return 0
|
||||
raise IndexError("Member not present in array template: {}".format(child))
|
||||
@@ -530,7 +549,7 @@ class Array(interfaces.objects.ObjectInterface, abc.Sequence):
|
||||
...
|
||||
|
||||
def __getitem__(self, i):
|
||||
"""Returns the i-th item from the array"""
|
||||
"""Returns the i-th item from the array."""
|
||||
result = [] # type: List[interfaces.objects.Template]
|
||||
mask = self._context.layers[self.vol.layer_name].address_mask
|
||||
# We use the range function to deal with slices for us
|
||||
@@ -551,7 +570,7 @@ class Array(interfaces.objects.ObjectInterface, abc.Sequence):
|
||||
return result
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Returns the length of the array"""
|
||||
"""Returns the length of the array."""
|
||||
return self.vol.count
|
||||
|
||||
def write(self, value) -> None:
|
||||
@@ -559,9 +578,10 @@ class Array(interfaces.objects.ObjectInterface, abc.Sequence):
|
||||
|
||||
|
||||
class AggregateType(interfaces.objects.ObjectInterface):
|
||||
"""Object which can contain members that are other objects
|
||||
"""Object which can contain members that are other objects.
|
||||
|
||||
Keep the number of methods in this class low or very specific, since each one could overload a valid member.
|
||||
Keep the number of methods in this class low or very specific, since
|
||||
each one could overload a valid member.
|
||||
"""
|
||||
|
||||
def __init__(self, context: interfaces.context.ContextInterface, type_name: str,
|
||||
@@ -573,27 +593,29 @@ class AggregateType(interfaces.objects.ObjectInterface):
|
||||
self._concrete_members = {} # type: Dict[str, Dict]
|
||||
|
||||
def has_member(self, member_name: str) -> bool:
|
||||
"""Returns whether the object would contain a member called member_name"""
|
||||
"""Returns whether the object would contain a member called
|
||||
member_name."""
|
||||
return member_name in self.vol.members
|
||||
|
||||
class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy):
|
||||
|
||||
@classmethod
|
||||
def size(cls, template: interfaces.objects.Template) -> int:
|
||||
"""Method to return the size of this type"""
|
||||
"""Method to return the size of this type."""
|
||||
if template.vol.get('size', None) is None:
|
||||
raise TypeError("ObjectTemplate not provided with a size")
|
||||
return template.vol.size
|
||||
|
||||
@classmethod
|
||||
def children(cls, template: interfaces.objects.Template) -> List[interfaces.objects.Template]:
|
||||
"""Method to list children of a template"""
|
||||
"""Method to list children of a template."""
|
||||
return [member for _, member in template.vol.members.values()]
|
||||
|
||||
@classmethod
|
||||
def replace_child(cls, template: interfaces.objects.Template, old_child: interfaces.objects.Template,
|
||||
new_child: interfaces.objects.Template) -> None:
|
||||
"""Replace a child elements within the arguments handed to the template"""
|
||||
"""Replace a child elements within the arguments handed to the
|
||||
template."""
|
||||
for member in template.vol.members.get('members', {}):
|
||||
relative_offset, member_template = template.vol.members[member]
|
||||
if member_template == old_child:
|
||||
@@ -608,7 +630,7 @@ class AggregateType(interfaces.objects.ObjectInterface):
|
||||
|
||||
@classmethod
|
||||
def relative_child_offset(cls, template: interfaces.objects.Template, child: str) -> int:
|
||||
"""Returns the relative offset of a child to its parent"""
|
||||
"""Returns the relative offset of a child to its parent."""
|
||||
retlist = template.vol.members.get(child, None)
|
||||
if retlist is None:
|
||||
raise IndexError("Member not present in template: {}".format(child))
|
||||
@@ -616,7 +638,8 @@ class AggregateType(interfaces.objects.ObjectInterface):
|
||||
|
||||
@classmethod
|
||||
def has_member(cls, template: interfaces.objects.Template, member_name: str) -> bool:
|
||||
"""Returns whether the object would contain a member called member_name"""
|
||||
"""Returns whether the object would contain a member called
|
||||
member_name."""
|
||||
return member_name in template.vol.members
|
||||
|
||||
@classmethod
|
||||
@@ -640,7 +663,7 @@ class AggregateType(interfaces.objects.ObjectInterface):
|
||||
return self.__getattr__(attr)
|
||||
|
||||
def __getattr__(self, attr: str) -> Any:
|
||||
"""Method for accessing members of the type"""
|
||||
"""Method for accessing members of the type."""
|
||||
if attr in self._concrete_members:
|
||||
return self._concrete_members[attr]
|
||||
elif attr in self.vol.members:
|
||||
@@ -664,7 +687,7 @@ class AggregateType(interfaces.objects.ObjectInterface):
|
||||
raise AttributeError("{} has no attribute: {}.{}".format(agg_name, self.vol.type_name, attr))
|
||||
|
||||
def __dir__(self) -> Iterable[str]:
|
||||
"""Returns a complete list of members when dir is called"""
|
||||
"""Returns a complete list of members when dir is called."""
|
||||
return list(super().__dir__()) + list(self.vol.members)
|
||||
|
||||
def write(self, value):
|
||||
|
||||
@@ -11,14 +11,15 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ObjectTemplate(interfaces.objects.Template):
|
||||
"""Factory class that produces objects that adhere to the Object interface on demand
|
||||
"""Factory class that produces objects that adhere to the Object interface
|
||||
on demand.
|
||||
|
||||
This is effectively a method of currying, but adds more structure to avoid abuse.
|
||||
It also allows inspection of information that should already be known:
|
||||
This is effectively a method of currying, but adds more structure to avoid abuse.
|
||||
It also allows inspection of information that should already be known:
|
||||
|
||||
* Type size
|
||||
* Members
|
||||
* etc
|
||||
* Type size
|
||||
* Members
|
||||
* etc
|
||||
"""
|
||||
|
||||
def __init__(self, object_class: Type[interfaces.objects.ObjectInterface], type_name: str, **arguments) -> None:
|
||||
@@ -31,35 +32,38 @@ class ObjectTemplate(interfaces.objects.Template):
|
||||
|
||||
@property
|
||||
def size(self) -> int:
|
||||
"""Returns the children of the templated object (see :class:`~volatility.framework.interfaces.objects.ObjectInterface.VolTemplateProxy`)"""
|
||||
"""Returns the children of the templated object (see :class:`~volatilit
|
||||
y.framework.interfaces.objects.ObjectInterface.VolTemplateProxy`)"""
|
||||
return self.vol.object_class.VolTemplateProxy.size(self)
|
||||
|
||||
@property
|
||||
def children(self) -> List[interfaces.objects.Template]:
|
||||
"""Returns the children of the templated object (see :class:`~volatility.framework.interfaces.objects.ObjectInterface.VolTemplateProxy`)
|
||||
"""
|
||||
"""Returns the children of the templated object (see :class:`~volatilit
|
||||
y.framework.interfaces.objects.ObjectInterface.VolTemplateProxy`)"""
|
||||
return self.vol.object_class.VolTemplateProxy.children(self)
|
||||
|
||||
def relative_child_offset(self, child: str) -> int:
|
||||
"""Returns the relative offset of a child of the templated object (see :class:`~volatility.framework.interfaces.objects.ObjectInterface.VolTemplateProxy`)
|
||||
"""
|
||||
"""Returns the relative offset of a child of the templated object (see
|
||||
:class:`~volatility.framework.interfaces.objects.ObjectInterface.VolTem
|
||||
plateProxy`)"""
|
||||
return self.vol.object_class.VolTemplateProxy.relative_child_offset(self, child)
|
||||
|
||||
def replace_child(self, old_child: interfaces.objects.Template, new_child: interfaces.objects.Template) -> None:
|
||||
"""Replaces `old_child` for `new_child` in the templated object's child list (see :class:`~volatility.framework.interfaces.objects.ObjectInterface.VolTemplateProxy`)
|
||||
"""
|
||||
"""Replaces `old_child` for `new_child` in the templated object's child
|
||||
list (see :class:`~volatility.framework.interfaces.objects.ObjectInterf
|
||||
ace.VolTemplateProxy`)"""
|
||||
return self.vol.object_class.VolTemplateProxy.replace_child(self, old_child, new_child)
|
||||
|
||||
def has_member(self, member_name: str) -> bool:
|
||||
"""Returns whether the object would contain a member called member_name
|
||||
"""
|
||||
"""Returns whether the object would contain a member called
|
||||
member_name."""
|
||||
return self.vol.object_class.VolTemplateProxy.has_member(self, member_name)
|
||||
|
||||
def __call__(self, context: interfaces.context.ContextInterface,
|
||||
object_info: interfaces.objects.ObjectInformation) -> interfaces.objects.ObjectInterface:
|
||||
"""Constructs the object
|
||||
"""Constructs the object.
|
||||
|
||||
Returns: an object adhereing to the :class:`~volatility.framework.interfaces.objects.ObjectInterface`
|
||||
Returns: an object adhereing to the :class:`~volatility.framework.interfaces.objects.ObjectInterface`
|
||||
"""
|
||||
arguments = {} # type: Dict[str, Any]
|
||||
for arg in self.vol:
|
||||
@@ -69,7 +73,7 @@ class ObjectTemplate(interfaces.objects.Template):
|
||||
|
||||
|
||||
class ReferenceTemplate(interfaces.objects.Template):
|
||||
"""Factory class that produces objects based on a delayed reference type
|
||||
"""Factory class that produces objects based on a delayed reference type.
|
||||
|
||||
Attempts to access any standard attributes of a resolved template will result in a
|
||||
:class:`~volatility.framework.exceptions.SymbolError`.
|
||||
@@ -80,9 +84,9 @@ class ReferenceTemplate(interfaces.objects.Template):
|
||||
return []
|
||||
|
||||
def _unresolved(self, *args, **kwargs) -> Any:
|
||||
"""Referenced symbols must be appropriately resolved before they can provide information such as size
|
||||
This is because the size request has no context within which to determine the actual symbol structure.
|
||||
"""
|
||||
"""Referenced symbols must be appropriately resolved before they can
|
||||
provide information such as size This is because the size request has
|
||||
no context within which to determine the actual symbol structure."""
|
||||
raise exceptions.SymbolError("Template contains no information about its structure: {}".format(
|
||||
self.vol.type_name))
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from volatility.framework import interfaces, objects, constants
|
||||
|
||||
def array_to_string(array: 'objects.Array', count: Optional[int] = None,
|
||||
errors: str = 'replace') -> interfaces.objects.ObjectInterface:
|
||||
"""Takes a volatility Array of characters and returns a string"""
|
||||
"""Takes a volatility Array of characters and returns a string."""
|
||||
# TODO: Consider checking the Array's target is a native char
|
||||
if count is None:
|
||||
count = array.vol.count
|
||||
@@ -20,7 +20,7 @@ def array_to_string(array: 'objects.Array', count: Optional[int] = None,
|
||||
|
||||
|
||||
def pointer_to_string(pointer: 'objects.Pointer', count: int, errors: str = 'replace'):
|
||||
"""Takes a volatility Pointer to characters and returns a string"""
|
||||
"""Takes a volatility Pointer to characters and returns a string."""
|
||||
if not isinstance(pointer, objects.Pointer):
|
||||
raise TypeError("pointer_to_string takes a Pointer")
|
||||
if count < 1:
|
||||
@@ -32,7 +32,7 @@ def pointer_to_string(pointer: 'objects.Pointer', count: int, errors: str = 'rep
|
||||
def array_of_pointers(array: interfaces.objects.ObjectInterface, count: int,
|
||||
subtype: Union[str, interfaces.objects.Template],
|
||||
context: interfaces.context.ContextInterface) -> interfaces.objects.ObjectInterface:
|
||||
"""Takes an object, and recasts it as an array of pointers to subtype"""
|
||||
"""Takes an object, and recasts it as an array of pointers to subtype."""
|
||||
symbol_table = array.vol.type_name.split(constants.BANG)[0]
|
||||
if isinstance(subtype, str) and context is not None:
|
||||
subtype = context.symbol_space.get_type(subtype)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# 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
|
||||
#
|
||||
"""All core generic plugins
|
||||
"""All core generic plugins.
|
||||
|
||||
These modules should only be imported from volatility.plugins NOT volatility.framework.plugins
|
||||
These modules should only be imported from volatility.plugins NOT
|
||||
volatility.framework.plugins
|
||||
"""
|
||||
|
||||
import logging
|
||||
@@ -19,7 +20,7 @@ def construct_plugin(context: interfaces.context.ContextInterface,
|
||||
plugin: Type[interfaces.plugins.PluginInterface], base_config_path: str,
|
||||
progress_callback: constants.ProgressCallback,
|
||||
file_consumer: interfaces.plugins.FileConsumerInterface) -> interfaces.plugins.PluginInterface:
|
||||
"""Constructs a plugin object based on the parameters
|
||||
"""Constructs a plugin object based on the parameters.
|
||||
|
||||
Clever magic figures out how to fulfill each requirement that might not be fulfilled
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConfigWriter(plugins.PluginInterface):
|
||||
"""Runs the automagics and both prints and outputs configuration in the output directory"""
|
||||
"""Runs the automagics and both prints and outputs configuration in the
|
||||
output directory."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
|
||||
@@ -14,7 +14,8 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LayerWriter(plugins.PluginInterface):
|
||||
"""Runs the automagics and lists out the generated layers if no layer name is specified, otherwise writes out the named layer"""
|
||||
"""Runs the automagics and lists out the generated layers if no layer name
|
||||
is specified, otherwise writes out the named layer."""
|
||||
|
||||
default_output_name = "output.raw"
|
||||
default_block_size = 0x500000
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""All core linux plugins
|
||||
"""All core linux plugins.
|
||||
|
||||
These modules should only be imported from volatility.plugins NOT volatility.framework.plugins
|
||||
These modules should only be imported from volatility.plugins NOT
|
||||
volatility.framework.plugins
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""A module containing a collection of plugins that produce data
|
||||
typically found in Linux's /proc file system.
|
||||
"""
|
||||
"""A module containing a collection of plugins that produce data typically
|
||||
found in Linux's /proc file system."""
|
||||
|
||||
import datetime
|
||||
import struct
|
||||
@@ -20,7 +19,7 @@ from volatility.plugins.linux import pslist
|
||||
|
||||
|
||||
class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
"""Recovers bash command history from memory"""
|
||||
"""Recovers bash command history from memory."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""A module containing a collection of plugins that produce data
|
||||
typically found in Linux's /proc file system.
|
||||
"""
|
||||
"""A module containing a collection of plugins that produce data typically
|
||||
found in Linux's /proc file system."""
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
@@ -18,7 +17,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Check_afinfo(plugins.PluginInterface):
|
||||
"""Verifies the operation function pointers of network protocols"""
|
||||
"""Verifies the operation function pointers of network protocols."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""A module containing a collection of plugins that produce data
|
||||
typically found in Linux's /proc file system.
|
||||
"""
|
||||
"""A module containing a collection of plugins that produce data typically
|
||||
found in Linux's /proc file system."""
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
@@ -25,7 +24,7 @@ except ImportError:
|
||||
|
||||
|
||||
class Check_syscall(plugins.PluginInterface):
|
||||
"""Check system call table for hooks"""
|
||||
"""Check system call table for hooks."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -36,9 +35,7 @@ class Check_syscall(plugins.PluginInterface):
|
||||
]
|
||||
|
||||
def _get_table_size_next_symbol(self, table_addr, ptr_sz, vmlinux):
|
||||
"""
|
||||
Returns the size of the table based on the next symbol
|
||||
"""
|
||||
"""Returns the size of the table based on the next symbol."""
|
||||
ret = 0
|
||||
|
||||
sym_table = self.context.symbol_space[vmlinux.name]
|
||||
@@ -58,10 +55,9 @@ class Check_syscall(plugins.PluginInterface):
|
||||
return ret
|
||||
|
||||
def _get_table_size_meta(self, vmlinux):
|
||||
"""
|
||||
returns the number of symbols that start with __syscall_meta__
|
||||
this is a fast way to determine the number of system calls, but not the most accurate
|
||||
"""
|
||||
"""returns the number of symbols that start with __syscall_meta__ this
|
||||
is a fast way to determine the number of system calls, but not the most
|
||||
accurate."""
|
||||
|
||||
return len(
|
||||
[sym for sym in self.context.symbol_space[vmlinux.name].symbols if sym.startswith("__syscall_meta__")])
|
||||
@@ -77,11 +73,9 @@ class Check_syscall(plugins.PluginInterface):
|
||||
return table_size
|
||||
|
||||
def _get_table_info_disassembly(self, ptr_sz, vmlinux):
|
||||
"""
|
||||
Find the size of the system call table by disassembling functions
|
||||
that immediately reference it in their first isntruction
|
||||
This is in the form 'cmp reg,NR_syscalls'
|
||||
"""
|
||||
"""Find the size of the system call table by disassembling functions
|
||||
that immediately reference it in their first isntruction This is in the
|
||||
form 'cmp reg,NR_syscalls'."""
|
||||
table_size = 0
|
||||
|
||||
if not has_capstone:
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""A module containing a collection of plugins that produce data
|
||||
typically found in Linux's /proc file system.
|
||||
"""
|
||||
"""A module containing a collection of plugins that produce data typically
|
||||
found in Linux's /proc file system."""
|
||||
|
||||
from typing import List
|
||||
|
||||
@@ -16,7 +15,7 @@ from volatility.plugins.linux import pslist
|
||||
|
||||
|
||||
class Elfs(plugins.PluginInterface):
|
||||
"""Lists all memory mapped ELF files for all processes"""
|
||||
"""Lists all memory mapped ELF files for all processes."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
# 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
|
||||
#
|
||||
"""A module containing a collection of plugins that produce data
|
||||
typically found in Linux's /proc file system.
|
||||
"""
|
||||
"""A module containing a collection of plugins that produce data typically
|
||||
found in Linux's /proc file system."""
|
||||
|
||||
from typing import List
|
||||
from typing import List, Generator, Iterable
|
||||
|
||||
from volatility.framework import contexts
|
||||
from volatility.framework import renderers, constants, interfaces
|
||||
@@ -17,7 +16,7 @@ from volatility.framework.renderers import format_hints
|
||||
|
||||
|
||||
class Lsmod(plugins.PluginInterface):
|
||||
"""Lists loaded kernel modules"""
|
||||
"""Lists loaded kernel modules."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -28,8 +27,18 @@ class Lsmod(plugins.PluginInterface):
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def list_modules(cls, context: interfaces.context.ContextInterface, layer_name: str, vmlinux_symbols: str):
|
||||
"""Lists all the modules in the primary layer"""
|
||||
def list_modules(cls, context: interfaces.context.ContextInterface, layer_name: str,
|
||||
vmlinux_symbols: str) -> Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""Lists all the modules in the primary layer.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
vmlinux_symbols: The name of the table containing the kernel symbols
|
||||
|
||||
Yields:
|
||||
The modules present in the `layer_name` layer's modules list
|
||||
"""
|
||||
linux.LinuxUtilities.aslr_mask_symbol_table(context, vmlinux_symbols, layer_name)
|
||||
|
||||
vmlinux = contexts.Module(context, vmlinux_symbols, layer_name, 0)
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""A module containing a collection of plugins that produce data
|
||||
typically found in Linux's /proc file system.
|
||||
"""
|
||||
"""A module containing a collection of plugins that produce data typically
|
||||
found in Linux's /proc file system."""
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
@@ -18,7 +17,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Lsof(plugins.PluginInterface):
|
||||
"""Lists all memory maps for all processes"""
|
||||
"""Lists all memory maps for all processes."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
|
||||
@@ -15,7 +15,7 @@ from volatility.framework.renderers import format_hints
|
||||
|
||||
|
||||
class Malfind(interfaces_plugins.PluginInterface):
|
||||
"""Lists process memory ranges that potentially contain injected code"""
|
||||
"""Lists process memory ranges that potentially contain injected code."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -26,9 +26,8 @@ class Malfind(interfaces_plugins.PluginInterface):
|
||||
]
|
||||
|
||||
def _list_injections(self, task):
|
||||
"""Generate memory regions for a process that may contain
|
||||
injected code.
|
||||
"""
|
||||
"""Generate memory regions for a process that may contain injected
|
||||
code."""
|
||||
|
||||
proc_layer_name = task.add_process_layer()
|
||||
if not proc_layer_name:
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""A module containing a collection of plugins that produce data
|
||||
typically found in Linux's /proc file system.
|
||||
"""
|
||||
"""A module containing a collection of plugins that produce data typically
|
||||
found in Linux's /proc file system."""
|
||||
|
||||
from volatility.framework import renderers
|
||||
from volatility.framework.configuration import requirements
|
||||
@@ -14,7 +13,7 @@ from volatility.plugins.linux import pslist
|
||||
|
||||
|
||||
class Maps(plugins.PluginInterface):
|
||||
"""Lists all memory maps for all processes"""
|
||||
"""Lists all memory maps for all processes."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
|
||||
@@ -12,7 +12,7 @@ from volatility.framework.objects import utility
|
||||
|
||||
|
||||
class PsList(interfaces_plugins.PluginInterface):
|
||||
"""Lists the processes present in a particular linux memory image"""
|
||||
"""Lists the processes present in a particular linux memory image."""
|
||||
|
||||
_version = (1, 0, 0)
|
||||
|
||||
@@ -26,6 +26,14 @@ class PsList(interfaces_plugins.PluginInterface):
|
||||
|
||||
@classmethod
|
||||
def create_pid_filter(cls, pid_list: List[int] = None) -> Callable[[Any], bool]:
|
||||
"""Constructs a filter function for process IDs.
|
||||
|
||||
Args:
|
||||
pid_list: List of process IDs that are acceptable (or None if all are acceptable)
|
||||
|
||||
Returns:
|
||||
Function which, when provided a process object, returns True if the process is to be filtered out of the list
|
||||
"""
|
||||
# FIXME: mypy #4973 or #2608
|
||||
pid_list = pid_list or []
|
||||
filter_list = [x for x in pid_list if x is not None]
|
||||
@@ -58,7 +66,16 @@ class PsList(interfaces_plugins.PluginInterface):
|
||||
vmlinux_symbols: str,
|
||||
filter_func: Callable[[int], bool] = lambda _: False
|
||||
) -> Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""Lists all the tasks in the primary layer"""
|
||||
"""Lists all the tasks in the primary layer.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
vmlinux_symbols: The name of the table containing the kernel symbols
|
||||
|
||||
Yields:
|
||||
Process objects
|
||||
"""
|
||||
linux.LinuxUtilities.aslr_mask_symbol_table(context, vmlinux_symbols, layer_name)
|
||||
|
||||
vmlinux = contexts.Module(context, vmlinux_symbols, layer_name, 0)
|
||||
|
||||
@@ -7,7 +7,8 @@ from volatility.plugins.linux import pslist
|
||||
|
||||
|
||||
class PsTree(pslist.PsList):
|
||||
"""Plugin for listing processes in a tree based on their parent process ID """
|
||||
"""Plugin for listing processes in a tree based on their parent process
|
||||
ID."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -16,7 +17,7 @@ class PsTree(pslist.PsList):
|
||||
self._children = {}
|
||||
|
||||
def find_level(self, pid):
|
||||
"""Finds how deep the pid is in the processes list"""
|
||||
"""Finds how deep the pid is in the processes list."""
|
||||
seen = set([])
|
||||
seen.add(pid)
|
||||
level = 0
|
||||
@@ -32,7 +33,7 @@ class PsTree(pslist.PsList):
|
||||
self._levels[pid] = level
|
||||
|
||||
def _generator(self):
|
||||
"""Generates the """
|
||||
"""Generates the."""
|
||||
for proc in self.list_tasks(self.context, self.config['primary'], self.config['vmlinux']):
|
||||
self._processes[proc.pid] = proc
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""A module containing a collection of plugins that produce data
|
||||
typically found in mac's /proc file system.
|
||||
"""
|
||||
"""A module containing a collection of plugins that produce data typically
|
||||
found in mac's /proc file system."""
|
||||
|
||||
import datetime
|
||||
import struct
|
||||
@@ -20,7 +19,7 @@ from volatility.framework.symbols.linux.bash import BashIntermedSymbols
|
||||
|
||||
|
||||
class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
"""Recovers bash command history from memory"""
|
||||
"""Recovers bash command history from memory."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
|
||||
@@ -15,7 +15,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Check_syscall(plugins.PluginInterface):
|
||||
"""Check system call table for hooks"""
|
||||
"""Check system call table for hooks."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
|
||||
@@ -17,7 +17,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Check_sysctl(plugins.PluginInterface):
|
||||
"""Check sysctl handlers for hooks"""
|
||||
"""Check sysctl handlers for hooks."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
|
||||
@@ -16,7 +16,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Check_trap_table(plugins.PluginInterface):
|
||||
"""Check mach trap table for hooks"""
|
||||
"""Check mach trap table for hooks."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""A module containing a collection of plugins that produce data
|
||||
typically found in Mac's lsmod command.
|
||||
"""
|
||||
"""A module containing a collection of plugins that produce data typically
|
||||
found in Mac's lsmod command."""
|
||||
from volatility.framework import renderers, interfaces, contexts
|
||||
from volatility.framework.automagic import mac
|
||||
from volatility.framework.configuration import requirements
|
||||
@@ -13,7 +12,7 @@ from volatility.framework.renderers import format_hints
|
||||
|
||||
|
||||
class Lsmod(plugins.PluginInterface):
|
||||
"""Lists loaded kernel modules"""
|
||||
"""Lists loaded kernel modules."""
|
||||
|
||||
_version = (1, 0, 0)
|
||||
|
||||
@@ -27,7 +26,16 @@ class Lsmod(plugins.PluginInterface):
|
||||
|
||||
@classmethod
|
||||
def list_modules(cls, context: interfaces.context.ContextInterface, layer_name: str, darwin_symbols: str):
|
||||
"""Lists all the modules in the primary layer"""
|
||||
"""Lists all the modules in the primary layer.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
darwin_symbols: The name of the table containing the kernel symbols
|
||||
|
||||
Returns:
|
||||
A list of modules from the `layer_name` layer
|
||||
"""
|
||||
mac.MacUtilities.aslr_mask_symbol_table(context, darwin_symbols, layer_name)
|
||||
|
||||
kernel = contexts.Module(context, darwin_symbols, layer_name, 0)
|
||||
|
||||
@@ -14,7 +14,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class lsof(plugins.PluginInterface):
|
||||
"""Lists all open file descriptors for all processes"""
|
||||
"""Lists all open file descriptors for all processes."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
|
||||
@@ -13,7 +13,7 @@ from volatility.framework.renderers import format_hints
|
||||
|
||||
|
||||
class Malfind(interfaces_plugins.PluginInterface):
|
||||
"""Lists process memory ranges that potentially contain injected code"""
|
||||
"""Lists process memory ranges that potentially contain injected code."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -24,9 +24,8 @@ class Malfind(interfaces_plugins.PluginInterface):
|
||||
]
|
||||
|
||||
def _list_injections(self, task):
|
||||
"""Generate memory regions for a process that may contain
|
||||
injected code.
|
||||
"""
|
||||
"""Generate memory regions for a process that may contain injected
|
||||
code."""
|
||||
|
||||
proc_layer_name = task.add_process_layer()
|
||||
if proc_layer_name is None:
|
||||
|
||||
@@ -16,7 +16,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Netstat(plugins.PluginInterface):
|
||||
"""Lists all network connections for all processes"""
|
||||
"""Lists all network connections for all processes."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
|
||||
@@ -12,7 +12,7 @@ from volatility.framework.renderers import format_hints
|
||||
|
||||
|
||||
class Maps(interfaces_plugins.PluginInterface):
|
||||
"""Lists process memory ranges that potentially contain injected code"""
|
||||
"""Lists process memory ranges that potentially contain injected code."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 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
|
||||
#
|
||||
"""In-memory artifacts from OSX systems"""
|
||||
"""In-memory artifacts from OSX systems."""
|
||||
from typing import Iterator, Tuple, Any, Generator, List
|
||||
|
||||
from volatility.framework import exceptions, renderers, interfaces
|
||||
@@ -12,7 +12,7 @@ from volatility.plugins.mac import pslist
|
||||
|
||||
|
||||
class Psaux(plugins.PluginInterface):
|
||||
"""Recovers program command line arguments"""
|
||||
"""Recovers program command line arguments."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
|
||||
@@ -14,7 +14,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PsList(interfaces.plugins.PluginInterface):
|
||||
"""Lists the processes present in a particular mac memory image"""
|
||||
"""Lists the processes present in a particular mac memory image."""
|
||||
|
||||
_version = (1, 0, 0)
|
||||
|
||||
@@ -59,7 +59,17 @@ class PsList(interfaces.plugins.PluginInterface):
|
||||
darwin_symbols: str,
|
||||
filter_func: Callable[[int], bool] = lambda _: False) -> \
|
||||
Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""Lists all the tasks in the primary layer"""
|
||||
"""Lists all the processes in the primary layer.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
darwin_symbols: The name of the table containing the kernel symbols
|
||||
filter_func: A function which takes a process object and returns True if the process should be ignored/filtered
|
||||
|
||||
Returns:
|
||||
The list of process objects from the processes linked list after filtering
|
||||
"""
|
||||
|
||||
mac.MacUtilities.aslr_mask_symbol_table(context, darwin_symbols, layer_name)
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ from volatility.plugins.mac import pslist
|
||||
|
||||
|
||||
class PsTree(plugins.PluginInterface):
|
||||
"""Plugin for listing processes in a tree based on their parent process ID """
|
||||
"""Plugin for listing processes in a tree based on their parent process
|
||||
ID."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -28,7 +29,7 @@ class PsTree(plugins.PluginInterface):
|
||||
]
|
||||
|
||||
def _find_level(self, pid):
|
||||
"""Finds how deep the pid is in the processes list"""
|
||||
"""Finds how deep the pid is in the processes list."""
|
||||
seen = set([])
|
||||
seen.add(pid)
|
||||
level = 0
|
||||
@@ -43,7 +44,7 @@ class PsTree(plugins.PluginInterface):
|
||||
self._levels[pid] = level
|
||||
|
||||
def _generator(self):
|
||||
"""Generates the """
|
||||
"""Generates the."""
|
||||
for proc in pslist.PsList.list_tasks(self.context, self.config['primary'], self.config['darwin']):
|
||||
self._processes[proc.p_pid] = proc
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Tasks(pslist.PsList):
|
||||
"""Lists the processes present in a particular mac memory image"""
|
||||
"""Lists the processes present in a particular mac memory image."""
|
||||
|
||||
@classmethod
|
||||
def list_tasks(cls,
|
||||
@@ -22,7 +22,17 @@ class Tasks(pslist.PsList):
|
||||
darwin_symbols: str,
|
||||
filter_func: Callable[[int], bool] = lambda _: False) -> \
|
||||
Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""Lists all the tasks in the primary layer"""
|
||||
"""Lists all the tasks in the primary layer.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
darwin_symbols: The name of the table containing the kernel symbols
|
||||
filter_func: A function which takes a task object and returns True if the task should be ignored/filtered
|
||||
|
||||
Returns:
|
||||
The list of task objects from the `layer_name` layer's `tasks` list after filtering
|
||||
"""
|
||||
|
||||
mac.MacUtilities.aslr_mask_symbol_table(context, darwin_symbols, layer_name)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Check_syscall(plugins.PluginInterface):
|
||||
"""Check system call table for hooks"""
|
||||
"""Check system call table for hooks."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
|
||||
@@ -26,18 +26,21 @@ class TimeLinerType(enum.IntEnum):
|
||||
|
||||
|
||||
class TimeLinerInterface(metaclass = abc.ABCMeta):
|
||||
"""Interface defining methods that timeliner will use to generate a body file"""
|
||||
"""Interface defining methods that timeliner will use to generate a body
|
||||
file."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def generate_timeline(self) -> Generator[Tuple[str, TimeLinerType, datetime.datetime], None, None]:
|
||||
"""Method generates Tuples of (description, timestamp_type, timestamp)
|
||||
|
||||
These need not be generated in any particular order, sorting will be done later
|
||||
These need not be generated in any particular order, sorting
|
||||
will be done later
|
||||
"""
|
||||
|
||||
|
||||
class Timeliner(interfaces.plugins.PluginInterface):
|
||||
"""Runs all relevant plugins that provide time related information and orders the results by time"""
|
||||
"""Runs all relevant plugins that provide time related information and
|
||||
orders the results by time."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -82,7 +85,8 @@ class Timeliner(interfaces.plugins.PluginInterface):
|
||||
]
|
||||
|
||||
def _generator(self, runable_plugins: List[TimeLinerInterface]) -> Optional[Iterable[Tuple[int, Tuple]]]:
|
||||
"""Takes a timeline, sorts it and output the data from each relevant row from each plugin"""
|
||||
"""Takes a timeline, sorts it and output the data from each relevant
|
||||
row from each plugin."""
|
||||
# Generate the results for each plugin
|
||||
for plugin in runable_plugins:
|
||||
plugin_name = plugin.__class__.__name__
|
||||
@@ -112,7 +116,7 @@ class Timeliner(interfaces.plugins.PluginInterface):
|
||||
yield data
|
||||
|
||||
def run(self):
|
||||
"""Isolate each plugin and run it"""
|
||||
"""Isolate each plugin and run it."""
|
||||
|
||||
# Use all the plugins if there's no filter
|
||||
self.usable_plugins = self.usable_plugins or self.get_usable_plugins()
|
||||
@@ -153,6 +157,7 @@ class Timeliner(interfaces.plugins.PluginInterface):
|
||||
generator = self._generator(runable_plugins))
|
||||
|
||||
def build_configuration(self):
|
||||
"""Builds the configuration to save for the plugin such that it can be reconstructed"""
|
||||
"""Builds the configuration to save for the plugin such that it can be
|
||||
reconstructed."""
|
||||
vollog.warning("Unable to record configuration data for the timeliner plugin")
|
||||
return []
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""All core windows plugins
|
||||
"""All core windows plugins.
|
||||
|
||||
These modules should only be imported from volatility.plugins NOT volatility.framework.plugins
|
||||
These modules should only be imported from volatility.plugins NOT
|
||||
volatility.framework.plugins
|
||||
"""
|
||||
|
||||
@@ -12,7 +12,7 @@ from volatility.plugins.windows import pslist
|
||||
|
||||
|
||||
class CmdLine(interfaces_plugins.PluginInterface):
|
||||
"""Lists process command line arguments"""
|
||||
"""Lists process command line arguments."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
|
||||
@@ -22,7 +22,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DllDump(interfaces_plugins.PluginInterface):
|
||||
"""Dumps process memory ranges as DLLs"""
|
||||
"""Dumps process memory ranges as DLLs."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
|
||||
@@ -12,7 +12,7 @@ from volatility.plugins.windows import pslist
|
||||
|
||||
|
||||
class DllList(interfaces_plugins.PluginInterface):
|
||||
"""Lists the loaded modules in a particular windows memory image"""
|
||||
"""Lists the loaded modules in a particular windows memory image."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
|
||||
@@ -21,7 +21,7 @@ MAJOR_FUNCTIONS = [
|
||||
|
||||
|
||||
class DriverIrp(plugins.PluginInterface):
|
||||
"""List IRPs for drivers in a particular windows memory image"""
|
||||
"""List IRPs for drivers in a particular windows memory image."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
|
||||
@@ -13,7 +13,7 @@ from volatility.framework.renderers import format_hints
|
||||
|
||||
|
||||
class DriverScan(plugins.PluginInterface):
|
||||
"""Scans for drivers present in a particular windows memory image"""
|
||||
"""Scans for drivers present in a particular windows memory image."""
|
||||
|
||||
_version = (1, 0, 0)
|
||||
|
||||
@@ -31,7 +31,16 @@ class DriverScan(plugins.PluginInterface):
|
||||
layer_name: str,
|
||||
symbol_table: str) -> \
|
||||
Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""Scans for drivers using the poolscanner module and constraints"""
|
||||
"""Scans for drivers using the poolscanner module and constraints.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
symbol_table: The name of the table containing the kernel symbols
|
||||
|
||||
Returns:
|
||||
A list of Driver objects as found from the `layer_name` layer based on Driver pool signatures
|
||||
"""
|
||||
|
||||
constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'Dri\xf6', b'Driv'])
|
||||
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
|
||||
from typing import Iterable
|
||||
|
||||
import volatility.plugins.windows.poolscanner as poolscanner
|
||||
|
||||
import volatility.framework.interfaces.plugins as plugins
|
||||
from volatility.framework import renderers, interfaces, exceptions
|
||||
from volatility.framework.configuration import requirements
|
||||
from volatility.framework.renderers import format_hints
|
||||
import volatility.plugins.windows.poolscanner as poolscanner
|
||||
|
||||
|
||||
class FileScan(plugins.PluginInterface):
|
||||
"""Scans for file objects present in a particular windows memory image"""
|
||||
"""Scans for file objects present in a particular windows memory image."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -28,7 +29,16 @@ class FileScan(plugins.PluginInterface):
|
||||
layer_name: str,
|
||||
symbol_table: str) -> \
|
||||
Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""Scans for file objects using the poolscanner module and constraints"""
|
||||
"""Scans for file objects using the poolscanner module and constraints.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
symbol_table: The name of the table containing the kernel symbols
|
||||
|
||||
Returns:
|
||||
A list of File objects as found from the `layer_name` layer based on File pool signatures
|
||||
"""
|
||||
|
||||
constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'Fil\xe5', b'File'])
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Dict
|
||||
|
||||
import volatility.plugins.windows.pslist as pslist
|
||||
|
||||
@@ -24,7 +24,7 @@ except ImportError:
|
||||
|
||||
|
||||
class Handles(interfaces_plugins.PluginInterface):
|
||||
"""Lists process open handles"""
|
||||
"""Lists process open handles."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -46,8 +46,11 @@ class Handles(interfaces_plugins.PluginInterface):
|
||||
|
||||
def _decode_pointer(self, value, magic):
|
||||
"""Windows encodes pointers to objects and decodes them on the fly
|
||||
before using them. This function mimics the decoding routine so we
|
||||
can generate the proper pointer values as well."""
|
||||
before using them.
|
||||
|
||||
This function mimics the decoding routine so we can generate the
|
||||
proper pointer values as well.
|
||||
"""
|
||||
|
||||
value = value & 0xFFFFFFFFFFFFFFF8
|
||||
value = value >> magic
|
||||
@@ -57,8 +60,8 @@ class Handles(interfaces_plugins.PluginInterface):
|
||||
return value
|
||||
|
||||
def _get_item(self, handle_table_entry, handle_value):
|
||||
"""Given a handle table entry (_HANDLE_TABLE_ENTRY) structure from
|
||||
a process' handle table, determine where the corresponding object's
|
||||
"""Given a handle table entry (_HANDLE_TABLE_ENTRY) structure from a
|
||||
process' handle table, determine where the corresponding object's
|
||||
_OBJECT_HEADER can be found."""
|
||||
|
||||
virtual = self.config["primary"]
|
||||
@@ -91,10 +94,12 @@ class Handles(interfaces_plugins.PluginInterface):
|
||||
return object_header
|
||||
|
||||
def find_sar_value(self):
|
||||
"""Locate ObpCaptureHandleInformationEx if it exists in the
|
||||
sample. Once found, parse it for the SAR value that we need
|
||||
to decode pointers in the _HANDLE_TABLE_ENTRY which allows us
|
||||
to find the associated _OBJECT_HEADER."""
|
||||
"""Locate ObpCaptureHandleInformationEx if it exists in the sample.
|
||||
|
||||
Once found, parse it for the SAR value that we need to decode
|
||||
pointers in the _HANDLE_TABLE_ENTRY which allows us to find the
|
||||
associated _OBJECT_HEADER.
|
||||
"""
|
||||
|
||||
if self._sar_value is None:
|
||||
|
||||
@@ -128,14 +133,25 @@ class Handles(interfaces_plugins.PluginInterface):
|
||||
return self._sar_value
|
||||
|
||||
@classmethod
|
||||
def list_objects(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str) -> dict:
|
||||
def list_objects(cls, context: interfaces.context.ContextInterface, layer_name: str,
|
||||
symbol_table: str) -> Dict[int, str]:
|
||||
"""List the executive object types (_OBJECT_TYPE) using the
|
||||
ObTypeIndexTable or ObpObjectTypes symbol (differs per OS).
|
||||
This method will be necessary for determining what type of
|
||||
object we have given an object header.
|
||||
ObTypeIndexTable or ObpObjectTypes symbol (differs per OS). This method
|
||||
will be necessary for determining what type of object we have given an
|
||||
object header.
|
||||
|
||||
Note: The object type index map was hard coded into profiles
|
||||
in vol2, but we generate it dynamically now."""
|
||||
Note:
|
||||
The object type index map was hard coded into profiles in previous versions of volatility.
|
||||
It is now generated dynamically.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
symbol_table: The name of the table containing the kernel symbols
|
||||
|
||||
Returns:
|
||||
A mapping of type indicies to type names
|
||||
"""
|
||||
|
||||
type_map = {}
|
||||
|
||||
@@ -150,7 +166,7 @@ class Handles(interfaces_plugins.PluginInterface):
|
||||
ptrs = ntkrnlmp.object(
|
||||
object_type = "array", offset = table_addr, subtype = ntkrnlmp.get_type("pointer"), count = 100)
|
||||
|
||||
for i, ptr in enumerate(ptrs): #type: ignore
|
||||
for i, ptr in enumerate(ptrs): # type: ignore
|
||||
# the first entry in the table is always null. break the
|
||||
# loop when we encounter the first null entry after that
|
||||
if i > 0 and ptr == 0:
|
||||
@@ -182,8 +198,8 @@ class Handles(interfaces_plugins.PluginInterface):
|
||||
return context.object(symbol_table + constants.BANG + "unsigned int", layer_name, offset = kvo + offset)
|
||||
|
||||
def _make_handle_array(self, offset, level, depth = 0):
|
||||
"""Parse a process' handle table and yield valid handle table
|
||||
entries, going as deep into the table "levels" as necessary."""
|
||||
"""Parse a process' handle table and yield valid handle table entries,
|
||||
going as deep into the table "levels" as necessary."""
|
||||
|
||||
virtual = self.config["primary"]
|
||||
kvo = self.context.layers[virtual].config['kernel_virtual_offset']
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
#
|
||||
|
||||
import time
|
||||
from typing import List
|
||||
from typing import List, Tuple, Iterable
|
||||
|
||||
from volatility.framework.interfaces import plugins
|
||||
from volatility.framework import constants, interfaces, layers
|
||||
from volatility.framework.configuration import requirements
|
||||
from volatility.framework.interfaces import plugins
|
||||
from volatility.framework.renderers import TreeGrid
|
||||
from volatility.framework.symbols import intermed
|
||||
from volatility.framework.symbols.windows import extensions
|
||||
@@ -15,7 +15,7 @@ from volatility.framework.symbols.windows.extensions import kdbg
|
||||
|
||||
|
||||
class Info(plugins.PluginInterface):
|
||||
"""Show OS & kernel details of the memory sample being analyzed"""
|
||||
"""Show OS & kernel details of the memory sample being analyzed."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# 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 typing import Iterable, Tuple
|
||||
|
||||
import volatility.plugins.windows.pslist as pslist
|
||||
import volatility.plugins.windows.vadinfo as vadinfo
|
||||
@@ -13,7 +14,7 @@ from volatility.framework.renderers import format_hints
|
||||
|
||||
|
||||
class Malfind(interfaces.plugins.PluginInterface):
|
||||
"""Lists process memory ranges that potentially contain injected code"""
|
||||
"""Lists process memory ranges that potentially contain injected code."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -27,16 +28,18 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def is_vad_empty(self, proc_layer, vad):
|
||||
"""Check if a VAD region is either entirely unavailable
|
||||
due to paging, entirely consisting of zeros, or a
|
||||
combination of the two. This helps ignore false positives
|
||||
whose VAD flags match task._injection_filter requirements
|
||||
but there's no data and thus not worth reporting it.
|
||||
def is_vad_empty(cls, proc_layer, vad):
|
||||
"""Check if a VAD region is either entirely unavailable due to paging,
|
||||
entirely consisting of zeros, or a combination of the two. This helps
|
||||
ignore false positives whose VAD flags match task._injection_filter
|
||||
requirements but there's no data and thus not worth reporting it.
|
||||
|
||||
Args:
|
||||
proc_layer: the process layer
|
||||
vad: the MMVAD structure to test
|
||||
|
||||
Returns:
|
||||
A boolean indicating whether a vad is empty or not
|
||||
"""
|
||||
|
||||
CHUNK_SIZE = 0x1000
|
||||
@@ -55,12 +58,18 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
|
||||
@classmethod
|
||||
def list_injections(cls, context: interfaces.context.ContextInterface, symbol_table: str,
|
||||
proc: interfaces.objects.ObjectInterface):
|
||||
"""Generate memory regions for a process that may contain
|
||||
injected code.
|
||||
proc: interfaces.objects.ObjectInterface
|
||||
) -> Iterable[Tuple[interfaces.objects.ObjectInterface, bytes]]:
|
||||
"""Generate memory regions for a process that may contain injected
|
||||
code.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
symbol_table: The name of the table containing the kernel symbols
|
||||
proc: an _EPROCESS instance
|
||||
|
||||
Returns:
|
||||
An iterable of VAD instances and the first 64 bytes of data containing in that region
|
||||
"""
|
||||
|
||||
proc_layer_name = proc.add_process_layer()
|
||||
|
||||
@@ -18,7 +18,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ModDump(interfaces.plugins.PluginInterface):
|
||||
"""Dumps kernel modules"""
|
||||
"""Dumps kernel modules."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -41,8 +41,14 @@ class ModDump(interfaces.plugins.PluginInterface):
|
||||
the primary/kernel layer. Then keep one layer per session by cycling
|
||||
through the process list.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
symbol_table: The name of the table containing the kernel symbols
|
||||
pids: A list of process identifiers to include exclusively or None for no filter
|
||||
|
||||
Returns:
|
||||
<list> of layer names
|
||||
A list of session layer names
|
||||
"""
|
||||
seen_ids = [] # type: List[interfaces.objects.ObjectInterface]
|
||||
filter_func = pslist.PsList.create_pid_filter(pids or [])
|
||||
@@ -72,15 +78,18 @@ class ModDump(interfaces.plugins.PluginInterface):
|
||||
@classmethod
|
||||
def find_session_layer(cls, context: interfaces.context.ContextInterface, session_layers: Iterable[str],
|
||||
base_address: int):
|
||||
"""Given a base address and a list of layer names, find a
|
||||
layer that can access the specified address.
|
||||
"""Given a base address and a list of layer names, find a layer that
|
||||
can access the specified address.
|
||||
|
||||
Args:
|
||||
session_layers: <list> of layer names
|
||||
base_address: <int> the base address
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
symbol_table: The name of the table containing the kernel symbols
|
||||
session_layers: A list of session layer names
|
||||
base_address: The base address to identify the layers that can access it
|
||||
|
||||
Returns:
|
||||
layer name (or None)
|
||||
Layer name or None if no layers that contain the base address can be found
|
||||
"""
|
||||
|
||||
for layer_name in session_layers:
|
||||
|
||||
@@ -13,7 +13,7 @@ from volatility.framework.renderers import format_hints
|
||||
|
||||
|
||||
class ModScan(plugins.PluginInterface):
|
||||
"""Scans for modules present in a particular windows memory image"""
|
||||
"""Scans for modules present in a particular windows memory image."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -29,7 +29,16 @@ class ModScan(plugins.PluginInterface):
|
||||
layer_name: str,
|
||||
symbol_table: str) -> \
|
||||
Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""Scans for modules using the poolscanner module and constraints"""
|
||||
"""Scans for modules using the poolscanner module and constraints.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
symbol_table: The name of the table containing the kernel symbols
|
||||
|
||||
Returns:
|
||||
A list of Driver objects as found from the `layer_name` layer based on Driver pool signatures
|
||||
"""
|
||||
|
||||
constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'MmLd'])
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
|
||||
#
|
||||
|
||||
from typing import List
|
||||
from typing import List, Iterable
|
||||
|
||||
from volatility.framework import constants
|
||||
from volatility.framework import exceptions, interfaces
|
||||
@@ -12,7 +12,7 @@ from volatility.framework.renderers import format_hints
|
||||
|
||||
|
||||
class Modules(interfaces.plugins.PluginInterface):
|
||||
"""Lists the loaded kernel modules"""
|
||||
"""Lists the loaded kernel modules."""
|
||||
|
||||
_version = (1, 0, 0)
|
||||
|
||||
@@ -46,8 +46,18 @@ class Modules(interfaces.plugins.PluginInterface):
|
||||
))
|
||||
|
||||
@classmethod
|
||||
def list_modules(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str):
|
||||
"""Lists all the modules in the primary layer"""
|
||||
def list_modules(cls, context: interfaces.context.ContextInterface, layer_name: str,
|
||||
symbol_table: str) -> Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""Lists all the modules in the primary layer.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
symbol_table: The name of the table containing the kernel symbols
|
||||
|
||||
Returns:
|
||||
A list of Modules as retrieved from PsLoadedModuleList
|
||||
"""
|
||||
|
||||
kvo = context.layers[layer_name].config['kernel_virtual_offset']
|
||||
ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo)
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
|
||||
from typing import Iterable
|
||||
|
||||
import volatility.plugins.windows.poolscanner as poolscanner
|
||||
|
||||
import volatility.framework.interfaces.plugins as plugins
|
||||
from volatility.framework import renderers, interfaces, exceptions
|
||||
from volatility.framework.configuration import requirements
|
||||
from volatility.framework.renderers import format_hints
|
||||
import volatility.plugins.windows.poolscanner as poolscanner
|
||||
|
||||
|
||||
class MutantScan(plugins.PluginInterface):
|
||||
"""Scans for mutexes present in a particular windows memory image"""
|
||||
"""Scans for mutexes present in a particular windows memory image."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -28,7 +29,16 @@ class MutantScan(plugins.PluginInterface):
|
||||
layer_name: str,
|
||||
symbol_table: str) -> \
|
||||
Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""Scans for mutants using the poolscanner module and constraints"""
|
||||
"""Scans for mutants using the poolscanner module and constraints.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
symbol_table: The name of the table containing the kernel symbols
|
||||
|
||||
Returns:
|
||||
A list of Mutant objects found by scanning memory for the Mutant pool signatures
|
||||
"""
|
||||
|
||||
constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'Mut\xe1', b'Muta'])
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
# TODO: When python3.5 is no longer supported, make this enum.IntFlag
|
||||
class PoolType(enum.IntEnum):
|
||||
"""Class to maintain the different possible PoolTypes
|
||||
The values must be integer powers of 2"""
|
||||
"""Class to maintain the different possible PoolTypes The values must be
|
||||
integer powers of 2."""
|
||||
|
||||
PAGED = 1
|
||||
NONPAGED = 2
|
||||
@@ -31,7 +31,8 @@ class PoolType(enum.IntEnum):
|
||||
|
||||
|
||||
class PoolConstraint:
|
||||
"""Class to maintain tag/size/index/type information about Pool header tags"""
|
||||
"""Class to maintain tag/size/index/type information about Pool header
|
||||
tags."""
|
||||
|
||||
def __init__(self,
|
||||
tag: bytes,
|
||||
@@ -110,24 +111,42 @@ class PoolHeaderScanner(interfaces.layers.ScannerInterface):
|
||||
def os_distinguisher(version_check: Callable[[Tuple[int, ...]], bool],
|
||||
fallback_checks: List[Tuple[str, Optional[str], bool]]
|
||||
) -> Callable[[interfaces.context.ContextInterface, str], bool]:
|
||||
"""Distinguishes a symbol table as being above a particular version or point
|
||||
"""Distinguishes a symbol table as being above a particular version or
|
||||
point.
|
||||
|
||||
This will primarily check the version metadata first and foremost.
|
||||
If that metadata isn't available then each item in the fallback_checks is tested.
|
||||
If invert is specified then the result will be true if the version is less than that specified, or in the case of
|
||||
fallback, if any of the fallback checks is successful.
|
||||
This will primarily check the version metadata first and foremost.
|
||||
If that metadata isn't available then each item in the fallback_checks is tested.
|
||||
If invert is specified then the result will be true if the version is less than that specified, or in the case of
|
||||
fallback, if any of the fallback checks is successful.
|
||||
|
||||
A fallback check is made up of:
|
||||
* a symbol or type name
|
||||
* a member name (implying that the value before was a type name)
|
||||
* whether that symbol, type or member must be present or absent for the symbol table to be more above the required point
|
||||
A fallback check is made up of:
|
||||
* a symbol or type name
|
||||
* a member name (implying that the value before was a type name)
|
||||
* whether that symbol, type or member must be present or absent for the symbol table to be more above the required point
|
||||
|
||||
Note: Specifying that a member must not be present includes the whole type not being present too (ie, either will pass the test)
|
||||
Note:
|
||||
Specifying that a member must not be present includes the whole type not being present too (ie, either will pass the test)
|
||||
|
||||
Args:
|
||||
version_check: Function that takes a 4-tuple version and returns whether whether the provided version is above a particular point
|
||||
fallback_checks: A list of symbol/types/members of types, and whether they must be present to be above the required point
|
||||
|
||||
Returns:
|
||||
A function that takes a context and a symbol table name and determines whether that symbol table passes the distinguishing checks
|
||||
"""
|
||||
|
||||
# try the primary method based on the pe version in the ISF
|
||||
@functools.wraps(version_check)
|
||||
def method(context: interfaces.context.ContextInterface, symbol_table: str) -> bool:
|
||||
"""
|
||||
|
||||
Args:
|
||||
context: The context that contains the symbol table named `symbol_table`
|
||||
symbol_table: Name of the symbol table within the context to distinguish the version of
|
||||
|
||||
Returns:
|
||||
True if the symbol table is of the required version
|
||||
"""
|
||||
|
||||
try:
|
||||
pe_version = context.symbol_space[symbol_table].metadata.pe_version
|
||||
@@ -160,7 +179,7 @@ def os_distinguisher(version_check: Callable[[Tuple[int, ...]], bool],
|
||||
|
||||
|
||||
class PoolScanner(plugins.PluginInterface):
|
||||
"""A generic pool scanner plugin"""
|
||||
"""A generic pool scanner plugin."""
|
||||
|
||||
_version = (1, 0, 0)
|
||||
|
||||
@@ -210,6 +229,13 @@ class PoolScanner(plugins.PluginInterface):
|
||||
The tags_filter is a list of pool tags, and the associated
|
||||
PoolConstraints are returned. If tags_filter is empty or
|
||||
not supplied, then all builtin constraints are returned.
|
||||
|
||||
Args:
|
||||
symbol_table: The name of the symbol table to prepend to the types used
|
||||
tags_filter: List of tags to return or None to return all
|
||||
|
||||
Returns:
|
||||
A list of well-known constructed PoolConstraints that match the provided tags
|
||||
"""
|
||||
|
||||
builtins = [
|
||||
@@ -316,6 +342,17 @@ class PoolScanner(plugins.PluginInterface):
|
||||
constraints: List[PoolConstraint]) \
|
||||
-> Generator[Tuple[
|
||||
PoolConstraint, interfaces.objects.ObjectInterface, interfaces.objects.ObjectInterface], None, None]:
|
||||
"""
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
symbol_table: The name of the table containing the kernel symbols
|
||||
constraints: List of pool constraints used to limit the scan results
|
||||
|
||||
Returns:
|
||||
Iterable of tuples, containing the constraint that matched, the object from memory, the object header used to determine the object
|
||||
"""
|
||||
|
||||
# get the object type map
|
||||
type_map = handles.Handles.list_objects(context = context, layer_name = layer_name, symbol_table = symbol_table)
|
||||
@@ -357,8 +394,22 @@ class PoolScanner(plugins.PluginInterface):
|
||||
alignment: int = 8,
|
||||
progress_callback: Optional[constants.ProgressCallback] = None) \
|
||||
-> Generator[Tuple[PoolConstraint, interfaces.objects.ObjectInterface], None, None]:
|
||||
"""Returns the _POOL_HEADER object (based on the symbol_table template) after scanning through layer_name
|
||||
returning all headers that match any of the constraints provided. Only one constraint can be provided per tag"""
|
||||
"""Returns the _POOL_HEADER object (based on the symbol_table template)
|
||||
after scanning through layer_name returning all headers that match any
|
||||
of the constraints provided. Only one constraint can be provided per
|
||||
tag.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
symbol_table: The name of the table containing the kernel symbols
|
||||
pool_constraints: List of pool constraints used to limit the scan results
|
||||
alignment: An optional value that all pool headers will be aligned to
|
||||
progress_callback: An optional function to provide progress feedback whilst scanning
|
||||
|
||||
Returns:
|
||||
An Iterable of pool constraints and the pool headers associated with them
|
||||
"""
|
||||
# Setup the pattern
|
||||
constraint_lookup = {} # type: Dict[bytes, PoolConstraint]
|
||||
for constraint in pool_constraints:
|
||||
|
||||
@@ -21,7 +21,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ProcDump(interfaces_plugins.PluginInterface):
|
||||
"""Dumps process executable images"""
|
||||
"""Dumps process executable images."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
|
||||
@@ -14,7 +14,7 @@ from volatility.plugins import timeliner
|
||||
|
||||
|
||||
class PsList(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
"""Lists the processes present in a particular windows memory image"""
|
||||
"""Lists the processes present in a particular windows memory image."""
|
||||
|
||||
_version = (1, 0, 0)
|
||||
PHYSICAL_DEFAULT = False
|
||||
@@ -37,6 +37,15 @@ class PsList(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
|
||||
@classmethod
|
||||
def create_pid_filter(cls, pid_list: List[int] = None) -> Callable[[interfaces.objects.ObjectInterface], bool]:
|
||||
"""A factory for producing filter functions that filter based on a list
|
||||
of process IDs.
|
||||
|
||||
Args:
|
||||
pid_list: A list of process IDs that are acceptable, all other processes will be filtered out
|
||||
|
||||
Returns:
|
||||
Filter function for passing to the `list_processes` method
|
||||
"""
|
||||
filter_func = lambda _: False
|
||||
# FIXME: mypy #4973 or #2608
|
||||
pid_list = pid_list or []
|
||||
@@ -47,6 +56,15 @@ class PsList(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
|
||||
@classmethod
|
||||
def create_name_filter(cls, name_list: List[str] = None) -> Callable[[interfaces.objects.ObjectInterface], bool]:
|
||||
"""A factory for producing filter functions that filter based on a list
|
||||
of process names.
|
||||
|
||||
Args:
|
||||
name_list: A list of process names that are acceptable, all other processes will be filtered out
|
||||
|
||||
Returns:
|
||||
Filter function for passing to the `list_processes` method
|
||||
"""
|
||||
filter_func = lambda _: False
|
||||
# FIXME: mypy #4973 or #2608
|
||||
name_list = name_list or []
|
||||
@@ -62,7 +80,18 @@ class PsList(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
symbol_table: str,
|
||||
filter_func: Callable[[interfaces.objects.ObjectInterface], bool] = lambda _: False) -> \
|
||||
Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""Lists all the processes in the primary layer that are in the pid config option"""
|
||||
"""Lists all the processes in the primary layer that are in the pid
|
||||
config option.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
symbol_table: The name of the table containing the kernel symbols
|
||||
filter_func: A function which takes an EPROCESS object and returns True if the process should be ignored/filtered
|
||||
|
||||
Returns:
|
||||
The list of EPROCESS objects from the `layer_name` layer's PsActiveProcessHead list after filtering
|
||||
"""
|
||||
|
||||
# We only use the object factory to demonstrate how to use one
|
||||
kvo = context.layers[layer_name].config['kernel_virtual_offset']
|
||||
|
||||
@@ -14,7 +14,7 @@ import volatility.plugins.windows.poolscanner as poolscanner
|
||||
|
||||
|
||||
class PsScan(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
"""Scans for processes present in a particular windows memory image"""
|
||||
"""Scans for processes present in a particular windows memory image."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -30,7 +30,16 @@ class PsScan(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
layer_name: str,
|
||||
symbol_table: str) -> \
|
||||
Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""Scans for processes using the poolscanner module and constraints"""
|
||||
"""Scans for processes using the poolscanner module and constraints.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
symbol_table: The name of the table containing the kernel symbols
|
||||
|
||||
Returns:
|
||||
A list of processes found by scanning the `layer_name` layer for process pool signatures
|
||||
"""
|
||||
|
||||
constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'Pro\xe3', b'Proc'])
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ from volatility.plugins.windows import pslist
|
||||
|
||||
|
||||
class PsTree(pslist.PsList):
|
||||
"""Plugin for listing processes in a tree based on their parent process ID """
|
||||
"""Plugin for listing processes in a tree based on their parent process
|
||||
ID."""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -19,7 +20,7 @@ class PsTree(pslist.PsList):
|
||||
self._children = {} # type: Dict[int, Set[int]]
|
||||
|
||||
def find_level(self, pid: objects.Pointer) -> None:
|
||||
"""Finds how deep the pid is in the processes list"""
|
||||
"""Finds how deep the pid is in the processes list."""
|
||||
seen = set([])
|
||||
seen.add(pid)
|
||||
level = 0
|
||||
@@ -33,7 +34,7 @@ class PsTree(pslist.PsList):
|
||||
self._levels[pid] = level
|
||||
|
||||
def _generator(self):
|
||||
"""Generates the Tree of processes"""
|
||||
"""Generates the Tree of processes."""
|
||||
for proc in self.list_processes(self.context, self.config['primary'], self.config['nt_symbols']):
|
||||
|
||||
if not self.config.get('physical', self.PHYSICAL_DEFAULT):
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""All core windows registry plugins
|
||||
"""All core windows registry plugins.
|
||||
|
||||
These modules should only be imported from volatility.plugins NOT volatility.framework.plugins
|
||||
These modules should only be imported from volatility.plugins NOT
|
||||
volatility.framework.plugins
|
||||
"""
|
||||
|
||||
@@ -13,7 +13,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HiveList(plugins.PluginInterface):
|
||||
"""Lists the registry hives present in a particular memory image"""
|
||||
"""Lists the registry hives present in a particular memory image."""
|
||||
|
||||
_version = (1, 0, 0)
|
||||
|
||||
@@ -41,8 +41,18 @@ class HiveList(plugins.PluginInterface):
|
||||
context: interfaces.context.ContextInterface,
|
||||
layer_name: str,
|
||||
symbol_table: str,
|
||||
filter_string: None = None) -> Iterator[interfaces.objects.ObjectInterface]:
|
||||
"""Lists all the hives in the primary layer"""
|
||||
filter_string: str = None) -> Iterator[interfaces.objects.ObjectInterface]:
|
||||
"""Lists all the hives in the primary layer.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
symbol_table: The name of the table containing the kernel symbols
|
||||
filter_string: A string which must be present in the hive name if specified
|
||||
|
||||
Returns:
|
||||
The list of registry hives from the `layer_name` layer as filtered against using the `filter_string`
|
||||
"""
|
||||
|
||||
# We only use the object factory to demonstrate how to use one
|
||||
kvo = context.layers[layer_name].config['kernel_virtual_offset']
|
||||
|
||||
@@ -13,7 +13,8 @@ from volatility.framework.renderers import format_hints
|
||||
|
||||
|
||||
class HiveScan(plugins.PluginInterface):
|
||||
"""Scans for registry hives present in a particular windows memory image"""
|
||||
"""Scans for registry hives present in a particular windows memory
|
||||
image."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -29,7 +30,16 @@ class HiveScan(plugins.PluginInterface):
|
||||
layer_name: str,
|
||||
symbol_table: str) -> \
|
||||
Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""Scans for hives using the poolscanner module and constraints"""
|
||||
"""Scans for hives using the poolscanner module and constraints.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
layer_name: The name of the layer on which to operate
|
||||
symbol_table: The name of the table containing the kernel symbols
|
||||
|
||||
Returns:
|
||||
A list of Hive objects as found from the `layer_name` layer based on Hive pool signatures
|
||||
"""
|
||||
|
||||
constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'CM10'])
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PrintKey(interfaces.plugins.PluginInterface):
|
||||
"""Lists the registry keys under a hive or specific key value"""
|
||||
"""Lists the registry keys under a hive or specific key value."""
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -36,8 +36,17 @@ class PrintKey(interfaces.plugins.PluginInterface):
|
||||
@classmethod
|
||||
def hive_walker(cls, hive: RegistryHive, node_path: Sequence[objects.StructType] = None,
|
||||
recurse: bool = False) -> Generator:
|
||||
"""Walks through a set of nodes from a given node (last one in node_path).
|
||||
Avoids loops by not traversing into nodes already present in the node_path
|
||||
"""Walks through a set of nodes from a given node (last one in
|
||||
node_path). Avoids loops by not traversing into nodes already present
|
||||
in the node_path.
|
||||
|
||||
Args:
|
||||
hive: The registry hive to walk
|
||||
node_path: The list of nodes that make up the
|
||||
recurse: Traverse down the node tree or stay only on the same level
|
||||
|
||||
Yields:
|
||||
The depth, and a tuple of results (last write time, hive offset, type, path, name, data and volatile)
|
||||
"""
|
||||
if not node_path:
|
||||
node_path = [hive.get_node(hive.root_cell_offset)]
|
||||
@@ -100,7 +109,7 @@ class PrintKey(interfaces.plugins.PluginInterface):
|
||||
symbol_table: str,
|
||||
offset: int = None,
|
||||
key: str = None):
|
||||
"""Walks through a registry, hive by hive"""
|
||||
"""Walks through a registry, hive by hive."""
|
||||
if offset is None:
|
||||
try:
|
||||
hive_offsets = [
|
||||
|
||||
@@ -20,7 +20,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UserAssist(interfaces.plugins.PluginInterface):
|
||||
"""Print userassist registry keys and information"""
|
||||
"""Print userassist registry keys and information."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -41,7 +41,8 @@ class UserAssist(interfaces.plugins.PluginInterface):
|
||||
]
|
||||
|
||||
def parse_userassist_data(self, reg_val):
|
||||
"""Reads the raw data of a _CM_KEY_VALUE and returns a dict of userassist fields"""
|
||||
"""Reads the raw data of a _CM_KEY_VALUE and returns a dict of
|
||||
userassist fields."""
|
||||
|
||||
item = {
|
||||
"id": renderers.UnparsableValue(),
|
||||
@@ -95,7 +96,8 @@ class UserAssist(interfaces.plugins.PluginInterface):
|
||||
return item
|
||||
|
||||
def _determine_userassist_type(self) -> None:
|
||||
"""Determine the userassist type and size depending on the OS version"""
|
||||
"""Determine the userassist type and size depending on the OS
|
||||
version."""
|
||||
|
||||
if self._win7 is True:
|
||||
self._userassist_type_name = "_VOL_USERASSIST_TYPES_7"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user