From 57b5aabdfe885dd054ceb7d1cd1227b46f30ac22 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Thu, 3 Oct 2024 11:50:03 +1000 Subject: [PATCH 1/7] 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 f92dfdd33b504e781d9ba660b8fba625618e33a9 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 8 Oct 2024 15:53:07 +1100 Subject: [PATCH 2/7] 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 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] 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 3/7] 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 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 @@ def get_threads(self) -> Iterable[interfaces.objects.ObjectInterface]: ): 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 4/7] Linux: ptrace: move tracee TIDs to multiple rows --- volatility3/framework/plugins/linux/ptrace.py | 105 +++++++----------- 1 file changed, 41 insertions(+), 64 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 @@ 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 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] ] @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() ] - - 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 + flags = task.get_ptrace_tracee_flags() or renderers.NotAvailableValue() + + 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 5/7] 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 @@ def _generator(self, vmlinux_module_name): ] 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 @@ def run(self): ("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 6/7] 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 7/7] 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]: