From 0abd2e53abefd0856c83bfad2cf61ab500868d38 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 5 Jun 2022 10:56:42 +0100 Subject: [PATCH 01/15] Pyinstaller: Fix path need to current directory to be correct --- vol.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vol.spec b/vol.spec index 42b69af3f..666526dde 100644 --- a/vol.spec +++ b/vol.spec @@ -26,7 +26,7 @@ except ImportError: # Volatility must be findable in sys.path in order for collect_submodules to work # This adds the current working directory, which should usually do the trick -sys.path.append(os.getcwd()) +sys.path.append(os.path.dirname(os.path.abspath(SPEC))) vol_analysis = Analysis(['vol.py'], pathex = [], From db3408bdaa978de5b23eecd2d4411df8a6de1f16 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 5 Jun 2022 22:23:30 +0100 Subject: [PATCH 02/15] Windows: Extend the pdb support to modules --- .../framework/symbols/windows/pdbutil.py | 75 +++++++++++++++---- 1 file changed, 61 insertions(+), 14 deletions(-) diff --git a/volatility3/framework/symbols/windows/pdbutil.py b/volatility3/framework/symbols/windows/pdbutil.py index 585e96b6d..41037d464 100644 --- a/volatility3/framework/symbols/windows/pdbutil.py +++ b/volatility3/framework/symbols/windows/pdbutil.py @@ -10,10 +10,10 @@ import os import re import struct from typing import Any, Dict, Generator, List, Optional, Tuple, Union -from urllib import request, parse +from urllib import parse, request from volatility3 import symbols -from volatility3.framework import constants, interfaces, exceptions +from volatility3.framework import constants, contexts, exceptions, interfaces from volatility3.framework.configuration.requirements import SymbolTableRequirement from volatility3.framework.symbols import intermed from volatility3.framework.symbols.windows import pdbconv @@ -24,7 +24,7 @@ vollog = logging.getLogger(__name__) class PDBUtility(interfaces.configuration.VersionableInterface): """Class to handle and manage all getting symbols based on MZ header""" - _version = (1, 0, 0) + _version = (1, 0, 1) _required_framework_version = (2, 0, 0) @classmethod @@ -131,14 +131,14 @@ class PDBUtility(interfaces.configuration.VersionableInterface): # Check it is actually the MZ header if mz_sig != b"MZ": return None - + nt_header_start, = struct.unpack(" str: + pdb_name: str, module_offset: int = None, module_size: int = None) -> str: """Creates symbol table for a module in the specified layer_name. Searches the memory section of the loaded module for its PDB GUID @@ -307,6 +307,19 @@ class PDBUtility(interfaces.configuration.VersionableInterface): Returns: The name of the constructed and loaded symbol table """ + _, symbol_table_name = cls._modtable_from_pdb(context, config_path, layer_name, pdb_name, module_offset, + module_size) + return symbol_table_name + + @classmethod + def _modtable_from_pdb(cls, context: interfaces.context.ContextInterface, config_path: str, layer_name: str, + pdb_name: str, module_offset: int = None, module_size: int = None, + create_module: bool = False) -> Tuple[Optional[str], Optional[str]]: + + if module_offset is None: + module_offset = context.layers[layer_name].minimum_address + if module_size is None: + module_size = context.layers[layer_name].maximum_address - module_offset guids = list( cls.pdbname_scan(context, @@ -323,12 +336,46 @@ class PDBUtility(interfaces.configuration.VersionableInterface): vollog.debug(f"Found {guid['pdb_name']}: {guid['GUID']}-{guid['age']}") - return cls.load_windows_symbol_table(context, - guid["GUID"], - guid["age"], - guid["pdb_name"], - "volatility3.framework.symbols.intermed.IntermediateSymbolTable", - config_path = config_path) + module_name = guid["pdb_name"].strip('.pdb') + + symbol_table_name = cls.load_windows_symbol_table(context, + guid["GUID"], + guid["age"], + guid["pdb_name"], + "volatility3.framework.symbols.intermed.IntermediateSymbolTable", + config_path = config_path) + + new_module_name = None + if create_module: + new_module = contexts.Module.create(context, module_name, layer_name, offset = guid['mz_offset'], + symbol_table_name = symbol_table_name) + new_module_name = new_module.name + + return new_module_name, symbol_table_name + + @classmethod + def module_from_pdb(cls, context: interfaces.context.ContextInterface, config_path: str, layer_name: str, + pdb_name: str, module_offset: int = None, module_size: int = None) -> str: + """Creates a module in the specified layer_name based on a pdb name. + + Searches the memory section of the loaded module for its PDB GUID + and loads the associated symbol table into the symbol space. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + config_path: The config path where to find symbol files + layer_name: The name of the layer on which to operate + module_offset: This memory dump's module image offset + module_size: The size of the module for this dump + + Returns: + The name of the constructed and loaded symbol table + """ + + module_name, _ = cls._modtable_from_pdb(context, config_path, layer_name, pdb_name, module_offset, + module_size, create_module = True) + + return module_name class PdbSignatureScanner(interfaces.layers.ScannerInterface): From 21d916be0a08eccc91bbd4884f458ae6ff489b95 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Mon, 6 Jun 2022 14:53:52 +0100 Subject: [PATCH 03/15] Pyinstaller: Support pyinstaller 5 and later --- volatility3/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/volatility3/__init__.py b/volatility3/__init__.py index db52aa9b0..b6da6e01e 100644 --- a/volatility3/__init__.py +++ b/volatility3/__init__.py @@ -37,9 +37,9 @@ class WarningFindSpec(abc.MetaPathFinder): first.""" if fullname.startswith("volatility3.framework.plugins."): warning = "Please do not use the volatility3.framework.plugins namespace directly, only use volatility3.plugins" - # Pyinstaller uses walk_packages to import, but needs to read the modules to figure out dependencies - # As such, we only print the warning when directly imported rather than from within walk_packages - if inspect.stack()[-2].function != 'walk_packages': + # Pyinstaller uses walk_packages/_collect_submodules to import, but needs to read the modules to figure out dependencies + # As such, we only print the warning when directly imported rather than from within walk_packages/_collect_submodules + if inspect.stack()[-2].function in ['walk_packages', '_collect_submodules']: raise Warning(warning) From aa06ed6e674761c8ec1238daeff9a64603aff392 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 12 Jun 2022 20:32:06 +0900 Subject: [PATCH 04/15] Add: new options for vol-cli.rst --- doc/source/vol-cli.rst | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/doc/source/vol-cli.rst b/doc/source/vol-cli.rst index 9db29c818..902787c9c 100644 --- a/doc/source/vol-cli.rst +++ b/doc/source/vol-cli.rst @@ -9,7 +9,11 @@ Synopsis **volatility** [-h] [-c CONFIG] [--parallelism [{processes,threads,off}]] [-e EXTEND] [-p PLUGIN_DIRS] [-s SYMBOL_DIRS] [-v] [-l LOG] [-o OUTPUT_DIR] [-q] [-r RENDERER] [-f FILE] - [--write-config] [--single-location SINGLE_LOCATION] + [--write-config] [--save-config SAVE_CONFIG] + [--clear-cache] [--cache-path CACHE_PATH] + [--offline] + [--single-location SINGLE_LOCATION] + [--stackers [STACKERS ...]] [--single-swap-locations SINGLE_SWAP_LOCATIONS] ... @@ -105,11 +109,31 @@ Options other plugins, but there's no guarantee that plugins use the same configuration options. +--save-config + This flag specifies that volatility should write or overwrite a file + called config.json in the current directory. The file will contain + the necessary JSON configuration to recreate the environment that the + plugin was previously run in. This configuration *may* be accepted by + other plugins, but there's no guarantee that plugins use the same + configuration options. + +--clear-cache + Clears out all short-term cached items. + +--cache-path + Change the default path ({constants.CACHE_PATH}) used to store the cache. + +--offline + Do not search online for additional JSON files. + --single-location SINGLE_LOCATION This specifies a URL which will be downloaded if necessary, and built upon by the automagic and, since most plugins require a single memory image, can be considered the input for the program. +--stackers STACKERS + + --single-swap-locations SINGLE_SWAP_LOCATIONS A comma-separated list of swap files to be considered as part of the memory image specified by the single-location or file parameters. From 2d14e4e012d6745862fafa1a857414102a412eb1 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 12 Jun 2022 20:50:03 +0900 Subject: [PATCH 05/15] Add: descriptions of new options --- doc/source/vol-cli.rst | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/doc/source/vol-cli.rst b/doc/source/vol-cli.rst index 902787c9c..b9e16623d 100644 --- a/doc/source/vol-cli.rst +++ b/doc/source/vol-cli.rst @@ -102,12 +102,8 @@ Options attempt to build upon, and can be considered the input for the program. --write-config - This flag specifies that volatility should write or overwrite a file - called config.json in the current directory. The file will contain - the necessary JSON configuration to recreate the environment that the - plugin was previously run in. This configuration *may* be accepted by - other plugins, but there's no guarantee that plugins use the same - configuration options. + *Deprecated* + Use of `--write-config` has been deprecated, replaced by `--save-config` --save-config This flag specifies that volatility should write or overwrite a file @@ -121,19 +117,18 @@ Options Clears out all short-term cached items. --cache-path - Change the default path ({constants.CACHE_PATH}) used to store the cache. + Change the default path used to store the cache. --offline Do not search online for additional JSON files. + Run offline mode (defaults to false) and for + remote windows symbol tables, linux/mac banner repositories. --single-location SINGLE_LOCATION This specifies a URL which will be downloaded if necessary, and built upon by the automagic and, since most plugins require a single memory image, can be considered the input for the program. ---stackers STACKERS - - --single-swap-locations SINGLE_SWAP_LOCATIONS A comma-separated list of swap files to be considered as part of the memory image specified by the single-location or file parameters. From 3ef505641eb2f7d3d76174effb18cba69434298f Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 12 Jun 2022 20:55:15 +0900 Subject: [PATCH 06/15] Add: stacker descriptions --- doc/source/vol-cli.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/source/vol-cli.rst b/doc/source/vol-cli.rst index b9e16623d..cc6f7fe6a 100644 --- a/doc/source/vol-cli.rst +++ b/doc/source/vol-cli.rst @@ -129,6 +129,9 @@ Options upon by the automagic and, since most plugins require a single memory image, can be considered the input for the program. +--stackers STACKERS + Creates the list of stackers to use based on the config option. + --single-swap-locations SINGLE_SWAP_LOCATIONS A comma-separated list of swap files to be considered as part of the memory image specified by the single-location or file parameters. From e1f3f65202d7eb23901a4c9639ad1523f4429369 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Tue, 14 Jun 2022 21:03:33 +0900 Subject: [PATCH 07/15] Fix: typo for code comment, requirements name --- volatility3/framework/interfaces/configuration.py | 2 +- volatility3/framework/interfaces/layers.py | 4 ++-- volatility3/framework/plugins/mac/kevents.py | 2 +- volatility3/framework/plugins/windows/modscan.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/interfaces/configuration.py b/volatility3/framework/interfaces/configuration.py index c39dba680..e271ef6d4 100644 --- a/volatility3/framework/interfaces/configuration.py +++ b/volatility3/framework/interfaces/configuration.py @@ -523,7 +523,7 @@ class ConstructableRequirementInterface(RequirementInterface): 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. + arguments). """ def __init__(self, *args, **kwargs) -> None: diff --git a/volatility3/framework/interfaces/layers.py b/volatility3/framework/interfaces/layers.py index a42282c39..7ff110c6e 100644 --- a/volatility3/framework/interfaces/layers.py +++ b/volatility3/framework/interfaces/layers.py @@ -307,7 +307,7 @@ class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metacla while length > 0: chunk_size = min(length, scanner.chunk_size + scanner.overlap) yield [(layer_name, mapped_offset, chunk_size)], offset + chunk_size - # It we've got more than the scanner's chunk_size, only move up by the chunk_size + # If we've got more than the scanner's chunk_size, only move up by the chunk_size if chunk_size > scanner.chunk_size: chunk_size -= scanner.overlap length -= chunk_size @@ -517,7 +517,7 @@ class TranslationLayerInterface(DataLayerInterface, metaclass = ABCMeta): yield output, chunk_position output = [] chunk_position = chunk_start - # Take from chunk_position as far as far as the block can go, + # Take from chunk_position as far as the block can go, # or as much left of a scanner chunk as we can chunk_size = min(block_end - chunk_position, scanner.chunk_size + scanner.overlap - (chunk_position - chunk_start)) diff --git a/volatility3/framework/plugins/mac/kevents.py b/volatility3/framework/plugins/mac/kevents.py index 6f82c75cd..4a82d81cd 100644 --- a/volatility3/framework/plugins/mac/kevents.py +++ b/volatility3/framework/plugins/mac/kevents.py @@ -74,7 +74,7 @@ class Kevents(interfaces.plugins.PluginInterface): @classmethod def _walk_klist_array(cls, kernel, fdp, array_pointer_member, array_size_member): """ - Convience wrapper for walking an array of lists of kernel events + Convenience wrapper for walking an array of lists of kernel events Handles invalid address references """ try: diff --git a/volatility3/framework/plugins/windows/modscan.py b/volatility3/framework/plugins/windows/modscan.py index b661d71d7..e352c21fe 100644 --- a/volatility3/framework/plugins/windows/modscan.py +++ b/volatility3/framework/plugins/windows/modscan.py @@ -25,7 +25,7 @@ class ModScan(interfaces.plugins.PluginInterface): return [ requirements.ModuleRequirement(name = 'kernel', description = 'Windows kernel', architectures = ["Intel32", "Intel64"]), - requirements.VersionRequirement(name = 'poolerscanner', + requirements.VersionRequirement(name = 'poolscanner', component = poolscanner.PoolScanner, version = (1, 0, 0)), requirements.VersionRequirement(name = 'pslist', component = pslist.PsList, version = (2, 0, 0)), From dd92955a99249fe9e8863cb2754229e01a917d73 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Thu, 16 Jun 2022 05:40:26 +0900 Subject: [PATCH 08/15] Remove: unreachable code --- volatility3/cli/volshell/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/volatility3/cli/volshell/__init__.py b/volatility3/cli/volshell/__init__.py index 769e958fd..5eeef77cf 100644 --- a/volatility3/cli/volshell/__init__.py +++ b/volatility3/cli/volshell/__init__.py @@ -257,7 +257,6 @@ class VolShell(cli.CommandLine): constructed.run() except exceptions.VolatilityException as excp: self.process_exceptions(excp) - parser.exit(1, f"Unable to validate the plugin requirements: {[x for x in excp.unsatisfied]}\n") def main(): From 9f525dfa733dd65769458540d3996917a1daaa96 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Wed, 22 Jun 2022 19:57:47 +0900 Subject: [PATCH 09/15] Refactor: simplify comparision --- volatility3/framework/automagic/pdbscan.py | 2 +- volatility3/framework/plugins/windows/ldrmodules.py | 12 ++++++------ volatility3/framework/plugins/windows/vadinfo.py | 2 +- .../framework/symbols/linux/extensions/__init__.py | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/volatility3/framework/automagic/pdbscan.py b/volatility3/framework/automagic/pdbscan.py index 5db66a3d0..cedbc4919 100644 --- a/volatility3/framework/automagic/pdbscan.py +++ b/volatility3/framework/automagic/pdbscan.py @@ -148,7 +148,7 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface): vollog.debug("Kernel base determination - optimized scan virtual layer") valid_kernel = self._method_layer_pdb_scan(context, vlayer, test_virtual_kernel, True, False, progress_callback) - if valid_kernel != None: + if valid_kernel is not None: return valid_kernel vollog.debug("Kernel base determination - slow scan virtual layer") diff --git a/volatility3/framework/plugins/windows/ldrmodules.py b/volatility3/framework/plugins/windows/ldrmodules.py index e7c96e946..284d1afc2 100644 --- a/volatility3/framework/plugins/windows/ldrmodules.py +++ b/volatility3/framework/plugins/windows/ldrmodules.py @@ -71,14 +71,14 @@ class LdrModules(interfaces.plugins.PluginInterface): mem_mod = mem_order_mod.get(base, None) yield (0, [int(proc.UniqueProcessId), - str(proc.ImageFileName.cast("string", + str(proc.ImageFileName.cast("string", max_length = proc.ImageFileName.vol.count, errors = 'replace')), - format_hints.Hex(base), - load_mod != None, - init_mod != None, - mem_mod != None, - mapped_files[base]]) + format_hints.Hex(base), + load_mod is not None, + init_mod is not None, + mem_mod is not None, + mapped_files[base]]) def run(self): filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) diff --git a/volatility3/framework/plugins/windows/vadinfo.py b/volatility3/framework/plugins/windows/vadinfo.py index 9fa1458d1..e357b150a 100644 --- a/volatility3/framework/plugins/windows/vadinfo.py +++ b/volatility3/framework/plugins/windows/vadinfo.py @@ -132,7 +132,7 @@ class VadInfo(interfaces.plugins.PluginInterface): vollog.debug("Unable to find the starting/ending VPN member") return None - if maxsize > 0 and (vad_end - vad_start) > maxsize: + if 0 < maxsize < (vad_end - vad_start): vollog.debug(f"Skip VAD dump {vad_start:#x}-{vad_end:#x} due to maxsize limit") return None diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 6792ab19c..73f31115a 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -409,7 +409,7 @@ class vm_area_struct(objects.StructType): fname = linux.LinuxUtilities.path_for_file(context, task, self.vm_file) elif self.vm_start <= task.mm.start_brk and self.vm_end >= task.mm.brk: fname = "[heap]" - elif self.vm_start <= task.mm.start_stack and self.vm_end >= task.mm.start_stack: + elif self.vm_start <= task.mm.start_stack <= self.vm_end: fname = "[stack]" elif self.vm_mm.context.has_member("vdso") and self.vm_start == self.vm_mm.context.vdso: fname = "[vdso]" From 7ba27a75ca9cbecbe796f6475340718e6bce0dd0 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Wed, 22 Jun 2022 14:49:57 +0100 Subject: [PATCH 10/15] Documentation: Improve the simple-plugin example --- doc/source/simple-plugin.rst | 54 ++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/doc/source/simple-plugin.rst b/doc/source/simple-plugin.rst index 8446b0ef5..904c586c7 100644 --- a/doc/source/simple-plugin.rst +++ b/doc/source/simple-plugin.rst @@ -30,6 +30,9 @@ to be able to run properly. Any that are defined as optional need not necessari :: + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + @classmethod def get_requirements(cls): return [requirements.TranslationLayerRequirement(name = 'primary', @@ -37,13 +40,13 @@ to be able to run properly. Any that are defined as optional need not necessari architectures = ["Intel32", "Intel64"]), requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), - requirements.PluginRequirement(name = 'pslist', - plugin = pslist.PsList, - version = (1, 0, 0)), requirements.ListRequirement(name = 'pid', element_type = int, description = "Process IDs to include (all other processes are excluded)", - optional = True)] + optional = True), + requirements.PluginRequirement(name = 'pslist', + plugin = pslist.PsList, + version = (1, 0, 0))] This is a classmethod, because it is called before the specific plugin object has been instantiated (in order to know how @@ -91,29 +94,44 @@ name of the :py:class:`SymbolTable Date: Wed, 22 Jun 2022 15:12:40 +0100 Subject: [PATCH 11/15] Documentation: Update the documentation to the latest framework --- doc/source/simple-plugin.rst | 103 ++++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 38 deletions(-) diff --git a/doc/source/simple-plugin.rst b/doc/source/simple-plugin.rst index 904c586c7..543451b88 100644 --- a/doc/source/simple-plugin.rst +++ b/doc/source/simple-plugin.rst @@ -35,11 +35,8 @@ to be able to run properly. Any that are defined as optional need not necessari @classmethod def get_requirements(cls): - return [requirements.TranslationLayerRequirement(name = 'primary', - description = 'Memory layer for the kernel', - architectures = ["Intel32", "Intel64"]), - requirements.SymbolTableRequirement(name = "nt_symbols", - description = "Windows kernel symbols"), + return [requirements.ModuleRequirement(name = 'kernel', description = 'Windows kernel', + architectures = ["Intel32", "Intel64"]), requirements.ListRequirement(name = 'pid', element_type = int, description = "Process IDs to include (all other processes are excluded)", @@ -54,45 +51,73 @@ to instantiate the plugin). At the moment these requirements are fairly straigh :: - requirements.TranslationLayerRequirement(name = 'primary', - description = 'Memory layer for the kernel', - architectures = ["Intel32", "Intel64"]), + requirements.ModuleRequirement(name = 'kernel', description = 'Windows kernel', + architectures = ["Intel32", "Intel64"]), -This requirement indicates that the plugin will operate on a single -:py:class:`TranslationLayer `. The name of the -loaded layer will appear in the plugin's configuration under the name ``primary``. Requirement values can be -accessed within the plugin through the plugin's `config` attribute (for example ``self.config['pid']``). +This requirement specifies the need for a particular submodule. Each module requires a +:py:class:`TranslationLayer ` and a +:py:class:`SymbolTable `, which are fulfilled by two +subrequirements: a +:py:class:`~volatility3.framework.configuration.requirements.TranslationLayerRequirement` and a +:py:class:`~volatility3.framework.configuration.requirements.SymbolTableRequirement`. At the moment, the automagic +only fills `ModuleRequirements` with kernels, and so has relatively few parameters. It requires the architecture for +the underlying TranslationLayer, and the offset of the module within that layer. -.. note:: The name itself is dynamic depending on the other layers already present in the Context. Always use the value - from the configuration rather than attempting to guess what the layer will be called. +The name of the module will be stored in the ``kernel`` configuration option, and the module object itself +can be accessed from the ``context.modules`` collection. This requirement is a Complex Requirement and therefore will +not be requested directly from the user. -Finally, this defines that the translation layer must be on the Intel Architecture. At the moment, this acts as a filter, -failing to be satisfied by memory images that do not match the architecture required. -Most plugins will only operate on a single layer, but it is entirely possible for a plugin to request two different -layers, for example a plugin that carries out some form of difference or statistics against multiple memory images. +.. note:: -This requirement (and the next two) are known as Complex Requirements, and user interfaces will likely not directly -request a value for this from a user. The value stored in the configuration tree for a -:py:class:`~volatility3.framework.configuration.requirements.TranslationLayerRequirement` is -the string name of a layer present in the context's memory that satisfies the requirement. + In previous versions of volatility 3, there was no `ModuleRequirement`, and instead two requirements were defined + a :py:class:`TranslationLayer ` and a `SymbolTableRequirement`. These still exist, and can be used, most plugins just + define a single `ModuleRequirement` for the kernel, which the automagic will populate. The `ModuleRequirement` has + two automatic sub-requirements, a `TranslationLayerRequirement` and a `SymbolTableRequirement`, but the module also + includes the offset of the module, and will allow future expansion to specify specific modules when application + level plugins become more common. Below are how the requirements would be specified: -:: + :: - requirements.SymbolTableRequirement(name = "nt_symbols", - description = "Windows kernel symbols"), + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), -This requirement specifies the need for a particular -:py:class:`SymbolTable ` -to be loaded. This gets populated by various -:py:class:`Automagic ` as the nearest sibling to a particular -:py:class:`~volatility3.framework.configuration.requirements.TranslationLayerRequirement`. -This means that if the :py:class:`~volatility3.framework.configuration.requirements.TranslationLayerRequirement` -is satisfied and the :py:class:`Automagic ` can determine -the appropriate :py:class:`SymbolTable `, the -name of the :py:class:`SymbolTable ` will be stored in the configuration. + This requirement indicates that the plugin will operate on a single + :py:class:`TranslationLayer `. The name of the + loaded layer will appear in the plugin's configuration under the name ``primary``. Requirement values can be + accessed within the plugin through the plugin's `config` attribute (for example ``self.config['pid']``). -This requirement is also a Complex Requirement and therefore will not be requested directly from the user. + .. note:: The name itself is dynamic depending on the other layers already present in the Context. Always use the value + from the configuration rather than attempting to guess what the layer will be called. + + Finally, this defines that the translation layer must be on the Intel Architecture. At the moment, this acts as a filter, + failing to be satisfied by memory images that do not match the architecture required. + + Most plugins will only operate on a single layer, but it is entirely possible for a plugin to request two different + layers, for example a plugin that carries out some form of difference or statistics against multiple memory images. + + This requirement (and the next two) are known as Complex Requirements, and user interfaces will likely not directly + request a value for this from a user. The value stored in the configuration tree for a + :py:class:`~volatility3.framework.configuration.requirements.TranslationLayerRequirement` is + the string name of a layer present in the context's memory that satisfies the requirement. + + :: + + requirements.SymbolTableRequirement(name = "nt_symbols", + description = "Windows kernel symbols"), + + This requirement specifies the need for a particular + :py:class:`SymbolTable ` + to be loaded. This gets populated by various + :py:class:`Automagic ` as the nearest sibling to a particular + :py:class:`~volatility3.framework.configuration.requirements.TranslationLayerRequirement`. + This means that if the :py:class:`~volatility3.framework.configuration.requirements.TranslationLayerRequirement` + is satisfied and the :py:class:`Automagic ` can determine + the appropriate :py:class:`SymbolTable `, the + name of the :py:class:`SymbolTable ` will be stored in the configuration. + + This requirement is also a Complex Requirement and therefore will not be requested directly from the user. :: @@ -147,6 +172,7 @@ that will be output as part of the :py:class:`~volatility3.framework.interfaces. def run(self): filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + kernel = self.context.modules[self.config['kernel']] return renderers.TreeGrid([("PID", int), ("Process", str), @@ -155,8 +181,8 @@ that will be output as part of the :py:class:`~volatility3.framework.interfaces. ("Name", str), ("Path", str)], self._generator(pslist.PsList.list_processes(self.context, - self.config['primary'], - self.config['nt_symbols'], + kernel.layer_name, + kernel.symbol_table_name, filter_func = filter_func))) In this instance, the plugin constructs a filter (using the PsList plugin's *classmethod* for creating filters). @@ -175,7 +201,8 @@ the :py:class:`~volatility3.plugins.windows.pslist.PsList` plugin. That plugin so that other plugins can call it. As such, it takes all the necessary parameters rather than accessing them from a configuration. Since it must be portable code, it takes a context, as well as the layer name, symbol table and optionally a filter. In this instance we unconditionally -pass it the values from the configuration for the ``primary`` and ``nt_symbols`` requirements. This will generate a list +pass it the values from the configuration for the layer and symbol table from the kernel module object, constructed from +the ``kernel`` configuration requirement. This will generate a list of :py:class:`~volatility3.framework.symbols.windows.extensions.EPROCESS` objects, as provided by the :py:class:`~volatility.plugins.windows.pslist.PsList` plugin, and is not covered here but is used as an example for how to share code across plugins (both as the provider and the consumer of the shared code). From fd524a6b314750bd07779f65257d010ed635b1f6 Mon Sep 17 00:00:00 2001 From: ikelos Date: Wed, 22 Jun 2022 17:08:18 +0100 Subject: [PATCH 12/15] Update doc/source/simple-plugin.rst Yep, that seems fine. Co-authored-by: Donghyun Kim --- doc/source/simple-plugin.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/simple-plugin.rst b/doc/source/simple-plugin.rst index 543451b88..d03f7c7d6 100644 --- a/doc/source/simple-plugin.rst +++ b/doc/source/simple-plugin.rst @@ -43,7 +43,7 @@ to be able to run properly. Any that are defined as optional need not necessari optional = True), requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, - version = (1, 0, 0))] + version = (2, 0, 0))] This is a classmethod, because it is called before the specific plugin object has been instantiated (in order to know how From a386de72f5a22d176ecad730e83f804e2f62c633 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Wed, 22 Jun 2022 17:12:24 +0100 Subject: [PATCH 13/15] Documentation: Fix pslist plugin requirement --- doc/source/simple-plugin.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/simple-plugin.rst b/doc/source/simple-plugin.rst index d03f7c7d6..1c7b91205 100644 --- a/doc/source/simple-plugin.rst +++ b/doc/source/simple-plugin.rst @@ -134,7 +134,7 @@ being defined within the configuration tree at all. requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, - version = (1, 0, 0)) + version = (2, 0, 0))] This requirement indicates that the plugin will make use of another plugin's code, and specifies the version requirements on that plugin. The version is specified in terms of Semantic Versioning meaning that, to be compatible, the major From 6982650c188a7c8fccbbee3c1d7d1f47f309df28 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Wed, 29 Jun 2022 15:38:48 +0100 Subject: [PATCH 14/15] Volshell: Fixes use of old config variables Closes #780 --- volatility3/cli/volshell/linux.py | 4 ++-- volatility3/cli/volshell/mac.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/volatility3/cli/volshell/linux.py b/volatility3/cli/volshell/linux.py index 97a488743..0f2a90c7e 100644 --- a/volatility3/cli/volshell/linux.py +++ b/volatility3/cli/volshell/linux.py @@ -56,13 +56,13 @@ class Volshell(generic.Volshell): """Display Type describes the members of a particular object in alphabetical order""" if isinstance(object, str): if constants.BANG not in object: - object = self.config['vmlinux'] + constants.BANG + object + object = self.current_symbol_table + constants.BANG + object return super().display_type(object, offset) def display_symbols(self, symbol_table: str = None): """Prints an alphabetical list of symbols for a symbol table""" if symbol_table is None: - symbol_table = self.config['vmlinux'] + symbol_table = self.current_symbol_table return super().display_symbols(symbol_table) @property diff --git a/volatility3/cli/volshell/mac.py b/volatility3/cli/volshell/mac.py index 305f80505..6744f3394 100644 --- a/volatility3/cli/volshell/mac.py +++ b/volatility3/cli/volshell/mac.py @@ -56,7 +56,7 @@ class Volshell(generic.Volshell): """Display Type describes the members of a particular object in alphabetical order""" if isinstance(object, str): if constants.BANG not in object: - object = self.config['darwin'] + constants.BANG + object + object = self.current_symbol_table + constants.BANG + object return super().display_type(object, offset) def display_symbols(self, symbol_table: str = None): From 951a0f5d508b8db4985d54751ea93ca78e57b191 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Thu, 30 Jun 2022 11:43:38 +0100 Subject: [PATCH 15/15] Documentation: Clarify that the code is just an example Clarifies for #773 and #776 --- doc/source/simple-plugin.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/source/simple-plugin.rst b/doc/source/simple-plugin.rst index 1c7b91205..e2143f1b7 100644 --- a/doc/source/simple-plugin.rst +++ b/doc/source/simple-plugin.rst @@ -6,6 +6,12 @@ This guide will step through how to construct a simple plugin using Volatility 3 The example plugin we'll use is :py:class:`~volatility3.plugins.windows.dlllist.DllList`, which features the main traits of a normal plugin, and reuses other plugins appropriately. +.. note:: + + This document will not include the complete code necessary for a + working plugin (such as imports, etc) since it's designed to focus on the necessary componets for writing a plugin. + For complete and functioning plugins, the ``framework/plugins`` directory should be consulted. + Inherit from PluginInterface ----------------------------