From b8e8fb6e92e403bfad4e52675e2737b9eb7ba49c Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 21 Jan 2025 18:37:04 +0100 Subject: [PATCH 01/21] initial split linux modules utilities --- .../symbols/linux/utilities/modules.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 volatility3/framework/symbols/linux/utilities/modules.py diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py new file mode 100644 index 0000000000..bb85196435 --- /dev/null +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -0,0 +1,41 @@ +from volatility3 import framework +from volatility3.framework import interfaces +from volatility3.framework.symbols.linux import extensions, LinuxUtilities +from typing import Iterable, Optional + + +class Modules(interfaces.configuration.VersionableInterface): + """Kernel modules related utilities.""" + + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + + framework.require_interface_version(*_required_framework_version) + + @classmethod + def module_lookup_by_address( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + modules: Iterable[extensions.module], + target_address: int, + ) -> Optional[extensions.module]: + """ + Determine if a target address lies in a module memory space. + Returns the module where the provided address lies. + + Args: + context: The context on which to operate + layer_name: The name of the layer on which to operate + modules: An iterable containing the modules to match the address against + target_address: The address to check for a match + """ + + for module in modules: + _, start, end = LinuxUtilities.mask_mods_list( + context, layer_name, [module] + )[0] + if start <= target_address <= end: + return module + + return None From f007a28ee7e76ee85ec0641581d2acf506195121 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 21 Jan 2025 18:37:55 +0100 Subject: [PATCH 02/21] initial linux.tracing.ftrace.Check_ftrace --- .../framework/plugins/linux/tracing/ftrace.py | 285 ++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 volatility3/framework/plugins/linux/tracing/ftrace.py diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py new file mode 100644 index 0000000000..ba52a93ced --- /dev/null +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -0,0 +1,285 @@ +# This file is Copyright 2025 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +# 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 List, Iterable, Tuple, Set +from enum import auto, IntFlag +from volatility3.plugins.linux import hidden_modules, modxview +from volatility3.framework import constants, exceptions, interfaces +from volatility3.framework.configuration import requirements +from volatility3.framework.renderers import format_hints, TreeGrid, NotAvailableValue +from volatility3.framework.symbols.linux import extensions +from volatility3.framework.symbols.linux.utilities import modules as modules_utilities +from volatility3.framework.constants import architectures + +vollog = logging.getLogger(__name__) + + +# https://docs.python.org/3.13/library/enum.html#enum.IntFlag +class FTRACE_OPS_FLAGS(IntFlag): + """Denote the state of an ftrace_ops struct. + Based on https://elixir.bootlin.com/linux/v6.13-rc3/source/include/linux/ftrace.h#L255. + """ + + FTRACE_OPS_FL_ENABLED = auto() + FTRACE_OPS_FL_DYNAMIC = auto() + FTRACE_OPS_FL_SAVE_REGS = auto() + FTRACE_OPS_FL_SAVE_REGS_IF_SUPPORTED = auto() + FTRACE_OPS_FL_RECURSION = auto() + FTRACE_OPS_FL_STUB = auto() + FTRACE_OPS_FL_INITIALIZED = auto() + FTRACE_OPS_FL_DELETED = auto() + FTRACE_OPS_FL_ADDING = auto() + FTRACE_OPS_FL_REMOVING = auto() + FTRACE_OPS_FL_MODIFYING = auto() + FTRACE_OPS_FL_ALLOC_TRAMP = auto() + FTRACE_OPS_FL_IPMODIFY = auto() + FTRACE_OPS_FL_PID = auto() + FTRACE_OPS_FL_RCU = auto() + FTRACE_OPS_FL_TRACE_ARRAY = auto() + FTRACE_OPS_FL_PERMANENT = auto() + FTRACE_OPS_FL_DIRECT = auto() + FTRACE_OPS_FL_SUBOP = auto() + + +class Check_ftrace(interfaces.plugins.PluginInterface): + """Detect ftrace hooking""" + + _version = (1, 0, 0) + _required_framework_version = (2, 17, 0) + additional_description = """Investigate the ftrace infrastructure to uncover kernel attached callbacks, which can be leveraged + to hook kernel functions and modify their behaviour.""" + _hidden_modules_run = False + """Flag to determine if the hidden_modules plugin was run, + in the context of this plugin.""" + + @staticmethod + def get_requirements() -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=architectures.LINUX_ARCHS, + ), + requirements.VersionRequirement( + name="modules_utilities", + component=modules_utilities.Modules, + version=(1, 0, 0), + ), + requirements.PluginRequirement( + name="modxview", plugin=modxview.Modxview, version=(1, 0, 0) + ), + requirements.PluginRequirement( + name="hidden_modules", + plugin=hidden_modules.Hidden_modules, + version=(1, 0, 0), + ), + requirements.BooleanRequirement( + name="show_ftrace_flags", + description="Show ftrace flags associated with an ftrace_ops struct", + optional=True, + default=False, + ), + ] + + @classmethod + def _set_hidden_modules_run(cls) -> None: + """Use a self-contained setter, to prevent running hidden_modules multiple times.""" + cls._hidden_modules_run = True + + @staticmethod + def extract_hash_table_filters( + ftrace_ops: interfaces.objects.ObjectInterface, + ) -> Iterable[interfaces.objects.ObjectInterface]: + """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: + An iterable of ftrace_func_entry structs + """ + + try: + current_bucket_ptr = ftrace_ops.func_hash.filter_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 [] + + while current_bucket_ptr.is_readable(): + yield current_bucket_ptr.dereference().cast("ftrace_func_entry") + current_bucket_ptr = current_bucket_ptr.next + + @classmethod + def parse_ftrace_ops( + cls, + context: interfaces.context.ContextInterface, + kernel_name: str, + known_modules: Set[extensions.module], + ftrace_ops: interfaces.objects.ObjectInterface, + parse_flags: bool = False, + ) -> Tuple: + """Parse an ftrace_ops struct to highlight ftrace kernel hooking. + Iterates over embedded ftrace_func_entry entries, which point to hooked memory areas. + + Args: + known_modules: A set of known modules to iterate over, used to locate callbacks origin + ftrace_ops: The ftrace_ops struct to parse + parse_flags: Whether to parse ftrace_ops flags or not + + Yields: + A tuple containing a selection of useful fields (callback, hook, module) related to an ftrace_func_entry struct + """ + kernel = context.modules[kernel_name] + callback = ftrace_ops.func + + # Try to lookup within the known modules if the callback address fits + module = modules_utilities.Modules.module_lookup_by_address( + context, kernel.layer_name, known_modules, callback + ) + # Run hidden_modules plugin if a callback origin couldn't be determined (only done once, results are re-used afterwards) + if module is None and not cls._hidden_modules_run: + vollog.info( + f"A callback module origin could not be determined. hidden_modules plugin will be run to detect additional modules.", + ) + known_modules_addresses = set( + context.layers[kernel.layer_name].canonicalize(module.vol.offset) + for module in known_modules + ) + modules_memory_boundaries = ( + hidden_modules.Hidden_modules.get_modules_memory_boundaries( + context, kernel_name + ) + ) + known_modules.update( + hidden_modules.Hidden_modules.get_hidden_modules( + context, + kernel_name, + known_modules_addresses, + modules_memory_boundaries, + ) + ) + cls._set_hidden_modules_run() + # Lookup the updated list to see if hidden_modules was able + # to find the missing module + module = modules_utilities.Modules.module_lookup_by_address( + context, kernel.layer_name, known_modules, callback + ) + + # Fetch more information about the module + if module: + module_address = format_hints.Hex(module.vol.offset) + module_name = module.get_name() or NotAvailableValue() + callback_symbol = ( + module.get_symbol_by_address(callback) or NotAvailableValue() + ) + else: + vollog.warning( + f"Could not determine ftrace_ops@{ftrace_ops.vol.offset:#x} callback {callback:#x} module origin.", + ) + module_address = NotAvailableValue() + module_name = NotAvailableValue() + callback_symbol = NotAvailableValue() + + # Iterate over ftrace_func_entry list + for ftrace_func_entry in cls.extract_hash_table_filters(ftrace_ops): + hook_address = ftrace_func_entry.ip.cast("pointer") + + # Determine the symbols associated with a hook + hooked_symbols = kernel.get_symbols_by_absolute_location(hook_address) + hooked_symbols = ",".join( + [s.split(constants.BANG)[-1] for s in hooked_symbols] + ) + parsed_entry = ( + format_hints.Hex(ftrace_ops.vol.offset), + callback_symbol, + format_hints.Hex(callback), + hooked_symbols or NotAvailableValue(), + module_name, + module_address, + ) + + if parse_flags: + # e.g. FTRACE_OPS_FL_ENABLED,FTRACE_OPS_FL_DYNAMIC + parsed_entry += ( + FTRACE_OPS_FLAGS(ftrace_ops.flags).name.replace("|", ","), + ) + + return parsed_entry + + @staticmethod + def iterate_ftrace_ops_list( + context: interfaces.context.ContextInterface, kernel_name: str + ) -> Iterable[interfaces.objects.ObjectInterface]: + """Iterate over (ftrace_ops *)ftrace_ops_list. + + Returns: + An iterable of ftrace_ops structs + """ + kernel = context.modules[kernel_name] + current_frace_ops_ptr = kernel.object_from_symbol("ftrace_ops_list") + ftrace_list_end = kernel.object_from_symbol("ftrace_list_end") + + while current_frace_ops_ptr.is_readable(): + # ftrace_list_end is not considered a valid struct + # see kernel function test_rec_ops_needs_regs + if current_frace_ops_ptr != ftrace_list_end.vol.offset: + yield current_frace_ops_ptr.dereference() + current_frace_ops_ptr = current_frace_ops_ptr.next + else: + break + + def _generator(self): + kernel_name = self.config["kernel"] + kernel = self.context.modules[kernel_name] + + if not kernel.has_symbol("ftrace_ops_list"): + raise exceptions.SymbolError( + "ftrace_ops_list", + kernel.symbol_table_name, + 'The provided symbol table does not include the "ftrace_ops_list" symbol. This means you are either analyzing an unsupported kernel version or that your symbol table is corrupted.', + ) + + # Do not run hidden_modules by default, but only on failure to find a module + known_modules = set( + modxview.Modxview.flatten_run_modules_results( + modxview.Modxview.run_modules_scanners( + self.context, kernel_name, run_hidden_modules=False + ) + ) + ) + for ftrace_ops in self.iterate_ftrace_ops_list(self.context, kernel_name): + ftrace_ops_parsed = self.parse_ftrace_ops( + self.context, + kernel_name, + known_modules, + ftrace_ops, + self.config.get("show_ftrace_flags"), + ) + if ftrace_ops_parsed is not None: + yield (0, (ftrace_ops_parsed)) + + def run(self): + columns = [ + ("ftrace_ops address", format_hints.Hex), + ("Callback", str), + ("Callback address", format_hints.Hex), + ("Hooked symbols", str), + ("Module", str), + ("Module address", format_hints.Hex), + ] + + if self.config.get("show_ftrace_flags"): + columns.append(("Flags", str)) + + return TreeGrid( + columns, + self._generator(), + ) From 414cab128b06281fb845cb990f698885c0adce18 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 21 Jan 2025 18:38:24 +0100 Subject: [PATCH 03/21] 2.18.0 -> 2.19.0 bump --- volatility3/framework/constants/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/constants/_version.py b/volatility3/framework/constants/_version.py index 832b2a5ba7..f2403cf4a3 100644 --- a/volatility3/framework/constants/_version.py +++ b/volatility3/framework/constants/_version.py @@ -1,6 +1,6 @@ # We use the SemVer 2.0.0 versioning scheme VERSION_MAJOR = 2 # Number of releases of the library with a breaking change -VERSION_MINOR = 18 # Number of changes that only add to the interface +VERSION_MINOR = 19 # Number of changes that only add to the interface VERSION_PATCH = 0 # Number of changes that do not change the interface VERSION_SUFFIX = "" From d5a21340449c1dc63853e05e57aa0b9b06c1e234 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 21 Jan 2025 18:39:02 +0100 Subject: [PATCH 04/21] modules utilities __init__.py --- volatility3/framework/plugins/linux/tracing/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 volatility3/framework/plugins/linux/tracing/__init__.py diff --git a/volatility3/framework/plugins/linux/tracing/__init__.py b/volatility3/framework/plugins/linux/tracing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From 37b792b09c440591abcdfa19d8b49dc32f5b5695 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 21 Jan 2025 18:57:42 +0100 Subject: [PATCH 05/21] ruff fix --- volatility3/framework/plugins/linux/tracing/ftrace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index ba52a93ced..01268aa8a8 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -53,7 +53,7 @@ class Check_ftrace(interfaces.plugins.PluginInterface): additional_description = """Investigate the ftrace infrastructure to uncover kernel attached callbacks, which can be leveraged to hook kernel functions and modify their behaviour.""" _hidden_modules_run = False - """Flag to determine if the hidden_modules plugin was run, + """Flag to determine if the hidden_modules plugin was run, in the context of this plugin.""" @staticmethod @@ -147,7 +147,7 @@ def parse_ftrace_ops( # Run hidden_modules plugin if a callback origin couldn't be determined (only done once, results are re-used afterwards) if module is None and not cls._hidden_modules_run: vollog.info( - f"A callback module origin could not be determined. hidden_modules plugin will be run to detect additional modules.", + "A callback module origin could not be determined. hidden_modules plugin will be run to detect additional modules.", ) known_modules_addresses = set( context.layers[kernel.layer_name].canonicalize(module.vol.offset) From 614ac507be14a0500cd9b41d9315c47b218a6aae Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 21 Jan 2025 19:23:38 +0100 Subject: [PATCH 06/21] explicit returns and extra Optional type hinting --- .../framework/plugins/linux/tracing/ftrace.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index 01268aa8a8..a198647d9f 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 List, Iterable, Tuple, Set +from typing import List, Iterable, Optional, Tuple, Set from enum import auto, IntFlag from volatility3.plugins.linux import hidden_modules, modxview from volatility3.framework import constants, exceptions, interfaces @@ -93,7 +93,7 @@ def _set_hidden_modules_run(cls) -> None: @staticmethod def extract_hash_table_filters( ftrace_ops: interfaces.objects.ObjectInterface, - ) -> Iterable[interfaces.objects.ObjectInterface]: + ) -> Optional[Iterable[interfaces.objects.ObjectInterface]]: """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. @@ -117,6 +117,8 @@ def extract_hash_table_filters( yield current_bucket_ptr.dereference().cast("ftrace_func_entry") current_bucket_ptr = current_bucket_ptr.next + return None + @classmethod def parse_ftrace_ops( cls, @@ -125,7 +127,7 @@ def parse_ftrace_ops( known_modules: Set[extensions.module], ftrace_ops: interfaces.objects.ObjectInterface, parse_flags: bool = False, - ) -> Tuple: + ) -> Optional[Tuple]: """Parse an ftrace_ops struct to highlight ftrace kernel hooking. Iterates over embedded ftrace_func_entry entries, which point to hooked memory areas. @@ -214,10 +216,12 @@ def parse_ftrace_ops( return parsed_entry + return None + @staticmethod def iterate_ftrace_ops_list( context: interfaces.context.ContextInterface, kernel_name: str - ) -> Iterable[interfaces.objects.ObjectInterface]: + ) -> Optional[Iterable[interfaces.objects.ObjectInterface]]: """Iterate over (ftrace_ops *)ftrace_ops_list. Returns: From d297693876b057d7a56ba81ec851b0b0fe9627d0 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 21 Jan 2025 23:31:17 +0100 Subject: [PATCH 07/21] correct required framework version --- volatility3/framework/plugins/linux/tracing/ftrace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index a198647d9f..a044915507 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -49,7 +49,7 @@ class Check_ftrace(interfaces.plugins.PluginInterface): """Detect ftrace hooking""" _version = (1, 0, 0) - _required_framework_version = (2, 17, 0) + _required_framework_version = (2, 19, 0) additional_description = """Investigate the ftrace infrastructure to uncover kernel attached callbacks, which can be leveraged to hook kernel functions and modify their behaviour.""" _hidden_modules_run = False From 3ba60d55f7d0c6b62fdbc9b4381b9bd96006a171 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 22 Jan 2025 00:52:41 +0100 Subject: [PATCH 08/21] remove self-contained hidden_modules check, switch to dataclass --- .../framework/plugins/linux/tracing/ftrace.py | 131 ++++++++++-------- 1 file changed, 76 insertions(+), 55 deletions(-) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index a044915507..009d48ce82 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -5,8 +5,10 @@ # 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 List, Iterable, Optional, Tuple, Set +from typing import Dict, List, Iterable, Optional from enum import auto, IntFlag +from dataclasses import dataclass + from volatility3.plugins.linux import hidden_modules, modxview from volatility3.framework import constants, exceptions, interfaces from volatility3.framework.configuration import requirements @@ -19,7 +21,7 @@ # https://docs.python.org/3.13/library/enum.html#enum.IntFlag -class FTRACE_OPS_FLAGS(IntFlag): +class FtraceOpsFlags(IntFlag): """Denote the state of an ftrace_ops struct. Based on https://elixir.bootlin.com/linux/v6.13-rc3/source/include/linux/ftrace.h#L255. """ @@ -45,16 +47,27 @@ class FTRACE_OPS_FLAGS(IntFlag): FTRACE_OPS_FL_SUBOP = auto() -class Check_ftrace(interfaces.plugins.PluginInterface): +@dataclass +class ParsedFtraceOps: + """Parsed ftrace_ops struct representation, containing a selection of forensics valuable + informations.""" + + ftrace_ops_offset: int + callback_symbol: str + callback_address: int + hooked_symbols: str + module_name: str + module_address: int + flags: str + + +class CheckFtrace(interfaces.plugins.PluginInterface): """Detect ftrace hooking""" _version = (1, 0, 0) _required_framework_version = (2, 19, 0) additional_description = """Investigate the ftrace infrastructure to uncover kernel attached callbacks, which can be leveraged to hook kernel functions and modify their behaviour.""" - _hidden_modules_run = False - """Flag to determine if the hidden_modules plugin was run, - in the context of this plugin.""" @staticmethod def get_requirements() -> List[interfaces.configuration.RequirementInterface]: @@ -85,11 +98,6 @@ def get_requirements() -> List[interfaces.configuration.RequirementInterface]: ), ] - @classmethod - def _set_hidden_modules_run(cls) -> None: - """Use a self-contained setter, to prevent running hidden_modules multiple times.""" - cls._hidden_modules_run = True - @staticmethod def extract_hash_table_filters( ftrace_ops: interfaces.objects.ObjectInterface, @@ -124,43 +132,54 @@ def parse_ftrace_ops( cls, context: interfaces.context.ContextInterface, kernel_name: str, - known_modules: Set[extensions.module], + known_modules: Dict[str, List[extensions.module]], ftrace_ops: interfaces.objects.ObjectInterface, - parse_flags: bool = False, - ) -> Optional[Tuple]: + run_hidden_modules: bool = True, + ) -> Optional[Iterable[ParsedFtraceOps]]: """Parse an ftrace_ops struct to highlight ftrace kernel hooking. Iterates over embedded ftrace_func_entry entries, which point to hooked memory areas. Args: - known_modules: A set of known modules to iterate over, used to locate callbacks origin + known_modules: A dict of known modules, used to locate callbacks origin. Typically obtained through modxview.run_modules_scanners(). ftrace_ops: The ftrace_ops struct to parse - parse_flags: Whether to parse ftrace_ops flags or not + 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. Yields: - A tuple containing a selection of useful fields (callback, hook, module) related to an ftrace_func_entry struct + An iterable of ParsedFtraceOps dataclasses, containing a selection of useful fields (callback, hook, module) related to an ftrace_ops struct """ kernel = context.modules[kernel_name] callback = ftrace_ops.func + callback_symbol = module_address = module_name = None # Try to lookup within the known modules if the callback address fits module = modules_utilities.Modules.module_lookup_by_address( - context, kernel.layer_name, known_modules, callback + context, + kernel.layer_name, + modxview.Modxview.flatten_run_modules_results(known_modules), + callback, ) # Run hidden_modules plugin if a callback origin couldn't be determined (only done once, results are re-used afterwards) - if module is None and not cls._hidden_modules_run: + if ( + module is None + and run_hidden_modules + and "hidden_modules" not in known_modules + ): vollog.info( "A callback module origin could not be determined. hidden_modules plugin will be run to detect additional modules.", ) known_modules_addresses = set( context.layers[kernel.layer_name].canonicalize(module.vol.offset) - for module in known_modules + for module in modxview.Modxview.flatten_run_modules_results( + known_modules + ) ) modules_memory_boundaries = ( hidden_modules.Hidden_modules.get_modules_memory_boundaries( context, kernel_name ) ) - known_modules.update( + known_modules["hidden_modules"] = list( hidden_modules.Hidden_modules.get_hidden_modules( context, kernel_name, @@ -168,27 +187,24 @@ def parse_ftrace_ops( modules_memory_boundaries, ) ) - cls._set_hidden_modules_run() # Lookup the updated list to see if hidden_modules was able # to find the missing module module = modules_utilities.Modules.module_lookup_by_address( - context, kernel.layer_name, known_modules, callback + context, + kernel.layer_name, + modxview.Modxview.flatten_run_modules_results(known_modules), + callback, ) # Fetch more information about the module - if module: - module_address = format_hints.Hex(module.vol.offset) - module_name = module.get_name() or NotAvailableValue() - callback_symbol = ( - module.get_symbol_by_address(callback) or NotAvailableValue() - ) + if module is not None: + module_address = module.vol.offset + module_name = module.get_name() + callback_symbol = module.get_symbol_by_address(callback) else: vollog.warning( f"Could not determine ftrace_ops@{ftrace_ops.vol.offset:#x} callback {callback:#x} module origin.", ) - module_address = NotAvailableValue() - module_name = NotAvailableValue() - callback_symbol = NotAvailableValue() # Iterate over ftrace_func_entry list for ftrace_func_entry in cls.extract_hash_table_filters(ftrace_ops): @@ -199,23 +215,20 @@ def parse_ftrace_ops( hooked_symbols = ",".join( [s.split(constants.BANG)[-1] for s in hooked_symbols] ) - parsed_entry = ( - format_hints.Hex(ftrace_ops.vol.offset), + yield ParsedFtraceOps( + ftrace_ops.vol.offset, callback_symbol, - format_hints.Hex(callback), - hooked_symbols or NotAvailableValue(), + callback, + hooked_symbols, module_name, module_address, + # FtraceOpsFlags(ftrace_ops.flags).name is valid in > Python3.10, but + # returns None <= Python 3.10. We need to manipulate it like so to ensure compatibility: + # FtraceOpsFlags.FTRACE_OPS_FL_IPMODIFY|FTRACE_OPS_FL_ALLOC_TRAMP + # -> FTRACE_OPS_FL_IPMODIFY,FTRACE_OPS_FL_ALLOC_TRAMP + str(FtraceOpsFlags(ftrace_ops.flags)).split(".")[-1].replace("|", ","), ) - if parse_flags: - # e.g. FTRACE_OPS_FL_ENABLED,FTRACE_OPS_FL_DYNAMIC - parsed_entry += ( - FTRACE_OPS_FLAGS(ftrace_ops.flags).name.replace("|", ","), - ) - - return parsed_entry - return None @staticmethod @@ -252,23 +265,31 @@ def _generator(self): ) # Do not run hidden_modules by default, but only on failure to find a module - known_modules = set( - modxview.Modxview.flatten_run_modules_results( - modxview.Modxview.run_modules_scanners( - self.context, kernel_name, run_hidden_modules=False - ) - ) + known_modules = modxview.Modxview.run_modules_scanners( + self.context, kernel_name, run_hidden_modules=False ) for ftrace_ops in self.iterate_ftrace_ops_list(self.context, kernel_name): - ftrace_ops_parsed = self.parse_ftrace_ops( + for ftrace_ops_parsed in self.parse_ftrace_ops( self.context, kernel_name, known_modules, ftrace_ops, - self.config.get("show_ftrace_flags"), - ) - if ftrace_ops_parsed is not None: - yield (0, (ftrace_ops_parsed)) + ): + formatted_results = ( + format_hints.Hex(ftrace_ops_parsed.ftrace_ops_offset), + ftrace_ops_parsed.callback_symbol or NotAvailableValue(), + format_hints.Hex(ftrace_ops_parsed.callback_address), + ftrace_ops_parsed.hooked_symbols or NotAvailableValue(), + ftrace_ops_parsed.module_name or NotAvailableValue(), + ( + format_hints.Hex(ftrace_ops_parsed.module_address) + if ftrace_ops_parsed.module_address is not None + else NotAvailableValue() + ), + ) + if self.config["show_ftrace_flags"]: + formatted_results += (ftrace_ops_parsed.flags,) + yield (0, formatted_results) def run(self): columns = [ From e7b51bd1a71b9af453605b53182f6b8c9b719d08 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 22 Jan 2025 16:51:06 +0100 Subject: [PATCH 09/21] prefer staticmethod when cls is not needed --- volatility3/framework/symbols/linux/utilities/modules.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index bb85196435..4be460a684 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -12,9 +12,8 @@ class Modules(interfaces.configuration.VersionableInterface): framework.require_interface_version(*_required_framework_version) - @classmethod + @staticmethod def module_lookup_by_address( - cls, context: interfaces.context.ContextInterface, layer_name: str, modules: Iterable[extensions.module], From f75a4be0517f772ebe05aeaef4d0eb21fe9d98a4 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 26 Jan 2025 15:24:06 +0100 Subject: [PATCH 10/21] rename modules_utilities to linux_utilities_modules --- volatility3/framework/plugins/linux/tracing/ftrace.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index 009d48ce82..39df2ccc7e 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -14,7 +14,7 @@ from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints, TreeGrid, NotAvailableValue from volatility3.framework.symbols.linux import extensions -from volatility3.framework.symbols.linux.utilities import modules as modules_utilities +from volatility3.framework.symbols.linux.utilities import modules as linux_utilities_modules from volatility3.framework.constants import architectures vollog = logging.getLogger(__name__) @@ -78,8 +78,8 @@ def get_requirements() -> List[interfaces.configuration.RequirementInterface]: architectures=architectures.LINUX_ARCHS, ), requirements.VersionRequirement( - name="modules_utilities", - component=modules_utilities.Modules, + name="linux_utilities_modules", + component=linux_utilities_modules.Modules, version=(1, 0, 0), ), requirements.PluginRequirement( @@ -153,7 +153,7 @@ def parse_ftrace_ops( callback_symbol = module_address = module_name = None # Try to lookup within the known modules if the callback address fits - module = modules_utilities.Modules.module_lookup_by_address( + module = linux_utilities_modules.Modules.module_lookup_by_address( context, kernel.layer_name, modxview.Modxview.flatten_run_modules_results(known_modules), @@ -189,7 +189,7 @@ def parse_ftrace_ops( ) # Lookup the updated list to see if hidden_modules was able # to find the missing module - module = modules_utilities.Modules.module_lookup_by_address( + module = linux_utilities_modules.Modules.module_lookup_by_address( context, kernel.layer_name, modxview.Modxview.flatten_run_modules_results(known_modules), From 48eca36b00c885517e607e268b1fa11bea5f6bda Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 26 Jan 2025 15:25:11 +0100 Subject: [PATCH 11/21] 1.0.0 -> 1.1.0 Modules bump --- volatility3/framework/symbols/linux/utilities/modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index 692aa8d3a7..f529a61aee 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -9,7 +9,7 @@ class Modules(interfaces.configuration.VersionableInterface): """Kernel modules related utilities.""" - _version = (1, 0, 0) + _version = (1, 1, 0) _required_framework_version = (2, 0, 0) framework.require_interface_version(*_required_framework_version) From c57f60759faa7a6991e5a93f56889a44d1b4f3d2 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 26 Jan 2025 15:27:27 +0100 Subject: [PATCH 12/21] require Modules >= 1.1.0 --- volatility3/framework/plugins/linux/tracing/ftrace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index 39df2ccc7e..c35a6f5611 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -9,12 +9,12 @@ from enum import auto, IntFlag 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.configuration import requirements from volatility3.framework.renderers import format_hints, TreeGrid, NotAvailableValue from volatility3.framework.symbols.linux import extensions -from volatility3.framework.symbols.linux.utilities import modules as linux_utilities_modules from volatility3.framework.constants import architectures vollog = logging.getLogger(__name__) @@ -80,7 +80,7 @@ def get_requirements() -> List[interfaces.configuration.RequirementInterface]: requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(1, 0, 0), + version=(1, 1, 0), ), requirements.PluginRequirement( name="modxview", plugin=modxview.Modxview, version=(1, 0, 0) From f2ac62122013971a7c2828cb8afa21a7a902ab52 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 26 Jan 2025 15:30:37 +0100 Subject: [PATCH 13/21] use classmethod instead of staticmethod --- volatility3/framework/plugins/linux/tracing/ftrace.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index c35a6f5611..6e1a4e470a 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -69,8 +69,8 @@ class CheckFtrace(interfaces.plugins.PluginInterface): additional_description = """Investigate the ftrace infrastructure to uncover kernel attached callbacks, which can be leveraged to hook kernel functions and modify their behaviour.""" - @staticmethod - def get_requirements() -> List[interfaces.configuration.RequirementInterface]: + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: return [ requirements.ModuleRequirement( name="kernel", @@ -98,8 +98,9 @@ def get_requirements() -> List[interfaces.configuration.RequirementInterface]: ), ] - @staticmethod + @classmethod def extract_hash_table_filters( + cls, ftrace_ops: interfaces.objects.ObjectInterface, ) -> Optional[Iterable[interfaces.objects.ObjectInterface]]: """Wrap the process of walking to every ftrace_func_entry of an ftrace_ops. @@ -231,9 +232,9 @@ def parse_ftrace_ops( return None - @staticmethod + @classmethod def iterate_ftrace_ops_list( - context: interfaces.context.ContextInterface, kernel_name: str + cls, context: interfaces.context.ContextInterface, kernel_name: str ) -> Optional[Iterable[interfaces.objects.ObjectInterface]]: """Iterate over (ftrace_ops *)ftrace_ops_list. From 22a2fe17d8d82e7eaf02f1338c73f3b8f4408e15 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Mon, 27 Jan 2025 11:03:08 +0100 Subject: [PATCH 14/21] clarify generator variable --- volatility3/framework/plugins/linux/tracing/ftrace.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index 6e1a4e470a..629f1bab88 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -214,7 +214,10 @@ def parse_ftrace_ops( # Determine the symbols associated with a hook hooked_symbols = kernel.get_symbols_by_absolute_location(hook_address) hooked_symbols = ",".join( - [s.split(constants.BANG)[-1] for s in hooked_symbols] + [ + hooked_symbol.split(constants.BANG)[-1] + for hooked_symbol in hooked_symbols + ] ) yield ParsedFtraceOps( ftrace_ops.vol.offset, From 03cf84f9cff90aaa135190b025e28fe436fff69e Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Mon, 27 Jan 2025 11:05:08 +0100 Subject: [PATCH 15/21] assign kernel layer to variable early --- volatility3/framework/plugins/linux/tracing/ftrace.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index 629f1bab88..99961c9314 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -150,6 +150,7 @@ def parse_ftrace_ops( An iterable of ParsedFtraceOps dataclasses, containing a selection of useful fields (callback, hook, module) related to an ftrace_ops struct """ kernel = context.modules[kernel_name] + kernel_layer = context.layers[kernel.layer_name] callback = ftrace_ops.func callback_symbol = module_address = module_name = None @@ -170,7 +171,7 @@ def parse_ftrace_ops( "A callback module origin could not be determined. hidden_modules plugin will be run to detect additional modules.", ) known_modules_addresses = set( - context.layers[kernel.layer_name].canonicalize(module.vol.offset) + kernel_layer.canonicalize(module.vol.offset) for module in modxview.Modxview.flatten_run_modules_results( known_modules ) From 9d11c1f460844eb25f60c038a4bc81a0e78be5c9 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Mon, 27 Jan 2025 11:50:28 +0100 Subject: [PATCH 16/21] prevent modules memory space overlap scenario --- .../symbols/linux/utilities/modules.py | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index f529a61aee..baaeff683a 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -1,3 +1,4 @@ +import warnings from typing import Iterable, Iterator, List, Optional, Tuple from volatility3 import framework @@ -31,12 +32,29 @@ def module_lookup_by_address( layer_name: The name of the layer on which to operate modules: An iterable containing the modules to match the address against target_address: The address to check for a match - """ + Returns: + The first memory module in which the address fits + """ + matches = [] + seen_addresses = set() for module in modules: _, start, end = cls.mask_mods_list(context, layer_name, [module])[0] - if start <= target_address <= end: - return module + if ( + start <= target_address <= end + and module.vol.offset not in seen_addresses + ): + matches.append(module) + seen_addresses.add(module.vol.offset) + + if len(matches) > 1: + warnings.warn( + f"Address {hex(target_address)} fits in modules at {[hex(module.vol.offset) for module in matches]}, indicating potential modules memory space overlap.", + UserWarning, + ) + return matches[0] + elif len(matches) == 1: + return matches[0] return None From c000e812a6a402ea7380cdd817f6d95ad6619366 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Mon, 27 Jan 2025 19:40:47 +0100 Subject: [PATCH 17/21] tune with the new additional_description mechanism --- volatility3/framework/plugins/linux/tracing/ftrace.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index 99961c9314..29216187c0 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -62,12 +62,13 @@ class ParsedFtraceOps: class CheckFtrace(interfaces.plugins.PluginInterface): - """Detect ftrace hooking""" + """Detect ftrace hooking + + 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) _required_framework_version = (2, 19, 0) - additional_description = """Investigate the ftrace infrastructure to uncover kernel attached callbacks, which can be leveraged - to hook kernel functions and modify their behaviour.""" @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: From b055848576697547fc2bc139738f7c3a4d70fa13 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 29 Jan 2025 12:04:32 +0100 Subject: [PATCH 18/21] explicit powers of two instead of auto() --- .../framework/plugins/linux/tracing/ftrace.py | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index 29216187c0..da88d1de16 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -6,7 +6,7 @@ import logging from typing import Dict, List, Iterable, Optional -from enum import auto, IntFlag +from enum import IntFlag from dataclasses import dataclass import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules @@ -26,25 +26,25 @@ class FtraceOpsFlags(IntFlag): Based on https://elixir.bootlin.com/linux/v6.13-rc3/source/include/linux/ftrace.h#L255. """ - FTRACE_OPS_FL_ENABLED = auto() - FTRACE_OPS_FL_DYNAMIC = auto() - FTRACE_OPS_FL_SAVE_REGS = auto() - FTRACE_OPS_FL_SAVE_REGS_IF_SUPPORTED = auto() - FTRACE_OPS_FL_RECURSION = auto() - FTRACE_OPS_FL_STUB = auto() - FTRACE_OPS_FL_INITIALIZED = auto() - FTRACE_OPS_FL_DELETED = auto() - FTRACE_OPS_FL_ADDING = auto() - FTRACE_OPS_FL_REMOVING = auto() - FTRACE_OPS_FL_MODIFYING = auto() - FTRACE_OPS_FL_ALLOC_TRAMP = auto() - FTRACE_OPS_FL_IPMODIFY = auto() - FTRACE_OPS_FL_PID = auto() - FTRACE_OPS_FL_RCU = auto() - FTRACE_OPS_FL_TRACE_ARRAY = auto() - FTRACE_OPS_FL_PERMANENT = auto() - FTRACE_OPS_FL_DIRECT = auto() - FTRACE_OPS_FL_SUBOP = auto() + FTRACE_OPS_FL_ENABLED = 1 << 0 + FTRACE_OPS_FL_DYNAMIC = 1 << 1 + FTRACE_OPS_FL_SAVE_REGS = 1 << 2 + FTRACE_OPS_FL_SAVE_REGS_IF_SUPPORTED = 1 << 3 + FTRACE_OPS_FL_RECURSION = 1 << 4 + FTRACE_OPS_FL_STUB = 1 << 5 + FTRACE_OPS_FL_INITIALIZED = 1 << 6 + FTRACE_OPS_FL_DELETED = 1 << 7 + FTRACE_OPS_FL_ADDING = 1 << 8 + FTRACE_OPS_FL_REMOVING = 1 << 9 + FTRACE_OPS_FL_MODIFYING = 1 << 10 + FTRACE_OPS_FL_ALLOC_TRAMP = 1 << 11 + FTRACE_OPS_FL_IPMODIFY = 1 << 12 + FTRACE_OPS_FL_PID = 1 << 13 + FTRACE_OPS_FL_RCU = 1 << 14 + FTRACE_OPS_FL_TRACE_ARRAY = 1 << 15 + FTRACE_OPS_FL_PERMANENT = 1 << 16 + FTRACE_OPS_FL_DIRECT = 1 << 17 + FTRACE_OPS_FL_SUBOP = 1 << 18 @dataclass From c01f3c5556141102eac1a4ca65c8f63fabacb253 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 5 Feb 2025 19:17:25 +0100 Subject: [PATCH 19/21] non inclusive upper bound address check --- volatility3/framework/symbols/linux/utilities/modules.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index baaeff683a..d03e76c88d 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -35,13 +35,16 @@ def module_lookup_by_address( Returns: The first memory module in which the address fits + + Kernel documentation: + "within_module" and "within_module_mem_type" functions """ matches = [] seen_addresses = set() for module in modules: _, start, end = cls.mask_mods_list(context, layer_name, [module])[0] if ( - start <= target_address <= end + start <= target_address < end and module.vol.offset not in seen_addresses ): matches.append(module) From 637ff680353564f04cacdf17d3853992cea4abdc Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 5 Feb 2025 19:19:26 +0100 Subject: [PATCH 20/21] make ftrace flags parsing more readable --- .../framework/plugins/linux/tracing/ftrace.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index da88d1de16..75b4d3cca4 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -221,6 +221,12 @@ def parse_ftrace_ops( for hooked_symbol in hooked_symbols ] ) + # Manipulate FtraceOpsFlags(ftrace_ops.flags) like so: + # "FtraceOpsFlags.FTRACE_OPS_FL_IPMODIFY|FTRACE_OPS_FL_ALLOC_TRAMP" + # -> "FTRACE_OPS_FL_IPMODIFY,FTRACE_OPS_FL_ALLOC_TRAMP" + formatted_ftrace_flags = ( + str(FtraceOpsFlags(ftrace_ops.flags)).split(".")[-1].replace("|", ",") + ) yield ParsedFtraceOps( ftrace_ops.vol.offset, callback_symbol, @@ -228,11 +234,7 @@ def parse_ftrace_ops( hooked_symbols, module_name, module_address, - # FtraceOpsFlags(ftrace_ops.flags).name is valid in > Python3.10, but - # returns None <= Python 3.10. We need to manipulate it like so to ensure compatibility: - # FtraceOpsFlags.FTRACE_OPS_FL_IPMODIFY|FTRACE_OPS_FL_ALLOC_TRAMP - # -> FTRACE_OPS_FL_IPMODIFY,FTRACE_OPS_FL_ALLOC_TRAMP - str(FtraceOpsFlags(ftrace_ops.flags)).split(".")[-1].replace("|", ","), + formatted_ftrace_flags, ) return None From f5e8ed2457f7d276359028cb6a10b8547f5c4f94 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 5 Feb 2025 21:42:13 +0100 Subject: [PATCH 21/21] use a list comprehension for flags parsing --- volatility3/framework/plugins/linux/tracing/ftrace.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index 75b4d3cca4..6690769b79 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -6,7 +6,7 @@ import logging from typing import Dict, List, Iterable, Optional -from enum import IntFlag +from enum import Enum from dataclasses import dataclass import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules @@ -21,7 +21,7 @@ # https://docs.python.org/3.13/library/enum.html#enum.IntFlag -class FtraceOpsFlags(IntFlag): +class FtraceOpsFlags(Enum): """Denote the state of an ftrace_ops struct. Based on https://elixir.bootlin.com/linux/v6.13-rc3/source/include/linux/ftrace.h#L255. """ @@ -221,11 +221,8 @@ def parse_ftrace_ops( for hooked_symbol in hooked_symbols ] ) - # Manipulate FtraceOpsFlags(ftrace_ops.flags) like so: - # "FtraceOpsFlags.FTRACE_OPS_FL_IPMODIFY|FTRACE_OPS_FL_ALLOC_TRAMP" - # -> "FTRACE_OPS_FL_IPMODIFY,FTRACE_OPS_FL_ALLOC_TRAMP" - formatted_ftrace_flags = ( - str(FtraceOpsFlags(ftrace_ops.flags)).split(".")[-1].replace("|", ",") + formatted_ftrace_flags = ",".join( + [flag.name for flag in FtraceOpsFlags if flag.value & ftrace_ops.flags] ) yield ParsedFtraceOps( ftrace_ops.vol.offset,