mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-08-30 19:59:46 +02:00
Merge pull request #600 from gcmoreira/linux_pslist_pstree_improvements
Linux PSList and PSTree plugins improvements
This commit is contained in:
@@ -11,3 +11,6 @@ KERNEL_NAME = "__kernel__"
|
||||
# arch/x86/include/asm/page_types.h
|
||||
PAGE_SHIFT = 12
|
||||
"""The value hard coded from the Linux Kernel (hence not extracted from the layer itself)"""
|
||||
|
||||
# include/linux/sched.h
|
||||
PF_KTHREAD = 0x00200000 # I'm a kernel thread
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
|
||||
# This file is Copyright 2021 Volatility Foundation and licensed under the Volatility Software License 1.0
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
from typing import Callable, Iterable, List, Any
|
||||
from typing import Callable, Iterable, List, Any, Tuple
|
||||
|
||||
from volatility3.framework import renderers, interfaces
|
||||
from volatility3.framework.configuration import requirements
|
||||
@@ -14,7 +14,7 @@ class PsList(interfaces.plugins.PluginInterface):
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
_version = (2, 0, 0)
|
||||
_version = (2, 1, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -24,7 +24,15 @@ class PsList(interfaces.plugins.PluginInterface):
|
||||
requirements.ListRequirement(name = 'pid',
|
||||
description = 'Filter on specific process IDs',
|
||||
element_type = int,
|
||||
optional = True)
|
||||
optional = True),
|
||||
requirements.BooleanRequirement(name="threads",
|
||||
description="Include user threads",
|
||||
optional=True,
|
||||
default=False),
|
||||
requirements.BooleanRequirement(name="decorate_comm",
|
||||
description="Show `user threads` comm in curly brackets, and `kernel threads` comm in square brackets",
|
||||
optional=True,
|
||||
default=False),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
@@ -49,31 +57,76 @@ class PsList(interfaces.plugins.PluginInterface):
|
||||
else:
|
||||
return lambda _: False
|
||||
|
||||
def _generator(self):
|
||||
def _get_task_fields(
|
||||
self,
|
||||
task: interfaces.objects.ObjectInterface,
|
||||
decorate_comm: bool = False) -> Tuple[int, int, int, str]:
|
||||
"""Extract the fields needed for the final output
|
||||
|
||||
Args:
|
||||
task: A task object from where to get the fields.
|
||||
decorate_comm: If True, it decorates the comm string of
|
||||
- User threads: in curly brackets,
|
||||
- Kernel threads: in square brackets
|
||||
Defaults to False.
|
||||
Returns:
|
||||
A tuple with the fields to show in the plugin output.
|
||||
"""
|
||||
pid = task.tgid
|
||||
tid = task.pid
|
||||
ppid = task.parent.tgid if task.parent else 0
|
||||
name = utility.array_to_string(task.comm)
|
||||
if decorate_comm:
|
||||
if task.is_kernel_thread:
|
||||
name = f"[{name}]"
|
||||
elif task.is_user_thread:
|
||||
name = f"{{{name}}}"
|
||||
|
||||
task_fields = (format_hints.Hex(task.vol.offset), pid, tid, ppid, name)
|
||||
return task_fields
|
||||
|
||||
def _generator(
|
||||
self,
|
||||
pid_filter: Callable[[Any], bool],
|
||||
include_threads: bool = False,
|
||||
decorate_comm: bool = False):
|
||||
"""Generates the tasks list.
|
||||
|
||||
Args:
|
||||
pid_filter: A function which takes a process object and returns True if the process should be ignored/filtered
|
||||
include_threads: If True, the output will also show the user threads
|
||||
If False, only the thread group leaders will be shown
|
||||
Defaults to False.
|
||||
decorate_comm: If True, it decorates the comm string of
|
||||
- User threads: in curly brackets,
|
||||
- Kernel threads: in square brackets
|
||||
Defaults to False.
|
||||
Yields:
|
||||
Each rows
|
||||
"""
|
||||
for task in self.list_tasks(self.context,
|
||||
self.config['kernel'],
|
||||
filter_func = self.create_pid_filter(self.config.get('pid', None))):
|
||||
pid = task.pid
|
||||
ppid = 0
|
||||
if task.parent:
|
||||
ppid = task.parent.pid
|
||||
name = utility.array_to_string(task.comm)
|
||||
yield (0, (format_hints.Hex(task.vol.offset), name, pid, ppid))
|
||||
pid_filter,
|
||||
include_threads):
|
||||
row = self._get_task_fields(task, decorate_comm)
|
||||
yield (0, row)
|
||||
|
||||
@classmethod
|
||||
def list_tasks(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
vmlinux_module_name: str,
|
||||
filter_func: Callable[[int], bool] = lambda _: False) -> Iterable[interfaces.objects.ObjectInterface]:
|
||||
filter_func: Callable[[int], bool] = lambda _: False,
|
||||
include_threads: bool = False) -> Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""Lists all the tasks in the primary layer.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
vmlinux_module_name: The name of the kernel module on which to operate
|
||||
|
||||
filter_func: A function which takes a process object and returns True if the process should be ignored/filtered
|
||||
include_threads: If True, it will also return user threads.
|
||||
Yields:
|
||||
Process objects
|
||||
Task objects
|
||||
"""
|
||||
vmlinux = context.modules[vmlinux_module_name]
|
||||
|
||||
@@ -81,8 +134,19 @@ class PsList(interfaces.plugins.PluginInterface):
|
||||
|
||||
# Note that the init_task itself is not yielded, since "ps" also never shows it.
|
||||
for task in init_task.tasks:
|
||||
if not filter_func(task):
|
||||
yield task
|
||||
if filter_func(task):
|
||||
continue
|
||||
|
||||
yield task
|
||||
|
||||
if include_threads:
|
||||
yield from task.get_threads()
|
||||
|
||||
def run(self):
|
||||
return renderers.TreeGrid([("OFFSET (V)", format_hints.Hex), ("COMM", str), ("PID", int), ("PPID", int)], self._generator())
|
||||
pids = self.config.get('pid')
|
||||
include_threads = self.config.get('threads')
|
||||
decorate_comm = self.config.get('decorate_comm')
|
||||
filter_func = self.create_pid_filter(pids)
|
||||
|
||||
columns = [("OFFSET (V)", format_hints.Hex), ("PID", int), ("TID", int), ("PPID", int), ("COMM", str)]
|
||||
return renderers.TreeGrid(columns, self._generator(filter_func, include_threads, decorate_comm))
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
|
||||
# This file is Copyright 2021 Volatility Foundation and licensed under the Volatility Software License 1.0
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.plugins.linux import pslist
|
||||
|
||||
|
||||
@@ -12,44 +11,74 @@ class PsTree(pslist.PsList):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._processes = {}
|
||||
self._tasks = {}
|
||||
self._levels = {}
|
||||
self._children = {}
|
||||
|
||||
def find_level(self, pid):
|
||||
"""Finds how deep the pid is in the processes list."""
|
||||
seen = set([])
|
||||
seen.add(pid)
|
||||
level = 0
|
||||
proc = self._processes.get(pid, None)
|
||||
while proc is not None and proc.parent != 0 and proc.parent.pid not in seen:
|
||||
ppid = int(proc.parent.pid)
|
||||
def find_level(self, pid: int) -> None:
|
||||
"""Finds how deep the PID is in the tasks hierarchy.
|
||||
|
||||
child_list = self._children.get(ppid, set([]))
|
||||
Args:
|
||||
pid: PID to find the level in the hierachy
|
||||
"""
|
||||
seen = set([pid])
|
||||
level = 0
|
||||
proc = self._tasks.get(pid)
|
||||
while proc and proc.parent and proc.parent.pid not in seen:
|
||||
if proc.is_thread_group_leader:
|
||||
parent_pid = proc.parent.pid
|
||||
else:
|
||||
parent_pid = proc.tgid
|
||||
|
||||
child_list = self._children.setdefault(parent_pid, set())
|
||||
child_list.add(proc.pid)
|
||||
self._children[ppid] = child_list
|
||||
proc = self._processes.get(ppid, None)
|
||||
|
||||
proc = self._tasks.get(parent_pid)
|
||||
level += 1
|
||||
|
||||
self._levels[pid] = level
|
||||
|
||||
def _generator(self):
|
||||
"""Generates the."""
|
||||
def _generator(
|
||||
self,
|
||||
pid_filter,
|
||||
include_threads: bool = False,
|
||||
decorate_com: bool = False):
|
||||
"""Generates the tasks hierarchy tree.
|
||||
|
||||
Args:
|
||||
pid_filter: A function which takes a process object and returns True if the process should be ignored/filtered
|
||||
include_threads: If True, the output will also show the user threads
|
||||
If False, only the thread group leaders will be shown
|
||||
Defaults to False.
|
||||
decorate_comm: If True, it decorates the comm string of
|
||||
- User threads: in curly brackets,
|
||||
- Kernel threads: in square brackets
|
||||
Defaults to False.
|
||||
Yields:
|
||||
Each rows
|
||||
"""
|
||||
vmlinux = self.context.modules[self.config['kernel']]
|
||||
for proc in self.list_tasks(self.context, vmlinux.name):
|
||||
self._processes[proc.pid] = proc
|
||||
for proc in self.list_tasks(self.context,
|
||||
vmlinux.name,
|
||||
filter_func=pid_filter,
|
||||
include_threads=include_threads):
|
||||
self._tasks[proc.pid] = proc
|
||||
|
||||
# Build the child/level maps
|
||||
for pid in self._processes:
|
||||
for pid in self._tasks:
|
||||
self.find_level(pid)
|
||||
|
||||
def yield_processes(pid):
|
||||
proc = self._processes[pid]
|
||||
row = (proc.pid, proc.parent.pid, utility.array_to_string(proc.comm))
|
||||
task = self._tasks[pid]
|
||||
|
||||
yield (self._levels[pid] - 1, row)
|
||||
for child_pid in self._children.get(pid, []):
|
||||
row = self._get_task_fields(task, decorate_com)
|
||||
|
||||
tid = task.pid
|
||||
yield (self._levels[tid] - 1, row)
|
||||
|
||||
for child_pid in sorted(self._children.get(tid, [])):
|
||||
yield from yield_processes(child_pid)
|
||||
|
||||
for pid in self._levels:
|
||||
if self._levels[pid] == 1:
|
||||
for pid, level in self._levels.items():
|
||||
if level == 1:
|
||||
yield from yield_processes(pid)
|
||||
|
||||
@@ -266,4 +266,4 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
|
||||
while list_start:
|
||||
list_struct = vmlinux.object(object_type = struct_name, offset = list_start.vol.offset)
|
||||
yield list_struct
|
||||
list_start = getattr(list_struct, list_member)
|
||||
list_start = getattr(list_struct, list_member)
|
||||
@@ -201,15 +201,42 @@ class task_struct(generic.GenericIntelProcess):
|
||||
|
||||
yield (start, end - start)
|
||||
|
||||
@property
|
||||
def is_kernel_thread(self) -> bool:
|
||||
"""Checks if this task is a kernel thread.
|
||||
|
||||
Returns:
|
||||
bool: True, if this task is a kernel thread. Otherwise, False.
|
||||
"""
|
||||
return (self.flags & constants.linux.PF_KTHREAD) != 0
|
||||
|
||||
@property
|
||||
def is_thread_group_leader(self) -> bool:
|
||||
"""Checks if this task is a thread group leader.
|
||||
|
||||
Returns:
|
||||
bool: True, if this task is a thread group leader. Otherwise, False.
|
||||
"""
|
||||
return self.tgid == self.pid
|
||||
|
||||
@property
|
||||
def is_user_thread(self) -> bool:
|
||||
"""Checks if this task is a user thread.
|
||||
|
||||
Returns:
|
||||
bool: True, if this task is a user thread. Otherwise, False.
|
||||
"""
|
||||
return not self.is_kernel_thread and self.tgid != self.pid
|
||||
|
||||
def get_threads(self) -> Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""Returns a list of the task_struct based on the list_head
|
||||
"""Returns a list of the task_struct based on the list_head
|
||||
thread_node structure."""
|
||||
|
||||
task_symbol_table_name = self.get_symbol_table_name()
|
||||
|
||||
# iterating through the thread_list from thread_group
|
||||
# this allows iterating through pointers to grab the
|
||||
# threads and using the thread_group offset to get the
|
||||
# this allows iterating through pointers to grab the
|
||||
# threads and using the thread_group offset to get the
|
||||
# corresponding task_struct
|
||||
for task in self.thread_group.to_list(
|
||||
f"{task_symbol_table_name}{constants.BANG}task_struct",
|
||||
|
||||
Reference in New Issue
Block a user