Improve docstrings for all plugins, and reformat all docstrings.

This commit is contained in:
Mike Auty
2019-09-07 22:59:54 +01:00
parent dc0a809729
commit e922cef316
134 changed files with 1933 additions and 1250 deletions
+7 -4
View File
@@ -11,9 +11,10 @@ _S = TypeVar("_S")
class classproperty(object):
"""Class property decorator
"""Class property decorator.
Note this will change the return type """
Note this will change the return type
"""
def __init__(self, func: Callable[[_S], _T]) -> None:
self._func = func
@@ -23,11 +24,13 @@ class classproperty(object):
class WarningFindSpec(abc.MetaPathFinder):
"""Checks import attempts and throws a warning if the name shouldn't be used"""
"""Checks import attempts and throws a warning if the name shouldn't be
used."""
@staticmethod
def find_spec(fullname: str, path, target = None):
"""Mock find_spec method that just checks the name, this must go first"""
"""Mock find_spec method that just checks the name, this must go
first."""
if fullname.startswith("volatility.framework.plugins."):
warning = "Please do not use the volatility.framework.plugins namespace directly, only use volatility.plugins"
# Pyinstaller uses pkgutil to import, but needs to read the modules to figure out dependencies
+22 -18
View File
@@ -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()
+6 -13
View File
@@ -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
+8 -5
View File
@@ -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()
+5 -5
View File
@@ -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]
+2 -2
View File
@@ -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']
+5 -5
View File
@@ -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")
+8 -4
View File
@@ -1,7 +1,8 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
"""Automagic modules allow the framework to populate configuration elements that a user has not provided.
"""Automagic modules allow the framework to populate configuration elements
that a user has not provided.
Automagic objects accept a `context` and a `configurable`, and will make appropriate changes to the `context` in an
attempt to fulfill the requirements of the `configurable` object (or objects upon which that configurable may rely).
@@ -29,7 +30,8 @@ mac_automagic = ['ConstructionMagic', 'LayerStacker', 'MacBannerCache', 'MacSymb
def available(context: interfaces.context.ContextInterface) -> List[interfaces.automagic.AutomagicInterface]:
"""Returns an ordered list of all subclasses of :class:`~volatility.framework.interfaces.automagic.AutomagicInterface`.
"""Returns an ordered list of all subclasses of
:class:`~volatility.framework.interfaces.automagic.AutomagicInterface`.
The order is based on the priority attributes of the subclasses, in order to ensure the automagics are listed in
an appropriate order.
@@ -47,7 +49,8 @@ def available(context: interfaces.context.ContextInterface) -> List[interfaces.a
def choose_automagic(automagics, plugin):
"""Chooses which automagics to run, maintaining the order they were handed in"""
"""Chooses which automagics to run, maintaining the order they were handed
in."""
plugin_category = plugin.__module__.split('.')[2]
vollog.info("Detected a {} category plugin".format(plugin_category))
output = []
@@ -73,7 +76,8 @@ def run(automagics: List[interfaces.automagic.AutomagicInterface],
ConfigurableInterface]],
config_path: str,
progress_callback: constants.ProgressCallback = None) -> List[traceback.TracebackException]:
"""Runs through the list of `automagics` in order, allowing them to make changes to the context
"""Runs through the list of `automagics` in order, allowing them to make
changes to the context.
Args:
automagics: A list of :class:`~volatility.framework.interfaces.automagic.AutomagicInterface` objects
@@ -1,8 +1,9 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
"""An automagic module to use configuration data to configure and then construct classes that fulfill the descendants
of a :class:`~volatility.framework.interfaces.configuration.ConfigurableInterface`."""
"""An automagic module to use configuration data to configure and then
construct classes that fulfill the descendants of a :class:`~volatility.framewo
rk.interfaces.configuration.ConfigurableInterface`."""
import logging
from typing import List
@@ -14,7 +15,7 @@ vollog = logging.getLogger(__name__)
class ConstructionMagic(interfaces.automagic.AutomagicInterface):
"""Constructs underlying layers
"""Constructs underlying layers.
Class to run through the requirement tree of the :class:`~volatility.framework.interfaces.configuration.ConfigurableInterface`
and from the bottom of the tree upwards, attempt to construct all
+8 -6
View File
@@ -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
+8 -6
View File
@@ -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
+28 -18
View File
@@ -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
+7 -5
View File
@@ -24,7 +24,7 @@ vollog = logging.getLogger(__name__)
class LayerStacker(interfaces.automagic.AutomagicInterface):
"""Builds up layers in a single stack
"""Builds up layers in a single stack.
This class mimics the volatility 2 style of stacking address spaces. It builds up various layers based on
separate :class:`~volatility.framework.interfaces.automagic.StackerLayerInterface` classes. These classes are
@@ -46,7 +46,7 @@ class LayerStacker(interfaces.automagic.AutomagicInterface):
config_path: str,
requirement: interfaces.configuration.RequirementInterface,
progress_callback: constants.ProgressCallback = None) -> Optional[List[str]]:
"""Runs the automagic over the configurable"""
"""Runs the automagic over the configurable."""
# Quick exit if we're not needed
if not requirement.unsatisfied(context, config_path):
@@ -68,7 +68,8 @@ class LayerStacker(interfaces.automagic.AutomagicInterface):
def stack(self, context: interfaces.context.ContextInterface, config_path: str,
requirement: interfaces.configuration.RequirementInterface,
progress_callback: constants.ProgressCallback) -> None:
"""Stacks the various layers and attaches these to a specific requirement
"""Stacks the various layers and attaches these to a specific
requirement.
Args:
context: Context on which to operate
@@ -152,8 +153,9 @@ class LayerStacker(interfaces.automagic.AutomagicInterface):
def find_suitable_requirements(self, context: interfaces.context.ContextInterface, config_path: str,
requirement: interfaces.configuration.RequirementInterface,
stacked_layers: List[str]) -> Optional[Tuple[str, str]]:
"""Looks for translation layer requirements and attempts to apply the stacked layers to it. If it succeeds
it returns the configuration path and layer name where the stacked nodes were spliced into the tree.
"""Looks for translation layer requirements and attempts to apply the
stacked layers to it. If it succeeds it returns the configuration path
and layer name where the stacked nodes were spliced into the tree.
Returns:
A tuple of a configuration path and layer name for the top of the stacked layers
@@ -19,7 +19,7 @@ BannersType = Dict[bytes, List[str]]
class SymbolBannerCache(interfaces.automagic.AutomagicInterface):
"""Runs through all symbols tables and caches their banners"""
"""Runs through all symbols tables and caches their banners."""
# Since this is necessary for ConstructionMagic, we set a lower priority
# The user would run it eventually either way, but running it first means it can be used that run
@@ -70,7 +70,7 @@ class SymbolBannerCache(interfaces.automagic.AutomagicInterface):
pickle.dump(banners, f)
def __call__(self, context, config_path, configurable, progress_callback = None):
"""Runs the automagic over the configurable"""
"""Runs the automagic over the configurable."""
# Bomb out if we're just the generic interface
if self.os is None:
@@ -14,7 +14,7 @@ vollog = logging.getLogger(__name__)
class SymbolFinder(interfaces.automagic.AutomagicInterface):
"""Symbol loader based on signature strings"""
"""Symbol loader based on signature strings."""
priority = 40
banner_config_key = "banner" # type: str
@@ -28,7 +28,8 @@ class SymbolFinder(interfaces.automagic.AutomagicInterface):
@property
def banners(self) -> symbol_cache.BannersType:
"""Creates a cached copy of the results, but only it's been requested"""
"""Creates a cached copy of the results, but only it's been
requested."""
if not self._banners:
if not self.banner_cache:
raise RuntimeError("Cache has not been properly defined for {}".format(self.__class__.__name__))
@@ -40,7 +41,8 @@ class SymbolFinder(interfaces.automagic.AutomagicInterface):
config_path: str,
requirement: interfaces.configuration.RequirementInterface,
progress_callback: constants.ProgressCallback = None) -> None:
"""Searches for SymbolTableRequirements and attempt to populate them"""
"""Searches for SymbolTableRequirements and attempt to populate
them."""
# Bomb out early if our details haven't been configured
if self.symbol_class is None:
@@ -73,8 +75,8 @@ class SymbolFinder(interfaces.automagic.AutomagicInterface):
requirement: interfaces.configuration.ConstructableRequirementInterface,
layer_name: str,
progress_callback: constants.ProgressCallback = None) -> None:
"""Accepts a context, config_path and SymbolTableRequirement, with a constructed layer_name
and scans the layer for banners"""
"""Accepts a context, config_path and SymbolTableRequirement, with a
constructed layer_name and scans the layer for banners."""
# Bomb out early if there's no banners
if not self.banners:
+40 -25
View File
@@ -1,7 +1,8 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
"""Module to identify the Directory Table Base and architecture of windows memory images
"""Module to identify the Directory Table Base and architecture of windows
memory images.
This module contains a PageMapScanner that scans a physical layer to identify self-referential pointers.
All windows versions include a self-referential pointer in their Directory Table Base's top table, in order to
@@ -37,10 +38,12 @@ vollog = logging.getLogger(__name__)
class DtbTest:
"""This class generically contains the tests for a page based on a set of class parameters
"""This class generically contains the tests for a page based on a set of
class parameters.
When constructed it contains all the information necessary to extract a specific index from a page
and determine whether it points back to that page's offset.
When constructed it contains all the information necessary to
extract a specific index from a page and determine whether it points
back to that page's offset.
"""
def __init__(self, layer_type: Type[layers.intel.Intel], ptr_struct: str, ptr_reference: int, mask: int) -> None:
@@ -55,7 +58,8 @@ class DtbTest:
return struct.unpack("<" + self.ptr_struct, value)[0]
def __call__(self, data: bytes, data_offset: int, page_offset: int) -> Optional[Tuple[int, Any]]:
"""Tests a specific page in a chunk of data to see if it contains a self-referential pointer.
"""Tests a specific page in a chunk of data to see if it contains a
self-referential pointer.
Args:
data: The chunk of data that contains the page to be scanned
@@ -82,7 +86,8 @@ class DtbTest:
return None
def second_pass(self, dtb: int, data: bytes, data_offset: int) -> Optional[Tuple[int, Any]]:
"""Re-reads over the whole page to validate other records based on the number of pages marked user vs super
"""Re-reads over the whole page to validate other records based on the
number of pages marked user vs super.
Args:
dtb: The identified dtb that needs validating
@@ -128,11 +133,14 @@ class DtbTestPae(DtbTest):
layer_type = layers.intel.WindowsIntelPAE, ptr_struct = "Q", ptr_reference = 0x3, mask = 0x3FFFFFFFFFF000)
def second_pass(self, dtb: int, data: bytes, data_offset: int) -> Optional[Tuple[int, Any]]:
"""PAE top level directory tables contains four entries and the self-referential pointer occurs in the second
level of tables (so as not to use up a full quarter of the space). This is very high in the space, and occurs
in the fourht (last quarter) second-level table. The second-level tables appear always to come sequentially
directly after the real dtb. The value for the real DTB is therefore four page earlier (and the fourth entry
should point back to the `dtb` parameter this function was originally passed.
"""PAE top level directory tables contains four entries and the self-
referential pointer occurs in the second level of tables (so as not to
use up a full quarter of the space). This is very high in the space,
and occurs in the fourht (last quarter) second-level table. The
second-level tables appear always to come sequentially directly after
the real dtb. The value for the real DTB is therefore four page
earlier (and the fourth entry should point back to the `dtb` parameter
this function was originally passed.
Args:
dtb: The identified self-referential pointer that needs validating
@@ -153,7 +161,8 @@ class DtbTestPae(DtbTest):
class DtbSelfReferential(DtbTest):
"""A generic DTB test which looks for a self-referential pointer at *any* index within the page."""
"""A generic DTB test which looks for a self-referential pointer at *any*
index within the page."""
def __init__(self, layer_type: Type[layers.intel.Intel], ptr_struct: str, ptr_reference: int, mask: int) -> None:
super().__init__(layer_type = layer_type, ptr_struct = ptr_struct, ptr_reference = ptr_reference, mask = mask)
@@ -190,7 +199,8 @@ class DtbSelfRef64bit(DtbSelfReferential):
class PageMapScanner(interfaces.layers.ScannerInterface):
"""Scans through all pages using DTB tests to determine a dtb offset and architecture"""
"""Scans through all pages using DTB tests to determine a dtb offset and
architecture."""
overlap = 0x4000
thread_safe = True
tests = [DtbTest32bit(), DtbTest64bit(), DtbTestPae()]
@@ -210,13 +220,14 @@ class PageMapScanner(interfaces.layers.ScannerInterface):
class WintelHelper(interfaces.automagic.AutomagicInterface):
"""Windows DTB finder based on self-referential pointers
"""Windows DTB finder based on self-referential pointers.
This class adheres to the :class:`~volatility.framework.interfaces.automagic.AutomagicInterface` interface
and both determines the directory table base of an intel layer if one hasn't been specified, and constructs
the intel layer if necessary (for example when reconstructing a pre-existing configuration).
It will scan for existing TranslationLayers that do not have a DTB using the :class:`PageMapScanner`"""
It will scan for existing TranslationLayers that do not have a DTB using the :class:`PageMapScanner`
"""
priority = 20
tests = [DtbTest32bit(), DtbTest64bit(), DtbTestPae()]
@@ -272,12 +283,16 @@ class WintelStacker(interfaces.automagic.StackerLayerInterface):
context: interfaces.context.ContextInterface,
layer_name: str,
progress_callback: constants.ProgressCallback = None) -> Optional[interfaces.layers.DataLayerInterface]:
"""Attempts to determine and stack an intel layer on a physical layer where possible
"""Attempts to determine and stack an intel layer on a physical layer
where possible.
Where the DTB scan fails, it attempts a heuristic of checking for the DTB within a specific range.
New versions of windows, with randomized self-referential pointers, appear to always load their dtb within
a small specific range (`0x1a0000` and `0x1b0000`), so instead we scan for all self-referential pointers in
that range, and ignore any that contain multiple self-references (since the DTB is very unlikely to point to
Where the DTB scan fails, it attempts a heuristic of checking
for the DTB within a specific range. New versions of windows,
with randomized self-referential pointers, appear to always load
their dtb within a small specific range (`0x1a0000` and
`0x1b0000`), so instead we scan for all self-referential
pointers in that range, and ignore any that contain multiple
self-references (since the DTB is very unlikely to point to
itself more than once).
"""
base_layer = context.layers[layer_name]
@@ -348,15 +363,15 @@ class WintelStacker(interfaces.automagic.StackerLayerInterface):
class WinSwapLayers(interfaces.automagic.AutomagicInterface):
"""Class to read swap_layers filenames from single-swap-layers,
create the layers and populate the single-layers swap_layers"""
"""Class to read swap_layers filenames from single-swap-layers, create the
layers and populate the single-layers swap_layers."""
def __call__(self,
context: interfaces.context.ContextInterface,
config_path: str,
requirement: interfaces.configuration.RequirementInterface,
progress_callback: constants.ProgressCallback = None) -> None:
"""Finds translation layers that can have swap layers added"""
"""Finds translation layers that can have swap layers added."""
path_join = interfaces.configuration.path_join
self._translation_requirement = self.find_requirements(
context, config_path, requirement, requirements.TranslationLayerRequirement, shortcut = False)
@@ -398,7 +413,7 @@ class WinSwapLayers(interfaces.automagic.AutomagicInterface):
def find_swap_requirement(config: str,
requirement: requirements.TranslationLayerRequirement) \
-> Tuple[str, Optional[requirements.LayerListRequirement]]:
"""Takes a Translation layer and returns its swap_layer requirement"""
"""Takes a Translation layer and returns its swap_layer requirement."""
swap_req = None
for req_name in requirement.requirements:
req = requirement.requirements[req_name]
@@ -411,7 +426,7 @@ class WinSwapLayers(interfaces.automagic.AutomagicInterface):
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
"""Returns the requirements of this plugin"""
"""Returns the requirements of this plugin."""
return [
requirements.ListRequirement(
name = "single_swap_locations",
@@ -1,11 +1,12 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
"""Contains standard Requirement types that all adhere to the :class:`~volatility.framework.interfaces.configuration.RequirementInterface`.
These requirement types allow plugins to request simple information types (such as strings, integers,
etc) as well as indicating what they expect to be in the context (such as particular layers or symboltables).
"""Contains standard Requirement types that all adhere to the :class:`~volatili
ty.framework.interfaces.configuration.RequirementInterface`.
These requirement types allow plugins to request simple information
types (such as strings, integers, etc) as well as indicating what they
expect to be in the context (such as particular layers or symboltables).
"""
import abc
import logging
@@ -20,7 +21,8 @@ vollog = logging.getLogger(__name__)
class MultiRequirement(configuration.RequirementInterface):
"""Class to hold multiple requirements.
Technically the Interface could handle this, but it's an interface, so this is a concrete implementation.
Technically the Interface could handle this, but it's an interface,
so this is a concrete implementation.
"""
def unsatisfied(self, context: configuration.ContextInterface,
@@ -29,33 +31,35 @@ class MultiRequirement(configuration.RequirementInterface):
class BooleanRequirement(configuration.SimpleTypeRequirement):
"""A requirement type that contains a boolean value"""
"""A requirement type that contains a boolean value."""
# Note, this must be a separate class in order to differentiate between Booleans and other instance requirements
class IntRequirement(configuration.SimpleTypeRequirement):
"""A requirement type that contains a single integer"""
"""A requirement type that contains a single integer."""
instance_type = int # type: ClassVar[Type]
class StringRequirement(configuration.SimpleTypeRequirement):
"""A requirement type that contains a single unicode string"""
"""A requirement type that contains a single unicode string."""
# TODO: Maybe add string length limits?
instance_type = str # type: ClassVar[Type]
class URIRequirement(StringRequirement):
"""A requirement type that contains a single unicode string that is a valid URI"""
"""A requirement type that contains a single unicode string that is a valid
URI."""
# TODO: Maybe a a check that to unsatisfied that the path really is a URL?
class BytesRequirement(configuration.SimpleTypeRequirement):
"""A requirement type that contains a byte string"""
"""A requirement type that contains a byte string."""
instance_type = bytes # type: ClassVar[Type]
class ListRequirement(configuration.RequirementInterface):
"""Allows for a list of a specific type of requirement (all of which must be met for this requirement to be met) to be specified
"""Allows for a list of a specific type of requirement (all of which must
be met for this requirement to be met) to be specified.
This roughly correlates to allowing a number of arguments to follow a command line parameter,
such as a list of integers or a list of strings.
@@ -70,7 +74,7 @@ class ListRequirement(configuration.RequirementInterface):
min_elements: Optional[int] = None,
*args,
**kwargs) -> None:
"""Constructs the object
"""Constructs the object.
Args:
element_type: The (requirement) type of each element within the list
@@ -86,7 +90,8 @@ class ListRequirement(configuration.RequirementInterface):
def unsatisfied(self, context: interfaces.context.ContextInterface,
config_path: str) -> Dict[str, configuration.RequirementInterface]:
"""Check the types on each of the returned values and their number and then call the element type's check for each one"""
"""Check the types on each of the returned values and their number and
then call the element type's check for each one."""
config_path = configuration.path_join(config_path, self.name)
default = None
value = self.config_value(context, config_path, default)
@@ -113,10 +118,10 @@ class ListRequirement(configuration.RequirementInterface):
class ChoiceRequirement(configuration.RequirementInterface):
"""Allows one from a choice of strings"""
"""Allows one from a choice of strings."""
def __init__(self, choices: List[str], *args, **kwargs) -> None:
"""Constructs the object
"""Constructs the object.
Args:
choices: A list of possible string options that can be chosen from
@@ -128,7 +133,8 @@ class ChoiceRequirement(configuration.RequirementInterface):
def unsatisfied(self, context: interfaces.context.ContextInterface,
config_path: str) -> Dict[str, configuration.RequirementInterface]:
"""Validates the provided value to ensure it is one of the available choices"""
"""Validates the provided value to ensure it is one of the available
choices."""
config_path = configuration.path_join(config_path, self.name)
value = self.config_value(context, config_path)
if value not in self.choices:
@@ -138,11 +144,12 @@ class ChoiceRequirement(configuration.RequirementInterface):
class ComplexListRequirement(MultiRequirement, configuration.ConfigurableRequirementInterface, metaclass = abc.ABCMeta):
"""Allows a variable length list of requirements"""
"""Allows a variable length list of requirements."""
def unsatisfied(self, context: interfaces.context.ContextInterface,
config_path: str) -> Dict[str, configuration.RequirementInterface]:
"""Validates the provided value to ensure it is one of the available choices"""
"""Validates the provided value to ensure it is one of the available
choices."""
config_path = configuration.path_join(config_path, self.name)
ret_list = super().unsatisfied(context, config_path)
if ret_list:
@@ -162,11 +169,12 @@ class ComplexListRequirement(MultiRequirement, configuration.ConfigurableRequire
@abc.abstractmethod
def construct(self, context: interfaces.context.ContextInterface, config_path: str) -> None:
"""Method for constructing within the context any required elements from subrequirements"""
"""Method for constructing within the context any required elements
from subrequirements."""
@abc.abstractmethod
def new_requirement(self, index) -> configuration.RequirementInterface:
"""Builds a new requirement based on the specified index"""
"""Builds a new requirement based on the specified index."""
def build_configuration(self, context: interfaces.context.ContextInterface, config_path: str,
_: Any) -> configuration.HierarchicalDict:
@@ -187,10 +195,11 @@ class ComplexListRequirement(MultiRequirement, configuration.ConfigurableRequire
class LayerListRequirement(ComplexListRequirement):
"""Allows a variable length list of layers that must exist """
"""Allows a variable length list of layers that must exist."""
def construct(self, context: interfaces.context.ContextInterface, config_path: str) -> None:
"""Method for constructing within the context any required elements from subrequirements"""
"""Method for constructing within the context any required elements
from subrequirements."""
new_config_path = configuration.path_join(config_path, self.name)
num_layers_path = configuration.path_join(new_config_path, "number_of_elements")
number_of_layers = context.config[num_layers_path]
@@ -202,14 +211,15 @@ class LayerListRequirement(ComplexListRequirement):
layer_req.construct(context, new_config_path)
def new_requirement(self, index) -> configuration.RequirementInterface:
"""Constructs a new requirement based on the specified index"""
"""Constructs a new requirement based on the specified index."""
return TranslationLayerRequirement(
name = self.name + str(index), description = "Layer for swap space", optional = False)
class TranslationLayerRequirement(configuration.ConstructableRequirementInterface,
configuration.ConfigurableRequirementInterface):
"""Class maintaining the limitations on what sort of translation layers are acceptable"""
"""Class maintaining the limitations on what sort of translation layers are
acceptable."""
def __init__(self,
name: str,
@@ -218,7 +228,7 @@ class TranslationLayerRequirement(configuration.ConstructableRequirementInterfac
optional: bool = False,
oses: List = None,
architectures: List = None) -> None:
"""Constructs a Translation Layer Requirement
"""Constructs a Translation Layer Requirement.
The configuration option's value will be the name of the layer once it exists in the store
@@ -240,7 +250,8 @@ class TranslationLayerRequirement(configuration.ConstructableRequirementInterfac
def unsatisfied(self, context: interfaces.context.ContextInterface,
config_path: str) -> Dict[str, configuration.RequirementInterface]:
"""Validate that the value is a valid layer name and that the layer adheres to the requirements"""
"""Validate that the value is a valid layer name and that the layer
adheres to the requirements."""
config_path = configuration.path_join(config_path, self.name)
value = self.config_value(context, config_path, None)
if isinstance(value, str):
@@ -270,7 +281,8 @@ class TranslationLayerRequirement(configuration.ConstructableRequirementInterfac
return {config_path: self}
def construct(self, context: interfaces.context.ContextInterface, config_path: str) -> None:
"""Constructs the appropriate layer and adds it based on the class parameter"""
"""Constructs the appropriate layer and adds it based on the class
parameter."""
config_path = configuration.path_join(config_path, self.name)
# Determine the layer name
@@ -295,17 +307,20 @@ class TranslationLayerRequirement(configuration.ConstructableRequirementInterfac
def build_configuration(self, context: interfaces.context.ContextInterface, _: str,
value: Any) -> configuration.HierarchicalDict:
"""Builds the appropriate configuration for the specified requirement"""
"""Builds the appropriate configuration for the specified
requirement."""
return context.layers[value].build_configuration()
class SymbolTableRequirement(configuration.ConstructableRequirementInterface,
configuration.ConfigurableRequirementInterface):
"""Class maintaining the limitations on what sort of symbol spaces are acceptable"""
"""Class maintaining the limitations on what sort of symbol spaces are
acceptable."""
def unsatisfied(self, context: interfaces.context.ContextInterface,
config_path: str) -> Dict[str, configuration.RequirementInterface]:
"""Validate that the value is a valid within the symbol space of the provided context"""
"""Validate that the value is a valid within the symbol space of the
provided context."""
config_path = configuration.path_join(config_path, self.name)
value = self.config_value(context, config_path, None)
if not isinstance(value, str):
@@ -320,7 +335,8 @@ class SymbolTableRequirement(configuration.ConstructableRequirementInterface,
return {}
def construct(self, context: interfaces.context.ContextInterface, config_path: str) -> None:
"""Constructs the symbol space within the context based on the subrequirements"""
"""Constructs the symbol space within the context based on the
subrequirements."""
config_path = configuration.path_join(config_path, self.name)
# Determine the space name
name = context.symbol_space.free_table_name(self.name)
@@ -348,7 +364,8 @@ class SymbolTableRequirement(configuration.ConstructableRequirementInterface,
def build_configuration(self, context: interfaces.context.ContextInterface, _: str,
value: Any) -> configuration.HierarchicalDict:
"""Builds the appropriate configuration for the specified requirement"""
"""Builds the appropriate configuration for the specified
requirement."""
return context.symbol_space[value].build_configuration()
+6 -4
View File
@@ -1,10 +1,11 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
"""Volatility 3 Constants
"""Volatility 3 Constants.
Stores all the constant values that are generally fixed throughout volatility
This includes default scanning block sizes, etc."""
Stores all the constant values that are generally fixed throughout
volatility This includes default scanning block sizes, etc.
"""
import enum
import os.path
import sys
@@ -61,7 +62,8 @@ ProgressCallback = Optional[Callable[[float, str], None]]
class Parallelism(enum.IntEnum):
"""An enumeration listing the different types of parallelism applied to volatility"""
"""An enumeration listing the different types of parallelism applied to
volatility."""
Off = 0
Threading = 1
Multiprocessing = 2
@@ -1,9 +1,10 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
"""Volatility 3 Linux Constants
"""Volatility 3 Linux Constants.
Linux-specific values that aren't found in debug symbols"""
Linux-specific values that aren't found in debug symbols
"""
# arch/x86/include/asm/page_types.h
PAGE_SHIFT = 12
@@ -1,9 +1,10 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
"""Volatility 3 Linux Constants
"""Volatility 3 Linux Constants.
Windows-specific values that aren't found in debug symbols"""
Windows-specific values that aren't found in debug symbols
"""
KERNEL_MODULE_NAMES = ["ntkrnlmp", "ntkrnlpa", "ntkrpamp", "ntoskrnl"]
"""The list of names that kernel modules can have within the windows OS"""
+42 -26
View File
@@ -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:
+26 -18
View File
@@ -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):
+5 -3
View File
@@ -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
+15 -10
View File
@@ -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.
+133 -97
View File
@@ -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
"""
+48 -38
View File
@@ -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."""
+112 -83
View File
@@ -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
+71 -44
View File
@@ -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."""
+22 -14
View File
@@ -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
+56 -42
View File
@@ -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
"""
+52 -44
View File
@@ -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
+7 -5
View File
@@ -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 = []
+32 -22
View File
@@ -18,7 +18,7 @@ vollog = logging.getLogger(__name__)
class Intel(linear.LinearlyMappedLayer):
"""Translation Layer for the Intel IA32 memory mapping"""
"""Translation Layer for the Intel IA32 memory mapping."""
priority = 40
_entry_format = "<I"
@@ -53,14 +53,16 @@ class Intel(linear.LinearlyMappedLayer):
@classproperty
def page_size(cls) -> int:
"""Page size for the intel memory layers
"""Page size for the intel memory layers.
All Intel layers work on 4096 byte pages"""
All Intel layers work on 4096 byte pages
"""
return 1 << cls._page_size_in_bits
@classproperty
def bits_per_register(cls) -> int:
"""Returns the bits_per_register to determine the range of an IntelTranslationLayer"""
"""Returns the bits_per_register to determine the range of an
IntelTranslationLayer."""
return cls._bits_per_register
@classproperty
@@ -77,7 +79,7 @@ class Intel(linear.LinearlyMappedLayer):
@staticmethod
def _mask(value: int, high_bit: int, low_bit: int) -> int:
"""Returns the bits of a value between highbit and lowbit inclusive"""
"""Returns the bits of a value between highbit and lowbit inclusive."""
high_mask = (1 << (high_bit + 1)) - 1
low_mask = (1 << low_bit) - 1
mask = (high_mask ^ low_mask)
@@ -86,13 +88,15 @@ class Intel(linear.LinearlyMappedLayer):
@staticmethod
def _page_is_valid(entry: int) -> bool:
"""Returns whether a particular page is valid based on its entry"""
"""Returns whether a particular page is valid based on its entry."""
return bool(entry & 1)
def _translate(self, offset: int) -> Tuple[int, int, str]:
"""Translates a specific offset based on paging tables
"""Translates a specific offset based on paging tables.
Returns the translated offset, the contiguous pagesize that the translated address lives in and the layer_name that the address lives in
Returns the translated offset, the contiguous pagesize that the
translated address lives in and the layer_name that the address
lives in
"""
entry, position = self._translate_entry(offset)
@@ -105,9 +109,9 @@ class Intel(linear.LinearlyMappedLayer):
return page, 1 << (position + 1), self._base_layer
def _translate_entry(self, offset):
"""Translates a specific offset based on paging tables
"""Translates a specific offset based on paging tables.
Returns the translated entry value
Returns the translated entry value
"""
# Setup the entry and how far we are through the offset
# Position maintains the number of bits left to process
@@ -149,7 +153,7 @@ class Intel(linear.LinearlyMappedLayer):
@functools.lru_cache(1025)
def _get_valid_table(self, base_address: int) -> Optional[bytes]:
"""Extracts the table, validates it and returns it if it's valid"""
"""Extracts the table, validates it and returns it if it's valid."""
table = self._context.layers.read(self._base_layer, base_address, self.page_size)
# If the table is entirely duplicates, then mark the whole table as bad
@@ -158,7 +162,8 @@ class Intel(linear.LinearlyMappedLayer):
return table
def is_valid(self, offset: int, length: int = 1) -> bool:
"""Returns whether the address offset can be translated to a valid address"""
"""Returns whether the address offset can be translated to a valid
address."""
try:
# TODO: Consider reimplementing this, since calls to mapping can call is_valid
return all([
@@ -169,9 +174,11 @@ class Intel(linear.LinearlyMappedLayer):
return False
def mapping(self, offset: int, length: int, ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, str]]:
"""Returns a sorted iterable of (offset, mapped_offset, length, layer) mappings
"""Returns a sorted iterable of (offset, mapped_offset, length, layer)
mappings.
This allows translation layers to provide maps of contiguous regions in one layer
This allows translation layers to provide maps of contiguous
regions in one layer
"""
if length == 0:
try:
@@ -208,7 +215,8 @@ class Intel(linear.LinearlyMappedLayer):
@property
def dependencies(self) -> List[str]:
"""Returns a list of the lower layer names that this layer is dependent upon"""
"""Returns a list of the lower layer names that this layer is dependent
upon."""
return [self._base_layer] + self._swap_layers
@classmethod
@@ -223,7 +231,8 @@ class Intel(linear.LinearlyMappedLayer):
class IntelPAE(Intel):
"""Class for handling Physical Address Extensions for Intel architectures"""
"""Class for handling Physical Address Extensions for Intel
architectures."""
priority = 35
_entry_format = "<Q"
@@ -234,7 +243,8 @@ class IntelPAE(Intel):
class Intel32e(Intel):
"""Class for handling 64-bit (32-bit extensions) for Intel architectures"""
"""Class for handling 64-bit (32-bit extensions) for Intel
architectures."""
priority = 30
_direct_metadata = collections.ChainMap({'architecture': 'Intel64'}, Intel._direct_metadata)
@@ -250,14 +260,14 @@ class WindowsMixin(Intel):
@staticmethod
def _page_is_valid(entry: int) -> bool:
"""Returns whether a particular page is valid based on its entry
"""Returns whether a particular page is valid based on its entry.
Windows uses additional "available" bits to store flags
These flags allow windows to determine whether a page is still valid
Windows uses additional "available" bits to store flags
These flags allow windows to determine whether a page is still valid
Bit 11 is the transition flag, and Bit 10 is the prototype flag
Bit 11 is the transition flag, and Bit 10 is the prototype flag
For more information, see Windows Internals (6th Ed, Part 2, pages 268-269)
For more information, see Windows Internals (6th Ed, Part 2, pages 268-269)
"""
return bool((entry & 1) or ((entry & 1 << 11) and not entry & 1 << 10))
+6 -4
View File
@@ -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
+6 -3
View File
@@ -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):
+4 -2
View File
@@ -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
+21 -18
View File
@@ -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
+18 -14
View File
@@ -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
+6 -5
View File
@@ -37,12 +37,13 @@ vollog = logging.getLogger(__name__)
class ResourceAccessor(object):
"""Object for openning URLs as files (downloading locally first if necessary)"""
"""Object for openning URLs as files (downloading locally first if
necessary)"""
def __init__(self,
progress_callback: Optional[constants.ProgressCallback] = None,
context: Optional[ssl.SSLContext] = None) -> None:
"""Creates a resource accessor
"""Creates a resource accessor.
Note: context is an SSL context, not a volatility context
"""
@@ -54,7 +55,7 @@ class ResourceAccessor(object):
"Available URL handlers: {}".format(", ".join([x.__name__ for x in self._handlers])))
def open(self, url, mode = "rb"):
"""Returns a file-like object for a particular URL opened in mode"""
"""Returns a file-like object for a particular URL opened in mode."""
urllib.request.install_opener(urllib.request.build_opener(*self._handlers))
with contextlib.closing(urllib.request.urlopen(url, context = self._context)) as fp:
@@ -148,7 +149,7 @@ class ResourceAccessor(object):
class JarHandler(urllib.request.BaseHandler):
"""Handles the jar scheme for URIs
"""Handles the jar scheme for URIs.
Reference used for the schema syntax:
http://docs.netkernel.org/book/view/book:mod:reference/doc:layer1:schemes:jar
@@ -159,7 +160,7 @@ class JarHandler(urllib.request.BaseHandler):
@staticmethod
def default_open(req):
"""Handles the request if it's the jar scheme"""
"""Handles the request if it's the jar scheme."""
if req.type == 'jar':
subscheme, remainder = req.full_url.split(":")[1], ":".join(req.full_url.split(":")[2:])
if subscheme != 'file':
@@ -17,8 +17,8 @@ class BytesScanner(layers.ScannerInterface):
self.needle = needle
def __call__(self, data: bytes, data_offset: int) -> Generator[int, None, None]:
"""Runs through the data looking for the needle, and yields all offsets where the needle is found
"""
"""Runs through the data looking for the needle, and yields all offsets
where the needle is found."""
find_pos = data.find(self.needle)
while find_pos >= 0:
if find_pos < self.chunk_size:
@@ -34,8 +34,8 @@ class RegExScanner(layers.ScannerInterface):
self.regex = re.compile(pattern, flags)
def __call__(self, data: bytes, data_offset: int) -> Generator[int, None, None]:
"""Runs through the data looking for the needle, and yields all offsets where the needle is found
"""
"""Runs through the data looking for the needle, and yields all offsets
where the needle is found."""
find_pos = self.regex.finditer(data)
for match in find_pos:
offset = match.start()
@@ -54,7 +54,7 @@ class MultiStringScanner(layers.ScannerInterface):
self._patterns.preprocess()
def __call__(self, data: bytes, data_offset: int) -> Generator[Tuple[int, Union[str, bytes]], None, None]:
"""Runs through the data looking for the needles"""
"""Runs through the data looking for the needles."""
for offset, pattern in self._patterns.search(data):
if offset < self.chunk_size:
yield offset + data_offset, pattern
@@ -7,7 +7,7 @@ from typing import Generator, List, Tuple, Union
class MultiRegexp(object):
"""Algorithm for multi-string matching"""
"""Algorithm for multi-string matching."""
def __init__(self) -> None:
self._pattern_strings = [] # type: List[bytes]
@@ -6,7 +6,7 @@ from typing import Generator, List, Optional, Set, Tuple, Union
class WuManber(object):
"""Algorithm for multi-string matching"""
"""Algorithm for multi-string matching."""
def __init__(self, block_size: int = 3) -> None:
# Set a suitably large minimum
@@ -29,7 +29,7 @@ class WuManber(object):
self._patterns.append(pattern)
def preprocess(self) -> None:
"""Preprocesses the patterns by populating the three arrays"""
"""Preprocesses the patterns by populating the three arrays."""
if not self._patterns:
raise ValueError("No Linux symbols/banner patterns available")
@@ -50,15 +50,17 @@ class WuManber(object):
self._hashes[hashval].add(pattern)
def _hash_function(self, value_bytes: bytes) -> int:
"""Hash function to bucket _block_size number of bytes into sets
"""Hash function to bucket _block_size number of bytes into sets.
If this hash_function changes, the maximum number of responses must be set in self._maximum_hash
If this hash_function changes, the maximum number of responses
must be set in self._maximum_hash
"""
return (value_bytes[0] << 5) + (value_bytes[1] << 3) + value_bytes[2]
def search(self, haystack: bytes) \
-> Generator[Tuple[int, Union[str, bytes]], None, None]:
"""Search through a large body of data for patterns previously added with add_pattern"""
"""Search through a large body of data for patterns previously added
with add_pattern."""
if not isinstance(haystack, bytes):
raise TypeError("Search haystack must be a byte string")
if self._shift is None:
+14 -9
View File
@@ -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
+5 -4
View File
@@ -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
+89 -66
View File
@@ -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):
+25 -21
View File
@@ -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))
+3 -3
View File
@@ -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)
+4 -3
View File
@@ -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
+2 -1
View File
@@ -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]:
+2 -1
View File
@@ -14,7 +14,8 @@ vollog = logging.getLogger(__name__)
class LayerWriter(plugins.PluginInterface):
"""Runs the automagics and lists out the generated layers if no layer name is specified, otherwise writes out the named layer"""
"""Runs the automagics and lists out the generated layers if no layer name
is specified, otherwise writes out the named layer."""
default_output_name = "output.raw"
default_block_size = 0x500000
@@ -1,7 +1,8 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
"""All core linux plugins
"""All core linux plugins.
These modules should only be imported from volatility.plugins NOT volatility.framework.plugins
These modules should only be imported from volatility.plugins NOT
volatility.framework.plugins
"""
+3 -4
View File
@@ -1,9 +1,8 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
"""A module containing a collection of plugins that produce data
typically found in Linux's /proc file system.
"""
"""A module containing a collection of plugins that produce data typically
found in Linux's /proc file system."""
import datetime
import struct
@@ -20,7 +19,7 @@ from volatility.plugins.linux import pslist
class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Recovers bash command history from memory"""
"""Recovers bash command history from memory."""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -1,9 +1,8 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
"""A module containing a collection of plugins that produce data
typically found in Linux's /proc file system.
"""
"""A module containing a collection of plugins that produce data typically
found in Linux's /proc file system."""
import logging
from typing import List
@@ -18,7 +17,7 @@ vollog = logging.getLogger(__name__)
class Check_afinfo(plugins.PluginInterface):
"""Verifies the operation function pointers of network protocols"""
"""Verifies the operation function pointers of network protocols."""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -1,9 +1,8 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
"""A module containing a collection of plugins that produce data
typically found in Linux's /proc file system.
"""
"""A module containing a collection of plugins that produce data typically
found in Linux's /proc file system."""
import logging
from typing import List
@@ -25,7 +24,7 @@ except ImportError:
class Check_syscall(plugins.PluginInterface):
"""Check system call table for hooks"""
"""Check system call table for hooks."""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -36,9 +35,7 @@ class Check_syscall(plugins.PluginInterface):
]
def _get_table_size_next_symbol(self, table_addr, ptr_sz, vmlinux):
"""
Returns the size of the table based on the next symbol
"""
"""Returns the size of the table based on the next symbol."""
ret = 0
sym_table = self.context.symbol_space[vmlinux.name]
@@ -58,10 +55,9 @@ class Check_syscall(plugins.PluginInterface):
return ret
def _get_table_size_meta(self, vmlinux):
"""
returns the number of symbols that start with __syscall_meta__
this is a fast way to determine the number of system calls, but not the most accurate
"""
"""returns the number of symbols that start with __syscall_meta__ this
is a fast way to determine the number of system calls, but not the most
accurate."""
return len(
[sym for sym in self.context.symbol_space[vmlinux.name].symbols if sym.startswith("__syscall_meta__")])
@@ -77,11 +73,9 @@ class Check_syscall(plugins.PluginInterface):
return table_size
def _get_table_info_disassembly(self, ptr_sz, vmlinux):
"""
Find the size of the system call table by disassembling functions
that immediately reference it in their first isntruction
This is in the form 'cmp reg,NR_syscalls'
"""
"""Find the size of the system call table by disassembling functions
that immediately reference it in their first isntruction This is in the
form 'cmp reg,NR_syscalls'."""
table_size = 0
if not has_capstone:
+3 -4
View File
@@ -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]:
+16 -7
View File
@@ -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)
+3 -4
View File
@@ -1,9 +1,8 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
"""A module containing a collection of plugins that produce data
typically found in Linux's /proc file system.
"""
"""A module containing a collection of plugins that produce data typically
found in Linux's /proc file system."""
import logging
from typing import List
@@ -18,7 +17,7 @@ vollog = logging.getLogger(__name__)
class Lsof(plugins.PluginInterface):
"""Lists all memory maps for all processes"""
"""Lists all memory maps for all processes."""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -15,7 +15,7 @@ from volatility.framework.renderers import format_hints
class Malfind(interfaces_plugins.PluginInterface):
"""Lists process memory ranges that potentially contain injected code"""
"""Lists process memory ranges that potentially contain injected code."""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -26,9 +26,8 @@ class Malfind(interfaces_plugins.PluginInterface):
]
def _list_injections(self, task):
"""Generate memory regions for a process that may contain
injected code.
"""
"""Generate memory regions for a process that may contain injected
code."""
proc_layer_name = task.add_process_layer()
if not proc_layer_name:
+3 -4
View File
@@ -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):
+19 -2
View File
@@ -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)
+4 -3
View File
@@ -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
+3 -4
View File
@@ -1,9 +1,8 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
"""A module containing a collection of plugins that produce data
typically found in mac's /proc file system.
"""
"""A module containing a collection of plugins that produce data typically
found in mac's /proc file system."""
import datetime
import struct
@@ -20,7 +19,7 @@ from volatility.framework.symbols.linux.bash import BashIntermedSymbols
class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Recovers bash command history from memory"""
"""Recovers bash command history from memory."""
@classmethod
def get_requirements(cls):
@@ -15,7 +15,7 @@ vollog = logging.getLogger(__name__)
class Check_syscall(plugins.PluginInterface):
"""Check system call table for hooks"""
"""Check system call table for hooks."""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -17,7 +17,7 @@ vollog = logging.getLogger(__name__)
class Check_sysctl(plugins.PluginInterface):
"""Check sysctl handlers for hooks"""
"""Check sysctl handlers for hooks."""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -16,7 +16,7 @@ vollog = logging.getLogger(__name__)
class Check_trap_table(plugins.PluginInterface):
"""Check mach trap table for hooks"""
"""Check mach trap table for hooks."""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
+13 -5
View File
@@ -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)
+1 -1
View File
@@ -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):
+3 -4
View File
@@ -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:
+1 -1
View File
@@ -16,7 +16,7 @@ vollog = logging.getLogger(__name__)
class Netstat(plugins.PluginInterface):
"""Lists all network connections for all processes"""
"""Lists all network connections for all processes."""
@classmethod
def get_requirements(cls):
@@ -12,7 +12,7 @@ from volatility.framework.renderers import format_hints
class Maps(interfaces_plugins.PluginInterface):
"""Lists process memory ranges that potentially contain injected code"""
"""Lists process memory ranges that potentially contain injected code."""
@classmethod
def get_requirements(cls):
+2 -2
View File
@@ -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]:
+12 -2
View File
@@ -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)
+4 -3
View File
@@ -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
+12 -2
View File
@@ -13,7 +13,7 @@ vollog = logging.getLogger(__name__)
class Tasks(pslist.PsList):
"""Lists the processes present in a particular mac memory image"""
"""Lists the processes present in a particular mac memory image."""
@classmethod
def list_tasks(cls,
@@ -22,7 +22,17 @@ class Tasks(pslist.PsList):
darwin_symbols: str,
filter_func: Callable[[int], bool] = lambda _: False) -> \
Iterable[interfaces.objects.ObjectInterface]:
"""Lists all the tasks in the primary layer"""
"""Lists all the tasks in the primary layer.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
darwin_symbols: The name of the table containing the kernel symbols
filter_func: A function which takes a task object and returns True if the task should be ignored/filtered
Returns:
The list of task objects from the `layer_name` layer's `tasks` list after filtering
"""
mac.MacUtilities.aslr_mask_symbol_table(context, darwin_symbols, layer_name)
@@ -18,7 +18,7 @@ vollog = logging.getLogger(__name__)
class Check_syscall(plugins.PluginInterface):
"""Check system call table for hooks"""
"""Check system call table for hooks."""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
+11 -6
View File
@@ -26,18 +26,21 @@ class TimeLinerType(enum.IntEnum):
class TimeLinerInterface(metaclass = abc.ABCMeta):
"""Interface defining methods that timeliner will use to generate a body file"""
"""Interface defining methods that timeliner will use to generate a body
file."""
@abc.abstractmethod
def generate_timeline(self) -> Generator[Tuple[str, TimeLinerType, datetime.datetime], None, None]:
"""Method generates Tuples of (description, timestamp_type, timestamp)
These need not be generated in any particular order, sorting will be done later
These need not be generated in any particular order, sorting
will be done later
"""
class Timeliner(interfaces.plugins.PluginInterface):
"""Runs all relevant plugins that provide time related information and orders the results by time"""
"""Runs all relevant plugins that provide time related information and
orders the results by time."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -82,7 +85,8 @@ class Timeliner(interfaces.plugins.PluginInterface):
]
def _generator(self, runable_plugins: List[TimeLinerInterface]) -> Optional[Iterable[Tuple[int, Tuple]]]:
"""Takes a timeline, sorts it and output the data from each relevant row from each plugin"""
"""Takes a timeline, sorts it and output the data from each relevant
row from each plugin."""
# Generate the results for each plugin
for plugin in runable_plugins:
plugin_name = plugin.__class__.__name__
@@ -112,7 +116,7 @@ class Timeliner(interfaces.plugins.PluginInterface):
yield data
def run(self):
"""Isolate each plugin and run it"""
"""Isolate each plugin and run it."""
# Use all the plugins if there's no filter
self.usable_plugins = self.usable_plugins or self.get_usable_plugins()
@@ -153,6 +157,7 @@ class Timeliner(interfaces.plugins.PluginInterface):
generator = self._generator(runable_plugins))
def build_configuration(self):
"""Builds the configuration to save for the plugin such that it can be reconstructed"""
"""Builds the configuration to save for the plugin such that it can be
reconstructed."""
vollog.warning("Unable to record configuration data for the timeliner plugin")
return []
@@ -1,7 +1,8 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
"""All core windows plugins
"""All core windows plugins.
These modules should only be imported from volatility.plugins NOT volatility.framework.plugins
These modules should only be imported from volatility.plugins NOT
volatility.framework.plugins
"""
@@ -12,7 +12,7 @@ from volatility.plugins.windows import pslist
class CmdLine(interfaces_plugins.PluginInterface):
"""Lists process command line arguments"""
"""Lists process command line arguments."""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -22,7 +22,7 @@ vollog = logging.getLogger(__name__)
class DllDump(interfaces_plugins.PluginInterface):
"""Dumps process memory ranges as DLLs"""
"""Dumps process memory ranges as DLLs."""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -12,7 +12,7 @@ from volatility.plugins.windows import pslist
class DllList(interfaces_plugins.PluginInterface):
"""Lists the loaded modules in a particular windows memory image"""
"""Lists the loaded modules in a particular windows memory image."""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -21,7 +21,7 @@ MAJOR_FUNCTIONS = [
class DriverIrp(plugins.PluginInterface):
"""List IRPs for drivers in a particular windows memory image"""
"""List IRPs for drivers in a particular windows memory image."""
@classmethod
def get_requirements(cls):
@@ -13,7 +13,7 @@ from volatility.framework.renderers import format_hints
class DriverScan(plugins.PluginInterface):
"""Scans for drivers present in a particular windows memory image"""
"""Scans for drivers present in a particular windows memory image."""
_version = (1, 0, 0)
@@ -31,7 +31,16 @@ class DriverScan(plugins.PluginInterface):
layer_name: str,
symbol_table: str) -> \
Iterable[interfaces.objects.ObjectInterface]:
"""Scans for drivers using the poolscanner module and constraints"""
"""Scans for drivers using the poolscanner module and constraints.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
Returns:
A list of Driver objects as found from the `layer_name` layer based on Driver pool signatures
"""
constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'Dri\xf6', b'Driv'])
@@ -4,15 +4,16 @@
from typing import Iterable
import volatility.plugins.windows.poolscanner as poolscanner
import volatility.framework.interfaces.plugins as plugins
from volatility.framework import renderers, interfaces, exceptions
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
import volatility.plugins.windows.poolscanner as poolscanner
class FileScan(plugins.PluginInterface):
"""Scans for file objects present in a particular windows memory image"""
"""Scans for file objects present in a particular windows memory image."""
@classmethod
def get_requirements(cls):
@@ -28,7 +29,16 @@ class FileScan(plugins.PluginInterface):
layer_name: str,
symbol_table: str) -> \
Iterable[interfaces.objects.ObjectInterface]:
"""Scans for file objects using the poolscanner module and constraints"""
"""Scans for file objects using the poolscanner module and constraints.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
Returns:
A list of File objects as found from the `layer_name` layer based on File pool signatures
"""
constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'Fil\xe5', b'File'])
+35 -19
View File
@@ -3,7 +3,7 @@
#
import logging
from typing import List, Optional
from typing import List, Optional, Dict
import volatility.plugins.windows.pslist as pslist
@@ -24,7 +24,7 @@ except ImportError:
class Handles(interfaces_plugins.PluginInterface):
"""Lists process open handles"""
"""Lists process open handles."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -46,8 +46,11 @@ class Handles(interfaces_plugins.PluginInterface):
def _decode_pointer(self, value, magic):
"""Windows encodes pointers to objects and decodes them on the fly
before using them. This function mimics the decoding routine so we
can generate the proper pointer values as well."""
before using them.
This function mimics the decoding routine so we can generate the
proper pointer values as well.
"""
value = value & 0xFFFFFFFFFFFFFFF8
value = value >> magic
@@ -57,8 +60,8 @@ class Handles(interfaces_plugins.PluginInterface):
return value
def _get_item(self, handle_table_entry, handle_value):
"""Given a handle table entry (_HANDLE_TABLE_ENTRY) structure from
a process' handle table, determine where the corresponding object's
"""Given a handle table entry (_HANDLE_TABLE_ENTRY) structure from a
process' handle table, determine where the corresponding object's
_OBJECT_HEADER can be found."""
virtual = self.config["primary"]
@@ -91,10 +94,12 @@ class Handles(interfaces_plugins.PluginInterface):
return object_header
def find_sar_value(self):
"""Locate ObpCaptureHandleInformationEx if it exists in the
sample. Once found, parse it for the SAR value that we need
to decode pointers in the _HANDLE_TABLE_ENTRY which allows us
to find the associated _OBJECT_HEADER."""
"""Locate ObpCaptureHandleInformationEx if it exists in the sample.
Once found, parse it for the SAR value that we need to decode
pointers in the _HANDLE_TABLE_ENTRY which allows us to find the
associated _OBJECT_HEADER.
"""
if self._sar_value is None:
@@ -128,14 +133,25 @@ class Handles(interfaces_plugins.PluginInterface):
return self._sar_value
@classmethod
def list_objects(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str) -> dict:
def list_objects(cls, context: interfaces.context.ContextInterface, layer_name: str,
symbol_table: str) -> Dict[int, str]:
"""List the executive object types (_OBJECT_TYPE) using the
ObTypeIndexTable or ObpObjectTypes symbol (differs per OS).
This method will be necessary for determining what type of
object we have given an object header.
ObTypeIndexTable or ObpObjectTypes symbol (differs per OS). This method
will be necessary for determining what type of object we have given an
object header.
Note: The object type index map was hard coded into profiles
in vol2, but we generate it dynamically now."""
Note:
The object type index map was hard coded into profiles in previous versions of volatility.
It is now generated dynamically.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
Returns:
A mapping of type indicies to type names
"""
type_map = {}
@@ -150,7 +166,7 @@ class Handles(interfaces_plugins.PluginInterface):
ptrs = ntkrnlmp.object(
object_type = "array", offset = table_addr, subtype = ntkrnlmp.get_type("pointer"), count = 100)
for i, ptr in enumerate(ptrs): #type: ignore
for i, ptr in enumerate(ptrs): # type: ignore
# the first entry in the table is always null. break the
# loop when we encounter the first null entry after that
if i > 0 and ptr == 0:
@@ -182,8 +198,8 @@ class Handles(interfaces_plugins.PluginInterface):
return context.object(symbol_table + constants.BANG + "unsigned int", layer_name, offset = kvo + offset)
def _make_handle_array(self, offset, level, depth = 0):
"""Parse a process' handle table and yield valid handle table
entries, going as deep into the table "levels" as necessary."""
"""Parse a process' handle table and yield valid handle table entries,
going as deep into the table "levels" as necessary."""
virtual = self.config["primary"]
kvo = self.context.layers[virtual].config['kernel_virtual_offset']
+3 -3
View File
@@ -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]:
+19 -10
View File
@@ -1,6 +1,7 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
from typing import Iterable, Tuple
import volatility.plugins.windows.pslist as pslist
import volatility.plugins.windows.vadinfo as vadinfo
@@ -13,7 +14,7 @@ from volatility.framework.renderers import format_hints
class Malfind(interfaces.plugins.PluginInterface):
"""Lists process memory ranges that potentially contain injected code"""
"""Lists process memory ranges that potentially contain injected code."""
@classmethod
def get_requirements(cls):
@@ -27,16 +28,18 @@ class Malfind(interfaces.plugins.PluginInterface):
]
@classmethod
def is_vad_empty(self, proc_layer, vad):
"""Check if a VAD region is either entirely unavailable
due to paging, entirely consisting of zeros, or a
combination of the two. This helps ignore false positives
whose VAD flags match task._injection_filter requirements
but there's no data and thus not worth reporting it.
def is_vad_empty(cls, proc_layer, vad):
"""Check if a VAD region is either entirely unavailable due to paging,
entirely consisting of zeros, or a combination of the two. This helps
ignore false positives whose VAD flags match task._injection_filter
requirements but there's no data and thus not worth reporting it.
Args:
proc_layer: the process layer
vad: the MMVAD structure to test
Returns:
A boolean indicating whether a vad is empty or not
"""
CHUNK_SIZE = 0x1000
@@ -55,12 +58,18 @@ class Malfind(interfaces.plugins.PluginInterface):
@classmethod
def list_injections(cls, context: interfaces.context.ContextInterface, symbol_table: str,
proc: interfaces.objects.ObjectInterface):
"""Generate memory regions for a process that may contain
injected code.
proc: interfaces.objects.ObjectInterface
) -> Iterable[Tuple[interfaces.objects.ObjectInterface, bytes]]:
"""Generate memory regions for a process that may contain injected
code.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
symbol_table: The name of the table containing the kernel symbols
proc: an _EPROCESS instance
Returns:
An iterable of VAD instances and the first 64 bytes of data containing in that region
"""
proc_layer_name = proc.add_process_layer()
@@ -18,7 +18,7 @@ vollog = logging.getLogger(__name__)
class ModDump(interfaces.plugins.PluginInterface):
"""Dumps kernel modules"""
"""Dumps kernel modules."""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -41,8 +41,14 @@ class ModDump(interfaces.plugins.PluginInterface):
the primary/kernel layer. Then keep one layer per session by cycling
through the process list.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
pids: A list of process identifiers to include exclusively or None for no filter
Returns:
<list> of layer names
A list of session layer names
"""
seen_ids = [] # type: List[interfaces.objects.ObjectInterface]
filter_func = pslist.PsList.create_pid_filter(pids or [])
@@ -72,15 +78,18 @@ class ModDump(interfaces.plugins.PluginInterface):
@classmethod
def find_session_layer(cls, context: interfaces.context.ContextInterface, session_layers: Iterable[str],
base_address: int):
"""Given a base address and a list of layer names, find a
layer that can access the specified address.
"""Given a base address and a list of layer names, find a layer that
can access the specified address.
Args:
session_layers: <list> of layer names
base_address: <int> the base address
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
session_layers: A list of session layer names
base_address: The base address to identify the layers that can access it
Returns:
layer name (or None)
Layer name or None if no layers that contain the base address can be found
"""
for layer_name in session_layers:
@@ -13,7 +13,7 @@ from volatility.framework.renderers import format_hints
class ModScan(plugins.PluginInterface):
"""Scans for modules present in a particular windows memory image"""
"""Scans for modules present in a particular windows memory image."""
@classmethod
def get_requirements(cls):
@@ -29,7 +29,16 @@ class ModScan(plugins.PluginInterface):
layer_name: str,
symbol_table: str) -> \
Iterable[interfaces.objects.ObjectInterface]:
"""Scans for modules using the poolscanner module and constraints"""
"""Scans for modules using the poolscanner module and constraints.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
Returns:
A list of Driver objects as found from the `layer_name` layer based on Driver pool signatures
"""
constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'MmLd'])
@@ -2,7 +2,7 @@
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
from typing import List
from typing import List, Iterable
from volatility.framework import constants
from volatility.framework import exceptions, interfaces
@@ -12,7 +12,7 @@ from volatility.framework.renderers import format_hints
class Modules(interfaces.plugins.PluginInterface):
"""Lists the loaded kernel modules"""
"""Lists the loaded kernel modules."""
_version = (1, 0, 0)
@@ -46,8 +46,18 @@ class Modules(interfaces.plugins.PluginInterface):
))
@classmethod
def list_modules(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str):
"""Lists all the modules in the primary layer"""
def list_modules(cls, context: interfaces.context.ContextInterface, layer_name: str,
symbol_table: str) -> Iterable[interfaces.objects.ObjectInterface]:
"""Lists all the modules in the primary layer.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
Returns:
A list of Modules as retrieved from PsLoadedModuleList
"""
kvo = context.layers[layer_name].config['kernel_virtual_offset']
ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo)
@@ -4,15 +4,16 @@
from typing import Iterable
import volatility.plugins.windows.poolscanner as poolscanner
import volatility.framework.interfaces.plugins as plugins
from volatility.framework import renderers, interfaces, exceptions
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
import volatility.plugins.windows.poolscanner as poolscanner
class MutantScan(plugins.PluginInterface):
"""Scans for mutexes present in a particular windows memory image"""
"""Scans for mutexes present in a particular windows memory image."""
@classmethod
def get_requirements(cls):
@@ -28,7 +29,16 @@ class MutantScan(plugins.PluginInterface):
layer_name: str,
symbol_table: str) -> \
Iterable[interfaces.objects.ObjectInterface]:
"""Scans for mutants using the poolscanner module and constraints"""
"""Scans for mutants using the poolscanner module and constraints.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
Returns:
A list of Mutant objects found by scanning memory for the Mutant pool signatures
"""
constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'Mut\xe1', b'Muta'])
@@ -22,8 +22,8 @@ vollog = logging.getLogger(__name__)
# TODO: When python3.5 is no longer supported, make this enum.IntFlag
class PoolType(enum.IntEnum):
"""Class to maintain the different possible PoolTypes
The values must be integer powers of 2"""
"""Class to maintain the different possible PoolTypes The values must be
integer powers of 2."""
PAGED = 1
NONPAGED = 2
@@ -31,7 +31,8 @@ class PoolType(enum.IntEnum):
class PoolConstraint:
"""Class to maintain tag/size/index/type information about Pool header tags"""
"""Class to maintain tag/size/index/type information about Pool header
tags."""
def __init__(self,
tag: bytes,
@@ -110,24 +111,42 @@ class PoolHeaderScanner(interfaces.layers.ScannerInterface):
def os_distinguisher(version_check: Callable[[Tuple[int, ...]], bool],
fallback_checks: List[Tuple[str, Optional[str], bool]]
) -> Callable[[interfaces.context.ContextInterface, str], bool]:
"""Distinguishes a symbol table as being above a particular version or point
"""Distinguishes a symbol table as being above a particular version or
point.
This will primarily check the version metadata first and foremost.
If that metadata isn't available then each item in the fallback_checks is tested.
If invert is specified then the result will be true if the version is less than that specified, or in the case of
fallback, if any of the fallback checks is successful.
This will primarily check the version metadata first and foremost.
If that metadata isn't available then each item in the fallback_checks is tested.
If invert is specified then the result will be true if the version is less than that specified, or in the case of
fallback, if any of the fallback checks is successful.
A fallback check is made up of:
* a symbol or type name
* a member name (implying that the value before was a type name)
* whether that symbol, type or member must be present or absent for the symbol table to be more above the required point
A fallback check is made up of:
* a symbol or type name
* a member name (implying that the value before was a type name)
* whether that symbol, type or member must be present or absent for the symbol table to be more above the required point
Note: Specifying that a member must not be present includes the whole type not being present too (ie, either will pass the test)
Note:
Specifying that a member must not be present includes the whole type not being present too (ie, either will pass the test)
Args:
version_check: Function that takes a 4-tuple version and returns whether whether the provided version is above a particular point
fallback_checks: A list of symbol/types/members of types, and whether they must be present to be above the required point
Returns:
A function that takes a context and a symbol table name and determines whether that symbol table passes the distinguishing checks
"""
# try the primary method based on the pe version in the ISF
@functools.wraps(version_check)
def method(context: interfaces.context.ContextInterface, symbol_table: str) -> bool:
"""
Args:
context: The context that contains the symbol table named `symbol_table`
symbol_table: Name of the symbol table within the context to distinguish the version of
Returns:
True if the symbol table is of the required version
"""
try:
pe_version = context.symbol_space[symbol_table].metadata.pe_version
@@ -160,7 +179,7 @@ def os_distinguisher(version_check: Callable[[Tuple[int, ...]], bool],
class PoolScanner(plugins.PluginInterface):
"""A generic pool scanner plugin"""
"""A generic pool scanner plugin."""
_version = (1, 0, 0)
@@ -210,6 +229,13 @@ class PoolScanner(plugins.PluginInterface):
The tags_filter is a list of pool tags, and the associated
PoolConstraints are returned. If tags_filter is empty or
not supplied, then all builtin constraints are returned.
Args:
symbol_table: The name of the symbol table to prepend to the types used
tags_filter: List of tags to return or None to return all
Returns:
A list of well-known constructed PoolConstraints that match the provided tags
"""
builtins = [
@@ -316,6 +342,17 @@ class PoolScanner(plugins.PluginInterface):
constraints: List[PoolConstraint]) \
-> Generator[Tuple[
PoolConstraint, interfaces.objects.ObjectInterface, interfaces.objects.ObjectInterface], None, None]:
"""
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
constraints: List of pool constraints used to limit the scan results
Returns:
Iterable of tuples, containing the constraint that matched, the object from memory, the object header used to determine the object
"""
# get the object type map
type_map = handles.Handles.list_objects(context = context, layer_name = layer_name, symbol_table = symbol_table)
@@ -357,8 +394,22 @@ class PoolScanner(plugins.PluginInterface):
alignment: int = 8,
progress_callback: Optional[constants.ProgressCallback] = None) \
-> Generator[Tuple[PoolConstraint, interfaces.objects.ObjectInterface], None, None]:
"""Returns the _POOL_HEADER object (based on the symbol_table template) after scanning through layer_name
returning all headers that match any of the constraints provided. Only one constraint can be provided per tag"""
"""Returns the _POOL_HEADER object (based on the symbol_table template)
after scanning through layer_name returning all headers that match any
of the constraints provided. Only one constraint can be provided per
tag.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
pool_constraints: List of pool constraints used to limit the scan results
alignment: An optional value that all pool headers will be aligned to
progress_callback: An optional function to provide progress feedback whilst scanning
Returns:
An Iterable of pool constraints and the pool headers associated with them
"""
# Setup the pattern
constraint_lookup = {} # type: Dict[bytes, PoolConstraint]
for constraint in pool_constraints:
@@ -21,7 +21,7 @@ vollog = logging.getLogger(__name__)
class ProcDump(interfaces_plugins.PluginInterface):
"""Dumps process executable images"""
"""Dumps process executable images."""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
+31 -2
View File
@@ -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']
+11 -2
View File
@@ -14,7 +14,7 @@ import volatility.plugins.windows.poolscanner as poolscanner
class PsScan(plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Scans for processes present in a particular windows memory image"""
"""Scans for processes present in a particular windows memory image."""
@classmethod
def get_requirements(cls):
@@ -30,7 +30,16 @@ class PsScan(plugins.PluginInterface, timeliner.TimeLinerInterface):
layer_name: str,
symbol_table: str) -> \
Iterable[interfaces.objects.ObjectInterface]:
"""Scans for processes using the poolscanner module and constraints"""
"""Scans for processes using the poolscanner module and constraints.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
Returns:
A list of processes found by scanning the `layer_name` layer for process pool signatures
"""
constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'Pro\xe3', b'Proc'])
@@ -10,7 +10,8 @@ from volatility.plugins.windows import pslist
class PsTree(pslist.PsList):
"""Plugin for listing processes in a tree based on their parent process ID """
"""Plugin for listing processes in a tree based on their parent process
ID."""
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
@@ -19,7 +20,7 @@ class PsTree(pslist.PsList):
self._children = {} # type: Dict[int, Set[int]]
def find_level(self, pid: objects.Pointer) -> None:
"""Finds how deep the pid is in the processes list"""
"""Finds how deep the pid is in the processes list."""
seen = set([])
seen.add(pid)
level = 0
@@ -33,7 +34,7 @@ class PsTree(pslist.PsList):
self._levels[pid] = level
def _generator(self):
"""Generates the Tree of processes"""
"""Generates the Tree of processes."""
for proc in self.list_processes(self.context, self.config['primary'], self.config['nt_symbols']):
if not self.config.get('physical', self.PHYSICAL_DEFAULT):
@@ -1,7 +1,8 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0
#
"""All core windows registry plugins
"""All core windows registry plugins.
These modules should only be imported from volatility.plugins NOT volatility.framework.plugins
These modules should only be imported from volatility.plugins NOT
volatility.framework.plugins
"""
@@ -13,7 +13,7 @@ vollog = logging.getLogger(__name__)
class HiveList(plugins.PluginInterface):
"""Lists the registry hives present in a particular memory image"""
"""Lists the registry hives present in a particular memory image."""
_version = (1, 0, 0)
@@ -41,8 +41,18 @@ class HiveList(plugins.PluginInterface):
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
filter_string: None = None) -> Iterator[interfaces.objects.ObjectInterface]:
"""Lists all the hives in the primary layer"""
filter_string: str = None) -> Iterator[interfaces.objects.ObjectInterface]:
"""Lists all the hives in the primary layer.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
filter_string: A string which must be present in the hive name if specified
Returns:
The list of registry hives from the `layer_name` layer as filtered against using the `filter_string`
"""
# We only use the object factory to demonstrate how to use one
kvo = context.layers[layer_name].config['kernel_virtual_offset']
@@ -13,7 +13,8 @@ from volatility.framework.renderers import format_hints
class HiveScan(plugins.PluginInterface):
"""Scans for registry hives present in a particular windows memory image"""
"""Scans for registry hives present in a particular windows memory
image."""
@classmethod
def get_requirements(cls):
@@ -29,7 +30,16 @@ class HiveScan(plugins.PluginInterface):
layer_name: str,
symbol_table: str) -> \
Iterable[interfaces.objects.ObjectInterface]:
"""Scans for hives using the poolscanner module and constraints"""
"""Scans for hives using the poolscanner module and constraints.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
Returns:
A list of Hive objects as found from the `layer_name` layer based on Hive pool signatures
"""
constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'CM10'])
@@ -17,7 +17,7 @@ vollog = logging.getLogger(__name__)
class PrintKey(interfaces.plugins.PluginInterface):
"""Lists the registry keys under a hive or specific key value"""
"""Lists the registry keys under a hive or specific key value."""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -36,8 +36,17 @@ class PrintKey(interfaces.plugins.PluginInterface):
@classmethod
def hive_walker(cls, hive: RegistryHive, node_path: Sequence[objects.StructType] = None,
recurse: bool = False) -> Generator:
"""Walks through a set of nodes from a given node (last one in node_path).
Avoids loops by not traversing into nodes already present in the node_path
"""Walks through a set of nodes from a given node (last one in
node_path). Avoids loops by not traversing into nodes already present
in the node_path.
Args:
hive: The registry hive to walk
node_path: The list of nodes that make up the
recurse: Traverse down the node tree or stay only on the same level
Yields:
The depth, and a tuple of results (last write time, hive offset, type, path, name, data and volatile)
"""
if not node_path:
node_path = [hive.get_node(hive.root_cell_offset)]
@@ -100,7 +109,7 @@ class PrintKey(interfaces.plugins.PluginInterface):
symbol_table: str,
offset: int = None,
key: str = None):
"""Walks through a registry, hive by hive"""
"""Walks through a registry, hive by hive."""
if offset is None:
try:
hive_offsets = [
@@ -20,7 +20,7 @@ vollog = logging.getLogger(__name__)
class UserAssist(interfaces.plugins.PluginInterface):
"""Print userassist registry keys and information"""
"""Print userassist registry keys and information."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -41,7 +41,8 @@ class UserAssist(interfaces.plugins.PluginInterface):
]
def parse_userassist_data(self, reg_val):
"""Reads the raw data of a _CM_KEY_VALUE and returns a dict of userassist fields"""
"""Reads the raw data of a _CM_KEY_VALUE and returns a dict of
userassist fields."""
item = {
"id": renderers.UnparsableValue(),
@@ -95,7 +96,8 @@ class UserAssist(interfaces.plugins.PluginInterface):
return item
def _determine_userassist_type(self) -> None:
"""Determine the userassist type and size depending on the OS version"""
"""Determine the userassist type and size depending on the OS
version."""
if self._win7 is True:
self._userassist_type_name = "_VOL_USERASSIST_TYPES_7"

Some files were not shown because too many files have changed in this diff Show More