diff --git a/doc/source/using-as-a-library.rst b/doc/source/using-as-a-library.rst index 39fe5f4c4..abed65225 100644 --- a/doc/source/using-as-a-library.rst +++ b/doc/source/using-as-a-library.rst @@ -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 diff --git a/volatility/framework/interfaces/plugins.py b/volatility/framework/interfaces/plugins.py index d063e4beb..15f45e7a4 100644 --- a/volatility/framework/interfaces/plugins.py +++ b/volatility/framework/interfaces/plugins.py @@ -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") diff --git a/volatility/framework/plugins/__init__.py b/volatility/framework/plugins/__init__.py index 1418aefbb..b00b4ea47 100644 --- a/volatility/framework/plugins/__init__.py +++ b/volatility/framework/plugins/__init__.py @@ -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 diff --git a/volatility/framework/plugins/layerwriter.py b/volatility/framework/plugins/layerwriter.py index 7656ad630..5e3fa04b2 100644 --- a/volatility/framework/plugins/layerwriter.py +++ b/volatility/framework/plugins/layerwriter.py @@ -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()) diff --git a/volatility/framework/plugins/timeliner.py b/volatility/framework/plugins/timeliner.py index 4d51a163e..f0203ddfd 100644 --- a/volatility/framework/plugins/timeliner.py +++ b/volatility/framework/plugins/timeliner.py @@ -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 diff --git a/volatility/framework/plugins/windows/dlllist.py b/volatility/framework/plugins/windows/dlllist.py index eb9b09c0d..ab50ee61b 100644 --- a/volatility/framework/plugins/windows/dlllist.py +++ b/volatility/framework/plugins/windows/dlllist.py @@ -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, diff --git a/volatility/framework/plugins/windows/malfind.py b/volatility/framework/plugins/windows/malfind.py index 5ddd75363..80f8cc515 100644 --- a/volatility/framework/plugins/windows/malfind.py +++ b/volatility/framework/plugins/windows/malfind.py @@ -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( diff --git a/volatility/framework/plugins/windows/modules.py b/volatility/framework/plugins/windows/modules.py index 8624cc66c..df2c04fef 100644 --- a/volatility/framework/plugins/windows/modules.py +++ b/volatility/framework/plugins/windows/modules.py @@ -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), diff --git a/volatility/framework/plugins/windows/pslist.py b/volatility/framework/plugins/windows/pslist.py index 4bfb13194..4cd174856 100644 --- a/volatility/framework/plugins/windows/pslist.py +++ b/volatility/framework/plugins/windows/pslist.py @@ -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, diff --git a/volatility/framework/plugins/windows/psscan.py b/volatility/framework/plugins/windows/psscan.py index 6deb4c52d..769b3e879 100644 --- a/volatility/framework/plugins/windows/psscan.py +++ b/volatility/framework/plugins/windows/psscan.py @@ -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 diff --git a/volatility/framework/plugins/windows/vadinfo.py b/volatility/framework/plugins/windows/vadinfo.py index 089f1b6d4..2a23fa4f9 100644 --- a/volatility/framework/plugins/windows/vadinfo.py +++ b/volatility/framework/plugins/windows/vadinfo.py @@ -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),