From 557b200f0749b2c08d78c7a21a4bb2d26990a46d Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Thu, 6 Mar 2025 17:36:24 +0000 Subject: [PATCH 1/2] Fix several bugs found in the tracing plugins during mass testing --- .../framework/plugins/linux/tracing/ftrace.py | 11 ++++--- .../plugins/linux/tracing/tracepoints.py | 32 +++++++++++++------ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index 6690769b7..02b5a61f1 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -114,21 +114,24 @@ class CheckFtrace(interfaces.plugins.PluginInterface): An iterable of ftrace_func_entry structs """ + if hasattr(ftrace_ops, "func_hash"): + ftrace_hash = ftrace_ops.func_hash.filter_hash + else: + ftrace_hash = ftrace_ops.filter_hash + try: - current_bucket_ptr = ftrace_ops.func_hash.filter_hash.buckets.first + current_bucket_ptr = ftrace_hash.buckets.first except exceptions.InvalidAddressException: vollog.log( constants.LOGLEVEL_VV, f"ftrace_func_entry list of ftrace_ops@{ftrace_ops.vol.offset:#x} is empty/invalid. Skipping it...", ) - return [] + return while current_bucket_ptr.is_readable(): yield current_bucket_ptr.dereference().cast("ftrace_func_entry") current_bucket_ptr = current_bucket_ptr.next - return None - @classmethod def parse_ftrace_ops( cls, diff --git a/volatility3/framework/plugins/linux/tracing/tracepoints.py b/volatility3/framework/plugins/linux/tracing/tracepoints.py index 247e139d5..4af80b545 100644 --- a/volatility3/framework/plugins/linux/tracing/tracepoints.py +++ b/volatility3/framework/plugins/linux/tracing/tracepoints.py @@ -10,7 +10,7 @@ from dataclasses import dataclass import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules from volatility3.plugins.linux import hidden_modules, modxview -from volatility3.framework import constants, exceptions, interfaces +from volatility3.framework import constants, exceptions, interfaces, renderers from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints, NotAvailableValue, TreeGrid from volatility3.framework.symbols.linux import extensions @@ -116,18 +116,25 @@ class CheckTracepoints(interfaces.plugins.PluginInterface): known_modules: A dict of known modules, used to locate callbacks origin. Typically obtained through modxview.run_modules_scanners(). tracepoint: The tracepoint struct to parse run_hidden_modules: Whether to run the hidden_modules plugin or not. Note: it won't be run, even if specified, \ -if the "hidden_modules" key is present in known_modules. + if the "hidden_modules" key is present in known_modules. Yields: An iterable of ParsedTracepointFunc dataclasses, containing a selection of useful fields related to a tracepoint struct """ - kernel = context.modules[kernel_name] kernel_layer = context.layers[kernel.layer_name] for tracepoint_func in cls.iterate_tracepoint_funcs( context, kernel_layer.name, tracepoint ): + try: + tracepoint_name = utility.pointer_to_string(tracepoint.name, count=512) + except exceptions.InvalidAddressException: + vollog.debug( + f"Tracepoint function at {tracepoint.vol.offset:#x} is smeared." + ) + continue + probe_handler_address = tracepoint_func.func probe_handler_symbol = module_address = module_name = None @@ -183,16 +190,21 @@ if the "hidden_modules" key is present in known_modules. probe_handler_address ) else: - vollog.warning( + vollog.debug( f"Could not determine tracepoint@{tracepoint.vol.offset:#x} probe handler {probe_handler_address:#x} module origin.", ) + if hasattr(tracepoint_func, "prio"): + prio = tracepoint_func.prio + else: + prio = renderers.NotAvailableValue() + yield ParsedTracepointFunc( - utility.pointer_to_string(tracepoint.name, count=512), + tracepoint_name, tracepoint.vol.offset, probe_handler_symbol, probe_handler_address, - tracepoint_func.prio, + prio, module_name, module_address, ) @@ -258,11 +270,11 @@ if the "hidden_modules" key is present in known_modules. kernel_layer = self.context.layers[kernel.layer_name] if not kernel.has_symbol("__start___tracepoints_ptrs"): - raise exceptions.SymbolError( - "__start___tracepoints_ptrs", - self.vmlinux.symbol_table_name, - 'The provided symbol table does not include the "__start___tracepoints_ptrs" symbol. This means you are either analyzing an unsupported kernel version or that your symbol table is corrupted.', + vollog.error( + 'The provided symbol table does not include the "__start___tracepoints_ptrs" symbol.' + "This means you are either analyzing an unsupported kernel version or that your symbol table is corrupted." ) + return known_modules = modxview.Modxview.run_modules_scanners( self.context, kernel_name, run_hidden_modules=False From 14778cdf5c75b9fe10a28af2fa60c7ff5efe793f Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Thu, 6 Mar 2025 18:10:38 +0000 Subject: [PATCH 2/2] Address feedback --- .../framework/plugins/linux/tracing/ftrace.py | 14 ++++++-------- .../framework/plugins/linux/tracing/tracepoints.py | 6 +++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index 02b5a61f1..59168d5bc 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -5,7 +5,7 @@ # Public researches: https://i.blackhat.com/USA21/Wednesday-Handouts/us-21-Fixing-A-Memory-Forensics-Blind-Spot-Linux-Kernel-Tracing-wp.pdf import logging -from typing import Dict, List, Iterable, Optional +from typing import Dict, List, Generator from enum import Enum from dataclasses import dataclass @@ -67,7 +67,7 @@ class CheckFtrace(interfaces.plugins.PluginInterface): Investigate the ftrace infrastructure to uncover kernel attached callbacks, which can be leveraged to hook kernel functions and modify their behaviour.""" - _version = (1, 0, 0) + _version = (2, 0, 0) _required_framework_version = (2, 19, 0) @classmethod @@ -103,14 +103,14 @@ class CheckFtrace(interfaces.plugins.PluginInterface): def extract_hash_table_filters( cls, ftrace_ops: interfaces.objects.ObjectInterface, - ) -> Optional[Iterable[interfaces.objects.ObjectInterface]]: + ) -> Generator[interfaces.objects.ObjectInterface, None, None]: """Wrap the process of walking to every ftrace_func_entry of an ftrace_ops. Those are stored in a hash table of filters that indicates the addresses hooked. Args: ftrace_ops: The ftrace_ops struct to walk through - Returns: + Return, None, None: An iterable of ftrace_func_entry structs """ @@ -140,7 +140,7 @@ class CheckFtrace(interfaces.plugins.PluginInterface): known_modules: Dict[str, List[extensions.module]], ftrace_ops: interfaces.objects.ObjectInterface, run_hidden_modules: bool = True, - ) -> Optional[Iterable[ParsedFtraceOps]]: + ) -> Generator[ParsedFtraceOps, None, None]: """Parse an ftrace_ops struct to highlight ftrace kernel hooking. Iterates over embedded ftrace_func_entry entries, which point to hooked memory areas. @@ -237,12 +237,10 @@ if the "hidden_modules" key is present in known_modules. formatted_ftrace_flags, ) - return None - @classmethod def iterate_ftrace_ops_list( cls, context: interfaces.context.ContextInterface, kernel_name: str - ) -> Optional[Iterable[interfaces.objects.ObjectInterface]]: + ) -> Generator[interfaces.objects.ObjectInterface, None, None]: """Iterate over (ftrace_ops *)ftrace_ops_list. Returns: diff --git a/volatility3/framework/plugins/linux/tracing/tracepoints.py b/volatility3/framework/plugins/linux/tracing/tracepoints.py index 4af80b545..f8edcc5b7 100644 --- a/volatility3/framework/plugins/linux/tracing/tracepoints.py +++ b/volatility3/framework/plugins/linux/tracing/tracepoints.py @@ -10,7 +10,7 @@ from dataclasses import dataclass import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules from volatility3.plugins.linux import hidden_modules, modxview -from volatility3.framework import constants, exceptions, interfaces, renderers +from volatility3.framework import constants, exceptions, interfaces from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints, NotAvailableValue, TreeGrid from volatility3.framework.symbols.linux import extensions @@ -197,7 +197,7 @@ class CheckTracepoints(interfaces.plugins.PluginInterface): if hasattr(tracepoint_func, "prio"): prio = tracepoint_func.prio else: - prio = renderers.NotAvailableValue() + prio = None yield ParsedTracepointFunc( tracepoint_name, @@ -293,7 +293,7 @@ class CheckTracepoints(interfaces.plugins.PluginInterface): format_hints.Hex(tracepoint_parsed.tracepoint_address), tracepoint_parsed.probe_name or NotAvailableValue(), format_hints.Hex(tracepoint_parsed.probe_address), - tracepoint_parsed.probe_priority, + tracepoint_parsed.probe_priority or NotAvailableValue(), tracepoint_parsed.module_name or NotAvailableValue(), ( format_hints.Hex(tracepoint_parsed.module_address)