Skip to content

Commit 028502d

Browse files
authored
Merge pull request #1288 from gcmoreira/linux_ptrace
Add Linux ptrace plugin
2 parents 7b0cb4f + 620dd4e commit 028502d

File tree

3 files changed

+171
-3
lines changed

3 files changed

+171
-3
lines changed

volatility3/framework/constants/linux/__init__.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
Linux-specific values that aren't found in debug symbols
77
"""
8-
from enum import IntEnum
8+
from enum import IntEnum, Flag
99

1010
KERNEL_NAME = "__kernel__"
1111

@@ -302,3 +302,40 @@ class ELF_CLASS(IntEnum):
302302
ELFCLASSNONE = 0
303303
ELFCLASS32 = 1
304304
ELFCLASS64 = 2
305+
306+
307+
PT_OPT_FLAG_SHIFT = 3
308+
309+
PTRACE_EVENT_FORK = 1
310+
PTRACE_EVENT_VFORK = 2
311+
PTRACE_EVENT_CLONE = 3
312+
PTRACE_EVENT_EXEC = 4
313+
PTRACE_EVENT_VFORK_DONE = 5
314+
PTRACE_EVENT_EXIT = 6
315+
PTRACE_EVENT_SECCOMP = 7
316+
317+
PTRACE_O_EXITKILL = 1 << 20
318+
PTRACE_O_SUSPEND_SECCOMP = 1 << 21
319+
320+
321+
class PT_FLAGS(Flag):
322+
"PTrace flags"
323+
PT_PTRACED = 0x00001
324+
PT_SEIZED = 0x10000
325+
326+
PT_TRACESYSGOOD = 1 << (PT_OPT_FLAG_SHIFT + 0)
327+
PT_TRACE_FORK = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_FORK)
328+
PT_TRACE_VFORK = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_VFORK)
329+
PT_TRACE_CLONE = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_CLONE)
330+
PT_TRACE_EXEC = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_EXEC)
331+
PT_TRACE_VFORK_DONE = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_VFORK_DONE)
332+
PT_TRACE_EXIT = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_EXIT)
333+
PT_TRACE_SECCOMP = 1 << (PT_OPT_FLAG_SHIFT + PTRACE_EVENT_SECCOMP)
334+
335+
PT_EXITKILL = PTRACE_O_EXITKILL << PT_OPT_FLAG_SHIFT
336+
PT_SUSPEND_SECCOMP = PTRACE_O_SUSPEND_SECCOMP << PT_OPT_FLAG_SHIFT
337+
338+
@property
339+
def flags(self) -> str:
340+
"""Returns the ptrace flags string"""
341+
return str(self).replace(self.__class__.__name__ + ".", "")
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
2+
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
3+
#
4+
5+
import logging
6+
from typing import List, Iterator
7+
8+
from volatility3.framework import renderers, interfaces
9+
from volatility3.framework.constants.architectures import LINUX_ARCHS
10+
from volatility3.framework.objects import utility
11+
from volatility3.framework.configuration import requirements
12+
from volatility3.framework.interfaces import plugins
13+
from volatility3.plugins.linux import pslist
14+
15+
vollog = logging.getLogger(__name__)
16+
17+
18+
class Ptrace(plugins.PluginInterface):
19+
"""Enumerates ptrace's tracer and tracee tasks"""
20+
21+
_required_framework_version = (2, 10, 0)
22+
_version = (1, 0, 0)
23+
24+
@classmethod
25+
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
26+
return [
27+
requirements.ModuleRequirement(
28+
name="kernel",
29+
description="Linux kernel",
30+
architectures=LINUX_ARCHS,
31+
),
32+
requirements.PluginRequirement(
33+
name="pslist", plugin=pslist.PsList, version=(2, 2, 0)
34+
),
35+
]
36+
37+
@classmethod
38+
def enumerate_ptrace_tasks(
39+
cls,
40+
context: interfaces.context.ContextInterface,
41+
vmlinux_module_name: str,
42+
) -> Iterator[interfaces.objects.ObjectInterface]:
43+
"""Enumerates ptrace's tracer and tracee tasks
44+
45+
Args:
46+
context: The context to retrieve required elements (layers, symbol tables) from
47+
vmlinux_module_name: The name of the kernel module on which to operate
48+
49+
Yields:
50+
A task_struct object
51+
"""
52+
53+
tasks = pslist.PsList.list_tasks(
54+
context,
55+
vmlinux_module_name,
56+
filter_func=pslist.PsList.create_pid_filter(),
57+
include_threads=True,
58+
)
59+
60+
for task in tasks:
61+
if task.is_being_ptraced or task.is_ptracing:
62+
yield task
63+
64+
def _generator(self, vmlinux_module_name):
65+
for task in self.enumerate_ptrace_tasks(self.context, vmlinux_module_name):
66+
task_comm = utility.array_to_string(task.comm)
67+
user_pid = task.tgid
68+
user_tid = task.pid
69+
tracer_tid = task.get_ptrace_tracer_tid() or renderers.NotAvailableValue()
70+
tracee_tids = task.get_ptrace_tracee_tids() or [
71+
renderers.NotAvailableValue()
72+
]
73+
flags = task.get_ptrace_tracee_flags() or renderers.NotAvailableValue()
74+
75+
for level, tracee_tid in enumerate(tracee_tids):
76+
fields = [
77+
task_comm,
78+
user_pid,
79+
user_tid,
80+
tracer_tid,
81+
tracee_tid,
82+
flags,
83+
]
84+
yield (level, fields)
85+
86+
def run(self):
87+
vmlinux_module_name = self.config["kernel"]
88+
89+
headers = [
90+
("Process", str),
91+
("PID", int),
92+
("TID", int),
93+
("Tracer TID", int),
94+
("Tracee TID", int),
95+
("Flags", str),
96+
]
97+
return renderers.TreeGrid(headers, self._generator(vmlinux_module_name))

