Plugins: Introduce FileHandlerInterface

This commit is contained in:
Mike Auty
2020-10-29 09:43:16 +00:00
committed by ikelos
parent bbc2ac0018
commit e8fb8c929c
25 changed files with 255 additions and 180 deletions
+1 -1
View File
@@ -168,7 +168,7 @@ be called whenever a plugin produces an auxiliary file.
::
constructed = plugin(context, plugin_config_path, progress_callback = progress_callback)
constructed.set_file_consumer(file_consumer)
constructed.set_file_handler(file_consumer)
The file_consumer must adhere to the :py:class:`~volatility.framework.interfaces.plugins.FileConsumerInterface`,
which has a `consume_file` method that takes a :py:class:`~volatility.framework.interfaces.plugins.FileInterface`
+26 -11
View File
@@ -12,6 +12,7 @@ User interfaces make use of the framework to:
"""
import argparse
import inspect
import io
import json
import logging
import os
@@ -66,7 +67,7 @@ class MuteProgress(PrintedProgress):
pass
class CommandLine(interfaces.plugins.FileConsumerInterface):
class CommandLine:
"""Constructs a command-line interface object for users to run plugins."""
CLI_NAME = 'volatility'
@@ -85,7 +86,7 @@ class CommandLine(interfaces.plugins.FileConsumerInterface):
"""Executes the command line module, taking the system arguments,
determining the plugin to run and then running it."""
volatility.framework.require_interface_version(1, 0, 0)
volatility.framework.require_interface_version(2, 0, 0)
renderers = dict([(x.name.lower(), x) for x in framework.class_subclasses(text_renderer.CLIRenderer)])
@@ -296,7 +297,8 @@ class CommandLine(interfaces.plugins.FileConsumerInterface):
if args.quiet:
progress_callback = MuteProgress()
constructed = plugins.construct_plugin(ctx, automagics, plugin, base_config_path, progress_callback, self)
constructed = plugins.construct_plugin(ctx, automagics, plugin, base_config_path, progress_callback,
self.file_handler_class_factory())
if args.write_config:
vollog.debug("Writing out configuration data to config.json")
@@ -449,22 +451,35 @@ class CommandLine(interfaces.plugins.FileConsumerInterface):
extended_path = interfaces.configuration.path_join(config_path, requirement.name)
context.config[extended_path] = value
def consume_file(self, filedata: interfaces.plugins.FileInterface):
"""Consumes a file as produced by a plugin."""
if self.output_dir is None:
raise TypeError("Output directory is not a string")
os.makedirs(self.output_dir, exist_ok = True)
def file_handler_class_factory(self):
output_dir = self.output_dir
pref_name_array = filedata.preferred_filename.split('.')
filename, extension = os.path.join(self.output_dir, '.'.join(pref_name_array[:-1])), pref_name_array[-1]
class CLIFileHandler(io.BytesIO, interfaces.plugins.FileHandlerInterface):
def __init__(self, filename: str, immediate_commit: bool = False):
io.BytesIO.__init__(self)
interfaces.plugins.FileHandlerInterface.__init__(self, filename, immediate_commit)
def close(self):
if self.closed:
return
if output_dir is None:
raise TypeError("Output directory is not a string")
os.makedirs(output_dir, exist_ok = True)
pref_name_array = self.preferred_filename.split('.')
filename, extension = os.path.join(output_dir, '.'.join(pref_name_array[:-1])), pref_name_array[-1]
output_filename = "{}.{}".format(filename, extension)
if not os.path.exists(output_filename):
with open(output_filename, "wb") as current_file:
current_file.write(filedata.data.getvalue())
current_file.write(self.read())
vollog.log(logging.INFO, "Saved stored plugin file: {}".format(output_filename))
else:
vollog.warning("Refusing to overwrite an existing file: {}".format(output_filename))
super().close()
return CLIFileHandler
def populate_requirements_argparse(self, parser: Union[argparse.ArgumentParser, argparse._ArgumentGroup],
configurable: Type[interfaces.configuration.ConfigurableInterface]):
+14 -9
View File
@@ -3,6 +3,7 @@
#
import binascii
import code
import io
import random
import string
import struct
@@ -263,10 +264,6 @@ class Volshell(interfaces.plugins.PluginInterface):
else:
return hex(value.vol.offset)
def consume_file(self, file: interfaces.plugins.FileInterface) -> None:
"""Dummy file consumer to satisfy the interface"""
pass
def generate_treegrid(self, plugin: Type[interfaces.plugins.PluginInterface],
**kwargs) -> Optional[interfaces.renderers.TreeGrid]:
"""Generates a TreeGrid based on a specific plugin passing in kwarg configuration values"""
@@ -281,7 +278,7 @@ class Volshell(interfaces.plugins.PluginInterface):
self.config[path_join(plugin_config_suffix, plugin.__name__, name)] = value
try:
constructed = plugins.construct_plugin(self.context, [], plugin, plugin_path, None, NullFileConsumer())
constructed = plugins.construct_plugin(self.context, [], plugin, plugin_path, None, NullFileHandler())
return constructed.run()
except exceptions.UnsatisfiedException as excp:
print("Unable to validate the plugin requirements: {}\n".format([x for x in excp.unsatisfied]))
@@ -320,9 +317,17 @@ class Volshell(interfaces.plugins.PluginInterface):
print(" " * (longest_offset - len_offset), hex(symbol.address), " ", symbol.name)
class NullFileConsumer(interfaces.plugins.FileConsumerInterface):
"""Null FileConsumer that swallows files whole"""
class NullFileHandler(io.BytesIO, interfaces.plugins.FileHandlerInterface):
"""Null FileHandler that swallows files whole without consuming memory"""
def consume_file(self, file: interfaces.plugins.FileInterface) -> None:
"""Dummy file consumer to satisfy the FileConsumerInterface"""
def __init__(self, preferred_name: str, immediate_commit: bool = False):
interfaces.plugins.FileHandlerInterface.__init__(self, preferred_name, immediate_commit)
super().__init__()
def writelines(self, lines):
"""Dummy method"""
pass
def write(self, data):
"""Dummy method"""
return len(data)
+1 -1
View File
@@ -44,7 +44,7 @@ def available(context: interfaces.context.ContextInterface) -> List[interfaces.a
clazz(context, interfaces.configuration.path_join(config_path, clazz.__name__))
for clazz in class_subclasses(interfaces.automagic.AutomagicInterface)
],
key = lambda x: x.priority)
key = lambda x: x.priority)
def choose_automagic(
+54 -31
View File
@@ -8,10 +8,10 @@ using objects constructed from symbols.
"""
# Configuration interfaces must be imported separately, since we're part of interfaces and can't import ourselves
import io
import logging
import os
from abc import ABCMeta, abstractmethod
from typing import List, Optional, Tuple
from typing import List, Tuple, Type, IO
from volatility import framework
from volatility.framework import exceptions, constants, interfaces
@@ -19,38 +19,61 @@ from volatility.framework import exceptions, constants, interfaces
vollog = logging.getLogger(__name__)
class FileInterface(metaclass = ABCMeta):
class FileHandlerInterface(IO[bytes]):
"""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:
"""
def __init__(self, filename: str, immediate_commit: bool = False) -> None:
"""Creates a FileTemplate
Args:
filename: The requested name of the filename for the data
data: The data to be stored in a file
"""
self._immediate_commit = immediate_commit
self._committed = False
self._preferred_filename = None
self.preferred_filename = filename
if data is None:
data = b''
self.data = io.BytesIO(data)
super().__init__()
@property
def committed(self):
return self._committed
class FileConsumerInterface(object):
"""Class for consuming files potentially produced by plugins.
@property
def preferred_filename(self):
return self._preferred_filename
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).
"""
@preferred_filename.setter
def preferred_filename(self, filename):
"""Sets the preferred filename"""
if self._committed:
raise IOError
if not isinstance(filename, str):
raise TypeError("FileTemplateInterface preferred filenames must be strings")
if os.path.sep in filename:
raise ValueError("FileTemplateInterface filenames cannot contain path separators")
self._preferred_filename = filename
def consume_file(self, file: FileInterface) -> None:
"""Consumes a file as passed back to a UI by a plugin.
def __enter__(self):
return self
Args:
file: A FileInterface object with the data to write to a file
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is None and exc_value is None and traceback is None:
self.close()
if self._immediate_commit:
self.commit()
else:
vollog.warning("File {} could not be written: {}".format(self._preferred_filename, str(exc_value)))
self.close()
def commit(self):
"""Commits the file to whatever medium is necessary, the file cannot be altered after this point
nor can its preferred_name be chaned
This also ensures that a UI can determine when a file is fully complete rather than partially written
"""
self.close()
self._committed = True
#
@@ -105,21 +128,21 @@ class PluginInterface(interfaces.configuration.ConfigurableInterface,
if requirement.name not in self.config:
self.config[requirement.name] = requirement.default
self._file_consumer = None # type: Optional[FileConsumerInterface]
self._file_handler = FileHandlerInterface # type: Type[FileHandlerInterface]
framework.require_interface_version(*self._required_framework_version)
def set_file_consumer(self, consumer: FileConsumerInterface) -> None:
"""Sets the file consumer to be used by this plugin."""
self._file_consumer = consumer
def open(self, preferred_filename: str) -> FileHandlerInterface:
"""Opens a file for output"""
if self._file_handler is not None:
return self._file_handler(preferred_filename)
raise IOError("FileTemplate not specified for this plugin")
def produce_file(self, filedata: FileInterface) -> None:
"""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:
vollog.debug("No file consumer specified to consume: {}".format(filedata.preferred_filename))
def set_file_handler(self, handler: Type[FileHandlerInterface]) -> None:
"""Sets the file handler to be used by this plugin."""
if not issubclass(handler, FileHandlerInterface):
raise ValueError("FileHandler must be a subclass of FileHandlerInterface")
self._file_handler = handler
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
+4 -4
View File
@@ -19,7 +19,7 @@ def construct_plugin(context: interfaces.context.ContextInterface,
automagics: List[interfaces.automagic.AutomagicInterface],
plugin: Type[interfaces.plugins.PluginInterface], base_config_path: str,
progress_callback: constants.ProgressCallback,
file_consumer: interfaces.plugins.FileConsumerInterface) -> interfaces.plugins.PluginInterface:
file_handler: Type[interfaces.plugins.FileHandlerInterface]) -> interfaces.plugins.PluginInterface:
"""Constructs a plugin object based on the parameters.
Clever magic figures out how to fulfill each requirement that might not be fulfilled
@@ -30,7 +30,7 @@ def construct_plugin(context: interfaces.context.ContextInterface,
plugin: The plugin to run
base_config_path: The path within the context's config containing the plugin's configuration
progress_callback: Callback function to provide feedback for ongoing processes
file_consumer: Object to pass any generated files to
file_handler: Object to pass any generated files to
Returns:
The constructed plugin object
@@ -49,6 +49,6 @@ def construct_plugin(context: interfaces.context.ContextInterface,
raise exceptions.UnsatisfiedException(unsatisfied)
constructed = plugin(context, plugin_config_path, progress_callback = progress_callback)
if file_consumer:
constructed.set_file_consumer(file_consumer)
if file_handler:
constructed.set_file_handler(file_handler)
return constructed
+2 -3
View File
@@ -39,9 +39,8 @@ class ConfigWriter(plugins.PluginInterface):
config = dict(self.context.config)
filename = "config.extra"
try:
filedata = plugins.FileInterface(filename)
filedata.data.write(bytes(json.dumps(config, sort_keys = True, indent = 2), 'raw_unicode_escape'))
self.produce_file(filedata)
with self._file_handler(filename, True) as filedata:
filedata.write(bytes(json.dumps(config, sort_keys = True, indent = 2), 'raw_unicode_escape'))
except Exception:
vollog.warning("Unable to JSON encode configuration")
+9 -6
View File
@@ -4,7 +4,7 @@
import logging
import os
from typing import List, Optional
from typing import List, Optional, Type
from volatility.framework import renderers, interfaces, constants, exceptions
from volatility.framework.configuration import requirements
@@ -43,8 +43,10 @@ class LayerWriter(plugins.PluginInterface):
context: interfaces.context.ContextInterface,
layer_name: str,
preferred_name: str,
file_handler: Type[plugins.FileHandlerInterface],
chunk_size: Optional[int] = None,
progress_callback: Optional[constants.ProgressCallback] = None) -> Optional[plugins.FileInterface]:
progress_callback: Optional[constants.ProgressCallback] = None) -> Optional[
plugins.FileHandlerInterface]:
"""Produces a filedata from the named layer in the provided context
Args:
@@ -62,11 +64,11 @@ class LayerWriter(plugins.PluginInterface):
if chunk_size is None:
chunk_size = cls.default_block_size
filedata = plugins.FileInterface(preferred_name)
filedata = file_handler(preferred_name)
for i in range(0, layer.maximum_address, chunk_size):
current_chunk_size = min(chunk_size, layer.maximum_address - i)
data = layer.read(i, current_chunk_size, pad = True)
filedata.data.write(data)
filedata.write(data)
if progress_callback:
progress_callback((i / layer.maximum_address) * 100, 'Writing layer {}'.format(layer_name))
return filedata
@@ -80,9 +82,10 @@ class LayerWriter(plugins.PluginInterface):
output_name = self.config.get('output', self.default_output_name)
try:
filedata = self.write_layer(self.context, self.config['primary'], output_name,
self._file_handler,
self.config.get('block_size', self.default_block_size),
self._progress_callback)
self.produce_file(filedata)
progress_callback = self._progress_callback)
filedata.commit()
except IOError as excp:
yield 0, ('Layer cannot be written to {}: {}'.format(self.config['output_name'], excp),)
+5 -7
View File
@@ -138,8 +138,8 @@ class Timeliner(interfaces.plugins.PluginInterface):
# Write out a body file if necessary
if self.config.get('create-bodyfile', True):
filedata = interfaces.plugins.FileInterface("volatility.body")
with io.TextIOWrapper(filedata.data, write_through = True) as fp:
filedata = self._file_handler("volatility.body", True)
with io.TextIOWrapper(filedata, write_through = True) as fp:
for (plugin_name, item) in self.timeline:
times = self.timeline[(plugin_name, item)]
# Body format is: MD5|name|inode|mode_as_string|UID|GID|size|atime|mtime|ctime|crtime
@@ -151,7 +151,6 @@ class Timeliner(interfaces.plugins.PluginInterface):
self._text_format(times.get(TimeLinerType.MODIFIED, "")),
self._text_format(times.get(TimeLinerType.CHANGED, "")),
self._text_format(times.get(TimeLinerType.CREATED, ""))))
self.produce_file(filedata)
def _sanitize_body_format(self, value):
return value.replace("|", "_")
@@ -185,7 +184,7 @@ class Timeliner(interfaces.plugins.PluginInterface):
automagics = automagic.choose_automagic(self.automagics, plugin_class)
plugin = plugins.construct_plugin(self.context, automagics, plugin_class, self.config_path,
self._progress_callback, self._file_template)
self._progress_callback, self._file_handler)
if isinstance(plugin, TimeLinerInterface):
if not len(filter_list) or any(
@@ -203,10 +202,9 @@ class Timeliner(interfaces.plugins.PluginInterface):
for entry in old_dict:
total_config[interfaces.configuration.path_join(plugin.__class__.__name__, entry)] = old_dict[entry]
filedata = interfaces.plugins.FileInterface("config.json")
with io.TextIOWrapper(filedata.data, write_through = True) as fp:
filedata = self._file_handler("config.json", True)
with io.TextIOWrapper(filedata, write_through = True) as fp:
json.dump(total_config, fp, sort_keys = True, indent = 2)
self.produce_file(filedata)
return renderers.TreeGrid(columns = [("Plugin", str), ("Description", str), ("Created Date", datetime.datetime),
("Modified Date", datetime.datetime), ("Accessed Date", datetime.datetime),
@@ -26,7 +26,7 @@ class CmdLine(interfaces.plugins.PluginInterface):
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)),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)),
requirements.ListRequirement(name = 'pid',
element_type = int,
description = "Process IDs to include (all other processes are excluded)",
+22 -15
View File
@@ -21,7 +21,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Lists the loaded modules in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -31,7 +31,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"),
requirements.VersionRequirement(name = 'pslist', component = pslist.PsList, version = (1, 0, 0)),
requirements.VersionRequirement(name = 'pslist', component = pslist.PsList, version = (2, 0, 0)),
requirements.VersionRequirement(name = 'info', component = info.Info, version = (1, 0, 0)),
requirements.ListRequirement(name = 'pid',
element_type = int,
@@ -48,7 +48,8 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
context: interfaces.context.ContextInterface,
pe_table_name: str,
dll_entry: interfaces.objects.ObjectInterface,
layer_name: str = None) -> interfaces.plugins.FileInterface:
file_handler: Type[interfaces.plugins.FileHandlerInterface],
layer_name: str = None) -> interfaces.plugins.FileHandlerInterface:
"""Extracts the complete data for a process as a FileInterface
Args:
@@ -56,6 +57,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
pe_table_name: the name for the symbol table containing the PE format symbols
dll_entry: the object representing the module
layer_name: the layer that the DLL lives within
file_handler: class for constructing output files
Returns:
A FileInterface object containing the complete data for the DLL or None in the case of failure"""
@@ -69,17 +71,21 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
if layer_name is None:
layer_name = dll_entry.vol.layer_name
filedata = interfaces.plugins.FileInterface("{0}.{1:#x}.{2:#x}.dmp".format(
ntpath.basename(name), dll_entry.vol.offset, dll_entry.DllBase))
filedata = file_handler(
"{0}.{1}.{2:#x}.{3:#x}.dmp".format(layer_name, ntpath.basename(name), dll_entry.vol.offset,
dll_entry.DllBase), True)
dos_header = context.object(pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
offset = dll_entry.DllBase,
layer_name = layer_name)
for offset, data in dos_header.reconstruct():
filedata.data.seek(offset)
filedata.data.write(data)
except Exception as excp:
with file_handler("{0}.{1}.{2:#x}.{3:#x}.dmp".format(layer_name, ntpath.basename(name),
dll_entry.vol.offset,
dll_entry.DllBase)):
for offset, data in dos_header.reconstruct():
filedata.seek(offset)
filedata.write(data)
except (IOError, exceptions.VolatilityException) as excp:
vollog.debug("Unable to dump dll at offset {}: {}".format(dll_entry.DllBase, excp))
return filedata
@@ -120,13 +126,14 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
else:
DllLoadTime = renderers.NotApplicableValue()
dumped = False
file_output = "Disabled"
if self.config['dump']:
filedata = self.dump_pe(self.context, pe_table_name, entry, proc_layer_name)
if filedata:
filedata.preferred_filename = "pid.{0}.".format(proc_id) + filedata.preferred_filename
dumped = True
self.produce_file(filedata)
filedata = self.dump_pe(self.context, pe_table_name, entry, self._file_handler,
proc_layer_name)
if filedata and filedata.committed:
file_output = filedata.preferred_filename
else:
file_output = "Error outputting file"
yield (0, (proc.UniqueProcessId,
proc.ImageFileName.cast("string",
@@ -46,7 +46,7 @@ class Handles(interfaces.plugins.PluginInterface):
element_type = int,
description = "Process IDs to include (all other processes are excluded)",
optional = True),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (1, 0, 0))
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0))
]
def _decode_pointer(self, value, magic):
@@ -35,7 +35,7 @@ class Malfind(interfaces.plugins.PluginInterface):
description = "Extract injected VADs",
default = False,
optional = True),
requirements.VersionRequirement(name = 'pslist', component = pslist.PsList, version = (1, 1, 0)),
requirements.VersionRequirement(name = 'pslist', component = pslist.PsList, version = (2, 0, 0)),
requirements.VersionRequirement(name = 'vadinfo', component = vadinfo.VadInfo, version = (1, 1, 0))
]
+20 -16
View File
@@ -25,7 +25,7 @@ class Memmap(interfaces.plugins.PluginInterface):
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)),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)),
requirements.IntRequirement(name = 'pid',
description = "Process ID to include (all other processes are excluded)",
optional = True),
@@ -48,34 +48,38 @@ class Memmap(interfaces.plugins.PluginInterface):
excp.layer_name))
continue
filedata = interfaces.plugins.FileInterface("pid.{}.dmp".format(pid))
filedata = self._file_handler("pid.{}.dmp".format(pid))
for mapval in proc_layer.mapping(0x0, proc_layer.maximum_address, ignore_errors = True):
offset, size, mapped_offset, mapped_size, maplayer = mapval
dumped = False
file_output = "Disabled"
if self.config['dump']:
try:
data = proc_layer.read(offset, size, pad = True)
filedata.data.write(data)
dumped = True
filedata.write(data)
file_output = filedata.preferred_filename
except exceptions.InvalidAddressException:
file_output = "Error outputting file"
vollog.debug("Unable to write {}'s address {} to {}.dmp".format(
proc_layer_name, offset, filedata.preferred_filename))
yield (0, (format_hints.Hex(offset), format_hints.Hex(mapped_offset), format_hints.Hex(mapped_size),
format_hints.Hex(offset), dumped))
yield (0, (
format_hints.Hex(offset),
format_hints.Hex(mapped_offset),
format_hints.Hex(mapped_size),
format_hints.Hex(offset),
file_output))
offset += mapped_size
self.produce_file(filedata)
def run(self):
filter_func = pslist.PsList.create_pid_filter([self.config.get('pid', None)])
return renderers.TreeGrid([("Virtual", format_hints.Hex), ("Physical", format_hints.Hex),
("Size", format_hints.Hex), ("Offset", format_hints.Hex), ("Dumped", bool)],
self._generator(
pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func)))
return renderers.TreeGrid(
[("Virtual", format_hints.Hex), ("Physical", format_hints.Hex), ("Size", format_hints.Hex),
("Offset", format_hints.Hex), ("File output", str)],
self._generator(
pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func)))
+18 -11
View File
@@ -8,7 +8,7 @@ from volatility.framework import renderers, interfaces, exceptions
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
from volatility.framework.symbols import intermed
from volatility.framework.symbols.windows import extensions
from volatility.framework.symbols.windows.extensions import pe
from volatility.plugins.windows import poolscanner, dlllist
@@ -27,7 +27,7 @@ class ModScan(interfaces.plugins.PluginInterface):
requirements.VersionRequirement(name = 'poolerscanner',
component = poolscanner.PoolScanner,
version = (1, 0, 0)),
requirements.VersionRequirement(name = 'dlllist', component = dlllist.DllList, version = (1, 0, 0)),
requirements.VersionRequirement(name = 'dlllist', component = dlllist.DllList, version = (2, 0, 0)),
requirements.BooleanRequirement(name = 'dump',
description = "Extract listed modules",
default = False,
@@ -63,7 +63,7 @@ class ModScan(interfaces.plugins.PluginInterface):
self.config_path,
"windows",
"pe",
class_types = extensions.pe.class_types)
class_types = pe.class_types)
for mod in self.scan_modules(self.context, self.config['primary'], self.config['nt_symbols']):
@@ -77,16 +77,23 @@ class ModScan(interfaces.plugins.PluginInterface):
except exceptions.InvalidAddressException:
FullDllName = ""
dumped = False
file_output = "Disabled"
if self.config['dump']:
filedata = dlllist.DllList.dump_pe(self.context, pe_table_name, mod)
if filedata:
self.produce_file(filedata)
dumped = True
filedata = dlllist.DllList.dump_pe(self.context, pe_table_name, mod, self._file_handler)
if filedata and filedata.committed:
file_output = filedata.preferred_filename
else:
file_output = "Error outputting file"
yield (0, (format_hints.Hex(mod.vol.offset), format_hints.Hex(mod.DllBase),
format_hints.Hex(mod.SizeOfImage), BaseDllName, FullDllName, dumped))
yield (0, (
format_hints.Hex(mod.vol.offset),
format_hints.Hex(mod.DllBase),
format_hints.Hex(mod.SizeOfImage),
BaseDllName,
FullDllName,
file_output
))
def run(self):
return renderers.TreeGrid([("Offset", format_hints.Hex), ("Base", format_hints.Hex), ("Size", format_hints.Hex),
("Name", str), ("Path", str), ("Dumped", bool)], self._generator())
("Name", str), ("Path", str), ("File output", str)], self._generator())
+19 -12
View File
@@ -10,7 +10,7 @@ from volatility.framework import renderers
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
from volatility.framework.symbols import intermed
from volatility.framework.symbols.windows import extensions
from volatility.framework.symbols.windows.extensions import pe
from volatility.plugins.windows import pslist, dlllist
vollog = logging.getLogger(__name__)
@@ -29,8 +29,8 @@ class Modules(interfaces.plugins.PluginInterface):
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"),
requirements.VersionRequirement(name = 'pslist', component = pslist.PsList, version = (1, 1, 0)),
requirements.VersionRequirement(name = 'dlllist', component = dlllist.DllList, version = (1, 0, 0)),
requirements.VersionRequirement(name = 'pslist', component = pslist.PsList, version = (2, 0, 0)),
requirements.VersionRequirement(name = 'dlllist', component = dlllist.DllList, version = (2, 0, 0)),
requirements.BooleanRequirement(name = 'dump',
description = "Extract listed modules",
default = False,
@@ -42,7 +42,7 @@ class Modules(interfaces.plugins.PluginInterface):
self.config_path,
"windows",
"pe",
class_types = extensions.pe.class_types)
class_types = pe.class_types)
for mod in self.list_modules(self.context, self.config['primary'], self.config['nt_symbols']):
@@ -56,15 +56,22 @@ class Modules(interfaces.plugins.PluginInterface):
except exceptions.InvalidAddressException:
FullDllName = ""
dumped = False
file_output = "Disabled"
if self.config['dump']:
filedata = dlllist.DllList.dump_pe(self.context, pe_table_name, mod)
if filedata:
self.produce_file(filedata)
dumped = True
filedata = dlllist.DllList.dump_pe(self.context, pe_table_name, mod, self._file_handler)
if filedata and filedata.committed:
file_output = filedata.preferred_filename
else:
file_output = "Error outputting file"
yield (0, (format_hints.Hex(mod.vol.offset), format_hints.Hex(mod.DllBase),
format_hints.Hex(mod.SizeOfImage), BaseDllName, FullDllName, dumped))
yield (0, (
format_hints.Hex(mod.vol.offset),
format_hints.Hex(mod.DllBase),
format_hints.Hex(mod.SizeOfImage),
BaseDllName,
FullDllName,
file_output
))
@classmethod
def get_session_layers(cls,
@@ -175,4 +182,4 @@ class Modules(interfaces.plugins.PluginInterface):
def run(self):
return renderers.TreeGrid([("Offset", format_hints.Hex), ("Base", format_hints.Hex), ("Size", format_hints.Hex),
("Name", str), ("Path", str), ("Dumped", bool)], self._generator())
("Name", str), ("Path", str), ("File output", str)], self._generator())
+25 -19
View File
@@ -4,14 +4,14 @@
import datetime
import logging
from typing import Callable, Iterable, List
from typing import Callable, Iterable, List, Type
from volatility.framework import renderers, interfaces, layers, constants
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
from volatility.framework.renderers import format_hints
from volatility.framework.symbols import intermed
from volatility.framework.symbols.windows import extensions
from volatility.framework.symbols.windows.extensions import pe
from volatility.plugins import timeliner
vollog = logging.getLogger(__name__)
@@ -21,7 +21,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Lists the processes present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (1, 1, 0)
_version = (2, 0, 0)
PHYSICAL_DEFAULT = False
@classmethod
@@ -46,18 +46,23 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
]
@classmethod
def process_dump(cls, context: interfaces.context.ContextInterface, kernel_table_name: str, pe_table_name: str,
proc: interfaces.objects.ObjectInterface) -> interfaces.plugins.FileInterface:
"""Extracts the complete data for a process as a FileInterface
def process_dump(cls,
context: interfaces.context.ContextInterface,
kernel_table_name: str, pe_table_name: str,
proc: interfaces.objects.ObjectInterface,
file_handler: Type[
interfaces.plugins.FileHandlerInterface]) -> interfaces.plugins.FileHandlerInterface:
"""Extracts the complete data for a process as a FileHandlerInterface
Args:
context: the context to operate upon
kernel_table_name: the name for the symbol table containing the kernel's symbols
pe_table_name: the name for the symbol table containing the PE format symbols
proc: the process object whose memory should be output
file_handler: class to write construct for writing the file
Returns:
A FileInterface object containing the complete data for the process or None in the case of failure
A FileHandlerInterface object containing the complete data for the process or None in the case of failure
"""
filedata = None
@@ -70,11 +75,10 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
dos_header = context.object(pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
offset = peb.ImageBaseAddress,
layer_name = proc_layer_name)
filedata = interfaces.plugins.FileInterface("pid.{0}.{1:#x}.dmp".format(proc.UniqueProcessId,
peb.ImageBaseAddress))
filedata = file_handler("pid.{0}.{1:#x}.dmp".format(proc.UniqueProcessId, peb.ImageBaseAddress))
for offset, data in dos_header.reconstruct():
filedata.data.seek(offset)
filedata.data.write(data)
filedata.seek(offset)
filedata.write(data)
except Exception as excp:
vollog.debug("Unable to dump PE with pid {}: {}".format(proc.UniqueProcessId, excp))
@@ -168,7 +172,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
self.config_path,
"windows",
"pe",
class_types = extensions.pe.class_types)
class_types = pe.class_types)
memory = self.context.layers[self.config['primary']]
if not isinstance(memory, layers.intel.Intel):
@@ -184,17 +188,19 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
else:
(_, _, offset, _, _) = list(memory.mapping(offset = proc.vol.offset, length = 0))[0]
dumped = False
file_output = "Disabled"
if self.config['dump']:
filedata = self.process_dump(self.context, self.config['nt_symbols'], pe_table_name, proc)
if filedata:
dumped = True
self.produce_file(filedata)
filedata = self.process_dump(self.context, self.config['nt_symbols'], pe_table_name, proc,
self._file_handler)
if filedata and filedata.committed:
file_output = filedata.preferred_filename
else:
file_output = "Error outputting file"
yield (0, (proc.UniqueProcessId, proc.InheritedFromUniqueProcessId,
proc.ImageFileName.cast("string", max_length = proc.ImageFileName.vol.count, errors = 'replace'),
format_hints.Hex(offset), proc.ActiveThreads, proc.get_handle_count(), proc.get_session_id(),
proc.get_is_wow64(), proc.get_create_time(), proc.get_exit_time(), dumped))
proc.get_is_wow64(), proc.get_create_time(), proc.get_exit_time(), file_output))
def generate_timeline(self):
for row in self._generator():
@@ -210,4 +216,4 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
("Offset{0}".format(offsettype), format_hints.Hex), ("Threads", int),
("Handles", int), ("SessionId", int), ("Wow64", bool),
("CreateTime", datetime.datetime), ("ExitTime", datetime.datetime),
("Dumped", bool)], self._generator())
("File output", str)], self._generator())
@@ -6,7 +6,6 @@ from typing import Iterator, List, Tuple, Iterable, Optional
from volatility.framework import renderers, interfaces, exceptions
from volatility.framework.configuration import requirements
from volatility.framework.interfaces import plugins
from volatility.framework.layers import registry
from volatility.framework.renderers import format_hints
from volatility.plugins.windows.registry import hivescan
@@ -71,7 +70,7 @@ class HiveList(interfaces.plugins.PluginInterface):
symbol_table = self.config["nt_symbols"],
filter_string = self.config.get('filter', None)):
dumped = False
file_output = "Disabled"
if self.config['dump']:
# Construct the hive
hive = next(
@@ -83,23 +82,22 @@ class HiveList(interfaces.plugins.PluginInterface):
maxaddr = hive.hive.Storage[0].Length
hive_name = self._sanitize_hive_name(hive.get_name())
filedata = plugins.FileInterface('registry.{}.{}.hive'.format(hive_name, hex(hive.hive_offset)))
if hive._base_block:
hive_data = self.context.layers[hive.dependencies[0]].read(hive.hive.BaseBlock, 1 << 12)
else:
hive_data = '\x00' * (1 << 12)
filedata.data.write(hive_data)
with self._file_handler('registry.{}.{}.hive'.format(hive_name, hex(hive.hive_offset))) as filedata:
if hive._base_block:
hive_data = self.context.layers[hive.dependencies[0]].read(hive.hive.BaseBlock, 1 << 12)
else:
hive_data = '\x00' * (1 << 12)
filedata.write(hive_data)
for i in range(0, maxaddr, chunk_size):
current_chunk_size = min(chunk_size, maxaddr - i)
data = hive.read(i, current_chunk_size, pad = True)
filedata.data.write(data)
# if self._progress_callback:
# self._progress_callback((i / maxaddr) * 100, 'Writing layer {}'.format(hive_name))
self.produce_file(filedata)
dumped = True
for i in range(0, maxaddr, chunk_size):
current_chunk_size = min(chunk_size, maxaddr - i)
data = hive.read(i, current_chunk_size, pad = True)
filedata.write(data)
# if self._progress_callback:
# self._progress_callback((i / maxaddr) * 100, 'Writing layer {}'.format(hive_name))
file_output = filedata.preferred_filename
yield (0, (format_hints.Hex(hive_object.vol.offset), hive_object.get_name() or "", dumped))
yield (0, (format_hints.Hex(hive_object.vol.offset), hive_object.get_name() or "", file_output))
@classmethod
def list_hives(cls,
@@ -238,5 +236,5 @@ class HiveList(interfaces.plugins.PluginInterface):
hex(hive.vol.offset)))
def run(self) -> renderers.TreeGrid:
return renderers.TreeGrid([("Offset", format_hints.Hex), ("FileFullPath", str), ("Dumped", bool)],
return renderers.TreeGrid([("Offset", format_hints.Hex), ("FileFullPath", str), ("File output", str)],
self._generator())
@@ -25,7 +25,7 @@ class Strings(interfaces.plugins.PluginInterface):
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (1, 0, 0)),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)),
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
@@ -31,7 +31,7 @@ class SvcScan(interfaces.plugins.PluginInterface):
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)),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)),
requirements.PluginRequirement(name = 'poolscanner', plugin = poolscanner.PoolScanner, version = (1, 0, 0)),
requirements.PluginRequirement(name = 'vadyarascan', plugin = vadyarascan.VadYaraScan, version = (1, 0, 0))
]
@@ -5,7 +5,7 @@
import logging
from typing import Callable, List, Generator, Iterable, Optional
from volatility.framework import renderers, interfaces, exceptions
from volatility.framework import renderers, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
from volatility.framework.renderers import format_hints
@@ -58,7 +58,7 @@ class VadInfo(interfaces.plugins.PluginInterface):
description = 'Filter on specific process IDs',
element_type = int,
optional = True),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (1, 0, 0)),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)),
requirements.BooleanRequirement(name = 'dump',
description = "Extract listed memory ranges",
default = False,
@@ -180,6 +180,7 @@ class VadInfo(interfaces.plugins.PluginInterface):
for proc in procs:
process_name = utility.array_to_string(proc.ImageFileName)
proc_layer_name = proc.add_process_layer()
for vad in self.list_vads(proc, filter_func = filter_func):
@@ -44,7 +44,7 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
default = 0x40000000,
description = "Set the maximum size (default is 1GB)",
optional = True),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (1, 0, 0)),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)),
requirements.VersionRequirement(name = 'yarascanner', component = yarascan.YaraScanner,
version = (2, 0, 0)),
requirements.ListRequirement(name = 'pid',
@@ -10,7 +10,7 @@ from volatility.framework import exceptions, renderers, constants, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
from volatility.framework.symbols import intermed
from volatility.framework.symbols.windows import extensions
from volatility.framework.symbols.windows.extensions import pe
from volatility.plugins.windows import pslist, modules, dlllist
vollog = logging.getLogger(__name__)
@@ -32,9 +32,9 @@ class VerInfo(interfaces.plugins.PluginInterface):
## TODO: we might add a regex option on the name later, but otherwise we're good
## TODO: and we don't want any CLI options from pslist, modules, or moddump
return [
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (1, 0, 0)),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)),
requirements.PluginRequirement(name = 'modules', plugin = modules.Modules, version = (1, 0, 0)),
requirements.VersionRequirement(name = 'dlllist', component = dlllist.DllList, version = (1, 0, 0)),
requirements.VersionRequirement(name = 'dlllist', component = dlllist.DllList, version = (2, 0, 0)),
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
@@ -101,7 +101,7 @@ class VerInfo(interfaces.plugins.PluginInterface):
self.config_path,
"windows",
"pe",
class_types = extensions.pe.class_types)
class_types = pe.class_types)
for mod in mods:
try:
@@ -10,6 +10,8 @@ from volatility.plugins.windows.registry import hivelist, printkey
class Certificates(interfaces.plugins.PluginInterface):
"""Lists the certificates in the registry's Certificate Store."""
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
@@ -55,10 +57,9 @@ class Certificates(interfaces.plugins.PluginInterface):
key_hash = key_path[key_path.rindex("\\") + 1:]
if not isinstance(certificate_data, interfaces.renderers.BaseAbsentValue):
filedata = interfaces.plugins.FileInterface("{} - {} - {}.crt".format(
hex(hive.hive_offset), reg_section, key_hash))
filedata.data.write(certificate_data)
self.produce_file(filedata)
with self._file_handler("{} - {} - {}.crt".format(
hex(hive.hive_offset), reg_section, key_hash), True) as filedata:
filedata.write(certificate_data)
yield (0, (top_key, reg_section, key_hash, name))
except KeyError:
# Key wasn't found in this hive, carry on
+1
View File
@@ -13,6 +13,7 @@ vollog = logging.getLogger(__name__)
class Statistics(plugins.PluginInterface):
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: