From 87fe3444ebb0d3bf7595c6cefc24f04145ff8dae Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Wed, 14 Aug 2024 23:50:39 -0700 Subject: [PATCH 01/19] Linux: Add support for mount namespace in kernels 6.8+. This fixes volatilityfoundation/volatility3#1187 --- .../framework/symbols/linux/__init__.py | 33 +++++++++++++++ .../symbols/linux/extensions/__init__.py | 42 +++++++++++++++---- 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 03353135d..525492141 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -425,3 +425,36 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): kernel = context.modules[kernel_module_name] return kernel + + +class RBTree(object): + """Simple Red-Black tree abstraction""" + + def __init__(self, root): + self.root = root + + def _walk_nodes(self, root_node) -> Iterator[int]: + """Traverses the Red-Black tree from the root node and yields a pointer to each + node in this tree. + + Args: + root_node: A Red-Black tree node from which to start descending + + Yields: + A pointer to every node descending from the specified root node + """ + if not root_node: + return + + yield root_node + yield from self._walk_nodes(root_node.rb_left) + yield from self._walk_nodes(root_node.rb_right) + + def get_nodes(self) -> Iterator[int]: + """Yields a pointer to each node in the Red-Black tree + + Yields: + A pointer to every node in the Red-Black tree + """ + + yield from self._walk_nodes(root_node=self.root.rb_node) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 05679523f..645f493cb 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1299,14 +1299,42 @@ class mnt_namespace(objects.StructType): else: raise AttributeError("Unable to find mnt_namespace inode") - def get_mount_points(self): + def get_mount_points( + self, + ) -> Iterator[interfaces.objects.ObjectInterface]: + """Yields the mount points for this mount namespace. + + Yields: + mount struct instances + """ table_name = self.vol.type_name.split(constants.BANG)[0] - mnt_type = table_name + constants.BANG + "mount" - if not self._context.symbol_space.has_type(mnt_type): - # Old kernels ~ 2.6 - mnt_type = table_name + constants.BANG + "vfsmount" - for mount in self.list.to_list(mnt_type, "mnt_list"): - yield mount + + if self.has_member("list"): + # kernels < 6.8 + mnt_type = table_name + constants.BANG + "mount" + if not self._context.symbol_space.has_type(mnt_type): + # In kernels < 3.3, the 'mount' struct didn't exist, and the 'mnt_list' + # member was part of the 'vfsmount' struct. + mnt_type = table_name + constants.BANG + "vfsmount" + + yield from self.list.to_list(mnt_type, "mnt_list") + elif ( + self.has_member("mounts") + and self.mounts.vol.type_name == table_name + constants.BANG + "rb_root" + ): + # kernels >= 6.8 + vmlinux = linux.LinuxUtilities.get_module_from_volobj_type( + self._context, self + ) + for node in linux.RBTree(self.mounts).get_nodes(): + mnt = linux.LinuxUtilities.container_of( + node, "mount", "mnt_list", vmlinux + ) + yield mnt + else: + raise exceptions.VolatilityException( + "Unsupported kernel mount namespace implementation" + ) class net(objects.StructType): From 4513806a7ec7f8c4da746a7ffde952d9f170a07f Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Thu, 15 Aug 2024 07:55:45 -0700 Subject: [PATCH 02/19] Move RBTree to the rb_root extension object for better integration --- .../framework/symbols/linux/__init__.py | 34 +------------------ .../symbols/linux/extensions/__init__.py | 30 +++++++++++++++- 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 525492141..c2eb3d791 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -46,6 +46,7 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable): # Might not exist in older kernels or the current symbols self.optional_set_type_class("mount", extensions.mount) self.optional_set_type_class("mnt_namespace", extensions.mnt_namespace) + self.optional_set_type_class("rb_root", extensions.rb_root) # Network self.set_type_class("net", extensions.net) @@ -425,36 +426,3 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): kernel = context.modules[kernel_module_name] return kernel - - -class RBTree(object): - """Simple Red-Black tree abstraction""" - - def __init__(self, root): - self.root = root - - def _walk_nodes(self, root_node) -> Iterator[int]: - """Traverses the Red-Black tree from the root node and yields a pointer to each - node in this tree. - - Args: - root_node: A Red-Black tree node from which to start descending - - Yields: - A pointer to every node descending from the specified root node - """ - if not root_node: - return - - yield root_node - yield from self._walk_nodes(root_node.rb_left) - yield from self._walk_nodes(root_node.rb_right) - - def get_nodes(self) -> Iterator[int]: - """Yields a pointer to each node in the Red-Black tree - - Yields: - A pointer to every node in the Red-Black tree - """ - - yield from self._walk_nodes(root_node=self.root.rb_node) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 645f493cb..c18cd093d 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1326,7 +1326,7 @@ class mnt_namespace(objects.StructType): vmlinux = linux.LinuxUtilities.get_module_from_volobj_type( self._context, self ) - for node in linux.RBTree(self.mounts).get_nodes(): + for node in self.mounts.get_nodes(): mnt = linux.LinuxUtilities.container_of( node, "mount", "mnt_list", vmlinux ) @@ -1925,3 +1925,31 @@ class inode(objects.StructType): The inode's file mode string """ return stat.filemode(self.i_mode) + + +class rb_root(objects.StructType): + def _walk_nodes(self, root_node) -> Iterator[int]: + """Traverses the Red-Black tree from the root node and yields a pointer to each + node in this tree. + + Args: + root_node: A Red-Black tree node from which to start descending + + Yields: + A pointer to every node descending from the specified root node + """ + if not root_node: + return + + yield root_node + yield from self._walk_nodes(root_node.rb_left) + yield from self._walk_nodes(root_node.rb_right) + + def get_nodes(self) -> Iterator[int]: + """Yields a pointer to each node in the Red-Black tree + + Yields: + A pointer to every node in the Red-Black tree + """ + + yield from self._walk_nodes(root_node=self.rb_node) From 57b5aabdfe885dd054ceb7d1cd1227b46f30ac22 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Thu, 3 Oct 2024 11:50:03 +1000 Subject: [PATCH 03/19] Add linux.ptrace plugin to enumerate tracer and tracee tasks --- .../framework/constants/linux/__init__.py | 39 +++++- volatility3/framework/plugins/linux/ptrace.py | 120 ++++++++++++++++++ 2 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 volatility3/framework/plugins/linux/ptrace.py diff --git a/volatility3/framework/constants/linux/__init__.py b/volatility3/framework/constants/linux/__init__.py index 3eabc2341..0567b8574 100644 --- a/volatility3/framework/constants/linux/__init__.py +++ b/volatility3/framework/constants/linux/__init__.py @@ -5,7 +5,7 @@ Linux-specific values that aren't found in debug symbols """ -from enum import IntEnum +from enum import IntEnum, Flag KERNEL_NAME = "__kernel__" @@ -302,3 +302,40 @@ class ELF_CLASS(IntEnum): ELFCLASSNONE = 0 ELFCLASS32 = 1 ELFCLASS64 = 2 + + +PT_OPT_FLAG_SHIFT = 3 + +PTRACE_EVENT_FORK = 1 +PTRACE_EVENT_VFORK = 2 +PTRACE_EVENT_CLONE = 3 +PTRACE_EVENT_EXEC = 4 +PTRACE_EVENT_VFORK_DONE = 5 +PTRACE_EVENT_EXIT = 6 +PTRACE_EVENT_SECCOMP = 7 + +PTRACE_O_EXITKILL = 1 << 20 +PTRACE_O_SUSPEND_SECCOMP = 1 << 21 + + +class PT_FLAGS(Flag): + "PTrace flags" + PT_PTRACED = 0x00001 + PT_SEIZED = 0x10000 + + PT_TRACESYSGOOD = 1 << (PT_OPT_FLAG_SHIFT + 0) + PT_TRACE_FORK = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_FORK) + PT_TRACE_VFORK = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_VFORK) + PT_TRACE_CLONE = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_CLONE) + PT_TRACE_EXEC = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_EXEC) + PT_TRACE_VFORK_DONE = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_VFORK_DONE) + PT_TRACE_EXIT = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_EXIT) + PT_TRACE_SECCOMP = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_SECCOMP) + + PT_EXITKILL = PTRACE_O_EXITKILL << PT_OPT_FLAG_SHIFT + PT_SUSPEND_SECCOMP = PTRACE_O_SUSPEND_SECCOMP << PT_OPT_FLAG_SHIFT + + @property + def flags(self) -> str: + """Returns the ptrace flags string""" + return str(self).replace(self.__class__.__name__ + ".", "") diff --git a/volatility3/framework/plugins/linux/ptrace.py b/volatility3/framework/plugins/linux/ptrace.py new file mode 100644 index 000000000..793a1b3a1 --- /dev/null +++ b/volatility3/framework/plugins/linux/ptrace.py @@ -0,0 +1,120 @@ +# This file is Copyright 2024 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 +from typing import List + +from volatility3.framework import renderers, interfaces, constants, objects +from volatility3.framework.constants.linux import PT_FLAGS +from volatility3.framework.constants.architectures import LINUX_ARCHS +from volatility3.framework.objects import utility +from volatility3.framework.configuration import requirements +from volatility3.framework.interfaces import plugins +from volatility3.plugins.linux import pslist + +vollog = logging.getLogger(__name__) + + +class Ptrace(plugins.PluginInterface): + """Enumerates tracer and tracee tasks""" + + _required_framework_version = (2, 10, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=LINUX_ARCHS, + ), + requirements.PluginRequirement( + name="pslist", plugin=pslist.PsList, version=(2, 2, 1) + ), + ] + + @classmethod + def enumerate_ptraced_tasks( + cls, + context: interfaces.context.ContextInterface, + symbol_table: str, + ): + vmlinux = context.modules[symbol_table] + + tsk_struct_symname = vmlinux.symbol_table_name + constants.BANG + "task_struct" + + tasks = pslist.PsList.list_tasks( + context, + symbol_table, + filter_func=pslist.PsList.create_pid_filter(), + include_threads=True, + ) + + for task in tasks: + tracing_tid_list = [ + int(task_being_traced.pid) + for task_being_traced in task.ptraced.to_list( + tsk_struct_symname, "ptrace_entry" + ) + ] + + if task.ptrace == 0 and not tracing_tid_list: + continue + + flags = ( + PT_FLAGS(task.ptrace).flags + if task.ptrace != 0 + else renderers.NotAvailableValue() + ) + + traced_by_tid = ( + task.parent.pid + if task.real_parent != task.parent + else renderers.NotAvailableValue() + ) + + tracing_tids = ",".join(map(str, tracing_tid_list)) + + yield task.comm, task.tgid, task.pid, traced_by_tid, tracing_tids, flags + + def _generator(self, symbol_table): + for fields in self.enumerate_ptraced_tasks(self.context, symbol_table): + yield (0, fields) + + @staticmethod + def format_fields_with_headers(headers, generator): + """Uses the headers type to cast the fields obtained from the generator""" + for level, fields in generator: + formatted_fields = [] + for header, field in zip(headers, fields): + header_type = header[1] + + if isinstance( + field, (header_type, interfaces.renderers.BaseAbsentValue) + ): + formatted_field = field + elif isinstance(field, objects.Array) and header_type is str: + formatted_field = utility.array_to_string(field) + else: + formatted_field = header_type(field) + + formatted_fields.append(formatted_field) + yield level, formatted_fields + + def run(self): + symbol_table = self.config["kernel"] + + headers = [ + ("Process", str), + ("PID", int), + ("TID", int), + ("Traced by TID", int), + ("Tracing TIDs", str), + ("Flags", str), + ] + return renderers.TreeGrid( + headers, + self.format_fields_with_headers(headers, self._generator(symbol_table)), + ) From e605bee4119e5c7da857ce9576520c36a7eb4b19 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Fri, 6 Sep 2024 14:07:24 -0500 Subject: [PATCH 04/19] Windows: unloadedmodules bugfix This fixes a bug in the `windows.unloadedmodules` plugin. An `InvalidAddressException` can be raised when parsing the module name; this adds a try/except block to replace the missing module name with a `renderers.UnreadableValue` when the exception occurs. --- volatility3/framework/plugins/windows/unloadedmodules.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/windows/unloadedmodules.py b/volatility3/framework/plugins/windows/unloadedmodules.py index 0d88e96ff..01e575818 100644 --- a/volatility3/framework/plugins/windows/unloadedmodules.py +++ b/volatility3/framework/plugins/windows/unloadedmodules.py @@ -7,7 +7,7 @@ import datetime from typing import List, Iterable from volatility3.framework import constants -from volatility3.framework import interfaces, symbols +from volatility3.framework import interfaces, symbols, exceptions from volatility3.framework import renderers from volatility3.framework.configuration import requirements from volatility3.framework.interfaces import configuration @@ -132,10 +132,15 @@ class UnloadedModules(interfaces.plugins.PluginInterface, timeliner.TimeLinerInt kernel.symbol_table_name, unloadedmodule_table_name, ): + try: + name = mod.Name.String + except exceptions.InvalidAddressException: + name = renderers.UnreadableValue() + yield ( 0, ( - mod.Name.String, + name, format_hints.Hex(mod.StartAddress), format_hints.Hex(mod.EndAddress), conversion.wintime_to_datetime(mod.CurrentTime), From f92dfdd33b504e781d9ba660b8fba625618e33a9 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 8 Oct 2024 15:53:07 +1100 Subject: [PATCH 05/19] Linux: ptrace: remove patch number in plugin requirement --- volatility3/framework/plugins/linux/ptrace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/ptrace.py b/volatility3/framework/plugins/linux/ptrace.py index 793a1b3a1..1a37b8782 100644 --- a/volatility3/framework/plugins/linux/ptrace.py +++ b/volatility3/framework/plugins/linux/ptrace.py @@ -31,7 +31,7 @@ class Ptrace(plugins.PluginInterface): architectures=LINUX_ARCHS, ), requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(2, 2, 1) + name="pslist", plugin=pslist.PsList, version=(2, 2, 0) ), ] From eb2d4b176518d8337a82ebc61b99ed04186904e3 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 8 Oct 2024 15:54:48 +1100 Subject: [PATCH 06/19] Linux: ptrace: add ptrace functions to the task_struct object extension --- .../symbols/linux/extensions/__init__.py | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index fdd34403a..7f6d44319 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -13,13 +13,12 @@ from typing import Generator, Iterable, Iterator, Optional, Tuple, List, Union, from volatility3.framework import constants, exceptions, objects, interfaces, symbols from volatility3.framework.renderers import conversion -from volatility3.framework.configuration import requirements from volatility3.framework.constants.linux import SOCK_TYPES, SOCK_FAMILY from volatility3.framework.constants.linux import IP_PROTOCOLS, IPV6_PROTOCOLS from volatility3.framework.constants.linux import TCP_STATES, NETLINK_PROTOCOLS from volatility3.framework.constants.linux import ETH_PROTOCOLS, BLUETOOTH_STATES from volatility3.framework.constants.linux import BLUETOOTH_PROTOCOLS, SOCKET_STATES -from volatility3.framework.constants.linux import CAPABILITIES +from volatility3.framework.constants.linux import CAPABILITIES, PT_FLAGS from volatility3.framework.layers import linear from volatility3.framework.objects import utility from volatility3.framework.symbols import generic, linux, intermed @@ -374,6 +373,41 @@ class task_struct(generic.GenericIntelProcess): ): yield task + @property + def is_being_ptraced(self) -> bool: + """Returns True if this task is being traced using ptrace""" + return self.ptrace != 0 + + @property + def is_ptracing(self) -> bool: + """Returns True if this task is tracing other tasks using ptrace""" + is_tracing = ( + self.ptraced.next.is_readable() + and self.ptraced.next.dereference().vol.offset != self.ptraced.vol.offset + ) + return is_tracing + + def get_ptrace_tracer_tid(self) -> Optional[int]: + """Returns the tracer's TID tracing this task""" + return self.parent.pid if self.is_being_ptraced else None + + def get_ptrace_tracee_tids(self) -> List[int]: + """Returns the list of TIDs being traced by this task""" + task_symbol_table_name = self.get_symbol_table_name() + + task_struct_symname = f"{task_symbol_table_name}{constants.BANG}task_struct" + tracing_tid_list = [ + task_being_traced.pid + for task_being_traced in self.ptraced.to_list( + task_struct_symname, "ptrace_entry" + ) + ] + return tracing_tid_list + + def get_ptrace_tracee_flags(self) -> Optional[str]: + """Returns a string with the ptrace flags""" + return PT_FLAGS(self.ptrace).flags if self.is_being_ptraced else None + class fs_struct(objects.StructType): def get_root_dentry(self): From 3c4b9996ce78a00ef5121782fd75a45f90a557ae Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 8 Oct 2024 16:00:35 +1100 Subject: [PATCH 07/19] Linux: ptrace: move tracee TIDs to multiple rows --- volatility3/framework/plugins/linux/ptrace.py | 103 +++++++----------- 1 file changed, 40 insertions(+), 63 deletions(-) diff --git a/volatility3/framework/plugins/linux/ptrace.py b/volatility3/framework/plugins/linux/ptrace.py index 1a37b8782..a1964d960 100644 --- a/volatility3/framework/plugins/linux/ptrace.py +++ b/volatility3/framework/plugins/linux/ptrace.py @@ -3,10 +3,9 @@ # import logging -from typing import List +from typing import List, Iterator -from volatility3.framework import renderers, interfaces, constants, objects -from volatility3.framework.constants.linux import PT_FLAGS +from volatility3.framework import renderers, interfaces from volatility3.framework.constants.architectures import LINUX_ARCHS from volatility3.framework.objects import utility from volatility3.framework.configuration import requirements @@ -17,7 +16,7 @@ vollog = logging.getLogger(__name__) class Ptrace(plugins.PluginInterface): - """Enumerates tracer and tracee tasks""" + """Enumerates ptrace's tracer and tracee tasks""" _required_framework_version = (2, 10, 0) _version = (1, 0, 0) @@ -36,85 +35,63 @@ class Ptrace(plugins.PluginInterface): ] @classmethod - def enumerate_ptraced_tasks( + def enumerate_ptrace_tasks( cls, context: interfaces.context.ContextInterface, - symbol_table: str, - ): - vmlinux = context.modules[symbol_table] + vmlinux_module_name: str, + ) -> Iterator[interfaces.objects.ObjectInterface]: + """Enumerates ptrace's tracer and tracee tasks - tsk_struct_symname = vmlinux.symbol_table_name + constants.BANG + "task_struct" + Args: + context: The context to retrieve required elements (layers, symbol tables) from + vmlinux_module_name: The name of the kernel module on which to operate + + Yields: + A task_struct object + """ tasks = pslist.PsList.list_tasks( context, - symbol_table, + vmlinux_module_name, filter_func=pslist.PsList.create_pid_filter(), include_threads=True, ) for task in tasks: - tracing_tid_list = [ - int(task_being_traced.pid) - for task_being_traced in task.ptraced.to_list( - tsk_struct_symname, "ptrace_entry" - ) + if task.is_being_ptraced or task.is_ptracing: + yield task + + def _generator(self, vmlinux_module_name): + for task in self.enumerate_ptrace_tasks(self.context, vmlinux_module_name): + task_comm = utility.array_to_string(task.comm) + user_pid = task.tgid + user_tid = task.pid + tracer_tid = task.get_ptrace_tracer_tid() or renderers.NotAvailableValue() + tracee_tids = task.get_ptrace_tracee_tids() or [ + renderers.NotAvailableValue() ] + flags = task.get_ptrace_tracee_flags() or renderers.NotAvailableValue() - if task.ptrace == 0 and not tracing_tid_list: - continue - - flags = ( - PT_FLAGS(task.ptrace).flags - if task.ptrace != 0 - else renderers.NotAvailableValue() - ) - - traced_by_tid = ( - task.parent.pid - if task.real_parent != task.parent - else renderers.NotAvailableValue() - ) - - tracing_tids = ",".join(map(str, tracing_tid_list)) - - yield task.comm, task.tgid, task.pid, traced_by_tid, tracing_tids, flags - - def _generator(self, symbol_table): - for fields in self.enumerate_ptraced_tasks(self.context, symbol_table): - yield (0, fields) - - @staticmethod - def format_fields_with_headers(headers, generator): - """Uses the headers type to cast the fields obtained from the generator""" - for level, fields in generator: - formatted_fields = [] - for header, field in zip(headers, fields): - header_type = header[1] - - if isinstance( - field, (header_type, interfaces.renderers.BaseAbsentValue) - ): - formatted_field = field - elif isinstance(field, objects.Array) and header_type is str: - formatted_field = utility.array_to_string(field) - else: - formatted_field = header_type(field) - - formatted_fields.append(formatted_field) - yield level, formatted_fields + for tree, tracing_tid in enumerate(tracee_tids): + fields = [ + task_comm, + user_pid, + user_tid, + tracer_tid, + tracing_tid, + flags, + ] + yield (tree, fields) def run(self): - symbol_table = self.config["kernel"] + vmlinux_module_name = self.config["kernel"] headers = [ ("Process", str), ("PID", int), ("TID", int), ("Traced by TID", int), - ("Tracing TIDs", str), + ("Tracing TID", int), ("Flags", str), ] - return renderers.TreeGrid( - headers, - self.format_fields_with_headers(headers, self._generator(symbol_table)), - ) + return renderers.TreeGrid(headers, self._generator(vmlinux_module_name)) From aa959139409c22156cadb0b8fc91819f4d4f2c34 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 8 Oct 2024 16:03:36 +1100 Subject: [PATCH 08/19] Linux: ptrace: improve header names and variables --- volatility3/framework/plugins/linux/ptrace.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/plugins/linux/ptrace.py b/volatility3/framework/plugins/linux/ptrace.py index a1964d960..e501f3294 100644 --- a/volatility3/framework/plugins/linux/ptrace.py +++ b/volatility3/framework/plugins/linux/ptrace.py @@ -72,16 +72,16 @@ class Ptrace(plugins.PluginInterface): ] flags = task.get_ptrace_tracee_flags() or renderers.NotAvailableValue() - for tree, tracing_tid in enumerate(tracee_tids): + for level, tracee_tid in enumerate(tracee_tids): fields = [ task_comm, user_pid, user_tid, tracer_tid, - tracing_tid, + tracee_tid, flags, ] - yield (tree, fields) + yield (level, fields) def run(self): vmlinux_module_name = self.config["kernel"] @@ -90,8 +90,8 @@ class Ptrace(plugins.PluginInterface): ("Process", str), ("PID", int), ("TID", int), - ("Traced by TID", int), - ("Tracing TID", int), + ("Tracer TID", int), + ("Tracee TID", int), ("Flags", str), ] return renderers.TreeGrid(headers, self._generator(vmlinux_module_name)) From 6b5bef41336b3fbd623aca85499aa39410188fb0 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 8 Oct 2024 21:27:29 +1100 Subject: [PATCH 09/19] Linux: ptrace: bumping version patch number to re-run the black linter with #1301 --- volatility3/framework/plugins/linux/ptrace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/ptrace.py b/volatility3/framework/plugins/linux/ptrace.py index e501f3294..34185f912 100644 --- a/volatility3/framework/plugins/linux/ptrace.py +++ b/volatility3/framework/plugins/linux/ptrace.py @@ -19,7 +19,7 @@ class Ptrace(plugins.PluginInterface): """Enumerates ptrace's tracer and tracee tasks""" _required_framework_version = (2, 10, 0) - _version = (1, 0, 0) + _version = (1, 0, 1) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: From 620dd4e67d971e97adab5daea523b60458bc5c35 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 8 Oct 2024 21:55:21 +1100 Subject: [PATCH 10/19] Linux: ptrace: version patch number back to zero --- volatility3/framework/plugins/linux/ptrace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/ptrace.py b/volatility3/framework/plugins/linux/ptrace.py index 34185f912..e501f3294 100644 --- a/volatility3/framework/plugins/linux/ptrace.py +++ b/volatility3/framework/plugins/linux/ptrace.py @@ -19,7 +19,7 @@ class Ptrace(plugins.PluginInterface): """Enumerates ptrace's tracer and tracee tasks""" _required_framework_version = (2, 10, 0) - _version = (1, 0, 1) + _version = (1, 0, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: From 15a51cf1ec1e139193097976c115c820861bc034 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 14 Oct 2024 16:13:56 +1100 Subject: [PATCH 11/19] Linux - Add hlist_head object extension. Fix #1313 --- .../framework/symbols/linux/__init__.py | 1 + .../symbols/linux/extensions/__init__.py | 39 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 219f120ee..1b7a331f8 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -22,6 +22,7 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable): # Set-up Linux specific types self.set_type_class("file", extensions.struct_file) self.set_type_class("list_head", extensions.list_head) + self.set_type_class("hlist_head", extensions.hlist_head) self.set_type_class("mm_struct", extensions.mm_struct) self.set_type_class("super_block", extensions.super_block) self.set_type_class("task_struct", extensions.task_struct) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index aa18b8262..4b1df2a0b 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -844,7 +844,7 @@ class dentry(objects.StructType): if self.has_member("d_sib") and self.has_member("d_children"): # kernels >= 6.8 walk_member = "d_sib" - list_head_member = self.d_children.first + list_head_member = self.d_children elif self.has_member("d_child") and self.has_member("d_subdirs"): # 2.5.0 <= kernels < 6.8 walk_member = "d_child" @@ -961,6 +961,43 @@ class list_head(objects.StructType, collections.abc.Iterable): return self.to_list(self.vol.parent.vol.type_name, self.vol.member_name) +class hlist_head(objects.StructType, collections.abc.Iterable): + def to_list( + self, + symbol_type: str, + member: str, + ) -> Iterator[interfaces.objects.ObjectInterface]: + """Returns an iterator of the entries in the list. + + This is a doubly linked list; however, it is not circular, so the 'forward' field + doesn't make sense. Also, the sentinel concept doesn't make sense here either; + unlike list_head, the head and nodes each have their own distinct types. A list_head + cannot be a node by itself. + - The 'pprev' of the first 'hlist_node' points to the 'hlist_head', not to the last node. + - The last element 'next' member is NULL + + Args: + symbol_type: Type of the list elements + member: Name of the list_head member in the list elements + + Yields: + Objects of the type specified via the "symbol_type" argument. + + """ + vmlinux = linux.LinuxUtilities.get_module_from_volobj_type(self._context, self) + + current = self.first + while current and current.is_readable(): + yield linux.LinuxUtilities.container_of( + current, symbol_type, member, vmlinux + ) + + current = current.next + + def __iter__(self) -> Iterator[interfaces.objects.ObjectInterface]: + return self.to_list(self.vol.parent.vol.type_name, self.vol.member_name) + + class files_struct(objects.StructType): def get_fds(self) -> interfaces.objects.ObjectInterface: if self.has_member("fdt"): From f00c4c3c392a26bea29ba87d961c3a8f51092829 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 14 Oct 2024 16:35:43 +1100 Subject: [PATCH 12/19] Allows to create objects when using the same symbol table --- volatility3/framework/contexts/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/contexts/__init__.py b/volatility3/framework/contexts/__init__.py index 4c169728c..6961d9328 100644 --- a/volatility3/framework/contexts/__init__.py +++ b/volatility3/framework/contexts/__init__.py @@ -245,7 +245,7 @@ class Module(interfaces.context.ModuleInterface): """ if constants.BANG not in object_type: object_type = self.symbol_table_name + constants.BANG + object_type - else: + elif not object_type.startswith(self.symbol_table_name + constants.BANG): raise ValueError( "Cannot reference another module when constructing an object" ) From e739d96a33217e013f0b126ab2c11007cf059cee Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Mon, 14 Oct 2024 14:03:38 +0200 Subject: [PATCH 13/19] dwarf2json rust type confusion sanity check --- volatility3/schemas/__init__.py | 54 +++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/volatility3/schemas/__init__.py b/volatility3/schemas/__init__.py index be120f2af..3ca00e5dc 100644 --- a/volatility3/schemas/__init__.py +++ b/volatility3/schemas/__init__.py @@ -6,8 +6,8 @@ import hashlib import json import logging import os -from typing import Any, Dict, Optional, Set - +import re +from typing import Any, Dict, Optional, Set, Tuple from volatility3.framework import constants vollog = logging.getLogger(__name__) @@ -77,6 +77,17 @@ def valid( input: Dict[str, Any], schema: Dict[str, Any], use_cache: bool = True ) -> bool: """Validates a json schema.""" + producer = input.get("metadata", {}).get("producer", {}) + if producer and producer.get("name") == "dwarf2json": + dwarf2json_version = parse_producer_version(producer.get("version", "")) + # No warnings if version couldn't be parsed, as it's not our role here + # to validate the schema. + if dwarf2json_version: + if dwarf2json_check_rust_type_confusion(input, dwarf2json_version): + vollog.warning( + "This ISF was generated by dwarf2json < 0.9.0, which is known to produce inaccurate results (see dwarf2json GitHub issue #63)." + ) + input_hash = create_json_hash(input, schema) if input_hash in cached_validations and use_cache: return True @@ -98,3 +109,42 @@ def valid( record_cached_validations(cached_validations) return True + + +def parse_producer_version(version_string: str) -> Optional[Tuple[int]]: + """Parses a producer version and returns a tuple of identifiers. + + Args: + version_string: string containing dot-separated integers, + expected to follow the Volatility3 versioning schema + + Returns: + A tuple containing each version identifier + """ + identifiers = re.search("^(\\d+)[.](\\d+)[.](\\d+)$", version_string) + if not identifiers: + return None + + return tuple(int(d) for d in identifiers.groups()) + + +# dwarf2json sanity checks # +def dwarf2json_check_rust_type_confusion( + input: Dict[str, Any], dwarf2json_version: Tuple[int] +) -> bool: + """dwarf2json sanity check for Rust and C types confusion: + - dwarf2json #63 + - volatility3 #1305 + + Args: + dwarf2json_version: a tuple containing each version identifier + + Returns: + True if the issue was detected + """ + + return "rust_helper_BUG" in input.get("symbols", {}) and dwarf2json_version < ( + 0, + 9, + 0, + ) From 65daab6cfa986130a33d6c63faab00d5e500534a Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Tue, 22 Oct 2024 21:47:13 +0100 Subject: [PATCH 14/19] Linux: Fix minor coding style transgression --- volatility3/framework/plugins/linux/ptrace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/ptrace.py b/volatility3/framework/plugins/linux/ptrace.py index e501f3294..e467ee644 100644 --- a/volatility3/framework/plugins/linux/ptrace.py +++ b/volatility3/framework/plugins/linux/ptrace.py @@ -6,7 +6,7 @@ import logging from typing import List, Iterator from volatility3.framework import renderers, interfaces -from volatility3.framework.constants.architectures import LINUX_ARCHS +from volatility3.framework.constants import architectures from volatility3.framework.objects import utility from volatility3.framework.configuration import requirements from volatility3.framework.interfaces import plugins @@ -27,7 +27,7 @@ class Ptrace(plugins.PluginInterface): requirements.ModuleRequirement( name="kernel", description="Linux kernel", - architectures=LINUX_ARCHS, + architectures=architectures.LINUX_ARCHS, ), requirements.PluginRequirement( name="pslist", plugin=pslist.PsList, version=(2, 2, 0) From db8667f88abec074f3165ecfd04219e7ef72fbd1 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Wed, 23 Oct 2024 08:55:44 +1100 Subject: [PATCH 15/19] Linux - Fix hlist_head object extension by dropping the iterator since it can't determine the correct member_name --- volatility3/framework/symbols/linux/extensions/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 4b1df2a0b..acf78278b 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -994,9 +994,6 @@ class hlist_head(objects.StructType, collections.abc.Iterable): current = current.next - def __iter__(self) -> Iterator[interfaces.objects.ObjectInterface]: - return self.to_list(self.vol.parent.vol.type_name, self.vol.member_name) - class files_struct(objects.StructType): def get_fds(self) -> interfaces.objects.ObjectInterface: From 708d9330216853bfebcaa9d5799053656e422e10 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 23 Oct 2024 21:23:14 +0200 Subject: [PATCH 16/19] use additional_locals in run method --- volatility3/cli/volshell/generic.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index c1ca7b5fa..39a7aa126 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -58,7 +58,7 @@ class Volshell(interfaces.plugins.PluginInterface): ] def run( - self, additional_locals: Dict[str, Any] = None + self, additional_locals: Dict[str, Any] = {} ) -> interfaces.renderers.TreeGrid: """Runs the interactive volshell plugin. @@ -94,7 +94,10 @@ class Volshell(interfaces.plugins.PluginInterface): """ sys.ps1 = f"({self.current_layer}) >>> " - self.__console = code.InteractiveConsole(locals=self._construct_locals_dict()) + # Dict self._construct_locals_dict() will have priority on keys + combined_locals = self._construct_locals_dict().copy() + combined_locals.update(additional_locals) + self.__console = code.InteractiveConsole(locals=combined_locals) # Since we have to do work to add the option only once for all different modes of volshell, we can't # rely on the default having been set if self.config.get("script", None) is not None: From 7f2d8eabb7f513148a7868d9a758c10f43d0450b Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 23 Oct 2024 21:39:57 +0200 Subject: [PATCH 17/19] set correct locals priority --- volatility3/cli/volshell/generic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 39a7aa126..a0477656e 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -95,8 +95,8 @@ class Volshell(interfaces.plugins.PluginInterface): sys.ps1 = f"({self.current_layer}) >>> " # Dict self._construct_locals_dict() will have priority on keys - combined_locals = self._construct_locals_dict().copy() - combined_locals.update(additional_locals) + combined_locals = additional_locals.copy() + combined_locals.update(self._construct_locals_dict()) self.__console = code.InteractiveConsole(locals=combined_locals) # Since we have to do work to add the option only once for all different modes of volshell, we can't # rely on the default having been set From 388a3e64587478b0a07121a41f2b05a684c4292e Mon Sep 17 00:00:00 2001 From: David McDonald Date: Thu, 24 Oct 2024 14:39:22 -0500 Subject: [PATCH 18/19] Linux: Adds missing abc.Iterable implementation `hlist_head` is missing an implementation for `collections.abc.Iterable`, resulting in a crash at runtime. This fixes the issue by providing the required `__iter__` implementation for `hlist_head`. --- volatility3/framework/symbols/linux/extensions/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 14be5ec0a..9bd401db2 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1037,6 +1037,9 @@ class hlist_head(objects.StructType, collections.abc.Iterable): current = current.next + def __iter__(self) -> Iterator[interfaces.objects.ObjectInterface]: + return self.to_list(self.vol.parent.vol.type_name, self.vol.member_name) + class files_struct(objects.StructType): def get_fds(self) -> interfaces.objects.ObjectInterface: From 4c2d2f98da5c6fcbfe8e8aa2efa590438cdf1444 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Thu, 24 Oct 2024 15:39:15 -0500 Subject: [PATCH 19/19] Removes collections.abc.Iterable superclass Per @gcmoreira this is the correct fix vs the one previously proposed, since self.vol.member_name cannot be used as the member name to iterate this type. --- volatility3/framework/symbols/linux/extensions/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 9bd401db2..eba775714 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1004,7 +1004,7 @@ class list_head(objects.StructType, collections.abc.Iterable): return self.to_list(self.vol.parent.vol.type_name, self.vol.member_name) -class hlist_head(objects.StructType, collections.abc.Iterable): +class hlist_head(objects.StructType): def to_list( self, symbol_type: str, @@ -1037,9 +1037,6 @@ class hlist_head(objects.StructType, collections.abc.Iterable): current = current.next - def __iter__(self) -> Iterator[interfaces.objects.ObjectInterface]: - return self.to_list(self.vol.parent.vol.type_name, self.vol.member_name) - class files_struct(objects.StructType): def get_fds(self) -> interfaces.objects.ObjectInterface: