From 2f8aecc025ec326aa9897c9762d099a51d171910 Mon Sep 17 00:00:00 2001 From: SolitudePy <47316655+SolitudePy@users.noreply.github.com> Date: Wed, 4 Jun 2025 22:13:28 +0300 Subject: [PATCH 01/12] linux_utilities show deleted fd + lsof files_only arg --- volatility3/framework/plugins/linux/lsof.py | 15 +++++++++++++-- volatility3/framework/symbols/linux/__init__.py | 17 +++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/plugins/linux/lsof.py b/volatility3/framework/plugins/linux/lsof.py index 283eabca0..1880bb09e 100644 --- a/volatility3/framework/plugins/linux/lsof.py +++ b/volatility3/framework/plugins/linux/lsof.py @@ -137,6 +137,12 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface): element_type=int, optional=True, ), + requirements.BooleanRequirement( + name="files_only", + description="Include only file descriptors of type file", + optional=True, + default=False, + ), ] @classmethod @@ -145,6 +151,7 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface): context: interfaces.context.ContextInterface, vmlinux_module_name: str, filter_func: Callable[[int], bool] = lambda _: False, + include_files_only: bool = False, ) -> Iterable[FDInternal]: """Enumerates open file descriptors in tasks @@ -167,7 +174,7 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface): linuxutils_symbol_table = task.vol.type_name.split(constants.BANG)[0] fd_generator = linux.LinuxUtilities.files_descriptors_for_process( - context, linuxutils_symbol_table, task + context, linuxutils_symbol_table, task, files_only=include_files_only ) for fd_fields in fd_generator: @@ -175,8 +182,12 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface): def _generator(self, pids, vmlinux_module_name): filter_func = pslist.PsList.create_pid_filter(pids) + include_files_only = self.config.get("files_only") for fd_internal in self.list_fds( - self.context, vmlinux_module_name, filter_func=filter_func + self.context, + vmlinux_module_name, + filter_func=filter_func, + include_files_only=include_files_only, ): fd_user = fd_internal.to_user() yield (0, dataclasses.astuple(fd_user)) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index ac27b7e42..d66d350b1 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -101,6 +101,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): _version = (2, 3, 1) _required_framework_version = (2, 0, 0) + deleted = " (deleted)" framework.require_interface_version(*_required_framework_version) @@ -168,6 +169,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): # vfsmnt can be the vfsmount object itself (>=3.3) or a vfsmount * (<3.3) return "" + inode = dentry.d_inode path_reversed = [] smeared = False while ( @@ -191,6 +193,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): parent = dentry.d_parent dname = dentry.d_name.name_as_str() + # empty dentry names are most likely # the result of smearing if not dname: @@ -204,6 +207,9 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): # path would be /foo/bar/baz, but bar is missing due to smear the results # returned here will show /foo//baz. Note the // for the missing dname. return f" {path}" + print(path, inode.i_nlink) + if inode and inode.is_readable() and inode.is_valid() and inode.i_nlink == 0: + path += LinuxUtilities.deleted return path @classmethod @@ -260,7 +266,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): pre_name = name.dereference().cast( "string", max_length=255, errors="replace" ) - return "/" + pre_name + " (deleted)" + return "/" + pre_name + LinuxUtilities.deleted else: pre_name = "" @@ -301,7 +307,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): return f"{pre_name}:[{inode.i_ino:d}]" @classmethod - def path_for_file(cls, context, task, filp) -> str: + def path_for_file(cls, context, task, filp, files_only) -> str: """Returns a file (or sock pipe) pathname relative to the task's root directory. A 'file' structure doesn't have enough information to properly restore its @@ -340,7 +346,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): except exceptions.InvalidAddressException: dname_is_valid = False - if dname_is_valid: + if dname_is_valid and not files_only: ret = LinuxUtilities._get_new_sock_pipe_path(context, task, filp) else: ret = LinuxUtilities._get_path_file(task, filp) @@ -353,6 +359,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): context: interfaces.context.ContextInterface, symbol_table: str, task: interfaces.objects.ObjectInterface, + files_only: bool = False, ): try: files = task.files @@ -376,7 +383,9 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): for fd_num, filp in enumerate(fds): if filp and filp.is_readable(): - full_path = LinuxUtilities.path_for_file(context, task, filp) + full_path = LinuxUtilities.path_for_file( + context, task, filp, files_only + ) yield fd_num, filp, full_path From bf68bb6590e75569a75bedffc05189fa5d8dddda Mon Sep 17 00:00:00 2001 From: SolitudePy <47316655+SolitudePy@users.noreply.github.com> Date: Wed, 4 Jun 2025 22:18:29 +0300 Subject: [PATCH 02/12] debug print remove --- 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 d66d350b1..dbea14d47 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -207,7 +207,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): # path would be /foo/bar/baz, but bar is missing due to smear the results # returned here will show /foo//baz. Note the // for the missing dname. return f" {path}" - print(path, inode.i_nlink) + if inode and inode.is_readable() and inode.is_valid() and inode.i_nlink == 0: path += LinuxUtilities.deleted return path From e29a01e6826a58b75c9f1f95e06a8e38b521cc21 Mon Sep 17 00:00:00 2001 From: SolitudePy <47316655+SolitudePy@users.noreply.github.com> Date: Wed, 4 Jun 2025 22:20:10 +0300 Subject: [PATCH 03/12] black --- 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 dbea14d47..6de445c6b 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -207,7 +207,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): # path would be /foo/bar/baz, but bar is missing due to smear the results # returned here will show /foo//baz. Note the // for the missing dname. return f" {path}" - + if inode and inode.is_readable() and inode.is_valid() and inode.i_nlink == 0: path += LinuxUtilities.deleted return path From 2efb7f580a6a574fd5c942e3a78082ec94f80023 Mon Sep 17 00:00:00 2001 From: SolitudePy <47316655+SolitudePy@users.noreply.github.com> Date: Wed, 4 Jun 2025 22:27:18 +0300 Subject: [PATCH 04/12] declare default value :(( --- 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 3b9a73e7c..8330d70aa 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1253,7 +1253,7 @@ class vm_area_struct(objects.StructType): def _do_get_name(self, context, task) -> str: if self.vm_file != 0: - fname = linux.LinuxUtilities.path_for_file(context, task, self.vm_file) + fname = linux.LinuxUtilities.path_for_file(context, task, self.vm_file, files_only=False) elif self.vm_start <= task.mm.start_brk and self.vm_end >= task.mm.brk: fname = "[heap]" elif self.vm_start <= task.mm.start_stack <= self.vm_end: From e0dc64938463722e3465a7dbfdd09bd053b66849 Mon Sep 17 00:00:00 2001 From: SolitudePy <47316655+SolitudePy@users.noreply.github.com> Date: Wed, 4 Jun 2025 22:28:27 +0300 Subject: [PATCH 05/12] black --- volatility3/framework/symbols/linux/extensions/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 8330d70aa..318424d5b 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1253,7 +1253,9 @@ class vm_area_struct(objects.StructType): def _do_get_name(self, context, task) -> str: if self.vm_file != 0: - fname = linux.LinuxUtilities.path_for_file(context, task, self.vm_file, files_only=False) + fname = linux.LinuxUtilities.path_for_file( + context, task, self.vm_file, files_only=False + ) elif self.vm_start <= task.mm.start_brk and self.vm_end >= task.mm.brk: fname = "[heap]" elif self.vm_start <= task.mm.start_stack <= self.vm_end: From 23e4d35017b7ed3f73458d862c77cdc471e5a1bf Mon Sep 17 00:00:00 2001 From: SolitudePy <47316655+SolitudePy@users.noreply.github.com> Date: Wed, 11 Jun 2025 19:44:50 +0300 Subject: [PATCH 06/12] prepend for deleted sock & file instead of (deleted) --- volatility3/framework/symbols/linux/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 6de445c6b..b3a8935c6 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -101,7 +101,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): _version = (2, 3, 1) _required_framework_version = (2, 0, 0) - deleted = " (deleted)" + deleted = "" framework.require_interface_version(*_required_framework_version) @@ -209,7 +209,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): return f" {path}" if inode and inode.is_readable() and inode.is_valid() and inode.i_nlink == 0: - path += LinuxUtilities.deleted + path = f"{LinuxUtilities.deleted} {path}" return path @classmethod @@ -266,7 +266,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): pre_name = name.dereference().cast( "string", max_length=255, errors="replace" ) - return "/" + pre_name + LinuxUtilities.deleted + return f"{LinuxUtilities.deleted} /{pre_name}" else: pre_name = "" From 0a8f2cd23e15d046a37bb986c6394e7bb04d9ec2 Mon Sep 17 00:00:00 2001 From: SolitudePy <47316655+SolitudePy@users.noreply.github.com> Date: Wed, 11 Jun 2025 19:47:27 +0300 Subject: [PATCH 07/12] files_only default arg & minor version bump --- volatility3/framework/symbols/linux/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index b3a8935c6..8e1b94a0a 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -99,7 +99,7 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable): class LinuxUtilities(interfaces.configuration.VersionableInterface): """Class with multiple useful linux functions.""" - _version = (2, 3, 1) + _version = (2, 4, 0) _required_framework_version = (2, 0, 0) deleted = "" @@ -307,7 +307,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): return f"{pre_name}:[{inode.i_ino:d}]" @classmethod - def path_for_file(cls, context, task, filp, files_only) -> str: + def path_for_file(cls, context, task, filp, files_only=False) -> str: """Returns a file (or sock pipe) pathname relative to the task's root directory. A 'file' structure doesn't have enough information to properly restore its From c64c6229d1db02f880d35be479410b2c58b31847 Mon Sep 17 00:00:00 2001 From: SolitudePy <47316655+SolitudePy@users.noreply.github.com> Date: Wed, 11 Jun 2025 19:58:18 +0300 Subject: [PATCH 08/12] move config arg to run() --- volatility3/framework/plugins/linux/lsof.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/linux/lsof.py b/volatility3/framework/plugins/linux/lsof.py index 1880bb09e..d807d444e 100644 --- a/volatility3/framework/plugins/linux/lsof.py +++ b/volatility3/framework/plugins/linux/lsof.py @@ -180,9 +180,9 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface): for fd_fields in fd_generator: yield FDInternal(task=task, fd_fields=fd_fields) - def _generator(self, pids, vmlinux_module_name): + def _generator(self, pids, vmlinux_module_name, include_files_only): filter_func = pslist.PsList.create_pid_filter(pids) - include_files_only = self.config.get("files_only") + for fd_internal in self.list_fds( self.context, vmlinux_module_name, @@ -195,6 +195,7 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface): def run(self): pids = self.config.get("pid", None) vmlinux_module_name = self.config["kernel"] + include_files_only = self.config.get("files_only") tree_grid_args = [ ("PID", int), @@ -212,7 +213,10 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface): ("Size", int), ] return renderers.TreeGrid( - tree_grid_args, self._generator(pids, vmlinux_module_name) + tree_grid_args, + self._generator( + pids, vmlinux_module_name, include_files_only=include_files_only + ), ) def generate_timeline(self): From 606323f25a13ae5bd73cc07721ad2e59780e4f5d Mon Sep 17 00:00:00 2001 From: SolitudePy <47316655+SolitudePy@users.noreply.github.com> Date: Thu, 12 Jun 2025 21:09:25 +0300 Subject: [PATCH 09/12] remove code for next PR --- volatility3/framework/symbols/linux/__init__.py | 2 +- volatility3/framework/symbols/linux/extensions/__init__.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 8e1b94a0a..19f8fc800 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -266,7 +266,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): pre_name = name.dereference().cast( "string", max_length=255, errors="replace" ) - return f"{LinuxUtilities.deleted} /{pre_name}" + return "/" + pre_name + " (deleted)" else: pre_name = "" diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 318424d5b..3b9a73e7c 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1253,9 +1253,7 @@ class vm_area_struct(objects.StructType): def _do_get_name(self, context, task) -> str: if self.vm_file != 0: - fname = linux.LinuxUtilities.path_for_file( - context, task, self.vm_file, files_only=False - ) + fname = linux.LinuxUtilities.path_for_file(context, task, self.vm_file) elif self.vm_start <= task.mm.start_brk and self.vm_end >= task.mm.brk: fname = "[heap]" elif self.vm_start <= task.mm.start_stack <= self.vm_end: From c4717075b68ce70ae9d01261165322a01a02928c Mon Sep 17 00:00:00 2001 From: SolitudePy <47316655+SolitudePy@users.noreply.github.com> Date: Thu, 12 Jun 2025 21:27:19 +0300 Subject: [PATCH 10/12] create potential smear tag --- volatility3/framework/symbols/linux/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 19f8fc800..856be961e 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -102,6 +102,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): _version = (2, 4, 0) _required_framework_version = (2, 0, 0) deleted = "" + smear = "" framework.require_interface_version(*_required_framework_version) @@ -206,7 +207,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): # if there is smear the missing dname will be empty. e.g. if the normal # path would be /foo/bar/baz, but bar is missing due to smear the results # returned here will show /foo//baz. Note the // for the missing dname. - return f" {path}" + return f"{LinuxUtilities.smear} {path}" if inode and inode.is_readable() and inode.is_valid() and inode.i_nlink == 0: path = f"{LinuxUtilities.deleted} {path}" From 7ed7d43c424010a9e5e1875854189299a5c52cc8 Mon Sep 17 00:00:00 2001 From: SolitudePy <47316655+SolitudePy@users.noreply.github.com> Date: Mon, 23 Jun 2025 10:18:47 +0300 Subject: [PATCH 11/12] plugins: bump lsof minor version --- volatility3/framework/plugins/linux/lsof.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/lsof.py b/volatility3/framework/plugins/linux/lsof.py index d807d444e..4b6f011ea 100644 --- a/volatility3/framework/plugins/linux/lsof.py +++ b/volatility3/framework/plugins/linux/lsof.py @@ -110,7 +110,7 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface): """Lists open files for each processes.""" _required_framework_version = (2, 0, 0) - _version = (2, 0, 2) + _version = (2, 1, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: From eb790083e2875f63dc14a9a9fb52f5088114f08e Mon Sep 17 00:00:00 2001 From: SolitudePy <47316655+SolitudePy@users.noreply.github.com> Date: Fri, 11 Jul 2025 15:26:03 +0300 Subject: [PATCH 12/12] plugins: lsof change back to (deleted) --- volatility3/framework/symbols/linux/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 856be961e..19fb8f1d4 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -101,7 +101,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): _version = (2, 4, 0) _required_framework_version = (2, 0, 0) - deleted = "" + deleted = "(deleted)" smear = "" framework.require_interface_version(*_required_framework_version) @@ -210,7 +210,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): return f"{LinuxUtilities.smear} {path}" if inode and inode.is_readable() and inode.is_valid() and inode.i_nlink == 0: - path = f"{LinuxUtilities.deleted} {path}" + path = f" {path} {LinuxUtilities.deleted}" return path @classmethod