From a68d56e19109a81098ae208d33ae94a679ff0824 Mon Sep 17 00:00:00 2001 From: Eve Date: Tue, 13 Dec 2022 16:09:57 +0000 Subject: [PATCH 01/83] add linux.vmayarascan based on windows.vadtarascan --- .../framework/plugins/linux/vmayarascan.py | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 volatility3/framework/plugins/linux/vmayarascan.py diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py new file mode 100644 index 000000000..e9482089a --- /dev/null +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -0,0 +1,121 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Iterable, List, Tuple + +from volatility3.framework import interfaces, renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.renderers import format_hints +from volatility3.plugins import yarascan +from volatility3.plugins.linux import pslist + +class VmaYaraScan(interfaces.plugins.PluginInterface): + """Scans all virtual memory areas for tasks using yara.""" + + _required_framework_version = (2, 4, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.BooleanRequirement( + name="wide", + description="Match wide (unicode) strings", + default=False, + optional=True, + ), + requirements.StringRequirement( + name="yara_rules", description="Yara rules (as a string)", optional=True + ), + requirements.URIRequirement( + name="yara_file", description="Yara rules (as a file)", optional=True + ), + # This additional requirement is to follow suit with upstream, who feel that compiled rules could potentially be used to execute malicious code + # As such, there's a separate option to run compiled files, as happened with yara-3.9 and later + requirements.URIRequirement( + name="yara_compiled_file", + description="Yara compiled rules (as a file)", + optional=True, + ), + requirements.IntRequirement( + name="max_size", + default=0x40000000, + description="Set the maximum size (default is 1GB)", + optional=True, + ), + 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", + element_type=int, + description="Process IDs to include (all other processes are excluded)", + optional=True, + ), + ] + + def _generator(self): + kernel = self.context.modules[self.config["kernel"]] + + rules = yarascan.YaraScan.process_yara_options(dict(self.config)) + + filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) + for task in pslist.PsList.list_tasks( + context=self.context, + vmlinux_module_name=self.config["kernel"], + filter_func=filter_func, + ): + proc_layer_name = task.add_process_layer() + if not proc_layer_name: + continue + + proc_layer = self.context.layers[proc_layer_name] + for offset, rule_name, name, value in proc_layer.scan( + context=self.context, + scanner=yarascan.YaraScanner(rules=rules), + sections=self.get_vma_maps(task), + ): + yield 0, ( + format_hints.Hex(offset), + task.tgid, + rule_name, + name, + value, + ) + + @staticmethod + def get_vma_maps( + task: interfaces.objects.ObjectInterface, + ) -> Iterable[Tuple[int, int]]: + """Creates a map of start/end addresses for each virtual memory area in a task. + + Args: + task: The task object of which to read the vmas from + + Returns: + An iterable of tuples containing start and end addresses for each descriptor + """ + if task.mm: + for vma in task.mm.get_mmap_iter(): + vm_size = vma.vm_end - vma.vm_start + yield (vma.vm_start, vm_size) + + def run(self): + return renderers.TreeGrid( + [ + ("Offset", format_hints.Hex), + ("PID", int), + ("Rule", str), + ("Component", str), + ("Value", bytes), + ], + self._generator(), + ) From f82a3f520facdc4e9ba973ddf8316473bbd190fe Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 4 Jan 2023 09:32:39 +0000 Subject: [PATCH 02/83] liniting for linux.vmayarascan --- volatility3/framework/plugins/linux/vmayarascan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py index e9482089a..3efbd2ae1 100644 --- a/volatility3/framework/plugins/linux/vmayarascan.py +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -10,6 +10,7 @@ from volatility3.framework.renderers import format_hints from volatility3.plugins import yarascan from volatility3.plugins.linux import pslist + class VmaYaraScan(interfaces.plugins.PluginInterface): """Scans all virtual memory areas for tasks using yara.""" From 6f82e3d8cf7b173f07c51634016413b5ef2243c6 Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 4 Jan 2023 09:36:20 +0000 Subject: [PATCH 03/83] remove unused variable in linux.vmayarascan --- volatility3/framework/plugins/linux/vmayarascan.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py index 3efbd2ae1..c7a48cc14 100644 --- a/volatility3/framework/plugins/linux/vmayarascan.py +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -64,8 +64,6 @@ class VmaYaraScan(interfaces.plugins.PluginInterface): ] def _generator(self): - kernel = self.context.modules[self.config["kernel"]] - rules = yarascan.YaraScan.process_yara_options(dict(self.config)) filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) From 558b31cbdc9002dd8b0dacc2af8c5296ee3bcba5 Mon Sep 17 00:00:00 2001 From: cstation Date: Fri, 13 Jan 2023 11:58:11 +0100 Subject: [PATCH 04/83] Dump ELFs to file --- volatility3/framework/plugins/linux/elfs.py | 113 +++++++++++++++++- volatility3/framework/plugins/linux/pslist.py | 101 +++++++++++----- 2 files changed, 180 insertions(+), 34 deletions(-) diff --git a/volatility3/framework/plugins/linux/elfs.py b/volatility3/framework/plugins/linux/elfs.py index 822a69dd6..f56438f4f 100644 --- a/volatility3/framework/plugins/linux/elfs.py +++ b/volatility3/framework/plugins/linux/elfs.py @@ -4,20 +4,26 @@ """A module containing a collection of plugins that produce data typically found in Linux's /proc file system.""" -from typing import List +import logging +from typing import List, Optional, Type -from volatility3.framework import renderers, interfaces +from volatility3.framework import constants, interfaces, renderers from volatility3.framework.configuration import requirements from volatility3.framework.interfaces import plugins from volatility3.framework.objects import utility from volatility3.framework.renderers import format_hints +from volatility3.framework.symbols import intermed +from volatility3.framework.symbols.linux.extensions import elf from volatility3.plugins.linux import pslist +vollog = logging.getLogger(__name__) + class Elfs(plugins.PluginInterface): """Lists all memory mapped ELF files for all processes.""" _required_framework_version = (2, 0, 0) + _version = (2, 0, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -36,9 +42,95 @@ class Elfs(plugins.PluginInterface): element_type=int, optional=True, ), + requirements.BooleanRequirement( + name="dump", + description="Extract listed processes", + default=False, + optional=True, + ), ] + @classmethod + def elf_dump( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + elf_table_name: str, + vma: interfaces.objects.ObjectInterface, + task: interfaces.objects.ObjectInterface, + open_method: Type[interfaces.plugins.FileHandlerInterface], + ) -> Optional[interfaces.plugins.FileHandlerInterface]: + """Extracts an ELF as a FileHandlerInterface + Args: + context: the context to operate upon + layer_name: The name of the layer on which to operate + elf_table_name: the name for the symbol table containing the symbols for ELF-files + vma: virtual memory allocation of ELF + task: the task object whose memory should be output + open_method: class to provide context manager for opening the file + Returns: + An open FileHandlerInterface object containing the complete data for the task or None in the case of failure + """ + + proc_layer = context.layers[layer_name] + file_handle = None + + try: + elf_object = context.object( + elf_table_name + constants.BANG + "Elf", + offset=vma.vm_start, + layer_name=layer_name, + ) + + if not elf_object.is_valid(): + return None + + sections = {} + # TODO: Apply more effort to reconstruct ELF, e.g.: https://github.com/enbarberis/core2ELF64 ? + for phdr in elf_object.get_program_headers(): + if phdr.p_type != 1: # PT_LOAD = 1 + continue + + start = phdr.p_vaddr + size = phdr.p_memsz + end = start + size + + # Use complete memory pages for dumping + # If start isn't a multiple of 4096, stick to the highest multiple < start + # If end isn't a multiple of 4096, stick to the lowest multiple > end + if start % 4096: + start = start & ~0xFFF + + if end % 4096: + end = (end & ~0xFFF) + 4096 + + real_size = end - start + + if real_size < 0 or real_size > 100000000: + continue + + sections[start] = real_size + + elf_data = b"" + for section_start in sorted(sections.keys()): + read_size = sections[section_start] + + buf = proc_layer.read(vma.vm_start + section_start, read_size, pad=True) + elf_data = elf_data + buf + + file_handle = open_method( + f"pid.{task.pid}.{utility.array_to_string(task.comm)}.{vma.vm_start:#x}.dmp" + ) + file_handle.write(elf_data) + except Exception as e: + vollog.debug(f"Unable to dump ELF with pid {task.pid}: {e}") + + return file_handle + def _generator(self, tasks): + elf_table_name = intermed.IntermediateSymbolTable.create( + self.context, self.config_path, "linux", "elf", class_types=elf.class_types + ) for task in tasks: proc_layer_name = task.add_process_layer() if not proc_layer_name: @@ -60,6 +152,21 @@ class Elfs(plugins.PluginInterface): path = vma.get_name(self.context, task) + file_output = "Disabled" + if self.config["dump"]: + file_handle = self.elf_dump( + self.context, + proc_layer_name, + elf_table_name, + vma, + task, + self.open, + ) + file_output = "Error outputting file" + if file_handle: + file_handle.close() + file_output = str(file_handle.preferred_filename) + yield ( 0, ( @@ -68,6 +175,7 @@ class Elfs(plugins.PluginInterface): format_hints.Hex(vma.vm_start), format_hints.Hex(vma.vm_end), path, + file_output, ), ) @@ -81,6 +189,7 @@ class Elfs(plugins.PluginInterface): ("Start", format_hints.Hex), ("End", format_hints.Hex), ("File Path", str), + ("File Output", str), ], self._generator( pslist.PsList.list_tasks( diff --git a/volatility3/framework/plugins/linux/pslist.py b/volatility3/framework/plugins/linux/pslist.py index af260a772..16e370b6e 100644 --- a/volatility3/framework/plugins/linux/pslist.py +++ b/volatility3/framework/plugins/linux/pslist.py @@ -1,12 +1,15 @@ # This file is Copyright 2021 Volatility Foundation and licensed under the Volatility Software License 1.0 # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # -from typing import Callable, Iterable, List, Any, Tuple +from typing import Any, Callable, Iterable, List -from volatility3.framework import renderers, interfaces +from volatility3.framework import interfaces, renderers from volatility3.framework.configuration import requirements from volatility3.framework.objects import utility from volatility3.framework.renderers import format_hints +from volatility3.framework.symbols import intermed +from volatility3.framework.symbols.linux.extensions import elf +from volatility3.plugins.linux import elfs class PsList(interfaces.plugins.PluginInterface): @@ -24,6 +27,9 @@ class PsList(interfaces.plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), + requirements.PluginRequirement( + name="elfs", plugin=elfs.Elfs, version=(2, 0, 0) + ), requirements.ListRequirement( name="pid", description="Filter on specific process IDs", @@ -42,6 +48,12 @@ class PsList(interfaces.plugins.PluginInterface): optional=True, default=False, ), + requirements.BooleanRequirement( + name="dump", + description="Extract listed processes", + optional=True, + default=False, + ), ] @classmethod @@ -66,38 +78,12 @@ class PsList(interfaces.plugins.PluginInterface): else: return lambda _: False - def _get_task_fields( - self, task: interfaces.objects.ObjectInterface, decorate_comm: bool = False - ) -> Tuple[int, int, int, str]: - """Extract the fields needed for the final output - - Args: - task: A task object from where to get the fields. - decorate_comm: If True, it decorates the comm string of - - User threads: in curly brackets, - - Kernel threads: in square brackets - Defaults to False. - Returns: - A tuple with the fields to show in the plugin output. - """ - pid = task.tgid - tid = task.pid - ppid = task.parent.tgid if task.parent else 0 - name = utility.array_to_string(task.comm) - if decorate_comm: - if task.is_kernel_thread: - name = f"[{name}]" - elif task.is_user_thread: - name = f"{{{name}}}" - - task_fields = (format_hints.Hex(task.vol.offset), pid, tid, ppid, name) - return task_fields - def _generator( self, pid_filter: Callable[[Any], bool], include_threads: bool = False, decorate_comm: bool = False, + dump: bool = False, ): """Generates the tasks list. @@ -110,14 +96,63 @@ class PsList(interfaces.plugins.PluginInterface): - User threads: in curly brackets, - Kernel threads: in square brackets Defaults to False. + dump: If True, the main executable of the process is written to a file + Defaults to False. Yields: Each rows """ for task in self.list_tasks( self.context, self.config["kernel"], pid_filter, include_threads ): - row = self._get_task_fields(task, decorate_comm) - yield (0, row) + elf_table_name = intermed.IntermediateSymbolTable.create( + self.context, + self.config_path, + "linux", + "elf", + class_types=elf.class_types, + ) + file_output = "Disabled" + if dump: + proc_layer_name = task.add_process_layer() + if not proc_layer_name: + continue + + # Find the vma that belongs to the main ELF of the process + file_output = "Error outputting file" + + for v in task.mm.get_mmap_iter(): + if v.vm_start == task.mm.start_code: + file_handle = elfs.Elfs.elf_dump( + self.context, + proc_layer_name, + elf_table_name, + v, + task, + self.open, + ) + if file_handle: + file_output = str(file_handle.preferred_filename) + file_handle.close() + break + + pid = task.tgid + tid = task.pid + ppid = task.parent.tgid if task.parent else 0 + name = utility.array_to_string(task.comm) + if decorate_comm: + if task.is_kernel_thread: + name = f"[{name}]" + elif task.is_user_thread: + name = f"{{{name}}}" + + yield 0, ( + format_hints.Hex(task.vol.offset), + pid, + tid, + ppid, + name, + file_output, + ) @classmethod def list_tasks( @@ -155,6 +190,7 @@ class PsList(interfaces.plugins.PluginInterface): pids = self.config.get("pid") include_threads = self.config.get("threads") decorate_comm = self.config.get("decorate_comm") + dump = self.config.get("dump") filter_func = self.create_pid_filter(pids) columns = [ @@ -163,7 +199,8 @@ class PsList(interfaces.plugins.PluginInterface): ("TID", int), ("PPID", int), ("COMM", str), + ("File output", str), ] return renderers.TreeGrid( - columns, self._generator(filter_func, include_threads, decorate_comm) + columns, self._generator(filter_func, include_threads, decorate_comm, dump) ) From 3b1e4ce0e2635db5ac860cf30bb5c5524d55632e Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 1 Feb 2023 11:27:44 +0000 Subject: [PATCH 05/83] Update linux.vmayarascan to pull requirements from the generic yarascan plugin --- .../framework/plugins/linux/vmayarascan.py | 79 ++++++++++--------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py index c7a48cc14..f0d42f6e3 100644 --- a/volatility3/framework/plugins/linux/vmayarascan.py +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -1,4 +1,4 @@ -# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# This file is Copyright 2023 Volatility Foundation and licensed under the Volatility Software License 1.0 # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # @@ -15,68 +15,71 @@ class VmaYaraScan(interfaces.plugins.PluginInterface): """Scans all virtual memory areas for tasks using yara.""" _required_framework_version = (2, 4, 0) + _version = (1, 0, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: - return [ - requirements.ModuleRequirement( - name="kernel", - description="Linux kernel", - architectures=["Intel32", "Intel64"], - ), - requirements.BooleanRequirement( - name="wide", - description="Match wide (unicode) strings", - default=False, - optional=True, - ), - requirements.StringRequirement( - name="yara_rules", description="Yara rules (as a string)", optional=True - ), - requirements.URIRequirement( - name="yara_file", description="Yara rules (as a file)", optional=True - ), - # This additional requirement is to follow suit with upstream, who feel that compiled rules could potentially be used to execute malicious code - # As such, there's a separate option to run compiled files, as happened with yara-3.9 and later - requirements.URIRequirement( - name="yara_compiled_file", - description="Yara compiled rules (as a file)", - optional=True, - ), - requirements.IntRequirement( - name="max_size", - default=0x40000000, - description="Set the maximum size (default is 1GB)", - optional=True, - ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(2, 0, 0) - ), - requirements.VersionRequirement( - name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) - ), + # create a list of requirements for vmayarascan + vmayarascan_requirements = [ requirements.ListRequirement( name="pid", element_type=int, description="Process IDs to include (all other processes are excluded)", optional=True, ), + requirements.PluginRequirement( + name="pslist", plugin=pslist.PsList, version=(2, 0, 0) + ), + requirements.PluginRequirement( + name="yarascan", plugin=yarascan.YaraScan, version=(1, 1, 0) + ), + requirements.VersionRequirement( + name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) + ), + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=["Intel32", "Intel64"], + ), ] + # get base yarascan requirements + yarascan_requirements = yarascan.YaraScan.get_requirements() + + # remove TranslationLayerRequirement from the base yarascan requirements + # if this is not removed automagic will not find both the TranslationLayerRequirement + # for YaraScan and the ModuleRequirement for VmaYaraScan + yarascan_requirements = [ + requirement + for requirement in yarascan_requirements + if not isinstance(requirement, requirements.TranslationLayerRequirement) + ] + + # return the combined requirements + return yarascan_requirements + vmayarascan_requirements + def _generator(self): + # use yarascan to parse the yara options provided and create the rules rules = yarascan.YaraScan.process_yara_options(dict(self.config)) + # filter based on the pid option if provided filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) for task in pslist.PsList.list_tasks( context=self.context, vmlinux_module_name=self.config["kernel"], filter_func=filter_func, ): + + # attempt to create a process layer for each task and skip those + # that cannot (e.g. kernel threads) proc_layer_name = task.add_process_layer() if not proc_layer_name: continue + # get the proc_layer object from the context proc_layer = self.context.layers[proc_layer_name] + + # scan the process layer with the yarascanner for offset, rule_name, name, value in proc_layer.scan( context=self.context, scanner=yarascan.YaraScanner(rules=rules), From 3386a4fdc0e0c75d3ba9fb03b50a8d99b0cbc57d Mon Sep 17 00:00:00 2001 From: cstation Date: Tue, 14 Mar 2023 22:22:51 +0100 Subject: [PATCH 06/83] Remove broad try-except clause --- volatility3/framework/plugins/linux/elfs.py | 73 ++++++++++----------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/volatility3/framework/plugins/linux/elfs.py b/volatility3/framework/plugins/linux/elfs.py index f56438f4f..23bdf1c3a 100644 --- a/volatility3/framework/plugins/linux/elfs.py +++ b/volatility3/framework/plugins/linux/elfs.py @@ -75,55 +75,52 @@ class Elfs(plugins.PluginInterface): proc_layer = context.layers[layer_name] file_handle = None - try: - elf_object = context.object( - elf_table_name + constants.BANG + "Elf", - offset=vma.vm_start, - layer_name=layer_name, - ) + elf_object = context.object( + elf_table_name + constants.BANG + "Elf", + offset=vma.vm_start, + layer_name=layer_name, + ) - if not elf_object.is_valid(): - return None + if not elf_object.is_valid(): + return None - sections = {} - # TODO: Apply more effort to reconstruct ELF, e.g.: https://github.com/enbarberis/core2ELF64 ? - for phdr in elf_object.get_program_headers(): - if phdr.p_type != 1: # PT_LOAD = 1 - continue + sections = {} + # TODO: Apply more effort to reconstruct ELF, e.g.: https://github.com/enbarberis/core2ELF64 ? + for phdr in elf_object.get_program_headers(): + if phdr.p_type != 1: # PT_LOAD = 1 + continue - start = phdr.p_vaddr - size = phdr.p_memsz - end = start + size + start = phdr.p_vaddr + size = phdr.p_memsz + end = start + size - # Use complete memory pages for dumping - # If start isn't a multiple of 4096, stick to the highest multiple < start - # If end isn't a multiple of 4096, stick to the lowest multiple > end - if start % 4096: - start = start & ~0xFFF + # Use complete memory pages for dumping + # If start isn't a multiple of 4096, stick to the highest multiple < start + # If end isn't a multiple of 4096, stick to the lowest multiple > end + if start % 4096: + start = start & ~0xFFF - if end % 4096: - end = (end & ~0xFFF) + 4096 + if end % 4096: + end = (end & ~0xFFF) + 4096 - real_size = end - start + real_size = end - start - if real_size < 0 or real_size > 100000000: - continue + if real_size < 0 or real_size > 100000000: + continue - sections[start] = real_size + sections[start] = real_size - elf_data = b"" - for section_start in sorted(sections.keys()): - read_size = sections[section_start] + elf_data = b"" + for section_start in sorted(sections.keys()): + read_size = sections[section_start] - buf = proc_layer.read(vma.vm_start + section_start, read_size, pad=True) - elf_data = elf_data + buf + buf = proc_layer.read(vma.vm_start + section_start, read_size, pad=True) + elf_data = elf_data + buf - file_handle = open_method( - f"pid.{task.pid}.{utility.array_to_string(task.comm)}.{vma.vm_start:#x}.dmp" - ) - file_handle.write(elf_data) - except Exception as e: - vollog.debug(f"Unable to dump ELF with pid {task.pid}: {e}") + file_handle = open_method( + f"pid.{task.pid}.{utility.array_to_string(task.comm)}.{vma.vm_start:#x}.dmp" + ) + file_handle.write(elf_data) return file_handle From 1a7ec07c28ee4830f14a0dfaa003aaa5f82eeffb Mon Sep 17 00:00:00 2001 From: Eve Date: Tue, 13 Dec 2022 16:09:57 +0000 Subject: [PATCH 07/83] add linux.vmayarascan based on windows.vadtarascan --- .../framework/plugins/linux/vmayarascan.py | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 volatility3/framework/plugins/linux/vmayarascan.py diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py new file mode 100644 index 000000000..e9482089a --- /dev/null +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -0,0 +1,121 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Iterable, List, Tuple + +from volatility3.framework import interfaces, renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.renderers import format_hints +from volatility3.plugins import yarascan +from volatility3.plugins.linux import pslist + +class VmaYaraScan(interfaces.plugins.PluginInterface): + """Scans all virtual memory areas for tasks using yara.""" + + _required_framework_version = (2, 4, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.BooleanRequirement( + name="wide", + description="Match wide (unicode) strings", + default=False, + optional=True, + ), + requirements.StringRequirement( + name="yara_rules", description="Yara rules (as a string)", optional=True + ), + requirements.URIRequirement( + name="yara_file", description="Yara rules (as a file)", optional=True + ), + # This additional requirement is to follow suit with upstream, who feel that compiled rules could potentially be used to execute malicious code + # As such, there's a separate option to run compiled files, as happened with yara-3.9 and later + requirements.URIRequirement( + name="yara_compiled_file", + description="Yara compiled rules (as a file)", + optional=True, + ), + requirements.IntRequirement( + name="max_size", + default=0x40000000, + description="Set the maximum size (default is 1GB)", + optional=True, + ), + 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", + element_type=int, + description="Process IDs to include (all other processes are excluded)", + optional=True, + ), + ] + + def _generator(self): + kernel = self.context.modules[self.config["kernel"]] + + rules = yarascan.YaraScan.process_yara_options(dict(self.config)) + + filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) + for task in pslist.PsList.list_tasks( + context=self.context, + vmlinux_module_name=self.config["kernel"], + filter_func=filter_func, + ): + proc_layer_name = task.add_process_layer() + if not proc_layer_name: + continue + + proc_layer = self.context.layers[proc_layer_name] + for offset, rule_name, name, value in proc_layer.scan( + context=self.context, + scanner=yarascan.YaraScanner(rules=rules), + sections=self.get_vma_maps(task), + ): + yield 0, ( + format_hints.Hex(offset), + task.tgid, + rule_name, + name, + value, + ) + + @staticmethod + def get_vma_maps( + task: interfaces.objects.ObjectInterface, + ) -> Iterable[Tuple[int, int]]: + """Creates a map of start/end addresses for each virtual memory area in a task. + + Args: + task: The task object of which to read the vmas from + + Returns: + An iterable of tuples containing start and end addresses for each descriptor + """ + if task.mm: + for vma in task.mm.get_mmap_iter(): + vm_size = vma.vm_end - vma.vm_start + yield (vma.vm_start, vm_size) + + def run(self): + return renderers.TreeGrid( + [ + ("Offset", format_hints.Hex), + ("PID", int), + ("Rule", str), + ("Component", str), + ("Value", bytes), + ], + self._generator(), + ) From 2279a83e3b53f92ae21cbd8ec8acd0dfa389458b Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 4 Jan 2023 09:32:39 +0000 Subject: [PATCH 08/83] liniting for linux.vmayarascan --- volatility3/framework/plugins/linux/vmayarascan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py index e9482089a..3efbd2ae1 100644 --- a/volatility3/framework/plugins/linux/vmayarascan.py +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -10,6 +10,7 @@ from volatility3.framework.renderers import format_hints from volatility3.plugins import yarascan from volatility3.plugins.linux import pslist + class VmaYaraScan(interfaces.plugins.PluginInterface): """Scans all virtual memory areas for tasks using yara.""" From 57e8234e6c795391ad99f4a76216bb3ef3db808a Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 4 Jan 2023 09:36:20 +0000 Subject: [PATCH 09/83] remove unused variable in linux.vmayarascan --- volatility3/framework/plugins/linux/vmayarascan.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py index 3efbd2ae1..c7a48cc14 100644 --- a/volatility3/framework/plugins/linux/vmayarascan.py +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -64,8 +64,6 @@ class VmaYaraScan(interfaces.plugins.PluginInterface): ] def _generator(self): - kernel = self.context.modules[self.config["kernel"]] - rules = yarascan.YaraScan.process_yara_options(dict(self.config)) filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) From b54bbfb88f600e660d1ca6e8e7cec77138179c1f Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 1 Feb 2023 11:27:44 +0000 Subject: [PATCH 10/83] Update linux.vmayarascan to pull requirements from the generic yarascan plugin --- .../framework/plugins/linux/vmayarascan.py | 79 ++++++++++--------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py index c7a48cc14..f0d42f6e3 100644 --- a/volatility3/framework/plugins/linux/vmayarascan.py +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -1,4 +1,4 @@ -# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# This file is Copyright 2023 Volatility Foundation and licensed under the Volatility Software License 1.0 # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # @@ -15,68 +15,71 @@ class VmaYaraScan(interfaces.plugins.PluginInterface): """Scans all virtual memory areas for tasks using yara.""" _required_framework_version = (2, 4, 0) + _version = (1, 0, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: - return [ - requirements.ModuleRequirement( - name="kernel", - description="Linux kernel", - architectures=["Intel32", "Intel64"], - ), - requirements.BooleanRequirement( - name="wide", - description="Match wide (unicode) strings", - default=False, - optional=True, - ), - requirements.StringRequirement( - name="yara_rules", description="Yara rules (as a string)", optional=True - ), - requirements.URIRequirement( - name="yara_file", description="Yara rules (as a file)", optional=True - ), - # This additional requirement is to follow suit with upstream, who feel that compiled rules could potentially be used to execute malicious code - # As such, there's a separate option to run compiled files, as happened with yara-3.9 and later - requirements.URIRequirement( - name="yara_compiled_file", - description="Yara compiled rules (as a file)", - optional=True, - ), - requirements.IntRequirement( - name="max_size", - default=0x40000000, - description="Set the maximum size (default is 1GB)", - optional=True, - ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(2, 0, 0) - ), - requirements.VersionRequirement( - name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) - ), + # create a list of requirements for vmayarascan + vmayarascan_requirements = [ requirements.ListRequirement( name="pid", element_type=int, description="Process IDs to include (all other processes are excluded)", optional=True, ), + requirements.PluginRequirement( + name="pslist", plugin=pslist.PsList, version=(2, 0, 0) + ), + requirements.PluginRequirement( + name="yarascan", plugin=yarascan.YaraScan, version=(1, 1, 0) + ), + requirements.VersionRequirement( + name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) + ), + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=["Intel32", "Intel64"], + ), ] + # get base yarascan requirements + yarascan_requirements = yarascan.YaraScan.get_requirements() + + # remove TranslationLayerRequirement from the base yarascan requirements + # if this is not removed automagic will not find both the TranslationLayerRequirement + # for YaraScan and the ModuleRequirement for VmaYaraScan + yarascan_requirements = [ + requirement + for requirement in yarascan_requirements + if not isinstance(requirement, requirements.TranslationLayerRequirement) + ] + + # return the combined requirements + return yarascan_requirements + vmayarascan_requirements + def _generator(self): + # use yarascan to parse the yara options provided and create the rules rules = yarascan.YaraScan.process_yara_options(dict(self.config)) + # filter based on the pid option if provided filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) for task in pslist.PsList.list_tasks( context=self.context, vmlinux_module_name=self.config["kernel"], filter_func=filter_func, ): + + # attempt to create a process layer for each task and skip those + # that cannot (e.g. kernel threads) proc_layer_name = task.add_process_layer() if not proc_layer_name: continue + # get the proc_layer object from the context proc_layer = self.context.layers[proc_layer_name] + + # scan the process layer with the yarascanner for offset, rule_name, name, value in proc_layer.scan( context=self.context, scanner=yarascan.YaraScanner(rules=rules), From 09b0c8e406ec1aaf49a87a99fc86523fce36ff69 Mon Sep 17 00:00:00 2001 From: Eve Date: Fri, 12 May 2023 13:30:32 +0100 Subject: [PATCH 11/83] Linux: Update linux.vmayarascan and yarascan so that command line options are taken from the base yarascan plugin --- .../framework/plugins/linux/vmayarascan.py | 16 +++------------- volatility3/framework/plugins/yarascan.py | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py index f0d42f6e3..8e4174c12 100644 --- a/volatility3/framework/plugins/linux/vmayarascan.py +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -31,7 +31,7 @@ class VmaYaraScan(interfaces.plugins.PluginInterface): name="pslist", plugin=pslist.PsList, version=(2, 0, 0) ), requirements.PluginRequirement( - name="yarascan", plugin=yarascan.YaraScan, version=(1, 1, 0) + name="yarascan", plugin=yarascan.YaraScan, version=(1, 2, 0) ), requirements.VersionRequirement( name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) @@ -43,17 +43,8 @@ class VmaYaraScan(interfaces.plugins.PluginInterface): ), ] - # get base yarascan requirements - yarascan_requirements = yarascan.YaraScan.get_requirements() - - # remove TranslationLayerRequirement from the base yarascan requirements - # if this is not removed automagic will not find both the TranslationLayerRequirement - # for YaraScan and the ModuleRequirement for VmaYaraScan - yarascan_requirements = [ - requirement - for requirement in yarascan_requirements - if not isinstance(requirement, requirements.TranslationLayerRequirement) - ] + # get base yarascan requirements for command line options + yarascan_requirements = yarascan.YaraScan.get_yarascan_option_requirements() # return the combined requirements return yarascan_requirements + vmayarascan_requirements @@ -69,7 +60,6 @@ class VmaYaraScan(interfaces.plugins.PluginInterface): vmlinux_module_name=self.config["kernel"], filter_func=filter_func, ): - # attempt to create a process layer for each task and skip those # that cannot (e.g. kernel threads) proc_layer_name = task.add_process_layer() diff --git a/volatility3/framework/plugins/yarascan.py b/volatility3/framework/plugins/yarascan.py index 1c8467689..11c708607 100644 --- a/volatility3/framework/plugins/yarascan.py +++ b/volatility3/framework/plugins/yarascan.py @@ -61,19 +61,31 @@ class YaraScan(plugins.PluginInterface): """Scans kernel memory using yara rules (string or file).""" _required_framework_version = (2, 0, 0) - _version = (1, 1, 0) + _version = (1, 2, 0) # TODO: When the major version is bumped, take the opportunity to rename the yara_rules config to yara_string # or something that makes more sense @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: - return [ + """Returns the requirements needed to run yarascan directly, combining the TranslationLayerRequirement + and the requirements from get_yarascan_option_requirements.""" + return cls.get_yarascan_option_requirements() + [ requirements.TranslationLayerRequirement( name="primary", description="Memory layer for the kernel", architectures=["Intel32", "Intel64"], - ), + ) + ] + + @classmethod + def get_yarascan_option_requirements( + cls, + ) -> List[interfaces.configuration.RequirementInterface]: + """Returns the requirements needed for the command lines options used by yarascan. This can + then also be used by other plugins that are using yarascan. This does not include a + TranslationLayerRequirement or a ModuleRequirement.""" + return [ requirements.BooleanRequirement( name="insensitive", description="Makes the search case insensitive", From cd03c52fd30d7caf57aaa38a7b32197e4d158ca2 Mon Sep 17 00:00:00 2001 From: Eve Date: Fri, 12 May 2023 13:37:15 +0100 Subject: [PATCH 12/83] Linux: Update linux.vmayarascan to use task.mm.get_vma_iter() which means it will work on linux kernels 6.1 and above --- volatility3/framework/plugins/linux/vmayarascan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py index 8e4174c12..eda0d7dca 100644 --- a/volatility3/framework/plugins/linux/vmayarascan.py +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -96,7 +96,7 @@ class VmaYaraScan(interfaces.plugins.PluginInterface): An iterable of tuples containing start and end addresses for each descriptor """ if task.mm: - for vma in task.mm.get_mmap_iter(): + for vma in task.mm.get_vma_iter(): vm_size = vma.vm_end - vma.vm_start yield (vma.vm_start, vm_size) From 8373b5ed5ac8fe73a67c2d0c8b4f69367d60e9d0 Mon Sep 17 00:00:00 2001 From: cstation Date: Sat, 22 Jul 2023 18:52:39 +0200 Subject: [PATCH 13/83] Push ELF export limit to a constant --- volatility3/framework/constants/linux/__init__.py | 2 ++ volatility3/framework/plugins/linux/elfs.py | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/constants/linux/__init__.py b/volatility3/framework/constants/linux/__init__.py index 1b133eb42..ba7181db8 100644 --- a/volatility3/framework/constants/linux/__init__.py +++ b/volatility3/framework/constants/linux/__init__.py @@ -234,3 +234,5 @@ BLUETOOTH_PROTOCOLS = ( "HIDP", "AVDTP", ) + +ELF_MAX_EXTRACTION_SIZE = 1024 * 1024 * 1024 * 4 - 1 diff --git a/volatility3/framework/plugins/linux/elfs.py b/volatility3/framework/plugins/linux/elfs.py index 23bdf1c3a..c6334c977 100644 --- a/volatility3/framework/plugins/linux/elfs.py +++ b/volatility3/framework/plugins/linux/elfs.py @@ -105,8 +105,9 @@ class Elfs(plugins.PluginInterface): real_size = end - start - if real_size < 0 or real_size > 100000000: - continue + # Check if ELF has a legitimate size + if real_size < 0 or real_size > constants.linux.ELF_MAX_EXTRACTION_SIZE: + raise ValueError(f"The claimed size of the ELF is invalid: {real_size}") sections[start] = real_size From d53714fac84ce3c31fef6cd2b5b5dc6201ec5315 Mon Sep 17 00:00:00 2001 From: cstation Date: Sat, 22 Jul 2023 17:03:57 +0000 Subject: [PATCH 14/83] Fix linting --- volatility3/framework/constants/linux/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/constants/linux/__init__.py b/volatility3/framework/constants/linux/__init__.py index e5132f6ce..6e8883f19 100644 --- a/volatility3/framework/constants/linux/__init__.py +++ b/volatility3/framework/constants/linux/__init__.py @@ -280,4 +280,4 @@ CAPABILITIES = ( "checkpoint_restore", ) -ELF_MAX_EXTRACTION_SIZE = 1024 * 1024 * 1024 * 4 - 1 \ No newline at end of file +ELF_MAX_EXTRACTION_SIZE = 1024 * 1024 * 1024 * 4 - 1 From 7c82da4f5044a4bf35028c01924e659ccac5e828 Mon Sep 17 00:00:00 2001 From: xabrouck Date: Mon, 14 Aug 2023 11:53:14 +0200 Subject: [PATCH 15/83] check IoC of dirty bit in PTEs from executable VMAs. this can for example detect code injected using ptrace(). this can also detect injected code that was reset to the original code (malware uninstalled before memory dump happened). --- volatility3/framework/layers/intel.py | 9 +++++++++ volatility3/framework/plugins/linux/malfind.py | 2 +- .../framework/symbols/linux/extensions/__init__.py | 11 ++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/layers/intel.py b/volatility3/framework/layers/intel.py index 478eb168f..e2d89540d 100644 --- a/volatility3/framework/layers/intel.py +++ b/volatility3/framework/layers/intel.py @@ -110,6 +110,11 @@ class Intel(linear.LinearlyMappedLayer): def _page_is_valid(entry: int) -> bool: """Returns whether a particular page is valid based on its entry.""" return bool(entry & 1) + + @staticmethod + def _page_is_dirty(entry: int) -> bool: + """Returns whether a particular page is dirty based on its entry.""" + return bool(entry & (1<<6)) def canonicalize(self, addr: int) -> int: """Canonicalizes an address by performing an appropiate sign extension on the higher addresses""" @@ -259,6 +264,10 @@ class Intel(linear.LinearlyMappedLayer): except exceptions.InvalidAddressException: return False + def is_dirty(self, offset: int) -> bool: + """Returns whether the page at offset is marked dirty""" + return self._page_is_dirty(self._translate_entry(offset)[0]) + def mapping( self, offset: int, length: int, ignore_errors: bool = False ) -> Iterable[Tuple[int, int, int, int, str]]: diff --git a/volatility3/framework/plugins/linux/malfind.py b/volatility3/framework/plugins/linux/malfind.py index 1fd005de8..332d5ede1 100644 --- a/volatility3/framework/plugins/linux/malfind.py +++ b/volatility3/framework/plugins/linux/malfind.py @@ -47,7 +47,7 @@ class Malfind(interfaces.plugins.PluginInterface): proc_layer = self.context.layers[proc_layer_name] for vma in task.mm.get_vma_iter(): - if vma.is_suspicious() and vma.get_name(self.context, task) != "[vdso]": + if vma.is_suspicious(proc_layer) and vma.get_name(self.context, task) != "[vdso]": data = proc_layer.read(vma.vm_start, 64, pad=True) yield vma, data diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 527785a69..d2e6197ef 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -578,7 +578,7 @@ class vm_area_struct(objects.StructType): return fname # used by malfind - def is_suspicious(self): + def is_suspicious(self, proclayer): ret = False flags_str = self.get_protection() @@ -587,6 +587,15 @@ class vm_area_struct(objects.StructType): ret = True elif flags_str == "r-x" and self.vm_file.dereference().vol.offset == 0: ret = True + elif "x" in flags_str: + for i in range(self.vm_start,self.vm_end,constants.linux.PAGE_SHIFT): + try: + if proclayer.is_dirty(i): + vollog.warning(f"Found malicious (dirty+exec) page at {hex(i)} !") + ret = True + break + except (exceptions.PagedInvalidAddressException, exceptions.InvalidAddressException): + pass return ret From 804d68d94d507747d42f018baa3255dfe8635b4d Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Thu, 17 Aug 2023 13:38:23 +0100 Subject: [PATCH 16/83] Linux: fix bug where get_process_memory_sections fails with 6.1+ kernels --- volatility3/framework/symbols/linux/extensions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 527785a69..a40a87d5c 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -203,7 +203,7 @@ class task_struct(generic.GenericIntelProcess): ) -> Generator[Tuple[int, int], None, None]: """Returns a list of sections based on the memory manager's view of this task's virtual memory.""" - for vma in self.mm.get_mmap_iter(): + for vma in self.mm.get_vma_iter(): start = int(vma.vm_start) end = int(vma.vm_end) From 6f7f1284adbbcfacd759ec0d931859e0789cfc8a Mon Sep 17 00:00:00 2001 From: xabrouck Date: Fri, 18 Aug 2023 11:37:04 +0200 Subject: [PATCH 17/83] Fix bug with PAGE_SHIFT that wasn't shifted, also greatly increases performance Use black Better logging --- volatility3/framework/layers/intel.py | 6 +++--- .../framework/plugins/linux/malfind.py | 13 ++++++++++-- .../symbols/linux/extensions/__init__.py | 21 +++++++++++++------ 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/volatility3/framework/layers/intel.py b/volatility3/framework/layers/intel.py index e2d89540d..046203fa6 100644 --- a/volatility3/framework/layers/intel.py +++ b/volatility3/framework/layers/intel.py @@ -110,11 +110,11 @@ class Intel(linear.LinearlyMappedLayer): def _page_is_valid(entry: int) -> bool: """Returns whether a particular page is valid based on its entry.""" return bool(entry & 1) - + @staticmethod def _page_is_dirty(entry: int) -> bool: """Returns whether a particular page is dirty based on its entry.""" - return bool(entry & (1<<6)) + return bool(entry & (1 << 6)) def canonicalize(self, addr: int) -> int: """Canonicalizes an address by performing an appropiate sign extension on the higher addresses""" @@ -267,7 +267,7 @@ class Intel(linear.LinearlyMappedLayer): def is_dirty(self, offset: int) -> bool: """Returns whether the page at offset is marked dirty""" return self._page_is_dirty(self._translate_entry(offset)[0]) - + def mapping( self, offset: int, length: int, ignore_errors: bool = False ) -> Iterable[Tuple[int, int, int, int, str]]: diff --git a/volatility3/framework/plugins/linux/malfind.py b/volatility3/framework/plugins/linux/malfind.py index 332d5ede1..8a21afc03 100644 --- a/volatility3/framework/plugins/linux/malfind.py +++ b/volatility3/framework/plugins/linux/malfind.py @@ -3,7 +3,7 @@ # from typing import List - +import logging from volatility3.framework import constants, interfaces from volatility3.framework import renderers from volatility3.framework.configuration import requirements @@ -11,6 +11,8 @@ from volatility3.framework.objects import utility from volatility3.framework.renderers import format_hints from volatility3.plugins.linux import pslist +vollog = logging.getLogger(__name__) + class Malfind(interfaces.plugins.PluginInterface): """Lists process memory ranges that potentially contain injected code.""" @@ -47,7 +49,14 @@ class Malfind(interfaces.plugins.PluginInterface): proc_layer = self.context.layers[proc_layer_name] for vma in task.mm.get_vma_iter(): - if vma.is_suspicious(proc_layer) and vma.get_name(self.context, task) != "[vdso]": + vma_name = vma.get_name(self.context, task) + vollog.debug( + f"Injections : processing PID {task.pid} : VMA {vma_name} : {hex(vma.vm_start)}-{hex(vma.vm_end)}" + ) + if ( + vma.is_suspicious(proc_layer) + and vma.get_name(self.context, task) != "[vdso]" + ): data = proc_layer.read(vma.vm_start, 64, pad=True) yield vma, data diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index d2e6197ef..616e54e70 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -578,7 +578,7 @@ class vm_area_struct(objects.StructType): return fname # used by malfind - def is_suspicious(self, proclayer): + def is_suspicious(self, proclayer=None): ret = False flags_str = self.get_protection() @@ -587,15 +587,24 @@ class vm_area_struct(objects.StructType): ret = True elif flags_str == "r-x" and self.vm_file.dereference().vol.offset == 0: ret = True - elif "x" in flags_str: - for i in range(self.vm_start,self.vm_end,constants.linux.PAGE_SHIFT): + elif proclayer and "x" in flags_str: + for i in range(self.vm_start, self.vm_end, 1 << constants.linux.PAGE_SHIFT): try: if proclayer.is_dirty(i): - vollog.warning(f"Found malicious (dirty+exec) page at {hex(i)} !") + vollog.warning( + f"Found malicious (dirty+exec) page at {hex(i)} !" + ) + # We do not attempt to find other dirty+exec pages once we have found one ret = True break - except (exceptions.PagedInvalidAddressException, exceptions.InvalidAddressException): - pass + except ( + exceptions.PagedInvalidAddressException, + exceptions.InvalidAddressException, + ) as excp: + vollog.debug(f"Unable to translate address {hex(i)} : {excp}") + # Abort as it is likely that other addresses in the same range will also fail + ret = False + break return ret From 5c80a66d6dd9b3bc2400a4a8062ed30add10ce4b Mon Sep 17 00:00:00 2001 From: 616c696365 <616c696365@localhost.com> Date: Wed, 30 Aug 2023 20:12:12 +0100 Subject: [PATCH 18/83] Windows: Update pslist.py, add friendly option --- .../framework/plugins/windows/pslist.py | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/windows/pslist.py b/volatility3/framework/plugins/windows/pslist.py index 88697e71a..806bb678e 100644 --- a/volatility3/framework/plugins/windows/pslist.py +++ b/volatility3/framework/plugins/windows/pslist.py @@ -50,6 +50,12 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): default=False, optional=True, ), + requirements.BooleanRequirement( + name="friendly", + description="Display process name in dump filename", + default=False, + optional=True, + ), ] @classmethod @@ -60,6 +66,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): pe_table_name: str, proc: interfaces.objects.ObjectInterface, open_method: Type[interfaces.plugins.FileHandlerInterface], + friendly: bool = False, ) -> interfaces.plugins.FileHandlerInterface: """Extracts the complete data for a process as a FileHandlerInterface @@ -90,9 +97,20 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): offset=peb.ImageBaseAddress, layer_name=proc_layer_name, ) - file_handle = open_method( - f"pid.{proc.UniqueProcessId}.{peb.ImageBaseAddress:#x}.dmp" + + process_name = proc.ImageFileName.cast( + "string", + max_length=proc.ImageFileName.vol.count, + errors="replace", ) + if friendly: + file_handle = open_method( + f"{proc.UniqueProcessId}.{process_name}.{peb.ImageBaseAddress:#x}.dmp" + ) + else: + file_handle = open_method( + f"pid.{proc.UniqueProcessId}.{peb.ImageBaseAddress:#x}.dmp" + ) for offset, data in dos_header.reconstruct(): file_handle.seek(offset) file_handle.write(data) @@ -243,6 +261,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): pe_table_name, proc, self.open, + self.config["friendly"], ) file_output = "Error outputting file" if file_handle: From b4c6b661f01fc3dde54362a4f55be4d89e4cc6e5 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 3 Sep 2023 21:11:36 +0100 Subject: [PATCH 19/83] Core: Include only volatility3 in distributions packages Fixes #951 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 936a12af2..cfcda3d5c 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ setuptools.setup( include_package_data=True, exclude_package_data={"": ["development", "development.*"], "development": ["*"]}, packages=setuptools.find_namespace_packages( - exclude=["development", "development.*"] + include=["volatility3"] ), entry_points={ "console_scripts": [ From da203f7d6828fdeca1fd8f4d85361e65813804eb Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 3 Sep 2023 21:33:32 +0100 Subject: [PATCH 20/83] Documentation: Improve library documentation Fixes #993. --- doc/source/conf.py | 2 +- doc/source/using-as-a-library.rst | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 8b467ec1d..d601c1eee 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -27,7 +27,7 @@ def setup(app): source_dir = os.path.abspath(os.path.dirname(__file__)) sphinx.ext.apidoc.main( - argv=["-e", "-M", "-f", "-T", "-o", source_dir, volatility_directory] + ["-e", "-M", "-f", "-T", "-o", source_dir, volatility_directory] ) # Go through the volatility3.framework.plugins files and change them to volatility3.plugins diff --git a/doc/source/using-as-a-library.rst b/doc/source/using-as-a-library.rst index fb012f1ae..4acf35f98 100644 --- a/doc/source/using-as-a-library.rst +++ b/doc/source/using-as-a-library.rst @@ -54,6 +54,12 @@ also be included, which can be found in `volatility3.constants.PLUGINS_PATH`. volatility3.plugins.__path__ = + constants.PLUGINS_PATH failures = framework.import_files(volatility3.plugins, True) +.. note:: + + Volatility uses the `volatility3.plugins` namespace for all plugins (including those in `volatility3.framework.plugins`). + Please ensure you only use `volatility3.plugins` and only ever import plugins from this namespace. + This ensures the ability of users to override core plugins without needing write access to the framework directory. + Once the plugins have been imported, we can interrogate which plugins are available. The :py:func:`~volatility3.framework.list_plugins` call will return a dictionary of plugin names and the plugin classes. From 52207e09332f4322c33139aee63d9e104e0a05f6 Mon Sep 17 00:00:00 2001 From: Eve Date: Mon, 4 Sep 2023 17:44:24 +0100 Subject: [PATCH 21/83] Add extra debug information for layer stacker to show file size of the physical file --- volatility3/framework/automagic/stacker.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/volatility3/framework/automagic/stacker.py b/volatility3/framework/automagic/stacker.py index e611b5f06..d966d99fa 100644 --- a/volatility3/framework/automagic/stacker.py +++ b/volatility3/framework/automagic/stacker.py @@ -156,6 +156,9 @@ class LayerStacker(interfaces.automagic.AutomagicInterface): self._cached = context.config.get(path, None), context.config.branch( path ) + vollog.debug( + f"physical_layer maximum_address: {physical_layer.maximum_address}" + ) vollog.debug(f"Stacked layers: {stacked_layers}") @classmethod From 455487dbb65644ac8c633fdcda6351bbc3011019 Mon Sep 17 00:00:00 2001 From: xabrouck Date: Tue, 5 Sep 2023 16:20:46 +0200 Subject: [PATCH 22/83] Making cred optional as it didn't exist in old 2.6 kernels --- volatility3/framework/symbols/linux/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 5c42a436d..f96302684 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -28,7 +28,7 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable): self.set_type_class("fs_struct", extensions.fs_struct) self.set_type_class("files_struct", extensions.files_struct) self.set_type_class("kobject", extensions.kobject) - self.set_type_class("cred", extensions.cred) + self.optional_set_type_class("cred", extensions.cred) self.set_type_class("kernel_cap_struct", extensions.kernel_cap_struct) # Might not exist in the current symbols self.optional_set_type_class("module", extensions.module) From 05df365936a5965171632c7b0b0dbd1bee6c08a9 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Tue, 5 Sep 2023 18:23:48 +0100 Subject: [PATCH 23/83] Core: Fix missing packages in setup.py Fixes #1002. --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index cfcda3d5c..44ece8484 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ with open("README.md", "r", encoding="utf-8") as fh: def get_install_requires(): requirements = [] - with open("requirements-minimal.txt", "r", encoding = "utf-8") as fh: + with open("requirements-minimal.txt", "r", encoding="utf-8") as fh: for line in fh.readlines(): stripped_line = line.strip() if stripped_line == "" or stripped_line.startswith("#"): @@ -20,6 +20,7 @@ def get_install_requires(): requirements.append(stripped_line) return requirements + setuptools.setup( name="volatility3", description="Memory forensics framework", @@ -39,9 +40,8 @@ setuptools.setup( python_requires=">=3.7.0", include_package_data=True, exclude_package_data={"": ["development", "development.*"], "development": ["*"]}, - packages=setuptools.find_namespace_packages( - include=["volatility3"] - ), + packages=setuptools.find_namespace_packages(where="volatility3"), + package_dir={"": "volatility3"}, entry_points={ "console_scripts": [ "vol = volatility3.cli:main", From 627e2fbab92b30c389be91688fed01f4620c30a0 Mon Sep 17 00:00:00 2001 From: Shutdown <40902872+ShutdownRepo@users.noreply.github.com> Date: Tue, 5 Sep 2023 22:27:53 +0200 Subject: [PATCH 24/83] Adding install.yml workflow --- .github/workflows/install.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/install.yml diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml new file mode 100644 index 000000000..e7f9936b6 --- /dev/null +++ b/.github/workflows/install.yml @@ -0,0 +1,29 @@ +name: Test install Volatility3 +on: [push, pull_request] +jobs: + + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.7"] + steps: + - uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Setup python-pip + run: python -m pip install --upgrade pip + + - name: Install dependencies + run: | + pip install -r requirements.txt + + - name: Install volatility3 + run: pip install . + + - name: Run volatility3 + run: vol --help \ No newline at end of file From 2a67aa639a63b35b2203da07b64be4603d8d8ead Mon Sep 17 00:00:00 2001 From: Shutdown <40902872+ShutdownRepo@users.noreply.github.com> Date: Tue, 5 Sep 2023 22:29:25 +0200 Subject: [PATCH 25/83] Removing Python version --- .github/workflows/install.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index e7f9936b6..51768cc56 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -1,19 +1,14 @@ -name: Test install Volatility3 +name: Install Volatility3 test on: [push, pull_request] jobs: - build: + install: runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.7"] steps: - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - name: Setup python-pip run: python -m pip install --upgrade pip From 9982a44131f76987cbd55513a16d3582d27bb505 Mon Sep 17 00:00:00 2001 From: Shutdown <40902872+ShutdownRepo@users.noreply.github.com> Date: Tue, 5 Sep 2023 22:36:40 +0200 Subject: [PATCH 26/83] Adding matrix strategy for hosts and python version --- .github/workflows/install.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index 51768cc56..9ac14e6c6 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -2,13 +2,20 @@ name: Install Volatility3 test on: [push, pull_request] jobs: - install: - runs-on: ubuntu-latest + install_test: + runs-on: ${{ matrix.host }} + strategy: + matrix: + fail-fast: false + host: [ ubuntu-latest, macOS-latest, windows-latest ] + python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ] steps: - uses: actions/checkout@v3 - - name: Set up Python + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} - name: Setup python-pip run: python -m pip install --upgrade pip From ef341d54d6edb665b629799a76207f0f4a113f7b Mon Sep 17 00:00:00 2001 From: Shutdown <40902872+ShutdownRepo@users.noreply.github.com> Date: Tue, 5 Sep 2023 22:37:45 +0200 Subject: [PATCH 27/83] Fixing fail-fast strategy --- .github/workflows/install.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index 9ac14e6c6..0a5fec25e 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -5,8 +5,8 @@ jobs: install_test: runs-on: ${{ matrix.host }} strategy: + fail-fast: false matrix: - fail-fast: false host: [ ubuntu-latest, macOS-latest, windows-latest ] python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ] steps: From 9d2fd4051731ac696718cf40bb6e6543c6a1f40f Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Tue, 5 Sep 2023 23:07:57 +0100 Subject: [PATCH 28/83] Revert "Core: Include only volatility3 in distributions packages" This reverts commit b4c6b661f01fc3dde54362a4f55be4d89e4cc6e5. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cfcda3d5c..936a12af2 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ setuptools.setup( include_package_data=True, exclude_package_data={"": ["development", "development.*"], "development": ["*"]}, packages=setuptools.find_namespace_packages( - include=["volatility3"] + exclude=["development", "development.*"] ), entry_points={ "console_scripts": [ From 803c56e3c4c6495b2725b77cc7d045e39c98a9bd Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Tue, 5 Sep 2023 23:51:17 +0100 Subject: [PATCH 29/83] Core: include the volatility3 package and all volatility3 subpackages --- setup.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 44ece8484..c2c55067d 100644 --- a/setup.py +++ b/setup.py @@ -37,11 +37,12 @@ setuptools.setup( "Documentation": "https://volatility3.readthedocs.io/", "Source Code": "https://github.com/volatilityfoundation/volatility3", }, + packages=setuptools.find_namespace_packages( + include=["volatility3", "volatility3.*"] + ), + package_dir={"volatility3": "volatility3"}, python_requires=">=3.7.0", include_package_data=True, - exclude_package_data={"": ["development", "development.*"], "development": ["*"]}, - packages=setuptools.find_namespace_packages(where="volatility3"), - package_dir={"": "volatility3"}, entry_points={ "console_scripts": [ "vol = volatility3.cli:main", From 47ddf5d0e5142d6deeb071225ebb2d8bc366d381 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Wed, 6 Sep 2023 20:34:13 +0100 Subject: [PATCH 30/83] Core: Bump the version after 2.5.0 release branch --- volatility3/framework/constants/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/constants/__init__.py b/volatility3/framework/constants/__init__.py index de1674885..c3ebaca27 100644 --- a/volatility3/framework/constants/__init__.py +++ b/volatility3/framework/constants/__init__.py @@ -45,7 +45,7 @@ BANG = "!" # We use the SemVer 2.0.0 versioning scheme VERSION_MAJOR = 2 # Number of releases of the library with a breaking change VERSION_MINOR = 5 # Number of changes that only add to the interface -VERSION_PATCH = 0 # Number of changes that do not change the interface +VERSION_PATCH = 1 # Number of changes that do not change the interface VERSION_SUFFIX = "" # TODO: At version 2.0.0, remove the symbol_shift feature From 370b6774ea3de8fb98bf625ed30b4347d68e2b54 Mon Sep 17 00:00:00 2001 From: Shutdown <40902872+ShutdownRepo@users.noreply.github.com> Date: Sun, 17 Sep 2023 23:21:56 +0200 Subject: [PATCH 31/83] Remove macOS from matrix hosts --- .github/workflows/install.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index 0a5fec25e..cc2a7fd3e 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - host: [ ubuntu-latest, macOS-latest, windows-latest ] + host: [ ubuntu-latest, windows-latest ] python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ] steps: - uses: actions/checkout@v3 From a46c9d9d8ecf0a36352c672c2193227c59cd33c1 Mon Sep 17 00:00:00 2001 From: Eve Date: Fri, 29 Sep 2023 10:16:20 +0100 Subject: [PATCH 32/83] Linux: add padded read when getting magic for elf extension to help with smear and missing pages --- .../framework/symbols/linux/extensions/elf.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/elf.py b/volatility3/framework/symbols/linux/extensions/elf.py index 416a7e4d2..a05885a7b 100644 --- a/volatility3/framework/symbols/linux/extensions/elf.py +++ b/volatility3/framework/symbols/linux/extensions/elf.py @@ -33,14 +33,18 @@ class elf(objects.StructType): layer_name = self.vol.layer_name symbol_table_name = self.get_symbol_table_name() # We read the MAGIC: (0x0 to 0x4) 0x7f 0x45 0x4c 0x46 - magic = self._context.object( - symbol_table_name + constants.BANG + "unsigned long", - layer_name=layer_name, - offset=object_info.offset, - ) + magic = self._context.layers[layer_name].read(object_info.offset, 4, True) # Check validity - if magic != 0x464C457F: + if ( + magic[0] == 0x7F + and magic[1] == 0x45 # E + and magic[2] == 0x4C # L + and magic[3] == 0x46 # F + ): + self._valid_magic = True + else: + self._valid_magic = False return None # We need to read the EI_CLASS (0x4 offset) @@ -72,7 +76,10 @@ class elf(objects.StructType): """ Determine whether it is a valid object """ - return self._type_prefix is not None and self._hdr is not None + if self._valid_magic: + return self._type_prefix is not None and self._hdr is not None + else: + return False def __getattr__(self, name): # Just redirect to the corresponding header From 41a02fbf5b5bd860f35d53c7ed34a97bfa68f3cd Mon Sep 17 00:00:00 2001 From: Eve Date: Tue, 3 Oct 2023 07:13:28 +0100 Subject: [PATCH 33/83] Linux: use try/except in linux elf extension to catch paged and invalid addresses rather than crashing --- .../framework/symbols/linux/extensions/elf.py | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/elf.py b/volatility3/framework/symbols/linux/extensions/elf.py index a05885a7b..8b42b3075 100644 --- a/volatility3/framework/symbols/linux/extensions/elf.py +++ b/volatility3/framework/symbols/linux/extensions/elf.py @@ -3,9 +3,12 @@ # from typing import Dict, Tuple +import logging from volatility3.framework import constants -from volatility3.framework import objects, interfaces +from volatility3.framework import objects, interfaces, exceptions + +vollog = logging.getLogger(__name__) class elf(objects.StructType): @@ -33,20 +36,29 @@ class elf(objects.StructType): layer_name = self.vol.layer_name symbol_table_name = self.get_symbol_table_name() # We read the MAGIC: (0x0 to 0x4) 0x7f 0x45 0x4c 0x46 - magic = self._context.layers[layer_name].read(object_info.offset, 4, True) - - # Check validity - if ( - magic[0] == 0x7F - and magic[1] == 0x45 # E - and magic[2] == 0x4C # L - and magic[3] == 0x46 # F - ): - self._valid_magic = True - else: + try: + magic = self._context.object( + symbol_table_name + constants.BANG + "unsigned long", + layer_name=layer_name, + offset=object_info.offset, + ) + except ( + exceptions.PagedInvalidAddressException, + exceptions.InvalidAddressException, + ) as excp: + vollog.debug( + f"Unable to check magic bytes for ELF file at offset {hex(object_info.offset)} in layer {layer_name}: {excp}" + ) self._valid_magic = False return None + # Check validity + if magic != 0x464C457F: # e.g. ELF + self._valid_magic = False + return None + else: + self._valid_magic = True + # We need to read the EI_CLASS (0x4 offset) ei_class = self._context.object( symbol_table_name + constants.BANG + "unsigned char", From 93b297283292ff7080f539cc483e48c28271dfe8 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 8 Oct 2023 22:19:17 +0100 Subject: [PATCH 34/83] Renderers: Allow nodes to be turned into dictionaries --- volatility3/framework/constants/__init__.py | 2 +- volatility3/framework/renderers/__init__.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/constants/__init__.py b/volatility3/framework/constants/__init__.py index c3ebaca27..09dded076 100644 --- a/volatility3/framework/constants/__init__.py +++ b/volatility3/framework/constants/__init__.py @@ -45,7 +45,7 @@ BANG = "!" # We use the SemVer 2.0.0 versioning scheme VERSION_MAJOR = 2 # Number of releases of the library with a breaking change VERSION_MINOR = 5 # Number of changes that only add to the interface -VERSION_PATCH = 1 # Number of changes that do not change the interface +VERSION_PATCH = 2 # Number of changes that do not change the interface VERSION_SUFFIX = "" # TODO: At version 2.0.0, remove the symbol_shift feature diff --git a/volatility3/framework/renderers/__init__.py b/volatility3/framework/renderers/__init__.py index 534686022..43bb59a21 100644 --- a/volatility3/framework/renderers/__init__.py +++ b/volatility3/framework/renderers/__init__.py @@ -10,7 +10,7 @@ import collections import collections.abc import datetime import logging -from typing import Any, Callable, Iterable, List, Optional, Tuple, TypeVar, Union +from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, TypeVar, Union from volatility3.framework import interfaces from volatility3.framework.interfaces import renderers @@ -96,6 +96,10 @@ class TreeNode(interfaces.renderers.TreeNode): # if isinstance(val, datetime.datetime): # tznaive = val.tzinfo is None or val.tzinfo.utcoffset(val) is None + def asdict(self) -> Dict[str, Any]: + """Returns the contents of the node as a dictionary""" + return self._values._asdict() + @property def values(self) -> List[interfaces.renderers.BaseTypes]: """Returns the list of values from the particular node, based on column From 310b6508db305b46288e5d0f530ca169ce020726 Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 11 Oct 2023 09:09:15 +0100 Subject: [PATCH 35/83] vmware: Add warning when no metadata file is found for a vmem file. --- volatility3/framework/layers/vmware.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/volatility3/framework/layers/vmware.py b/volatility3/framework/layers/vmware.py index 0bc1a350b..5467c70c0 100644 --- a/volatility3/framework/layers/vmware.py +++ b/volatility3/framework/layers/vmware.py @@ -232,6 +232,9 @@ class VmwareStacker(interfaces.automagic.StackerLayerInterface): ) if not vmss_success and not vmsn_success: + vollog.warning( + f"No metadata file alongside VMEM file! A VMSS or VMSN file is required to correctly process a VMEM file. These should be placed in the same directory with the same file name, e.g. sample.vmem and sample.vmsn.", + ) return None new_layer_name = context.layers.free_layer_name("VmwareLayer") context.config[ From 9cc73b6a0388321ee6de9f6c82a124e263282d78 Mon Sep 17 00:00:00 2001 From: Dave Lassalle Date: Thu, 12 Oct 2023 09:57:24 -0500 Subject: [PATCH 36/83] issue #1017 - check for valid root node type --- volatility3/framework/layers/registry.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/layers/registry.py b/volatility3/framework/layers/registry.py index cc8ce1f4c..a2d3b0fd4 100644 --- a/volatility3/framework/layers/registry.py +++ b/volatility3/framework/layers/registry.py @@ -171,7 +171,12 @@ class RegistryHive(linear.LinearlyMappedLayer): node (default) or a list of nodes from root to the current node (if return_list is true). """ - node_key = [self.get_node(self.root_cell_offset)] + root_node = self.get_node(self.root_cell_offset) + if not root_node.vol.type_name.endswith(constants.BANG + "_CM_KEY_NODE"): + raise RegistryFormatException( + self.name, "Encountered {} instead of _CM_KEY_NODE".format(root_node.vol.type_name) + ) + node_key = [root_node] if key.endswith("\\"): key = key[:-1] key_array = key.split("\\") From 95c468c4f8aeaa00e658901ee1bd23054756d8a2 Mon Sep 17 00:00:00 2001 From: Dave Lassalle Date: Thu, 12 Oct 2023 10:16:56 -0500 Subject: [PATCH 37/83] issue #1019 - for subkeys, return the modified time of the subkey itself, not its parent key --- volatility3/framework/plugins/windows/registry/printkey.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/volatility3/framework/plugins/windows/registry/printkey.py b/volatility3/framework/plugins/windows/registry/printkey.py index 537bfc943..70a288b63 100644 --- a/volatility3/framework/plugins/windows/registry/printkey.py +++ b/volatility3/framework/plugins/windows/registry/printkey.py @@ -153,6 +153,9 @@ class PrintKey(interfaces.plugins.PluginInterface): vollog.debug(excp) key_node_name = renderers.UnreadableValue() + # if the item is a subkey, use the LastWriteTime of that subkey + last_write_time = conversion.wintime_to_datetime(node.LastWriteTime.QuadPart) + yield ( depth, ( From e45380f4f579e3c54f1475312efc2aff47341b2a Mon Sep 17 00:00:00 2001 From: Eve Date: Fri, 13 Oct 2023 09:29:46 +0100 Subject: [PATCH 38/83] Update vmware layer metadata file warning to be less doom and gloom. --- volatility3/framework/layers/vmware.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/layers/vmware.py b/volatility3/framework/layers/vmware.py index 5467c70c0..622ff0250 100644 --- a/volatility3/framework/layers/vmware.py +++ b/volatility3/framework/layers/vmware.py @@ -4,6 +4,7 @@ import contextlib import logging import struct +import os from typing import Any, Dict, List, Optional from volatility3.framework import constants, exceptions, interfaces @@ -232,8 +233,10 @@ class VmwareStacker(interfaces.automagic.StackerLayerInterface): ) if not vmss_success and not vmsn_success: + vmem_file_basename = os.path.basename(location) + example_vmss_file_basename = os.path.basename(vmss) vollog.warning( - f"No metadata file alongside VMEM file! A VMSS or VMSN file is required to correctly process a VMEM file. These should be placed in the same directory with the same file name, e.g. sample.vmem and sample.vmsn.", + f"No metadata file found alongside VMEM file. A VMSS or VMSN file may be required to correctly process a VMEM file. These should be placed in the same directory with the same file name, e.g. {vmem_file_basename} and {example_vmss_file_basename}.", ) return None new_layer_name = context.layers.free_layer_name("VmwareLayer") From 63ace6099508664b51b671f95a84bd891736e0d3 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Tue, 17 Oct 2023 15:54:10 +0100 Subject: [PATCH 39/83] Core: Add (optional) sanitization to the FileHandler class --- volatility3/framework/interfaces/plugins.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/interfaces/plugins.py b/volatility3/framework/interfaces/plugins.py index 0de109c5e..29395aadf 100644 --- a/volatility3/framework/interfaces/plugins.py +++ b/volatility3/framework/interfaces/plugins.py @@ -43,7 +43,7 @@ class FileHandlerInterface(io.RawIOBase): return self._preferred_filename @preferred_filename.setter - def preferred_filename(self, filename): + def preferred_filename(self, filename: str): """Sets the preferred filename""" if self.closed: raise IOError("FileHandler name cannot be changed once closed") @@ -57,6 +57,18 @@ class FileHandlerInterface(io.RawIOBase): def close(self): """Method that commits the file and fixes the final filename for use""" + @staticmethod + def sanitize_filename(filename: str) -> str: + """Sanititizes the filename to ensure only a specific whitelist of characters is allowed through""" + allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.- ()[]\{\}!$%^:#~?<>,|" + result = "" + for char in filename: + if char in allowed: + result += char + else: + result += "?" + return result + def __enter__(self): return self From 14fa5ad771a668b8f4f717674412803cf7229c50 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Tue, 17 Oct 2023 16:27:22 +0100 Subject: [PATCH 40/83] Documentation: Add in a CITATION.cff file Initial addition of a CITATION file, no version information was included because keeping it up to date with the version in the repo automatically would be tricky and not doing so would lead it to becoming out of sync. Fixes #1013. --- CITATION.cff | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 000000000..c36c3b7d5 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,37 @@ +# This CITATION.cff file was generated with cffinit. +# Visit https://bit.ly/cffinit to generate yours today! + +cff-version: 1.2.0 +title: Volatility 3 +message: >- + If you reference this software, please feel free to cite + it using the information below. +type: software +authors: + - name: Volatility Foundation + country: US + website: 'https://www.volatilityfoundation.org/' +identifiers: + - type: url + value: 'https://github.com/volatilityfoundation/volatility3' + description: Volatility 3 source code respository +repository-code: 'https://github.com/volatilityfoundation/volatility3' +url: 'https://github.com/volatilityfoundation/volatility3' +abstract: >- + Volatility is the world's most widely used framework for + extracting digital artifacts from volatile memory (RAM) + samples. The extraction techniques are performed + completely independent of the system being investigated + but offer visibility into the runtime state of the system. + The framework is intended to introduce people to the + techniques and complexities associated with extracting + digital artifacts from volatile memory samples and provide + a platform for further work into this exciting area of + research. +keywords: + - malware + - forensics + - memory + - python + - ram + - volatility From 5d43071f572a4c2aa5cbe573cb5b400e8d27607f Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Tue, 17 Oct 2023 15:54:10 +0100 Subject: [PATCH 41/83] Core: Add (optional) sanitization to the FileHandler class --- volatility3/framework/interfaces/plugins.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/interfaces/plugins.py b/volatility3/framework/interfaces/plugins.py index 0de109c5e..29395aadf 100644 --- a/volatility3/framework/interfaces/plugins.py +++ b/volatility3/framework/interfaces/plugins.py @@ -43,7 +43,7 @@ class FileHandlerInterface(io.RawIOBase): return self._preferred_filename @preferred_filename.setter - def preferred_filename(self, filename): + def preferred_filename(self, filename: str): """Sets the preferred filename""" if self.closed: raise IOError("FileHandler name cannot be changed once closed") @@ -57,6 +57,18 @@ class FileHandlerInterface(io.RawIOBase): def close(self): """Method that commits the file and fixes the final filename for use""" + @staticmethod + def sanitize_filename(filename: str) -> str: + """Sanititizes the filename to ensure only a specific whitelist of characters is allowed through""" + allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.- ()[]\{\}!$%^:#~?<>,|" + result = "" + for char in filename: + if char in allowed: + result += char + else: + result += "?" + return result + def __enter__(self): return self From 7323bd3a591a4d989fde5837d51a0a9c2d9061f3 Mon Sep 17 00:00:00 2001 From: 616c696365 <616c696365@localhost.com> Date: Wed, 18 Oct 2023 19:14:02 +0100 Subject: [PATCH 42/83] windows.pslist process name added to dumped file by default --- .../framework/plugins/windows/pslist.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/volatility3/framework/plugins/windows/pslist.py b/volatility3/framework/plugins/windows/pslist.py index 806bb678e..e7a0d5dd4 100644 --- a/volatility3/framework/plugins/windows/pslist.py +++ b/volatility3/framework/plugins/windows/pslist.py @@ -50,12 +50,6 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): default=False, optional=True, ), - requirements.BooleanRequirement( - name="friendly", - description="Display process name in dump filename", - default=False, - optional=True, - ), ] @classmethod @@ -66,7 +60,6 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): pe_table_name: str, proc: interfaces.objects.ObjectInterface, open_method: Type[interfaces.plugins.FileHandlerInterface], - friendly: bool = False, ) -> interfaces.plugins.FileHandlerInterface: """Extracts the complete data for a process as a FileHandlerInterface @@ -103,14 +96,13 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): max_length=proc.ImageFileName.vol.count, errors="replace", ) - if friendly: - file_handle = open_method( + + file_handle = open_method( + open_method.sanitize_filename( f"{proc.UniqueProcessId}.{process_name}.{peb.ImageBaseAddress:#x}.dmp" ) - else: - file_handle = open_method( - f"pid.{proc.UniqueProcessId}.{peb.ImageBaseAddress:#x}.dmp" - ) + ) + for offset, data in dos_header.reconstruct(): file_handle.seek(offset) file_handle.write(data) @@ -261,7 +253,6 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): pe_table_name, proc, self.open, - self.config["friendly"], ) file_output = "Error outputting file" if file_handle: From fcaba2d95a79a7bec70cde265199ab892b6c0948 Mon Sep 17 00:00:00 2001 From: Dave Lassalle Date: Wed, 18 Oct 2023 14:08:50 -0500 Subject: [PATCH 43/83] issue #1017 - black updates --- volatility3/framework/layers/registry.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/layers/registry.py b/volatility3/framework/layers/registry.py index a2d3b0fd4..9841d2bb0 100644 --- a/volatility3/framework/layers/registry.py +++ b/volatility3/framework/layers/registry.py @@ -174,7 +174,10 @@ class RegistryHive(linear.LinearlyMappedLayer): root_node = self.get_node(self.root_cell_offset) if not root_node.vol.type_name.endswith(constants.BANG + "_CM_KEY_NODE"): raise RegistryFormatException( - self.name, "Encountered {} instead of _CM_KEY_NODE".format(root_node.vol.type_name) + self.name, + "Encountered {} instead of _CM_KEY_NODE".format( + root_node.vol.type_name + ), ) node_key = [root_node] if key.endswith("\\"): From a497216bebd8e5a9d3ebe4fd039b673d90346ce7 Mon Sep 17 00:00:00 2001 From: Dave Lassalle Date: Wed, 18 Oct 2023 14:09:42 -0500 Subject: [PATCH 44/83] issue #1019 - black updates --- volatility3/framework/plugins/windows/registry/printkey.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/windows/registry/printkey.py b/volatility3/framework/plugins/windows/registry/printkey.py index 70a288b63..f66e55f4b 100644 --- a/volatility3/framework/plugins/windows/registry/printkey.py +++ b/volatility3/framework/plugins/windows/registry/printkey.py @@ -154,7 +154,9 @@ class PrintKey(interfaces.plugins.PluginInterface): key_node_name = renderers.UnreadableValue() # if the item is a subkey, use the LastWriteTime of that subkey - last_write_time = conversion.wintime_to_datetime(node.LastWriteTime.QuadPart) + last_write_time = conversion.wintime_to_datetime( + node.LastWriteTime.QuadPart + ) yield ( depth, From 2d3b3158dc953a89898cd3e403415e1725f4cbc2 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sat, 21 Oct 2023 23:34:17 +0200 Subject: [PATCH 45/83] correct sql query and strip identifier --- volatility3/framework/automagic/symbol_cache.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/volatility3/framework/automagic/symbol_cache.py b/volatility3/framework/automagic/symbol_cache.py index 29f2cfd08..2c605f915 100644 --- a/volatility3/framework/automagic/symbol_cache.py +++ b/volatility3/framework/automagic/symbol_cache.py @@ -429,8 +429,7 @@ class SqliteCache(CacheManagerInterface): progress_callback(0, "Reading remote ISF list") cursor = self._database.cursor() cursor.execute( - f"SELECT cached FROM cache WHERE local = 0 and cached < datetime('now', {self.cache_period})" - ) + f"SELECT cached FROM cache WHERE local = 0 and cached < datetime('now', '{self.cache_period}')" ) remote_identifiers = RemoteIdentifierFormat(constants.REMOTE_ISF_URL) progress_callback(50, "Reading remote ISF list") for operating_system in constants.OS_CATEGORIES: @@ -438,9 +437,11 @@ class SqliteCache(CacheManagerInterface): {}, operating_system=operating_system ) for identifier, location in identifiers: + identifier = identifier.rstrip() + identifier = identifier[:-1] if identifier.endswith(b"\x00") else identifier # Linux banners dumped by dwarf2json end with "\x00\n". If not stripped, the banner cannot match. cursor.execute( - "INSERT OR REPLACE INTO cache(identifier, location, operating_system, local, cached) VALUES (?, ?, ?, ?, datetime('now'))", - (location, identifier, operating_system, False), + "INSERT OR REPLACE INTO cache(identifier, location, operating_system, local, cached) VALUES (?, ?, ?, ?, datetime('now'))", + (identifier, location, operating_system, False), ) progress_callback(100, "Reading remote ISF list") self._database.commit() From c20baf9d1fd346fe9f809227d063d4cf56717879 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Mon, 23 Oct 2023 17:49:03 +0200 Subject: [PATCH 46/83] black formatting --- volatility3/framework/automagic/symbol_cache.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/automagic/symbol_cache.py b/volatility3/framework/automagic/symbol_cache.py index 2c605f915..22f1c94f3 100644 --- a/volatility3/framework/automagic/symbol_cache.py +++ b/volatility3/framework/automagic/symbol_cache.py @@ -429,7 +429,8 @@ class SqliteCache(CacheManagerInterface): progress_callback(0, "Reading remote ISF list") cursor = self._database.cursor() cursor.execute( - f"SELECT cached FROM cache WHERE local = 0 and cached < datetime('now', '{self.cache_period}')" ) + f"SELECT cached FROM cache WHERE local = 0 and cached < datetime('now', '{self.cache_period}')" + ) remote_identifiers = RemoteIdentifierFormat(constants.REMOTE_ISF_URL) progress_callback(50, "Reading remote ISF list") for operating_system in constants.OS_CATEGORIES: @@ -438,9 +439,11 @@ class SqliteCache(CacheManagerInterface): ) for identifier, location in identifiers: identifier = identifier.rstrip() - identifier = identifier[:-1] if identifier.endswith(b"\x00") else identifier # Linux banners dumped by dwarf2json end with "\x00\n". If not stripped, the banner cannot match. + identifier = ( + identifier[:-1] if identifier.endswith(b"\x00") else identifier + ) # Linux banners dumped by dwarf2json end with "\x00\n". If not stripped, the banner cannot match. cursor.execute( - "INSERT OR REPLACE INTO cache(identifier, location, operating_system, local, cached) VALUES (?, ?, ?, ?, datetime('now'))", + "INSERT OR REPLACE INTO cache(identifier, location, operating_system, local, cached) VALUES (?, ?, ?, ?, datetime('now'))", (identifier, location, operating_system, False), ) progress_callback(100, "Reading remote ISF list") From 3bb1285dbfb3200567f50662ceed663283c16d02 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 29 Oct 2023 13:53:55 +0000 Subject: [PATCH 47/83] Layers: Make removal of a layer more efficient, as noted in #809 --- volatility3/framework/interfaces/layers.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/volatility3/framework/interfaces/layers.py b/volatility3/framework/interfaces/layers.py index 68592f8cb..e2a68780a 100644 --- a/volatility3/framework/interfaces/layers.py +++ b/volatility3/framework/interfaces/layers.py @@ -678,16 +678,12 @@ class LayerContainer(collections.abc.Mapping): name: The name of the layer to delete """ for layer in self._layers: - depend_list = [ - superlayer - for superlayer in self._layers - if name in self._layers[layer].dependencies - ] - if depend_list: + if name in self._layers[layer].dependencies: raise exceptions.LayerException( self._layers[layer].name, - f"Layer {self._layers[layer].name} is depended upon: {', '.join(depend_list)}", + f"Layer {self._layers[layer].name} is depended upon by {layer}", ) + # Otherwise, wipe out the layer self._layers[name].destroy() del self._layers[name] From b9c4a1d8a76bd40b7a0ee40ea91c0e32da99000e Mon Sep 17 00:00:00 2001 From: Leron Gray Date: Tue, 31 Oct 2023 13:52:26 -0500 Subject: [PATCH 48/83] fix guid and pdb_name for #1026 --- volatility3/framework/symbols/windows/pdbutil.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/symbols/windows/pdbutil.py b/volatility3/framework/symbols/windows/pdbutil.py index a43933ccf..bdcf25fa1 100644 --- a/volatility3/framework/symbols/windows/pdbutil.py +++ b/volatility3/framework/symbols/windows/pdbutil.py @@ -9,6 +9,7 @@ import lzma import os import re import struct +from pathlib import PureWindowsPath from typing import Any, Dict, Generator, List, Optional, Tuple, Union from urllib import parse, request @@ -226,13 +227,12 @@ class PDBUtility(interfaces.configuration.VersionableInterface): return None pdb_name = debug_entry.PdbFileName.decode("utf-8").strip("\x00") + + # Let pathlib do the filename extraction. This will likely always be a Windows path though. + pdb_name = PureWindowsPath(pdb_name).name + age = debug_entry.Age - guid = "{:08x}{:04x}{:04x}{}".format( - debug_entry.Signature_Data1, - debug_entry.Signature_Data2, - debug_entry.Signature_Data3, - binascii.hexlify(debug_entry.Signature_Data4).decode("utf-8"), - ) + guid = debug_entry.Signature_String[:32] # Removes the Age from the GUID return guid, age, pdb_name @classmethod From c2b008969c44d9e3a0c8bfa8911ff6facbb53fea Mon Sep 17 00:00:00 2001 From: Leron Gray Date: Sun, 5 Nov 2023 16:25:43 -0600 Subject: [PATCH 49/83] update pefile requirements --- requirements-dev.txt | 2 +- requirements-minimal.txt | 2 +- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9db14d441..c9b615cd8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ # The following packages are required for core functionality. -pefile>=2017.8.1 +pefile>=2023.2.7 # The following packages are optional. # If certain packages are not necessary, place a comment (#) at the start of the line. diff --git a/requirements-minimal.txt b/requirements-minimal.txt index 31ac02814..c030b332d 100644 --- a/requirements-minimal.txt +++ b/requirements-minimal.txt @@ -1,2 +1,2 @@ # These packages are required for core functionality. -pefile>=2017.8.1 #foo \ No newline at end of file +pefile>=2023.2.7 #foo \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 99e0786cc..ddd1088c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # The following packages are required for core functionality. -pefile>=2017.8.1 +pefile>=2023.2.7 # The following packages are optional. # If certain packages are not necessary, place a comment (#) at the start of the line. From ebe19bf179068952a612ad94e29d08851b70b429 Mon Sep 17 00:00:00 2001 From: Eve Date: Fri, 10 Nov 2023 20:30:38 +0000 Subject: [PATCH 50/83] Linux: update maple tree extension to fix issue #1032 --- volatility3/framework/symbols/linux/extensions/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 3fb772135..c3e50fce4 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -301,7 +301,11 @@ class maple_tree(objects.StructType): self.ma_flags & self.MT_FLAGS_HEIGHT_MASK ) >> self.MT_FLAGS_HEIGHT_OFFSET yield from self._parse_maple_tree_node( - self.ma_root, maple_tree_offset, expected_maple_tree_depth + self.ma_root, + maple_tree_offset, + expected_maple_tree_depth, + seen=set(), + current_depth=1, ) def _parse_maple_tree_node( From 9fe7d479f6e3afe6db9f9eeb3b723b93bc4f7f19 Mon Sep 17 00:00:00 2001 From: Eve Date: Fri, 10 Nov 2023 20:58:07 +0000 Subject: [PATCH 51/83] Linux: update bash plugin to only get memory sections once --- volatility3/framework/plugins/linux/bash.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/bash.py b/volatility3/framework/plugins/linux/bash.py index b7dc2c16b..ce4567ca6 100644 --- a/volatility3/framework/plugins/linux/bash.py +++ b/volatility3/framework/plugins/linux/bash.py @@ -75,11 +75,16 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface): bang_addrs = [] + # get task memory sections to be used by scanners + task_memory_sections = [ + section for section in task.get_process_memory_sections(heap_only=True) + ] + # find '#' values on the heap for address in proc_layer.scan( self.context, scanners.BytesScanner(b"#"), - sections=task.get_process_memory_sections(heap_only=True), + sections=task_memory_sections, ): bang_addrs.append(struct.pack(pack_format, address)) @@ -89,7 +94,7 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface): for address, _ in proc_layer.scan( self.context, scanners.MultiStringScanner(bang_addrs), - sections=task.get_process_memory_sections(heap_only=True), + sections=task_memory_sections, ): hist = self.context.object( bash_table_name + constants.BANG + "hist_entry", From f7d5e722a2ef98370b45c3d3aaa811f13d5de480 Mon Sep 17 00:00:00 2001 From: Eve Date: Mon, 13 Nov 2023 07:05:30 +0000 Subject: [PATCH 52/83] Linux: add member presence checks to linux.checks_afinfo _check_afinfo function to stop crashes when none of the required members are present --- .../framework/plugins/linux/check_afinfo.py | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/plugins/linux/check_afinfo.py b/volatility3/framework/plugins/linux/check_afinfo.py index 90e714eaa..61c642f0e 100644 --- a/volatility3/framework/plugins/linux/check_afinfo.py +++ b/volatility3/framework/plugins/linux/check_afinfo.py @@ -51,10 +51,24 @@ class Check_afinfo(plugins.PluginInterface): yield check, addr def _check_afinfo(self, var_name, var, op_members, seq_members): - for hooked_member, hook_address in self._check_members( - var.seq_fops, var_name, op_members - ): - yield var_name, hooked_member, hook_address + # check if object has a least one of the members used for analysis by this function + required_members = ["seq_fops", "seq_ops", "seq_show"] + for member in required_members: + vollog.debug(f"{var_name}: {member} :{var.has_member(member)}") + has_required_member = any( + [var.has_member(member) for member in required_members] + ) + if not has_required_member: + vollog.warning( + f"This plugin requires the seq_fops, seq_ops, or seq_show members to be to check for hooks. These members are not present in the {var_name} object at {hex(var.vol.offset)}. This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt." + ) + return + + if var.has_member("seq_fops"): + for hooked_member, hook_address in self._check_members( + var.seq_fops, var_name, op_members + ): + yield var_name, hooked_member, hook_address # newer kernels if var.has_member("seq_ops"): @@ -64,8 +78,10 @@ class Check_afinfo(plugins.PluginInterface): yield var_name, hooked_member, hook_address # this is the most commonly hooked member by rootkits, so a force a check on it - elif not self._is_known_address(var.seq_show): - yield var_name, "show", var.seq_show + else: + if var.has_member("seq_show"): + if not self._is_known_address(var.seq_show): + yield var_name, "show", var.seq_show def _generator(self): vmlinux = self.context.modules[self.config["kernel"]] From 5ab0e4f83f7feb5444ce8d691e58bf56e495db1a Mon Sep 17 00:00:00 2001 From: Eve Date: Tue, 14 Nov 2023 07:22:00 +0000 Subject: [PATCH 53/83] Linux: remove _valid_magic from linux elf extension, check for _type_prefix and _hdr attrs instead --- volatility3/framework/symbols/linux/extensions/elf.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/elf.py b/volatility3/framework/symbols/linux/extensions/elf.py index 8b42b3075..629a05da5 100644 --- a/volatility3/framework/symbols/linux/extensions/elf.py +++ b/volatility3/framework/symbols/linux/extensions/elf.py @@ -49,15 +49,11 @@ class elf(objects.StructType): vollog.debug( f"Unable to check magic bytes for ELF file at offset {hex(object_info.offset)} in layer {layer_name}: {excp}" ) - self._valid_magic = False return None # Check validity if magic != 0x464C457F: # e.g. ELF - self._valid_magic = False return None - else: - self._valid_magic = True # We need to read the EI_CLASS (0x4 offset) ei_class = self._context.object( @@ -88,7 +84,7 @@ class elf(objects.StructType): """ Determine whether it is a valid object """ - if self._valid_magic: + if hasattr(self, "_type_prefix") and hasattr(self, "_hdr"): return self._type_prefix is not None and self._hdr is not None else: return False From 83de6274eff15f6a3dc9d5c96eecbe3d871f6b48 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 14 Nov 2023 16:53:08 +0100 Subject: [PATCH 54/83] fix symbols and function calls --- .../symbols/linux/extensions/__init__.py | 52 +++++++++++++------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 3fb772135..bb515f7de 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -71,11 +71,11 @@ class module(generic.GenericIntelProcess): def _get_sect_count(self, grp): """Try to determine the number of valid sections""" arr = self._context.object( - self.get_symbol_table().name + constants.BANG + "array", + self.get_symbol_table_name() + constants.BANG + "array", layer_name=self.vol.layer_name, offset=grp.attrs, subtype=self._context.symbol_space.get_type( - self.get_symbol_table().name + constants.BANG + "pointer" + self.get_symbol_table_name() + constants.BANG + "pointer" ), count=25, ) @@ -92,11 +92,11 @@ class module(generic.GenericIntelProcess): else: num_sects = self._get_sect_count(self.sect_attrs.grp) arr = self._context.object( - self.get_symbol_table().name + constants.BANG + "array", + self.get_symbol_table_name() + constants.BANG + "array", layer_name=self.vol.layer_name, offset=self.sect_attrs.attrs.vol.offset, subtype=self._context.symbol_space.get_type( - self.get_symbol_table().name + constants.BANG + "module_sect_attr" + self.get_symbol_table_name() + constants.BANG + "module_sect_attr" ), count=num_sects, ) @@ -105,13 +105,14 @@ class module(generic.GenericIntelProcess): yield attr def get_symbols(self): - if symbols.symbol_table_is_64bit(self._context, self.get_symbol_table().name): + """Get module symbols""" + if symbols.symbol_table_is_64bit(self._context, self.get_symbol_table_name()): prefix = "Elf64_" else: prefix = "Elf32_" elf_table_name = intermed.IntermediateSymbolTable.create( - self.context, - self.config_path, + self._context, + self._context.modules["kernel"].config_path, "linux", "elf", native_types=None, @@ -119,7 +120,7 @@ class module(generic.GenericIntelProcess): ) syms = self._context.object( - self.get_symbol_table().name + constants.BANG + "array", + self.get_symbol_table_name() + constants.BANG + "array", layer_name=self.vol.layer_name, offset=self.section_symtab, subtype=self._context.symbol_space.get_type( @@ -127,18 +128,39 @@ class module(generic.GenericIntelProcess): ), count=self.num_symtab + 1, ) + if self.section_strtab: for sym in syms: - sym.set_cached_strtab(self.section_strtab) - yield sym + try: + sym_offset = self.section_strtab + sym.st_name + sym_name = self._context.layers[self.vol.layer_name].read( + sym_offset, sym.st_size + ) + except exceptions.PagedInvalidAddressException: + continue + + if sym_name: + # Normalize sym_value + mask = self._context.layers[self.vol.layer_name].address_mask + sym_value = sym.st_value & mask + # Stop at first null byte (strtab is a null terminated strings list) + sym_name = sym_name.split(b"\x00")[0].decode("latin-1") + yield (sym_name, sym_value, sym_offset) def get_symbol(self, wanted_sym_name): - """Get value for a given symbol name""" - for sym in self.get_symbols(): - sym_name = sym.get_name() - sym_addr = sym.st_value + """Get symbol value for a given symbol name""" + for sym_name, sym_value, sym_offset in self.get_symbols(): if wanted_sym_name == sym_name: - return sym_addr + return sym_value + + return None + + def get_symbol_name_from_value(self, wanted_sym_value): + """Get symbol name for a given symbol value""" + for sym_name, sym_value, sym_offset in self.get_symbols(): + if wanted_sym_value == sym_value: + return sym_name + return None @property From 2de97af061e3569e9c49a717730543b8dcf743c0 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 14 Nov 2023 16:54:10 +0100 Subject: [PATCH 55/83] use refcnt for inheritance --- volatility3/framework/symbols/linux/extensions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index bb515f7de..72e26f2fa 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1154,7 +1154,7 @@ class vfsmount(objects.StructType): class kobject(objects.StructType): def reference_count(self): refcnt = self.kref.refcount - if self.has_member("counter"): + if refcnt.has_member("counter"): ret = refcnt.counter else: ret = refcnt.refs.counter From bf68fa8ce2d3997fe83b56e4056068047c94590a Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 14 Nov 2023 17:09:36 +0100 Subject: [PATCH 56/83] use KSYM_NAME_LEN for symbol name length --- volatility3/framework/symbols/linux/extensions/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 72e26f2fa..b0190e0f9 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -134,8 +134,8 @@ class module(generic.GenericIntelProcess): try: sym_offset = self.section_strtab + sym.st_name sym_name = self._context.layers[self.vol.layer_name].read( - sym_offset, sym.st_size - ) + sym_offset, 512 + ) # 512 is the value of KSYM_NAME_LEN except exceptions.PagedInvalidAddressException: continue From 36327a4315640b914cea0d300aa00fa28ff2096d Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 14 Nov 2023 17:20:16 +0100 Subject: [PATCH 57/83] check if symbol name is empty after strip --- volatility3/framework/symbols/linux/extensions/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index b0190e0f9..c37223d4d 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -139,12 +139,12 @@ class module(generic.GenericIntelProcess): except exceptions.PagedInvalidAddressException: continue - if sym_name: + # Stop at first null byte (strtab is a null terminated strings list) + sym_name = sym_name.split(b"\x00")[0].decode("latin-1") + if sym_name != "": # Normalize sym_value mask = self._context.layers[self.vol.layer_name].address_mask sym_value = sym.st_value & mask - # Stop at first null byte (strtab is a null terminated strings list) - sym_name = sym_name.split(b"\x00")[0].decode("latin-1") yield (sym_name, sym_value, sym_offset) def get_symbol(self, wanted_sym_name): From 4f4e2efd991e1f54af650d082c6d63cff80c38fb Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 15 Nov 2023 10:29:06 +0000 Subject: [PATCH 58/83] Linux: update warning logic in linux.checks_afinfo so that only one warning is shown and debug logs are cleaner --- .../framework/plugins/linux/check_afinfo.py | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/volatility3/framework/plugins/linux/check_afinfo.py b/volatility3/framework/plugins/linux/check_afinfo.py index 61c642f0e..7fced6acd 100644 --- a/volatility3/framework/plugins/linux/check_afinfo.py +++ b/volatility3/framework/plugins/linux/check_afinfo.py @@ -53,16 +53,14 @@ class Check_afinfo(plugins.PluginInterface): def _check_afinfo(self, var_name, var, op_members, seq_members): # check if object has a least one of the members used for analysis by this function required_members = ["seq_fops", "seq_ops", "seq_show"] - for member in required_members: - vollog.debug(f"{var_name}: {member} :{var.has_member(member)}") has_required_member = any( [var.has_member(member) for member in required_members] ) if not has_required_member: - vollog.warning( - f"This plugin requires the seq_fops, seq_ops, or seq_show members to be to check for hooks. These members are not present in the {var_name} object at {hex(var.vol.offset)}. This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt." + vollog.debug( + f"{var_name} object at {hex(var.vol.offset)} had none of the required members: {', '.join([member for member in required_members])}" ) - return + raise exceptions.PluginRequirementException if var.has_member("seq_fops"): for hooked_member, hook_address in self._check_members( @@ -101,6 +99,12 @@ class Check_afinfo(plugins.PluginInterface): ) protocols = [tcp, udp] + # used to track the calls to _check_afinfo and the + # number of errors produced due to missing members + symbols_checked = set() + symbols_with_errors = set() + + # loop through all symbols for struct_type, global_vars in protocols: for global_var_name in global_vars: # this will lookup fail for the IPv6 protocols on kernels without IPv6 support @@ -113,10 +117,20 @@ class Check_afinfo(plugins.PluginInterface): object_type=struct_type, offset=global_var.address ) - for name, member, address in self._check_afinfo( - global_var_name, global_var, op_members, seq_members - ): - yield 0, (name, member, format_hints.Hex(address)) + symbols_checked.add(global_var_name) + try: + for name, member, address in self._check_afinfo( + global_var_name, global_var, op_members, seq_members + ): + yield 0, (name, member, format_hints.Hex(address)) + except exceptions.PluginRequirementException: + symbols_with_errors.add(global_var_name) + + # if every call to _check_afinfo failed show a warning + if symbols_checked == symbols_with_errors: + vollog.warning( + "This plugin was not able to check for hooks. This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt." + ) def run(self): return renderers.TreeGrid( From 1a73717b9325ae332d04a77f9ecef58f89c9417b Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Fri, 17 Nov 2023 17:49:54 +0100 Subject: [PATCH 59/83] set 'module' as config string --- .../framework/symbols/linux/extensions/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index c37223d4d..48296b284 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -105,14 +105,18 @@ class module(generic.GenericIntelProcess): yield attr def get_symbols(self): - """Get module symbols""" + """Get module symbols + + Yields: + A tuple for each symbol containing the symbol name and its corresponding value + """ if symbols.symbol_table_is_64bit(self._context, self.get_symbol_table_name()): prefix = "Elf64_" else: prefix = "Elf32_" elf_table_name = intermed.IntermediateSymbolTable.create( self._context, - self._context.modules["kernel"].config_path, + "module", "linux", "elf", native_types=None, From 6372b6f367bfb765e1a1f38f30866adb966fed33 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Fri, 17 Nov 2023 17:52:58 +0100 Subject: [PATCH 60/83] symbols getters follow vol2 format --- .../symbols/linux/extensions/__init__.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 48296b284..21edfc669 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -132,37 +132,37 @@ class module(generic.GenericIntelProcess): ), count=self.num_symtab + 1, ) - if self.section_strtab: for sym in syms: + sym_arr = self._context.object( + self.get_symbol_table_name() + constants.BANG + "array", + layer_name=self.vol.native_layer_name, + offset=self.section_strtab + sym.st_name, + ) try: - sym_offset = self.section_strtab + sym.st_name - sym_name = self._context.layers[self.vol.layer_name].read( - sym_offset, 512 - ) # 512 is the value of KSYM_NAME_LEN - except exceptions.PagedInvalidAddressException: + sym_name = utility.array_to_string( + sym_arr, 512 + ) # 512 is the value of KSYM_NAME_LEN kernel constant + except exceptions.InvalidAddressException: continue - - # Stop at first null byte (strtab is a null terminated strings list) - sym_name = sym_name.split(b"\x00")[0].decode("latin-1") if sym_name != "": - # Normalize sym_value + # Normalize sym.st_value offset, which is an address pointing to the symbol value mask = self._context.layers[self.vol.layer_name].address_mask - sym_value = sym.st_value & mask - yield (sym_name, sym_value, sym_offset) + sym_address = sym.st_value & mask + yield (sym_name, sym_address) def get_symbol(self, wanted_sym_name): """Get symbol value for a given symbol name""" - for sym_name, sym_value, sym_offset in self.get_symbols(): + for sym_name, sym_address in self.get_symbols(): if wanted_sym_name == sym_name: - return sym_value + return sym_address return None - def get_symbol_name_from_value(self, wanted_sym_value): - """Get symbol name for a given symbol value""" - for sym_name, sym_value, sym_offset in self.get_symbols(): - if wanted_sym_value == sym_value: + def get_symbol_from_address(self, wanted_sym_address): + """Get symbol name for a given symbol address""" + for sym_name, sym_address in self.get_symbols(): + if wanted_sym_address == sym_address: return sym_name return None From fa2b840b5552c8ad3b4242652acdde362f9e0052 Mon Sep 17 00:00:00 2001 From: k1nd0ne Date: Sat, 18 Nov 2023 15:05:11 +0100 Subject: [PATCH 61/83] Object Storage Layer for PR #1037 --- volatility3/framework/layers/objectstorage.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 volatility3/framework/layers/objectstorage.py diff --git a/volatility3/framework/layers/objectstorage.py b/volatility3/framework/layers/objectstorage.py new file mode 100644 index 000000000..28f7a1bd4 --- /dev/null +++ b/volatility3/framework/layers/objectstorage.py @@ -0,0 +1,56 @@ +# This file is Copyright 2022 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +import urllib.parse +from typing import Optional, Any, List + +try: + import s3fs + HAS_S3FS = True +except ImportError: + HAS_S3FS = False + +try: + import gcsfs + HAS_GCSFS = True +except ImportError: + HAS_GCSFS = False + +from volatility3.framework import exceptions +from volatility3.framework.layers import resources + +vollog = logging.getLogger(__file__) + +class S3FileSystemHandler(resources.VolatilityHandler): + if HAS_S3FS: + @classmethod + def non_cached_schemes(cls) -> List[str]: + return ["s3"] + + @staticmethod + def default_open(req: urllib.request.Request) -> Optional[Any]: + """Handles the request if it's the s3 scheme.""" + if req.type == "s3": + object_uri = "://".join(req.full_url.split("://")[1:]) + return s3fs.S3FileSystem().open(object_uri) + else: + raise exceptions.LayerException("s3 requirement is missing.") + + +class GSFileSystemHandler(resources.VolatilityHandler): + if HAS_GCSFS: + @classmethod + def non_cached_schemes(cls) -> List[str]: + return ["gs"] + + @staticmethod + def default_open(req: urllib.request.Request) -> Optional[Any]: + """Handles the request if it's the gs scheme.""" + if req.type == "gs": + object_uri = "://".join(req.full_url.split("://")[1:]) + return gcsfs.GCSFileSystem().open(object_uri) + return None + else: + raise exceptions.LayerException("gcsfs requirement is missing.") \ No newline at end of file From f8e34642195eef8954fd0bdf4e55d63e7e296d6a Mon Sep 17 00:00:00 2001 From: k1nd0ne Date: Sat, 18 Nov 2023 15:17:54 +0100 Subject: [PATCH 62/83] Forgot to return 'None' in the s3 scheme --- volatility3/framework/layers/objectstorage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/volatility3/framework/layers/objectstorage.py b/volatility3/framework/layers/objectstorage.py index 28f7a1bd4..ce6cdb327 100644 --- a/volatility3/framework/layers/objectstorage.py +++ b/volatility3/framework/layers/objectstorage.py @@ -35,6 +35,7 @@ class S3FileSystemHandler(resources.VolatilityHandler): if req.type == "s3": object_uri = "://".join(req.full_url.split("://")[1:]) return s3fs.S3FileSystem().open(object_uri) + return None else: raise exceptions.LayerException("s3 requirement is missing.") From ceac912014f13e4262443e831bb390b928c57fd3 Mon Sep 17 00:00:00 2001 From: k1nd0ne Date: Sat, 18 Nov 2023 15:47:49 +0100 Subject: [PATCH 63/83] Adding requirements --- requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/requirements.txt b/requirements.txt index 99e0786cc..7e1c28595 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,3 +16,7 @@ pycryptodome # This is required for memory acquisition via leechcore/pcileech. leechcorepyc>=2.4.0 + +# This is required for memory analysis on a Amazon/MinIO S3 and Google Cloud object storage +gcsfs>=2023.6.0 +s3fs>=2023.6.0 \ No newline at end of file From 6c6d036cc0b2606406af418fbb7a15d6db325cfc Mon Sep 17 00:00:00 2001 From: k1nd0ne Date: Sun, 19 Nov 2023 00:54:56 +0100 Subject: [PATCH 64/83] Adding Alternate Data Stream Scanner --- .../framework/plugins/windows/mftscan.py | 153 +++++++++++++++++- .../framework/symbols/windows/mft.json | 16 +- 2 files changed, 167 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/windows/mftscan.py b/volatility3/framework/plugins/windows/mftscan.py index 87416d274..f5c3ebc95 100644 --- a/volatility3/framework/plugins/windows/mftscan.py +++ b/volatility3/framework/plugins/windows/mftscan.py @@ -5,7 +5,7 @@ import contextlib import datetime import logging -from volatility3.framework import constants, exceptions, interfaces, renderers +from volatility3.framework import constants, exceptions, interfaces, renderers, symbols from volatility3.framework.configuration import requirements from volatility3.framework.renderers import conversion, format_hints from volatility3.framework.symbols import intermed @@ -31,6 +31,7 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): requirements.VersionRequirement( name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) ), + ] def _generator(self): @@ -189,3 +190,153 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): ], self._generator(), ) + + +class ADS(interfaces.plugins.PluginInterface): + + """Scans for Alternate Data Stream""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement( + name="primary", + description="Memory layer for the kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.VersionRequirement( + name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) + ), + ] + + def _generator(self): + layer = self.context.layers[self.config["primary"]] + + # Yara Rule to scan for MFT Header Signatures + rules = yarascan.YaraScan.process_yara_options( + {"yara_rules": "/FILE0|FILE\*|BAAD/"} + ) + + # Read in the Symbol File + symbol_table = intermed.IntermediateSymbolTable.create( + context=self.context, + config_path=self.config_path, + sub_path="windows", + filename="mft", + class_types={"MFT_ENTRY": mft.MFTEntry,"FILE_NAME_ENTRY": mft.MFTFileName}, + ) + + # get each of the individual Field Sets + mft_object = symbol_table + constants.BANG + "MFT_ENTRY" + header_object = symbol_table + constants.BANG + "ATTR_HEADER" + attribute_object = symbol_table + constants.BANG + "ATTRIBUTE" + fn_object = symbol_table + constants.BANG + "FILE_NAME_ENTRY" + + # Scan the layer for Raw MFT records and parse the fields + for offset, _rule_name, _name, _value in layer.scan( + context=self.context, scanner=yarascan.YaraScanner(rules=rules) + ): + with contextlib.suppress(exceptions.PagedInvalidAddressException): + mft_record = self.context.object( + mft_object, offset=offset, layer_name=layer.name + ) + # We will update this on each pass in the next loop and use it as the new offset. + attr_base_offset = mft_record.FirstAttrOffset + + attr_header = self.context.object( + header_object, + offset=offset + attr_base_offset, + layer_name=layer.name, + ) + + # There is no field that has a count of Attributes + # Keep Attempting to read attributes until we get an invalid attr_header.AttrType + file_name = "" + while attr_header.AttrType.is_valid_choice: + + # Offset past the headers to the attribute data + attr_data_offset = ( + offset + + attr_base_offset + + self.context.symbol_space.get_type( + attribute_object + ).relative_child_offset("Attr_Data") + ) + + if attr_header.AttrType.lookup() == "FILE_NAME": + attr_data = self.context.object( + fn_object, offset=attr_data_offset, layer_name=layer.name + ) + file_name = attr_data.get_full_name() + + + # DATA Attribute (can be ADS or not) + if attr_header.AttrType.lookup() == "DATA": + if not attr_header.NonResidentFlag: + # It is a resident file + if attr_header.NameLength > 0: + attr_name_offset = ( + offset + + attr_base_offset + + attr_header.NameOffset + ) + ads_name = self._context.layers[layer.name].read( + attr_name_offset, attr_header.NameLength*2 , pad=True + ).decode('utf-16') + attr_content_offset = ( + offset + + attr_base_offset + + attr_header.ContentOffset + ) + content = self._context.layers[layer.name].read( + attr_content_offset, attr_header.ContentLength , pad=True + ) + + + # Preparing for Disassembly + architecture = layer.metadata.get("architecture", None) + disasm = interfaces.renderers.Disassembly( + content, 0, architecture.lower() + ) + + yield 0, ( + format_hints.Hex(attr_data_offset), + mft_record.get_signature(), + mft_record.RecordNumber, + attr_header.AttrType.lookup(), + file_name, + ads_name, + format_hints.HexBytes(content), + disasm, + ) + + # If there's no advancement the loop will never end, so break it now + if attr_header.Length == 0: + break + + # Update the base offset to point to the next attribute + attr_base_offset += attr_header.Length + # Get the next attribute + attr_header = self.context.object( + header_object, + offset=offset + attr_base_offset, + layer_name=layer.name, + ) + + + def run(self): + return renderers.TreeGrid( + [ + ("Offset", format_hints.Hex), + ("Record Type", str), + ("Record Number", int), + ("MFT Type", str), + ("Filename", str), + ("ADS Filename", str), + ("Hexdump", format_hints.HexBytes), + ("Disasm", interfaces.renderers.Disassembly), + ], + self._generator(), + ) \ No newline at end of file diff --git a/volatility3/framework/symbols/windows/mft.json b/volatility3/framework/symbols/windows/mft.json index e5de8f3fa..616e8990d 100644 --- a/volatility3/framework/symbols/windows/mft.json +++ b/volatility3/framework/symbols/windows/mft.json @@ -300,10 +300,24 @@ "kind": "base", "name": "unsigned short" } + }, + "ContentLength": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned int" + } + }, + "ContentOffset": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned short" + } } }, "kind": "struct", - "size": 16 + "size": 22 },"RESIDENT_HEADER": { "fields": { "AttrSize": { From 46a6ab1721e241939af0b4c29bafdf2fcb09c405 Mon Sep 17 00:00:00 2001 From: k1nd0ne Date: Sun, 19 Nov 2023 13:09:47 +0100 Subject: [PATCH 65/83] Making sure it is ADS --- .../framework/plugins/windows/mftscan.py | 77 ++++++++++--------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/volatility3/framework/plugins/windows/mftscan.py b/volatility3/framework/plugins/windows/mftscan.py index f5c3ebc95..b4af2a867 100644 --- a/volatility3/framework/plugins/windows/mftscan.py +++ b/volatility3/framework/plugins/windows/mftscan.py @@ -254,8 +254,8 @@ class ADS(interfaces.plugins.PluginInterface): # There is no field that has a count of Attributes # Keep Attempting to read attributes until we get an invalid attr_header.AttrType file_name = "" + is_ads = 0 while attr_header.AttrType.is_valid_choice: - # Offset past the headers to the attribute data attr_data_offset = ( offset @@ -274,43 +274,48 @@ class ADS(interfaces.plugins.PluginInterface): # DATA Attribute (can be ADS or not) if attr_header.AttrType.lookup() == "DATA": - if not attr_header.NonResidentFlag: - # It is a resident file - if attr_header.NameLength > 0: - attr_name_offset = ( - offset - + attr_base_offset - + attr_header.NameOffset - ) - ads_name = self._context.layers[layer.name].read( - attr_name_offset, attr_header.NameLength*2 , pad=True - ).decode('utf-16') - attr_content_offset = ( - offset - + attr_base_offset - + attr_header.ContentOffset - ) - content = self._context.layers[layer.name].read( - attr_content_offset, attr_header.ContentLength , pad=True - ) + if is_ads > 0: + if not attr_header.NonResidentFlag: + # Resident files are the most interesting. + if attr_header.NameLength > 0: + attr_name_offset = ( + offset + + attr_base_offset + + attr_header.NameOffset + ) + ads_name = self._context.layers[layer.name].read( + attr_name_offset, attr_header.NameLength*2 , pad=True + ).decode('utf-16') + attr_content_offset = ( + offset + + attr_base_offset + + attr_header.ContentOffset + ) + content = self._context.layers[layer.name].read( + attr_content_offset, attr_header.ContentLength , pad=True + ) - - # Preparing for Disassembly - architecture = layer.metadata.get("architecture", None) - disasm = interfaces.renderers.Disassembly( - content, 0, architecture.lower() - ) + + # Preparing for Disassembly + architecture = layer.metadata.get("architecture", None) + disasm = interfaces.renderers.Disassembly( + content, 0, architecture.lower() + ) - yield 0, ( - format_hints.Hex(attr_data_offset), - mft_record.get_signature(), - mft_record.RecordNumber, - attr_header.AttrType.lookup(), - file_name, - ads_name, - format_hints.HexBytes(content), - disasm, - ) + yield 0, ( + format_hints.Hex(attr_data_offset), + mft_record.get_signature(), + mft_record.RecordNumber, + attr_header.AttrType.lookup(), + file_name, + ads_name, + format_hints.HexBytes(content), + disasm, + ) + else: + # The First Data Attr is the file itself not the ADS + is_ads+= 1 + # If there's no advancement the loop will never end, so break it now if attr_header.Length == 0: From fd5c8289397bfcbff816fb9ce18db987d5e5470e Mon Sep 17 00:00:00 2001 From: k1nd0ne Date: Mon, 20 Nov 2023 11:02:31 +0100 Subject: [PATCH 66/83] Renaming file, handling url parsing using urllib, changing logger and requirement condition placement. --- .../framework/layers/{objectstorage.py => cloudstorage.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename volatility3/framework/layers/{objectstorage.py => cloudstorage.py} (100%) diff --git a/volatility3/framework/layers/objectstorage.py b/volatility3/framework/layers/cloudstorage.py similarity index 100% rename from volatility3/framework/layers/objectstorage.py rename to volatility3/framework/layers/cloudstorage.py From 80483722ebc1a149f17d887648ef628abf71ed40 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Mon, 20 Nov 2023 11:57:56 +0100 Subject: [PATCH 67/83] split module functions to keep current API --- .../symbols/linux/extensions/__init__.py | 77 +++++++++++-------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 21edfc669..f5c71eb0e 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -104,64 +104,79 @@ class module(generic.GenericIntelProcess): for attr in arr: yield attr - def get_symbols(self): - """Get module symbols - - Yields: - A tuple for each symbol containing the symbol name and its corresponding value - """ - if symbols.symbol_table_is_64bit(self._context, self.get_symbol_table_name()): - prefix = "Elf64_" - else: - prefix = "Elf32_" + def get_elf_table_name(self): elf_table_name = intermed.IntermediateSymbolTable.create( self._context, - "module", + "config_name_elf_symbol_table", "linux", "elf", native_types=None, class_types=elf.class_types, ) + return elf_table_name + def get_symbols(self): + """Get symbols of the module + + Yields: + A symbol object + """ + + if not hasattr(self, "_elf_table_name"): + self._elf_table_name = self.get_elf_table_name() + if symbols.symbol_table_is_64bit(self._context, self.get_symbol_table_name()): + prefix = "Elf64_" + else: + prefix = "Elf32_" syms = self._context.object( self.get_symbol_table_name() + constants.BANG + "array", layer_name=self.vol.layer_name, offset=self.section_symtab, subtype=self._context.symbol_space.get_type( - elf_table_name + constants.BANG + prefix + "Sym" + self._elf_table_name + constants.BANG + prefix + "Sym" ), count=self.num_symtab + 1, ) if self.section_strtab: for sym in syms: - sym_arr = self._context.object( - self.get_symbol_table_name() + constants.BANG + "array", - layer_name=self.vol.native_layer_name, - offset=self.section_strtab + sym.st_name, - ) - try: - sym_name = utility.array_to_string( - sym_arr, 512 - ) # 512 is the value of KSYM_NAME_LEN kernel constant - except exceptions.InvalidAddressException: - continue - if sym_name != "": - # Normalize sym.st_value offset, which is an address pointing to the symbol value - mask = self._context.layers[self.vol.layer_name].address_mask - sym_address = sym.st_value & mask - yield (sym_name, sym_address) + yield sym + + def get_symbols_names_and_addresses(self): + """Get names and addresses for each symbol of the module + + Yields: + A tuple for each symbol containing the symbol name and its corresponding value + """ + + for sym in self.get_symbols(): + sym_arr = self._context.object( + self.get_symbol_table_name() + constants.BANG + "array", + layer_name=self.vol.native_layer_name, + offset=self.section_strtab + sym.st_name, + ) + try: + sym_name = utility.array_to_string( + sym_arr, 512 + ) # 512 is the value of KSYM_NAME_LEN kernel constant + except exceptions.InvalidAddressException: + continue + if sym_name != "": + # Normalize sym.st_value offset, which is an address pointing to the symbol value + mask = self._context.layers[self.vol.layer_name].address_mask + sym_address = sym.st_value & mask + yield (sym_name, sym_address) def get_symbol(self, wanted_sym_name): """Get symbol value for a given symbol name""" - for sym_name, sym_address in self.get_symbols(): + for sym_name, sym_address in self.get_symbols_names_and_addresses(): if wanted_sym_name == sym_name: return sym_address return None - def get_symbol_from_address(self, wanted_sym_address): + def get_symbol_by_address(self, wanted_sym_address): """Get symbol name for a given symbol address""" - for sym_name, sym_address in self.get_symbols(): + for sym_name, sym_address in self.get_symbols_names_and_addresses(): if wanted_sym_address == sym_address: return sym_name From c3dbf9714d9158b01385f78fb7434ecee64ac5b8 Mon Sep 17 00:00:00 2001 From: k1nd0ne Date: Mon, 20 Nov 2023 19:12:07 +0100 Subject: [PATCH 68/83] Better variable init --- volatility3/framework/plugins/windows/mftscan.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/plugins/windows/mftscan.py b/volatility3/framework/plugins/windows/mftscan.py index b4af2a867..c40f7ef73 100644 --- a/volatility3/framework/plugins/windows/mftscan.py +++ b/volatility3/framework/plugins/windows/mftscan.py @@ -59,7 +59,7 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): fn_object = symbol_table + constants.BANG + "FILE_NAME_ENTRY" # Scan the layer for Raw MFT records and parse the fields - for offset, _rule_name, _name, _value in layer.scan( + for offset, _, _, _ in layer.scan( context=self.context, scanner=yarascan.YaraScanner(rules=rules) ): with contextlib.suppress(exceptions.PagedInvalidAddressException): @@ -253,8 +253,9 @@ class ADS(interfaces.plugins.PluginInterface): # There is no field that has a count of Attributes # Keep Attempting to read attributes until we get an invalid attr_header.AttrType - file_name = "" - is_ads = 0 + file_name = "N/A" + is_ads = False + # The First $DATA Attr is the 'principal' file itself not the ADS while attr_header.AttrType.is_valid_choice: # Offset past the headers to the attribute data attr_data_offset = ( @@ -274,7 +275,7 @@ class ADS(interfaces.plugins.PluginInterface): # DATA Attribute (can be ADS or not) if attr_header.AttrType.lookup() == "DATA": - if is_ads > 0: + if is_ads: if not attr_header.NonResidentFlag: # Resident files are the most interesting. if attr_header.NameLength > 0: @@ -313,8 +314,7 @@ class ADS(interfaces.plugins.PluginInterface): disasm, ) else: - # The First Data Attr is the file itself not the ADS - is_ads+= 1 + is_ads = True # If there's no advancement the loop will never end, so break it now From 24609856de629ccb7adbab46977de3c8492846e8 Mon Sep 17 00:00:00 2001 From: k1nd0ne Date: Sat, 25 Nov 2023 17:04:47 +0100 Subject: [PATCH 69/83] Fixing: unused import, typo, ISF enhancement --- .../framework/plugins/windows/mftscan.py | 24 +++++++++---------- .../framework/symbols/windows/mft.json | 8 +++---- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/volatility3/framework/plugins/windows/mftscan.py b/volatility3/framework/plugins/windows/mftscan.py index c40f7ef73..397ce75bc 100644 --- a/volatility3/framework/plugins/windows/mftscan.py +++ b/volatility3/framework/plugins/windows/mftscan.py @@ -5,7 +5,7 @@ import contextlib import datetime import logging -from volatility3.framework import constants, exceptions, interfaces, renderers, symbols +from volatility3.framework import constants, exceptions, interfaces, renderers from volatility3.framework.configuration import requirements from volatility3.framework.renderers import conversion, format_hints from volatility3.framework.symbols import intermed @@ -53,13 +53,12 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): # get each of the individual Field Sets mft_object = symbol_table + constants.BANG + "MFT_ENTRY" - attribute_object = symbol_table + constants.BANG + "ATTRIBUTE" header_object = symbol_table + constants.BANG + "ATTR_HEADER" si_object = symbol_table + constants.BANG + "STANDARD_INFORMATION_ENTRY" fn_object = symbol_table + constants.BANG + "FILE_NAME_ENTRY" # Scan the layer for Raw MFT records and parse the fields - for offset, _, _, _ in layer.scan( + for offset, _rule_name, _name, _value in layer.scan( context=self.context, scanner=yarascan.YaraScanner(rules=rules) ): with contextlib.suppress(exceptions.PagedInvalidAddressException): @@ -86,8 +85,8 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): offset + attr_base_offset + self.context.symbol_space.get_type( - attribute_object - ).relative_child_offset("Attr_Data") + header_object + ).size ) # MFT Flags determine the file type or dir @@ -231,7 +230,6 @@ class ADS(interfaces.plugins.PluginInterface): # get each of the individual Field Sets mft_object = symbol_table + constants.BANG + "MFT_ENTRY" header_object = symbol_table + constants.BANG + "ATTR_HEADER" - attribute_object = symbol_table + constants.BANG + "ATTRIBUTE" fn_object = symbol_table + constants.BANG + "FILE_NAME_ENTRY" # Scan the layer for Raw MFT records and parse the fields @@ -253,7 +251,7 @@ class ADS(interfaces.plugins.PluginInterface): # There is no field that has a count of Attributes # Keep Attempting to read attributes until we get an invalid attr_header.AttrType - file_name = "N/A" + file_name = renderers.NotAvailableValue is_ads = False # The First $DATA Attr is the 'principal' file itself not the ADS while attr_header.AttrType.is_valid_choice: @@ -262,8 +260,8 @@ class ADS(interfaces.plugins.PluginInterface): offset + attr_base_offset + self.context.symbol_space.get_type( - attribute_object - ).relative_child_offset("Attr_Data") + header_object + ).size ) if attr_header.AttrType.lookup() == "FILE_NAME": @@ -272,7 +270,6 @@ class ADS(interfaces.plugins.PluginInterface): ) file_name = attr_data.get_full_name() - # DATA Attribute (can be ADS or not) if attr_header.AttrType.lookup() == "DATA": if is_ads: @@ -284,19 +281,21 @@ class ADS(interfaces.plugins.PluginInterface): + attr_base_offset + attr_header.NameOffset ) + ads_name = self._context.layers[layer.name].read( attr_name_offset, attr_header.NameLength*2 , pad=True ).decode('utf-16') + attr_content_offset = ( offset + attr_base_offset + attr_header.ContentOffset - ) + ) + content = self._context.layers[layer.name].read( attr_content_offset, attr_header.ContentLength , pad=True ) - # Preparing for Disassembly architecture = layer.metadata.get("architecture", None) disasm = interfaces.renderers.Disassembly( @@ -330,7 +329,6 @@ class ADS(interfaces.plugins.PluginInterface): layer_name=layer.name, ) - def run(self): return renderers.TreeGrid( [ diff --git a/volatility3/framework/symbols/windows/mft.json b/volatility3/framework/symbols/windows/mft.json index 616e8990d..d4f2aef7a 100644 --- a/volatility3/framework/symbols/windows/mft.json +++ b/volatility3/framework/symbols/windows/mft.json @@ -230,21 +230,21 @@ "offset": 0, "type": { "kind": "struct", - "name": "mft!ATTR_HEADER" + "name": "ATTR_HEADER" } }, "Resident_Header": { "offset": 16, "type": { "kind": "struct", - "name": "mft!RESIDENT_HEADER" + "name": "RESIDENT_HEADER" } }, "Attr_Data": { "offset": 24, "type": { "kind": "struct", - "name": "mft!ATTR_HEADER" + "name": "ATTR_HEADER" } } }, @@ -317,7 +317,7 @@ } }, "kind": "struct", - "size": 22 + "size": 24 },"RESIDENT_HEADER": { "fields": { "AttrSize": { From 62506aece60740787374fbc6f141dc3c33a34027 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 26 Nov 2023 11:23:09 +0000 Subject: [PATCH 70/83] Core: Fix up github security issues This fixes an unused import, an improper use of self and lots and lots of places where we implicitly return None. This now explicitly returns None to improve readability and prevent mixed implicit and explicit return values. This should also somewhat aid type checking by humans. --- volatility3/cli/__init__.py | 4 +-- volatility3/cli/volshell/generic.py | 6 ++-- volatility3/cli/volshell/linux.py | 4 +-- volatility3/cli/volshell/mac.py | 4 +-- volatility3/cli/volshell/windows.py | 2 +- volatility3/framework/automagic/module.py | 6 ++-- volatility3/framework/automagic/stacker.py | 2 +- .../framework/automagic/symbol_finder.py | 4 +-- volatility3/framework/layers/intel.py | 4 +-- volatility3/framework/layers/msf.py | 2 +- volatility3/framework/layers/segmented.py | 4 +-- .../framework/plugins/linux/capabilities.py | 2 +- .../framework/plugins/linux/check_syscall.py | 2 +- .../framework/plugins/linux/malfind.py | 2 +- .../framework/plugins/linux/sockstat.py | 12 ++++---- .../framework/plugins/mac/check_sysctl.py | 2 +- volatility3/framework/plugins/mac/kevents.py | 4 +-- volatility3/framework/plugins/mac/lsmod.py | 2 +- volatility3/framework/plugins/mac/malfind.py | 2 +- .../framework/plugins/windows/cachedump.py | 10 +++---- .../framework/plugins/windows/callbacks.py | 10 +++---- .../framework/plugins/windows/dumpfiles.py | 2 +- .../framework/plugins/windows/handles.py | 4 +-- .../framework/plugins/windows/lsadump.py | 6 ++-- .../framework/plugins/windows/malfind.py | 2 +- .../framework/plugins/windows/netstat.py | 4 +-- .../framework/plugins/windows/pstree.py | 4 +-- .../plugins/windows/registry/hivelist.py | 2 +- .../plugins/windows/registry/printkey.py | 2 +- .../plugins/windows/registry/userassist.py | 4 +-- .../plugins/windows/skeleton_key_check.py | 8 +++--- .../framework/renderers/format_hints.py | 3 +- .../framework/symbols/linux/__init__.py | 8 +++--- .../symbols/linux/extensions/__init__.py | 28 +++++++++---------- .../framework/symbols/linux/extensions/elf.py | 2 +- volatility3/framework/symbols/mac/__init__.py | 4 +-- .../symbols/mac/extensions/__init__.py | 10 +++---- .../symbols/windows/extensions/__init__.py | 22 +++++++-------- .../symbols/windows/extensions/registry.py | 4 +-- .../symbols/windows/extensions/services.py | 2 +- .../framework/symbols/windows/pdbutil.py | 1 - 41 files changed, 106 insertions(+), 106 deletions(-) diff --git a/volatility3/cli/__init__.py b/volatility3/cli/__init__.py index 9bfd14c6c..91bda7c66 100644 --- a/volatility3/cli/__init__.py +++ b/volatility3/cli/__init__.py @@ -662,7 +662,7 @@ class CommandLine: def close(self): # Don't overcommit if self.closed: - return + return None self.seek(0) @@ -712,7 +712,7 @@ class CommandLine: """Closes and commits the file (by moving the temporary file to the correct name""" # Don't overcommit if self._file.closed: - return + return None self._file.close() output_filename = self._get_final_filename() diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index ea9e65d9b..b95129d19 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -108,7 +108,7 @@ class Volshell(interfaces.plugins.PluginInterface): """Describes the available commands""" if args: help(*args) - return + return None variables = [] print("\nMethods:") @@ -325,7 +325,7 @@ class Volshell(interfaces.plugins.PluginInterface): (str, interfaces.objects.ObjectInterface, interfaces.objects.Template), ): print("Cannot display information about non-type object") - return + return None if not isinstance(object, str): # Mypy requires us to order things this way @@ -453,7 +453,7 @@ class Volshell(interfaces.plugins.PluginInterface): """Prints an alphabetical list of symbols for a symbol table""" if symbol_table is None: print("No symbol table provided") - return + return None longest_offset = longest_name = 0 table = self.context.symbol_space[symbol_table] diff --git a/volatility3/cli/volshell/linux.py b/volatility3/cli/volshell/linux.py index 8c23bbec3..c5e555ec7 100644 --- a/volatility3/cli/volshell/linux.py +++ b/volatility3/cli/volshell/linux.py @@ -35,9 +35,9 @@ class Volshell(generic.Volshell): process_layer = task.add_process_layer() if process_layer is not None: self.change_layer(process_layer) - return + return None print(f"Layer for task ID {pid} could not be constructed") - return + return None print(f"No task with task ID {pid} found") def list_tasks(self): diff --git a/volatility3/cli/volshell/mac.py b/volatility3/cli/volshell/mac.py index b709511b1..2b32ad677 100644 --- a/volatility3/cli/volshell/mac.py +++ b/volatility3/cli/volshell/mac.py @@ -35,9 +35,9 @@ class Volshell(generic.Volshell): process_layer = task.add_process_layer() if process_layer is not None: self.change_layer(process_layer) - return + return None print(f"Layer for task ID {pid} could not be constructed") - return + return None print(f"No task with task ID {pid} found") def list_tasks(self, method=None): diff --git a/volatility3/cli/volshell/windows.py b/volatility3/cli/volshell/windows.py index 652b2e66b..5c2190c02 100644 --- a/volatility3/cli/volshell/windows.py +++ b/volatility3/cli/volshell/windows.py @@ -32,7 +32,7 @@ class Volshell(generic.Volshell): if process.UniqueProcessId == pid: process_layer = process.add_process_layer() self.change_layer(process_layer) - return + return None print(f"No process with process ID {pid} found") def list_processes(self): diff --git a/volatility3/framework/automagic/module.py b/volatility3/framework/automagic/module.py index 2bdaf3f62..ee56a040c 100644 --- a/volatility3/framework/automagic/module.py +++ b/volatility3/framework/automagic/module.py @@ -29,9 +29,9 @@ class KernelModule(interfaces.automagic.AutomagicInterface): requirement.requirements[req], progress_callback, ) - return + return None if not requirement.unsatisfied(context, config_path): - return + return None # The requirement is unfulfilled and is a ModuleRequirement context.config[ @@ -43,7 +43,7 @@ class KernelModule(interfaces.automagic.AutomagicInterface): requirement.requirements[req].unsatisfied(context, new_config_path) and req != "offset" ): - return + return None # We now just have the offset requirement, but the layer requirement has been fulfilled. # Unfortunately we don't know the layer name requirement's exact name diff --git a/volatility3/framework/automagic/stacker.py b/volatility3/framework/automagic/stacker.py index d966d99fa..c251d3c46 100644 --- a/volatility3/framework/automagic/stacker.py +++ b/volatility3/framework/automagic/stacker.py @@ -103,7 +103,7 @@ class LayerStacker(interfaces.automagic.AutomagicInterface): appropriate_config_path, layer_name = result context.config.merge(appropriate_config_path, subconfig) context.config[appropriate_config_path] = top_layer_name - return + return None self._cached = None new_context = context.clone() diff --git a/volatility3/framework/automagic/symbol_finder.py b/volatility3/framework/automagic/symbol_finder.py index f30dff456..bf1c8ff16 100644 --- a/volatility3/framework/automagic/symbol_finder.py +++ b/volatility3/framework/automagic/symbol_finder.py @@ -69,7 +69,7 @@ class SymbolFinder(interfaces.automagic.AutomagicInterface): # Bomb out early if our details haven't been configured if self.symbol_class is None: - return + return None self._requirements = self.find_requirements( context, @@ -120,7 +120,7 @@ class SymbolFinder(interfaces.automagic.AutomagicInterface): # Bomb out early if there's no banners if not self.banners: - return + return None mss = scanners.MultiStringScanner([x for x in self.banners if x is not None]) diff --git a/volatility3/framework/layers/intel.py b/volatility3/framework/layers/intel.py index 046203fa6..7d3b86a12 100644 --- a/volatility3/framework/layers/intel.py +++ b/volatility3/framework/layers/intel.py @@ -331,9 +331,9 @@ class Intel(linear.LinearlyMappedLayer): except exceptions.InvalidAddressException: if not ignore_errors: raise - return + return None yield offset, length, mapped_offset, length, layer_name - return + return None while length > 0: try: chunk_offset, page_size, layer_name = self._translate(offset) diff --git a/volatility3/framework/layers/msf.py b/volatility3/framework/layers/msf.py index 76c645e92..8d84a774b 100644 --- a/volatility3/framework/layers/msf.py +++ b/volatility3/framework/layers/msf.py @@ -47,7 +47,7 @@ class PdbMultiStreamFormat(linear.LinearlyMappedLayer): def read_streams(self): # Shortcut in case they've already been read if self._streams: - return + return None # Recover the root table, by recovering the root table index table... module = self.context.module(self.pdb_symbol_table, self._base_layer, offset=0) diff --git a/volatility3/framework/layers/segmented.py b/volatility3/framework/layers/segmented.py index beb667436..0d29d8bff 100644 --- a/volatility3/framework/layers/segmented.py +++ b/volatility3/framework/layers/segmented.py @@ -126,9 +126,9 @@ class NonLinearlySegmentedLayer( current_offset = logical_offset # If it starts too late then we're done if logical_offset > offset + length: - return + return None except exceptions.InvalidAddressException: - return + return None # Crop it to the amount we need left chunk_size = min(size, length + offset - logical_offset) yield logical_offset, chunk_size, mapped_offset, mapped_size, self._base_layer diff --git a/volatility3/framework/plugins/linux/capabilities.py b/volatility3/framework/plugins/linux/capabilities.py index 518f52603..bfdb69aba 100644 --- a/volatility3/framework/plugins/linux/capabilities.py +++ b/volatility3/framework/plugins/linux/capabilities.py @@ -88,7 +88,7 @@ class Capabilities(plugins.PluginInterface): kernel_cap_last_cap = vmlinux.object_from_symbol(symbol_name="cap_last_cap") except exceptions.SymbolError: # It should be a kernel < 3.2 - return + return None vol2_last_cap = extensions.kernel_cap_struct.get_last_cap_value() if kernel_cap_last_cap > vol2_last_cap: diff --git a/volatility3/framework/plugins/linux/check_syscall.py b/volatility3/framework/plugins/linux/check_syscall.py index b1d2919f9..b6634d612 100644 --- a/volatility3/framework/plugins/linux/check_syscall.py +++ b/volatility3/framework/plugins/linux/check_syscall.py @@ -145,7 +145,7 @@ class Check_syscall(plugins.PluginInterface): table_info = self._get_table_info(vmlinux, "sys_call_table", ptr_sz) except exceptions.SymbolError: vollog.error("Unable to find the system call table. Exiting.") - return + return None tables = [(table_name, table_info)] diff --git a/volatility3/framework/plugins/linux/malfind.py b/volatility3/framework/plugins/linux/malfind.py index 8a21afc03..cf06ee0cc 100644 --- a/volatility3/framework/plugins/linux/malfind.py +++ b/volatility3/framework/plugins/linux/malfind.py @@ -44,7 +44,7 @@ class Malfind(interfaces.plugins.PluginInterface): proc_layer_name = task.add_process_layer() if not proc_layer_name: - return + return None proc_layer = self.context.layers[proc_layer_name] diff --git a/volatility3/framework/plugins/linux/sockstat.py b/volatility3/framework/plugins/linux/sockstat.py index fa67122ba..e9c98a227 100644 --- a/volatility3/framework/plugins/linux/sockstat.py +++ b/volatility3/framework/plugins/linux/sockstat.py @@ -147,7 +147,7 @@ class SockHandlers(interfaces.configuration.VersionableInterface): socket_filter["bpf_filter_type"] = "cBPF" if not sock_filter.has_member("prog") or not sock_filter.prog: - return + return None bpfprog = sock_filter.prog @@ -158,13 +158,13 @@ class SockHandlers(interfaces.configuration.VersionableInterface): return # cBPF filter except AttributeError: # kernel < 3.18.140, it's a cBPF filter - return + return None BPF_PROG_TYPE_SOCKET_FILTER = 1 # eBPF filter if bpfprog_type != BPF_PROG_TYPE_SOCKET_FILTER: socket_filter["bpf_filter_type"] = f"UNK({bpfprog_type})" vollog.warning(f"Unexpected BPF type {bpfprog_type} for a socket") - return + return None socket_filter["bpf_filter_type"] = "eBPF" if not bpfprog.has_member("aux") or not bpfprog.aux: @@ -329,17 +329,17 @@ class SockHandlers(interfaces.configuration.VersionableInterface): xdp_sock = sock.cast("xdp_sock") device = xdp_sock.dev if not device: - return + return None src_addr = utility.array_to_string(device.name) src_port = dst_addr = dst_port = None bpfprog = device.xdp_prog if not bpfprog: - return + return None if not bpfprog.has_member("aux") or not bpfprog.aux: - return + return None bpfprog_aux = bpfprog.aux if bpfprog_aux.has_member("id"): diff --git a/volatility3/framework/plugins/mac/check_sysctl.py b/volatility3/framework/plugins/mac/check_sysctl.py index 165aad436..4f64eaed8 100644 --- a/volatility3/framework/plugins/mac/check_sysctl.py +++ b/volatility3/framework/plugins/mac/check_sysctl.py @@ -69,7 +69,7 @@ class Check_sysctl(plugins.PluginInterface): try: sysctl = sysctl.oid_link.sle_next.dereference() except exceptions.InvalidAddressException: - return + return None while sysctl: try: diff --git a/volatility3/framework/plugins/mac/kevents.py b/volatility3/framework/plugins/mac/kevents.py index 3b996bc0a..2a8692b77 100644 --- a/volatility3/framework/plugins/mac/kevents.py +++ b/volatility3/framework/plugins/mac/kevents.py @@ -116,7 +116,7 @@ class Kevents(interfaces.plugins.PluginInterface): ) except exceptions.InvalidAddressException: - return + return None for klist in klist_array: for kn in mac.MacUtilities.walk_slist(klist, "kn_link"): @@ -140,7 +140,7 @@ class Kevents(interfaces.plugins.PluginInterface): try: p_klist = task.p_klist except exceptions.InvalidAddressException: - return + return None for kn in mac.MacUtilities.walk_slist(p_klist, "kn_link"): yield kn diff --git a/volatility3/framework/plugins/mac/lsmod.py b/volatility3/framework/plugins/mac/lsmod.py index 2979e374b..c6f57f889 100644 --- a/volatility3/framework/plugins/mac/lsmod.py +++ b/volatility3/framework/plugins/mac/lsmod.py @@ -75,7 +75,7 @@ class Lsmod(plugins.PluginInterface): try: kmod = kmod.next except exceptions.InvalidAddressException: - return + return None return # Generation finished def _generator(self): diff --git a/volatility3/framework/plugins/mac/malfind.py b/volatility3/framework/plugins/mac/malfind.py index 98b282e24..3094ada85 100644 --- a/volatility3/framework/plugins/mac/malfind.py +++ b/volatility3/framework/plugins/mac/malfind.py @@ -40,7 +40,7 @@ class Malfind(interfaces.plugins.PluginInterface): proc_layer_name = task.add_process_layer() if proc_layer_name is None: - return + return None proc_layer = self.context.layers[proc_layer_name] diff --git a/volatility3/framework/plugins/windows/cachedump.py b/volatility3/framework/plugins/windows/cachedump.py index a9b669add..6e667984a 100644 --- a/volatility3/framework/plugins/windows/cachedump.py +++ b/volatility3/framework/plugins/windows/cachedump.py @@ -108,12 +108,12 @@ class Cachedump(interfaces.plugins.PluginInterface): vollog.warning("Unable to locate SYSTEM hive") if sechive is None: vollog.warning("Unable to locate SECURITY hive") - return + return None bootkey = hashdump.Hashdump.get_bootkey(syshive) if not bootkey: vollog.warning("Unable to find bootkey") - return + return None kernel = self.context.modules[self.config["kernel"]] @@ -124,17 +124,17 @@ class Cachedump(interfaces.plugins.PluginInterface): lsakey = lsadump.Lsadump.get_lsa_key(sechive, bootkey, vista_or_later) if not lsakey: vollog.warning("Unable to find lsa key") - return + return None nlkm = self.get_nlkm(sechive, lsakey, vista_or_later) if not nlkm: vollog.warning("Unable to find nlkma key") - return + return None cache = hashdump.Hashdump.get_hive_key(sechive, "Cache") if not cache: vollog.warning("Unable to find cache key") - return + return None for cache_item in cache.get_values(): if cache_item.Name == "NL$Control": diff --git a/volatility3/framework/plugins/windows/callbacks.py b/volatility3/framework/plugins/windows/callbacks.py index 48b2e7c62..fcc333b9f 100644 --- a/volatility3/framework/plugins/windows/callbacks.py +++ b/volatility3/framework/plugins/windows/callbacks.py @@ -157,7 +157,7 @@ class Callbacks(interfaces.plugins.PluginInterface): ) if callback_count == 0: - return + return None fast_refs = ntkrnlmp.object( object_type="array", @@ -199,7 +199,7 @@ class Callbacks(interfaces.plugins.PluginInterface): ) if callback_count == 0: - return + return None callback_list = ntkrnlmp.object(object_type="_LIST_ENTRY", offset=symbol_offset) for callback in callback_list.to_list(full_type_name, "Link"): @@ -256,7 +256,7 @@ class Callbacks(interfaces.plugins.PluginInterface): symbol_status = "exists" vollog.debug(f"symbol {symbol_name} {symbol_status}.") - return + return None @classmethod def list_bugcheck_reason_callbacks( @@ -287,7 +287,7 @@ class Callbacks(interfaces.plugins.PluginInterface): ).address except exceptions.SymbolError: vollog.debug("Cannot find KeBugCheckReasonCallbackListHead") - return + return None full_type_name = ( callback_table_name + constants.BANG + "_KBUGCHECK_REASON_CALLBACK_RECORD" @@ -343,7 +343,7 @@ class Callbacks(interfaces.plugins.PluginInterface): list_offset = ntkrnlmp.get_symbol("KeBugCheckCallbackListHead").address except exceptions.SymbolError: vollog.debug("Cannot find KeBugCheckCallbackListHead") - return + return None full_type_name = ( callback_table_name + constants.BANG + "_KBUGCHECK_CALLBACK_RECORD" diff --git a/volatility3/framework/plugins/windows/dumpfiles.py b/volatility3/framework/plugins/windows/dumpfiles.py index 38d55d15d..dd82d897e 100755 --- a/volatility3/framework/plugins/windows/dumpfiles.py +++ b/volatility3/framework/plugins/windows/dumpfiles.py @@ -130,7 +130,7 @@ class DumpFiles(interfaces.plugins.PluginInterface): constants.LOGLEVEL_VVV, f"The file object at {file_obj.vol.offset:#x} is not a file on disk", ) - return + return None # Depending on the type of object (DataSection, ImageSection, SharedCacheMap) we may need to # read from the memory layer or the primary layer. diff --git a/volatility3/framework/plugins/windows/handles.py b/volatility3/framework/plugins/windows/handles.py index dd7c90860..ddd9cb78e 100644 --- a/volatility3/framework/plugins/windows/handles.py +++ b/volatility3/framework/plugins/windows/handles.py @@ -285,7 +285,7 @@ class Handles(interfaces.plugins.PluginInterface): count = 0x1000 / subtype.size if not self.context.layers[virtual].is_valid(offset): - return + return None table = ntkrnlmp.object( object_type="array", @@ -335,7 +335,7 @@ class Handles(interfaces.plugins.PluginInterface): constants.LOGLEVEL_VVV, "Handle table parsing was aborted due to an invalid address exception", ) - return + return None for handle_table_entry in self._make_handle_array(TableCode, table_levels): yield handle_table_entry diff --git a/volatility3/framework/plugins/windows/lsadump.py b/volatility3/framework/plugins/windows/lsadump.py index 12589b07e..da8dee325 100644 --- a/volatility3/framework/plugins/windows/lsadump.py +++ b/volatility3/framework/plugins/windows/lsadump.py @@ -168,16 +168,16 @@ class Lsadump(interfaces.plugins.PluginInterface): lsakey = self.get_lsa_key(sechive, bootkey, vista_or_later) if not bootkey: vollog.warning("Unable to find bootkey") - return + return None if not lsakey: vollog.warning("Unable to find lsa key") - return + return None secrets_key = hashdump.Hashdump.get_hive_key(sechive, "Policy\\Secrets") if not secrets_key: vollog.warning("Unable to find secrets key") - return + return None for key in secrets_key.get_subkeys(): sec_val_key = hashdump.Hashdump.get_hive_key( diff --git a/volatility3/framework/plugins/windows/malfind.py b/volatility3/framework/plugins/windows/malfind.py index 424925955..6ed078996 100644 --- a/volatility3/framework/plugins/windows/malfind.py +++ b/volatility3/framework/plugins/windows/malfind.py @@ -110,7 +110,7 @@ class Malfind(interfaces.plugins.PluginInterface): proc_id, excp.invalid_address, excp.layer_name ) ) - return + return None proc_layer = context.layers[proc_layer_name] diff --git a/volatility3/framework/plugins/windows/netstat.py b/volatility3/framework/plugins/windows/netstat.py index d3ce3fd2e..24eb02018 100644 --- a/volatility3/framework/plugins/windows/netstat.py +++ b/volatility3/framework/plugins/windows/netstat.py @@ -154,7 +154,7 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): ) else: # invalid argument. - return + return None vollog.debug(f"Current Port: {port}") # the given port serves as a shifted index into the port pool lists @@ -175,7 +175,7 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): assignment = inpa.InPaBigPoolBase.Assignments[truncated_port] if not assignment: - return + return None # the value within assignment.Entry is a) masked and b) points inside of the network object # first decode the pointer diff --git a/volatility3/framework/plugins/windows/pstree.py b/volatility3/framework/plugins/windows/pstree.py index 5c78d1682..a39fe7485 100644 --- a/volatility3/framework/plugins/windows/pstree.py +++ b/volatility3/framework/plugins/windows/pstree.py @@ -108,13 +108,13 @@ class PsTree(interfaces.plugins.PluginInterface): def yield_processes(pid, descendant: bool = False): if pid in process_pids: vollog.debug(f"Pid cycle: already processed pid {pid}") - return + return None process_pids.add(pid) if pid not in self._ancestors and not descendant: vollog.debug(f"Pid cycle: pid {pid} not in filtered tree") - return + return None proc, offset = self._processes[pid] row = ( diff --git a/volatility3/framework/plugins/windows/registry/hivelist.py b/volatility3/framework/plugins/windows/registry/hivelist.py index 91798de40..1cc76dad6 100644 --- a/volatility3/framework/plugins/windows/registry/hivelist.py +++ b/volatility3/framework/plugins/windows/registry/hivelist.py @@ -30,7 +30,7 @@ class HiveGenerator: ): if not hive.is_valid(): self._invalid = hive.vol.offset - return + return None yield hive @property diff --git a/volatility3/framework/plugins/windows/registry/printkey.py b/volatility3/framework/plugins/windows/registry/printkey.py index f66e55f4b..e248c19bc 100644 --- a/volatility3/framework/plugins/windows/registry/printkey.py +++ b/volatility3/framework/plugins/windows/registry/printkey.py @@ -74,7 +74,7 @@ class PrintKey(interfaces.plugins.PluginInterface): node_path = [hive.get_node(hive.root_cell_offset)] if not isinstance(node_path, list) or len(node_path) < 1: vollog.warning("Hive walker was not passed a valid node_path (or None)") - return + return None node = node_path[-1] key_path_items = [hive] + node_path[1:] key_path = "\\".join([k.get_name() for k in key_path_items]) diff --git a/volatility3/framework/plugins/windows/registry/userassist.py b/volatility3/framework/plugins/windows/registry/userassist.py index f90724f66..70c75b50b 100644 --- a/volatility3/framework/plugins/windows/registry/userassist.py +++ b/volatility3/framework/plugins/windows/registry/userassist.py @@ -173,11 +173,11 @@ class UserAssist(interfaces.plugins.PluginInterface): if not userassist_node_path: vollog.warning("list_userassist did not find a valid node_path (or None)") - return + return None if not isinstance(userassist_node_path, list): vollog.warning("userassist_node_path did not return a list as expected") - return + return None userassist_node = userassist_node_path[-1] # iterate through the GUIDs under the userassist key for guidkey in userassist_node.get_subkeys(): diff --git a/volatility3/framework/plugins/windows/skeleton_key_check.py b/volatility3/framework/plugins/windows/skeleton_key_check.py index b697774cb..d321c2cc0 100644 --- a/volatility3/framework/plugins/windows/skeleton_key_check.py +++ b/volatility3/framework/plugins/windows/skeleton_key_check.py @@ -601,21 +601,21 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface): if not symbols.symbol_table_is_64bit(self.context, kernel.symbol_table_name): vollog.info("This plugin only supports 64bit Windows memory samples") - return + return None lsass_proc, proc_layer_name = self._find_lsass_proc(procs) if not lsass_proc: vollog.info( "Unable to find a valid lsass.exe process in the process list. This should never happen. Analysis cannot proceed." ) - return + return None cryptdll_base, cryptdll_size = self._find_cryptdll(lsass_proc) if not cryptdll_base: vollog.info( "Unable to find the location of cryptdll.dll inside of lsass.exe. Analysis cannot proceed." ) - return + return None # the custom type information from binary analysis cryptdll_types = self._get_cryptdll_types( @@ -649,7 +649,7 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface): vollog.info( "Unable to find CSystems inside of cryptdll.dll. Analysis cannot proceed." ) - return + return None for csystem in csystems: if not self.context.layers[proc_layer_name].is_valid( diff --git a/volatility3/framework/renderers/format_hints.py b/volatility3/framework/renderers/format_hints.py index 6ec9ebab9..6120b77c9 100644 --- a/volatility3/framework/renderers/format_hints.py +++ b/volatility3/framework/renderers/format_hints.py @@ -59,7 +59,8 @@ class MultiTypeData(bytes): def __eq__(self, other): return ( - super(self) == super(other) + isinstance(other, self.__class__) + and super() == super(self.__class__, other) and self.converted_int == other.converted_int and self.encoding == other.encoding and self.split_nulls == other.split_nulls diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index f96302684..3d424dedd 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -243,17 +243,17 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): ): # task.files can be null if not task.files: - return + return None fd_table = task.files.get_fds() if fd_table == 0: - return + return None max_fds = task.files.get_max_fds() # corruption check if max_fds > 500000: - return + return None file_type = symbol_table + constants.BANG + "file" @@ -378,7 +378,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): """ if not addr: - return + return None type_dec = vmlinux.get_type(type_name) member_offset = type_dec.relative_child_offset(member_name) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 3fb772135..d1edfdfe0 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -319,7 +319,7 @@ class maple_tree(objects.StructType): vollog.warning( f"The mte {hex(maple_tree_entry)} has all ready been seen, no further results will be produced for this node." ) - return + return None else: seen.add(maple_tree_entry) # check if we have exceeded the expected depth of this maple tree. @@ -402,7 +402,7 @@ class mm_struct(objects.StructType): "get_mmap_iter called on mm_struct where no mmap member exists." ) if not self.mmap: - return + return None yield self.mmap seen = {self.mmap.vol.offset} @@ -723,7 +723,7 @@ class list_head(objects.StructType, collections.abc.Iterable): try: link = getattr(self, direction).dereference() except exceptions.InvalidAddressException: - return + return None if not sentinel: yield self._context.object( symbol_type, layer, offset=self.vol.offset - relative_offset @@ -1218,7 +1218,7 @@ class sock(objects.StructType): return self.sk_socket.get_inode() def get_protocol(self): - return + return None def get_state(self): # Return the generic socket state @@ -1230,13 +1230,13 @@ class sock(objects.StructType): class unix_sock(objects.StructType): def get_name(self): if not self.addr: - return + return None sockaddr_un = self.addr.name.cast("sockaddr_un") saddr = str(utility.array_to_string(sockaddr_un.sun_path)) return saddr def get_protocol(self): - return + return None def get_state(self): """Return a string representing the sock state.""" @@ -1295,7 +1295,7 @@ class inet_sock(objects.StructType): elif hasattr(sk_common, "skc_dport"): dport_le = sk_common.skc_dport else: - return + return None return socket_module.htons(dport_le) def get_src_addr(self): @@ -1313,7 +1313,7 @@ class inet_sock(objects.StructType): addr_size = 16 saddr = self.pinet6.saddr else: - return + return None parent_layer = self._context.layers[self.vol.layer_name] try: addr_bytes = parent_layer.read(saddr.vol.offset, addr_size) @@ -1321,7 +1321,7 @@ class inet_sock(objects.StructType): vollog.debug( f"Unable to read socket src address from {saddr.vol.offset:#x}" ) - return + return None return socket_module.inet_ntop(family, addr_bytes) def get_dst_addr(self): @@ -1342,7 +1342,7 @@ class inet_sock(objects.StructType): daddr = sk_common.skc_v6_daddr addr_size = 16 else: - return + return None parent_layer = self._context.layers[self.vol.layer_name] try: addr_bytes = parent_layer.read(daddr.vol.offset, addr_size) @@ -1350,7 +1350,7 @@ class inet_sock(objects.StructType): vollog.debug( f"Unable to read socket dst address from {daddr.vol.offset:#x}" ) - return + return None return socket_module.inet_ntop(family, addr_bytes) @@ -1388,7 +1388,7 @@ class netlink_sock(objects.StructType): class vsock_sock(objects.StructType): def get_protocol(self): # The protocol should always be 0 for vsocks - return + return None def get_state(self): # Return the generic socket state @@ -1399,7 +1399,7 @@ class packet_sock(objects.StructType): def get_protocol(self): eth_proto = socket_module.htons(self.num) if eth_proto == 0: - return + return None elif eth_proto in ETH_PROTOCOLS: return ETH_PROTOCOLS[eth_proto] else: @@ -1425,7 +1425,7 @@ class bt_sock(objects.StructType): class xdp_sock(objects.StructType): def get_protocol(self): # The protocol should always be 0 for xdp_sock - return + return None def get_state(self): # xdp_sock.state is an enum diff --git a/volatility3/framework/symbols/linux/extensions/elf.py b/volatility3/framework/symbols/linux/extensions/elf.py index 416a7e4d2..e3034d643 100644 --- a/volatility3/framework/symbols/linux/extensions/elf.py +++ b/volatility3/framework/symbols/linux/extensions/elf.py @@ -171,7 +171,7 @@ class elf(objects.StructType): self._find_symbols() if self._cached_symtab is None: - return + return None symtab_arr = self._context.object( self.get_symbol_table_name() + constants.BANG + "array", diff --git a/volatility3/framework/symbols/mac/__init__.py b/volatility3/framework/symbols/mac/__init__.py index 56ac96633..bc98e5bdc 100644 --- a/volatility3/framework/symbols/mac/__init__.py +++ b/volatility3/framework/symbols/mac/__init__.py @@ -169,7 +169,7 @@ class MacUtilities(interfaces.configuration.VersionableInterface): try: table_addr = task.p_fd.fd_ofiles.dereference() except exceptions.InvalidAddressException: - return + return None fds = objects.utility.array_of_pointers( table_addr, count=num_fds, subtype=file_type, context=context @@ -204,7 +204,7 @@ class MacUtilities(interfaces.configuration.VersionableInterface): try: current = queue.member(attr=list_head_member) except exceptions.InvalidAddressException: - return + return None while current: if current.vol.offset in seen: diff --git a/volatility3/framework/symbols/mac/extensions/__init__.py b/volatility3/framework/symbols/mac/extensions/__init__.py index c89b527e6..bf0b3d775 100644 --- a/volatility3/framework/symbols/mac/extensions/__init__.py +++ b/volatility3/framework/symbols/mac/extensions/__init__.py @@ -50,7 +50,7 @@ class proc(generic.GenericIntelProcess): task = self.get_task() current_map = task.map.hdr.links.next except exceptions.InvalidAddressException: - return + return None seen: Set[int] = set() @@ -138,13 +138,13 @@ class vm_map_object(objects.StructType): class vnode(objects.StructType): def _do_calc_path(self, ret, vnodeobj, vname): if vnodeobj is None: - return + return None if vname: try: ret.append(utility.pointer_to_string(vname, 255)) except exceptions.InvalidAddressException: - return + return None if int(vnodeobj.v_flag) & 0x000001 != 0 and int(vnodeobj.v_mount) != 0: if int(vnodeobj.v_mount.mnt_vnodecovered) != 0: @@ -158,7 +158,7 @@ class vnode(objects.StructType): parent = vnodeobj.v_parent parent_name = parent.v_name except exceptions.InvalidAddressException: - return + return None self._do_calc_path(ret, parent, parent_name) @@ -502,7 +502,7 @@ class queue_entry(objects.StructType): yielded = yielded + 1 if yielded == max_size: - return + return None n = ( getattr(n.member(attr=member_name), attr) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index ba00a4053..d435851d7 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -91,7 +91,7 @@ class MMVAD_SHORT(objects.StructType): if vad_address in visited: vollog.log(constants.LOGLEVEL_VVV, "VAD node already seen!") - return + return None visited.add(vad_address) tag = self.get_tag() @@ -111,7 +111,7 @@ class MMVAD_SHORT(objects.StructType): constants.LOGLEVEL_VVV, f"Skipping VAD at {self.vol.offset} depth {depth} with tag {tag}", ) - return + return None if target: vad_object = self.cast(target) @@ -665,7 +665,7 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): ): yield entry except exceptions.InvalidAddressException: - return + return None def init_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: """Generator for DLLs in the order that they were initialized""" @@ -678,7 +678,7 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): ): yield entry except exceptions.InvalidAddressException: - return + return None def mem_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: """Generator for DLLs in the order that they appear in memory""" @@ -691,7 +691,7 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): ): yield entry except exceptions.InvalidAddressException: - return + return None def get_handle_count(self): try: @@ -841,11 +841,11 @@ class LIST_ENTRY(objects.StructType, collections.abc.Iterable): try: is_valid = trans_layer.is_valid(self.vol.offset) if not is_valid: - return + return None link = getattr(self, direction).dereference() except exceptions.InvalidAddressException: - return + return None if not sentinel: yield self._context.object( @@ -860,7 +860,7 @@ class LIST_ENTRY(objects.StructType, collections.abc.Iterable): obj_offset = link.vol.offset - relative_offset if not trans_layer.is_valid(obj_offset): - return + return None obj = self._context.object( symbol_type, @@ -875,7 +875,7 @@ class LIST_ENTRY(objects.StructType, collections.abc.Iterable): try: link = getattr(link, direction).dereference() except exceptions.InvalidAddressException: - return + return None def __iter__(self) -> Iterator[interfaces.objects.ObjectInterface]: return self.to_list(self.vol.parent.vol.type_name, self.vol.member_name) @@ -905,10 +905,10 @@ class TOKEN(objects.StructType): sid = sid_and_attr.Sid.dereference().cast("_SID") # catch invalid pointers (UserAndGroupCount is too high) if sid is None: - return + return None # this mimics the windows API IsValidSid if sid.Revision & 0xF != 1 or sid.SubAuthorityCount > 15: - return + return None id_auth = "" for i in sid.IdentifierAuthority.Value: id_auth = i diff --git a/volatility3/framework/symbols/windows/extensions/registry.py b/volatility3/framework/symbols/windows/extensions/registry.py index fbd3ead8e..51be0841c 100644 --- a/volatility3/framework/symbols/windows/extensions/registry.py +++ b/volatility3/framework/symbols/windows/extensions/registry.py @@ -162,7 +162,7 @@ class CM_KEY_NODE(objects.StructType): try: signature = node.cast("string", max_length=2, encoding="latin-1") except (exceptions.InvalidAddressException, RegistryFormatException): - return + return None listjump = None if signature == "ri": @@ -220,7 +220,7 @@ class CM_KEY_NODE(objects.StructType): yield node except (exceptions.InvalidAddressException, RegistryFormatException) as excp: vollog.debug(f"Invalid address in get_values iteration: {excp}") - return + return None def get_name(self) -> interfaces.objects.ObjectInterface: """Gets the name for the current key node""" diff --git a/volatility3/framework/symbols/windows/extensions/services.py b/volatility3/framework/symbols/windows/extensions/services.py index 00fb1cc4e..e14de761d 100644 --- a/volatility3/framework/symbols/windows/extensions/services.py +++ b/volatility3/framework/symbols/windows/extensions/services.py @@ -110,7 +110,7 @@ class SERVICE_RECORD(objects.StructType): yield rec rec = rec.ServiceList.Blink.dereference() except exceptions.InvalidAddressException: - return + return None class SERVICE_HEADER(objects.StructType): diff --git a/volatility3/framework/symbols/windows/pdbutil.py b/volatility3/framework/symbols/windows/pdbutil.py index bdcf25fa1..3816312cd 100644 --- a/volatility3/framework/symbols/windows/pdbutil.py +++ b/volatility3/framework/symbols/windows/pdbutil.py @@ -2,7 +2,6 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # -import binascii import json import logging import lzma From 8b6ab44310e3e39cd3fa06321dd208c2e88d5cd8 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 26 Nov 2023 12:30:06 +0000 Subject: [PATCH 71/83] Core: Fix array_of_pointers to act only on those pointers --- volatility3/framework/objects/utility.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/objects/utility.py b/volatility3/framework/objects/utility.py index 177074cd7..0292608c1 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -44,8 +44,9 @@ def array_of_pointers( raise TypeError( "Subtype must be a valid template (or string name of an object template)" ) + # We have to clone the pointer class, or we'll be defining the pointer subtype for all future pointers subtype_pointer = context.symbol_space.get_type( symbol_table + constants.BANG + "pointer" - ) + ).clone() subtype_pointer.update_vol(subtype=subtype) return array.cast("array", count=count, subtype=subtype_pointer) From 8d5877e904834811b215f33cedec384c6f25678e Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Mon, 27 Nov 2023 00:59:56 +0000 Subject: [PATCH 72/83] Simplify attribute object accesses This is a very small percentage slower, for some reason, than the previous mechanism, probably overhead from object creation/member access. However, it vastly simplifies the code and makes better use of the volatility object model. --- .../framework/plugins/windows/mftscan.py | 48 +++++++------------ .../framework/symbols/windows/mft.json | 6 +-- 2 files changed, 19 insertions(+), 35 deletions(-) diff --git a/volatility3/framework/plugins/windows/mftscan.py b/volatility3/framework/plugins/windows/mftscan.py index 87416d274..4612077b8 100644 --- a/volatility3/framework/plugins/windows/mftscan.py +++ b/volatility3/framework/plugins/windows/mftscan.py @@ -67,9 +67,8 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): ) # We will update this on each pass in the next loop and use it as the new offset. attr_base_offset = mft_record.FirstAttrOffset - - attr_header = self.context.object( - header_object, + attr = self.context.object( + attribute_object, offset=offset + attr_base_offset, layer_name=layer.name, ) @@ -77,17 +76,8 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): # There is no field that has a count of Attributes # Keep Attempting to read attributes until we get an invalid attr_header.AttrType - while attr_header.AttrType.is_valid_choice: - vollog.debug(f"Attr Type: {attr_header.AttrType.lookup()}") - - # Offset past the headers to the attribute data - attr_data_offset = ( - offset - + attr_base_offset - + self.context.symbol_space.get_type( - attribute_object - ).relative_child_offset("Attr_Data") - ) + while attr.Attr_Header.AttrType.is_valid_choice: + vollog.debug(f"Attr Type: {attr.Attr_Header.AttrType.lookup()}") # MFT Flags determine the file type or dir # If we don't have a valid enum, coerce to hex so we can keep the record @@ -97,19 +87,16 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): mft_flag = hex(mft_record.Flags) # Standard Information Attribute - if attr_header.AttrType.lookup() == "STANDARD_INFORMATION": - attr_data = self.context.object( - si_object, offset=attr_data_offset, layer_name=layer.name - ) - + if attr.Attr_Header.AttrType.lookup() == "STANDARD_INFORMATION": + attr_data = attr.Attr_Data.cast(si_object) yield 0, ( - format_hints.Hex(attr_data_offset), + format_hints.Hex(attr_data.vol.offset), mft_record.get_signature(), mft_record.RecordNumber, mft_record.LinkCount, mft_flag, renderers.NotApplicableValue(), - attr_header.AttrType.lookup(), + attr.Attr_Header.AttrType.lookup(), conversion.wintime_to_datetime(attr_data.CreationTime), conversion.wintime_to_datetime(attr_data.ModifiedTime), conversion.wintime_to_datetime(attr_data.UpdatedTime), @@ -118,10 +105,8 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): ) # File Name Attribute - if attr_header.AttrType.lookup() == "FILE_NAME": - attr_data = self.context.object( - fn_object, offset=attr_data_offset, layer_name=layer.name - ) + if attr.Attr_Header.AttrType.lookup() == "FILE_NAME": + attr_data = attr.Attr_Data.cast(fn_object) file_name = attr_data.get_full_name() # If we don't have a valid enum, coerce to hex so we can keep the record @@ -131,13 +116,13 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): permissions = hex(attr_data.Flags) yield 1, ( - format_hints.Hex(attr_data_offset), + format_hints.Hex(attr_data.vol.offset), mft_record.get_signature(), mft_record.RecordNumber, mft_record.LinkCount, mft_flag, permissions, - attr_header.AttrType.lookup(), + attr.Attr_Header.AttrType.lookup(), conversion.wintime_to_datetime(attr_data.CreationTime), conversion.wintime_to_datetime(attr_data.ModifiedTime), conversion.wintime_to_datetime(attr_data.UpdatedTime), @@ -146,14 +131,13 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): ) # If there's no advancement the loop will never end, so break it now - if attr_header.Length == 0: + if attr.Attr_Header.Length == 0: break # Update the base offset to point to the next attribute - attr_base_offset += attr_header.Length - # Get the next attribute - attr_header = self.context.object( - header_object, + attr_base_offset += attr.Attr_Header.Length + attr = self.context.object( + attribute_object, offset=offset + attr_base_offset, layer_name=layer.name, ) diff --git a/volatility3/framework/symbols/windows/mft.json b/volatility3/framework/symbols/windows/mft.json index e5de8f3fa..ecbf1d2d7 100644 --- a/volatility3/framework/symbols/windows/mft.json +++ b/volatility3/framework/symbols/windows/mft.json @@ -230,21 +230,21 @@ "offset": 0, "type": { "kind": "struct", - "name": "mft!ATTR_HEADER" + "name": "ATTR_HEADER" } }, "Resident_Header": { "offset": 16, "type": { "kind": "struct", - "name": "mft!RESIDENT_HEADER" + "name": "RESIDENT_HEADER" } }, "Attr_Data": { "offset": 24, "type": { "kind": "struct", - "name": "mft!ATTR_HEADER" + "name": "ATTR_HEADER" } } }, From 7624c494e81fddfe1f4ae1b754fae8fbecb76ee2 Mon Sep 17 00:00:00 2001 From: k1nd0ne Date: Tue, 28 Nov 2023 13:57:02 +0100 Subject: [PATCH 73/83] Simplify attribute object accesses like #1049 + custom class (MFTAttribute) --- .../framework/plugins/windows/mftscan.py | 71 ++++++------------- .../symbols/windows/extensions/mft.py | 22 ++++++ 2 files changed, 44 insertions(+), 49 deletions(-) diff --git a/volatility3/framework/plugins/windows/mftscan.py b/volatility3/framework/plugins/windows/mftscan.py index 397ce75bc..623497638 100644 --- a/volatility3/framework/plugins/windows/mftscan.py +++ b/volatility3/framework/plugins/windows/mftscan.py @@ -224,12 +224,12 @@ class ADS(interfaces.plugins.PluginInterface): config_path=self.config_path, sub_path="windows", filename="mft", - class_types={"MFT_ENTRY": mft.MFTEntry,"FILE_NAME_ENTRY": mft.MFTFileName}, + class_types={"MFT_ENTRY": mft.MFTEntry,"FILE_NAME_ENTRY": mft.MFTFileName, "ATTRIBUTE": mft.MFTAttribute}, ) # get each of the individual Field Sets mft_object = symbol_table + constants.BANG + "MFT_ENTRY" - header_object = symbol_table + constants.BANG + "ATTR_HEADER" + attribute_object = symbol_table + constants.BANG + "ATTRIBUTE" fn_object = symbol_table + constants.BANG + "FILE_NAME_ENTRY" # Scan the layer for Raw MFT records and parse the fields @@ -243,58 +243,32 @@ class ADS(interfaces.plugins.PluginInterface): # We will update this on each pass in the next loop and use it as the new offset. attr_base_offset = mft_record.FirstAttrOffset - attr_header = self.context.object( - header_object, + attr = self.context.object( + attribute_object, offset=offset + attr_base_offset, layer_name=layer.name, ) # There is no field that has a count of Attributes - # Keep Attempting to read attributes until we get an invalid attr_header.AttrType + # Keep Attempting to read attributes until we get an invalid attr.AttrType file_name = renderers.NotAvailableValue is_ads = False - # The First $DATA Attr is the 'principal' file itself not the ADS - while attr_header.AttrType.is_valid_choice: - # Offset past the headers to the attribute data - attr_data_offset = ( - offset - + attr_base_offset - + self.context.symbol_space.get_type( - header_object - ).size - ) + + # The First $DATA Attr is the 'principal' file itself not the ADS + while attr.Attr_Header.AttrType.is_valid_choice: - if attr_header.AttrType.lookup() == "FILE_NAME": - attr_data = self.context.object( - fn_object, offset=attr_data_offset, layer_name=layer.name - ) + if attr.Attr_Header.AttrType.lookup() == "FILE_NAME": + attr_data = attr.Attr_Data.cast(fn_object) file_name = attr_data.get_full_name() - - # DATA Attribute (can be ADS or not) - if attr_header.AttrType.lookup() == "DATA": + + if attr.Attr_Header.AttrType.lookup() == "DATA": if is_ads: - if not attr_header.NonResidentFlag: + if not attr.Attr_Header.NonResidentFlag: # Resident files are the most interesting. - if attr_header.NameLength > 0: - attr_name_offset = ( - offset - + attr_base_offset - + attr_header.NameOffset - ) + if attr.Attr_Header.NameLength > 0: - ads_name = self._context.layers[layer.name].read( - attr_name_offset, attr_header.NameLength*2 , pad=True - ).decode('utf-16') - - attr_content_offset = ( - offset - + attr_base_offset - + attr_header.ContentOffset - ) - - content = self._context.layers[layer.name].read( - attr_content_offset, attr_header.ContentLength , pad=True - ) + ads_name = attr.get_resident_filename() + content = attr.get_resident_filecontent() # Preparing for Disassembly architecture = layer.metadata.get("architecture", None) @@ -303,10 +277,10 @@ class ADS(interfaces.plugins.PluginInterface): ) yield 0, ( - format_hints.Hex(attr_data_offset), + format_hints.Hex(attr_data.vol.offset), mft_record.get_signature(), mft_record.RecordNumber, - attr_header.AttrType.lookup(), + attr.Attr_Header.AttrType.lookup(), file_name, ads_name, format_hints.HexBytes(content), @@ -317,18 +291,17 @@ class ADS(interfaces.plugins.PluginInterface): # If there's no advancement the loop will never end, so break it now - if attr_header.Length == 0: + if attr.Attr_Header.Length == 0: break # Update the base offset to point to the next attribute - attr_base_offset += attr_header.Length + attr_base_offset += attr.Attr_Header.Length # Get the next attribute - attr_header = self.context.object( - header_object, + attr = self.context.object( + attribute_object, offset=offset + attr_base_offset, layer_name=layer.name, ) - def run(self): return renderers.TreeGrid( [ diff --git a/volatility3/framework/symbols/windows/extensions/mft.py b/volatility3/framework/symbols/windows/extensions/mft.py index 17b6c8325..1b5d5fce4 100644 --- a/volatility3/framework/symbols/windows/extensions/mft.py +++ b/volatility3/framework/symbols/windows/extensions/mft.py @@ -21,3 +21,25 @@ class MFTFileName(objects.StructType): "string", encoding="utf16", max_length=self.NameLength * 2, errors="replace" ) return output + + +class MFTAttribute(objects.StructType): + """This represents an MFT ATTRIBUTE""" + + def get_resident_filename(self) -> str: + # To get the resident name, we jump to relative name offset and read name length * 2 bytes of data + layer = self._context.layers[self.vol.layer_name] + attr_name_offset = self.vol.offset + self.Attr_Header.NameOffset + + return self._context.layers[layer.name].read( + attr_name_offset, self.Attr_Header.NameLength*2 , pad=True + ).decode('utf-16') + + def get_resident_filecontent(self) -> bytes: + # To get the resident content, we jump to relative content offset and read name length * 2 bytes of data + layer = self._context.layers[self.vol.layer_name] + attr_content_offset = self.vol.offset + self.Attr_Header.ContentOffset + + return self._context.layers[layer.name].read( + attr_content_offset, self.Attr_Header.ContentLength , pad=True + ) From c610497fa04de41042104fdefbfa243c0bcf76a9 Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 29 Nov 2023 09:28:34 +0000 Subject: [PATCH 74/83] Windows: update vadyarascan to use generic yarascan requirements --- .../framework/plugins/windows/vadyarascan.py | 39 ++++++------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/volatility3/framework/plugins/windows/vadyarascan.py b/volatility3/framework/plugins/windows/vadyarascan.py index 4b30a9d8b..d795818e9 100644 --- a/volatility3/framework/plugins/windows/vadyarascan.py +++ b/volatility3/framework/plugins/windows/vadyarascan.py @@ -18,47 +18,26 @@ class VadYaraScan(interfaces.plugins.PluginInterface): """Scans all the Virtual Address Descriptor memory maps using yara.""" _required_framework_version = (2, 4, 0) - _version = (1, 0, 0) + _version = (1, 0, 1) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: - return [ + # create a list of requirements for vadyarascan + vadyarascan_requirements = [ requirements.ModuleRequirement( name="kernel", description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.BooleanRequirement( - name="wide", - description="Match wide (unicode) strings", - default=False, - optional=True, - ), - requirements.StringRequirement( - name="yara_rules", description="Yara rules (as a string)", optional=True - ), - requirements.URIRequirement( - name="yara_file", description="Yara rules (as a file)", optional=True - ), - # This additional requirement is to follow suit with upstream, who feel that compiled rules could potentially be used to execute malicious code - # As such, there's a separate option to run compiled files, as happened with yara-3.9 and later - requirements.URIRequirement( - name="yara_compiled_file", - description="Yara compiled rules (as a file)", - optional=True, - ), - requirements.IntRequirement( - name="max_size", - default=0x40000000, - description="Set the maximum size (default is 1GB)", - optional=True, - ), requirements.PluginRequirement( name="pslist", plugin=pslist.PsList, version=(2, 0, 0) ), requirements.VersionRequirement( name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) ), + requirements.PluginRequirement( + name="yarascan", plugin=yarascan.YaraScan, version=(1, 2, 0) + ), requirements.ListRequirement( name="pid", element_type=int, @@ -67,6 +46,12 @@ class VadYaraScan(interfaces.plugins.PluginInterface): ), ] + # get base yarascan requirements for command line options + yarascan_requirements = yarascan.YaraScan.get_yarascan_option_requirements() + + # return the combined requirements + return yarascan_requirements + vadyarascan_requirements + def _generator(self): kernel = self.context.modules[self.config["kernel"]] From 39144ff45fc89bb3a22325afe996cf1ba2dc4584 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 29 Nov 2023 12:51:08 +0100 Subject: [PATCH 75/83] type hint get_symbols_names_and_addresses --- volatility3/framework/symbols/linux/extensions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index f5c71eb0e..1ec9c7416 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -141,7 +141,7 @@ class module(generic.GenericIntelProcess): for sym in syms: yield sym - def get_symbols_names_and_addresses(self): + def get_symbols_names_and_addresses(self) -> Tuple[str, int]: """Get names and addresses for each symbol of the module Yields: From b20643d5e01d8d8771937071ab433926e51fc18d Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Wed, 29 Nov 2023 12:03:43 +0000 Subject: [PATCH 76/83] Linux: Tidy up the elf symbol table name --- volatility3/framework/symbols/linux/extensions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 1824a5b76..c9f7f50da 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -107,7 +107,7 @@ class module(generic.GenericIntelProcess): def get_elf_table_name(self): elf_table_name = intermed.IntermediateSymbolTable.create( self._context, - "config_name_elf_symbol_table", + "elf_symbol_table", "linux", "elf", native_types=None, From 099403d07ce0651119d679accb79c8d0ff1253db Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 29 Nov 2023 20:47:14 +0000 Subject: [PATCH 77/83] Linux: fix bug with iomem plugin where an absolute address is used to make an object but the absolute flag is not set --- volatility3/framework/plugins/linux/iomem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/iomem.py b/volatility3/framework/plugins/linux/iomem.py index 785405ef3..6732084db 100644 --- a/volatility3/framework/plugins/linux/iomem.py +++ b/volatility3/framework/plugins/linux/iomem.py @@ -16,7 +16,7 @@ class IOMem(interfaces.plugins.PluginInterface): """Generates an output similar to /proc/iomem on a running system.""" _required_framework_version = (2, 0, 0) - _version = (1, 0, 0) + _version = (1, 0, 1) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -53,7 +53,7 @@ class IOMem(interfaces.plugins.PluginInterface): # create the resource object with protection against memory smear try: - resource = vmlinux.object("resource", resource_offset) + resource = vmlinux.object("resource", resource_offset, absolute=True) except exceptions.InvalidAddressException: vollog.warning( f"Unable to create resource object at {resource_offset:#x}. This resource, " From ed98d453f676072dacac9f3ae343e54bcb17bd2e Mon Sep 17 00:00:00 2001 From: k1nd0ne Date: Thu, 30 Nov 2023 11:50:04 +0100 Subject: [PATCH 78/83] Changing requirements + formating --- requirements.txt | 4 +- volatility3/framework/layers/cloudstorage.py | 56 ++++++++++---------- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7e1c28595..c05546466 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,5 +18,5 @@ pycryptodome leechcorepyc>=2.4.0 # This is required for memory analysis on a Amazon/MinIO S3 and Google Cloud object storage -gcsfs>=2023.6.0 -s3fs>=2023.6.0 \ No newline at end of file +gcsfs>=2023.1.0 +s3fs>=2023.1.0 \ No newline at end of file diff --git a/volatility3/framework/layers/cloudstorage.py b/volatility3/framework/layers/cloudstorage.py index ce6cdb327..41afa324d 100644 --- a/volatility3/framework/layers/cloudstorage.py +++ b/volatility3/framework/layers/cloudstorage.py @@ -23,35 +23,33 @@ from volatility3.framework.layers import resources vollog = logging.getLogger(__file__) -class S3FileSystemHandler(resources.VolatilityHandler): - if HAS_S3FS: - @classmethod - def non_cached_schemes(cls) -> List[str]: - return ["s3"] +if HAS_S3FS: - @staticmethod - def default_open(req: urllib.request.Request) -> Optional[Any]: - """Handles the request if it's the s3 scheme.""" - if req.type == "s3": - object_uri = "://".join(req.full_url.split("://")[1:]) - return s3fs.S3FileSystem().open(object_uri) - return None - else: - raise exceptions.LayerException("s3 requirement is missing.") + class S3FileSystemHandler(resources.VolatilityHandler): + + @classmethod + def non_cached_schemes(cls) -> List[str]: + return ["s3"] + @staticmethod + def default_open(req: urllib.request.Request) -> Optional[Any]: + """Handles the request if it's the s3 scheme.""" + if req.type == "s3": + object_uri = "://".join(req.full_url.split("://")[1:]) + return s3fs.S3FileSystem().open(object_uri) + return None -class GSFileSystemHandler(resources.VolatilityHandler): - if HAS_GCSFS: - @classmethod - def non_cached_schemes(cls) -> List[str]: - return ["gs"] - - @staticmethod - def default_open(req: urllib.request.Request) -> Optional[Any]: - """Handles the request if it's the gs scheme.""" - if req.type == "gs": - object_uri = "://".join(req.full_url.split("://")[1:]) - return gcsfs.GCSFileSystem().open(object_uri) - return None - else: - raise exceptions.LayerException("gcsfs requirement is missing.") \ No newline at end of file +if HAS_GCSFS: + + class GSFileSystemHandler(resources.VolatilityHandler): + @classmethod + def non_cached_schemes(cls) -> List[str]: + return ["gs"] + + @staticmethod + def default_open(req: urllib.request.Request) -> Optional[Any]: + """Handles the request if it's the gs scheme.""" + if req.type == "gs": + object_uri = "://".join(req.full_url.split("://")[1:]) + return gcsfs.GCSFileSystem().open(object_uri) + return None \ No newline at end of file From 1f5a18d679424563a419d216e2e3353b20683f2c Mon Sep 17 00:00:00 2001 From: k1nd0ne Date: Thu, 30 Nov 2023 11:54:55 +0100 Subject: [PATCH 79/83] patch running black --- volatility3/framework/layers/cloudstorage.py | 48 ++++++++++---------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/volatility3/framework/layers/cloudstorage.py b/volatility3/framework/layers/cloudstorage.py index 41afa324d..3f88ef34f 100644 --- a/volatility3/framework/layers/cloudstorage.py +++ b/volatility3/framework/layers/cloudstorage.py @@ -8,12 +8,14 @@ from typing import Optional, Any, List try: import s3fs + HAS_S3FS = True except ImportError: HAS_S3FS = False try: import gcsfs + HAS_GCSFS = True except ImportError: HAS_GCSFS = False @@ -26,30 +28,30 @@ vollog = logging.getLogger(__file__) if HAS_S3FS: class S3FileSystemHandler(resources.VolatilityHandler): - - @classmethod - def non_cached_schemes(cls) -> List[str]: - return ["s3"] + @classmethod + def non_cached_schemes(cls) -> List[str]: + return ["s3"] + + @staticmethod + def default_open(req: urllib.request.Request) -> Optional[Any]: + """Handles the request if it's the s3 scheme.""" + if req.type == "s3": + object_uri = "://".join(req.full_url.split("://")[1:]) + return s3fs.S3FileSystem().open(object_uri) + return None - @staticmethod - def default_open(req: urllib.request.Request) -> Optional[Any]: - """Handles the request if it's the s3 scheme.""" - if req.type == "s3": - object_uri = "://".join(req.full_url.split("://")[1:]) - return s3fs.S3FileSystem().open(object_uri) - return None if HAS_GCSFS: - + class GSFileSystemHandler(resources.VolatilityHandler): - @classmethod - def non_cached_schemes(cls) -> List[str]: - return ["gs"] - - @staticmethod - def default_open(req: urllib.request.Request) -> Optional[Any]: - """Handles the request if it's the gs scheme.""" - if req.type == "gs": - object_uri = "://".join(req.full_url.split("://")[1:]) - return gcsfs.GCSFileSystem().open(object_uri) - return None \ No newline at end of file + @classmethod + def non_cached_schemes(cls) -> List[str]: + return ["gs"] + + @staticmethod + def default_open(req: urllib.request.Request) -> Optional[Any]: + """Handles the request if it's the gs scheme.""" + if req.type == "gs": + object_uri = "://".join(req.full_url.split("://")[1:]) + return gcsfs.GCSFileSystem().open(object_uri) + return None From 7fe086f64f7c98a2c46990eff2e585a383d4bea2 Mon Sep 17 00:00:00 2001 From: Eve Date: Fri, 1 Dec 2023 13:37:09 +0000 Subject: [PATCH 80/83] Linux: update maple tree extension to fix issue #1032 correcting the mutable type used as a default parameter. --- .../symbols/linux/extensions/__init__.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index c3e50fce4..92c544c30 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -301,11 +301,7 @@ class maple_tree(objects.StructType): self.ma_flags & self.MT_FLAGS_HEIGHT_MASK ) >> self.MT_FLAGS_HEIGHT_OFFSET yield from self._parse_maple_tree_node( - self.ma_root, - maple_tree_offset, - expected_maple_tree_depth, - seen=set(), - current_depth=1, + self.ma_root, maple_tree_offset, expected_maple_tree_depth ) def _parse_maple_tree_node( @@ -313,11 +309,16 @@ class maple_tree(objects.StructType): maple_tree_entry, parent, expected_maple_tree_depth, - seen=set(), + seen=None, current_depth=1, ): """Recursively parse Maple Tree Nodes and yield all non empty slots""" + # create seen set if it does not exist, e.g. on the first call into + # this recursive function. + if seen == None: + seen = set() + # protect against unlikely loop if maple_tree_entry in seen: vollog.warning( @@ -326,6 +327,7 @@ class maple_tree(objects.StructType): return else: seen.add(maple_tree_entry) + # check if we have exceeded the expected depth of this maple tree. # e.g. when current_depth is larger than expected_maple_tree_depth there may be an issue. # it is normal that expected_maple_tree_depth is equal to current_depth. @@ -334,6 +336,7 @@ class maple_tree(objects.StructType): f"The depth for the maple tree at {hex(self.vol.offset)} is {expected_maple_tree_depth}, however when parsing the nodes " f"a depth of {current_depth} was reached. This is unexpected and may lead to incorrect results." ) + # parse the mte to extract the pointer value, node type, and leaf status pointer = maple_tree_entry & ~(self.MAPLE_NODE_POINTER_MASK) node_type = ( From 276e695237e0a93cdb4755bcc8c063acda7f3a85 Mon Sep 17 00:00:00 2001 From: Eve Date: Fri, 1 Dec 2023 13:46:00 +0000 Subject: [PATCH 81/83] Linux: update maple tree extension comment around the seen set. --- .../framework/symbols/linux/extensions/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 92c544c30..dc31a7628 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -314,8 +314,13 @@ class maple_tree(objects.StructType): ): """Recursively parse Maple Tree Nodes and yield all non empty slots""" - # create seen set if it does not exist, e.g. on the first call into - # this recursive function. + # Create seen set if it does not exist, e.g. on the first call into this recursive function. This + # must be None or an existing set of addresses for MTEs that have already been processed or that + # should otherwise be ignored. If parsing from the root node for example this should be None on the + # first call. If you needed to parse all nodes downwards from part of the tree this should still be + # None. If however you wanted to parse from a node, but ignore some parts of the tree below it then + # this could be populated with the addresses of the nodes you wish to ignore. + if seen == None: seen = set() From ed2db939d6b36d18dd44bad13d6a603b760b62a2 Mon Sep 17 00:00:00 2001 From: k1nd0ne Date: Sun, 3 Dec 2023 12:24:11 +0100 Subject: [PATCH 82/83] Better exception handling. Fetching data using objects --- .../framework/plugins/windows/mftscan.py | 93 +++++++++---------- .../symbols/windows/extensions/mft.py | 39 +++++--- 2 files changed, 70 insertions(+), 62 deletions(-) diff --git a/volatility3/framework/plugins/windows/mftscan.py b/volatility3/framework/plugins/windows/mftscan.py index 623497638..4d58eb1e2 100644 --- a/volatility3/framework/plugins/windows/mftscan.py +++ b/volatility3/framework/plugins/windows/mftscan.py @@ -31,7 +31,6 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): requirements.VersionRequirement( name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) ), - ] def _generator(self): @@ -53,6 +52,7 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): # get each of the individual Field Sets mft_object = symbol_table + constants.BANG + "MFT_ENTRY" + attribute_object = symbol_table + constants.BANG + "ATTRIBUTE" header_object = symbol_table + constants.BANG + "ATTR_HEADER" si_object = symbol_table + constants.BANG + "STANDARD_INFORMATION_ENTRY" fn_object = symbol_table + constants.BANG + "FILE_NAME_ENTRY" @@ -67,9 +67,8 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): ) # We will update this on each pass in the next loop and use it as the new offset. attr_base_offset = mft_record.FirstAttrOffset - - attr_header = self.context.object( - header_object, + attr = self.context.object( + attribute_object, offset=offset + attr_base_offset, layer_name=layer.name, ) @@ -77,17 +76,8 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): # There is no field that has a count of Attributes # Keep Attempting to read attributes until we get an invalid attr_header.AttrType - while attr_header.AttrType.is_valid_choice: - vollog.debug(f"Attr Type: {attr_header.AttrType.lookup()}") - - # Offset past the headers to the attribute data - attr_data_offset = ( - offset - + attr_base_offset - + self.context.symbol_space.get_type( - header_object - ).size - ) + while attr.Attr_Header.AttrType.is_valid_choice: + vollog.debug(f"Attr Type: {attr.Attr_Header.AttrType.lookup()}") # MFT Flags determine the file type or dir # If we don't have a valid enum, coerce to hex so we can keep the record @@ -97,19 +87,16 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): mft_flag = hex(mft_record.Flags) # Standard Information Attribute - if attr_header.AttrType.lookup() == "STANDARD_INFORMATION": - attr_data = self.context.object( - si_object, offset=attr_data_offset, layer_name=layer.name - ) - + if attr.Attr_Header.AttrType.lookup() == "STANDARD_INFORMATION": + attr_data = attr.Attr_Data.cast(si_object) yield 0, ( - format_hints.Hex(attr_data_offset), + format_hints.Hex(attr_data.vol.offset), mft_record.get_signature(), mft_record.RecordNumber, mft_record.LinkCount, mft_flag, renderers.NotApplicableValue(), - attr_header.AttrType.lookup(), + attr.Attr_Header.AttrType.lookup(), conversion.wintime_to_datetime(attr_data.CreationTime), conversion.wintime_to_datetime(attr_data.ModifiedTime), conversion.wintime_to_datetime(attr_data.UpdatedTime), @@ -118,10 +105,8 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): ) # File Name Attribute - if attr_header.AttrType.lookup() == "FILE_NAME": - attr_data = self.context.object( - fn_object, offset=attr_data_offset, layer_name=layer.name - ) + if attr.Attr_Header.AttrType.lookup() == "FILE_NAME": + attr_data = attr.Attr_Data.cast(fn_object) file_name = attr_data.get_full_name() # If we don't have a valid enum, coerce to hex so we can keep the record @@ -131,13 +116,13 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): permissions = hex(attr_data.Flags) yield 1, ( - format_hints.Hex(attr_data_offset), + format_hints.Hex(attr_data.vol.offset), mft_record.get_signature(), mft_record.RecordNumber, mft_record.LinkCount, mft_flag, permissions, - attr_header.AttrType.lookup(), + attr.Attr_Header.AttrType.lookup(), conversion.wintime_to_datetime(attr_data.CreationTime), conversion.wintime_to_datetime(attr_data.ModifiedTime), conversion.wintime_to_datetime(attr_data.UpdatedTime), @@ -146,14 +131,13 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): ) # If there's no advancement the loop will never end, so break it now - if attr_header.Length == 0: + if attr.Attr_Header.Length == 0: break # Update the base offset to point to the next attribute - attr_base_offset += attr_header.Length - # Get the next attribute - attr_header = self.context.object( - header_object, + attr_base_offset += attr.Attr_Header.Length + attr = self.context.object( + attribute_object, offset=offset + attr_base_offset, layer_name=layer.name, ) @@ -224,7 +208,11 @@ class ADS(interfaces.plugins.PluginInterface): config_path=self.config_path, sub_path="windows", filename="mft", - class_types={"MFT_ENTRY": mft.MFTEntry,"FILE_NAME_ENTRY": mft.MFTFileName, "ATTRIBUTE": mft.MFTAttribute}, + class_types={ + "MFT_ENTRY": mft.MFTEntry, + "FILE_NAME_ENTRY": mft.MFTFileName, + "ATTRIBUTE": mft.MFTAttribute, + }, ) # get each of the individual Field Sets @@ -251,30 +239,39 @@ class ADS(interfaces.plugins.PluginInterface): # There is no field that has a count of Attributes # Keep Attempting to read attributes until we get an invalid attr.AttrType - file_name = renderers.NotAvailableValue is_ads = False - + file_name = renderers.NotAvailableValue # The First $DATA Attr is the 'principal' file itself not the ADS while attr.Attr_Header.AttrType.is_valid_choice: - if attr.Attr_Header.AttrType.lookup() == "FILE_NAME": attr_data = attr.Attr_Data.cast(fn_object) file_name = attr_data.get_full_name() - if attr.Attr_Header.AttrType.lookup() == "DATA": if is_ads: if not attr.Attr_Header.NonResidentFlag: # Resident files are the most interesting. if attr.Attr_Header.NameLength > 0: - ads_name = attr.get_resident_filename() - content = attr.get_resident_filecontent() + if not ads_name: + ads_name = renderers.NotAvailableValue - # Preparing for Disassembly - architecture = layer.metadata.get("architecture", None) - disasm = interfaces.renderers.Disassembly( - content, 0, architecture.lower() - ) + content = attr.get_resident_filecontent() + if content: + # Preparing for Disassembly + architecture = layer.metadata.get( + "architecture", None + ) + + disasm = ( + interfaces.renderers.Disassembly( + content, 0, architecture.lower() + ) + if architecture + else interfaces.renderers.BaseAbsentValue + ) + else: + content = renderers.NotAvailableValue + disasm = interfaces.renderers.BaseAbsentValue yield 0, ( format_hints.Hex(attr_data.vol.offset), @@ -288,8 +285,7 @@ class ADS(interfaces.plugins.PluginInterface): ) else: is_ads = True - - + # If there's no advancement the loop will never end, so break it now if attr.Attr_Header.Length == 0: break @@ -302,6 +298,7 @@ class ADS(interfaces.plugins.PluginInterface): offset=offset + attr_base_offset, layer_name=layer.name, ) + def run(self): return renderers.TreeGrid( [ @@ -315,4 +312,4 @@ class ADS(interfaces.plugins.PluginInterface): ("Disasm", interfaces.renderers.Disassembly), ], self._generator(), - ) \ No newline at end of file + ) diff --git a/volatility3/framework/symbols/windows/extensions/mft.py b/volatility3/framework/symbols/windows/extensions/mft.py index 1b5d5fce4..14c1f08d6 100644 --- a/volatility3/framework/symbols/windows/extensions/mft.py +++ b/volatility3/framework/symbols/windows/extensions/mft.py @@ -2,7 +2,7 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # -from volatility3.framework import objects +from volatility3.framework import objects, constants, exceptions class MFTEntry(objects.StructType): @@ -28,18 +28,29 @@ class MFTAttribute(objects.StructType): def get_resident_filename(self) -> str: # To get the resident name, we jump to relative name offset and read name length * 2 bytes of data - layer = self._context.layers[self.vol.layer_name] - attr_name_offset = self.vol.offset + self.Attr_Header.NameOffset - - return self._context.layers[layer.name].read( - attr_name_offset, self.Attr_Header.NameLength*2 , pad=True - ).decode('utf-16') - + try: + name = self._context.object( + self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "string", + layer_name=self.vol.layer_name, + offset=self.vol.offset + self.Attr_Header.NameOffset, + max_length=self.Attr_Header.NameLength * 2, + errors="replace", + encoding="utf16", + ) + return name + except exceptions.InvalidAddressException: + return None + def get_resident_filecontent(self) -> bytes: # To get the resident content, we jump to relative content offset and read name length * 2 bytes of data - layer = self._context.layers[self.vol.layer_name] - attr_content_offset = self.vol.offset + self.Attr_Header.ContentOffset - - return self._context.layers[layer.name].read( - attr_content_offset, self.Attr_Header.ContentLength , pad=True - ) + try: + bytesobj = self._context.object( + self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "bytes", + layer_name=self.vol.layer_name, + offset=self.vol.offset + self.Attr_Header.ContentOffset, + native_layer_name=self.vol.native_layer_name, + length=self.Attr_Header.ContentLength, + ) + return bytesobj + except exceptions.InvalidAddressException: + return None From acb088dbcdb8532e643a72fb4571ae1cba24786c Mon Sep 17 00:00:00 2001 From: k1nd0ne Date: Sun, 3 Dec 2023 13:05:45 +0100 Subject: [PATCH 83/83] Better code reading --- volatility3/framework/plugins/windows/mftscan.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/plugins/windows/mftscan.py b/volatility3/framework/plugins/windows/mftscan.py index 4d58eb1e2..7e4e1ca18 100644 --- a/volatility3/framework/plugins/windows/mftscan.py +++ b/volatility3/framework/plugins/windows/mftscan.py @@ -258,17 +258,14 @@ class ADS(interfaces.plugins.PluginInterface): content = attr.get_resident_filecontent() if content: # Preparing for Disassembly + disasm = interfaces.renderers.BaseAbsentValue architecture = layer.metadata.get( "architecture", None ) - - disasm = ( - interfaces.renderers.Disassembly( + if architecture: + disasm = interfaces.renderers.Disassembly( content, 0, architecture.lower() ) - if architecture - else interfaces.renderers.BaseAbsentValue - ) else: content = renderers.NotAvailableValue disasm = interfaces.renderers.BaseAbsentValue