Skip to content

Commit 9bba628

Browse files
authored
Merge pull request #1213 from forensicxlab/feature/lsof_inodes
Improvement: Linux/lsof
2 parents 8d7edfd + 71cdca5 commit 9bba628

File tree

1 file changed

+95
-14
lines changed
  • volatility3/framework/plugins/linux

1 file changed

+95
-14
lines changed

volatility3/framework/plugins/linux/lsof.py

Lines changed: 95 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
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
55
found in Linux's /proc file system."""
6-
import logging
6+
import logging, datetime
77
from typing import List, Callable
88

9-
from volatility3.framework import renderers, interfaces, constants
9+
from volatility3.framework import renderers, interfaces, constants, exceptions
1010
from volatility3.framework.configuration import requirements
1111
from volatility3.framework.interfaces import plugins
1212
from volatility3.framework.objects import utility
1313
from volatility3.framework.symbols import linux
1414
from volatility3.plugins.linux import pslist
15+
from volatility3.plugins import timeliner
1516

1617
vollog = 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

Comments
 (0)