From 8adf86948d6ad07668de527538cc22cc084f5113 Mon Sep 17 00:00:00 2001 From: Eve Date: Thu, 5 Oct 2023 09:34:00 +0100 Subject: [PATCH 1/3] Update GenericIntelProcess and task_struct extension to use native_layer_name when creating a layer for a task --- volatility3/framework/symbols/generic/__init__.py | 2 +- volatility3/framework/symbols/linux/extensions/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/generic/__init__.py b/volatility3/framework/symbols/generic/__init__.py index 9d6da5aa41..f1179d02dc 100644 --- a/volatility3/framework/symbols/generic/__init__.py +++ b/volatility3/framework/symbols/generic/__init__.py @@ -39,7 +39,7 @@ def _add_process_layer( preferred_name = context.layers.free_layer_name(prefix=preferred_name) # Copy the parent's config and then make suitable changes - parent_layer = context.layers[self.vol.layer_name] + parent_layer = context.layers[self.vol.native_layer_name] parent_config = parent_layer.build_configuration() # It's an intel layer, because we hardwire the "memory_layer" config option # FIXME: this could be for other architectures if we don't hardwire this/these values diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 3fb772135d..d343aee357 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -179,7 +179,7 @@ def add_process_layer( Returns the name of the Layer or None. """ - parent_layer = self._context.layers[self.vol.layer_name] + parent_layer = self._context.layers[self.vol.native_layer_name] try: pgd = self.mm.pgd except exceptions.InvalidAddressException: From 18646c1b39372b11421c9d3679540372ca649388 Mon Sep 17 00:00:00 2001 From: Eve Date: Thu, 5 Oct 2023 09:35:07 +0100 Subject: [PATCH 2/3] Linux: Add a --pid filter option to linux.psscan --- volatility3/framework/plugins/linux/psscan.py | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/linux/psscan.py b/volatility3/framework/plugins/linux/psscan.py index 462577e583..e05dc76b5d 100644 --- a/volatility3/framework/plugins/linux/psscan.py +++ b/volatility3/framework/plugins/linux/psscan.py @@ -2,7 +2,7 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # import logging -from typing import Iterable, List, Tuple +from typing import Iterable, List, Tuple, Callable import struct from enum import Enum @@ -11,6 +11,7 @@ from volatility3.framework.objects import utility from volatility3.framework.layers import scanners from volatility3.framework.renderers import format_hints +from volatility3.plugins.linux import pslist vollog = logging.getLogger(__name__) @@ -28,7 +29,7 @@ class PsScan(interfaces.plugins.PluginInterface): """Scans for processes present in a particular linux image.""" _required_framework_version = (2, 0, 0) - _version = (1, 0, 0) + _version = (1, 1, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -38,6 +39,15 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] description="Linux kernel", architectures=["Intel32", "Intel64"], ), + requirements.ListRequirement( + name="pid", + description="Filter on specific process IDs", + element_type=int, + optional=True, + ), + requirements.PluginRequirement( + name="pslist", plugin=pslist.PsList, version=(2, 0, 0) + ), ] def _get_task_fields( @@ -75,8 +85,10 @@ def _generator(self): vmlinux_module_name = self.config["kernel"] vmlinux = self.context.modules[vmlinux_module_name] + filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) + for task in self.scan_tasks( - self.context, vmlinux_module_name, vmlinux.layer_name + self.context, vmlinux_module_name, vmlinux.layer_name, filter_func ): row = self._get_task_fields(task) yield (0, row) @@ -87,6 +99,7 @@ def scan_tasks( context: interfaces.context.ContextInterface, vmlinux_module_name: str, kernel_layer_name: str, + filter_func: Callable[[int], bool] = lambda _: False, ) -> Iterable[interfaces.objects.ObjectInterface]: """Scans for tasks in the memory layer. @@ -168,6 +181,10 @@ def scan_tasks( f"Skipping task_struct at {hex(ptask.vol.offset)} as pid {ptask.pid} is likely not valid" ) continue + + if filter_func(ptask): + continue + yield ptask def run(self): From d88e0ab72e2f9b460144e0822518607689ff5b66 Mon Sep 17 00:00:00 2001 From: Eve Date: Thu, 5 Oct 2023 09:39:23 +0100 Subject: [PATCH 3/3] Linux: Add basic support for scanned tasks to the linux plugins psaux, elfs, and lsof --- volatility3/framework/plugins/linux/elfs.py | 61 ++++++++++++++------ volatility3/framework/plugins/linux/lsof.py | 40 +++++++++++-- volatility3/framework/plugins/linux/psaux.py | 48 ++++++++++++--- 3 files changed, 116 insertions(+), 33 deletions(-) diff --git a/volatility3/framework/plugins/linux/elfs.py b/volatility3/framework/plugins/linux/elfs.py index e688ecb424..4a3349852f 100644 --- a/volatility3/framework/plugins/linux/elfs.py +++ b/volatility3/framework/plugins/linux/elfs.py @@ -14,7 +14,7 @@ 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 +from volatility3.plugins.linux import pslist, psscan vollog = logging.getLogger(__name__) @@ -36,6 +36,9 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] requirements.PluginRequirement( name="pslist", plugin=pslist.PsList, version=(2, 0, 0) ), + requirements.PluginRequirement( + name="psscan", plugin=psscan.PsScan, version=(1, 1, 0) + ), requirements.ListRequirement( name="pid", description="Filter on specific process IDs", @@ -48,6 +51,12 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] default=False, optional=True, ), + requirements.BooleanRequirement( + name="scan", + description="Scan for processes rather than using pslist", + optional=True, + default=False, + ), ] @classmethod @@ -133,11 +142,9 @@ def _generator(self, tasks): proc_layer_name = task.add_process_layer() if not proc_layer_name: continue - proc_layer = self.context.layers[proc_layer_name] name = utility.array_to_string(task.comm) - for vma in task.mm.get_vma_iter(): hdr = proc_layer.read(vma.vm_start, 4, pad=True) if not ( @@ -180,18 +187,36 @@ def _generator(self, tasks): def run(self): filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) - return renderers.TreeGrid( - [ - ("PID", int), - ("Process", str), - ("Start", format_hints.Hex), - ("End", format_hints.Hex), - ("File Path", str), - ("File Output", str), - ], - self._generator( - pslist.PsList.list_tasks( - self.context, self.config["kernel"], filter_func=filter_func - ) - ), - ) + tree_grid_fields = [ + ("PID", int), + ("Process", str), + ("Start", format_hints.Hex), + ("End", format_hints.Hex), + ("File Path", str), + ("File Output", str), + ] + + if self.config.get("scan", None) == True: + vmlinux_module_name = self.config["kernel"] + vmlinux = self.context.modules[vmlinux_module_name] + return renderers.TreeGrid( + tree_grid_fields, + self._generator( + psscan.PsScan.scan_tasks( + self.context, + vmlinux_module_name, + vmlinux.layer_name, + filter_func=filter_func, + ) + ), + ) + + else: + return renderers.TreeGrid( + tree_grid_fields, + self._generator( + pslist.PsList.list_tasks( + self.context, self.config["kernel"], filter_func=filter_func + ) + ), + ) diff --git a/volatility3/framework/plugins/linux/lsof.py b/volatility3/framework/plugins/linux/lsof.py index d970ad8a95..9a49bd4890 100644 --- a/volatility3/framework/plugins/linux/lsof.py +++ b/volatility3/framework/plugins/linux/lsof.py @@ -11,7 +11,7 @@ from volatility3.framework.interfaces import plugins from volatility3.framework.objects import utility from volatility3.framework.symbols import linux -from volatility3.plugins.linux import pslist +from volatility3.plugins.linux import pslist, psscan vollog = logging.getLogger(__name__) @@ -21,7 +21,7 @@ class Lsof(plugins.PluginInterface): _required_framework_version = (2, 0, 0) - _version = (1, 1, 0) + _version = (1, 2, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -34,6 +34,9 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] requirements.PluginRequirement( name="pslist", plugin=pslist.PsList, version=(2, 0, 0) ), + requirements.PluginRequirement( + name="psscan", plugin=psscan.PsScan, version=(1, 1, 0) + ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) ), @@ -43,6 +46,12 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] element_type=int, optional=True, ), + requirements.BooleanRequirement( + name="scan", + description="Scan for processes rather than using pslist", + optional=True, + default=False, + ), ] @classmethod @@ -51,9 +60,25 @@ def list_fds( context: interfaces.context.ContextInterface, symbol_table: str, filter_func: Callable[[int], bool] = lambda _: False, + scan: bool = False, ): linuxutils_symbol_table = None # type: ignore - for task in pslist.PsList.list_tasks(context, symbol_table, filter_func): + + # select the function used to find task objects + if scan: + vmlinux = context.modules[symbol_table] + task_finder_function = psscan.PsScan.scan_tasks( + context, + symbol_table, + vmlinux.layer_name, + filter_func=filter_func, + ) + else: + task_finder_function = pslist.PsList.list_tasks( + context, symbol_table, filter_func + ) + + for task in task_finder_function: if linuxutils_symbol_table is None: if constants.BANG not in task.vol.type_name: raise ValueError("Task is not part of a symbol table") @@ -69,10 +94,10 @@ def list_fds( for fd_fields in fd_generator: yield pid, task_comm, task, fd_fields - def _generator(self, pids, symbol_table): + def _generator(self, pids, symbol_table, scan): filter_func = pslist.PsList.create_pid_filter(pids) fds_generator = self.list_fds( - self.context, symbol_table, filter_func=filter_func + self.context, symbol_table, filter_func=filter_func, scan=scan ) for pid, task_comm, _task, fd_fields in fds_generator: @@ -84,6 +109,9 @@ def _generator(self, pids, symbol_table): def run(self): pids = self.config.get("pid", None) symbol_table = self.config["kernel"] + scan = self.config.get("scan", None) tree_grid_args = [("PID", int), ("Process", str), ("FD", int), ("Path", str)] - return renderers.TreeGrid(tree_grid_args, self._generator(pids, symbol_table)) + return renderers.TreeGrid( + tree_grid_args, self._generator(pids, symbol_table, scan) + ) diff --git a/volatility3/framework/plugins/linux/psaux.py b/volatility3/framework/plugins/linux/psaux.py index a4a23498fe..9d533b8f50 100644 --- a/volatility3/framework/plugins/linux/psaux.py +++ b/volatility3/framework/plugins/linux/psaux.py @@ -8,7 +8,7 @@ from volatility3.framework.configuration import requirements from volatility3.framework.interfaces import plugins from volatility3.framework.objects import utility -from volatility3.plugins.linux import pslist +from volatility3.plugins.linux import pslist, psscan class PsAux(plugins.PluginInterface): @@ -28,12 +28,21 @@ def get_requirements(cls): requirements.PluginRequirement( name="pslist", plugin=pslist.PsList, version=(2, 0, 0) ), + requirements.PluginRequirement( + name="psscan", plugin=psscan.PsScan, version=(1, 1, 0) + ), requirements.ListRequirement( name="pid", description="Filter on specific process IDs", element_type=int, optional=True, ), + requirements.BooleanRequirement( + name="scan", + description="Scan for processes rather than using pslist", + optional=True, + default=False, + ), ] def _get_command_line_args( @@ -80,6 +89,9 @@ def _get_command_line_args( s = argv.decode().split("\x00") args = " ".join(s) else: + # TODO: when scanning for tasks many are in EXIT_ZOMBIE state and have no mm + # even though they were not kernel threads + # kernel thread # [ ] mimics ps on a live system # also helps identify malware masquerading as a kernel thread, which is fairly common @@ -112,11 +124,29 @@ def _generator(self, tasks): def run(self): filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) - return renderers.TreeGrid( - [("PID", int), ("PPID", int), ("COMM", str), ("ARGS", str)], - self._generator( - pslist.PsList.list_tasks( - self.context, self.config["kernel"], filter_func=filter_func - ) - ), - ) + tree_grid_fields = [("PID", int), ("PPID", int), ("COMM", str), ("ARGS", str)] + + if self.config.get("scan", None) == True: + vmlinux_module_name = self.config["kernel"] + vmlinux = self.context.modules[vmlinux_module_name] + return renderers.TreeGrid( + tree_grid_fields, + self._generator( + psscan.PsScan.scan_tasks( + self.context, + vmlinux_module_name, + vmlinux.layer_name, + filter_func=filter_func, + ) + ), + ) + + else: + return renderers.TreeGrid( + tree_grid_fields, + self._generator( + pslist.PsList.list_tasks( + self.context, self.config["kernel"], filter_func=filter_func + ) + ), + )