From b70321002448f9be0be5cf536b2b57f93bd05bdf Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sat, 29 Aug 2020 17:51:18 +0100 Subject: [PATCH] Windows: Refactor names of FileInterfaces --- volatility/cli/__init__.py | 9 ++++-- volatility/framework/interfaces/plugins.py | 30 ++++++++++++------- volatility/framework/plugins/configwriter.py | 8 ++--- volatility/framework/plugins/layerwriter.py | 6 ++-- volatility/framework/plugins/timeliner.py | 8 ++--- .../framework/plugins/windows/dlllist.py | 26 ++++++++-------- .../framework/plugins/windows/malfind.py | 6 ++-- .../framework/plugins/windows/memmap.py | 11 +++---- .../framework/plugins/windows/modscan.py | 6 ++-- .../framework/plugins/windows/modules.py | 6 ++-- .../framework/plugins/windows/pslist.py | 18 +++++------ .../plugins/windows/registry/hivelist.py | 9 +++--- .../framework/plugins/windows/vadinfo.py | 30 +++++++++---------- .../plugins/windows/registry/certificates.py | 4 +-- 14 files changed, 95 insertions(+), 82 deletions(-) diff --git a/volatility/cli/__init__.py b/volatility/cli/__init__.py index 455f62c9f..798b573a3 100644 --- a/volatility/cli/__init__.py +++ b/volatility/cli/__init__.py @@ -465,6 +465,7 @@ class CommandLine: return self.seek(0) + if output_dir is None: raise TypeError("Output directory is not a string") os.makedirs(output_dir, exist_ok = True) @@ -473,13 +474,15 @@ class CommandLine: 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): + counter = 1 + while os.path.exists(output_filename): + output_filename = "{}-{}.{}".format(filename, counter, extension) + counter += 1 with open(output_filename, "wb") as current_file: current_file.write(self.read()) self._committed = True 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 diff --git a/volatility/framework/interfaces/plugins.py b/volatility/framework/interfaces/plugins.py index cfe12c55b..d063e4beb 100644 --- a/volatility/framework/interfaces/plugins.py +++ b/volatility/framework/interfaces/plugins.py @@ -20,11 +20,13 @@ vollog = logging.getLogger(__name__) class FileHandlerInterface(IO[bytes]): - """Class for storing Files in the plugin as a means to output a file or - files when necessary.""" + """Class for storing Files in the plugin as a means to output a file when necessary. + + This can be used as ContextManager that will close/produce the file automatically when exiting the context block + """ def __init__(self, filename: str) -> None: - """Creates a FileTemplate + """Creates a FileHandler Args: filename: The requested name of the filename for the data @@ -35,19 +37,26 @@ class FileHandlerInterface(IO[bytes]): @property def preferred_filename(self): + """The preferred filename to save the data to. + Until this file has been written, this value may not be the final filename the data is written to. + """ return self._preferred_filename @preferred_filename.setter def preferred_filename(self, filename): """Sets the preferred filename""" if self.closed: - raise IOError("FileTemplate name cannot be changed once closed") + raise IOError("FileHandler name cannot be changed once closed") if not isinstance(filename, str): - raise TypeError("FileTemplateInterface preferred filenames must be strings") + raise TypeError("FileHandler preferred filenames must be strings") if os.path.sep in filename: - raise ValueError("FileTemplateInterface filenames cannot contain path separators") + raise ValueError("FileHandler filenames cannot contain path separators") self._preferred_filename = filename + @abstractmethod + def close(self): + """Method that commits the file and fixes the final filename for use""" + def __enter__(self): return self @@ -115,11 +124,10 @@ class PluginInterface(interfaces.configuration.ConfigurableInterface, framework.require_interface_version(*self._required_framework_version) - def open(self, preferred_filename: str) -> FileHandlerInterface: - """Opens a file for output in bytes mode""" - if self._file_handler is not None: - return self._file_handler(preferred_filename) - raise IOError("FileTemplate not specified for this plugin") + @property + def open(self): + """Returns a context manager and thus can be called like open""" + return self._file_handler def set_file_handler(self, handler: Type[FileHandlerInterface]) -> None: """Sets the file handler to be used by this plugin.""" diff --git a/volatility/framework/plugins/configwriter.py b/volatility/framework/plugins/configwriter.py index caec2ff8e..3d44edfe5 100644 --- a/volatility/framework/plugins/configwriter.py +++ b/volatility/framework/plugins/configwriter.py @@ -39,10 +39,10 @@ class ConfigWriter(plugins.PluginInterface): config = dict(self.context.config) filename = "config.extra" try: - with self.open(filename) 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") + with self.open(filename) as file_data: + file_data.write(bytes(json.dumps(config, sort_keys = True, indent = 2), 'raw_unicode_escape')) + except Exception as excp: + vollog.warning("Unable to JSON encode configuration: {}".format(excp)) for k, v in config.items(): yield (0, (k, json.dumps(v))) diff --git a/volatility/framework/plugins/layerwriter.py b/volatility/framework/plugins/layerwriter.py index c744cadb7..01808c6af 100644 --- a/volatility/framework/plugins/layerwriter.py +++ b/volatility/framework/plugins/layerwriter.py @@ -47,7 +47,7 @@ class LayerWriter(plugins.PluginInterface): chunk_size: Optional[int] = None, progress_callback: Optional[constants.ProgressCallback] = None) -> Optional[ plugins.FileHandlerInterface]: - """Produces a filedata from the named layer in the provided context + """Produces a FileHandler from the named layer in the provided context or None on failure Args: context: the context from which to read the memory layer @@ -65,11 +65,11 @@ class LayerWriter(plugins.PluginInterface): chunk_size = cls.default_block_size filehandler = file_handler(preferred_name) - with filehandler as filedata: + with filehandler 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) - filedata.write(data) + file_data.write(data) if progress_callback: progress_callback((i / layer.maximum_address) * 100, 'Writing layer {}'.format(layer_name)) return filehandler diff --git a/volatility/framework/plugins/timeliner.py b/volatility/framework/plugins/timeliner.py index c5b3c3fce..644d5b9a6 100644 --- a/volatility/framework/plugins/timeliner.py +++ b/volatility/framework/plugins/timeliner.py @@ -138,8 +138,8 @@ class Timeliner(interfaces.plugins.PluginInterface): # Write out a body file if necessary if self.config.get('create-bodyfile', True): - with self.open("volatility.body") as filedata: - with io.TextIOWrapper(filedata, write_through = True) as fp: + with self.open("volatility.body") as file_data: + with io.TextIOWrapper(file_data, 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 @@ -203,8 +203,8 @@ class Timeliner(interfaces.plugins.PluginInterface): for entry in old_dict: total_config[interfaces.configuration.path_join(plugin.__class__.__name__, entry)] = old_dict[entry] - with self.open("config.json") as filedata: - with io.TextIOWrapper(filedata, write_through = True) as fp: + with self.open("config.json") as file_data: + with io.TextIOWrapper(file_data, write_through = True) as fp: json.dump(total_config, fp, sort_keys = True, indent = 2) return renderers.TreeGrid(columns = [("Plugin", str), ("Description", str), ("Created Date", datetime.datetime), diff --git a/volatility/framework/plugins/windows/dlllist.py b/volatility/framework/plugins/windows/dlllist.py index faf745a5f..647661e67 100644 --- a/volatility/framework/plugins/windows/dlllist.py +++ b/volatility/framework/plugins/windows/dlllist.py @@ -71,23 +71,23 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): if layer_name is None: layer_name = dll_entry.vol.layer_name - filehandler = file_handler("{}{}.{:#x}.{:#x}.dmp".format(prefix, - ntpath.basename(name), - dll_entry.vol.offset, - dll_entry.DllBase)) + file_handler = file_handler("{}{}.{:#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 filehandler as filedata: + with file_handler as file_data: for offset, data in dos_header.reconstruct(): - filedata.seek(offset) - filedata.write(data) - except (IOError, exceptions.VolatilityException, OverflowError) as excp: + file_data.seek(offset) + file_data.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 - return filehandler + return file_handler def _generator(self, procs): pe_table_name = intermed.IntermediateSymbolTable.create(self.context, @@ -128,11 +128,11 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): file_output = "Disabled" if self.config['dump']: - filedata = self.dump_pe(self.context, pe_table_name, entry, self._file_handler, - proc_layer_name, prefix = "pid.{}.".format(proc_id)) + file_handler = self.dump_pe(self.context, pe_table_name, entry, self._file_handler, + proc_layer_name, prefix = "pid.{}.".format(proc_id)) file_output = "Error outputting file" - if filedata: - file_output = filedata.preferred_filename + if file_handler: + file_output = file_handler.preferred_filename yield (0, (proc.UniqueProcessId, proc.ImageFileName.cast("string", diff --git a/volatility/framework/plugins/windows/malfind.py b/volatility/framework/plugins/windows/malfind.py index 0464621a9..bf87bdfa1 100644 --- a/volatility/framework/plugins/windows/malfind.py +++ b/volatility/framework/plugins/windows/malfind.py @@ -36,7 +36,7 @@ class Malfind(interfaces.plugins.PluginInterface): default = False, optional = True), requirements.VersionRequirement(name = 'pslist', component = pslist.PsList, version = (2, 0, 0)), - requirements.VersionRequirement(name = 'vadinfo', component = vadinfo.VadInfo, version = (1, 1, 0)) + requirements.VersionRequirement(name = 'vadinfo', component = vadinfo.VadInfo, version = (2, 0, 0)) ] @classmethod @@ -135,8 +135,8 @@ class Malfind(interfaces.plugins.PluginInterface): if self.config['dump']: file_output = "Error outputting to file" try: - filedata = vadinfo.VadInfo.vad_dump(self.context, proc, vad, self._file_handler) - file_output = filedata.preferred_filename + file_handler = vadinfo.VadInfo.vad_dump(self.context, proc, vad, self._file_handler) + file_output = file_handler.preferred_filename except (exceptions.InvalidAddressException, OverflowError) as excp: vollog.debug("Unable to dump PE with pid {0}.{1:#x}: {2}".format(proc.UniqueProcessId, vad.get_start(), excp)) diff --git a/volatility/framework/plugins/windows/memmap.py b/volatility/framework/plugins/windows/memmap.py index 3518886f6..da9796eed 100644 --- a/volatility/framework/plugins/windows/memmap.py +++ b/volatility/framework/plugins/windows/memmap.py @@ -48,7 +48,8 @@ class Memmap(interfaces.plugins.PluginInterface): excp.layer_name)) continue - with self.open("pid.{}.dmp".format(pid)) as filedata: + file_handler = self.open("pid.{}.dmp".format(pid)) + with file_handler as file_data: for mapval in proc_layer.mapping(0x0, proc_layer.maximum_address, ignore_errors = True): offset, size, mapped_offset, mapped_size, maplayer = mapval @@ -57,12 +58,12 @@ class Memmap(interfaces.plugins.PluginInterface): if self.config['dump']: try: data = proc_layer.read(offset, size, pad = True) - filedata.write(data) - file_output = filedata.preferred_filename + file_data.write(data) + file_output = file_handler.preferred_filename except exceptions.InvalidAddressException: file_output = "Error outputting to file" - vollog.debug("Unable to write {}'s address {} to {}.dmp".format(proc_layer_name, offset, - filedata.preferred_filename)) + vollog.debug("Unable to write {}'s address {} to {}".format(proc_layer_name, offset, + file_handler.preferred_filename)) yield (0, ( format_hints.Hex(offset), diff --git a/volatility/framework/plugins/windows/modscan.py b/volatility/framework/plugins/windows/modscan.py index 4e87790bc..6c45ed101 100644 --- a/volatility/framework/plugins/windows/modscan.py +++ b/volatility/framework/plugins/windows/modscan.py @@ -79,10 +79,10 @@ class ModScan(interfaces.plugins.PluginInterface): file_output = "Disabled" if self.config['dump']: - filedata = dlllist.DllList.dump_pe(self.context, pe_table_name, mod, self._file_handler) + file_handler = dlllist.DllList.dump_pe(self.context, pe_table_name, mod, self.open) file_output = "Error outputting file" - if filedata: - file_output = filedata.preferred_filename + if file_handler: + file_output = file_handler.preferred_filename yield (0, ( format_hints.Hex(mod.vol.offset), diff --git a/volatility/framework/plugins/windows/modules.py b/volatility/framework/plugins/windows/modules.py index 0d8faab27..88b4b6fee 100644 --- a/volatility/framework/plugins/windows/modules.py +++ b/volatility/framework/plugins/windows/modules.py @@ -58,10 +58,10 @@ class Modules(interfaces.plugins.PluginInterface): file_output = "Disabled" if self.config['dump']: - filedata = dlllist.DllList.dump_pe(self.context, pe_table_name, mod, self._file_handler) + file_handler = dlllist.DllList.dump_pe(self.context, pe_table_name, mod, self._file_handler) file_output = "Error outputting file" - if filedata: - file_output = filedata.preferred_filename + if file_handler: + file_output = file_handler.preferred_filename yield (0, ( format_hints.Hex(mod.vol.offset), diff --git a/volatility/framework/plugins/windows/pslist.py b/volatility/framework/plugins/windows/pslist.py index 59efa6aee..b8e4b7ff3 100644 --- a/volatility/framework/plugins/windows/pslist.py +++ b/volatility/framework/plugins/windows/pslist.py @@ -74,15 +74,15 @@ 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) - filehandler = file_handler("pid.{0}.{1:#x}.dmp".format(proc.UniqueProcessId, peb.ImageBaseAddress)) - with filehandler as filedata: + file_handler = file_handler("pid.{0}.{1:#x}.dmp".format(proc.UniqueProcessId, peb.ImageBaseAddress)) + with file_handler as file_data: for offset, data in dos_header.reconstruct(): - filedata.seek(offset) - filedata.write(data) + file_data.seek(offset) + file_data.write(data) except Exception as excp: vollog.debug("Unable to dump PE with pid {}: {}".format(proc.UniqueProcessId, excp)) - return filehandler + return file_handler @classmethod def create_pid_filter(cls, pid_list: List[int] = None) -> Callable[[interfaces.objects.ObjectInterface], bool]: @@ -190,11 +190,11 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): file_output = "Disabled" if self.config['dump']: - filedata = self.process_dump(self.context, self.config['nt_symbols'], pe_table_name, proc, - self._file_handler) + file_handler = self.process_dump(self.context, self.config['nt_symbols'], pe_table_name, proc, + self._file_handler) file_output = "Error outputting file" - if filedata: - file_output = filedata.preferred_filename + if file_handler: + file_output = file_handler.preferred_filename yield (0, (proc.UniqueProcessId, proc.InheritedFromUniqueProcessId, proc.ImageFileName.cast("string", max_length = proc.ImageFileName.vol.count, errors = 'replace'), diff --git a/volatility/framework/plugins/windows/registry/hivelist.py b/volatility/framework/plugins/windows/registry/hivelist.py index 2f1e4f4c0..44dbe99a3 100644 --- a/volatility/framework/plugins/windows/registry/hivelist.py +++ b/volatility/framework/plugins/windows/registry/hivelist.py @@ -82,20 +82,21 @@ class HiveList(interfaces.plugins.PluginInterface): maxaddr = hive.hive.Storage[0].Length hive_name = self._sanitize_hive_name(hive.get_name()) - with self.open('registry.{}.{}.hive'.format(hive_name, hex(hive.hive_offset))) as filedata: + file_handler = self.open('registry.{}.{}.hive'.format(hive_name, hex(hive.hive_offset))) + with file_handler as file_data: 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) + file_data.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.write(data) + file_data.write(data) # if self._progress_callback: # self._progress_callback((i / maxaddr) * 100, 'Writing layer {}'.format(hive_name)) - file_output = filedata.preferred_filename + file_output = file_handler.preferred_filename yield (0, (format_hints.Hex(hive_object.vol.offset), hive_object.get_name() or "", file_output)) diff --git a/volatility/framework/plugins/windows/vadinfo.py b/volatility/framework/plugins/windows/vadinfo.py index 16ac8e638..4f82cc81e 100644 --- a/volatility/framework/plugins/windows/vadinfo.py +++ b/volatility/framework/plugins/windows/vadinfo.py @@ -34,7 +34,7 @@ class VadInfo(interfaces.plugins.PluginInterface): """Lists process memory ranges.""" _required_framework_version = (2, 0, 0) - _version = (1, 1, 0) + _version = (2, 0, 0) MAXSIZE_DEFAULT = 0 def __init__(self, *args, **kwargs): @@ -111,7 +111,7 @@ class VadInfo(interfaces.plugins.PluginInterface): def vad_dump(cls, context: interfaces.context.ContextInterface, proc: interfaces.objects.ObjectInterface, - vad: interfaces.objects.ObjectInterface, + vad: interfaces.objects.ObjectInterface, file_handler: Type[ interfaces.plugins.FileHandlerInterface]) -> Optional[interfaces.plugins.FileHandlerInterface]: """Extracts the complete data for Vad as a FileInterface. @@ -150,16 +150,16 @@ class VadInfo(interfaces.plugins.PluginInterface): file_name = "pid.{0}.vad.{1:#x}-{2:#x}.dmp".format(proc_id, vad_start, vad_end) try: file_handler = file_handler(file_name) - with file_handler as filedata: - 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 - filedata.write(data) - offset += to_read + with file_handler 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 except Exception as excp: vollog.debug("Unable to dump VAD {}: {}".format(file_name, excp)) @@ -188,10 +188,10 @@ class VadInfo(interfaces.plugins.PluginInterface): file_output = "Disabled" if self.config['dump']: - filedata = self.vad_dump(self.context, proc, vad, self._file_handler) + file_handler = self.vad_dump(self.context, proc, vad, self._file_handler) file_output = "Error outputting file" - if filedata: - file_output = filedata.preferred_filename + if file_handler: + file_output = file_handler.preferred_filename yield (0, (proc.UniqueProcessId, process_name, format_hints.Hex(vad.vol.offset), format_hints.Hex(vad.get_start()), format_hints.Hex(vad.get_end()), vad.get_tag(), diff --git a/volatility/plugins/windows/registry/certificates.py b/volatility/plugins/windows/registry/certificates.py index 38f64cbd4..52a91fffb 100644 --- a/volatility/plugins/windows/registry/certificates.py +++ b/volatility/plugins/windows/registry/certificates.py @@ -58,8 +58,8 @@ class Certificates(interfaces.plugins.PluginInterface): if not isinstance(certificate_data, interfaces.renderers.BaseAbsentValue): with self.open("{} - {} - {}.crt".format( - hex(hive.hive_offset), reg_section, key_hash)) as filedata: - filedata.write(certificate_data) + hex(hive.hive_offset), reg_section, key_hash)) as file_data: + file_data.write(certificate_data) yield (0, (top_key, reg_section, key_hash, name)) except KeyError: # Key wasn't found in this hive, carry on