Skip to content

Commit

Permalink
Merge pull request #1288 from gcmoreira/linux_ptrace
Browse files Browse the repository at this point in the history
Add Linux ptrace plugin
  • Loading branch information
ikelos authored Oct 22, 2024
2 parents 7b0cb4f + 620dd4e commit 028502d
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 3 deletions.
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 @@ -383,6 +382,41 @@ def get_threads(self) -> Iterable[interfaces.objects.ObjectInterface]:
threads_seen.add(task.vol.offset)
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

0 comments on commit 028502d

Please sign in to comment.