volatility3/framework/symbols/linux/extensions/__init__.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@
1313

1414
from volatility3.framework import constants, exceptions, objects, interfaces, symbols
1515
from volatility3.framework.renderers import conversion
16-
from volatility3.framework.configuration import requirements
1716
from volatility3.framework.constants.linux import SOCK_TYPES, SOCK_FAMILY
1817
from volatility3.framework.constants.linux import IP_PROTOCOLS, IPV6_PROTOCOLS
1918
from volatility3.framework.constants.linux import TCP_STATES, NETLINK_PROTOCOLS
2019
from volatility3.framework.constants.linux import ETH_PROTOCOLS, BLUETOOTH_STATES
2120
from volatility3.framework.constants.linux import BLUETOOTH_PROTOCOLS, SOCKET_STATES
22-
from volatility3.framework.constants.linux import CAPABILITIES
21+
from volatility3.framework.constants.linux import CAPABILITIES, PT_FLAGS
2322
from volatility3.framework.layers import linear
2423
from volatility3.framework.objects import utility
2524
from volatility3.framework.symbols import generic, linux, intermed
@@ -383,6 +382,41 @@ def get_threads(self) -> Iterable[interfaces.objects.ObjectInterface]:
383382
threads_seen.add(task.vol.offset)
384383
yield task
385384

385+
@property
386+
def is_being_ptraced(self) -> bool:
387+
"""Returns True if this task is being traced using ptrace"""
388+
return self.ptrace != 0
389+
390+
@property
391+
def is_ptracing(self) -> bool:
392+
"""Returns True if this task is tracing other tasks using ptrace"""
393+
is_tracing = (
394+
self.ptraced.next.is_readable()
395+
and self.ptraced.next.dereference().vol.offset != self.ptraced.vol.offset
396+
)
397+
return is_tracing
398+
399+
def get_ptrace_tracer_tid(self) -> Optional[int]:
400+
"""Returns the tracer's TID tracing this task"""
401+
return self.parent.pid if self.is_being_ptraced else None
402+
403+
def get_ptrace_tracee_tids(self) -> List[int]:
404+
"""Returns the list of TIDs being traced by this task"""
405+
task_symbol_table_name = self.get_symbol_table_name()
406+
407+
task_struct_symname = f"{task_symbol_table_name}{constants.BANG}task_struct"
408+
tracing_tid_list = [
409+
task_being_traced.pid
410+
for task_being_traced in self.ptraced.to_list(
411+
task_struct_symname, "ptrace_entry"
412+
)
413+
]
414+
return tracing_tid_list
415+
416+
def get_ptrace_tracee_flags(self) -> Optional[str]:
417+
"""Returns a string with the ptrace flags"""
418+
return PT_FLAGS(self.ptrace).flags if self.is_being_ptraced else None
419+
386420

387421
class fs_struct(objects.StructType):
388422
def get_root_dentry(self):

0 commit comments

Comments
 (0)