Skip to content

Commit 70188b0

Browse files
authored
Merge pull request #1317 from gcmoreira/linux_boottime_support
Linux - Boottime support
2 parents 932ea7f + f05a169 commit 70188b0

File tree

4 files changed

+438
-15
lines changed

4 files changed

+438
-15
lines changed

volatility3/framework/constants/linux/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ class ELF_CLASS(IntEnum):
304304
ELFCLASS64 = 2
305305

306306

307+
# PTrace
307308
PT_OPT_FLAG_SHIFT = 3
308309

309310
PTRACE_EVENT_FORK = 1
@@ -341,6 +342,10 @@ def flags(self) -> str:
341342
return str(self).replace(self.__class__.__name__ + ".", "")
342343

343344

345+
# Boot time
346+
NSEC_PER_SEC = 1e9
347+
348+
344349
# Valid sizes for modules. Note that the Linux kernel does not define these values; they
345350
# are based on empirical observations of typical memory allocations for kernel modules.
346351
# We use this to verify that the found module falls within reasonable limits.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
2+
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
3+
#
4+
import datetime
5+
from typing import List, Tuple, Iterable
6+
7+
8+
from volatility3.framework import interfaces, renderers
9+
from volatility3.framework.configuration import requirements
10+
from volatility3.plugins import timeliner
11+
from volatility3.plugins.linux import pslist
12+
13+
14+
class Boottime(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
15+
"""Shows the time the system was started"""
16+
17+
_required_framework_version = (2, 11, 0)
18+
19+
_version = (1, 0, 0)
20+
21+
@classmethod
22+
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
23+
return [
24+
requirements.ModuleRequirement(
25+
name="kernel",
26+
description="Linux kernel",
27+
architectures=["Intel32", "Intel64"],
28+
),
29+
requirements.PluginRequirement(
30+
name="pslist", plugin=pslist.PsList, version=(2, 3, 0)
31+
),
32+
]
33+
34+
@classmethod
35+
def get_time_namespaces_bootime(
36+
cls,
37+
context: interfaces.context.ContextInterface,
38+
vmlinux_module_name: str,
39+
) -> Iterable[Tuple[int, int, int, str, datetime.datetime]]:
40+
"""Enumerates tasks' boot times based on their time namespaces.
41+
42+
Args:
43+
context: The context to retrieve required elements (layers, symbol tables) from
44+
vmlinux_module_name: The name of the kernel module on which to operate
45+
pids: Pid list
46+
unique: Filter unique time namespaces
47+
48+
Yields:
49+
A tuple with the fields to show in the plugin output.
50+
"""
51+
time_namespace_ids = set()
52+
for task in pslist.PsList.list_tasks(context, vmlinux_module_name):
53+
time_namespace_id = task.get_time_namespace_id()
54+
# If it cannot get the time namespace i.e. kernels < 5.6, this still works
55+
# using None to just get the first tasks
56+
if time_namespace_id in time_namespace_ids:
57+
continue
58+
time_namespace_ids.add(time_namespace_id)
59+
boottime = task.get_boottime(root_time_namespace=False)
60+
61+
fields = (
62+
time_namespace_id,
63+
boottime,
64+
)
65+
yield fields
66+
67+
def _generator(self):
68+
for (
69+
time_namespace_id,
70+
boottime,
71+
) in self.get_time_namespaces_bootime(
72+
self.context,
73+
self.config["kernel"],
74+
):
75+
fields = [
76+
time_namespace_id or renderers.NotAvailableValue(),
77+
boottime,
78+
]
79+
yield 0, fields
80+
81+
def generate_timeline(self):
82+
for (
83+
time_namespace_id,
84+
boottime,
85+
) in self.get_time_namespaces_bootime(
86+
self.context,
87+
self.config["kernel"],
88+
):
89+
description = f"System boot time for time namespace {time_namespace_id}"
90+
91+
yield description, timeliner.TimeLinerType.CREATED, boottime
92+
93+
def run(self):
94+
columns = [
95+
("TIME NS", int),
96+
("Boot Time", datetime.datetime),
97+
]
98+
return renderers.TreeGrid(columns, self._generator())

volatility3/framework/plugins/linux/pslist.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# This file is Copyright 2021 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
#
4+
import datetime
45
from typing import Any, Callable, Iterable, List, Tuple
56

67
from volatility3.framework import interfaces, renderers
@@ -9,15 +10,16 @@
910
from volatility3.framework.renderers import format_hints
1011
from volatility3.framework.symbols import intermed
1112
from volatility3.framework.symbols.linux.extensions import elf
13+
from volatility3.plugins import timeliner
1214
from volatility3.plugins.linux import elfs
1315

1416

15-
class PsList(interfaces.plugins.PluginInterface):
17+
class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
1618
"""Lists the processes present in a particular linux memory image."""
1719

1820
_required_framework_version = (2, 0, 0)
1921

20-
_version = (2, 2, 1)
22+
_version = (2, 3, 0)
2123

2224
@classmethod
2325
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -81,7 +83,7 @@ def filter_func(x):
8183
@classmethod
8284
def get_task_fields(
8385
cls, task: interfaces.objects.ObjectInterface, decorate_comm: bool = False
84-
) -> Tuple[int, int, int, str]:
86+
) -> Tuple[int, int, int, int, str, datetime.datetime]:
8587
"""Extract the fields needed for the final output
8688
8789
Args:
@@ -96,13 +98,14 @@ def get_task_fields(
9698
tid = task.pid
9799
ppid = task.parent.tgid if task.parent else 0
98100
name = utility.array_to_string(task.comm)
101+
start_time = task.get_create_time()
99102
if decorate_comm:
100103
if task.is_kernel_thread:
101104
name = f"[{name}]"
102105
elif task.is_user_thread:
103106
name = f"{{{name}}}"
104107

105-
task_fields = (task.vol.offset, pid, tid, ppid, name)
108+
task_fields = (task.vol.offset, pid, tid, ppid, name, start_time)
106109
return task_fields
107110

108111
def _get_file_output(self, task: interfaces.objects.ObjectInterface) -> str:
@@ -177,14 +180,17 @@ def _generator(
177180
else:
178181
file_output = "Disabled"
179182

180-
offset, pid, tid, ppid, name = self.get_task_fields(task, decorate_comm)
183+
offset, pid, tid, ppid, name, creation_time = self.get_task_fields(
184+
task, decorate_comm
185+
)
181186

182187
yield 0, (
183188
format_hints.Hex(offset),
184189
pid,
185190
tid,
186191
ppid,
187192
name,
193+
creation_time or renderers.NotAvailableValue(),
188194
file_output,
189195
)
190196

@@ -233,8 +239,23 @@ def run(self):
233239
("TID", int),
234240
("PPID", int),
235241
("COMM", str),
242+
("CREATION TIME", datetime.datetime),
236243
("File output", str),
237244
]
238245
return renderers.TreeGrid(
239246
columns, self._generator(filter_func, include_threads, decorate_comm, dump)
240247
)
248+
249+
def generate_timeline(self):
250+
pids = self.config.get("pid")
251+
filter_func = self.create_pid_filter(pids)
252+
for task in self.list_tasks(
253+
self.context, self.config["kernel"], filter_func, include_threads=True
254+
):
255+
offset, user_pid, user_tid, _user_ppid, name, creation_time = (
256+
self.get_task_fields(task)
257+
)
258+
259+
description = f"Process {user_pid}/{user_tid} {name} ({offset})"
260+
261+
yield (description, timeliner.TimeLinerType.CREATED, creation_time)

0 commit comments

Comments
 (0)