diff --git a/volatility3/framework/plugins/linux/lsof.py b/volatility3/framework/plugins/linux/lsof.py index 283eabca0..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]: @@ -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,16 +174,20 @@ 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: 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) + 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)) @@ -184,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), @@ -201,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): diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index ac27b7e42..19fb8f1d4 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -99,8 +99,10 @@ 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 = "(deleted)" + smear = "" framework.require_interface_version(*_required_framework_version) @@ -168,6 +170,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 +194,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: @@ -203,7 +207,10 @@ 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" {path} {LinuxUtilities.deleted}" return path @classmethod @@ -301,7 +308,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=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 @@ -340,7 +347,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 +360,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 +384,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