Merge branch 'develop' into fix/ethread

This commit is contained in:
Donghyun Kim
2022-07-04 16:01:37 +09:00
committed by GitHub
17 changed files with 179 additions and 73 deletions
+104 -53
View File
@@ -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
----------------------------
@@ -30,20 +36,20 @@ 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',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "nt_symbols",
description = "Windows kernel symbols"),
requirements.PluginRequirement(name = 'pslist',
plugin = pslist.PsList,
version = (1, 0, 0)),
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)",
optional = True)]
optional = True),
requirements.PluginRequirement(name = 'pslist',
plugin = pslist.PsList,
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
@@ -51,69 +57,112 @@ 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 <volatility3.framework.interfaces.layers.TranslationLayerInterface>`. 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 <volatility3.framework.interfaces.layers.TranslationLayerInterface>` and a
:py:class:`SymbolTable <volatility3.framework.interfaces.symbols.SymbolTableInterface>`, 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 <volatility3.framework.interfaces.layers.TranslationLayerInterface>` 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 <volatility3.framework.interfaces.symbols.SymbolTableInterface>`
to be loaded. This gets populated by various
:py:class:`Automagic <volatility3.framework.interfaces.automagic.AutoMagicInterface>` 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 <volatility3.framework.interfaces.automagic.AutoMagicInterface>` can determine
the appropriate :py:class:`SymbolTable <volatility3.framework.interfaces.symbols.SymbolTableInterface>`, the
name of the :py:class:`SymbolTable <volatility3.framework.interfaces.symbols.SymbolTableInterface>` will be stored in the configuration.
This requirement indicates that the plugin will operate on a single
:py:class:`TranslationLayer <volatility3.framework.interfaces.layers.TranslationLayerInterface>`. 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.
requirements.PluginRequirement(name = 'pslist',
plugin = pslist.PsList,
version = (1, 0, 0)),
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 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
versions must be identical and the minor version must be equal to or higher than the one provided. This requirement
does not make use of any data from the configuration, even if it were provided, it is merely a functional check before
running the plugin.
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 <volatility3.framework.interfaces.symbols.SymbolTableInterface>`
to be loaded. This gets populated by various
:py:class:`Automagic <volatility3.framework.interfaces.automagic.AutoMagicInterface>` 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 <volatility3.framework.interfaces.automagic.AutoMagicInterface>` can determine
the appropriate :py:class:`SymbolTable <volatility3.framework.interfaces.symbols.SymbolTableInterface>`, the
name of the :py:class:`SymbolTable <volatility3.framework.interfaces.symbols.SymbolTableInterface>` will be stored in the configuration.
This requirement is also a Complex Requirement and therefore will not be requested directly from the user.
::
requirements.ListRequirement(name = 'pid',
description = 'Filter on specific process IDs',
element_type = int,
optional = True)
optional = True),
The final requirement is a List Requirement, populated by integers. The description will be presented to the user to
The next requirement is a List Requirement, populated by integers. The description will be presented to the user to
describe what the value represents. The optional flag indicates that the plugin can function without the ``pid`` value
being defined within the configuration tree at all.
::
requirements.PluginRequirement(name = 'pslist',
plugin = pslist.PsList,
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
versions must be identical and the minor version must be equal to or higher than the one provided. This requirement
does not make use of any data from the configuration, even if it were provided, it is merely a functional check before
running the plugin. To define the version of a plugin, populate the `_version` class variable as a tuple of version
numbers `(major, minor, patch)`. So for example:
::
_version = (1, 0, 0)
The plugin may also require a specific version of the framework, and this also uses Semantic Versioning, and can be
set by defining the `_required_framework_version`. The major version should match the version of volatility the plugin
is to be used with, which at the time of writing would be 2.2.0, and so would be specified as below. If only features, for example,
from 2.0.0 are used, then the lowest applicable version number should be used to support the greatest number of
installations:
::
_required_framework_version = (2, 0, 0)
Define the `run` method
-----------------------
@@ -129,6 +178,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),
@@ -137,8 +187,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).
@@ -157,7 +207,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).
+23 -1
View File
@@ -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]
<plugin> ...
@@ -98,6 +102,10 @@ Options
attempt to build upon, and can be considered the input for the program.
--write-config
*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
called config.json in the current directory. The file will contain
the necessary JSON configuration to recreate the environment that the
@@ -105,11 +113,25 @@ Options
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 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
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.
-1
View File
@@ -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():
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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):
+1 -1
View File
@@ -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")
+1 -1
View File
@@ -39,7 +39,7 @@ BANG = "!"
# We use the SemVer 2.0.0 versioning scheme
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
VERSION_MINOR = 2 # Number of changes that only add to the interface
VERSION_MINOR = 3 # Number of changes that only add to the interface
VERSION_PATCH = 1 # Number of changes that do not change the interface
VERSION_SUFFIX = ""
@@ -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:
+2 -2
View File
@@ -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))
@@ -241,6 +241,12 @@ class ObjectInterface(metaclass = abc.ABCMeta):
the child member."""
raise KeyError(f"Template does not contain any children: {template.vol.type_name}")
@classmethod
@abc.abstractmethod
def child_template(cls, template: 'Template', child: str) -> 'interfaces.objects.Template':
"""Returns the template of the child member from the parent."""
raise KeyError(f"Template does not contain any children: {template.vol.type_name}")
@classmethod
@abc.abstractmethod
def has_member(cls, template: 'Template', member_name: str) -> bool:
@@ -305,6 +311,10 @@ class Template:
"""Returns the relative offset of the `child` member from its parent
offset."""
@abc.abstractmethod
def child_template(self, child: str) -> 'interfaces.objects.Template':
"""Returns the `child` member template from its parent."""
@abc.abstractmethod
def replace_child(self, old_child: 'Template', new_child: 'Template') -> None:
"""Replaces `old_child` with `new_child` in the list of children."""
+17
View File
@@ -602,6 +602,14 @@ class Array(interfaces.objects.ObjectInterface, collections.abc.Sequence):
return 0
raise IndexError(f"Member not present in array template: {child}")
@classmethod
def child_template(cls, template: interfaces.objects.Template, child: str) -> interfaces.objects.Template:
"""Returns the template of the child member."""
if 'subtype' in template.vol and child == 'subtype':
return template.vol.subtype
raise IndexError(f"Member not present in array template: {child}")
@overload
def __getitem__(self, i: int) -> interfaces.objects.Template:
...
@@ -715,6 +723,15 @@ class AggregateType(interfaces.objects.ObjectInterface):
raise IndexError(f"Member not present in template: {child}")
return retlist[0]
@classmethod
def child_template(cls, template: interfaces.objects.Template, child: str) -> interfaces.objects.Template:
"""Returns the template of a child to its parent."""
retlist = template.vol.members.get(child, None)
if retlist is None:
raise IndexError(f"Member not present in template: {child}")
return retlist[1]
@classmethod
def has_member(cls, template: interfaces.objects.Template, member_name: str) -> bool:
"""Returns whether the object would contain a member called
@@ -48,6 +48,12 @@ class ObjectTemplate(interfaces.objects.Template):
plateProxy`)"""
return self.vol.object_class.VolTemplateProxy.relative_child_offset(self, child)
def child_template(self, child: str) -> interfaces.objects.Template:
"""Returns the template of a child of the templated object (see
:class:`~volatility3.framework.interfaces.objects.ObjectInterface.VolTem
plateProxy`)"""
return self.vol.object_class.VolTemplateProxy.child_template(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:`~volatility3.framework.interfaces.objects.ObjectInterf
@@ -99,6 +105,7 @@ class ReferenceTemplate(interfaces.objects.Template):
size: ClassVar[Any] = property(_unresolved)
replace_child: ClassVar[Any] = _unresolved
relative_child_offset: ClassVar[Any] = _unresolved
child_template: ClassVar[Any] = _unresolved
has_member: ClassVar[Any] = _unresolved
def __call__(self, context: interfaces.context.ContextInterface, object_info: interfaces.objects.ObjectInformation):
+1 -1
View File
@@ -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:
@@ -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))
@@ -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)),
@@ -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
@@ -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]"