Skip to content

Commit 4ac261c

Browse files
authored
Merge pull request #1057 from eve-mem/linux_fix_pstree
Linux: Update pslist to fix pstree
2 parents 713b5f9 + e2f7e7e commit 4ac261c

File tree

2 files changed

+149
-61
lines changed

2 files changed

+149
-61
lines changed

volatility3/framework/plugins/linux/pslist.py

Lines changed: 74 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +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-
from typing import Any, Callable, Iterable, List
4+
from typing import Any, Callable, Iterable, List, Tuple
55

66
from volatility3.framework import interfaces, renderers
77
from volatility3.framework.configuration import requirements
@@ -17,7 +17,7 @@ class PsList(interfaces.plugins.PluginInterface):
1717

1818
_required_framework_version = (2, 0, 0)
1919

20-
_version = (2, 1, 0)
20+
_version = (2, 2, 0)
2121

2222
@classmethod
2323
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -78,6 +78,72 @@ def filter_func(x):
7878
else:
7979
return lambda _: False
8080

81+
@classmethod
82+
def get_task_fields(
83+
cls, task: interfaces.objects.ObjectInterface, decorate_comm: bool = False
84+
) -> Tuple[int, int, int, str]:
85+
"""Extract the fields needed for the final output
86+
Args:
87+
task: A task object from where to get the fields.
88+
decorate_comm: If True, it decorates the comm string of
89+
- User threads: in curly brackets,
90+
- Kernel threads: in square brackets
91+
Defaults to False.
92+
Returns:
93+
A tuple with the fields to show in the plugin output.
94+
"""
95+
pid = task.tgid
96+
tid = task.pid
97+
ppid = task.parent.tgid if task.parent else 0
98+
name = utility.array_to_string(task.comm)
99+
if decorate_comm:
100+
if task.is_kernel_thread:
101+
name = f"[{name}]"
102+
elif task.is_user_thread:
103+
name = f"{{{name}}}"
104+
105+
task_fields = (task.vol.offset, pid, tid, ppid, name)
106+
return task_fields
107+
108+
def _get_file_output(self, task: interfaces.objects.ObjectInterface) -> str:
109+
"""Extract the elf for the process if requested
110+
Args:
111+
task: A task object to extract from.
112+
Returns:
113+
A string showing the results of the extraction, either
114+
the filename used or an error.
115+
"""
116+
elf_table_name = intermed.IntermediateSymbolTable.create(
117+
self.context,
118+
self.config_path,
119+
"linux",
120+
"elf",
121+
class_types=elf.class_types,
122+
)
123+
proc_layer_name = task.add_process_layer()
124+
if not proc_layer_name:
125+
# if we can't build a proc layer we can't
126+
# extract the elf
127+
return renderers.NotApplicableValue()
128+
else:
129+
# Find the vma that belongs to the main ELF of the process
130+
file_output = "Error outputting file"
131+
for v in task.mm.get_mmap_iter():
132+
if v.vm_start == task.mm.start_code:
133+
file_handle = elfs.Elfs.elf_dump(
134+
self.context,
135+
proc_layer_name,
136+
elf_table_name,
137+
v,
138+
task,
139+
self.open,
140+
)
141+
if file_handle:
142+
file_output = str(file_handle.preferred_filename)
143+
file_handle.close()
144+
break
145+
return file_output
146+
81147
def _generator(
82148
self,
83149
pid_filter: Callable[[Any], bool],
@@ -104,49 +170,15 @@ def _generator(
104170
for task in self.list_tasks(
105171
self.context, self.config["kernel"], pid_filter, include_threads
106172
):
107-
elf_table_name = intermed.IntermediateSymbolTable.create(
108-
self.context,
109-
self.config_path,
110-
"linux",
111-
"elf",
112-
class_types=elf.class_types,
113-
)
114-
file_output = "Disabled"
115173
if dump:
116-
proc_layer_name = task.add_process_layer()
117-
if not proc_layer_name:
118-
continue
119-
120-
# Find the vma that belongs to the main ELF of the process
121-
file_output = "Error outputting file"
122-
123-
for v in task.mm.get_mmap_iter():
124-
if v.vm_start == task.mm.start_code:
125-
file_handle = elfs.Elfs.elf_dump(
126-
self.context,
127-
proc_layer_name,
128-
elf_table_name,
129-
v,
130-
task,
131-
self.open,
132-
)
133-
if file_handle:
134-
file_output = str(file_handle.preferred_filename)
135-
file_handle.close()
136-
break
137-
138-
pid = task.tgid
139-
tid = task.pid
140-
ppid = task.parent.tgid if task.parent else 0
141-
name = utility.array_to_string(task.comm)
142-
if decorate_comm:
143-
if task.is_kernel_thread:
144-
name = f"[{name}]"
145-
elif task.is_user_thread:
146-
name = f"{{{name}}}"
174+
file_output = self._get_file_output(task)
175+
else:
176+
file_output = "Disabled"
177+
178+
offset, pid, tid, ppid, name = self.get_task_fields(task, decorate_comm)
147179

148180
yield 0, (
149-
format_hints.Hex(task.vol.offset),
181+
format_hints.Hex(offset),
150182
pid,
151183
tid,
152184
ppid,

volatility3/framework/plugins/linux/pstree.py

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,49 @@
22
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
33
#
44

5+
from volatility3.framework import interfaces, renderers
6+
from volatility3.framework.configuration import requirements
7+
from volatility3.framework.renderers import format_hints
58
from volatility3.plugins.linux import pslist
69

710

8-
class PsTree(pslist.PsList):
11+
class PsTree(interfaces.plugins.PluginInterface):
912
"""Plugin for listing processes in a tree based on their parent process
1013
ID."""
1114

12-
def __init__(self, *args, **kwargs):
13-
super().__init__(*args, **kwargs)
14-
self._tasks = {}
15-
self._levels = {}
16-
self._children = {}
15+
_required_framework_version = (2, 0, 0)
16+
17+
@classmethod
18+
def get_requirements(cls):
19+
# Since we're calling the plugin, make sure we have the plugin's requirements
20+
return [
21+
requirements.ModuleRequirement(
22+
name="kernel",
23+
description="Linux kernel",
24+
architectures=["Intel32", "Intel64"],
25+
),
26+
requirements.PluginRequirement(
27+
name="pslist", plugin=pslist.PsList, version=(2, 2, 0)
28+
),
29+
requirements.ListRequirement(
30+
name="pid",
31+
description="Filter on specific process IDs",
32+
element_type=int,
33+
optional=True,
34+
),
35+
requirements.BooleanRequirement(
36+
name="threads",
37+
description="Include user threads",
38+
optional=True,
39+
default=False,
40+
),
41+
requirements.BooleanRequirement(
42+
name="decorate_comm",
43+
description="Show `user threads` comm in curly brackets, and `kernel threads` comm in square brackets",
44+
optional=True,
45+
default=False,
46+
),
47+
]
1748

1849
def find_level(self, pid: int) -> None:
1950
"""Finds how deep the PID is in the tasks hierarchy.
@@ -39,29 +70,27 @@ def find_level(self, pid: int) -> None:
3970
self._levels[pid] = level
4071

4172
def _generator(
42-
self, pid_filter, include_threads: bool = False, decorate_com: bool = False
73+
self,
74+
tasks: list,
75+
decorate_comm: bool = False,
4376
):
4477
"""Generates the tasks hierarchy tree.
4578
4679
Args:
47-
pid_filter: A function which takes a process object and returns True if the process should be ignored/filtered
48-
include_threads: If True, the output will also show the user threads
49-
If False, only the thread group leaders will be shown
50-
Defaults to False.
80+
tasks: A list of task objects to be displayed
5181
decorate_comm: If True, it decorates the comm string of
5282
- User threads: in curly brackets,
5383
- Kernel threads: in square brackets
5484
Defaults to False.
5585
Yields:
5686
Each rows
5787
"""
58-
vmlinux = self.context.modules[self.config["kernel"]]
59-
for proc in self.list_tasks(
60-
self.context,
61-
vmlinux.name,
62-
filter_func=pid_filter,
63-
include_threads=include_threads,
64-
):
88+
89+
self._tasks = {}
90+
self._levels = {}
91+
self._children = {}
92+
93+
for proc in tasks:
6594
self._tasks[proc.pid] = proc
6695

6796
# Build the child/level maps
@@ -71,7 +100,10 @@ def _generator(
71100
def yield_processes(pid):
72101
task = self._tasks[pid]
73102

74-
row = self._get_task_fields(task, decorate_com)
103+
row = pslist.PsList.get_task_fields(task, decorate_comm)
104+
# update the first element, the offset, in the row tuple to use format_hints.Hex
105+
# as a simple int is returned from get_task_fields.
106+
row = (format_hints.Hex(row[0]),) + row[1:]
75107

76108
tid = task.pid
77109
yield (self._levels[tid] - 1, row)
@@ -82,3 +114,27 @@ def yield_processes(pid):
82114
for pid, level in self._levels.items():
83115
if level == 1:
84116
yield from yield_processes(pid)
117+
118+
def run(self):
119+
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
120+
include_threads = self.config.get("threads")
121+
decorate_comm = self.config.get("decorate_comm")
122+
123+
return renderers.TreeGrid(
124+
[
125+
("OFFSET (V)", format_hints.Hex),
126+
("PID", int),
127+
("TID", int),
128+
("PPID", int),
129+
("COMM", str),
130+
],
131+
self._generator(
132+
pslist.PsList.list_tasks(
133+
self.context,
134+
self.config["kernel"],
135+
filter_func=filter_func,
136+
include_threads=include_threads,
137+
),
138+
decorate_comm=decorate_comm,
139+
),
140+
)

0 commit comments

Comments
 (0)