mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-08 02:37:39 +02:00
Plugins: Make open method clearer to use
This highlights that the FileHandler class can also be seen as a method similar to open, and it removes unnecessary context managers, allowing plugins to close files as they wish (they must, however, remember to close the file for it to be committed).
This commit is contained in:
@@ -162,17 +162,16 @@ Dictionary of the hierarchy paths and their associated requirements that weren't
|
||||
|
||||
The plugin can then be instantiated with the context (containing the plugin's configuration) and the path that the
|
||||
plugin can find its configuration at. A progress_callback can also be provided to give users feedback whilst the
|
||||
plugin is running. Also, should the plugin produce files, a file_consumer can be set on the plugin, which will
|
||||
plugin is running. Also, should the plugin produce files, an open_method can be set on the plugin, which will
|
||||
be called whenever a plugin produces an auxiliary file.
|
||||
|
||||
::
|
||||
|
||||
constructed = plugin(context, plugin_config_path, progress_callback = progress_callback)
|
||||
constructed.set_file_handler(file_consumer)
|
||||
constructed.set_open_method(file_handler)
|
||||
|
||||
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`
|
||||
whose data attribute roughly mimics an IO class, but also contains a `preferred_filename` attribute as a hint.
|
||||
The file_handler must adhere to the :py:class:`~volatility.framework.interfaces.plugins.FileHandlerInterface`,
|
||||
which represents an IO[bytes] object but also contains a `preferred_filename` attribute as a hint.
|
||||
|
||||
All of this functionality has been condensed into a framework method called `construct_plugin` which will
|
||||
take and run the automagics, and instantiate the plugin on the provided `base_config_path`. It also
|
||||
|
||||
@@ -129,7 +129,7 @@ class PluginInterface(interfaces.configuration.ConfigurableInterface,
|
||||
"""Returns a context manager and thus can be called like open"""
|
||||
return self._file_handler
|
||||
|
||||
def set_file_handler(self, handler: Type[FileHandlerInterface]) -> None:
|
||||
def set_open_method(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")
|
||||
|
||||
@@ -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_handler: Type[interfaces.plugins.FileHandlerInterface]) -> interfaces.plugins.PluginInterface:
|
||||
open_method: 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_handler: Object to pass any generated files to
|
||||
open_method: class to provide context manager for opening the file
|
||||
|
||||
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_handler:
|
||||
constructed.set_file_handler(file_handler)
|
||||
if open_method:
|
||||
constructed.set_open_method(open_method)
|
||||
return constructed
|
||||
|
||||
@@ -44,7 +44,7 @@ class LayerWriter(plugins.PluginInterface):
|
||||
context: interfaces.context.ContextInterface,
|
||||
layer_name: str,
|
||||
preferred_name: str,
|
||||
file_handler: Type[plugins.FileHandlerInterface],
|
||||
open_method: Type[plugins.FileHandlerInterface],
|
||||
chunk_size: Optional[int] = None,
|
||||
progress_callback: Optional[constants.ProgressCallback] = None) -> Optional[plugins.FileHandlerInterface]:
|
||||
"""Produces a FileHandler from the named layer in the provided context or None on failure
|
||||
@@ -54,7 +54,7 @@ class LayerWriter(plugins.PluginInterface):
|
||||
layer_name: the name of the layer to write out
|
||||
preferred_name: a string with the preferred filename for hte file
|
||||
chunk_size: an optional size for the chunks that should be written (defaults to 0x500000)
|
||||
file_handler: class for creating FileHandler context managers
|
||||
open_method: class for creating FileHandler context managers
|
||||
progress_callback: an optional function that takes a percentage and a string that displays output
|
||||
"""
|
||||
|
||||
@@ -65,34 +65,34 @@ class LayerWriter(plugins.PluginInterface):
|
||||
if chunk_size is None:
|
||||
chunk_size = cls.default_block_size
|
||||
|
||||
file_handle = file_handler(preferred_name)
|
||||
with file_handle as file_data:
|
||||
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)
|
||||
file_data.write(data)
|
||||
if progress_callback:
|
||||
progress_callback((i / layer.maximum_address) * 100, 'Writing layer {}'.format(layer_name))
|
||||
file_handle = open_method(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)
|
||||
file_handle.write(data)
|
||||
if progress_callback:
|
||||
progress_callback((i / layer.maximum_address) * 100, 'Writing layer {}'.format(layer_name))
|
||||
return file_handle
|
||||
|
||||
def _generator(self):
|
||||
if self.config['primary'] not in self.context.layers:
|
||||
yield 0, ('Layer Name does not exist', )
|
||||
yield 0, ('Layer Name does not exist',)
|
||||
elif os.path.exists(self.config.get('output', self.default_output_name)):
|
||||
yield 0, ('Refusing to overwrite existing output file', )
|
||||
yield 0, ('Refusing to overwrite existing output file',)
|
||||
else:
|
||||
output_name = self.config.get('output', self.default_output_name)
|
||||
try:
|
||||
self.write_layer(self.context,
|
||||
self.config['primary'],
|
||||
output_name,
|
||||
self._file_handler,
|
||||
self.config.get('block_size', self.default_block_size),
|
||||
progress_callback = self._progress_callback)
|
||||
file_handle = self.write_layer(self.context,
|
||||
self.config['primary'],
|
||||
output_name,
|
||||
self.open,
|
||||
self.config.get('block_size', self.default_block_size),
|
||||
progress_callback = self._progress_callback)
|
||||
file_handle.close()
|
||||
except IOError as excp:
|
||||
yield 0, ('Layer cannot be written to {}: {}'.format(self.config['output_name'], excp), )
|
||||
yield 0, ('Layer cannot be written to {}: {}'.format(self.config['output_name'], excp),)
|
||||
|
||||
yield 0, ('Layer has been written to {}'.format(output_name), )
|
||||
yield 0, ('Layer has been written to {}'.format(output_name),)
|
||||
|
||||
def run(self):
|
||||
return renderers.TreeGrid([("Status", str)], self._generator())
|
||||
|
||||
@@ -184,11 +184,11 @@ 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_handler)
|
||||
self._progress_callback, self.open)
|
||||
|
||||
if isinstance(plugin, TimeLinerInterface):
|
||||
if not len(filter_list) or any(
|
||||
[filter in plugin.__module__ + '.' + plugin.__class__.__name__ for filter in filter_list]):
|
||||
[filter in plugin.__module__ + '.' + plugin.__class__.__name__ for filter in filter_list]):
|
||||
plugins_to_run.append(plugin)
|
||||
except exceptions.UnsatisfiedException as excp:
|
||||
# Remove the failed plugin from the list and continue
|
||||
|
||||
@@ -48,7 +48,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
context: interfaces.context.ContextInterface,
|
||||
pe_table_name: str,
|
||||
dll_entry: interfaces.objects.ObjectInterface,
|
||||
file_handler: Type[interfaces.plugins.FileHandlerInterface],
|
||||
open_method: Type[interfaces.plugins.FileHandlerInterface],
|
||||
layer_name: str = None,
|
||||
prefix: str = '') -> Optional[interfaces.plugins.FileHandlerInterface]:
|
||||
"""Extracts the complete data for a process as a FileInterface
|
||||
@@ -58,10 +58,11 @@ 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
|
||||
open_method: class for constructing output files
|
||||
|
||||
Returns:
|
||||
A FileInterface object containing the complete data for the DLL or None in the case of failure"""
|
||||
An open FileHandlerInterface object containing the complete data for the DLL or None in the case of failure
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
name = dll_entry.FullDllName.get_string()
|
||||
@@ -71,17 +72,16 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
if layer_name is None:
|
||||
layer_name = dll_entry.vol.layer_name
|
||||
|
||||
file_handle = file_handler("{}{}.{:#x}.{:#x}.dmp".format(prefix, ntpath.basename(name),
|
||||
dll_entry.vol.offset, dll_entry.DllBase))
|
||||
file_handle = open_method("{}{}.{:#x}.{:#x}.dmp".format(prefix, ntpath.basename(name),
|
||||
dll_entry.vol.offset, dll_entry.DllBase))
|
||||
|
||||
dos_header = context.object(pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
|
||||
offset = dll_entry.DllBase,
|
||||
layer_name = layer_name)
|
||||
|
||||
with file_handle as file_data:
|
||||
for offset, data in dos_header.reconstruct():
|
||||
file_data.seek(offset)
|
||||
file_data.write(data)
|
||||
for offset, data in dos_header.reconstruct():
|
||||
file_handle.seek(offset)
|
||||
file_handle.write(data)
|
||||
except (IOError, exceptions.VolatilityException, OverflowError, ValueError) as excp:
|
||||
vollog.debug("Unable to dump dll at offset {}: {}".format(dll_entry.DllBase, excp))
|
||||
return None
|
||||
@@ -129,11 +129,12 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
file_handle = self.dump_pe(self.context,
|
||||
pe_table_name,
|
||||
entry,
|
||||
self._file_handler,
|
||||
self.open,
|
||||
proc_layer_name,
|
||||
prefix = "pid.{}.".format(proc_id))
|
||||
file_output = "Error outputting file"
|
||||
if file_handle:
|
||||
file_handle.close()
|
||||
file_output = file_handle.preferred_filename
|
||||
|
||||
yield (0, (proc.UniqueProcessId,
|
||||
|
||||
@@ -105,8 +105,8 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
continue
|
||||
|
||||
if (vad.get_private_memory() == 1
|
||||
and vad.get_tag() == "VadS") or (vad.get_private_memory() == 0
|
||||
and protection_string != "PAGE_EXECUTE_WRITECOPY"):
|
||||
and vad.get_tag() == "VadS") or (vad.get_private_memory() == 0
|
||||
and protection_string != "PAGE_EXECUTE_WRITECOPY"):
|
||||
if cls.is_vad_empty(proc_layer, vad):
|
||||
continue
|
||||
|
||||
@@ -135,7 +135,8 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
if self.config['dump']:
|
||||
file_output = "Error outputting to file"
|
||||
try:
|
||||
file_handle = vadinfo.VadInfo.vad_dump(self.context, proc, vad, self._file_handler)
|
||||
file_handle = vadinfo.VadInfo.vad_dump(self.context, proc, vad, self.open)
|
||||
file_handle.close()
|
||||
file_output = file_handle.preferred_filename
|
||||
except (exceptions.InvalidAddressException, OverflowError) as excp:
|
||||
vollog.debug("Unable to dump PE with pid {0}.{1:#x}: {2}".format(
|
||||
|
||||
@@ -58,9 +58,10 @@ class Modules(interfaces.plugins.PluginInterface):
|
||||
|
||||
file_output = "Disabled"
|
||||
if self.config['dump']:
|
||||
file_handle = dlllist.DllList.dump_pe(self.context, pe_table_name, mod, self._file_handler)
|
||||
file_handle = dlllist.DllList.dump_pe(self.context, pe_table_name, mod, self.open)
|
||||
file_output = "Error outputting file"
|
||||
if file_handle:
|
||||
file_handle.close()
|
||||
file_output = file_handle.preferred_filename
|
||||
|
||||
yield (0, (format_hints.Hex(mod.vol.offset), format_hints.Hex(mod.DllBase),
|
||||
|
||||
@@ -49,7 +49,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
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:
|
||||
open_method: Type[interfaces.plugins.FileHandlerInterface]) -> interfaces.plugins.FileHandlerInterface:
|
||||
"""Extracts the complete data for a process as a FileHandlerInterface
|
||||
|
||||
Args:
|
||||
@@ -57,10 +57,10 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
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 provide context manager for opening the file
|
||||
open_method: class to provide context manager for opening the file
|
||||
|
||||
Returns:
|
||||
A FileHandlerInterface object containing the complete data for the process or None in the case of failure
|
||||
An open FileHandlerInterface object containing the complete data for the process or None in the case of failure
|
||||
"""
|
||||
|
||||
file_handle = None
|
||||
@@ -73,11 +73,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)
|
||||
file_handle = file_handler("pid.{0}.{1:#x}.dmp".format(proc.UniqueProcessId, peb.ImageBaseAddress))
|
||||
with file_handle as file_data:
|
||||
for offset, data in dos_header.reconstruct():
|
||||
file_data.seek(offset)
|
||||
file_data.write(data)
|
||||
file_handle = open_method("pid.{0}.{1:#x}.dmp".format(proc.UniqueProcessId, peb.ImageBaseAddress))
|
||||
for offset, data in dos_header.reconstruct():
|
||||
file_handle.seek(offset)
|
||||
file_handle.write(data)
|
||||
except Exception as excp:
|
||||
vollog.debug("Unable to dump PE with pid {}: {}".format(proc.UniqueProcessId, excp))
|
||||
|
||||
@@ -190,9 +189,10 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
file_output = "Disabled"
|
||||
if self.config['dump']:
|
||||
file_handle = self.process_dump(self.context, self.config['nt_symbols'], pe_table_name, proc,
|
||||
self._file_handler)
|
||||
self.open)
|
||||
file_output = "Error outputting file"
|
||||
if file_handle:
|
||||
file_handle.close()
|
||||
file_output = str(file_handle.preferred_filename)
|
||||
|
||||
yield (0, (proc.UniqueProcessId, proc.InheritedFromUniqueProcessId,
|
||||
|
||||
@@ -143,7 +143,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
self.config['nt_symbols'], proc)
|
||||
|
||||
file_handle = pslist.PsList.process_dump(self.context, self.config['nt_symbols'], pe_table_name,
|
||||
vproc, self._file_handler)
|
||||
vproc, self.open)
|
||||
file_output = "Error outputting file"
|
||||
if file_handle:
|
||||
file_output = file_handle.preferred_filename
|
||||
|
||||
@@ -112,18 +112,20 @@ class VadInfo(interfaces.plugins.PluginInterface):
|
||||
context: interfaces.context.ContextInterface,
|
||||
proc: interfaces.objects.ObjectInterface,
|
||||
vad: interfaces.objects.ObjectInterface,
|
||||
file_handler: Type[
|
||||
interfaces.plugins.FileHandlerInterface]) -> Optional[interfaces.plugins.FileHandlerInterface]:
|
||||
open_method: Type[
|
||||
interfaces.plugins.FileHandlerInterface],
|
||||
maxsize: int = MAXSIZE_DEFAULT) -> Optional[interfaces.plugins.FileHandlerInterface]:
|
||||
"""Extracts the complete data for Vad as a FileInterface.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
proc: an _EPROCESS instance
|
||||
vad: The suspected VAD to extract (ObjectInterface)
|
||||
open_method: class to provide context manager for opening the file
|
||||
maxsize: Max size of VAD section (default MAXSIZE_DEFAULT)
|
||||
|
||||
Returns:
|
||||
A FileInterface object containing the complete data for the process or None in the case of failure
|
||||
An open FileInterface object containing the complete data for the process or None in the case of failure
|
||||
"""
|
||||
|
||||
try:
|
||||
@@ -149,17 +151,16 @@ class VadInfo(interfaces.plugins.PluginInterface):
|
||||
proc_layer = context.layers[proc_layer_name]
|
||||
file_name = "pid.{0}.vad.{1:#x}-{2:#x}.dmp".format(proc_id, vad_start, vad_end)
|
||||
try:
|
||||
file_handle = file_handler(file_name)
|
||||
with file_handle as file_data:
|
||||
chunk_size = 1024 * 1024 * 10
|
||||
offset = vad_start
|
||||
while offset < vad_end:
|
||||
to_read = min(chunk_size, vad_end - offset)
|
||||
data = proc_layer.read(offset, to_read, pad = True)
|
||||
if not data:
|
||||
break
|
||||
file_data.write(data)
|
||||
offset += to_read
|
||||
file_handle = open_method(file_name)
|
||||
chunk_size = 1024 * 1024 * 10
|
||||
offset = vad_start
|
||||
while offset < vad_end:
|
||||
to_read = min(chunk_size, vad_end - offset)
|
||||
data = proc_layer.read(offset, to_read, pad = True)
|
||||
if not data:
|
||||
break
|
||||
file_handle.write(data)
|
||||
offset += to_read
|
||||
|
||||
except Exception as excp:
|
||||
vollog.debug("Unable to dump VAD {}: {}".format(file_name, excp))
|
||||
@@ -188,9 +189,10 @@ class VadInfo(interfaces.plugins.PluginInterface):
|
||||
|
||||
file_output = "Disabled"
|
||||
if self.config['dump']:
|
||||
file_handle = self.vad_dump(self.context, proc, vad, self._file_handler)
|
||||
file_handle = self.vad_dump(self.context, proc, vad, self.open)
|
||||
file_output = "Error outputting file"
|
||||
if file_handle:
|
||||
file_handle.close()
|
||||
file_output = file_handle.preferred_filename
|
||||
|
||||
yield (0, (proc.UniqueProcessId, process_name, format_hints.Hex(vad.vol.offset),
|
||||
|
||||
Reference in New Issue
Block a user