Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Linux ptrace plugin #1288

Merged
merged 7 commits into from
Oct 22, 2024
39 changes: 38 additions & 1 deletion volatility3/framework/constants/linux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

Linux-specific values that aren't found in debug symbols
"""
from enum import IntEnum
from enum import IntEnum, Flag

KERNEL_NAME = "__kernel__"

Expand Down Expand Up @@ -302,3 +302,40 @@ class ELF_CLASS(IntEnum):
ELFCLASSNONE = 0
ELFCLASS32 = 1
ELFCLASS64 = 2


PT_OPT_FLAG_SHIFT = 3

PTRACE_EVENT_FORK = 1
PTRACE_EVENT_VFORK = 2
PTRACE_EVENT_CLONE = 3
PTRACE_EVENT_EXEC = 4
PTRACE_EVENT_VFORK_DONE = 5
PTRACE_EVENT_EXIT = 6
PTRACE_EVENT_SECCOMP = 7

PTRACE_O_EXITKILL = 1 << 20
PTRACE_O_SUSPEND_SECCOMP = 1 << 21


class PT_FLAGS(Flag):
"PTrace flags"
PT_PTRACED = 0x00001
PT_SEIZED = 0x10000

PT_TRACESYSGOOD = 1 << (PT_OPT_FLAG_SHIFT + 0)
PT_TRACE_FORK = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_FORK)
PT_TRACE_VFORK = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_VFORK)
PT_TRACE_CLONE = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_CLONE)
PT_TRACE_EXEC = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_EXEC)
PT_TRACE_VFORK_DONE = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_VFORK_DONE)
PT_TRACE_EXIT = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_EXIT)
PT_TRACE_SECCOMP = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_SECCOMP)

PT_EXITKILL = PTRACE_O_EXITKILL << PT_OPT_FLAG_SHIFT
PT_SUSPEND_SECCOMP = PTRACE_O_SUSPEND_SECCOMP << PT_OPT_FLAG_SHIFT

@property
def flags(self) -> str:
"""Returns the ptrace flags string"""
return str(self).replace(self.__class__.__name__ + ".", "")
97 changes: 97 additions & 0 deletions volatility3/framework/plugins/linux/ptrace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#

import logging
from typing import List, Iterator

from volatility3.framework import renderers, interfaces
from volatility3.framework.constants.architectures import LINUX_ARCHS
from volatility3.framework.objects import utility
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.plugins.linux import pslist

vollog = logging.getLogger(__name__)


class Ptrace(plugins.PluginInterface):
"""Enumerates ptrace's tracer and tracee tasks"""

_required_framework_version = (2, 10, 0)
_version = (1, 0, 0)

@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=LINUX_ARCHS,
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 2, 0)
),
]

@classmethod
def enumerate_ptrace_tasks(
cls,
context: interfaces.context.ContextInterface,
vmlinux_module_name: str,
) -> Iterator[interfaces.objects.ObjectInterface]:
"""Enumerates ptrace's tracer and tracee tasks

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

Yields:
A task_struct object
"""

tasks = pslist.PsList.list_tasks(
context,
vmlinux_module_name,
filter_func=pslist.PsList.create_pid_filter(),
include_threads=True,
)

for task in tasks:
if task.is_being_ptraced or task.is_ptracing:
yield task

def _generator(self, vmlinux_module_name):
for task in self.enumerate_ptrace_tasks(self.context, vmlinux_module_name):
task_comm = utility.array_to_string(task.comm)
user_pid = task.tgid
user_tid = task.pid
tracer_tid = task.get_ptrace_tracer_tid() or renderers.NotAvailableValue()
tracee_tids = task.get_ptrace_tracee_tids() or [
renderers.NotAvailableValue()
]
flags = task.get_ptrace_tracee_flags() or renderers.NotAvailableValue()

for level, tracee_tid in enumerate(tracee_tids):
fields = [
task_comm,
user_pid,
user_tid,
tracer_tid,
tracee_tid,
flags,
]
yield (level, fields)

def run(self):
vmlinux_module_name = self.config["kernel"]

headers = [
("Process", str),
("PID", int),
("TID", int),
("Tracer TID", int),
("Tracee TID", int),
("Flags", str),
]
return renderers.TreeGrid(headers, self._generator(vmlinux_module_name))
38 changes: 36 additions & 2 deletions volatility3/framework/symbols/linux/extensions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@

from volatility3.framework import constants, exceptions, objects, interfaces, symbols
from volatility3.framework.renderers import conversion
from volatility3.framework.configuration import requirements
from volatility3.framework.constants.linux import SOCK_TYPES, SOCK_FAMILY
from volatility3.framework.constants.linux import IP_PROTOCOLS, IPV6_PROTOCOLS
from volatility3.framework.constants.linux import TCP_STATES, NETLINK_PROTOCOLS
from volatility3.framework.constants.linux import ETH_PROTOCOLS, BLUETOOTH_STATES
from volatility3.framework.constants.linux import BLUETOOTH_PROTOCOLS, SOCKET_STATES
from volatility3.framework.constants.linux import CAPABILITIES
from volatility3.framework.constants.linux import CAPABILITIES, PT_FLAGS
from volatility3.framework.layers import linear
from volatility3.framework.objects import utility
from volatility3.framework.symbols import generic, linux, intermed
Expand Down Expand Up @@ -374,6 +373,41 @@ def get_threads(self) -> Iterable[interfaces.objects.ObjectInterface]:
):
yield task

@property
def is_being_ptraced(self) -> bool:
"""Returns True if this task is being traced using ptrace"""
return self.ptrace != 0

@property
def is_ptracing(self) -> bool:
"""Returns True if this task is tracing other tasks using ptrace"""
is_tracing = (
self.ptraced.next.is_readable()
and self.ptraced.next.dereference().vol.offset != self.ptraced.vol.offset
)
return is_tracing

def get_ptrace_tracer_tid(self) -> Optional[int]:
"""Returns the tracer's TID tracing this task"""
return self.parent.pid if self.is_being_ptraced else None

def get_ptrace_tracee_tids(self) -> List[int]:
"""Returns the list of TIDs being traced by this task"""
task_symbol_table_name = self.get_symbol_table_name()

task_struct_symname = f"{task_symbol_table_name}{constants.BANG}task_struct"
tracing_tid_list = [
task_being_traced.pid
for task_being_traced in self.ptraced.to_list(
task_struct_symname, "ptrace_entry"
)
]
return tracing_tid_list

def get_ptrace_tracee_flags(self) -> Optional[str]:
"""Returns a string with the ptrace flags"""
return PT_FLAGS(self.ptrace).flags if self.is_being_ptraced else None


class fs_struct(objects.StructType):
def get_root_dentry(self):
Expand Down
Loading