1
1
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
2
2
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
3
3
#
4
- """A module containing a collection of plugins that produce data typically
5
- found in Linux's /proc file system."""
6
- import logging , datetime
4
+ import logging
5
+ from datetime import datetime
6
+ from dataclasses import dataclass , astuple , field
7
7
from typing import List , Callable
8
8
9
- from volatility3 .framework import renderers , interfaces , constants , exceptions
9
+ from volatility3 .framework import renderers , interfaces , constants
10
10
from volatility3 .framework .configuration import requirements
11
11
from volatility3 .framework .interfaces import plugins
12
12
from volatility3 .framework .objects import utility
17
17
vollog = logging .getLogger (__name__ )
18
18
19
19
20
+ @dataclass
21
+ class FDUser :
22
+ """FD user representation, featuring augmented information and formatted fields.
23
+ This is the data the plugin will eventually display.
24
+ """
25
+
26
+ task_tgid : int
27
+ task_tid : int
28
+ task_comm : str
29
+ fd_num : int
30
+ full_path : str
31
+ device : str = field (default = renderers .NotAvailableValue ())
32
+ inode_num : int = field (default = renderers .NotAvailableValue ())
33
+ inode_type : str = field (default = renderers .NotAvailableValue ())
34
+ file_mode : str = field (default = renderers .NotAvailableValue ())
35
+ change_time : datetime = field (default = renderers .NotAvailableValue ())
36
+ modification_time : datetime = field (default = renderers .NotAvailableValue ())
37
+ access_time : datetime = field (default = renderers .NotAvailableValue ())
38
+ inode_size : int = field (default = renderers .NotAvailableValue ())
39
+
40
+
41
+ @dataclass
42
+ class FDInternal :
43
+ """FD internal representation containing only the core objects
44
+
45
+ Fields:
46
+ task: 'task_truct' object
47
+ fd_fields: FD fields as obtained from LinuxUtilities.files_descriptors_for_process()
48
+ """
49
+
50
+ task : interfaces .objects .ObjectInterface
51
+ fd_fields : tuple [int , int , str ]
52
+
53
+ def to_user (self ) -> FDUser :
54
+ """Augment the FD information to be presented to the user
55
+
56
+ Returns:
57
+ An InodeUser dataclass
58
+ """
59
+ # Ensure all types are atomic immutable. Otherwise, astuple() will take a long
60
+ # time doing a deepcopy of the Volatility objects.
61
+ task_tgid = int (self .task .tgid )
62
+ task_tid = int (self .task .pid )
63
+ task_comm = utility .array_to_string (self .task .comm )
64
+ fd_num , filp , full_path = self .fd_fields
65
+ fd_num = int (fd_num )
66
+ full_path = str (full_path )
67
+ inode = filp .get_inode ()
68
+ if inode :
69
+ superblock_ptr = inode .i_sb
70
+ if superblock_ptr and superblock_ptr .is_readable ():
71
+ device = f"{ superblock_ptr .major } :{ superblock_ptr .minor } "
72
+ else :
73
+ device = renderers .NotAvailableValue ()
74
+
75
+ fd_user = FDUser (
76
+ task_tgid = task_tgid ,
77
+ task_tid = task_tid ,
78
+ task_comm = task_comm ,
79
+ fd_num = fd_num ,
80
+ full_path = full_path ,
81
+ device = device ,
82
+ inode_num = int (inode .i_ino ),
83
+ inode_type = inode .get_inode_type () or renderers .UnparsableValue (),
84
+ file_mode = inode .get_file_mode (),
85
+ change_time = inode .get_change_time (),
86
+ modification_time = inode .get_modification_time (),
87
+ access_time = inode .get_access_time (),
88
+ inode_size = int (inode .i_size ),
89
+ )
90
+ else :
91
+ # We use the dataclasses' default values
92
+ fd_user = FDUser (
93
+ task_tgid = task_tgid ,
94
+ task_tid = task_tid ,
95
+ task_comm = task_comm ,
96
+ fd_num = fd_num ,
97
+ full_path = full_path ,
98
+ )
99
+
100
+ return fd_user
101
+
102
+
20
103
class Lsof (plugins .PluginInterface , timeliner .TimeLinerInterface ):
21
104
"""Lists open files for each processes."""
22
105
23
106
_required_framework_version = (2 , 0 , 0 )
24
- _version = (1 , 2 , 0 )
107
+ _version = (2 , 0 , 0 )
25
108
26
109
@classmethod
27
110
def get_requirements (cls ) -> List [interfaces .configuration .RequirementInterface ]:
@@ -45,126 +128,86 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
45
128
),
46
129
]
47
130
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
-
71
131
@classmethod
72
132
def list_fds (
73
133
cls ,
74
134
context : interfaces .context .ContextInterface ,
75
- symbol_table : str ,
135
+ vmlinux_module_name : str ,
76
136
filter_func : Callable [[int ], bool ] = lambda _ : False ,
77
- ):
137
+ ) -> FDInternal :
138
+ """Enumerates open file descriptors in tasks
139
+
140
+ Args:
141
+ context: The context to retrieve required elements (layers, symbol tables) from
142
+ vmlinux_module_name: The name of the kernel module on which to operate
143
+ filter_func: A function which takes a process object and returns True if the process
144
+ should be ignored/filtered
145
+
146
+ Yields:
147
+ A FDInternal object
148
+ """
78
149
linuxutils_symbol_table = None
79
- for task in pslist .PsList .list_tasks (context , symbol_table , filter_func ):
150
+ for task in pslist .PsList .list_tasks (
151
+ context , vmlinux_module_name , filter_func , include_threads = True
152
+ ):
80
153
if linuxutils_symbol_table is None :
81
154
if constants .BANG not in task .vol .type_name :
82
155
raise ValueError ("Task is not part of a symbol table" )
83
156
linuxutils_symbol_table = task .vol .type_name .split (constants .BANG )[0 ]
84
157
85
- task_comm = utility .array_to_string (task .comm )
86
- pid = int (task .pid )
87
-
88
158
fd_generator = linux .LinuxUtilities .files_descriptors_for_process (
89
159
context , linuxutils_symbol_table , task
90
160
)
91
161
92
162
for fd_fields in fd_generator :
93
- yield pid , task_comm , task , fd_fields
163
+ yield FDInternal ( task = task , fd_fields = fd_fields )
94
164
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
-
112
- def _generator (self , pids , symbol_table ):
165
+ def _generator (self , pids , vmlinux_module_name ):
113
166
filter_func = pslist .PsList .create_pid_filter (pids )
114
- fds_generator = self .list_fds_and_inodes (
115
- self .context , symbol_table , filter_func = filter_func
116
- )
117
-
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
- )
141
- yield (0 , fields )
167
+ for fd_internal in self .list_fds (
168
+ self .context , vmlinux_module_name , filter_func = filter_func
169
+ ):
170
+ fd_user = fd_internal .to_user ()
171
+ yield (0 , astuple (fd_user ))
142
172
143
173
def run (self ):
144
174
pids = self .config .get ("pid" , None )
145
- symbol_table = self .config ["kernel" ]
175
+ vmlinux_module_name = self .config ["kernel" ]
146
176
147
177
tree_grid_args = [
148
178
("PID" , int ),
179
+ ("TID" , int ),
149
180
("Process" , str ),
150
181
("FD" , int ),
151
182
("Path" , str ),
183
+ ("Device" , str ),
152
184
("Inode" , int ),
153
185
("Type" , str ),
154
186
("Mode" , str ),
155
- ("Changed" , datetime . datetime ),
156
- ("Modified" , datetime . datetime ),
157
- ("Accessed" , datetime . datetime ),
187
+ ("Changed" , datetime ),
188
+ ("Modified" , datetime ),
189
+ ("Accessed" , datetime ),
158
190
("Size" , int ),
159
191
]
160
- return renderers .TreeGrid (tree_grid_args , self ._generator (pids , symbol_table ))
192
+ return renderers .TreeGrid (
193
+ tree_grid_args , self ._generator (pids , vmlinux_module_name )
194
+ )
161
195
162
196
def generate_timeline (self ):
163
197
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 ]
198
+ vmlinux_module_name = self .config ["kernel" ]
199
+
200
+ filter_func = pslist .PsList .create_pid_filter (pids )
201
+ for fd_internal in self .list_fds (
202
+ self .context , vmlinux_module_name , filter_func = filter_func
203
+ ):
204
+ fd_user = fd_internal .to_user ()
205
+
206
+ description = (
207
+ f"Process { fd_user .task_comm } ({ fd_user .task_tgid } /{ fd_user .task_tid } ) "
208
+ f"Open '{ fd_user .full_path } '"
209
+ )
210
+
211
+ yield description , timeliner .TimeLinerType .CHANGED , fd_user .change_time
212
+ yield description , timeliner .TimeLinerType .MODIFIED , fd_user .modification_time
213
+ yield description , timeliner .TimeLinerType .ACCESSED , fd_user .access_time
0 commit comments