1- # This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
1+ # This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
22# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
33#
44"""A module containing a collection of plugins that produce data typically
55found in Linux's /proc file system."""
6- import logging
6+ import logging , datetime
77from typing import List , Callable
88
9- from volatility3 .framework import renderers , interfaces , constants
9+ from volatility3 .framework import renderers , interfaces , constants , exceptions
1010from volatility3 .framework .configuration import requirements
1111from volatility3 .framework .interfaces import plugins
1212from volatility3 .framework .objects import utility
1313from volatility3 .framework .symbols import linux
1414from volatility3 .plugins .linux import pslist
15+ from volatility3 .plugins import timeliner
1516
1617vollog = logging .getLogger (__name__ )
1718
1819
19- class Lsof (plugins .PluginInterface ):
20- """Lists all memory maps for all processes."""
20+ class Lsof (plugins .PluginInterface , timeliner . TimeLinerInterface ):
21+ """Lists open files for each processes."""
2122
2223 _required_framework_version = (2 , 0 , 0 )
23-
24- _version = (1 , 1 , 0 )
24+ _version = (1 , 2 , 0 )
2525
2626 @classmethod
2727 def get_requirements (cls ) -> List [interfaces .configuration .RequirementInterface ]:
@@ -45,14 +45,37 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
4545 ),
4646 ]
4747
48+ @classmethod
49+ def get_inode_metadata (cls , filp : interfaces .objects .ObjectInterface ):
50+ try :
51+ dentry = filp .get_dentry ()
52+ if dentry :
53+ inode_object = dentry .d_inode
54+ if inode_object and inode_object .is_valid ():
55+ itype = (
56+ inode_object .get_inode_type () or renderers .NotAvailableValue ()
57+ )
58+ return (
59+ inode_object .i_ino ,
60+ itype ,
61+ inode_object .i_size ,
62+ inode_object .get_file_mode (),
63+ inode_object .get_change_time (),
64+ inode_object .get_modification_time (),
65+ inode_object .get_access_time (),
66+ )
67+ except (exceptions .InvalidAddressException , AttributeError ) as e :
68+ vollog .warning (f"Can't get inode metadata: { e } " )
69+ return None
70+
4871 @classmethod
4972 def list_fds (
5073 cls ,
5174 context : interfaces .context .ContextInterface ,
5275 symbol_table : str ,
5376 filter_func : Callable [[int ], bool ] = lambda _ : False ,
5477 ):
55- linuxutils_symbol_table = None # type: ignore
78+ linuxutils_symbol_table = None
5679 for task in pslist .PsList .list_tasks (context , symbol_table , filter_func ):
5780 if linuxutils_symbol_table is None :
5881 if constants .BANG not in task .vol .type_name :
@@ -69,21 +92,79 @@ def list_fds(
6992 for fd_fields in fd_generator :
7093 yield pid , task_comm , task , fd_fields
7194
95+ @classmethod
96+ def list_fds_and_inodes (
97+ cls ,
98+ context : interfaces .context .ContextInterface ,
99+ symbol_table : str ,
100+ filter_func : Callable [[int ], bool ] = lambda _ : False ,
101+ ):
102+ for pid , task_comm , task , (fd_num , filp , full_path ) in cls .list_fds (
103+ context , symbol_table , filter_func
104+ ):
105+ inode_metadata = cls .get_inode_metadata (filp )
106+ if inode_metadata is None :
107+ inode_metadata = tuple (
108+ interfaces .renderers .BaseAbsentValue () for _ in range (7 )
109+ )
110+ yield pid , task_comm , task , fd_num , filp , full_path , inode_metadata
111+
72112 def _generator (self , pids , symbol_table ):
73113 filter_func = pslist .PsList .create_pid_filter (pids )
74- fds_generator = self .list_fds (
114+ fds_generator = self .list_fds_and_inodes (
75115 self .context , symbol_table , filter_func = filter_func
76116 )
77117
78- for pid , task_comm , _task , fd_fields in fds_generator :
79- fd_num , _filp , full_path = fd_fields
80-
81- fields = (pid , task_comm , fd_num , full_path )
118+ for (
119+ pid ,
120+ task_comm ,
121+ task ,
122+ fd_num ,
123+ filp ,
124+ full_path ,
125+ inode_metadata ,
126+ ) in fds_generator :
127+ inode_num , itype , file_size , imode , ctime , mtime , atime = inode_metadata
128+ fields = (
129+ pid ,
130+ task_comm ,
131+ fd_num ,
132+ full_path ,
133+ inode_num ,
134+ itype ,
135+ imode ,
136+ ctime ,
137+ mtime ,
138+ atime ,
139+ file_size ,
140+ )
82141 yield (0 , fields )
83142
84143 def run (self ):
85144 pids = self .config .get ("pid" , None )
86145 symbol_table = self .config ["kernel" ]
87146
88- tree_grid_args = [("PID" , int ), ("Process" , str ), ("FD" , int ), ("Path" , str )]
147+ tree_grid_args = [
148+ ("PID" , int ),
149+ ("Process" , str ),
150+ ("FD" , int ),
151+ ("Path" , str ),
152+ ("Inode" , int ),
153+ ("Type" , str ),
154+ ("Mode" , str ),
155+ ("Changed" , datetime .datetime ),
156+ ("Modified" , datetime .datetime ),
157+ ("Accessed" , datetime .datetime ),
158+ ("Size" , int ),
159+ ]
89160 return renderers .TreeGrid (tree_grid_args , self ._generator (pids , symbol_table ))
161+
162+ def generate_timeline (self ):
163+ pids = self .config .get ("pid" , None )
164+ symbol_table = self .config ["kernel" ]
165+ for row in self ._generator (pids , symbol_table ):
166+ _depth , row_data = row
167+ description = f'Process { row_data [1 ]} ({ row_data [0 ]} ) Open "{ row_data [3 ]} "'
168+ yield description , timeliner .TimeLinerType .CHANGED , row_data [7 ]
169+ yield description , timeliner .TimeLinerType .MODIFIED , row_data [8 ]
170+ yield description , timeliner .TimeLinerType .ACCESSED , row_data [9 ]
0 commit comments