diff --git a/volatility/__init__.py b/volatility/__init__.py index 6991de2a3..645f0df27 100644 --- a/volatility/__init__.py +++ b/volatility/__init__.py @@ -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 diff --git a/volatility/cli/__init__.py b/volatility/cli/__init__.py index 94839fedb..7e710eb52 100644 --- a/volatility/cli/__init__.py +++ b/volatility/cli/__init__.py @@ -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() diff --git a/volatility/cli/text_renderer.py b/volatility/cli/text_renderer.py index 58b5bcfec..2dc7d3f8a 100644 --- a/volatility/cli/text_renderer.py +++ b/volatility/cli/text_renderer.py @@ -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 diff --git a/volatility/cli/volshell/__init__.py b/volatility/cli/volshell/__init__.py index 67cee7931..519078700 100644 --- a/volatility/cli/volshell/__init__.py +++ b/volatility/cli/volshell/__init__.py @@ -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() diff --git a/volatility/cli/volshell/shellplugin.py b/volatility/cli/volshell/shellplugin.py index 22ff3d8d0..398a66c81 100644 --- a/volatility/cli/volshell/shellplugin.py +++ b/volatility/cli/volshell/shellplugin.py @@ -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] diff --git a/volatility/cli/volshell/windows.py b/volatility/cli/volshell/windows.py index 2fd86a5ea..f6bbe1047 100644 --- a/volatility/cli/volshell/windows.py +++ b/volatility/cli/volshell/windows.py @@ -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'] diff --git a/volatility/framework/__init__.py b/volatility/framework/__init__.py index 8446e6890..d525e48fc 100644 --- a/volatility/framework/__init__.py +++ b/volatility/framework/__init__.py @@ -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") diff --git a/volatility/framework/automagic/__init__.py b/volatility/framework/automagic/__init__.py index db85d0699..481aa37d6 100644 --- a/volatility/framework/automagic/__init__.py +++ b/volatility/framework/automagic/__init__.py @@ -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 diff --git a/volatility/framework/automagic/construct_layers.py b/volatility/framework/automagic/construct_layers.py index 6e5dcbd51..85e40d5dc 100644 --- a/volatility/framework/automagic/construct_layers.py +++ b/volatility/framework/automagic/construct_layers.py @@ -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 diff --git a/volatility/framework/automagic/linux.py b/volatility/framework/automagic/linux.py index 2678cbb46..45fb82b1e 100644 --- a/volatility/framework/automagic/linux.py +++ b/volatility/framework/automagic/linux.py @@ -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 diff --git a/volatility/framework/automagic/mac.py b/volatility/framework/automagic/mac.py index e0cdeb679..da60890a2 100644 --- a/volatility/framework/automagic/mac.py +++ b/volatility/framework/automagic/mac.py @@ -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 diff --git a/volatility/framework/automagic/pdbscan.py b/volatility/framework/automagic/pdbscan.py index d32babb4c..cf89ba5aa 100644 --- a/volatility/framework/automagic/pdbscan.py +++ b/volatility/framework/automagic/pdbscan.py @@ -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 diff --git a/volatility/framework/automagic/stacker.py b/volatility/framework/automagic/stacker.py index 50d29b59c..a8fa461b6 100644 --- a/volatility/framework/automagic/stacker.py +++ b/volatility/framework/automagic/stacker.py @@ -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 diff --git a/volatility/framework/automagic/symbol_cache.py b/volatility/framework/automagic/symbol_cache.py index 7cbb6b104..03957f062 100644 --- a/volatility/framework/automagic/symbol_cache.py +++ b/volatility/framework/automagic/symbol_cache.py @@ -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: diff --git a/volatility/framework/automagic/symbol_finder.py b/volatility/framework/automagic/symbol_finder.py index a499f5899..acda56aaa 100644 --- a/volatility/framework/automagic/symbol_finder.py +++ b/volatility/framework/automagic/symbol_finder.py @@ -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: diff --git a/volatility/framework/automagic/windows.py b/volatility/framework/automagic/windows.py index 5d05cef44..b926c537a 100644 --- a/volatility/framework/automagic/windows.py +++ b/volatility/framework/automagic/windows.py @@ -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", diff --git a/volatility/framework/configuration/requirements.py b/volatility/framework/configuration/requirements.py index 54f51715a..ba365f67b 100644 --- a/volatility/framework/configuration/requirements.py +++ b/volatility/framework/configuration/requirements.py @@ -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() diff --git a/volatility/framework/constants/__init__.py b/volatility/framework/constants/__init__.py index a407562b1..e37dad0b1 100644 --- a/volatility/framework/constants/__init__.py +++ b/volatility/framework/constants/__init__.py @@ -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 diff --git a/volatility/framework/constants/linux/__init__.py b/volatility/framework/constants/linux/__init__.py index b145d4e3e..73bdae6e7 100644 --- a/volatility/framework/constants/linux/__init__.py +++ b/volatility/framework/constants/linux/__init__.py @@ -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 diff --git a/volatility/framework/constants/windows/__init__.py b/volatility/framework/constants/windows/__init__.py index 73b4c0649..8684dfeb1 100644 --- a/volatility/framework/constants/windows/__init__.py +++ b/volatility/framework/constants/windows/__init__.py @@ -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""" diff --git a/volatility/framework/contexts/__init__.py b/volatility/framework/contexts/__init__.py index 9eb9ae78a..3b230b323 100644 --- a/volatility/framework/contexts/__init__.py +++ b/volatility/framework/contexts/__init__.py @@ -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: diff --git a/volatility/framework/exceptions.py b/volatility/framework/exceptions.py index d490af9d0..646b0e7a6 100644 --- a/volatility/framework/exceptions.py +++ b/volatility/framework/exceptions.py @@ -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): diff --git a/volatility/framework/interfaces/__init__.py b/volatility/framework/interfaces/__init__.py index b665108d9..ee3c6c990 100644 --- a/volatility/framework/interfaces/__init__.py +++ b/volatility/framework/interfaces/__init__.py @@ -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 diff --git a/volatility/framework/interfaces/automagic.py b/volatility/framework/interfaces/automagic.py index 496b65ca0..f8fd5a295 100644 --- a/volatility/framework/interfaces/automagic.py +++ b/volatility/framework/interfaces/automagic.py @@ -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. diff --git a/volatility/framework/interfaces/configuration.py b/volatility/framework/interfaces/configuration.py index 69d5adf1c..8cdc63e4b 100644 --- a/volatility/framework/interfaces/configuration.py +++ b/volatility/framework/interfaces/configuration.py @@ -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 """ diff --git a/volatility/framework/interfaces/context.py b/volatility/framework/interfaces/context.py index 9c5a97383..08c0f29d8 100644 --- a/volatility/framework/interfaces/context.py +++ b/volatility/framework/interfaces/context.py @@ -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.""" diff --git a/volatility/framework/interfaces/layers.py b/volatility/framework/interfaces/layers.py index b6e943cca..cdd097bf3 100644 --- a/volatility/framework/interfaces/layers.py +++ b/volatility/framework/interfaces/layers.py @@ -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 diff --git a/volatility/framework/interfaces/objects.py b/volatility/framework/interfaces/objects.py index 1056d431d..5eb5c1a1e 100644 --- a/volatility/framework/interfaces/objects.py +++ b/volatility/framework/interfaces/objects.py @@ -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.""" diff --git a/volatility/framework/interfaces/plugins.py b/volatility/framework/interfaces/plugins.py index 1160ef58a..61797f619 100644 --- a/volatility/framework/interfaces/plugins.py +++ b/volatility/framework/interfaces/plugins.py @@ -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 diff --git a/volatility/framework/interfaces/renderers.py b/volatility/framework/interfaces/renderers.py index 1ae782325..3cac324f7 100644 --- a/volatility/framework/interfaces/renderers.py +++ b/volatility/framework/interfaces/renderers.py @@ -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 """ diff --git a/volatility/framework/interfaces/symbols.py b/volatility/framework/interfaces/symbols.py index 5e5fa0177..ab2de32de 100644 --- a/volatility/framework/interfaces/symbols.py +++ b/volatility/framework/interfaces/symbols.py @@ -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 diff --git a/volatility/framework/layers/crash.py b/volatility/framework/layers/crash.py index 9847df616..afe7d508e 100644 --- a/volatility/framework/layers/crash.py +++ b/volatility/framework/layers/crash.py @@ -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 = [] diff --git a/volatility/framework/layers/intel.py b/volatility/framework/layers/intel.py index ac9629a53..9430c39a8 100644 --- a/volatility/framework/layers/intel.py +++ b/volatility/framework/layers/intel.py @@ -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 = " 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 = " 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)) diff --git a/volatility/framework/layers/lime.py b/volatility/framework/layers/lime.py index a91a17810..732998e59 100644 --- a/volatility/framework/layers/lime.py +++ b/volatility/framework/layers/lime.py @@ -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 diff --git a/volatility/framework/layers/linear.py b/volatility/framework/layers/linear.py index be9323c22..401c303d8 100644 --- a/volatility/framework/layers/linear.py +++ b/volatility/framework/layers/linear.py @@ -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): diff --git a/volatility/framework/layers/msf.py b/volatility/framework/layers/msf.py index 8e9517bbc..7219723d9 100644 --- a/volatility/framework/layers/msf.py +++ b/volatility/framework/layers/msf.py @@ -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 diff --git a/volatility/framework/layers/physical.py b/volatility/framework/layers/physical.py index 24195c32c..5bbc8129a 100644 --- a/volatility/framework/layers/physical.py +++ b/volatility/framework/layers/physical.py @@ -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 diff --git a/volatility/framework/layers/registry.py b/volatility/framework/layers/registry.py index 005f50dda..8441d4ce9 100644 --- a/volatility/framework/layers/registry.py +++ b/volatility/framework/layers/registry.py @@ -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 diff --git a/volatility/framework/layers/resources.py b/volatility/framework/layers/resources.py index 124280504..e89f03ea6 100644 --- a/volatility/framework/layers/resources.py +++ b/volatility/framework/layers/resources.py @@ -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': diff --git a/volatility/framework/layers/scanners/__init__.py b/volatility/framework/layers/scanners/__init__.py index a0cdee75d..0b60ae6ff 100644 --- a/volatility/framework/layers/scanners/__init__.py +++ b/volatility/framework/layers/scanners/__init__.py @@ -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 diff --git a/volatility/framework/layers/scanners/multiregexp.py b/volatility/framework/layers/scanners/multiregexp.py index 28761eeb7..52fa2673c 100644 --- a/volatility/framework/layers/scanners/multiregexp.py +++ b/volatility/framework/layers/scanners/multiregexp.py @@ -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] diff --git a/volatility/framework/layers/scanners/wumanber.py b/volatility/framework/layers/scanners/wumanber.py index b5c0c26f3..cee1afdae 100644 --- a/volatility/framework/layers/scanners/wumanber.py +++ b/volatility/framework/layers/scanners/wumanber.py @@ -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: diff --git a/volatility/framework/layers/segmented.py b/volatility/framework/layers/segmented.py index d8a4be5f4..fab57d788 100644 --- a/volatility/framework/layers/segmented.py +++ b/volatility/framework/layers/segmented.py @@ -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 diff --git a/volatility/framework/layers/vmware.py b/volatility/framework/layers/vmware.py index a1ef5c582..25e3c22cf 100644 --- a/volatility/framework/layers/vmware.py +++ b/volatility/framework/layers/vmware.py @@ -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 diff --git a/volatility/framework/objects/__init__.py b/volatility/framework/objects/__init__.py index 25961ff1c..2f3b6ca0a 100644 --- a/volatility/framework/objects/__init__.py +++ b/volatility/framework/objects/__init__.py @@ -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): diff --git a/volatility/framework/objects/templates.py b/volatility/framework/objects/templates.py index 9f37d2175..e01e0a84a 100644 --- a/volatility/framework/objects/templates.py +++ b/volatility/framework/objects/templates.py @@ -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)) diff --git a/volatility/framework/objects/utility.py b/volatility/framework/objects/utility.py index e5970a2c6..ef372fb5f 100644 --- a/volatility/framework/objects/utility.py +++ b/volatility/framework/objects/utility.py @@ -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) diff --git a/volatility/framework/plugins/__init__.py b/volatility/framework/plugins/__init__.py index bd4a16d1b..b8dcaa28a 100644 --- a/volatility/framework/plugins/__init__.py +++ b/volatility/framework/plugins/__init__.py @@ -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 diff --git a/volatility/framework/plugins/configwriter.py b/volatility/framework/plugins/configwriter.py index 32e837217..816d98337 100644 --- a/volatility/framework/plugins/configwriter.py +++ b/volatility/framework/plugins/configwriter.py @@ -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]: diff --git a/volatility/framework/plugins/layerwriter.py b/volatility/framework/plugins/layerwriter.py index 4ea6bd9aa..9669edae4 100644 --- a/volatility/framework/plugins/layerwriter.py +++ b/volatility/framework/plugins/layerwriter.py @@ -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 diff --git a/volatility/framework/plugins/linux/__init__.py b/volatility/framework/plugins/linux/__init__.py index 48174bfbb..38f6089de 100644 --- a/volatility/framework/plugins/linux/__init__.py +++ b/volatility/framework/plugins/linux/__init__.py @@ -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 """ diff --git a/volatility/framework/plugins/linux/bash.py b/volatility/framework/plugins/linux/bash.py index 540afdfde..43ee6982d 100644 --- a/volatility/framework/plugins/linux/bash.py +++ b/volatility/framework/plugins/linux/bash.py @@ -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]: diff --git a/volatility/framework/plugins/linux/check_afinfo.py b/volatility/framework/plugins/linux/check_afinfo.py index 5e57992f2..7a374732f 100644 --- a/volatility/framework/plugins/linux/check_afinfo.py +++ b/volatility/framework/plugins/linux/check_afinfo.py @@ -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]: diff --git a/volatility/framework/plugins/linux/check_syscall.py b/volatility/framework/plugins/linux/check_syscall.py index 80eeb7d87..ad61e193e 100644 --- a/volatility/framework/plugins/linux/check_syscall.py +++ b/volatility/framework/plugins/linux/check_syscall.py @@ -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: diff --git a/volatility/framework/plugins/linux/elfs.py b/volatility/framework/plugins/linux/elfs.py index 0cb3bab8d..e9ab8bfeb 100644 --- a/volatility/framework/plugins/linux/elfs.py +++ b/volatility/framework/plugins/linux/elfs.py @@ -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]: diff --git a/volatility/framework/plugins/linux/lsmod.py b/volatility/framework/plugins/linux/lsmod.py index d8f581118..972cd6809 100644 --- a/volatility/framework/plugins/linux/lsmod.py +++ b/volatility/framework/plugins/linux/lsmod.py @@ -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) diff --git a/volatility/framework/plugins/linux/lsof.py b/volatility/framework/plugins/linux/lsof.py index dd0c1feee..ad4550b9f 100644 --- a/volatility/framework/plugins/linux/lsof.py +++ b/volatility/framework/plugins/linux/lsof.py @@ -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]: diff --git a/volatility/framework/plugins/linux/malfind.py b/volatility/framework/plugins/linux/malfind.py index ac1985ab3..a57ec8f1a 100644 --- a/volatility/framework/plugins/linux/malfind.py +++ b/volatility/framework/plugins/linux/malfind.py @@ -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: diff --git a/volatility/framework/plugins/linux/proc.py b/volatility/framework/plugins/linux/proc.py index 535c95b3f..e38747d81 100644 --- a/volatility/framework/plugins/linux/proc.py +++ b/volatility/framework/plugins/linux/proc.py @@ -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): diff --git a/volatility/framework/plugins/linux/pslist.py b/volatility/framework/plugins/linux/pslist.py index 409ec97d8..a9d8cdfdf 100644 --- a/volatility/framework/plugins/linux/pslist.py +++ b/volatility/framework/plugins/linux/pslist.py @@ -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) diff --git a/volatility/framework/plugins/linux/pstree.py b/volatility/framework/plugins/linux/pstree.py index 1ac588c19..a98eb0041 100644 --- a/volatility/framework/plugins/linux/pstree.py +++ b/volatility/framework/plugins/linux/pstree.py @@ -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 diff --git a/volatility/framework/plugins/mac/bash.py b/volatility/framework/plugins/mac/bash.py index b8f924be7..806198a84 100644 --- a/volatility/framework/plugins/mac/bash.py +++ b/volatility/framework/plugins/mac/bash.py @@ -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): diff --git a/volatility/framework/plugins/mac/check_syscall.py b/volatility/framework/plugins/mac/check_syscall.py index 03085ead6..7d1d63dba 100644 --- a/volatility/framework/plugins/mac/check_syscall.py +++ b/volatility/framework/plugins/mac/check_syscall.py @@ -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]: diff --git a/volatility/framework/plugins/mac/check_sysctl.py b/volatility/framework/plugins/mac/check_sysctl.py index 6e1dcb80c..80301873e 100644 --- a/volatility/framework/plugins/mac/check_sysctl.py +++ b/volatility/framework/plugins/mac/check_sysctl.py @@ -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]: diff --git a/volatility/framework/plugins/mac/check_trap_table.py b/volatility/framework/plugins/mac/check_trap_table.py index e8ab1fcf8..76d53d347 100644 --- a/volatility/framework/plugins/mac/check_trap_table.py +++ b/volatility/framework/plugins/mac/check_trap_table.py @@ -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]: diff --git a/volatility/framework/plugins/mac/lsmod.py b/volatility/framework/plugins/mac/lsmod.py index 6271103f3..0796832a4 100644 --- a/volatility/framework/plugins/mac/lsmod.py +++ b/volatility/framework/plugins/mac/lsmod.py @@ -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) diff --git a/volatility/framework/plugins/mac/lsof.py b/volatility/framework/plugins/mac/lsof.py index 1973851c0..85f412335 100644 --- a/volatility/framework/plugins/mac/lsof.py +++ b/volatility/framework/plugins/mac/lsof.py @@ -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): diff --git a/volatility/framework/plugins/mac/malfind.py b/volatility/framework/plugins/mac/malfind.py index b5a7dd564..c73ecd17c 100644 --- a/volatility/framework/plugins/mac/malfind.py +++ b/volatility/framework/plugins/mac/malfind.py @@ -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: diff --git a/volatility/framework/plugins/mac/netstat.py b/volatility/framework/plugins/mac/netstat.py index d01aaa9fa..4b0e9e630 100644 --- a/volatility/framework/plugins/mac/netstat.py +++ b/volatility/framework/plugins/mac/netstat.py @@ -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): diff --git a/volatility/framework/plugins/mac/proc_maps.py b/volatility/framework/plugins/mac/proc_maps.py index 9dad93cd9..2ab16b074 100644 --- a/volatility/framework/plugins/mac/proc_maps.py +++ b/volatility/framework/plugins/mac/proc_maps.py @@ -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): diff --git a/volatility/framework/plugins/mac/psaux.py b/volatility/framework/plugins/mac/psaux.py index b4aaa0ac1..08485d588 100644 --- a/volatility/framework/plugins/mac/psaux.py +++ b/volatility/framework/plugins/mac/psaux.py @@ -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]: diff --git a/volatility/framework/plugins/mac/pslist.py b/volatility/framework/plugins/mac/pslist.py index 575534a5d..424bf2cb6 100644 --- a/volatility/framework/plugins/mac/pslist.py +++ b/volatility/framework/plugins/mac/pslist.py @@ -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) diff --git a/volatility/framework/plugins/mac/pstree.py b/volatility/framework/plugins/mac/pstree.py index 400276ed3..ff0908a4f 100644 --- a/volatility/framework/plugins/mac/pstree.py +++ b/volatility/framework/plugins/mac/pstree.py @@ -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 diff --git a/volatility/framework/plugins/mac/tasks.py b/volatility/framework/plugins/mac/tasks.py index 4620ce125..076e19f5e 100644 --- a/volatility/framework/plugins/mac/tasks.py +++ b/volatility/framework/plugins/mac/tasks.py @@ -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) diff --git a/volatility/framework/plugins/mac/trustedbsd.py b/volatility/framework/plugins/mac/trustedbsd.py index 599a04e4d..8454a086b 100644 --- a/volatility/framework/plugins/mac/trustedbsd.py +++ b/volatility/framework/plugins/mac/trustedbsd.py @@ -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]: diff --git a/volatility/framework/plugins/timeliner.py b/volatility/framework/plugins/timeliner.py index 42afc0c74..d0f0d9c47 100644 --- a/volatility/framework/plugins/timeliner.py +++ b/volatility/framework/plugins/timeliner.py @@ -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 [] diff --git a/volatility/framework/plugins/windows/__init__.py b/volatility/framework/plugins/windows/__init__.py index 1bf538bb7..ed7b35222 100644 --- a/volatility/framework/plugins/windows/__init__.py +++ b/volatility/framework/plugins/windows/__init__.py @@ -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 """ diff --git a/volatility/framework/plugins/windows/cmdline.py b/volatility/framework/plugins/windows/cmdline.py index 0da7a81fc..f05056d24 100644 --- a/volatility/framework/plugins/windows/cmdline.py +++ b/volatility/framework/plugins/windows/cmdline.py @@ -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]: diff --git a/volatility/framework/plugins/windows/dlldump.py b/volatility/framework/plugins/windows/dlldump.py index 1c0685d1f..1ad46bd01 100644 --- a/volatility/framework/plugins/windows/dlldump.py +++ b/volatility/framework/plugins/windows/dlldump.py @@ -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]: diff --git a/volatility/framework/plugins/windows/dlllist.py b/volatility/framework/plugins/windows/dlllist.py index 219c39048..3aa61200c 100644 --- a/volatility/framework/plugins/windows/dlllist.py +++ b/volatility/framework/plugins/windows/dlllist.py @@ -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]: diff --git a/volatility/framework/plugins/windows/driverirp.py b/volatility/framework/plugins/windows/driverirp.py index ee4218d33..ea05f0ca7 100644 --- a/volatility/framework/plugins/windows/driverirp.py +++ b/volatility/framework/plugins/windows/driverirp.py @@ -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): diff --git a/volatility/framework/plugins/windows/driverscan.py b/volatility/framework/plugins/windows/driverscan.py index de5e75ea1..e23b871a5 100644 --- a/volatility/framework/plugins/windows/driverscan.py +++ b/volatility/framework/plugins/windows/driverscan.py @@ -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']) diff --git a/volatility/framework/plugins/windows/filescan.py b/volatility/framework/plugins/windows/filescan.py index a6afc8955..6b139da53 100644 --- a/volatility/framework/plugins/windows/filescan.py +++ b/volatility/framework/plugins/windows/filescan.py @@ -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']) diff --git a/volatility/framework/plugins/windows/handles.py b/volatility/framework/plugins/windows/handles.py index fc632c2b4..ee35de483 100644 --- a/volatility/framework/plugins/windows/handles.py +++ b/volatility/framework/plugins/windows/handles.py @@ -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'] diff --git a/volatility/framework/plugins/windows/info.py b/volatility/framework/plugins/windows/info.py index e08eb4e7e..2962af3c0 100644 --- a/volatility/framework/plugins/windows/info.py +++ b/volatility/framework/plugins/windows/info.py @@ -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]: diff --git a/volatility/framework/plugins/windows/malfind.py b/volatility/framework/plugins/windows/malfind.py index 3c0f07ef7..c23fcd0f9 100644 --- a/volatility/framework/plugins/windows/malfind.py +++ b/volatility/framework/plugins/windows/malfind.py @@ -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() diff --git a/volatility/framework/plugins/windows/moddump.py b/volatility/framework/plugins/windows/moddump.py index a86fe0c2b..a8b81090a 100644 --- a/volatility/framework/plugins/windows/moddump.py +++ b/volatility/framework/plugins/windows/moddump.py @@ -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: - 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: of layer names - base_address: 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: diff --git a/volatility/framework/plugins/windows/modscan.py b/volatility/framework/plugins/windows/modscan.py index 69ee5789c..8718d506d 100644 --- a/volatility/framework/plugins/windows/modscan.py +++ b/volatility/framework/plugins/windows/modscan.py @@ -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']) diff --git a/volatility/framework/plugins/windows/modules.py b/volatility/framework/plugins/windows/modules.py index 24ab1909f..9f48bc03d 100644 --- a/volatility/framework/plugins/windows/modules.py +++ b/volatility/framework/plugins/windows/modules.py @@ -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) diff --git a/volatility/framework/plugins/windows/mutantscan.py b/volatility/framework/plugins/windows/mutantscan.py index 53bc72412..54545086b 100644 --- a/volatility/framework/plugins/windows/mutantscan.py +++ b/volatility/framework/plugins/windows/mutantscan.py @@ -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']) diff --git a/volatility/framework/plugins/windows/poolscanner.py b/volatility/framework/plugins/windows/poolscanner.py index 67610ec60..66482e9fc 100644 --- a/volatility/framework/plugins/windows/poolscanner.py +++ b/volatility/framework/plugins/windows/poolscanner.py @@ -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: diff --git a/volatility/framework/plugins/windows/procdump.py b/volatility/framework/plugins/windows/procdump.py index 510de420d..b641b04b5 100644 --- a/volatility/framework/plugins/windows/procdump.py +++ b/volatility/framework/plugins/windows/procdump.py @@ -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]: diff --git a/volatility/framework/plugins/windows/pslist.py b/volatility/framework/plugins/windows/pslist.py index e2c0ca171..517a314d6 100644 --- a/volatility/framework/plugins/windows/pslist.py +++ b/volatility/framework/plugins/windows/pslist.py @@ -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'] diff --git a/volatility/framework/plugins/windows/psscan.py b/volatility/framework/plugins/windows/psscan.py index a4c186b40..d94f76c93 100644 --- a/volatility/framework/plugins/windows/psscan.py +++ b/volatility/framework/plugins/windows/psscan.py @@ -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']) diff --git a/volatility/framework/plugins/windows/pstree.py b/volatility/framework/plugins/windows/pstree.py index 019847383..050436bc0 100644 --- a/volatility/framework/plugins/windows/pstree.py +++ b/volatility/framework/plugins/windows/pstree.py @@ -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): diff --git a/volatility/framework/plugins/windows/registry/__init__.py b/volatility/framework/plugins/windows/registry/__init__.py index 7e75bb6a3..8a81bb322 100644 --- a/volatility/framework/plugins/windows/registry/__init__.py +++ b/volatility/framework/plugins/windows/registry/__init__.py @@ -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 """ diff --git a/volatility/framework/plugins/windows/registry/hivelist.py b/volatility/framework/plugins/windows/registry/hivelist.py index fdfee62c7..bea604a40 100644 --- a/volatility/framework/plugins/windows/registry/hivelist.py +++ b/volatility/framework/plugins/windows/registry/hivelist.py @@ -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'] diff --git a/volatility/framework/plugins/windows/registry/hivescan.py b/volatility/framework/plugins/windows/registry/hivescan.py index f8eba981f..0eb105a92 100644 --- a/volatility/framework/plugins/windows/registry/hivescan.py +++ b/volatility/framework/plugins/windows/registry/hivescan.py @@ -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']) diff --git a/volatility/framework/plugins/windows/registry/printkey.py b/volatility/framework/plugins/windows/registry/printkey.py index eef046dcf..8d2c578cd 100644 --- a/volatility/framework/plugins/windows/registry/printkey.py +++ b/volatility/framework/plugins/windows/registry/printkey.py @@ -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 = [ diff --git a/volatility/framework/plugins/windows/registry/userassist.py b/volatility/framework/plugins/windows/registry/userassist.py index 10ddb32ef..e70db2c2e 100644 --- a/volatility/framework/plugins/windows/registry/userassist.py +++ b/volatility/framework/plugins/windows/registry/userassist.py @@ -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" diff --git a/volatility/framework/plugins/windows/ssdt.py b/volatility/framework/plugins/windows/ssdt.py index 75943344a..9acf4902b 100644 --- a/volatility/framework/plugins/windows/ssdt.py +++ b/volatility/framework/plugins/windows/ssdt.py @@ -17,7 +17,7 @@ from volatility.plugins.windows import modules class SSDT(plugins.PluginInterface): - """Lists the system call table""" + """Lists the system call table.""" _version = (1, 0, 0) @@ -33,7 +33,16 @@ class SSDT(plugins.PluginInterface): @classmethod def build_module_collection(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str) -> contexts.ModuleCollection: - """Builds a collection of modules""" + """Builds a collection of modules. + + 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 Module collection of available modules based on `Modules.list_modules` + """ mods = modules.Modules.list_modules(context, layer_name, symbol_table) context_modules = [] diff --git a/volatility/framework/plugins/windows/strings.py b/volatility/framework/plugins/windows/strings.py index 0a17c246e..cf59097c3 100644 --- a/volatility/framework/plugins/windows/strings.py +++ b/volatility/framework/plugins/windows/strings.py @@ -33,7 +33,7 @@ class Strings(interfaces.plugins.PluginInterface): self._generator()) def _generator(self) -> Generator[Tuple, None, None]: - """Generates results from a strings file""" + """Generates results from a strings file.""" revmap = self.generate_mapping(self.config['primary']) accessor = resources.ResourceAccessor() @@ -52,7 +52,14 @@ class Strings(interfaces.plugins.PluginInterface): @staticmethod def _parse_line(line: bytes) -> Tuple[int, bytes]: - """Parses a single line from a strings file""" + """Parses a single line from a strings file. + + Args: + line: bytes of the line of a strings file (an offset and a string) + + Returns: + Tuple of the offset and the string found at that offset + """ pattern = re.compile(rb"(?:\W*)([0-9]+)(?:\W*)(\w[\w\W]+)") match = pattern.search(line) if not match: @@ -61,7 +68,15 @@ class Strings(interfaces.plugins.PluginInterface): return int(offset), string def generate_mapping(self, layer_name: str) -> Dict[int, Set[Tuple[str, int]]]: - """Creates a reverse mapping between virtual addresses and physical addresses""" + """Creates a reverse mapping between virtual addresses and physical + addresses. + + Args: + layer_name: the layer to map against the string lines + + Returns: + A mapping of virtual offsets to strings and physical offsets + """ layer = self._context.layers[layer_name] reverse_map = dict() # type: Dict[int, Set[Tuple[str, int]]] if isinstance(layer, intel.Intel): diff --git a/volatility/framework/plugins/windows/svcscan.py b/volatility/framework/plugins/windows/svcscan.py index c551818ca..9881853c6 100644 --- a/volatility/framework/plugins/windows/svcscan.py +++ b/volatility/framework/plugins/windows/svcscan.py @@ -17,7 +17,7 @@ vollog = logging.getLogger(__name__) class SvcScan(interfaces.plugins.PluginInterface): - """Scans for windows services""" + """Scans for windows services.""" is_vista_or_later = poolscanner.os_distinguisher( version_check = lambda x: x >= (6, 0), fallback_checks = [("KdCopyDataBlock", None, True)]) @@ -60,7 +60,17 @@ class SvcScan(interfaces.plugins.PluginInterface): @staticmethod def create_service_table(context: interfaces.context.ContextInterface, symbol_table: str, config_path: str) -> str: + """Constructs a symbol table containing the symbols for services + depending upon the operating system in use. + Args: + context: The context to retrieve required elements (layers, symbol tables) from + symbol_table: The name of the table containing the kernel symbols + config_path: The configuration path for any settings required by the new table + + Returns: + A symbol table containing the symbols necessary for services + """ native_types = context.symbol_space[symbol_table].natives is_64bit = symbols.symbol_table_is_64bit(context, symbol_table) diff --git a/volatility/framework/plugins/windows/symlinkscan.py b/volatility/framework/plugins/windows/symlinkscan.py index 84d87d67b..b6e768d6a 100644 --- a/volatility/framework/plugins/windows/symlinkscan.py +++ b/volatility/framework/plugins/windows/symlinkscan.py @@ -13,7 +13,7 @@ from volatility.plugins import timeliner class SymlinkScan(plugins.PluginInterface, timeliner.TimeLinerInterface): - """Scans for links present in a particular windows memory image""" + """Scans for links present in a particular windows memory image.""" @classmethod def get_requirements(cls): @@ -29,7 +29,16 @@ class SymlinkScan(plugins.PluginInterface, timeliner.TimeLinerInterface): layer_name: str, symbol_table: str) -> \ Iterable[interfaces.objects.ObjectInterface]: - """Scans for links using the poolscanner module and constraints""" + """Scans for links 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 symlink objects found by scanning memory for the Symlink pool signatures + """ constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'Sym\xe2', b'Symb']) diff --git a/volatility/framework/plugins/windows/vaddump.py b/volatility/framework/plugins/windows/vaddump.py index da42a4e7b..825a88a9c 100644 --- a/volatility/framework/plugins/windows/vaddump.py +++ b/volatility/framework/plugins/windows/vaddump.py @@ -17,7 +17,7 @@ vollog = logging.getLogger(__name__) class VadDump(interfaces_plugins.PluginInterface): - """Dumps process memory ranges""" + """Dumps process memory ranges.""" @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: diff --git a/volatility/framework/plugins/windows/vadinfo.py b/volatility/framework/plugins/windows/vadinfo.py index d24fa222d..216c40534 100644 --- a/volatility/framework/plugins/windows/vadinfo.py +++ b/volatility/framework/plugins/windows/vadinfo.py @@ -32,7 +32,7 @@ winnt_protections = { class VadInfo(interfaces.plugins.PluginInterface): - """Lists process memory ranges""" + """Lists process memory ranges.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -57,23 +57,38 @@ class VadInfo(interfaces.plugins.PluginInterface): ] @classmethod - def protect_values(cls, context: interfaces.context.ContextInterface, virtual_layer: str, - nt_symbols: str) -> Iterable[int]: - """Look up the array of memory protection constants from the memory sample. - These don't change often, but if they do in the future, then finding them - # dynamically versus hard-coding here will ensure we parse them properly.""" + def protect_values(cls, context: interfaces.context.ContextInterface, layer_name: str, + symbol_table: str) -> Iterable[int]: + """Look up the array of memory protection constants from the memory + sample. These don't change often, but if they do in the future, then + finding them dynamically versus hard-coding here will ensure we parse + them properly. - kvo = context.layers[virtual_layer].config["kernel_virtual_offset"] - ntkrnlmp = context.module(nt_symbols, layer_name = virtual_layer, offset = kvo) + 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 + """ + + kvo = context.layers[layer_name].config["kernel_virtual_offset"] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) addr = ntkrnlmp.get_symbol("MmProtectToValue").address values = ntkrnlmp.object(object_type = "array", offset = addr, subtype = ntkrnlmp.get_type("int"), count = 32) return values # type: ignore @classmethod def list_vads(cls, proc: interfaces.objects.ObjectInterface, - filter_func: Callable[[int], bool] = lambda _: False) -> \ + filter_func: Callable[[interfaces.objects.ObjectInterface], bool] = lambda _: False) -> \ Generator[interfaces.objects.ObjectInterface, None, None]: + """Lists the Virtual Address Descriptors of a specific process. + Args: + proc: _EPROCESS object from which to list the VADs + filter_func: Function to take a virtual address descriptor value and return True if it should be filtered out + + Returns: + A list of virtual address descriptors based on the process and filtered based on the filter function + """ for vad in proc.get_vad_root().traverse(): if not filter_func(vad): yield vad diff --git a/volatility/framework/plugins/windows/vadyarascan.py b/volatility/framework/plugins/windows/vadyarascan.py index 43e33bdb8..2fc3c372a 100644 --- a/volatility/framework/plugins/windows/vadyarascan.py +++ b/volatility/framework/plugins/windows/vadyarascan.py @@ -77,7 +77,15 @@ class VadYaraScan(interfaces.plugins.PluginInterface): @staticmethod def get_vad_maps(task: interfaces.objects.ObjectInterface) -> Iterable[Tuple[int, int]]: + """Creates a map of start/end addresses within a virtual address + descriptor tree. + Args: + task: The EPROCESS object of which to traverse the vad tree + + Returns: + An iterable of tuples containing start and end addresses for each descriptor + """ vad_root = task.get_vad_root() for vad in vad_root.traverse(): end = vad.get_end() diff --git a/volatility/framework/plugins/windows/verinfo.py b/volatility/framework/plugins/windows/verinfo.py index e108b3db4..93dc21d78 100644 --- a/volatility/framework/plugins/windows/verinfo.py +++ b/volatility/framework/plugins/windows/verinfo.py @@ -26,7 +26,7 @@ except ImportError: class VerInfo(interfaces_plugins.PluginInterface): - """Lists version information from PE files""" + """Lists version information from PE files.""" @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -42,7 +42,7 @@ class VerInfo(interfaces_plugins.PluginInterface): @classmethod def get_version_information(cls, context: interfaces.context.ContextInterface, pe_table_name: str, layer_name: str, base_address: int) -> Tuple[int, int, int, int]: - """Get File and Product version information from PE files + """Get File and Product version information from PE files. Args: context: volatility context on which to operate @@ -85,7 +85,8 @@ class VerInfo(interfaces_plugins.PluginInterface): def _generator(self, procs: Generator[interfaces.objects.ObjectInterface, None, None], mods: Generator[interfaces.objects.ObjectInterface, None, None], session_layers: Generator[str, None, None]): - """Generates a list of PE file version info for processes, dlls, and modules. + """Generates a list of PE file version info for processes, dlls, and + modules. Args: procs: of processes diff --git a/volatility/framework/plugins/windows/virtmap.py b/volatility/framework/plugins/windows/virtmap.py index b60e358d3..8dabfb917 100644 --- a/volatility/framework/plugins/windows/virtmap.py +++ b/volatility/framework/plugins/windows/virtmap.py @@ -13,7 +13,7 @@ vollog = logging.getLogger(__name__) class VirtMap(interfaces.plugins.PluginInterface): - """Lists virtual mapped sections""" + """Lists virtual mapped sections.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -35,7 +35,7 @@ class VirtMap(interfaces.plugins.PluginInterface): @classmethod def determine_map(cls, module: interfaces.context.ModuleInterface) -> \ Dict[int, List[Tuple[int, int]]]: - """Returns the virtual map from a windows kernel module""" + """Returns the virtual map from a windows kernel module.""" result = {} system_va_type = module.get_enumeration('_MI_SYSTEM_VA_TYPE') large_page_size = (module.context.layers[module.layer_name].page_size ** 2) // module.get_type("_MMPTE").size diff --git a/volatility/framework/plugins/yarascan.py b/volatility/framework/plugins/yarascan.py index ae2950e4e..2e4ed2ff0 100644 --- a/volatility/framework/plugins/yarascan.py +++ b/volatility/framework/plugins/yarascan.py @@ -34,7 +34,8 @@ class YaraScanner(interfaces.layers.ScannerInterface): class YaraScan(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.""" @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: diff --git a/volatility/framework/renderers/__init__.py b/volatility/framework/renderers/__init__.py index f4c6acb81..4c3ab29eb 100644 --- a/volatility/framework/renderers/__init__.py +++ b/volatility/framework/renderers/__init__.py @@ -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 # -"""Renderers +"""Renderers. -Renderers display the unified output format in some manner (be it text or file or graphical output""" +Renderers display the unified output format in some manner (be it text +or file or graphical output +""" import collections import datetime from typing import Any, Callable, Iterable, List, Optional, Tuple, TypeVar, Union @@ -13,29 +15,35 @@ from volatility.framework.interfaces import renderers class UnreadableValue(interfaces.renderers.BaseAbsentValue): - """Class that represents values which are empty because the data cannot be read""" + """Class that represents values which are empty because the data cannot be + read.""" class UnparsableValue(interfaces.renderers.BaseAbsentValue): - """Class that represents values which are empty because the data cannot be interpreted correctly""" + """Class that represents values which are empty because the data cannot be + interpreted correctly.""" class NotApplicableValue(interfaces.renderers.BaseAbsentValue): - """Class that represents values which are empty because they don't make sense for this node""" + """Class that represents values which are empty because they don't make + sense for this node.""" class NotAvailableValue(interfaces.renderers.BaseAbsentValue): - """Class that represents values which cannot be provided now (but might in a future run) + """Class that represents values which cannot be provided now (but might in + a future run) - This might occur when information packed with volatility (such as symbol information) is not available, - but a future version or a different run may later have that information available (ie, it could be applicable, - but we can't get it and it's not because it's unreadable or unparsable). - Unreadable and Unparsable should be used in preference, and only if neither fits should this be used. + This might occur when information packed with volatility (such as + symbol information) is not available, but a future version or a + different run may later have that information available (ie, it + could be applicable, but we can't get it and it's not because it's + unreadable or unparsable). Unreadable and Unparsable should be used + in preference, and only if neither fits should this be used. """ class TreeNode(interfaces.renderers.TreeNode): - """Class representing a particular node in a tree grid""" + """Class representing a particular node in a tree grid.""" def __init__(self, path: str, treegrid: 'TreeGrid', parent: Optional['TreeNode'], values: List[interfaces.renderers.BaseTypes]) -> None: @@ -57,7 +65,8 @@ class TreeNode(interfaces.renderers.TreeNode): return len(self._treegrid.children(self)) def _validate_values(self, values: List[interfaces.renderers.BaseTypes]) -> None: - """A function for raising exceptions if a given set of values is invalid according to the column properties.""" + """A function for raising exceptions if a given set of values is + invalid according to the column properties.""" if not (isinstance(values, collections.Sequence) and len(values) == len(self._treegrid.columns)): raise TypeError( "Values must be a list of objects made up of simple types and number the same as the columns") @@ -74,32 +83,36 @@ class TreeNode(interfaces.renderers.TreeNode): @property def values(self) -> Iterable[interfaces.renderers.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.""" return self._values @property 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. """ return self._path @property def parent(self) -> Optional['TreeNode']: - """Returns the parent node of this node or None""" + """Returns the parent node of this node or None.""" return self._parent @property def path_depth(self) -> int: - """Return the path depth of the current node""" + """Return the path depth of the current node.""" return len(self.path.split(TreeGrid.path_sep)) 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. """ components = self._path.split(TreeGrid.path_sep) changed = path.split(TreeGrid.path_sep) @@ -126,7 +139,7 @@ class TreeGrid(interfaces.renderers.TreeGrid): def __init__(self, columns: List[Tuple[str, interfaces.renderers.BaseTypes]], generator: Optional[Iterable[Tuple[int, Tuple]]]) -> 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, @@ -166,10 +179,11 @@ class TreeGrid(interfaces.renderers.TreeGrid): return output def populate(self, func: interfaces.renderers.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. """ accumulator = initial_accumulator if func is None: @@ -191,27 +205,28 @@ class TreeGrid(interfaces.renderers.TreeGrid): @property def populated(self): - """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.""" return self._populated @property def columns(self) -> List[interfaces.renderers.Column]: - """Returns the available columns and their ordering and types""" + """Returns the available columns and their ordering and types.""" return self._columns @property def row_count(self) -> int: - """Returns the number of rows populated""" + """Returns the number of rows populated.""" return self._row_count def children(self, node) -> List[interfaces.renderers.TreeNode]: - """Returns the subnodes of a particular node in order""" + """Returns the subnodes of a particular node in order.""" return [node for node, _ in self._find_children(node)] def _find_children(self, node): - """Returns the children list associated with a particular node + """Returns the children list associated with a particular node. - Returns None if the node does not exist + Returns None if the node does not exist """ children = self._children try: @@ -223,21 +238,22 @@ class TreeGrid(interfaces.renderers.TreeGrid): return children def values(self, node): - """Returns the values for a particular node + """Returns the values for a particular node. - The values returned are mutable, + The values returned are mutable, """ if node is None: raise ValueError("Node must be a valid node within the TreeGrid") return node.values def _append(self, parent, values): - """Adds a new node at the top level if parent is None, or under the parent node otherwise, after all other children.""" + """Adds a new node at the top level if parent is None, or under the + parent node otherwise, after all other children.""" children = self.children(parent) return self._insert(parent, len(children), values) def _insert(self, parent, position, values): - """Inserts an element into the tree at a specific position""" + """Inserts an element into the tree at a specific position.""" parent_path = "" children = self._find_children(parent) if parent is not None: @@ -250,11 +266,11 @@ class TreeGrid(interfaces.renderers.TreeGrid): return tree_item def is_ancestor(self, node, descendant): - """Returns true if descendent is a child, grandchild, etc of node""" + """Returns true if descendent is a child, grandchild, etc of node.""" return descendant.path.startswith(node.path) def max_depth(self): - """Returns the maximum depth of the tree""" + """Returns the maximum depth of the tree.""" return self.visit(None, lambda n, a: max(a, self.path_depth(n)), 0) _T = TypeVar("_T") @@ -266,15 +282,15 @@ class TreeGrid(interfaces.renderers.TreeGrid): sort_key: Optional[interfaces.renderers.ColumnSortKey] = 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). - We use the private _find_children function so that we don't have to re-traverse the tree - for every node we descend further down + We use the private _find_children function so that we don't have to re-traverse the tree + for every node we descend further down """ if not self.populated: self.populate() @@ -299,7 +315,7 @@ class TreeGrid(interfaces.renderers.TreeGrid): function: Callable, accumulator: _T, sort_key: Optional[interfaces.renderers.ColumnSortKey] = None) -> _T: - """Visits all the nodes in a tree, calling function on each one""" + """Visits all the nodes in a tree, calling function on each one.""" if list_of_children is not None: for n, children in list_of_children: accumulator = function(n, accumulator) @@ -327,7 +343,7 @@ class ColumnSortKey(interfaces.renderers.ColumnSortKey): self._index = _index def __call__(self, values: List[Any]) -> Any: - """The key function passed as the sort key""" + """The key function passed as the sort key.""" value = values[self._index] if isinstance(value, interfaces.renderers.BaseAbsentValue): if self._type == datetime.datetime: diff --git a/volatility/framework/renderers/conversion.py b/volatility/framework/renderers/conversion.py index 0769d4ce3..5995e62c5 100644 --- a/volatility/framework/renderers/conversion.py +++ b/volatility/framework/renderers/conversion.py @@ -92,16 +92,11 @@ def convert_port(port_as_integer): def convert_network_four_tuple(family, four_tuple): - """ - Converts the connection four_tuple: - (source ip, - source port, - dest ip, - dest port) + """Converts the connection four_tuple: (source ip, source port, dest ip, + dest port) - into their string equivalents. - IP addresses are expected as a tuple of unsigned shorts - Ports are converted to proper endianess as well + into their string equivalents. IP addresses are expected as a tuple + of unsigned shorts Ports are converted to proper endianess as well """ if family == socket.AF_INET: diff --git a/volatility/framework/renderers/format_hints.py b/volatility/framework/renderers/format_hints.py index c280449d5..442a0a643 100644 --- a/volatility/framework/renderers/format_hints.py +++ b/volatility/framework/renderers/format_hints.py @@ -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 # -"""The official list of format hints that text renderers and plugins can rely upon existing within the framework +"""The official list of format hints that text renderers and plugins can rely +upon existing within the framework. These hints allow a plugin to indicate how they would like data from a particular column to be represented. @@ -10,12 +11,15 @@ Text renderers should attempt to honour all hints provided in this module where class Bin(int): - """A class to indicate that the integer value should be represented as a binary value""" + """A class to indicate that the integer value should be represented as a + binary value.""" class Hex(int): - """A class to indicate that the integer value should be represented as a hexidecimal value""" + """A class to indicate that the integer value should be represented as a + hexidecimal value.""" class HexBytes(bytes): - """A class to indicate that the bytes should be display in an extended format showing hexadecimal and ascii printable display""" + """A class to indicate that the bytes should be display in an extended + format showing hexadecimal and ascii printable display.""" diff --git a/volatility/framework/symbols/__init__.py b/volatility/framework/symbols/__init__.py index bfbe4d6af..ed9ced979 100644 --- a/volatility/framework/symbols/__init__.py +++ b/volatility/framework/symbols/__init__.py @@ -23,10 +23,10 @@ class SymbolType(enum.Enum): class SymbolSpace(interfaces.symbols.SymbolSpaceInterface): - """Handles an ordered collection of SymbolTables + """Handles an ordered collection of SymbolTables. - This collection is ordered so that resolution of symbols can - proceed down through the ranks if a namespace isn't specified. + This collection is ordered so that resolution of symbols can proceed + down through the ranks if a namespace isn't specified. """ def __init__(self) -> None: @@ -37,7 +37,8 @@ class SymbolSpace(interfaces.symbols.SymbolSpaceInterface): self._resolved_symbols = {} # type: Dict[str, interfaces.objects.Template] 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.""" count = 1 while prefix + str(count) in self: count += 1 @@ -46,13 +47,13 @@ class SymbolSpace(interfaces.symbols.SymbolSpaceInterface): ### Symbol functions 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.""" for table in self._dict: for symbol_name in self._dict[table].get_symbols_by_type(type_name): yield table + constants.BANG + symbol_name def get_symbols_by_location(self, offset: int, size: int = 0, table_name: str = None) -> Iterable[str]: - """Returns all symbols that exist at a specific relative address""" + """Returns all symbols that exist at a specific relative address.""" table_list = self._dict.values() # type: Iterable[interfaces.symbols.BaseSymbolTableInterface] if table_name is not None: if table_name in self._dict: @@ -66,19 +67,19 @@ class SymbolSpace(interfaces.symbols.SymbolSpaceInterface): ### Space functions def __len__(self) -> int: - """Returns the number of tables within the space""" + """Returns the number of tables within the space.""" return len(self._dict) def __getitem__(self, i: str) -> Any: - """Returns a specific table from the space""" + """Returns a specific table from the space.""" return self._dict[i] def __iter__(self) -> Iterator[str]: - """Iterates through all available tables in the symbol space""" + """Iterates through all available tables in the symbol space.""" return iter(self._dict) def append(self, value: interfaces.symbols.BaseSymbolTableInterface) -> None: - """Adds a symbol_list to the end of the space""" + """Adds a symbol_list to the end of the space.""" if not isinstance(value, interfaces.symbols.BaseSymbolTableInterface): raise TypeError(value) if value.name in self._dict: @@ -86,7 +87,7 @@ class SymbolSpace(interfaces.symbols.SymbolSpaceInterface): self._dict[value.name] = value def remove(self, key: str) -> None: - """Removes a named symbol_list from the space""" + """Removes a named symbol_list from the space.""" # Reset the resolved list, since we're removing some symbols self._resolved = {} del self._dict[key] @@ -94,14 +95,14 @@ class SymbolSpace(interfaces.symbols.SymbolSpaceInterface): ### Resolution functions class _UnresolvedTemplate(objects.templates.ReferenceTemplate): - """Class to highlight when missing symbols are present + """Class to highlight when missing symbols are present. - This class is identical to a reference template, but differentiable by its classname. - It will output a debug log to indicate when it has been instantiated and with what name. + This class is identical to a reference template, but differentiable by its classname. + It will output a debug log to indicate when it has been instantiated and with what name. - This class is designed to be output ONLY as part of the SymbolSpace resolution system. - Individual SymbolTables that cannot resolve a symbol should still return a SymbolError to - indicate this failure in resolution. + This class is designed to be output ONLY as part of the SymbolSpace resolution system. + Individual SymbolTables that cannot resolve a symbol should still return a SymbolError to + indicate this failure in resolution. """ def __init__(self, type_name: str, **kwargs) -> None: @@ -109,7 +110,7 @@ class SymbolSpace(interfaces.symbols.SymbolSpaceInterface): super().__init__(type_name = type_name, **kwargs) def _weak_resolve(self, resolve_type: SymbolType, name: str) -> SymbolSpaceReturnType: - """Takes a symbol name and resolves it with ReferentialTemplates""" + """Takes a symbol name and resolves it with ReferentialTemplates.""" if resolve_type == SymbolType.TYPE: get_function = 'get_type' elif resolve_type == SymbolType.SYMBOL: @@ -130,7 +131,8 @@ class SymbolSpace(interfaces.symbols.SymbolSpaceInterface): raise exceptions.SymbolError("Malformed name: {}".format(name)) def _iterative_resolve(self, traverse_list): - """Iteratively resolves a type, populating linked child ReferenceTemplates with their properly resolved counterparts""" + """Iteratively resolves a type, populating linked child + ReferenceTemplates with their properly resolved counterparts.""" replacements = set() # Whole Symbols that still need traversing while traverse_list: @@ -157,10 +159,10 @@ class SymbolSpace(interfaces.symbols.SymbolSpaceInterface): parent.replace_child(child, self._resolved[child.vol.type_name]) def get_type(self, type_name: str) -> interfaces.objects.Template: - """Takes a symbol name and resolves it + """Takes a symbol name and resolves it. - This method ensures that all referenced templates (including self-referential templates) - are satisfied as ObjectTemplates + This method ensures that all referenced templates (including + self-referential templates) are satisfied as ObjectTemplates """ # Traverse down any resolutions if type_name not in self._resolved: @@ -171,7 +173,7 @@ class SymbolSpace(interfaces.symbols.SymbolSpaceInterface): return self._resolved[type_name] def get_symbol(self, symbol_name: str) -> interfaces.symbols.SymbolInterface: - """Look-up a symbol name across all the contained symbol spaces""" + """Look-up a symbol name across all the contained symbol spaces.""" retval = self._weak_resolve(SymbolType.SYMBOL, symbol_name) if symbol_name not in self._resolved_symbols and retval.type is not None: # Stash the old resolved type if it exists @@ -191,14 +193,15 @@ class SymbolSpace(interfaces.symbols.SymbolSpaceInterface): return retval def get_enumeration(self, enum_name: str) -> interfaces.objects.Template: - """Look-up a set of enumeration choices from a specific symbol table""" + """Look-up a set of enumeration choices from a specific symbol + table.""" retval = self._weak_resolve(SymbolType.ENUM, enum_name) if not isinstance(retval, interfaces.objects.Template): raise exceptions.SymbolError("Unresolvable Enumeration: {}".format(enum_name)) return retval def _membership(self, member_type: SymbolType, name: str) -> bool: - """Test for membership of a component within a table""" + """Test for membership of a component within a table.""" name_array = name.split(constants.BANG) if len(name_array) == 2: @@ -232,7 +235,8 @@ class SymbolSpace(interfaces.symbols.SymbolSpaceInterface): def mask_symbol_table(symbol_table: interfaces.symbols.SymbolTableInterface, address_mask: int = 0, table_aslr_shift: int = 0): - """Alters a symbol table, such that all symbols returned have their address masked by the address mask""" + """Alters a symbol table, such that all symbols returned have their address + masked by the address mask.""" original_get_symbol = symbol_table.get_symbol cached_symbols = {} # type: Dict[interfaces.symbols.SymbolInterface, interfaces.symbols.SymbolInterface] @@ -255,5 +259,6 @@ def mask_symbol_table(symbol_table: interfaces.symbols.SymbolTableInterface, def symbol_table_is_64bit(context: interfaces.context.ContextInterface, symbol_table_name: str) -> bool: - """Returns a boolean as to whether a particular symbol table within a context is 64-bit or not""" + """Returns a boolean as to whether a particular symbol table within a + context is 64-bit or not.""" return context.symbol_space.get_type(symbol_table_name + constants.BANG + "pointer").size == 8 diff --git a/volatility/framework/symbols/generic/__init__.py b/volatility/framework/symbols/generic/__init__.py index 01e7f709d..4c01b07e9 100644 --- a/volatility/framework/symbols/generic/__init__.py +++ b/volatility/framework/symbols/generic/__init__.py @@ -16,7 +16,7 @@ class GenericIntelProcess(objects.StructType): dtb: Union[int, interfaces.objects.ObjectInterface], config_prefix: str = None, preferred_name: str = None) -> str: - """Constructs a new layer based on the process's DirectoryTableBase""" + """Constructs a new layer based on the process's DirectoryTableBase.""" if config_prefix is None: # TODO: Ensure collisions can't happen by verifying the config_prefix is empty diff --git a/volatility/framework/symbols/intermed.py b/volatility/framework/symbols/intermed.py index b7726bd98..a9e5e9e78 100644 --- a/volatility/framework/symbols/intermed.py +++ b/volatility/framework/symbols/intermed.py @@ -60,9 +60,10 @@ def _construct_delegate_function(name: str, is_property: bool = False) -> Any: class IntermediateSymbolTable(interfaces.symbols.SymbolTableInterface): - """The IntermediateSymbolTable class reads a JSON file and conducts common tasks such as validation, construction - by looking up a JSON file from the available files and ensuring the appropriate version of the schema and proxy are - chosen. + """The IntermediateSymbolTable class reads a JSON file and conducts common + tasks such as validation, construction by looking up a JSON file from the + available files and ensuring the appropriate version of the schema and + proxy are chosen. The JSON format itself is made up of various groups (symbols, user_types, base_types, enums and metadata) * Symbols link a name to a particular offset relative to the start of a section of memory @@ -128,11 +129,14 @@ class IntermediateSymbolTable(interfaces.symbols.SymbolTableInterface): @staticmethod def _closest_version(version: str, versions: Dict[Tuple[int, int, int], Type['ISFormatTable']]) \ -> Type['ISFormatTable']: - """Determines the highest suitable handler for specified version format + """Determines the highest suitable handler for specified version + format. - An interface version such as Major.Minor.Patch means that Major of the provider must be equal to that of the - consumer, and the provider (the JSON in this instance) must have a greater minor (indicating that only additive - changes have been made) than the consumer (in this case, the file reader). + An interface version such as Major.Minor.Patch means that Major + of the provider must be equal to that of the consumer, and the + provider (the JSON in this instance) must have a greater minor + (indicating that only additive changes have been made) than + the consumer (in this case, the file reader). """ major, minor, patch = [int(x) for x in version.split(".")] supported_versions = [x for x in versions if x[0] == major and x[1] >= minor] @@ -154,9 +158,11 @@ class IntermediateSymbolTable(interfaces.symbols.SymbolTableInterface): @classmethod def file_symbol_url(cls, sub_path: str, filename: Optional[str] = None) -> Generator[str, None, None]: - """Returns an iterator of appropriate file-scheme symbol URLs that can be opened by a ResourceAccessor class + """Returns an iterator of appropriate file-scheme symbol URLs that can + be opened by a ResourceAccessor class. - Filter reduces the number of results returned to only those URLs containing that string + Filter reduces the number of results returned to only those URLs + containing that string """ # Check user-modifiable files first, then compressed ones @@ -203,7 +209,8 @@ class IntermediateSymbolTable(interfaces.symbols.SymbolTableInterface): native_types: Optional[interfaces.symbols.NativeTableInterface] = None, table_mapping: Optional[Dict[str, str]] = None, class_types: Optional[Dict[str, Type[interfaces.objects.ObjectInterface]]] = None) -> str: - """Takes a context and loads an intermediate symbol table based on a filename. + """Takes a context and loads an intermediate symbol table based on a + filename. Args: context: The context that the current plugin is being run within @@ -214,7 +221,8 @@ class IntermediateSymbolTable(interfaces.symbols.SymbolTableInterface): table_mapping: a dictionary of table names mentioned within the ISF file, and the tables within the context which they map to Returns: - the name of the added symbol table""" + the name of the added symbol table + """ urls = list(cls.file_symbol_url(sub_path, filename)) if not urls: raise ValueError("No symbol files found at provided filename: {}", filename) @@ -239,7 +247,7 @@ class IntermediateSymbolTable(interfaces.symbols.SymbolTableInterface): class ISFormatTable(interfaces.symbols.SymbolTableInterface, metaclass = ABCMeta): - """Provide a base class to identify all subclasses""" + """Provide a base class to identify all subclasses.""" version = (0, 0, 0) def __init__(self, @@ -261,7 +269,8 @@ class ISFormatTable(interfaces.symbols.SymbolTableInterface, metaclass = ABCMeta self._symbol_cache = {} # type: Dict[str, interfaces.symbols.SymbolInterface] def _get_natives(self) -> Optional[interfaces.symbols.NativeTableInterface]: - """Determines the appropriate native_types to use from the JSON data""" + """Determines the appropriate native_types to use from the JSON + data.""" # TODO: Consider how to generate the natives entirely from the ISF classes = {"x64": native.x64NativeTable, "x86": native.x86NativeTable} for nc in sorted(classes): @@ -288,16 +297,17 @@ class ISFormatTable(interfaces.symbols.SymbolTableInterface, metaclass = ABCMeta @property def metadata(self) -> Optional[interfaces.symbols.MetadataInterface]: - """Returns a metadata object containing information about the symbol table""" + """Returns a metadata object containing information about the symbol + table.""" return None class Version1Format(ISFormatTable): - """Class for storing intermediate debugging data as objects and classes""" + """Class for storing intermediate debugging data as objects and classes.""" version = (0, 0, 1) def get_symbol(self, name: str) -> interfaces.symbols.SymbolInterface: - """Returns the location offset given by the symbol name""" + """Returns the location offset given by the symbol name.""" # TODO: Add the ability to add/remove/change symbols after creation # note that this should invalidate/update the cache if self._symbol_cache.get(name, None): @@ -310,17 +320,17 @@ class Version1Format(ISFormatTable): @property def symbols(self) -> Iterable[str]: - """Returns an iterator of the symbol names""" + """Returns an iterator of the symbol names.""" return list(self._json_object.get('symbols', {})) @property def enumerations(self) -> Iterable[str]: - """Returns an iterator of the available enumerations""" + """Returns an iterator of the available enumerations.""" return list(self._json_object.get('enums', {})) @property def types(self) -> Iterable[str]: - """Returns an iterator of the symbol type names""" + """Returns an iterator of the symbol type names.""" return list(self._json_object.get('user_types', {})) + list(self.natives.types) def get_type_class(self, name: str) -> Type[interfaces.objects.ObjectInterface]: @@ -336,7 +346,7 @@ class Version1Format(ISFormatTable): del self._overrides[name] def _interdict_to_template(self, dictionary: Dict[str, Any]) -> interfaces.objects.Template: - """Converts an intermediate format dict into an object template""" + """Converts an intermediate format dict into an object template.""" if not dictionary: raise exceptions.SymbolSpaceError("Invalid intermediate dictionary: {}".format(dictionary)) @@ -383,7 +393,8 @@ class Version1Format(ISFormatTable): return objects.templates.ReferenceTemplate(type_name = reference_name) def _lookup_enum(self, name: str) -> Dict[str, Any]: - """Looks up an enumeration and returns a dictionary of __init__ parameters for an Enum""" + """Looks up an enumeration and returns a dictionary of __init__ + parameters for an Enum.""" lookup = self._json_object['enums'].get(name, None) if not lookup: raise exceptions.SymbolSpaceError("Unknown enumeration: {}".format(name)) @@ -391,7 +402,7 @@ class Version1Format(ISFormatTable): return result def get_enumeration(self, enum_name: str) -> interfaces.objects.Template: - """Resolves an individual enumeration""" + """Resolves an individual enumeration.""" if constants.BANG in enum_name: raise exceptions.SymbolError("Enumeration for a different table requested: {}".format(enum_name)) if enum_name not in self._json_object['enums']: @@ -407,7 +418,7 @@ class Version1Format(ISFormatTable): choices = curdict['constants']) def get_type(self, type_name: str) -> interfaces.objects.Template: - """Resolves an individual symbol""" + """Resolves an individual symbol.""" if constants.BANG in type_name: raise exceptions.SymbolError("Symbol for a different table requested: {}".format(type_name)) if type_name not in self._json_object['user_types']: @@ -432,11 +443,12 @@ class Version1Format(ISFormatTable): class Version2Format(Version1Format): - """Class for storing intermediate debugging data as objects and classes""" + """Class for storing intermediate debugging data as objects and classes.""" version = (2, 0, 0) def _get_natives(self) -> Optional[interfaces.symbols.NativeTableInterface]: - """Determines the appropriate native_types to use from the JSON data""" + """Determines the appropriate native_types to use from the JSON + data.""" classes = {"x64": native.x64NativeTable, "x86": native.x86NativeTable} for nc in sorted(classes): native_class = classes[nc] @@ -453,7 +465,7 @@ class Version2Format(Version1Format): return None def get_type(self, type_name: str) -> interfaces.objects.Template: - """Resolves an individual symbol""" + """Resolves an individual symbol.""" if constants.BANG in type_name: raise exceptions.SymbolError("Symbol for a different table requested: {}".format(type_name)) if type_name not in self._json_object['user_types']: @@ -481,11 +493,11 @@ class Version2Format(Version1Format): class Version3Format(Version2Format): - """Class for storing intermediate debugging data as objects and classes""" + """Class for storing intermediate debugging data as objects and classes.""" version = (2, 1, 0) def get_symbol(self, name: str) -> interfaces.symbols.SymbolInterface: - """Returns the symbol given by the symbol name""" + """Returns the symbol given by the symbol name.""" if self._symbol_cache.get(name, None): return self._symbol_cache[name] symbol = self._json_object['symbols'].get(name, None) @@ -500,7 +512,7 @@ class Version3Format(Version2Format): class Version4Format(Version3Format): - """Class for storing intermediate debugging data as objects and classes""" + """Class for storing intermediate debugging data as objects and classes.""" version = (4, 0, 0) format_mapping = { @@ -512,7 +524,8 @@ class Version4Format(Version3Format): } def _get_natives(self) -> Optional[interfaces.symbols.NativeTableInterface]: - """Determines the appropriate native_types to use from the JSON data""" + """Determines the appropriate native_types to use from the JSON + data.""" native_dict = {} base_types = self._json_object['base_types'] for base_type in base_types: @@ -531,11 +544,11 @@ class Version4Format(Version3Format): class Version5Format(Version4Format): - """Class for storing intermediate debugging data as objects and classes""" + """Class for storing intermediate debugging data as objects and classes.""" version = (4, 1, 0) def get_symbol(self, name: str) -> interfaces.symbols.SymbolInterface: - """Returns the symbol given by the symbol name""" + """Returns the symbol given by the symbol name.""" if self._symbol_cache.get(name, None): return self._symbol_cache[name] symbol = self._json_object['symbols'].get(name, None) @@ -553,12 +566,12 @@ class Version5Format(Version4Format): class Version6Format(Version5Format): - """Class for storing intermediate debugging data as objects and classes""" + """Class for storing intermediate debugging data as objects and classes.""" version = (6, 0, 0) @property def metadata(self) -> Optional[interfaces.symbols.MetadataInterface]: - """Returns a MetadataInterface object""" + """Returns a MetadataInterface object.""" if self._json_object.get('metadata', {}).get('windows'): return metadata.WindowsMetadata(self._json_object['metadata']['windows']) if self._json_object.get('metadata', {}).get('linux'): @@ -567,5 +580,5 @@ class Version6Format(Version5Format): class Version7Format(Version6Format): - """Class for storing intermediate debugging data as objects and classes""" + """Class for storing intermediate debugging data as objects and classes.""" version = (6, 1, 0) diff --git a/volatility/framework/symbols/linux/extensions/__init__.py b/volatility/framework/symbols/linux/extensions/__init__.py index 7640267e4..302ae9a34 100644 --- a/volatility/framework/symbols/linux/extensions/__init__.py +++ b/volatility/framework/symbols/linux/extensions/__init__.py @@ -43,6 +43,7 @@ class task_struct(generic.GenericIntelProcess): def add_process_layer(self, config_prefix: str = None, preferred_name: str = None) -> Optional[str]: """Constructs a new layer based on the process's DTB. + Returns the name of the Layer or None. """ @@ -63,7 +64,8 @@ class task_struct(generic.GenericIntelProcess): return self._add_process_layer(self._context, dtb, config_prefix, preferred_name) def get_process_memory_sections(self, heap_only: bool = False) -> Generator[Tuple[int, int], None, None]: - """Returns a list of sections based on the memory manager's view of this task's virtual memory""" + """Returns a list of sections based on the memory manager's view of + this task's virtual memory.""" for vma in self.mm.get_mmap_iter(): start = int(vma.vm_start) end = int(vma.vm_end) @@ -173,7 +175,8 @@ class vm_area_struct(objects.StructType): } def _parse_flags(self, vm_flags, parse_flags) -> str: - """Returns an string representation of the flags in a vm_area_struct.""" + """Returns an string representation of the flags in a + vm_area_struct.""" retval = "" diff --git a/volatility/framework/symbols/mac/extensions/__init__.py b/volatility/framework/symbols/mac/extensions/__init__.py index 43014a715..68a5fe594 100644 --- a/volatility/framework/symbols/mac/extensions/__init__.py +++ b/volatility/framework/symbols/mac/extensions/__init__.py @@ -18,6 +18,7 @@ class proc(generic.GenericIntelProcess): def add_process_layer(self, config_prefix: str = None, preferred_name: str = None) -> Optional[str]: """Constructs a new layer based on the process's DTB. + Returns the name of the Layer or None. """ parent_layer = self._context.layers[self.vol.layer_name] @@ -65,7 +66,8 @@ class proc(generic.GenericIntelProcess): config_prefix: str, rw_no_file: bool = False) -> \ Generator[Tuple[int, int], None, None]: - """Returns a list of sections based on the memory manager's view of this task's virtual memory""" + """Returns a list of sections based on the memory manager's view of + this task's virtual memory.""" for vma in self.get_map_iter(): start = int(vma.links.start) end = int(vma.links.end) @@ -141,7 +143,8 @@ class vnode(objects.StructType): class vm_map_entry(objects.StructType): def is_suspicious(self, context, config_prefix): - """Flags memory regions that are mapped rwx or that map an executable not back from a file on disk""" + """Flags memory regions that are mapped rwx or that map an executable + not back from a file on disk.""" ret = False perms = self.get_perms() diff --git a/volatility/framework/symbols/metadata.py b/volatility/framework/symbols/metadata.py index 4823ede4e..d86824d69 100644 --- a/volatility/framework/symbols/metadata.py +++ b/volatility/framework/symbols/metadata.py @@ -8,7 +8,7 @@ from volatility.framework import interfaces class WindowsMetadata(interfaces.symbols.MetadataInterface): - """Class to handle the metadata from a Windows symbol table""" + """Class to handle the metadata from a Windows symbol table.""" @property def pe_version(self) -> Optional[Tuple]: @@ -38,4 +38,4 @@ class WindowsMetadata(interfaces.symbols.MetadataInterface): class LinuxMetadata(interfaces.symbols.MetadataInterface): - """Class to handle the etadata from a Linux symbol table""" + """Class to handle the etadata from a Linux symbol table.""" diff --git a/volatility/framework/symbols/native.py b/volatility/framework/symbols/native.py index 701c640f1..65e8aad0b 100644 --- a/volatility/framework/symbols/native.py +++ b/volatility/framework/symbols/native.py @@ -9,7 +9,7 @@ from volatility.framework import constants, interfaces, objects class NativeTable(interfaces.symbols.NativeTableInterface): - """Symbol List that handles Native types""" + """Symbol List that handles Native types.""" # FIXME: typing the native_dictionary as Tuple[interfaces.objects.ObjectInterface, str] throws many errors def __init__(self, name: str, native_dictionary: Dict[str, Any]) -> None: @@ -29,14 +29,16 @@ class NativeTable(interfaces.symbols.NativeTableInterface): @property def types(self) -> Iterable[str]: - """Returns an iterator of the symbol type names""" + """Returns an iterator of the symbol type names.""" return self._types def get_type(self, type_name: str) -> interfaces.objects.Template: - """Resolves a symbol name into an object template + """Resolves a symbol name into an object template. - This always construct a new python object, rather than using a cached value otherwise changes made later may - affect the cached copy. Calling clone after every native type construction was extremely slow. + This always construct a new python object, rather than using a + cached value otherwise changes made later may affect the cached + copy. Calling clone after every native type construction was + extremely slow. """ # NOTE: These need updating whenever the object init signatures change prefix = "" diff --git a/volatility/framework/symbols/windows/extensions/__init__.py b/volatility/framework/symbols/windows/extensions/__init__.py index 35011d8cc..132bc6139 100644 --- a/volatility/framework/symbols/windows/extensions/__init__.py +++ b/volatility/framework/symbols/windows/extensions/__init__.py @@ -19,8 +19,11 @@ vollog = logging.getLogger(__name__) class _POOL_HEADER(objects.StructType): - """A kernel pool allocation header. Exists at the base of the - allocation and provides a tag that we can scan for.""" + """A kernel pool allocation header. + + Exists at the base of the allocation and provides a tag that we can + scan for. + """ def get_object(self, type_name: str, @@ -122,8 +125,11 @@ class _KSYSTEM_TIME(objects.StructType): class _MMVAD_SHORT(objects.StructType): - """A class that represents process virtual memory ranges. Each instance - is a node in a binary tree structure and is pointed to by VadRoot.""" + """A class that represents process virtual memory ranges. + + Each instance is a node in a binary tree structure and is pointed to + by VadRoot. + """ @functools.lru_cache(maxsize = None) def get_tag(self): @@ -152,8 +158,9 @@ class _MMVAD_SHORT(objects.StructType): return None def traverse(self, visited = None, depth = 0): - """Traverse the VAD tree, determining each underlying VAD node type by looking - up the pool tag for the structure and then casting into a new object.""" + """Traverse the VAD tree, determining each underlying VAD node type by + looking up the pool tag for the structure and then casting into a new + object.""" # TODO: this is an arbitrary limit chosen based on past observations if depth > 100: @@ -198,7 +205,7 @@ class _MMVAD_SHORT(objects.StructType): yield vad_node def get_right_child(self): - """Get the right child member""" + """Get the right child member.""" if self.has_member("RightChild"): return self.RightChild @@ -209,7 +216,7 @@ class _MMVAD_SHORT(objects.StructType): raise AttributeError("Unable to find the right child member") def get_left_child(self): - """Get the left child member""" + """Get the left child member.""" if self.has_member("LeftChild"): return self.LeftChild @@ -220,7 +227,7 @@ class _MMVAD_SHORT(objects.StructType): raise AttributeError("Unable to find the left child member") def get_parent(self): - """Get the VAD's parent member""" + """Get the VAD's parent member.""" # this is for xp and 2003 if self.has_member("Parent"): @@ -251,7 +258,7 @@ class _MMVAD_SHORT(objects.StructType): raise AttributeError("Unable to find the parent member") def get_start(self): - """Get the VAD's starting virtual address""" + """Get the VAD's starting virtual address.""" if self.has_member("StartingVpn"): @@ -270,7 +277,7 @@ class _MMVAD_SHORT(objects.StructType): raise AttributeError("Unable to find the starting VPN member") def get_end(self): - """Get the VAD's ending virtual address""" + """Get the VAD's ending virtual address.""" if self.has_member("EndingVpn"): @@ -302,7 +309,7 @@ class _MMVAD_SHORT(objects.StructType): raise AttributeError("Unable to find the commit charge member") def get_private_memory(self): - """Get the VAD's private memory setting""" + """Get the VAD's private memory setting.""" if self.has_member("u1") and self.u1.has_member("VadFlags1") and self.u1.VadFlags1.has_member("PrivateMemory"): return self.u1.VadFlags1.PrivateMemory @@ -322,7 +329,7 @@ class _MMVAD_SHORT(objects.StructType): raise AttributeError("Unable to find the private memory member") def get_protection(self, protect_values, winnt_protections): - """Get the VAD's protection constants as a string""" + """Get the VAD's protection constants as a string.""" protect = None @@ -346,7 +353,7 @@ class _MMVAD_SHORT(objects.StructType): return "|".join(names) def get_file_name(self): - """Only long(er) vads have mapped files""" + """Only long(er) vads have mapped files.""" return renderers.NotApplicableValue() @@ -376,9 +383,11 @@ class _MMVAD(_MMVAD_SHORT): class _EX_FAST_REF(objects.StructType): - """This is a standard Windows structure that stores a pointer to an - object but also leverages the least significant bits to encode additional - details. When dereferencing the pointer, we need to strip off the extra bits.""" + """This is a standard Windows structure that stores a pointer to an object + but also leverages the least significant bits to encode additional details. + + When dereferencing the pointer, we need to strip off the extra bits. + """ def dereference(self) -> interfaces.objects.ObjectInterface: @@ -400,8 +409,8 @@ class _EX_FAST_REF(objects.StructType): class ExecutiveObject(interfaces.objects.ObjectInterface): - """This is used as a "mixin" that provides all kernel executive - objects with a means of finding their own object header.""" + """This is used as a "mixin" that provides all kernel executive objects + with a means of finding their own object header.""" def object_header(self) -> '_OBJECT_HEADER': if constants.BANG not in self.vol.type_name: @@ -432,7 +441,7 @@ class _DRIVER_OBJECT(objects.StructType, ExecutiveObject): return header.NameInfo.Name.String # type: ignore def is_valid(self) -> bool: - """Determine if the object is valid""" + """Determine if the object is valid.""" return True @@ -444,7 +453,7 @@ class _OBJECT_SYMBOLIC_LINK(objects.StructType, ExecutiveObject): return header.NameInfo.Name.String # type: ignore def is_valid(self) -> bool: - """Determine if the object is valid""" + """Determine if the object is valid.""" return True def get_create_time(self): @@ -452,10 +461,10 @@ class _OBJECT_SYMBOLIC_LINK(objects.StructType, ExecutiveObject): class _FILE_OBJECT(objects.StructType, ExecutiveObject): - """A class for windows file objects""" + """A class for windows file objects.""" def is_valid(self) -> bool: - """Determine if the object is valid""" + """Determine if the object is valid.""" return self.FileName.Length > 0 and self._context.layers[self.vol.layer_name].is_valid(self.FileName.Buffer) def file_name_with_device(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: @@ -473,14 +482,14 @@ class _FILE_OBJECT(objects.StructType, ExecutiveObject): class _KMUTANT(objects.StructType, ExecutiveObject): - """A class for windows mutant objects""" + """A class for windows mutant objects.""" def is_valid(self) -> bool: - """Determine if the object is valid""" + """Determine if the object is valid.""" return True def get_name(self) -> str: - """Get the object's name from the object header""" + """Get the object's name from the object header.""" header = self.object_header() return header.NameInfo.Name.String # type: ignore @@ -490,7 +499,7 @@ class _OBJECT_HEADER(objects.StructType): quota information, ownership details, naming data, and ACLs.""" def is_valid(self) -> bool: - """Determine if the object is valid""" + """Determine if the object is valid.""" # if self.InfoMask > 0x48: # return False @@ -504,9 +513,12 @@ class _OBJECT_HEADER(objects.StructType): return True def get_object_type(self, type_map: Dict[int, str], cookie: int = None) -> Optional[str]: - """Across all Windows versions, the _OBJECT_HEADER embeds details on the type of - object (i.e. process, file) but the way its embedded differs between versions. - This API abstracts away those details.""" + """Across all Windows versions, the _OBJECT_HEADER embeds details on + the type of object (i.e. process, file) but the way its embedded + differs between versions. + + This API abstracts away those details. + """ try: # vista and earlier have a Type member @@ -561,7 +573,7 @@ class _ETHREAD(objects.StructType): """A class for executive thread objects.""" def owning_process(self, kernel_layer: str = None) -> interfaces.objects.ObjectInterface: - """Return the EPROCESS that owns this thread""" + """Return the EPROCESS that owns this thread.""" return self.ThreadsProcess.dereference(kernel_layer) @@ -582,7 +594,7 @@ class _EPROCESS(generic.GenericIntelProcess, ExecutiveObject): """A class for executive kernel processes objects.""" def is_valid(self) -> bool: - """Determine if the object is valid""" + """Determine if the object is valid.""" try: name = objects.utility.array_to_string(self.ImageFileName) @@ -620,7 +632,7 @@ class _EPROCESS(generic.GenericIntelProcess, ExecutiveObject): return True def add_process_layer(self, config_prefix: str = None, preferred_name: str = None): - """Constructs a new layer based on the process's DirectoryTableBase""" + """Constructs a new layer based on the process's DirectoryTableBase.""" parent_layer = self._context.layers[self.vol.layer_name] @@ -640,7 +652,7 @@ class _EPROCESS(generic.GenericIntelProcess, ExecutiveObject): return self._add_process_layer(self._context, dtb, config_prefix, preferred_name) def load_order_modules(self) -> Iterable[int]: - """Generator for DLLs in the order that they were loaded""" + """Generator for DLLs in the order that they were loaded.""" if constants.BANG not in self.vol.type_name: raise ValueError("Invalid symbol table name syntax (no {} found)".format(constants.BANG)) @@ -742,7 +754,7 @@ class _LIST_ENTRY(objects.StructType, collections.abc.Iterable): forward: bool = True, sentinel: bool = True, layer: Optional[str] = None) -> Iterator[interfaces.objects.ObjectInterface]: - """Returns an iterator of the entries in the list""" + """Returns an iterator of the entries in the list.""" layer = layer or self.vol.layer_name diff --git a/volatility/framework/symbols/windows/extensions/kdbg.py b/volatility/framework/symbols/windows/extensions/kdbg.py index 653953417..82fe28091 100644 --- a/volatility/framework/symbols/windows/extensions/kdbg.py +++ b/volatility/framework/symbols/windows/extensions/kdbg.py @@ -9,7 +9,7 @@ from volatility.framework import objects class _KDDEBUGGER_DATA64(objects.StructType): def get_build_lab(self): - """Returns the NT build lab string from the KDBG""" + """Returns the NT build lab string from the KDBG.""" layer_name = self.vol.layer_name symbol_table_name = self.get_symbol_table().name diff --git a/volatility/framework/symbols/windows/extensions/pe.py b/volatility/framework/symbols/windows/extensions/pe.py index e2dc9c852..88148e514 100644 --- a/volatility/framework/symbols/windows/extensions/pe.py +++ b/volatility/framework/symbols/windows/extensions/pe.py @@ -60,9 +60,9 @@ class _IMAGE_DOS_HEADER(objects.StructType): return result def fix_image_base(self, raw_data: bytes, nt_header: interfaces.objects.ObjectInterface) -> bytes: - """Fix the _OPTIONAL_HEADER.ImageBase value (which is either an unsigned long - for 32-bit PE's or unsigned long long for 64-bit PE's) to match the address - where the PE file was carved out of memory. + """Fix the _OPTIONAL_HEADER.ImageBase value (which is either an + unsigned long for 32-bit PE's or unsigned long long for 64-bit PE's) to + match the address where the PE file was carved out of memory. Args: raw_data: a bytes object of the PE's data @@ -79,9 +79,9 @@ class _IMAGE_DOS_HEADER(objects.StructType): return raw_data[:image_base_offset] + newval + raw_data[image_base_offset + member_size:] def reconstruct(self) -> Generator[Tuple[int, bytes], None, None]: - """This method generates the content necessary to reconstruct a PE - file from memory. It preserves slack space (similar to the old --memory) - and automatically fixes the ImageBase in the output PE file. + """This method generates the content necessary to reconstruct a PE file + from memory. It preserves slack space (similar to the old --memory) and + automatically fixes the ImageBase in the output PE file. Returns: of ( offset, data) diff --git a/volatility/framework/symbols/windows/extensions/registry.py b/volatility/framework/symbols/windows/extensions/registry.py index bbef73b23..448aa199c 100644 --- a/volatility/framework/symbols/windows/extensions/registry.py +++ b/volatility/framework/symbols/windows/extensions/registry.py @@ -38,8 +38,11 @@ class RegValueTypes(enum.Enum): @classmethod def get(cls, value): - """An alternative method for using this enum when the value may be unknown. - This is used to support unknown value requests in Python <3.6.""" + """An alternative method for using this enum when the value may be + unknown. + + This is used to support unknown value requests in Python <3.6. + """ try: return cls(value) except ValueError: @@ -70,9 +73,12 @@ class _HMAP_ENTRY(objects.StructType): class _CMHIVE(objects.StructType): def get_name(self) -> Optional[interfaces.objects.ObjectInterface]: - """Determine a name for the hive. Note that some attributes are - unpredictably blank across different OS versions while others are populated, - so we check all possibilities and take the first one that's not empty""" + """Determine a name for the hive. + + Note that some attributes are unpredictably blank across + different OS versions while others are populated, so we check + all possibilities and take the first one that's not empty + """ for attr in ["FileFullPath", "FileUserName", "HiveRootPath"]: try: @@ -86,12 +92,12 @@ class _CMHIVE(objects.StructType): class _CM_KEY_BODY(objects.StructType): - """This represents an open handle to a registry key and - is not tied to the registry hive file format on disk.""" + """This represents an open handle to a registry key and is not tied to the + registry hive file format on disk.""" def _skip_key_hive_entry_path(self, kcb_flags): - """Win10 14393 introduced an extra path element that it skips - over by checking for Flags that contain KEY_HIVE_ENTRY""" + """Win10 14393 introduced an extra path element that it skips over by + checking for Flags that contain KEY_HIVE_ENTRY.""" # _CM_KEY_BODY.Trans introduced in Win10 14393 if hasattr(self, "Trans") and RegKeyFlags.KEY_HIVE_ENTRY & kcb_flags == RegKeyFlags.KEY_HIVE_ENTRY: @@ -119,7 +125,7 @@ class _CM_KEY_BODY(objects.StructType): class _CM_KEY_NODE(objects.StructType): - """Extension to allow traversal of registry keys""" + """Extension to allow traversal of registry keys.""" def get_volatile(self) -> bool: if not isinstance(self._context.layers[self.vol.layer_name], RegistryHive): @@ -127,7 +133,7 @@ class _CM_KEY_NODE(objects.StructType): return bool(self.vol.offset & 0x80000000) def get_subkeys(self) -> Iterable[interfaces.objects.ObjectInterface]: - """Returns a list of the key nodes""" + """Returns a list of the key nodes.""" hive = self._context.layers[self.vol.layer_name] if not isinstance(hive, RegistryHive): raise TypeError("CM_KEY_NODE was not instantiated on a RegistryHive layer") @@ -138,7 +144,7 @@ class _CM_KEY_NODE(objects.StructType): def _get_subkeys_recursive(self, hive: RegistryHive, node: interfaces.objects.ObjectInterface ) -> Iterable[interfaces.objects.ObjectInterface]: - """Recursively descend a node returning subkeys""" + """Recursively descend a node returning subkeys.""" # The keylist appears to include 4 bytes of key name after each value # We can either double the list and only use the even items, or # We could change the array type to a struct with both parts @@ -174,7 +180,7 @@ class _CM_KEY_NODE(objects.StructType): yield from self._get_subkeys_recursive(hive, subnode) def get_values(self) -> Iterable[interfaces.objects.ObjectInterface]: - """Returns a list of the Value nodes for a key""" + """Returns a list of the Value nodes for a key.""" hive = self._context.layers[self.vol.layer_name] if not isinstance(hive, RegistryHive): raise TypeError("CM_KEY_NODE was not instantiated on a RegistryHive layer") @@ -196,7 +202,7 @@ class _CM_KEY_NODE(objects.StructType): return def get_name(self) -> interfaces.objects.ObjectInterface: - """Since this is just a casting convenience, it can be a property""" + """Since this is just a casting convenience, it can be a property.""" return self.Name.cast("string", max_length = self.NameLength, encoding = "latin-1") def get_key_path(self) -> interfaces.objects.ObjectInterface: @@ -212,15 +218,15 @@ class _CM_KEY_NODE(objects.StructType): class _CM_KEY_VALUE(objects.StructType): - """Extensions to extract data from CM_KEY_VALUE nodes""" + """Extensions to extract data from CM_KEY_VALUE nodes.""" def get_name(self) -> interfaces.objects.ObjectInterface: - """Since this is just a casting convenience, it can be a property""" + """Since this is just a casting convenience, it can be a property.""" self.Name.count = self.NameLength return self.Name.cast("string", max_length = self.NameLength, encoding = "latin-1") def decode_data(self) -> Union[str, bytes]: - """Since this is just a casting convenience, it can be a property""" + """Since this is just a casting convenience, it can be a property.""" # Determine if the data is stored inline datalen = self.DataLength & 0x7fffffff data = b"" diff --git a/volatility/framework/symbols/windows/extensions/services.py b/volatility/framework/symbols/windows/extensions/services.py index 56e2a77ac..f2b435ac9 100644 --- a/volatility/framework/symbols/windows/extensions/services.py +++ b/volatility/framework/symbols/windows/extensions/services.py @@ -9,10 +9,10 @@ from volatility.framework import renderers from typing import Union class _SERVICE_RECORD(objects.StructType): - """A service record structure""" + """A service record structure.""" def is_valid(self) -> bool: - """Determine if the structure is valid""" + """Determine if the structure is valid.""" if self.Order < 0 or self.Order > 0xFFFF: return False @@ -25,7 +25,7 @@ class _SERVICE_RECORD(objects.StructType): return True def get_pid(self) -> Union[int, interfaces.renderers.BaseAbsentValue]: - """Return the pid of the process, if any""" + """Return the pid of the process, if any.""" if self.State.description != "SERVICE_RUNNING" or "PROCESS" not in self.get_type(): return renderers.NotApplicableValue() @@ -35,7 +35,7 @@ class _SERVICE_RECORD(objects.StructType): return renderers.UnreadableValue() def get_binary(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: - """Returns the binary associated with the service""" + """Returns the binary associated with the service.""" if self.State.description != "SERVICE_RUNNING": return renderers.NotApplicableValue() @@ -56,7 +56,7 @@ class _SERVICE_RECORD(objects.StructType): return renderers.UnreadableValue() def get_name(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: - """Returns the service name""" + """Returns the service name.""" try: return self.ServiceName.dereference().cast("string", encoding = "utf-16", @@ -66,7 +66,7 @@ class _SERVICE_RECORD(objects.StructType): return renderers.UnreadableValue() def get_display(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: - """Returns the service display""" + """Returns the service display.""" try: return self.DisplayName.dereference().cast("string", encoding = "utf-16", @@ -76,7 +76,7 @@ class _SERVICE_RECORD(objects.StructType): return renderers.UnreadableValue() def get_type(self) -> str: - """Returns the binary types""" + """Returns the binary types.""" SERVICE_TYPE_FLAGS = { 'SERVICE_KERNEL_DRIVER': 1, @@ -92,7 +92,7 @@ class _SERVICE_RECORD(objects.StructType): return "|".join(type_flags(self.Type)) def traverse(self): - """Generator that enumerates other services""" + """Generator that enumerates other services.""" try: if hasattr(self, "PrevEntry"): @@ -113,10 +113,10 @@ class _SERVICE_RECORD(objects.StructType): raise StopIteration class _SERVICE_HEADER(objects.StructType): - """A service header structure""" + """A service header structure.""" def is_valid(self) -> bool: - """Determine if the structure is valid""" + """Determine if the structure is valid.""" try: return self.ServiceRecord.is_valid() except exceptions.InvalidAddressException: diff --git a/volatility/framework/symbols/windows/pdbconv.py b/volatility/framework/symbols/windows/pdbconv.py index 7a3ffd299..d2af516b7 100644 --- a/volatility/framework/symbols/windows/pdbconv.py +++ b/volatility/framework/symbols/windows/pdbconv.py @@ -236,7 +236,7 @@ class ForwardArrayCount: class PdbReader: - """Class to read Microsoft PDB files + """Class to read Microsoft PDB files. This reads the various streams according to various sources as to how pdb should be read. These sources include: @@ -254,7 +254,6 @@ class PdbReader: particularly when it comes to names. We must therefore parse it after we've collected other information already. This is in comparison to something such as Construct/pdbparse which can use just-parsed data to determine dynamically sized data following. - """ def __init__(self, @@ -287,9 +286,10 @@ class PdbReader: @classmethod def load_pdb_layer(cls, context: interfaces.context.ContextInterface, location: str) -> Tuple[str, interfaces.context.ContextInterface]: - """Loads a PDB file into a layer within the context and returns the name of the new layer + """Loads a PDB file into a layer within the context and returns the + name of the new layer. - Note: the context may be changed by this method + Note: the context may be changed by this method """ physical_layer_name = context.layers.free_layer_name("FileLayer") physical_config_path = interfaces.configuration.path_join("pdbreader", physical_layer_name) @@ -322,7 +322,8 @@ class PdbReader: self._omap_mapping = [] def read_necessary_streams(self): - """Read streams to populate the various internal components for a PDB table""" + """Read streams to populate the various internal components for a PDB + table.""" if not self.metadata['windows'].get('pdb', None): self.read_pdb_info_stream() if not self.user_types: @@ -331,7 +332,7 @@ class PdbReader: self.read_symbol_stream() def read_tpi_stream(self) -> None: - """Reads the TPI type steam""" + """Reads the TPI type steam.""" vollog.debug("Reading TPI") tpi_layer = self._context.layers.get(self._layer_name + "_stream2", None) if not tpi_layer: @@ -379,7 +380,7 @@ class PdbReader: self.process_types(type_references) def read_dbi_stream(self) -> None: - """Reads the DBI Stream""" + """Reads the DBI Stream.""" vollog.debug("Reading DBI stream") dbi_layer = self._context.layers.get(self._layer_name + "_stream3", None) if not dbi_layer: @@ -430,7 +431,7 @@ class PdbReader: consumed += section.vol.size def read_symbol_stream(self): - """Reads in the symbol stream""" + """Reads in the symbol stream.""" self.symbols = {} if not self._dbiheader: @@ -474,7 +475,7 @@ class PdbReader: offset += sym.length + 2 # Add on length itself def read_pdb_info_stream(self): - """Reads in the pdb information stream""" + """Reads in the pdb information stream.""" if not self._dbiheader: self.read_dbi_stream() @@ -494,7 +495,7 @@ class PdbReader: } def convert_bytes_to_guid(self, original: bytes) -> str: - """Convert the bytes to the correct ordering for a GUID""" + """Convert the bytes to the correct ordering for a GUID.""" orig_guid_list = [x for x in original] guid_list = [] for i in [3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15]: @@ -504,7 +505,7 @@ class PdbReader: # SYMBOL HANDLING CODE def omap_lookup(self, address): - """Looks up an address using the omap mapping""" + """Looks up an address using the omap mapping.""" pos = bisect(self._omap_mapping, (address, -1)) if self._omap_mapping[pos][0] > address: pos -= 1 @@ -514,7 +515,7 @@ class PdbReader: return self._omap_mapping[pos][1] + (address - self._omap_mapping[pos][0]) def name_strip(self, name): - """Strips unnecessary components from the start of a symbol name""" + """Strips unnecessary components from the start of a symbol name.""" new_name = name if new_name[:7] in ["__imp__", "__imp_@"]: @@ -534,7 +535,7 @@ class PdbReader: return new_name def get_json(self): - """Returns the intermediate format JSON data from this pdb file""" + """Returns the intermediate format JSON data from this pdb file.""" self.read_necessary_streams() # Set the time/datestamp for the output @@ -553,7 +554,7 @@ class PdbReader: } def get_type_from_index(self, index: int) -> Union[List[Any], Dict[str, Any]]: - """Takes a type index and returns appropriate dictionary""" + """Takes a type index and returns appropriate dictionary.""" if index < 0x1000: base_name, base = primatives[index & 0xff] self.bases[base_name] = base @@ -607,7 +608,8 @@ class PdbReader: return result def get_size_from_index(self, index: int) -> int: - """Returns the size of the structure based on the type index provided""" + """Returns the size of the structure based on the type index + provided.""" result = -1 name = '' if index < 0x1000: @@ -652,7 +654,8 @@ class PdbReader: ### TYPE HANDLING CODE def process_types(self, type_references: Dict[str, int]) -> None: - """Reads the TPI and symbol streams to populate the reader's variables""" + """Reads the TPI and symbol streams to populate the reader's + variables.""" self.bases = {} self.user_types = {} @@ -699,7 +702,8 @@ class PdbReader: self, module: interfaces.context.ModuleInterface, offset: int, length: int ) -> Tuple[Tuple[Optional[interfaces.objects.ObjectInterface], Optional[str], Union[None, List, interfaces.objects. ObjectInterface]], int]: - """Returns a (leaf_type, name, object) Tuple for a type, and the number of bytes consumed""" + """Returns a (leaf_type, name, object) Tuple for a type, and the number + of bytes consumed.""" result = None, None, None # type: Tuple[Optional[interfaces.objects.ObjectInterface], Optional[str], Optional[Union[List, interfaces.objects.ObjectInterface]]] leaf_type = self.context.object( module.get_enumeration("LEAF_TYPE"), layer_name = module._layer_name, offset = offset) @@ -782,14 +786,14 @@ class PdbReader: return result, consumed def consume_padding(self, layer_name: str, offset: int) -> int: - """Returns the amount of padding used between fields""" + """Returns the amount of padding used between fields.""" val = self.context.layers[layer_name].read(offset, 1) if not ((val[0] & 0xf0) == 0xf0): return 0 return (int(val[0]) & 0x0f) def convert_fields(self, fields: int) -> Dict[Optional[str], Dict[str, Any]]: - """Converts a field list into a list of fields""" + """Converts a field list into a list of fields.""" result = {} # type: Dict[Optional[str], Dict[str, Any]] _, _, fields_struct = self.types[fields] if not isinstance(fields_struct, list): @@ -801,7 +805,8 @@ class PdbReader: return result def replace_forward_references(self, types, type_references): - """Finds all ForwardArrayCounts and calculates them once ForwardReferences have been resolved""" + """Finds all ForwardArrayCounts and calculates them once + ForwardReferences have been resolved.""" if isinstance(types, dict): for k, v in types.items(): types[k] = self.replace_forward_references(v, type_references) @@ -836,7 +841,8 @@ class PdbReader: @staticmethod def parse_string(structure: interfaces.objects.ObjectInterface, parse_as_pascal: bool = False, size: int = 0) -> str: - """Consumes either a c-string or a pascal string depending on the leaf_type""" + """Consumes either a c-string or a pascal string depending on the + leaf_type.""" if not parse_as_pascal: name = structure.cast("string", max_length = size, encoding = "latin-1") else: @@ -847,7 +853,8 @@ class PdbReader: def determine_extended_value(self, leaf_type: interfaces.objects.ObjectInterface, value: interfaces.objects.ObjectInterface, module: interfaces.context.ModuleInterface, length: int) -> Tuple[str, interfaces.objects.ObjectInterface, int]: - """Reads a value and potentially consumes more data to construct the value""" + """Reads a value and potentially consumes more data to construct the + value.""" excess = 0 if value >= leaf_type.LF_CHAR: sub_leaf_type = self.context.object( @@ -878,7 +885,8 @@ class PdbReader: class PdbRetreiver: def get_report_hook(self, progress_callback, url): - """Returns a report hook that converts output into a progress_callback""" + """Returns a report hook that converts output into a + progress_callback.""" if progress_callback is None: return lambda x, y, z: None @@ -918,13 +926,14 @@ if __name__ == '__main__': import argparse 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. diff --git a/volatility/framework/symbols/wrappers.py b/volatility/framework/symbols/wrappers.py index 055882c53..993c9d688 100644 --- a/volatility/framework/symbols/wrappers.py +++ b/volatility/framework/symbols/wrappers.py @@ -8,7 +8,8 @@ from volatility.framework import interfaces class Flags: - """Object that converts an integer into a set of flags based on their masks""" + """Object that converts an integer into a set of flags based on their + masks.""" def __init__(self, choices: Mapping[str, int]) -> None: self._choices = interfaces.objects.ReadOnlyMapping(choices) @@ -18,7 +19,7 @@ class Flags: return self._choices def __call__(self, value: int) -> List[str]: - """Return the appropriate Flags """ + """Return the appropriate Flags.""" result = [] for k, v in self.choices.items(): if value & v: diff --git a/volatility/plugins/__init__.py b/volatility/plugins/__init__.py index fcdc84398..f6fe4fe6f 100644 --- a/volatility/plugins/__init__.py +++ b/volatility/plugins/__init__.py @@ -1,16 +1,16 @@ # 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 plugin architecture +"""Defines the plugin architecture. - This is the namespace for all volatility plugins, - and determines the path for loading plugins +This is the namespace for all volatility plugins, +and determines the path for loading plugins - NOTE: This file is important for core plugins to run (which certain components such as the windows registry layers) - are dependent upon, please DO NOT alter or remove this file unless you know the consequences of doing so. +NOTE: This file is important for core plugins to run (which certain components such as the windows registry layers) +are dependent upon, please DO NOT alter or remove this file unless you know the consequences of doing so. - The framework is configured this way to allow plugin developers/users to override any plugin functionality whether - existing or new. +The framework is configured this way to allow plugin developers/users to override any plugin functionality whether +existing or new. """ from volatility.framework import constants diff --git a/volatility/plugins/linux/__init__.py b/volatility/plugins/linux/__init__.py index 8eec6e48d..c48eb2359 100644 --- a/volatility/plugins/linux/__init__.py +++ b/volatility/plugins/linux/__init__.py @@ -1,13 +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 Linux-related plugins +"""All Linux-related plugins. - NOTE: This file is important for core plugins to run (which certain components such as the windows registry layers) - are dependent upon, please DO NOT alter or remove this file unless you know the consequences of doing so. +NOTE: This file is important for core plugins to run (which certain components such as the windows registry layers) +are dependent upon, please DO NOT alter or remove this file unless you know the consequences of doing so. - The framework is configured this way to allow plugin developers/users to override any plugin functionality whether - existing or new. +The framework is configured this way to allow plugin developers/users to override any plugin functionality whether +existing or new. """ import os diff --git a/volatility/plugins/mac/__init__.py b/volatility/plugins/mac/__init__.py index 4b01bb4cc..7ce0f3159 100644 --- a/volatility/plugins/mac/__init__.py +++ b/volatility/plugins/mac/__init__.py @@ -1,13 +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 Mac-related plugins +"""All Mac-related plugins. - NOTE: This file is important for core plugins to run (which certain components such as the windows registry layers) - are dependent upon, please DO NOT alter or remove this file unless you know the consequences of doing so. +NOTE: This file is important for core plugins to run (which certain components such as the windows registry layers) +are dependent upon, please DO NOT alter or remove this file unless you know the consequences of doing so. - The framework is configured this way to allow plugin developers/users to override any plugin functionality whether - existing or new. +The framework is configured this way to allow plugin developers/users to override any plugin functionality whether +existing or new. """ import os diff --git a/volatility/plugins/windows/__init__.py b/volatility/plugins/windows/__init__.py index 30443bbce..50c35918c 100644 --- a/volatility/plugins/windows/__init__.py +++ b/volatility/plugins/windows/__init__.py @@ -1,13 +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 Windows OS plugins +"""All Windows OS plugins. - NOTE: This file is important for core plugins to run (which certain components such as the windows registry layers) - are dependent upon, please DO NOT alter or remove this file unless you know the consequences of doing so. +NOTE: This file is important for core plugins to run (which certain components such as the windows registry layers) +are dependent upon, please DO NOT alter or remove this file unless you know the consequences of doing so. - The framework is configured this way to allow plugin developers/users to override any plugin functionality whether - existing or new. +The framework is configured this way to allow plugin developers/users to override any plugin functionality whether +existing or new. """ import os diff --git a/volatility/plugins/windows/registry/__init__.py b/volatility/plugins/windows/registry/__init__.py index 2b8eae9dc..0907a0cde 100644 --- a/volatility/plugins/windows/registry/__init__.py +++ b/volatility/plugins/windows/registry/__init__.py @@ -1,13 +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 # -"""Windows registry plugins +"""Windows registry plugins. - NOTE: This file is important for core plugins to run (which certain components such as the windows registry layers) - are dependent upon, please DO NOT alter or remove this file unless you know the consequences of doing so. +NOTE: This file is important for core plugins to run (which certain components such as the windows registry layers) +are dependent upon, please DO NOT alter or remove this file unless you know the consequences of doing so. - The framework is configured this way to allow plugin developers/users to override any plugin functionality whether - existing or new. +The framework is configured this way to allow plugin developers/users to override any plugin functionality whether +existing or new. """ import os diff --git a/volatility/schemas/__init__.py b/volatility/schemas/__init__.py index ddb038356..23051bd31 100644 --- a/volatility/schemas/__init__.py +++ b/volatility/schemas/__init__.py @@ -16,7 +16,8 @@ cached_validation_filepath = os.path.join(constants.CACHE_PATH, "valid_isf.cache def load_cached_validations() -> Set[str]: - """Loads up the list of successfully cached json objects, so we don't need to revalidate them""" + """Loads up the list of successfully cached json objects, so we don't need + to revalidate them.""" validhashes = set() # type: Set if os.path.exists(cached_validation_filepath): with open(cached_validation_filepath, "r") as f: @@ -25,7 +26,8 @@ def load_cached_validations() -> Set[str]: def record_cached_validations(validations): - """Record the cached validations, so we don't need to revalidate them in future""" + """Record the cached validations, so we don't need to revalidate them in + future.""" with open(cached_validation_filepath, "w") as f: json.dump(list(validations), f) @@ -34,7 +36,7 @@ cached_validations = load_cached_validations() def validate(input: Dict[str, Any], use_cache: bool = True) -> bool: - """Validates an input JSON file based upon """ + """Validates an input JSON file based upon.""" format = input.get('metadata', {}).get('format', None) if not format: vollog.debug("No schema format defined") @@ -50,12 +52,13 @@ def validate(input: Dict[str, Any], use_cache: bool = True) -> bool: def create_json_hash(input: Dict[str, Any], schema: Dict[str, Any]) -> str: - """Constructs the hash of the input and schema to create a unique indentifier for a particular JSON file""" + """Constructs the hash of the input and schema to create a unique + indentifier for a particular JSON file.""" return hashlib.sha1(bytes(json.dumps((input, schema), sort_keys = True), 'utf-8')).hexdigest() def valid(input: Dict[str, Any], schema: Dict[str, Any], use_cache: bool = True) -> bool: - """Validates a json schema""" + """Validates a json schema.""" input_hash = create_json_hash(input, schema) if input_hash in cached_validations and use_cache: return True diff --git a/volatility/symbols/__init__.py b/volatility/symbols/__init__.py index 8aea018f5..c46de68b2 100644 --- a/volatility/symbols/__init__.py +++ b/volatility/symbols/__init__.py @@ -1,10 +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 # -"""Defines the symbols architecture +"""Defines the symbols architecture. - This is the namespace for all volatility symbols, - and determines the path for loading symbol ISF files +This is the namespace for all volatility symbols, and determines the +path for loading symbol ISF files """ from volatility.framework import constants