Merge branch 'fix/ethread' of https://github.com/Digitalisx/volatility3 into fix/ethread

This commit is contained in:
Donghyun Kim
2022-05-02 10:34:13 +09:00
9 changed files with 175 additions and 58 deletions
+1 -4
View File
@@ -322,10 +322,7 @@ class PrettyTextRenderer(CLIRenderer):
tab_width = 8
while line.find('\t') >= 0:
i = line.find('\t')
if (tab_width > 0):
pad = " " * (tab_width - (i % tab_width))
else:
pad = ""
pad = " " * (tab_width - (i % tab_width))
line = line.replace("\t", pad, 1)
return line
@@ -470,14 +470,14 @@ class ModuleRequirement(interfaces.configuration.ConstructableRequirementInterfa
if req_unsatisfied:
result.update(req_unsatisfied)
if not result:
vollog.log(constants.LOGLEVEL_V, f"IndexError - No configuration provided: {config_path}")
result = {config_path: self}
return result
### NOTE: This validate method has side effects (the dependencies can change)!!!
self._validate_class(context, interfaces.configuration.parent_path(config_path))
vollog.log(constants.LOGLEVEL_V, f"IndexError - No configuration provided: {config_path}")
return {config_path: self}
return result
def construct(self, context: interfaces.context.ContextInterface, config_path: str) -> None:
"""Constructs the appropriate layer and adds it based on the class parameter."""
@@ -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
-3
View File
@@ -3,7 +3,6 @@
#
import functools
import json
import math
from typing import Optional, Dict, Any, Tuple, List, Set
from volatility3.framework import interfaces, exceptions, constants
@@ -131,8 +130,6 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer):
index = 8
section_info = dict()
current_section_id = -1
version_id = -1
name = None
while section_byte != self.QEVM_EOF and index <= base_layer.maximum_address:
section_byte = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned char',
offset = index,
+83 -18
View File
@@ -1,11 +1,12 @@
# 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
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
class PsList(interfaces.plugins.PluginInterface):
@@ -13,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]:
@@ -23,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
@@ -48,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, (pid, ppid, name))
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]
@@ -80,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([("PID", int), ("PPID", int), ("COMM", str)], 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))
+54 -25
View File
@@ -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)
@@ -1,7 +1,6 @@
from volatility3.framework import interfaces, constants
from volatility3.framework import renderers, interfaces, exceptions
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows.extensions import pe
@@ -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",