From 4e2227e2643b52d2a6f660f62c058ba85526123b Mon Sep 17 00:00:00 2001 From: eve Date: Mon, 16 Dec 2024 17:54:01 +0000 Subject: [PATCH 001/120] Windows: Update get_commit_charge extension to handle Core.CommitCharge case --- volatility3/framework/symbols/windows/extensions/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 793e506c3..ff6d14a8c 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -269,7 +269,10 @@ class MMVAD_SHORT(objects.StructType): return self.u.VadFlags.CommitCharge elif self.has_member("Core"): - return self.Core.u1.VadFlags1.CommitCharge + if self.Core.has_member("CommitCharge"): + return self.Core.CommitCharge + else: + return self.Core.u1.VadFlags1.CommitCharge raise AttributeError("Unable to find the commit charge member") From b8e8fb6e92e403bfad4e52675e2737b9eb7ba49c Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 21 Jan 2025 18:37:04 +0100 Subject: [PATCH 002/120] 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 000000000..bb8519643 --- /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 003/120] 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 000000000..ba52a93ce --- /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 004/120] 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 832b2a5ba..f2403cf4a 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 005/120] 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 000000000..e69de29bb From 37b792b09c440591abcdfa19d8b49dc32f5b5695 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 21 Jan 2025 18:57:42 +0100 Subject: [PATCH 006/120] 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 ba52a93ce..01268aa8a 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 @@ class Check_ftrace(interfaces.plugins.PluginInterface): # 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 007/120] 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 01268aa8a..a198647d9 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 @@ class Check_ftrace(interfaces.plugins.PluginInterface): @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 @@ class Check_ftrace(interfaces.plugins.PluginInterface): 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 @@ class Check_ftrace(interfaces.plugins.PluginInterface): 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 @@ class Check_ftrace(interfaces.plugins.PluginInterface): 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 008/120] 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 a198647d9..a04491550 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 009/120] 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 a04491550..009d48ce8 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 @@ vollog = logging.getLogger(__name__) # 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 @@ class Check_ftrace(interfaces.plugins.PluginInterface): ), ] - @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 @@ class Check_ftrace(interfaces.plugins.PluginInterface): 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 @@ class Check_ftrace(interfaces.plugins.PluginInterface): 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 @@ class Check_ftrace(interfaces.plugins.PluginInterface): 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 @@ class Check_ftrace(interfaces.plugins.PluginInterface): ) # 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 010/120] 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 bb8519643..4be460a68 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 bd83369c67428e9962379012414509efb3febb8c Mon Sep 17 00:00:00 2001 From: hsarkey Date: Wed, 9 Oct 2024 15:31:53 -0400 Subject: [PATCH 011/120] Updates to make the windows.dlllist plugin report dlls from wow64 processes. --- .../symbols/windows/extensions/__init__.py | 144 +- .../framework/symbols/windows/wow64.json | 1585 +++++++++++++++++ 2 files changed, 1709 insertions(+), 20 deletions(-) create mode 100644 volatility3/framework/symbols/windows/wow64.json diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 214002f49..2b73fa625 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -24,6 +24,7 @@ from volatility3.framework.objects import utility from volatility3.framework.renderers import conversion from volatility3.framework.symbols import generic from volatility3.framework.symbols.windows.extensions import pool +from volatility3.framework.symbols import windows vollog = logging.getLogger(__name__) @@ -484,9 +485,9 @@ class FILE_OBJECT(objects.StructType, pool.ExecutiveObject): ].is_valid(self.FileName.Buffer) def file_name_with_device(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: - name: Union[str, interfaces.renderers.BaseAbsentValue] = ( - renderers.UnreadableValue() - ) + name: Union[ + str, interfaces.renderers.BaseAbsentValue + ] = renderers.UnreadableValue() # this pointer needs to be checked against native_layer_name because the object may # be instantiated from a primary (virtual) layer or a memory (physical) layer. @@ -775,15 +776,93 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): ) return peb + def get_peb32(self) -> interfaces.objects.ObjectInterface: + """Constructs a PEB32 object""" + if constants.BANG not in self.vol.type_name: + raise ValueError( + f"Invalid symbol table name syntax (no {constants.BANG} found)" + ) + + # add_process_layer can raise InvalidAddressException. + # if that happens, we let the exception propagate upwards + proc_layer_name = self.add_process_layer() + proc_layer = self._context.layers[proc_layer_name] + + # Determine if process is running under WOW64. + if self.get_is_wow64(): + peb32 = self.get_wow_64_process() + else: + return None + # Confirm WoW64Process points to a valid process address + if not proc_layer.is_valid(peb32): + raise exceptions.InvalidAddressException( + proc_layer_name, peb32, f"Invalid Wow64Process address at {self.Peb:0x}" + ) + + # Leverage the context of existing symbol table to help configure + # a new symbol table for 32-bit types + sym_table = self.get_symbol_table_name() + config_path = self._context.symbol_space[sym_table].config_path + + # Load the 32-bit types into a new symbol space + # We use the WindowsKernelIntermedSymbols class to make + # sure we get all the object helpers. For example, traversing + # linked-lists. + self._32bit_table_name = windows.WindowsKernelIntermedSymbols.create( + self._context, config_path, "windows", "wow64" + ) + + # windows 10 + if self._context.symbol_space.has_type( + sym_table + constants.BANG + "_EWOW64PROCESS" + ): + peb32 = self._context.object( + f"{self._32bit_table_name}{constants.BANG}_PEB32", + layer_name=proc_layer_name, + offset=peb32.Peb, + ) + return peb32 + + # vista sp0-sp1 and 2003 sp1-sp2 + elif self._context.symbol_space.has_type( + sym_table + constants.BANG + "_WOW64_PROCESS" + ): + peb32 = self._context.object( + f"{self._32bit_table_name}{constants.BANG}_PEB32", + layer_name=proc_layer_name, + offset=peb32.Wow64, + ) + return peb32 + + else: + peb32 = self._context.object( + f"{self._32bit_table_name}{constants.BANG}_PEB32", + layer_name=proc_layer_name, + offset=peb32, + ) + return peb32 + def load_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: """Generator for DLLs in the order that they were loaded.""" - try: - peb = self.get_peb() - yield from peb.Ldr.InLoadOrderModuleList.to_list( - f"{self.get_symbol_table_name()}{constants.BANG}_LDR_DATA_TABLE_ENTRY", - "InLoadOrderLinks", - ) + pebs = [ + [self.get_peb(), "_LDR_DATA_TABLE_ENTRY"], + [self.get_peb32(), "_LDR_DATA_TABLE_ENTRY"], + ] + for peb, table_name in pebs: + if peb != None: + sym_table = self.get_symbol_table_name() + if peb.Ldr.vol.type_name.endswith("unsigned long"): + Ldr_data = self._context.symbol_space.get_type( + self._32bit_table_name + constants.BANG + "_PEB_LDR_DATA" + ) + peb.Ldr = peb.Ldr.cast("pointer", subtype=Ldr_data) + sym_table = self._32bit_table_name + for entry in peb.Ldr.InLoadOrderModuleList.to_list( + f"{sym_table}{constants.BANG}" + table_name, + "InLoadOrderLinks", + ): + yield entry except exceptions.InvalidAddressException: return None @@ -791,23 +870,48 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): """Generator for DLLs in the order that they were initialized""" try: - peb = self.get_peb() - yield from peb.Ldr.InInitializationOrderModuleList.to_list( - f"{self.get_symbol_table_name()}{constants.BANG}_LDR_DATA_TABLE_ENTRY", - "InInitializationOrderLinks", - ) + pebs = [ + [self.get_peb(), "_LDR_DATA_TABLE_ENTRY"], + [self.get_peb32(), "_LDR_DATA_TABLE_ENTRY"], + ] + for peb, table_name in pebs: + if peb != None: + sym_table = self.get_symbol_table_name() + if peb.Ldr.vol.type_name.endswith("unsigned long"): + Ldr_data = self._context.symbol_space.get_type( + self._32bit_table_name + constants.BANG + "_PEB_LDR_DATA" + ) + peb.Ldr = peb.Ldr.cast("pointer", subtype=Ldr_data) + sym_table = self._32bit_table_name + for entry in peb.Ldr.InInitializationOrderModuleList.to_list( + f"{sym_table}{constants.BANG}" + table_name, + "InInitializationOrderLinks", + ): + yield entry except exceptions.InvalidAddressException: return None def mem_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: """Generator for DLLs in the order that they appear in memory""" - try: - peb = self.get_peb() - yield from peb.Ldr.InMemoryOrderModuleList.to_list( - f"{self.get_symbol_table_name()}{constants.BANG}_LDR_DATA_TABLE_ENTRY", - "InMemoryOrderLinks", - ) + pebs = [ + [self.get_peb(), "_LDR_DATA_TABLE_ENTRY"], + [self.get_peb32(), "_LDR_DATA_TABLE_ENTRY"], + ] + for peb, table_name in pebs: + if peb != None: + sym_table = self.get_symbol_table_name() + if peb.Ldr.vol.type_name.endswith("unsigned long"): + Ldr_data = self._context.symbol_space.get_type( + self._32bit_table_name + constants.BANG + "_PEB_LDR_DATA" + ) + peb.Ldr = peb.Ldr.cast("pointer", subtype=Ldr_data) + sym_table = self._32bit_table_name + for entry in peb.Ldr.InMemoryOrderModuleList.to_list( + f"{sym_table}{constants.BANG}" + table_name, + "InMemoryOrderLinks", + ): + yield entry except exceptions.InvalidAddressException: return None diff --git a/volatility3/framework/symbols/windows/wow64.json b/volatility3/framework/symbols/windows/wow64.json new file mode 100644 index 000000000..e28d77241 --- /dev/null +++ b/volatility3/framework/symbols/windows/wow64.json @@ -0,0 +1,1585 @@ +{ + "symbols": {}, + "enums": { + "_LDR_DLL_LOAD_REASON": { + "base": "int", + "constants": { + "LoadReasonAsDataLoad": 6, + "LoadReasonAsImageLoad": 5, + "LoadReasonDelayloadDependency": 3, + "LoadReasonDynamicForwarderDependency": 2, + "LoadReasonDynamicLoad": 4, + "LoadReasonStaticDependency": 0, + "LoadReasonStaticForwarderDependency": 1, + "LoadReasonUnknown": -1, + }, + "size": 4, + }, + "_LDR_DDAG_STATE": { + "base": "int", + "constants": { + "LdrModulesCondensed": 6, + "LdrModulesInitError": -4, + "LdrModulesInitializing": 8, + "LdrModulesMapped": 2, + "LdrModulesMapping": 1, + "LdrModulesMerged": -5, + "LdrModulesPlaceHolder": 0, + "LdrModulesReadyToInit": 7, + "LdrModulesReadyToRun": 9, + "LdrModulesSnapError": -3, + "LdrModulesSnapped": 5, + "LdrModulesSnapping": 4, + "LdrModulesUnloaded": -2, + "LdrModulesUnloading": -1, + "LdrModulesWaitingForDependencies": 3, + }, + "size": 4, + }, + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little", + }, + "int": {"endian": "little", "kind": "int", "signed": true, "size": 4}, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little", + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little", + }, + "pointer": {"kind": "int", "size": 4, "signed": false, "endian": "little"}, + "unsigned int": {"kind": "int", "size": 4, "signed": false, "endian": "little"}, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little", + }, + "long": {"kind": "int", "size": 4, "signed": false, "endian": "little"}, + "long long": {"endian": "little", "kind": "int", "signed": true, "size": 8}, + "void": {"endian": "little", "kind": "void", "signed": true, "size": 0}, + }, + "metadata": { + "format": "4.1.0", + "producer": { + "datetime": "2024-05-30T17:02:06.755760", + "name": "awalters-by-hand", + "version": "0.0.2", + }, + }, + "user_types": { + "_LDR_SERVICE_TAG_RECORD": { + "fields": { + "Next": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LDR_SERVICE_TAG_RECORD", + }, + }, + }, + "ServiceTag": { + "offset": 4, + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "kind": "struct", + "size": 8, + }, + "_KTIMER": { + "fields": { + "Dpc": { + "offset": 32, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_KDPC"}, + }, + }, + "DueTime": { + "offset": 16, + "type": {"kind": "union", "name": "_ULARGE_INTEGER"}, + }, + "Header": { + "offset": 0, + "type": {"kind": "struct", "name": "_DISPATCHER_HEADER"}, + }, + "Period": { + "offset": 36, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "TimerListEntry": { + "offset": 24, + "type": {"kind": "struct", "name": "_LIST_ENTRY"}, + }, + }, + "kind": "struct", + "size": 40, + }, + "_ERESOURCE": { + "fields": { + "ActiveCount": { + "offset": 12, + "type": {"kind": "base", "name": "short"}, + }, + "ActiveEntries": { + "offset": 32, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "Address": { + "offset": 48, + "type": { + "kind": "pointer", + "subtype": {"kind": "base", "name": "void"}, + }, + }, + "ContentionCount": { + "offset": 36, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "CreatorBackTraceIndex": { + "offset": 48, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ExclusiveWaiters": { + "offset": 20, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_KEVENT"}, + }, + }, + "Flag": { + "offset": 14, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "NumberOfExclusiveWaiters": { + "offset": 44, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "NumberOfSharedWaiters": { + "offset": 40, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "OwnerEntry": { + "offset": 24, + "type": {"kind": "struct", "name": "_OWNER_ENTRY"}, + }, + "OwnerTable": { + "offset": 8, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_OWNER_ENTRY"}, + }, + }, + "ReservedLowFlags": { + "offset": 14, + "type": {"kind": "base", "name": "unsigned char"}, + }, + "SharedWaiters": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_KSEMAPHORE"}, + }, + }, + "SpinLock": { + "offset": 52, + "type": {"kind": "base", "name": "unsigned long long"}, + }, + "SystemResourcesList": { + "offset": 0, + "type": {"kind": "struct", "name": "_LIST_ENTRY"}, + }, + "WaiterPriority": { + "offset": 15, + "type": {"kind": "base", "name": "unsigned char"}, + }, + }, + "kind": "struct", + "size": 56, + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": {"offset": 4, "type": {"kind": "base", "name": "long"}}, + "LowPart": { + "offset": 0, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "QuadPart": { + "offset": 0, + "type": {"kind": "base", "name": "long long"}, + }, + "u": { + "offset": 0, + "type": {"kind": "struct", "name": "__unnamed_1083"}, + }, + }, + "kind": "union", + "size": 8, + }, + "_ETHREAD": { + "fields": { + "Cid": { + "offset": 868, + "type": {"kind": "struct", "name": "_CLIENT_ID"}, + }, + "CreateTime": { + "offset": 824, + "type": {"kind": "union", "name": "_LARGE_INTEGER"}, + }, + "CrossThreadFlags": { + "offset": 952, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ExitTime": { + "offset": 832, + "type": {"kind": "union", "name": "_LARGE_INTEGER"}, + }, + "Tcb": {"offset": 0, "type": {"kind": "struct", "name": "_KTHREAD"}}, + }, + "kind": "struct", + "size": 1048, + }, + "_KTHREAD": { + "fields": { + "State": { + "offset": 144, + "type": {"kind": "base", "name": "unsigned char"}, + }, + "WaitReason": { + "offset": 395, + "type": {"kind": "base", "name": "unsigned char"}, + }, + }, + "kind": "struct", + "size": 824, + }, + "_EPROCESS": { + "fields": { + "CreateTime": { + "offset": 168, + "type": {"kind": "union", "name": "_LARGE_INTEGER"}, + }, + "ExitTime": { + "offset": 688, + "type": {"kind": "union", "name": "_LARGE_INTEGER"}, + }, + "ImageFileName": { + "offset": 1080, + "type": { + "count": 368, + "kind": "array", + "subtype": {"kind": "base", "name": "unsigned char"}, + }, + }, + "ObjectTable": { + "offset": 336, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_HANDLE_TABLE"}, + }, + }, + "Pcb": {"offset": 0, "type": {"kind": "struct", "name": "_KPROCESS"}}, + "Peb": { + "offset": 320, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_PEB"}, + }, + }, + "Session": { + "offset": 324, + "type": { + "kind": "pointer", + "subtype": {"kind": "base", "name": "void"}, + }, + }, + "ThreadListHead": { + "offset": 404, + "type": {"kind": "struct", "name": "_LIST_ENTRY"}, + }, + "UniqueProcessId": { + "offset": 180, + "type": { + "kind": "pointer", + "subtype": {"kind": "base", "name": "void"}, + }, + }, + "VadRoot": { + "offset": 628, + "type": {"kind": "struct", "name": "_RTL_AVL_TREE"}, + }, + }, + "kind": "struct", + "size": 760, + }, + "_EX_FAST_REF": { + "fields": { + "Object": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": {"kind": "base", "name": "void"}, + }, + }, + "RefCnt": { + "offset": 0, + "type": { + "bit_length": 4, + "bit_position": 0, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "Value": { + "offset": 0, + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "kind": "struct", + "size": 4, + }, + "_TOKEN": { + "fields": { + "Privileges": { + "offset": 64, + "type": {"kind": "struct", "name": "_SEP_TOKEN_PRIVILEGES"}, + }, + "UserAndGroupCount": { + "offset": 124, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "UserAndGroups": { + "offset": 148, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_SID_AND_ATTRIBUTES"}, + }, + }, + }, + "kind": "struct", + "size": 656, + }, + "_OBJECT_HEADER": { + "fields": { + "Body": {"offset": 24, "type": {"kind": "struct", "name": "_QUAD"}}, + "InfoMask": { + "offset": 14, + "type": {"kind": "base", "name": "unsigned char"}, + }, + "PointerCount": {"offset": 0, "type": {"kind": "base", "name": "long"}}, + "TypeIndex": { + "offset": 12, + "type": {"kind": "base", "name": "unsigned char"}, + }, + }, + "kind": "struct", + "size": 32, + }, + "_FILE_OBJECT": { + "fields": { + "DeleteAccess": { + "offset": 40, + "type": {"kind": "base", "name": "unsigned char"}, + }, + "DeviceObject": { + "offset": 4, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_DEVICE_OBJECT"}, + }, + }, + "FileName": { + "offset": 48, + "type": {"kind": "struct", "name": "_UNICODE_STRING"}, + }, + "ReadAccess": { + "offset": 38, + "type": {"kind": "base", "name": "unsigned char"}, + }, + "SharedDelete": { + "offset": 43, + "type": {"kind": "base", "name": "unsigned char"}, + }, + "SharedRead": { + "offset": 41, + "type": {"kind": "base", "name": "unsigned char"}, + }, + "SharedWrite": { + "offset": 42, + "type": {"kind": "base", "name": "unsigned char"}, + }, + "WriteAccess": { + "offset": 39, + "type": {"kind": "base", "name": "unsigned char"}, + }, + }, + "kind": "struct", + "size": 128, + }, + "_DEVICE_OBJECT": { + "fields": { + "AttachedDevice": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_DEVICE_OBJECT"}, + }, + }, + "Flags": { + "offset": 48, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "NextDevice": { + "offset": 12, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_DEVICE_OBJECT"}, + }, + }, + }, + "kind": "struct", + "size": 184, + }, + "_CM_KEY_BODY": { + "fields": { + "KeyControlBlock": { + "offset": 4, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_CM_KEY_CONTROL_BLOCK"}, + }, + }, + "Type": { + "offset": 0, + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "kind": "struct", + "size": 44, + }, + "_CMHIVE": { + "fields": { + "FileFullPath": { + "offset": 1136, + "type": {"kind": "struct", "name": "_UNICODE_STRING"}, + }, + "FileUserName": { + "offset": 1144, + "type": {"kind": "struct", "name": "_UNICODE_STRING"}, + }, + "Hive": {"offset": 0, "type": {"kind": "struct", "name": "_HHIVE"}}, + "HiveRootPath": { + "offset": 1160, + "type": {"kind": "struct", "name": "_UNICODE_STRING"}, + }, + }, + "kind": "struct", + "size": 3104, + }, + "_CM_KEY_NODE": { + "fields": { + "Name": { + "offset": 76, + "type": { + "count": 1, + "kind": "array", + "subtype": {"kind": "base", "name": "wchar"}, + }, + }, + "NameLength": { + "offset": 72, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "Parent": { + "offset": 16, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "SubKeyLists": { + "offset": 28, + "type": { + "count": 2, + "kind": "array", + "subtype": {"kind": "base", "name": "unsigned long"}, + }, + }, + "ValueList": { + "offset": 36, + "type": {"kind": "struct", "name": "_CHILD_LIST"}, + }, + }, + "kind": "struct", + "size": 80, + }, + "_CM_KEY_VALUE": { + "fields": { + "Data": { + "offset": 8, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "DataLength": { + "offset": 4, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "Flags": { + "offset": 16, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "Name": { + "offset": 20, + "type": { + "count": 1, + "kind": "array", + "subtype": {"kind": "base", "name": "wchar"}, + }, + }, + "NameLength": { + "offset": 2, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "Signature": { + "offset": 0, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "Spare": { + "offset": 18, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "Type": { + "offset": 12, + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "kind": "struct", + "size": 24, + }, + "_HMAP_ENTRY": { + "fields": { + "BinAddress": { + "offset": 4, + "type": {"kind": "base", "name": "unsigned long long"}, + }, + "BlockAddress": { + "offset": 0, + "type": {"kind": "base", "name": "unsigned long long"}, + }, + "MemAlloc": { + "offset": 8, + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "kind": "struct", + "size": 12, + }, + "_MMVAD_SHORT": { + "fields": { + "EndingVpn": { + "offset": 16, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "NextVad": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_MMVAD_SHORT"}, + }, + }, + "StartingVpn": { + "offset": 12, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "VadNode": { + "offset": 0, + "type": {"kind": "struct", "name": "_RTL_BALANCED_NODE"}, + }, + }, + "kind": "struct", + "size": 40, + }, + "_MMVAD": { + "fields": { + "Core": { + "offset": 0, + "type": {"kind": "struct", "name": "_MMVAD_SHORT"}, + }, + "Subsection": { + "offset": 44, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_SUBSECTION"}, + }, + }, + }, + "kind": "struct", + "size": 72, + }, + "_KSYSTEM_TIME": { + "fields": { + "High1Time": {"offset": 4, "type": {"kind": "base", "name": "long"}}, + "High2Time": {"offset": 8, "type": {"kind": "base", "name": "long"}}, + "LowPart": { + "offset": 0, + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "kind": "struct", + "size": 12, + }, + "_KMUTANT": { + "fields": { + "Header": { + "offset": 0, + "type": {"kind": "struct", "name": "_DISPATCHER_HEADER"}, + } + }, + "kind": "struct", + "size": 32, + }, + "_DRIVER_OBJECT": { + "fields": { + "DeviceObject": { + "offset": 4, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_DEVICE_OBJECT"}, + }, + } + }, + "kind": "struct", + "size": 168, + }, + "_OBJECT_SYMBOLIC_LINK": { + "fields": { + "CreationTime": { + "offset": 0, + "type": {"kind": "union", "name": "_LARGE_INTEGER"}, + } + }, + "kind": "struct", + "size": 24, + }, + "_CONTROL_AREA": { + "fields": { + "FilePointer": { + "offset": 32, + "type": {"kind": "struct", "name": "_EX_FAST_REF"}, + } + }, + "kind": "struct", + "size": 80, + }, + "_SHARED_CACHE_MAP": { + "fields": { + "FileSize": { + "offset": 8, + "type": {"kind": "union", "name": "_LARGE_INTEGER"}, + }, + "InitialVacbs": { + "offset": 48, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_VACB"}, + }, + }, + }, + "Section": { + "offset": 108, + "type": { + "kind": "pointer", + "subtype": {"kind": "base", "name": "void"}, + }, + }, + "SectionSize": { + "offset": 24, + "type": {"kind": "union", "name": "_LARGE_INTEGER"}, + }, + "Vacbs": { + "offset": 64, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_VACB"}, + }, + }, + }, + "ValidDataLength": { + "offset": 32, + "type": {"kind": "union", "name": "_LARGE_INTEGER"}, + }, + }, + "kind": "struct", + "size": 368, + }, + "_VACB": { + "fields": { + "ArrayHead": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_VACB_ARRAY_HEADER"}, + }, + }, + "BaseAddress": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": {"kind": "base", "name": "void"}, + }, + }, + "Overlay": { + "offset": 8, + "type": {"kind": "union", "name": "__unnamed_1971"}, + }, + "SharedCacheMap": { + "offset": 4, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_SHARED_CACHE_MAP"}, + }, + }, + }, + "kind": "struct", + "size": 24, + }, + "_POOL_TRACKER_BIG_PAGES": { + "fields": { + "Key": {"offset": 4, "type": {"kind": "base", "name": "unsigned long"}}, + "NumberOfBytes": { + "offset": 12, + "type": {"kind": "base", "name": "unsigned long long"}, + }, + "PoolType": { + "offset": 8, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "Va": {"offset": 0, "type": {"kind": "base", "name": "unsigned long"}}, + }, + "kind": "struct", + "size": 16, + }, + "_IMAGE_DOS_HEADER": { + "fields": { + "e_cblp": { + "offset": 2, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "e_cp": { + "offset": 4, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "e_cparhdr": { + "offset": 8, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "e_crlc": { + "offset": 6, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "e_cs": { + "offset": 22, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "e_csum": { + "offset": 18, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "e_ip": { + "offset": 20, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "e_lfanew": {"offset": 60, "type": {"kind": "base", "name": "long"}}, + "e_lfarlc": { + "offset": 24, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "e_magic": { + "offset": 0, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "e_maxalloc": { + "offset": 12, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "e_minalloc": { + "offset": 10, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "e_oemid": { + "offset": 36, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "e_oeminfo": { + "offset": 38, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "e_ovno": { + "offset": 26, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "e_res": { + "offset": 28, + "type": { + "count": 4, + "kind": "array", + "subtype": {"kind": "base", "name": "unsigned short"}, + }, + }, + "e_res2": { + "offset": 40, + "type": { + "count": 10, + "kind": "array", + "subtype": {"kind": "base", "name": "unsigned short"}, + }, + }, + "e_sp": { + "offset": 16, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "e_ss": { + "offset": 14, + "type": {"kind": "base", "name": "unsigned short"}, + }, + }, + "kind": "struct", + "size": 64, + }, + "_SINGLE_LIST_ENTRY": { + "fields": { + "Next": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_SINGLE_LIST_ENTRY"}, + }, + } + }, + "kind": "struct", + "size": 4, + }, + "_LDRP_CSLIST": { + "fields": { + "Tail": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_SINGLE_LIST_ENTRY"}, + }, + } + }, + "kind": "struct", + "size": 4, + }, + "_RTL_BALANCED_NODE": { + "fields": { + "Balance": { + "offset": 8, + "type": { + "bit_length": 2, + "bit_position": 0, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned char"}, + }, + }, + "Children": { + "offset": 0, + "type": { + "count": 2, + "kind": "array", + "subtype": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_RTL_BALANCED_NODE"}, + }, + }, + }, + "Left": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_RTL_BALANCED_NODE"}, + }, + }, + "ParentValue": { + "offset": 8, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "Red": { + "offset": 8, + "type": { + "bit_length": 1, + "bit_position": 0, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned char"}, + }, + }, + "Right": { + "offset": 4, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_RTL_BALANCED_NODE"}, + }, + }, + }, + "kind": "struct", + "size": 12, + }, + "_LIST_ENTRY": { + "fields": { + "Blink": { + "offset": 4, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_LIST_ENTRY"}, + }, + }, + "Flink": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": {"kind": "struct", "name": "_LIST_ENTRY"}, + }, + }, + }, + "kind": "struct", + "size": 8, + }, + "LIST_ENTRY32": { + "fields": { + "Blink": { + "offset": 4, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "Flink": { + "offset": 0, + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "kind": "struct", + "size": 8, + }, + "_PEB_LDR_DATA": { + "fields": { + "EntryInProgress": { + "offset": 36, + "type": { + "kind": "pointer", + "subtype": {"kind": "base", "name": "void"}, + }, + }, + "InInitializationOrderModuleList": { + "offset": 28, + "type": {"kind": "struct", "name": "_LIST_ENTRY"}, + }, + "InLoadOrderModuleList": { + "offset": 12, + "type": {"kind": "struct", "name": "_LIST_ENTRY"}, + }, + "InMemoryOrderModuleList": { + "offset": 20, + "type": {"kind": "struct", "name": "_LIST_ENTRY"}, + }, + "Initialized": { + "offset": 4, + "type": {"kind": "base", "name": "unsigned char"}, + }, + "Length": { + "offset": 0, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ShutdownInProgress": { + "offset": 40, + "type": {"kind": "base", "name": "unsigned char"}, + }, + "ShutdownThreadId": { + "offset": 44, + "type": { + "kind": "pointer", + "subtype": {"kind": "base", "name": "void"}, + }, + }, + "SsHandle": { + "offset": 8, + "type": { + "kind": "pointer", + "subtype": {"kind": "base", "name": "void"}, + }, + }, + }, + "kind": "struct", + "size": 48, + }, + "_LDR_DATA_TABLE_ENTRY": { + "fields": { + "BaseDllName": { + "offset": 44, + "type": {"kind": "struct", "name": "_UNICODE_STRING"}, + }, + "FullDllName": { + "offset": 36, + "type": {"kind": "struct", "name": "_UNICODE_STRING"}, + }, + "LoadTime": { + "offset": 256, + "type": {"kind": "union", "name": "_LARGE_INTEGER"}, + }, + "DllBase": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": {"kind": "base", "name": "void"}, + }, + }, + "SizeOfImage": { + "offset": 32, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "InInitializationOrderLinks": { + "offset": 16, + "type": {"kind": "struct", "name": "_LIST_ENTRY"}, + }, + "InLoadOrderLinks": { + "offset": 0, + "type": {"kind": "struct", "name": "_LIST_ENTRY"}, + }, + "InMemoryOrderLinks": { + "offset": 8, + "type": {"kind": "struct", "name": "_LIST_ENTRY"}, + }, + }, + "kind": "struct", + "size": 160, + }, + "_PEB32": { + "fields": { + "ActivationContextData": { + "offset": 504, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ActiveProcessAffinityMask": { + "offset": 192, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "AnsiCodePageData": { + "offset": 88, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ApiSetMap": { + "offset": 56, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "AppCompatFlags": { + "offset": 472, + "type": {"kind": "union", "name": "_ULARGE_INTEGER"}, + }, + "AppCompatFlagsUser": { + "offset": 480, + "type": {"kind": "union", "name": "_ULARGE_INTEGER"}, + }, + "AppCompatInfo": { + "offset": 492, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "AtlThunkSListPtr": { + "offset": 32, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "AtlThunkSListPtr32": { + "offset": 52, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "BeingDebugged": { + "offset": 2, + "type": {"kind": "base", "name": "unsigned char"}, + }, + "BitField": { + "offset": 3, + "type": {"kind": "base", "name": "unsigned char"}, + }, + "CSDVersion": { + "offset": 496, + "type": {"kind": "struct", "name": "_STRING32"}, + }, + "CritSecTracingEnabled": { + "offset": 576, + "type": { + "bit_length": 1, + "bit_position": 1, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "CriticalSectionTimeout": { + "offset": 112, + "type": {"kind": "union", "name": "_LARGE_INTEGER"}, + }, + "CrossProcessFlags": { + "offset": 40, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "CsrServerReadOnlySharedMemoryBase": { + "offset": 584, + "type": {"kind": "base", "name": "unsigned long long"}, + }, + "FastPebLock": { + "offset": 28, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "FlsBitmap": { + "offset": 536, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "FlsBitmapBits": { + "offset": 540, + "type": { + "count": 4, + "kind": "array", + "subtype": {"kind": "base", "name": "unsigned long"}, + }, + }, + "FlsCallback": { + "offset": 524, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "FlsHighIndex": { + "offset": 556, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "FlsListHead": { + "offset": 528, + "type": {"kind": "struct", "name": "LIST_ENTRY32"}, + }, + "GdiDCAttributeList": { + "offset": 156, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "GdiHandleBuffer": { + "offset": 196, + "type": { + "count": 34, + "kind": "array", + "subtype": {"kind": "base", "name": "unsigned long"}, + }, + }, + "GdiSharedHandleTable": { + "offset": 148, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "HeapDeCommitFreeBlockThreshold": { + "offset": 132, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "HeapDeCommitTotalFreeThreshold": { + "offset": 128, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "HeapSegmentCommit": { + "offset": 124, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "HeapSegmentReserve": { + "offset": 120, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "HeapTracingEnabled": { + "offset": 576, + "type": { + "bit_length": 1, + "bit_position": 0, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "IFEOKey": { + "offset": 36, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ImageBaseAddress": { + "offset": 8, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ImageSubsystem": { + "offset": 180, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ImageSubsystemMajorVersion": { + "offset": 184, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ImageSubsystemMinorVersion": { + "offset": 188, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ImageUsesLargePages": { + "offset": 3, + "type": { + "bit_length": 1, + "bit_position": 0, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned char"}, + }, + }, + "InheritedAddressSpace": { + "offset": 0, + "type": {"kind": "base", "name": "unsigned char"}, + }, + "IsAppContainer": { + "offset": 3, + "type": { + "bit_length": 1, + "bit_position": 5, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned char"}, + }, + }, + "IsImageDynamicallyRelocated": { + "offset": 3, + "type": { + "bit_length": 1, + "bit_position": 2, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned char"}, + }, + }, + "IsPackagedProcess": { + "offset": 3, + "type": { + "bit_length": 1, + "bit_position": 4, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned char"}, + }, + }, + "IsProtectedProcess": { + "offset": 3, + "type": { + "bit_length": 1, + "bit_position": 1, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned char"}, + }, + }, + "IsProtectedProcessLight": { + "offset": 3, + "type": { + "bit_length": 1, + "bit_position": 6, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned char"}, + }, + }, + "KernelCallbackTable": { + "offset": 44, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "Ldr": { + "offset": 12, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "LibLoaderTracingEnabled": { + "offset": 576, + "type": { + "bit_length": 1, + "bit_position": 2, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "LoaderLock": { + "offset": 160, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "MaximumNumberOfHeaps": { + "offset": 140, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "MinimumStackCommit": { + "offset": 520, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "Mutant": { + "offset": 4, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "NtGlobalFlag": { + "offset": 104, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "NumberOfHeaps": { + "offset": 136, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "NumberOfProcessors": { + "offset": 100, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "OSBuildNumber": { + "offset": 172, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "OSCSDVersion": { + "offset": 174, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "OSMajorVersion": { + "offset": 164, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "OSMinorVersion": { + "offset": 168, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "OSPlatformId": { + "offset": 176, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "OemCodePageData": { + "offset": 92, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "PostProcessInitRoutine": { + "offset": 332, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ProcessAssemblyStorageMap": { + "offset": 508, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ProcessHeap": { + "offset": 24, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ProcessHeaps": { + "offset": 144, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ProcessInJob": { + "offset": 40, + "type": { + "bit_length": 1, + "bit_position": 0, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "ProcessInitializing": { + "offset": 40, + "type": { + "bit_length": 1, + "bit_position": 1, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "ProcessParameters": { + "offset": 16, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ProcessStarterHelper": { + "offset": 152, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ProcessUsingFTH": { + "offset": 40, + "type": { + "bit_length": 1, + "bit_position": 4, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "ProcessUsingVCH": { + "offset": 40, + "type": { + "bit_length": 1, + "bit_position": 3, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "ProcessUsingVEH": { + "offset": 40, + "type": { + "bit_length": 1, + "bit_position": 2, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "ReadImageFileExecOptions": { + "offset": 1, + "type": {"kind": "base", "name": "unsigned char"}, + }, + "ReadOnlySharedMemoryBase": { + "offset": 76, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ReadOnlyStaticServerData": { + "offset": 84, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "ReservedBits0": { + "offset": 40, + "type": { + "bit_length": 27, + "bit_position": 5, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "SessionId": { + "offset": 468, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "SkipPatchingUser32Forwarders": { + "offset": 3, + "type": { + "bit_length": 1, + "bit_position": 3, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned char"}, + }, + }, + "SpareBits": { + "offset": 3, + "type": { + "bit_length": 1, + "bit_position": 7, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned char"}, + }, + }, + "SparePvoid0": { + "offset": 80, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "SpareTracingBits": { + "offset": 576, + "type": { + "bit_length": 29, + "bit_position": 3, + "kind": "bitfield", + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "SubSystemData": { + "offset": 20, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "SystemAssemblyStorageMap": { + "offset": 516, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "SystemDefaultActivationContextData": { + "offset": 512, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "SystemReserved": { + "offset": 48, + "type": { + "count": 1, + "kind": "array", + "subtype": {"kind": "base", "name": "unsigned long"}, + }, + }, + "TlsBitmap": { + "offset": 64, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "TlsBitmapBits": { + "offset": 68, + "type": { + "count": 2, + "kind": "array", + "subtype": {"kind": "base", "name": "unsigned long"}, + }, + }, + "TlsExpansionBitmap": { + "offset": 336, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "TlsExpansionBitmapBits": { + "offset": 340, + "type": { + "count": 32, + "kind": "array", + "subtype": {"kind": "base", "name": "unsigned long"}, + }, + }, + "TlsExpansionCounter": { + "offset": 60, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "TracingFlags": { + "offset": 576, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "UnicodeCaseTableData": { + "offset": 96, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "UserSharedInfoPtr": { + "offset": 44, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "WerRegistrationData": { + "offset": 560, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "WerShipAssertPtr": { + "offset": 564, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "pImageHeaderHash": { + "offset": 572, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "pShimData": { + "offset": 488, + "type": {"kind": "base", "name": "unsigned long"}, + }, + "pUnused": { + "offset": 568, + "type": {"kind": "base", "name": "unsigned long"}, + }, + }, + "kind": "struct", + "size": 592, + }, + "_UNICODE_STRING": { + "fields": { + "Buffer": { + "offset": 4, + "type": { + "kind": "pointer", + "subtype": {"kind": "base", "name": "unsigned short"}, + }, + }, + "Length": { + "offset": 0, + "type": {"kind": "base", "name": "unsigned short"}, + }, + "MaximumLength": { + "offset": 2, + "type": {"kind": "base", "name": "unsigned short"}, + }, + }, + "kind": "struct", + "size": 8, + }, + }, +} From c317cad835f66e01d4ad6ee50f24967d5a8be7b7 Mon Sep 17 00:00:00 2001 From: hsarkey Date: Wed, 9 Oct 2024 15:53:59 -0400 Subject: [PATCH 012/120] Syntax changes to wow64.json --- .../framework/symbols/windows/wow64.json | 3941 ++++++++++------- 1 file changed, 2391 insertions(+), 1550 deletions(-) diff --git a/volatility3/framework/symbols/windows/wow64.json b/volatility3/framework/symbols/windows/wow64.json index e28d77241..4c5cdd4a4 100644 --- a/volatility3/framework/symbols/windows/wow64.json +++ b/volatility3/framework/symbols/windows/wow64.json @@ -1,1585 +1,2426 @@ { - "symbols": {}, - "enums": { - "_LDR_DLL_LOAD_REASON": { - "base": "int", - "constants": { - "LoadReasonAsDataLoad": 6, - "LoadReasonAsImageLoad": 5, - "LoadReasonDelayloadDependency": 3, - "LoadReasonDynamicForwarderDependency": 2, - "LoadReasonDynamicLoad": 4, - "LoadReasonStaticDependency": 0, - "LoadReasonStaticForwarderDependency": 1, - "LoadReasonUnknown": -1, - }, - "size": 4, - }, - "_LDR_DDAG_STATE": { - "base": "int", - "constants": { - "LdrModulesCondensed": 6, - "LdrModulesInitError": -4, - "LdrModulesInitializing": 8, - "LdrModulesMapped": 2, - "LdrModulesMapping": 1, - "LdrModulesMerged": -5, - "LdrModulesPlaceHolder": 0, - "LdrModulesReadyToInit": 7, - "LdrModulesReadyToRun": 9, - "LdrModulesSnapError": -3, - "LdrModulesSnapped": 5, - "LdrModulesSnapping": 4, - "LdrModulesUnloaded": -2, - "LdrModulesUnloading": -1, - "LdrModulesWaitingForDependencies": 3, - }, - "size": 4, - }, + "symbols": { + }, + "enums": { + "_LDR_DLL_LOAD_REASON": { + "base": "int", + "constants": { + "LoadReasonAsDataLoad": 6, + "LoadReasonAsImageLoad": 5, + "LoadReasonDelayloadDependency": 3, + "LoadReasonDynamicForwarderDependency": 2, + "LoadReasonDynamicLoad": 4, + "LoadReasonStaticDependency": 0, + "LoadReasonStaticForwarderDependency": 1, + "LoadReasonUnknown": -1 + }, + "size": 4 }, + "_LDR_DDAG_STATE": { + "base": "int", + "constants": { + "LdrModulesCondensed": 6, + "LdrModulesInitError": -4, + "LdrModulesInitializing": 8, + "LdrModulesMapped": 2, + "LdrModulesMapping": 1, + "LdrModulesMerged": -5, + "LdrModulesPlaceHolder": 0, + "LdrModulesReadyToInit": 7, + "LdrModulesReadyToRun": 9, + "LdrModulesSnapError": -3, + "LdrModulesSnapped": 5, + "LdrModulesSnapping": 4, + "LdrModulesUnloaded": -2, + "LdrModulesUnloading": -1, + "LdrModulesWaitingForDependencies": 3 + }, + "size": 4 + } + }, "base_types": { "unsigned long": { "kind": "int", "size": 4, "signed": false, - "endian": "little", + "endian": "little" + }, + "int": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 4 }, - "int": {"endian": "little", "kind": "int", "signed": true, "size": 4}, "unsigned long long": { "kind": "int", "size": 8, "signed": false, - "endian": "little", + "endian": "little" }, "unsigned char": { "kind": "char", "size": 1, "signed": false, - "endian": "little", + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" }, - "pointer": {"kind": "int", "size": 4, "signed": false, "endian": "little"}, - "unsigned int": {"kind": "int", "size": 4, "signed": false, "endian": "little"}, "unsigned short": { "kind": "int", "size": 2, "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "long long": { "endian": "little", + "kind": "int", + "signed": true, + "size": 8 }, - "long": {"kind": "int", "size": 4, "signed": false, "endian": "little"}, - "long long": {"endian": "little", "kind": "int", "signed": true, "size": 8}, - "void": {"endian": "little", "kind": "void", "signed": true, "size": 0}, + "void": { + "endian": "little", + "kind": "void", + "signed": true, + "size": 0 + } }, - "metadata": { - "format": "4.1.0", - "producer": { - "datetime": "2024-05-30T17:02:06.755760", - "name": "awalters-by-hand", - "version": "0.0.2", + "metadata": { + "format": "4.1.0", + "producer": { + "datetime": "2024-05-30T17:02:06.755760", + "name": "awalters-by-hand", + "version": "0.0.2" + } + }, + "user_types": { + "_LDR_SERVICE_TAG_RECORD": { + "fields": { + "Next": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LDR_SERVICE_TAG_RECORD" + } + } }, + "ServiceTag": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 }, - "user_types": { - "_LDR_SERVICE_TAG_RECORD": { - "fields": { - "Next": { - "offset": 0, - "type": { - "kind": "pointer", - "subtype": { - "kind": "struct", - "name": "_LDR_SERVICE_TAG_RECORD", - }, - }, - }, - "ServiceTag": { - "offset": 4, - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "kind": "struct", - "size": 8, + "_KTIMER": { + "fields": { + "Dpc": { + "offset": 32, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_KDPC" + } + } }, - "_KTIMER": { - "fields": { - "Dpc": { - "offset": 32, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_KDPC"}, - }, - }, - "DueTime": { - "offset": 16, - "type": {"kind": "union", "name": "_ULARGE_INTEGER"}, - }, - "Header": { - "offset": 0, - "type": {"kind": "struct", "name": "_DISPATCHER_HEADER"}, - }, - "Period": { - "offset": 36, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "TimerListEntry": { - "offset": 24, - "type": {"kind": "struct", "name": "_LIST_ENTRY"}, - }, - }, - "kind": "struct", - "size": 40, - }, - "_ERESOURCE": { - "fields": { - "ActiveCount": { - "offset": 12, - "type": {"kind": "base", "name": "short"}, - }, - "ActiveEntries": { - "offset": 32, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "Address": { - "offset": 48, - "type": { - "kind": "pointer", - "subtype": {"kind": "base", "name": "void"}, - }, - }, - "ContentionCount": { - "offset": 36, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "CreatorBackTraceIndex": { - "offset": 48, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ExclusiveWaiters": { - "offset": 20, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_KEVENT"}, - }, - }, - "Flag": { - "offset": 14, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "NumberOfExclusiveWaiters": { - "offset": 44, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "NumberOfSharedWaiters": { - "offset": 40, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "OwnerEntry": { - "offset": 24, - "type": {"kind": "struct", "name": "_OWNER_ENTRY"}, - }, - "OwnerTable": { - "offset": 8, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_OWNER_ENTRY"}, - }, - }, - "ReservedLowFlags": { - "offset": 14, - "type": {"kind": "base", "name": "unsigned char"}, - }, - "SharedWaiters": { - "offset": 16, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_KSEMAPHORE"}, - }, - }, - "SpinLock": { - "offset": 52, - "type": {"kind": "base", "name": "unsigned long long"}, - }, - "SystemResourcesList": { - "offset": 0, - "type": {"kind": "struct", "name": "_LIST_ENTRY"}, - }, - "WaiterPriority": { - "offset": 15, - "type": {"kind": "base", "name": "unsigned char"}, - }, - }, - "kind": "struct", - "size": 56, - }, - "_LARGE_INTEGER": { - "fields": { - "HighPart": {"offset": 4, "type": {"kind": "base", "name": "long"}}, - "LowPart": { - "offset": 0, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "QuadPart": { - "offset": 0, - "type": {"kind": "base", "name": "long long"}, - }, - "u": { - "offset": 0, - "type": {"kind": "struct", "name": "__unnamed_1083"}, - }, - }, + "DueTime": { + "offset": 16, + "type": { "kind": "union", - "size": 8, + "name": "_ULARGE_INTEGER" + } }, - "_ETHREAD": { - "fields": { - "Cid": { - "offset": 868, - "type": {"kind": "struct", "name": "_CLIENT_ID"}, - }, - "CreateTime": { - "offset": 824, - "type": {"kind": "union", "name": "_LARGE_INTEGER"}, - }, - "CrossThreadFlags": { - "offset": 952, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ExitTime": { - "offset": 832, - "type": {"kind": "union", "name": "_LARGE_INTEGER"}, - }, - "Tcb": {"offset": 0, "type": {"kind": "struct", "name": "_KTHREAD"}}, - }, + "Header": { + "offset": 0, + "type": { "kind": "struct", - "size": 1048, + "name": "_DISPATCHER_HEADER" + } }, - "_KTHREAD": { - "fields": { - "State": { - "offset": 144, - "type": {"kind": "base", "name": "unsigned char"}, - }, - "WaitReason": { - "offset": 395, - "type": {"kind": "base", "name": "unsigned char"}, - }, - }, + "Period": { + "offset": 36, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "TimerListEntry": { + "offset": 24, + "type": { "kind": "struct", - "size": 824, - }, - "_EPROCESS": { - "fields": { - "CreateTime": { - "offset": 168, - "type": {"kind": "union", "name": "_LARGE_INTEGER"}, - }, - "ExitTime": { - "offset": 688, - "type": {"kind": "union", "name": "_LARGE_INTEGER"}, - }, - "ImageFileName": { - "offset": 1080, - "type": { - "count": 368, - "kind": "array", - "subtype": {"kind": "base", "name": "unsigned char"}, - }, - }, - "ObjectTable": { - "offset": 336, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_HANDLE_TABLE"}, - }, - }, - "Pcb": {"offset": 0, "type": {"kind": "struct", "name": "_KPROCESS"}}, - "Peb": { - "offset": 320, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_PEB"}, - }, - }, - "Session": { - "offset": 324, - "type": { - "kind": "pointer", - "subtype": {"kind": "base", "name": "void"}, - }, - }, - "ThreadListHead": { - "offset": 404, - "type": {"kind": "struct", "name": "_LIST_ENTRY"}, - }, - "UniqueProcessId": { - "offset": 180, - "type": { - "kind": "pointer", - "subtype": {"kind": "base", "name": "void"}, - }, - }, - "VadRoot": { - "offset": 628, - "type": {"kind": "struct", "name": "_RTL_AVL_TREE"}, - }, - }, - "kind": "struct", - "size": 760, - }, - "_EX_FAST_REF": { - "fields": { - "Object": { - "offset": 0, - "type": { - "kind": "pointer", - "subtype": {"kind": "base", "name": "void"}, - }, - }, - "RefCnt": { - "offset": 0, - "type": { - "bit_length": 4, - "bit_position": 0, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "Value": { - "offset": 0, - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "kind": "struct", - "size": 4, - }, - "_TOKEN": { - "fields": { - "Privileges": { - "offset": 64, - "type": {"kind": "struct", "name": "_SEP_TOKEN_PRIVILEGES"}, - }, - "UserAndGroupCount": { - "offset": 124, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "UserAndGroups": { - "offset": 148, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_SID_AND_ATTRIBUTES"}, - }, - }, - }, - "kind": "struct", - "size": 656, - }, - "_OBJECT_HEADER": { - "fields": { - "Body": {"offset": 24, "type": {"kind": "struct", "name": "_QUAD"}}, - "InfoMask": { - "offset": 14, - "type": {"kind": "base", "name": "unsigned char"}, - }, - "PointerCount": {"offset": 0, "type": {"kind": "base", "name": "long"}}, - "TypeIndex": { - "offset": 12, - "type": {"kind": "base", "name": "unsigned char"}, - }, - }, - "kind": "struct", - "size": 32, - }, - "_FILE_OBJECT": { - "fields": { - "DeleteAccess": { - "offset": 40, - "type": {"kind": "base", "name": "unsigned char"}, - }, - "DeviceObject": { - "offset": 4, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_DEVICE_OBJECT"}, - }, - }, - "FileName": { - "offset": 48, - "type": {"kind": "struct", "name": "_UNICODE_STRING"}, - }, - "ReadAccess": { - "offset": 38, - "type": {"kind": "base", "name": "unsigned char"}, - }, - "SharedDelete": { - "offset": 43, - "type": {"kind": "base", "name": "unsigned char"}, - }, - "SharedRead": { - "offset": 41, - "type": {"kind": "base", "name": "unsigned char"}, - }, - "SharedWrite": { - "offset": 42, - "type": {"kind": "base", "name": "unsigned char"}, - }, - "WriteAccess": { - "offset": 39, - "type": {"kind": "base", "name": "unsigned char"}, - }, - }, - "kind": "struct", - "size": 128, - }, - "_DEVICE_OBJECT": { - "fields": { - "AttachedDevice": { - "offset": 16, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_DEVICE_OBJECT"}, - }, - }, - "Flags": { - "offset": 48, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "NextDevice": { - "offset": 12, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_DEVICE_OBJECT"}, - }, - }, - }, - "kind": "struct", - "size": 184, - }, - "_CM_KEY_BODY": { - "fields": { - "KeyControlBlock": { - "offset": 4, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_CM_KEY_CONTROL_BLOCK"}, - }, - }, - "Type": { - "offset": 0, - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "kind": "struct", - "size": 44, - }, - "_CMHIVE": { - "fields": { - "FileFullPath": { - "offset": 1136, - "type": {"kind": "struct", "name": "_UNICODE_STRING"}, - }, - "FileUserName": { - "offset": 1144, - "type": {"kind": "struct", "name": "_UNICODE_STRING"}, - }, - "Hive": {"offset": 0, "type": {"kind": "struct", "name": "_HHIVE"}}, - "HiveRootPath": { - "offset": 1160, - "type": {"kind": "struct", "name": "_UNICODE_STRING"}, - }, - }, - "kind": "struct", - "size": 3104, - }, - "_CM_KEY_NODE": { - "fields": { - "Name": { - "offset": 76, - "type": { - "count": 1, - "kind": "array", - "subtype": {"kind": "base", "name": "wchar"}, - }, - }, - "NameLength": { - "offset": 72, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "Parent": { - "offset": 16, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "SubKeyLists": { - "offset": 28, - "type": { - "count": 2, - "kind": "array", - "subtype": {"kind": "base", "name": "unsigned long"}, - }, - }, - "ValueList": { - "offset": 36, - "type": {"kind": "struct", "name": "_CHILD_LIST"}, - }, - }, - "kind": "struct", - "size": 80, - }, - "_CM_KEY_VALUE": { - "fields": { - "Data": { - "offset": 8, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "DataLength": { - "offset": 4, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "Flags": { - "offset": 16, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "Name": { - "offset": 20, - "type": { - "count": 1, - "kind": "array", - "subtype": {"kind": "base", "name": "wchar"}, - }, - }, - "NameLength": { - "offset": 2, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "Signature": { - "offset": 0, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "Spare": { - "offset": 18, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "Type": { - "offset": 12, - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "kind": "struct", - "size": 24, - }, - "_HMAP_ENTRY": { - "fields": { - "BinAddress": { - "offset": 4, - "type": {"kind": "base", "name": "unsigned long long"}, - }, - "BlockAddress": { - "offset": 0, - "type": {"kind": "base", "name": "unsigned long long"}, - }, - "MemAlloc": { - "offset": 8, - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "kind": "struct", - "size": 12, - }, - "_MMVAD_SHORT": { - "fields": { - "EndingVpn": { - "offset": 16, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "NextVad": { - "offset": 0, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_MMVAD_SHORT"}, - }, - }, - "StartingVpn": { - "offset": 12, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "VadNode": { - "offset": 0, - "type": {"kind": "struct", "name": "_RTL_BALANCED_NODE"}, - }, - }, - "kind": "struct", - "size": 40, - }, - "_MMVAD": { - "fields": { - "Core": { - "offset": 0, - "type": {"kind": "struct", "name": "_MMVAD_SHORT"}, - }, - "Subsection": { - "offset": 44, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_SUBSECTION"}, - }, - }, - }, - "kind": "struct", - "size": 72, - }, - "_KSYSTEM_TIME": { - "fields": { - "High1Time": {"offset": 4, "type": {"kind": "base", "name": "long"}}, - "High2Time": {"offset": 8, "type": {"kind": "base", "name": "long"}}, - "LowPart": { - "offset": 0, - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "kind": "struct", - "size": 12, - }, - "_KMUTANT": { - "fields": { - "Header": { - "offset": 0, - "type": {"kind": "struct", "name": "_DISPATCHER_HEADER"}, - } - }, - "kind": "struct", - "size": 32, - }, - "_DRIVER_OBJECT": { - "fields": { - "DeviceObject": { - "offset": 4, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_DEVICE_OBJECT"}, - }, - } - }, - "kind": "struct", - "size": 168, - }, - "_OBJECT_SYMBOLIC_LINK": { - "fields": { - "CreationTime": { - "offset": 0, - "type": {"kind": "union", "name": "_LARGE_INTEGER"}, - } - }, - "kind": "struct", - "size": 24, - }, - "_CONTROL_AREA": { - "fields": { - "FilePointer": { - "offset": 32, - "type": {"kind": "struct", "name": "_EX_FAST_REF"}, - } - }, - "kind": "struct", - "size": 80, - }, - "_SHARED_CACHE_MAP": { - "fields": { - "FileSize": { - "offset": 8, - "type": {"kind": "union", "name": "_LARGE_INTEGER"}, - }, - "InitialVacbs": { - "offset": 48, - "type": { - "count": 4, - "kind": "array", - "subtype": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_VACB"}, - }, - }, - }, - "Section": { - "offset": 108, - "type": { - "kind": "pointer", - "subtype": {"kind": "base", "name": "void"}, - }, - }, - "SectionSize": { - "offset": 24, - "type": {"kind": "union", "name": "_LARGE_INTEGER"}, - }, - "Vacbs": { - "offset": 64, - "type": { - "kind": "pointer", - "subtype": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_VACB"}, - }, - }, - }, - "ValidDataLength": { - "offset": 32, - "type": {"kind": "union", "name": "_LARGE_INTEGER"}, - }, - }, - "kind": "struct", - "size": 368, - }, - "_VACB": { - "fields": { - "ArrayHead": { - "offset": 16, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_VACB_ARRAY_HEADER"}, - }, - }, - "BaseAddress": { - "offset": 0, - "type": { - "kind": "pointer", - "subtype": {"kind": "base", "name": "void"}, - }, - }, - "Overlay": { - "offset": 8, - "type": {"kind": "union", "name": "__unnamed_1971"}, - }, - "SharedCacheMap": { - "offset": 4, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_SHARED_CACHE_MAP"}, - }, - }, - }, - "kind": "struct", - "size": 24, - }, - "_POOL_TRACKER_BIG_PAGES": { - "fields": { - "Key": {"offset": 4, "type": {"kind": "base", "name": "unsigned long"}}, - "NumberOfBytes": { - "offset": 12, - "type": {"kind": "base", "name": "unsigned long long"}, - }, - "PoolType": { - "offset": 8, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "Va": {"offset": 0, "type": {"kind": "base", "name": "unsigned long"}}, - }, - "kind": "struct", - "size": 16, - }, - "_IMAGE_DOS_HEADER": { - "fields": { - "e_cblp": { - "offset": 2, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "e_cp": { - "offset": 4, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "e_cparhdr": { - "offset": 8, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "e_crlc": { - "offset": 6, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "e_cs": { - "offset": 22, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "e_csum": { - "offset": 18, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "e_ip": { - "offset": 20, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "e_lfanew": {"offset": 60, "type": {"kind": "base", "name": "long"}}, - "e_lfarlc": { - "offset": 24, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "e_magic": { - "offset": 0, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "e_maxalloc": { - "offset": 12, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "e_minalloc": { - "offset": 10, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "e_oemid": { - "offset": 36, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "e_oeminfo": { - "offset": 38, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "e_ovno": { - "offset": 26, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "e_res": { - "offset": 28, - "type": { - "count": 4, - "kind": "array", - "subtype": {"kind": "base", "name": "unsigned short"}, - }, - }, - "e_res2": { - "offset": 40, - "type": { - "count": 10, - "kind": "array", - "subtype": {"kind": "base", "name": "unsigned short"}, - }, - }, - "e_sp": { - "offset": 16, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "e_ss": { - "offset": 14, - "type": {"kind": "base", "name": "unsigned short"}, - }, - }, - "kind": "struct", - "size": 64, - }, - "_SINGLE_LIST_ENTRY": { - "fields": { - "Next": { - "offset": 0, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_SINGLE_LIST_ENTRY"}, - }, - } - }, - "kind": "struct", - "size": 4, - }, - "_LDRP_CSLIST": { - "fields": { - "Tail": { - "offset": 0, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_SINGLE_LIST_ENTRY"}, - }, - } - }, - "kind": "struct", - "size": 4, - }, - "_RTL_BALANCED_NODE": { - "fields": { - "Balance": { - "offset": 8, - "type": { - "bit_length": 2, - "bit_position": 0, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned char"}, - }, - }, - "Children": { - "offset": 0, - "type": { - "count": 2, - "kind": "array", - "subtype": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_RTL_BALANCED_NODE"}, - }, - }, - }, - "Left": { - "offset": 0, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_RTL_BALANCED_NODE"}, - }, - }, - "ParentValue": { - "offset": 8, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "Red": { - "offset": 8, - "type": { - "bit_length": 1, - "bit_position": 0, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned char"}, - }, - }, - "Right": { - "offset": 4, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_RTL_BALANCED_NODE"}, - }, - }, - }, - "kind": "struct", - "size": 12, - }, - "_LIST_ENTRY": { - "fields": { - "Blink": { - "offset": 4, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_LIST_ENTRY"}, - }, - }, - "Flink": { - "offset": 0, - "type": { - "kind": "pointer", - "subtype": {"kind": "struct", "name": "_LIST_ENTRY"}, - }, - }, - }, - "kind": "struct", - "size": 8, - }, - "LIST_ENTRY32": { - "fields": { - "Blink": { - "offset": 4, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "Flink": { - "offset": 0, - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "kind": "struct", - "size": 8, - }, - "_PEB_LDR_DATA": { - "fields": { - "EntryInProgress": { - "offset": 36, - "type": { - "kind": "pointer", - "subtype": {"kind": "base", "name": "void"}, - }, - }, - "InInitializationOrderModuleList": { - "offset": 28, - "type": {"kind": "struct", "name": "_LIST_ENTRY"}, - }, - "InLoadOrderModuleList": { - "offset": 12, - "type": {"kind": "struct", "name": "_LIST_ENTRY"}, - }, - "InMemoryOrderModuleList": { - "offset": 20, - "type": {"kind": "struct", "name": "_LIST_ENTRY"}, - }, - "Initialized": { - "offset": 4, - "type": {"kind": "base", "name": "unsigned char"}, - }, - "Length": { - "offset": 0, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ShutdownInProgress": { - "offset": 40, - "type": {"kind": "base", "name": "unsigned char"}, - }, - "ShutdownThreadId": { - "offset": 44, - "type": { - "kind": "pointer", - "subtype": {"kind": "base", "name": "void"}, - }, - }, - "SsHandle": { - "offset": 8, - "type": { - "kind": "pointer", - "subtype": {"kind": "base", "name": "void"}, - }, - }, - }, - "kind": "struct", - "size": 48, - }, - "_LDR_DATA_TABLE_ENTRY": { - "fields": { - "BaseDllName": { - "offset": 44, - "type": {"kind": "struct", "name": "_UNICODE_STRING"}, - }, - "FullDllName": { - "offset": 36, - "type": {"kind": "struct", "name": "_UNICODE_STRING"}, - }, - "LoadTime": { - "offset": 256, - "type": {"kind": "union", "name": "_LARGE_INTEGER"}, - }, - "DllBase": { - "offset": 24, - "type": { - "kind": "pointer", - "subtype": {"kind": "base", "name": "void"}, - }, - }, - "SizeOfImage": { - "offset": 32, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "InInitializationOrderLinks": { - "offset": 16, - "type": {"kind": "struct", "name": "_LIST_ENTRY"}, - }, - "InLoadOrderLinks": { - "offset": 0, - "type": {"kind": "struct", "name": "_LIST_ENTRY"}, - }, - "InMemoryOrderLinks": { - "offset": 8, - "type": {"kind": "struct", "name": "_LIST_ENTRY"}, - }, - }, - "kind": "struct", - "size": 160, - }, - "_PEB32": { - "fields": { - "ActivationContextData": { - "offset": 504, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ActiveProcessAffinityMask": { - "offset": 192, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "AnsiCodePageData": { - "offset": 88, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ApiSetMap": { - "offset": 56, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "AppCompatFlags": { - "offset": 472, - "type": {"kind": "union", "name": "_ULARGE_INTEGER"}, - }, - "AppCompatFlagsUser": { - "offset": 480, - "type": {"kind": "union", "name": "_ULARGE_INTEGER"}, - }, - "AppCompatInfo": { - "offset": 492, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "AtlThunkSListPtr": { - "offset": 32, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "AtlThunkSListPtr32": { - "offset": 52, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "BeingDebugged": { - "offset": 2, - "type": {"kind": "base", "name": "unsigned char"}, - }, - "BitField": { - "offset": 3, - "type": {"kind": "base", "name": "unsigned char"}, - }, - "CSDVersion": { - "offset": 496, - "type": {"kind": "struct", "name": "_STRING32"}, - }, - "CritSecTracingEnabled": { - "offset": 576, - "type": { - "bit_length": 1, - "bit_position": 1, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "CriticalSectionTimeout": { - "offset": 112, - "type": {"kind": "union", "name": "_LARGE_INTEGER"}, - }, - "CrossProcessFlags": { - "offset": 40, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "CsrServerReadOnlySharedMemoryBase": { - "offset": 584, - "type": {"kind": "base", "name": "unsigned long long"}, - }, - "FastPebLock": { - "offset": 28, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "FlsBitmap": { - "offset": 536, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "FlsBitmapBits": { - "offset": 540, - "type": { - "count": 4, - "kind": "array", - "subtype": {"kind": "base", "name": "unsigned long"}, - }, - }, - "FlsCallback": { - "offset": 524, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "FlsHighIndex": { - "offset": 556, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "FlsListHead": { - "offset": 528, - "type": {"kind": "struct", "name": "LIST_ENTRY32"}, - }, - "GdiDCAttributeList": { - "offset": 156, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "GdiHandleBuffer": { - "offset": 196, - "type": { - "count": 34, - "kind": "array", - "subtype": {"kind": "base", "name": "unsigned long"}, - }, - }, - "GdiSharedHandleTable": { - "offset": 148, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "HeapDeCommitFreeBlockThreshold": { - "offset": 132, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "HeapDeCommitTotalFreeThreshold": { - "offset": 128, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "HeapSegmentCommit": { - "offset": 124, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "HeapSegmentReserve": { - "offset": 120, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "HeapTracingEnabled": { - "offset": 576, - "type": { - "bit_length": 1, - "bit_position": 0, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "IFEOKey": { - "offset": 36, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ImageBaseAddress": { - "offset": 8, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ImageSubsystem": { - "offset": 180, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ImageSubsystemMajorVersion": { - "offset": 184, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ImageSubsystemMinorVersion": { - "offset": 188, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ImageUsesLargePages": { - "offset": 3, - "type": { - "bit_length": 1, - "bit_position": 0, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned char"}, - }, - }, - "InheritedAddressSpace": { - "offset": 0, - "type": {"kind": "base", "name": "unsigned char"}, - }, - "IsAppContainer": { - "offset": 3, - "type": { - "bit_length": 1, - "bit_position": 5, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned char"}, - }, - }, - "IsImageDynamicallyRelocated": { - "offset": 3, - "type": { - "bit_length": 1, - "bit_position": 2, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned char"}, - }, - }, - "IsPackagedProcess": { - "offset": 3, - "type": { - "bit_length": 1, - "bit_position": 4, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned char"}, - }, - }, - "IsProtectedProcess": { - "offset": 3, - "type": { - "bit_length": 1, - "bit_position": 1, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned char"}, - }, - }, - "IsProtectedProcessLight": { - "offset": 3, - "type": { - "bit_length": 1, - "bit_position": 6, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned char"}, - }, - }, - "KernelCallbackTable": { - "offset": 44, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "Ldr": { - "offset": 12, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "LibLoaderTracingEnabled": { - "offset": 576, - "type": { - "bit_length": 1, - "bit_position": 2, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "LoaderLock": { - "offset": 160, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "MaximumNumberOfHeaps": { - "offset": 140, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "MinimumStackCommit": { - "offset": 520, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "Mutant": { - "offset": 4, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "NtGlobalFlag": { - "offset": 104, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "NumberOfHeaps": { - "offset": 136, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "NumberOfProcessors": { - "offset": 100, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "OSBuildNumber": { - "offset": 172, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "OSCSDVersion": { - "offset": 174, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "OSMajorVersion": { - "offset": 164, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "OSMinorVersion": { - "offset": 168, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "OSPlatformId": { - "offset": 176, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "OemCodePageData": { - "offset": 92, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "PostProcessInitRoutine": { - "offset": 332, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ProcessAssemblyStorageMap": { - "offset": 508, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ProcessHeap": { - "offset": 24, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ProcessHeaps": { - "offset": 144, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ProcessInJob": { - "offset": 40, - "type": { - "bit_length": 1, - "bit_position": 0, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "ProcessInitializing": { - "offset": 40, - "type": { - "bit_length": 1, - "bit_position": 1, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "ProcessParameters": { - "offset": 16, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ProcessStarterHelper": { - "offset": 152, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ProcessUsingFTH": { - "offset": 40, - "type": { - "bit_length": 1, - "bit_position": 4, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "ProcessUsingVCH": { - "offset": 40, - "type": { - "bit_length": 1, - "bit_position": 3, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "ProcessUsingVEH": { - "offset": 40, - "type": { - "bit_length": 1, - "bit_position": 2, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "ReadImageFileExecOptions": { - "offset": 1, - "type": {"kind": "base", "name": "unsigned char"}, - }, - "ReadOnlySharedMemoryBase": { - "offset": 76, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ReadOnlyStaticServerData": { - "offset": 84, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "ReservedBits0": { - "offset": 40, - "type": { - "bit_length": 27, - "bit_position": 5, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "SessionId": { - "offset": 468, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "SkipPatchingUser32Forwarders": { - "offset": 3, - "type": { - "bit_length": 1, - "bit_position": 3, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned char"}, - }, - }, - "SpareBits": { - "offset": 3, - "type": { - "bit_length": 1, - "bit_position": 7, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned char"}, - }, - }, - "SparePvoid0": { - "offset": 80, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "SpareTracingBits": { - "offset": 576, - "type": { - "bit_length": 29, - "bit_position": 3, - "kind": "bitfield", - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "SubSystemData": { - "offset": 20, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "SystemAssemblyStorageMap": { - "offset": 516, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "SystemDefaultActivationContextData": { - "offset": 512, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "SystemReserved": { - "offset": 48, - "type": { - "count": 1, - "kind": "array", - "subtype": {"kind": "base", "name": "unsigned long"}, - }, - }, - "TlsBitmap": { - "offset": 64, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "TlsBitmapBits": { - "offset": 68, - "type": { - "count": 2, - "kind": "array", - "subtype": {"kind": "base", "name": "unsigned long"}, - }, - }, - "TlsExpansionBitmap": { - "offset": 336, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "TlsExpansionBitmapBits": { - "offset": 340, - "type": { - "count": 32, - "kind": "array", - "subtype": {"kind": "base", "name": "unsigned long"}, - }, - }, - "TlsExpansionCounter": { - "offset": 60, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "TracingFlags": { - "offset": 576, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "UnicodeCaseTableData": { - "offset": 96, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "UserSharedInfoPtr": { - "offset": 44, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "WerRegistrationData": { - "offset": 560, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "WerShipAssertPtr": { - "offset": 564, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "pImageHeaderHash": { - "offset": 572, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "pShimData": { - "offset": 488, - "type": {"kind": "base", "name": "unsigned long"}, - }, - "pUnused": { - "offset": 568, - "type": {"kind": "base", "name": "unsigned long"}, - }, - }, - "kind": "struct", - "size": 592, - }, - "_UNICODE_STRING": { - "fields": { - "Buffer": { - "offset": 4, - "type": { - "kind": "pointer", - "subtype": {"kind": "base", "name": "unsigned short"}, - }, - }, - "Length": { - "offset": 0, - "type": {"kind": "base", "name": "unsigned short"}, - }, - "MaximumLength": { - "offset": 2, - "type": {"kind": "base", "name": "unsigned short"}, - }, - }, - "kind": "struct", - "size": 8, - }, + "name": "_LIST_ENTRY" + } + } + }, + "kind": "struct", + "size": 40 }, + "_ERESOURCE": { + "fields": { + "ActiveCount": { + "offset": 12, + "type": { + "kind": "base", + "name": "short" + } + }, + "ActiveEntries": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Address": { + "offset": 48, + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + } + }, + "ContentionCount": { + "offset": 36, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "CreatorBackTraceIndex": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ExclusiveWaiters": { + "offset": 20, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_KEVENT" + } + } + }, + "Flag": { + "offset": 14, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "NumberOfExclusiveWaiters": { + "offset": 44, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NumberOfSharedWaiters": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "OwnerEntry": { + "offset": 24, + "type": { + "kind": "struct", + "name": "_OWNER_ENTRY" + } + }, + "OwnerTable": { + "offset": 8, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_OWNER_ENTRY" + } + } + }, + "ReservedLowFlags": { + "offset": 14, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "SharedWaiters": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_KSEMAPHORE" + } + } + }, + "SpinLock": { + "offset": 52, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "SystemResourcesList": { + "offset": 0, + "type": { + "kind": "struct", + "name": "_LIST_ENTRY" + } + }, + "WaiterPriority": { + "offset": 15, + "type": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "kind": "struct", + "size": 56 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_1083" + } + } + }, + "kind": "union", + "size": 8 + }, + "_ETHREAD": { + "fields": { + "Cid": { + "offset": 868, + "type": { + "kind": "struct", + "name": "_CLIENT_ID" + } + }, + "CreateTime": { + "offset": 824, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "CrossThreadFlags": { + "offset": 952, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ExitTime": { + "offset": 832, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "Tcb": { + "offset": 0, + "type": { + "kind": "struct", + "name": "_KTHREAD" + } + } + }, + "kind": "struct", + "size": 1048 + }, + "_KTHREAD": { + "fields": { + "State": { + "offset": 144, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "WaitReason": { + "offset": 395, + "type": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "kind": "struct", + "size": 824 + }, + "_EPROCESS": { + "fields": { + "CreateTime": { + "offset": 168, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ExitTime": { + "offset": 688, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ImageFileName": { + "offset": 1080, + "type": { + "count": 368, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "ObjectTable": { + "offset": 336, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_HANDLE_TABLE" + } + } + }, + "Pcb": { + "offset": 0, + "type": { + "kind": "struct", + "name": "_KPROCESS" + } + }, + "Peb": { + "offset": 320, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_PEB" + } + } + }, + "Session": { + "offset": 324, + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + } + }, + "ThreadListHead": { + "offset": 404, + "type": { + "kind": "struct", + "name": "_LIST_ENTRY" + } + }, + "UniqueProcessId": { + "offset": 180, + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + } + }, + "VadRoot": { + "offset": 628, + "type": { + "kind": "struct", + "name": "_RTL_AVL_TREE" + } + } + }, + "kind": "struct", + "size": 760 + }, + "_EX_FAST_REF": { + "fields": { + "Object": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + } + }, + "RefCnt": { + "offset": 0, + "type": { + "bit_length": 4, + "bit_position": 0, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "Value": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 4 + }, + "_TOKEN": { + "fields": { + "Privileges": { + "offset": 64, + "type": { + "kind": "struct", + "name": "_SEP_TOKEN_PRIVILEGES" + } + }, + "UserAndGroupCount": { + "offset": 124, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "UserAndGroups": { + "offset": 148, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SID_AND_ATTRIBUTES" + } + } + } + }, + "kind": "struct", + "size": 656 + }, + "_OBJECT_HEADER": { + "fields": { + "Body": { + "offset": 24, + "type": { + "kind": "struct", + "name": "_QUAD" + } + }, + "InfoMask": { + "offset": 14, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "PointerCount": { + "offset": 0, + "type": { + "kind": "base", + "name": "long" + } + }, + "TypeIndex": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "kind": "struct", + "size": 32 + }, + "_FILE_OBJECT": { + "fields": { + "DeleteAccess": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "DeviceObject": { + "offset": 4, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_DEVICE_OBJECT" + } + } + }, + "FileName": { + "offset": 48, + "type": { + "kind": "struct", + "name": "_UNICODE_STRING" + } + }, + "ReadAccess": { + "offset": 38, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "SharedDelete": { + "offset": 43, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "SharedRead": { + "offset": 41, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "SharedWrite": { + "offset": 42, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "WriteAccess": { + "offset": 39, + "type": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "kind": "struct", + "size": 128 + }, + "_DEVICE_OBJECT": { + "fields": { + "AttachedDevice": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_DEVICE_OBJECT" + } + } + }, + "Flags": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NextDevice": { + "offset": 12, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_DEVICE_OBJECT" + } + } + } + }, + "kind": "struct", + "size": 184 + }, + "_CM_KEY_BODY": { + "fields": { + "KeyControlBlock": { + "offset": 4, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CM_KEY_CONTROL_BLOCK" + } + } + }, + "Type": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 44 + }, + "_CMHIVE": { + "fields": { + "FileFullPath": { + "offset": 1136, + "type": { + "kind": "struct", + "name": "_UNICODE_STRING" + } + }, + "FileUserName": { + "offset": 1144, + "type": { + "kind": "struct", + "name": "_UNICODE_STRING" + } + }, + "Hive": { + "offset": 0, + "type": { + "kind": "struct", + "name": "_HHIVE" + } + }, + "HiveRootPath": { + "offset": 1160, + "type": { + "kind": "struct", + "name": "_UNICODE_STRING" + } + } + }, + "kind": "struct", + "size": 3104 + }, + "_CM_KEY_NODE": { + "fields": { + "Name": { + "offset": 76, + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "base", + "name": "wchar" + } + } + }, + "NameLength": { + "offset": 72, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "Parent": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SubKeyLists": { + "offset": 28, + "type": { + "count": 2, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "ValueList": { + "offset": 36, + "type": { + "kind": "struct", + "name": "_CHILD_LIST" + } + } + }, + "kind": "struct", + "size": 80 + }, + "_CM_KEY_VALUE": { + "fields": { + "Data": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "DataLength": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Flags": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "Name": { + "offset": 20, + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "base", + "name": "wchar" + } + } + }, + "NameLength": { + "offset": 2, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "Signature": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "Spare": { + "offset": 18, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "Type": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 24 + }, + "_HMAP_ENTRY": { + "fields": { + "BinAddress": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "BlockAddress": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "MemAlloc": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 12 + }, + "_MMVAD_SHORT": { + "fields": { + "EndingVpn": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NextVad": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_MMVAD_SHORT" + } + } + }, + "StartingVpn": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "VadNode": { + "offset": 0, + "type": { + "kind": "struct", + "name": "_RTL_BALANCED_NODE" + } + } + }, + "kind": "struct", + "size": 40 + }, + "_MMVAD": { + "fields": { + "Core": { + "offset": 0, + "type": { + "kind": "struct", + "name": "_MMVAD_SHORT" + } + }, + "Subsection": { + "offset": 44, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SUBSECTION" + } + } + } + }, + "kind": "struct", + "size": 72 + }, + "_KSYSTEM_TIME": { + "fields": { + "High1Time": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "High2Time": { + "offset": 8, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 12 + }, + "_KMUTANT": { + "fields": { + "Header": { + "offset": 0, + "type": { + "kind": "struct", + "name": "_DISPATCHER_HEADER" + } + } + }, + "kind": "struct", + "size": 32 + }, + "_DRIVER_OBJECT": { + "fields": { + "DeviceObject": { + "offset": 4, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_DEVICE_OBJECT" + } + } + } + }, + "kind": "struct", + "size": 168 + }, + "_OBJECT_SYMBOLIC_LINK": { + "fields": { + "CreationTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + } + }, + "kind": "struct", + "size": 24 + }, + "_CONTROL_AREA": { + "fields": { + "FilePointer": { + "offset": 32, + "type": { + "kind": "struct", + "name": "_EX_FAST_REF" + } + } + }, + "kind": "struct", + "size": 80 + }, + "_SHARED_CACHE_MAP": { + "fields": { + "FileSize": { + "offset": 8, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "InitialVacbs": { + "offset": 48, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_VACB" + } + } + } + }, + "Section": { + "offset": 108, + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + } + }, + "SectionSize": { + "offset": 24, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "Vacbs": { + "offset": 64, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_VACB" + } + } + } + }, + "ValidDataLength": { + "offset": 32, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + } + }, + "kind": "struct", + "size": 368 + }, + "_VACB": { + "fields": { + "ArrayHead": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_VACB_ARRAY_HEADER" + } + } + }, + "BaseAddress": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + } + }, + "Overlay": { + "offset": 8, + "type": { + "kind": "union", + "name": "__unnamed_1971" + } + }, + "SharedCacheMap": { + "offset": 4, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SHARED_CACHE_MAP" + } + } + } + }, + "kind": "struct", + "size": 24 + }, + "_POOL_TRACKER_BIG_PAGES": { + "fields": { + "Key": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NumberOfBytes": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "PoolType": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Va": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 16 + }, + "_IMAGE_DOS_HEADER": { + "fields": { + "e_cblp": { + "offset": 2, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_cp": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_cparhdr": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_crlc": { + "offset": 6, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_cs": { + "offset": 22, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_csum": { + "offset": 18, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_ip": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_lfanew": { + "offset": 60, + "type": { + "kind": "base", + "name": "long" + } + }, + "e_lfarlc": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_magic": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_maxalloc": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_minalloc": { + "offset": 10, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_oemid": { + "offset": 36, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_oeminfo": { + "offset": 38, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_ovno": { + "offset": 26, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_res": { + "offset": 28, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "e_res2": { + "offset": 40, + "type": { + "count": 10, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "e_sp": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_ss": { + "offset": 14, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 64 + }, + "_SINGLE_LIST_ENTRY": { + "fields": { + "Next": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SINGLE_LIST_ENTRY" + } + } + } + }, + "kind": "struct", + "size": 4 + }, + "_LDRP_CSLIST": { + "fields": { + "Tail": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SINGLE_LIST_ENTRY" + } + } + } + }, + "kind": "struct", + "size": 4 + }, + "_RTL_BALANCED_NODE": { + "fields": { + "Balance": { + "offset": 8, + "type": { + "bit_length": 2, + "bit_position": 0, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "Children": { + "offset": 0, + "type": { + "count": 2, + "kind": "array", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_NODE" + } + } + } + }, + "Left": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_NODE" + } + } + }, + "ParentValue": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Red": { + "offset": 8, + "type": { + "bit_length": 1, + "bit_position": 0, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "Right": { + "offset": 4, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_NODE" + } + } + } + }, + "kind": "struct", + "size": 12 + }, + "_LIST_ENTRY": { + "fields": { + "Blink": { + "offset": 4, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LIST_ENTRY" + } + } + }, + "Flink": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LIST_ENTRY" + } + } + } + }, + "kind": "struct", + "size": 8 + }, + "LIST_ENTRY32": { + "fields": { + "Blink": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Flink": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "_PEB_LDR_DATA": { + "fields": { + "EntryInProgress": { + "offset": 36, + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + } + }, + "InInitializationOrderModuleList": { + "offset": 28, + "type": { + "kind": "struct", + "name": "_LIST_ENTRY" + } + }, + "InLoadOrderModuleList": { + "offset": 12, + "type": { + "kind": "struct", + "name": "_LIST_ENTRY" + } + }, + "InMemoryOrderModuleList": { + "offset": 20, + "type": { + "kind": "struct", + "name": "_LIST_ENTRY" + } + }, + "Initialized": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "Length": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ShutdownInProgress": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "ShutdownThreadId": { + "offset": 44, + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + } + }, + "SsHandle": { + "offset": 8, + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + } + } + }, + "kind": "struct", + "size": 48 + }, + "_LDR_DATA_TABLE_ENTRY": { + "fields": { + "BaseDllName": { + "offset": 44, + "type": { + "kind": "struct", + "name": "_UNICODE_STRING" + } + }, + "FullDllName": { + "offset": 36, + "type": { + "kind": "struct", + "name": "_UNICODE_STRING" + } + }, + "LoadTime": { + "offset": 256, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "DllBase": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + } + }, + "SizeOfImage": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "InInitializationOrderLinks": { + "offset": 16, + "type": { + "kind": "struct", + "name": "_LIST_ENTRY" + } + }, + "InLoadOrderLinks": { + "offset": 0, + "type": { + "kind": "struct", + "name": "_LIST_ENTRY" + } + }, + "InMemoryOrderLinks": { + "offset": 8, + "type": { + "kind": "struct", + "name": "_LIST_ENTRY" + } + } + }, + "kind": "struct", + "size": 160 + }, + "_PEB32": { + "fields": { + "ActivationContextData": { + "offset": 504, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ActiveProcessAffinityMask": { + "offset": 192, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "AnsiCodePageData": { + "offset": 88, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ApiSetMap": { + "offset": 56, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "AppCompatFlags": { + "offset": 472, + "type": { + "kind": "union", + "name": "_ULARGE_INTEGER" + } + }, + "AppCompatFlagsUser": { + "offset": 480, + "type": { + "kind": "union", + "name": "_ULARGE_INTEGER" + } + }, + "AppCompatInfo": { + "offset": 492, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "AtlThunkSListPtr": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "AtlThunkSListPtr32": { + "offset": 52, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "BeingDebugged": { + "offset": 2, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "BitField": { + "offset": 3, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "CSDVersion": { + "offset": 496, + "type": { + "kind": "struct", + "name": "_STRING32" + } + }, + "CritSecTracingEnabled": { + "offset": 576, + "type": { + "bit_length": 1, + "bit_position": 1, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "CriticalSectionTimeout": { + "offset": 112, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "CrossProcessFlags": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "CsrServerReadOnlySharedMemoryBase": { + "offset": 584, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "FastPebLock": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "FlsBitmap": { + "offset": 536, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "FlsBitmapBits": { + "offset": 540, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "FlsCallback": { + "offset": 524, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "FlsHighIndex": { + "offset": 556, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "FlsListHead": { + "offset": 528, + "type": { + "kind": "struct", + "name": "LIST_ENTRY32" + } + }, + "GdiDCAttributeList": { + "offset": 156, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "GdiHandleBuffer": { + "offset": 196, + "type": { + "count": 34, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "GdiSharedHandleTable": { + "offset": 148, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "HeapDeCommitFreeBlockThreshold": { + "offset": 132, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "HeapDeCommitTotalFreeThreshold": { + "offset": 128, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "HeapSegmentCommit": { + "offset": 124, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "HeapSegmentReserve": { + "offset": 120, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "HeapTracingEnabled": { + "offset": 576, + "type": { + "bit_length": 1, + "bit_position": 0, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "IFEOKey": { + "offset": 36, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ImageBaseAddress": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ImageSubsystem": { + "offset": 180, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ImageSubsystemMajorVersion": { + "offset": 184, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ImageSubsystemMinorVersion": { + "offset": 188, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ImageUsesLargePages": { + "offset": 3, + "type": { + "bit_length": 1, + "bit_position": 0, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "InheritedAddressSpace": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "IsAppContainer": { + "offset": 3, + "type": { + "bit_length": 1, + "bit_position": 5, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "IsImageDynamicallyRelocated": { + "offset": 3, + "type": { + "bit_length": 1, + "bit_position": 2, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "IsPackagedProcess": { + "offset": 3, + "type": { + "bit_length": 1, + "bit_position": 4, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "IsProtectedProcess": { + "offset": 3, + "type": { + "bit_length": 1, + "bit_position": 1, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "IsProtectedProcessLight": { + "offset": 3, + "type": { + "bit_length": 1, + "bit_position": 6, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "KernelCallbackTable": { + "offset": 44, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Ldr": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "LibLoaderTracingEnabled": { + "offset": 576, + "type": { + "bit_length": 1, + "bit_position": 2, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "LoaderLock": { + "offset": 160, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "MaximumNumberOfHeaps": { + "offset": 140, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "MinimumStackCommit": { + "offset": 520, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Mutant": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NtGlobalFlag": { + "offset": 104, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NumberOfHeaps": { + "offset": 136, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NumberOfProcessors": { + "offset": 100, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "OSBuildNumber": { + "offset": 172, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "OSCSDVersion": { + "offset": 174, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "OSMajorVersion": { + "offset": 164, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "OSMinorVersion": { + "offset": 168, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "OSPlatformId": { + "offset": 176, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "OemCodePageData": { + "offset": 92, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "PostProcessInitRoutine": { + "offset": 332, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ProcessAssemblyStorageMap": { + "offset": 508, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ProcessHeap": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ProcessHeaps": { + "offset": 144, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ProcessInJob": { + "offset": 40, + "type": { + "bit_length": 1, + "bit_position": 0, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "ProcessInitializing": { + "offset": 40, + "type": { + "bit_length": 1, + "bit_position": 1, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "ProcessParameters": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ProcessStarterHelper": { + "offset": 152, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ProcessUsingFTH": { + "offset": 40, + "type": { + "bit_length": 1, + "bit_position": 4, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "ProcessUsingVCH": { + "offset": 40, + "type": { + "bit_length": 1, + "bit_position": 3, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "ProcessUsingVEH": { + "offset": 40, + "type": { + "bit_length": 1, + "bit_position": 2, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "ReadImageFileExecOptions": { + "offset": 1, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "ReadOnlySharedMemoryBase": { + "offset": 76, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ReadOnlyStaticServerData": { + "offset": 84, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ReservedBits0": { + "offset": 40, + "type": { + "bit_length": 27, + "bit_position": 5, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "SessionId": { + "offset": 468, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SkipPatchingUser32Forwarders": { + "offset": 3, + "type": { + "bit_length": 1, + "bit_position": 3, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "SpareBits": { + "offset": 3, + "type": { + "bit_length": 1, + "bit_position": 7, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "SparePvoid0": { + "offset": 80, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SpareTracingBits": { + "offset": 576, + "type": { + "bit_length": 29, + "bit_position": 3, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "SubSystemData": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SystemAssemblyStorageMap": { + "offset": 516, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SystemDefaultActivationContextData": { + "offset": 512, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SystemReserved": { + "offset": 48, + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "TlsBitmap": { + "offset": 64, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "TlsBitmapBits": { + "offset": 68, + "type": { + "count": 2, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "TlsExpansionBitmap": { + "offset": 336, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "TlsExpansionBitmapBits": { + "offset": 340, + "type": { + "count": 32, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "TlsExpansionCounter": { + "offset": 60, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "TracingFlags": { + "offset": 576, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "UnicodeCaseTableData": { + "offset": 96, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "UserSharedInfoPtr": { + "offset": 44, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "WerRegistrationData": { + "offset": 560, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "WerShipAssertPtr": { + "offset": 564, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "pImageHeaderHash": { + "offset": 572, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "pShimData": { + "offset": 488, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "pUnused": { + "offset": 568, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 592 + }, + "_UNICODE_STRING": { + "fields": { + "Buffer": { + "offset": 4, + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "Length": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "MaximumLength": { + "offset": 2, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 8 + } + } } From d172e1271716c27e1c3d0145fb651eef552916d8 Mon Sep 17 00:00:00 2001 From: hsarkey Date: Fri, 24 Jan 2025 08:08:57 -0500 Subject: [PATCH 013/120] Updated fixes after internal review. Allows windows.dlllist to report back DLLs from wow64 processes. --- .../symbols/windows/extensions/__init__.py | 73 ++++++++----------- 1 file changed, 31 insertions(+), 42 deletions(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 2b73fa625..3ac737ac8 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -790,13 +790,13 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): # Determine if process is running under WOW64. if self.get_is_wow64(): - peb32 = self.get_wow_64_process() + proc = self.get_wow_64_process() else: return None # Confirm WoW64Process points to a valid process address - if not proc_layer.is_valid(peb32): + if not proc_layer.is_valid(proc): raise exceptions.InvalidAddressException( - proc_layer_name, peb32, f"Invalid Wow64Process address at {self.Peb:0x}" + proc_layer_name, proc, f"Invalid Wow64Process address at {self.Peb:0x}" ) # Leverage the context of existing symbol table to help configure @@ -816,50 +816,41 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): if self._context.symbol_space.has_type( sym_table + constants.BANG + "_EWOW64PROCESS" ): - peb32 = self._context.object( - f"{self._32bit_table_name}{constants.BANG}_PEB32", - layer_name=proc_layer_name, - offset=peb32.Peb, - ) - return peb32 + offset=proc.Peb # vista sp0-sp1 and 2003 sp1-sp2 elif self._context.symbol_space.has_type( sym_table + constants.BANG + "_WOW64_PROCESS" ): - peb32 = self._context.object( - f"{self._32bit_table_name}{constants.BANG}_PEB32", - layer_name=proc_layer_name, - offset=peb32.Wow64, - ) - return peb32 + offset=proc.Wow64 else: - peb32 = self._context.object( - f"{self._32bit_table_name}{constants.BANG}_PEB32", - layer_name=proc_layer_name, - offset=peb32, - ) - return peb32 + offset=proc + + peb32 = self._context.object( + f"{self._32bit_table_name}{constants.BANG}_PEB32", + layer_name=proc_layer_name, + offset=offset, + ) + return peb32 def load_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: """Generator for DLLs in the order that they were loaded.""" try: pebs = [ - [self.get_peb(), "_LDR_DATA_TABLE_ENTRY"], - [self.get_peb32(), "_LDR_DATA_TABLE_ENTRY"], + self.get_peb(), self.get_peb32(), ] - for peb, table_name in pebs: - if peb != None: + for peb in pebs: + if peb: sym_table = self.get_symbol_table_name() if peb.Ldr.vol.type_name.endswith("unsigned long"): - Ldr_data = self._context.symbol_space.get_type( + ldr_data = self._context.symbol_space.get_type( self._32bit_table_name + constants.BANG + "_PEB_LDR_DATA" ) - peb.Ldr = peb.Ldr.cast("pointer", subtype=Ldr_data) + peb.Ldr = peb.Ldr.cast("pointer", subtype=ldr_data) sym_table = self._32bit_table_name for entry in peb.Ldr.InLoadOrderModuleList.to_list( - f"{sym_table}{constants.BANG}" + table_name, + f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY", "InLoadOrderLinks", ): yield entry @@ -871,20 +862,19 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): try: pebs = [ - [self.get_peb(), "_LDR_DATA_TABLE_ENTRY"], - [self.get_peb32(), "_LDR_DATA_TABLE_ENTRY"], + self.get_peb(), self.get_peb32(), ] - for peb, table_name in pebs: - if peb != None: + for peb in pebs: + if peb: sym_table = self.get_symbol_table_name() if peb.Ldr.vol.type_name.endswith("unsigned long"): - Ldr_data = self._context.symbol_space.get_type( + ldr_data = self._context.symbol_space.get_type( self._32bit_table_name + constants.BANG + "_PEB_LDR_DATA" ) - peb.Ldr = peb.Ldr.cast("pointer", subtype=Ldr_data) + peb.Ldr = peb.Ldr.cast("pointer", subtype=ldr_data) sym_table = self._32bit_table_name for entry in peb.Ldr.InInitializationOrderModuleList.to_list( - f"{sym_table}{constants.BANG}" + table_name, + f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY", "InInitializationOrderLinks", ): yield entry @@ -895,20 +885,19 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): """Generator for DLLs in the order that they appear in memory""" try: pebs = [ - [self.get_peb(), "_LDR_DATA_TABLE_ENTRY"], - [self.get_peb32(), "_LDR_DATA_TABLE_ENTRY"], + self.get_peb(), self.get_peb32(), ] - for peb, table_name in pebs: - if peb != None: + for peb in pebs: + if peb: sym_table = self.get_symbol_table_name() if peb.Ldr.vol.type_name.endswith("unsigned long"): - Ldr_data = self._context.symbol_space.get_type( + ldr_data = self._context.symbol_space.get_type( self._32bit_table_name + constants.BANG + "_PEB_LDR_DATA" ) - peb.Ldr = peb.Ldr.cast("pointer", subtype=Ldr_data) + peb.Ldr = peb.Ldr.cast("pointer", subtype=ldr_data) sym_table = self._32bit_table_name for entry in peb.Ldr.InMemoryOrderModuleList.to_list( - f"{sym_table}{constants.BANG}" + table_name, + f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY", "InMemoryOrderLinks", ): yield entry From 9ce1fd7ac054dad51602a710c6628550786dc7aa Mon Sep 17 00:00:00 2001 From: hsarkey Date: Fri, 24 Jan 2025 08:37:02 -0500 Subject: [PATCH 014/120] Black formatting updates --- .../symbols/windows/extensions/__init__.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 3ac737ac8..9c4fbb1b6 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -816,17 +816,17 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): if self._context.symbol_space.has_type( sym_table + constants.BANG + "_EWOW64PROCESS" ): - offset=proc.Peb + offset = proc.Peb # vista sp0-sp1 and 2003 sp1-sp2 elif self._context.symbol_space.has_type( sym_table + constants.BANG + "_WOW64_PROCESS" ): - offset=proc.Wow64 + offset = proc.Wow64 else: - offset=proc - + offset = proc + peb32 = self._context.object( f"{self._32bit_table_name}{constants.BANG}_PEB32", layer_name=proc_layer_name, @@ -838,7 +838,8 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): """Generator for DLLs in the order that they were loaded.""" try: pebs = [ - self.get_peb(), self.get_peb32(), + self.get_peb(), + self.get_peb32(), ] for peb in pebs: if peb: @@ -850,7 +851,7 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): peb.Ldr = peb.Ldr.cast("pointer", subtype=ldr_data) sym_table = self._32bit_table_name for entry in peb.Ldr.InLoadOrderModuleList.to_list( - f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY", + f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY", "InLoadOrderLinks", ): yield entry @@ -862,7 +863,8 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): try: pebs = [ - self.get_peb(), self.get_peb32(), + self.get_peb(), + self.get_peb32(), ] for peb in pebs: if peb: @@ -885,7 +887,8 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): """Generator for DLLs in the order that they appear in memory""" try: pebs = [ - self.get_peb(), self.get_peb32(), + self.get_peb(), + self.get_peb32(), ] for peb in pebs: if peb: From 02d0790475e61cf4af15452980478a82a8f6ec0e Mon Sep 17 00:00:00 2001 From: hsarkey Date: Fri, 24 Jan 2025 11:32:48 -0500 Subject: [PATCH 015/120] Updated black and ruff formatting errors --- .../symbols/windows/extensions/__init__.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 9c4fbb1b6..f84c4105f 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -850,11 +850,10 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): ) peb.Ldr = peb.Ldr.cast("pointer", subtype=ldr_data) sym_table = self._32bit_table_name - for entry in peb.Ldr.InLoadOrderModuleList.to_list( + yield from peb.Ldr.InLoadOrderModuleList.to_list( f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY", "InLoadOrderLinks", - ): - yield entry + ) except exceptions.InvalidAddressException: return None @@ -875,11 +874,10 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): ) peb.Ldr = peb.Ldr.cast("pointer", subtype=ldr_data) sym_table = self._32bit_table_name - for entry in peb.Ldr.InInitializationOrderModuleList.to_list( + yield from peb.Ldr.InInitializationOrderModuleList.to_list( f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY", "InInitializationOrderLinks", - ): - yield entry + ) except exceptions.InvalidAddressException: return None @@ -899,11 +897,10 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): ) peb.Ldr = peb.Ldr.cast("pointer", subtype=ldr_data) sym_table = self._32bit_table_name - for entry in peb.Ldr.InMemoryOrderModuleList.to_list( + yield from peb.Ldr.InMemoryOrderModuleList.to_list( f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY", "InMemoryOrderLinks", - ): - yield entry + ) except exceptions.InvalidAddressException: return None From af9bd53cae0e69b00fe14845f437688f883013b0 Mon Sep 17 00:00:00 2001 From: hsarkey Date: Fri, 24 Jan 2025 13:35:39 -0500 Subject: [PATCH 016/120] Resolving conflicts and formatting issues --- .../framework/symbols/windows/extensions/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index f84c4105f..818c8c83f 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -485,10 +485,10 @@ class FILE_OBJECT(objects.StructType, pool.ExecutiveObject): ].is_valid(self.FileName.Buffer) def file_name_with_device(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: - name: Union[ - str, interfaces.renderers.BaseAbsentValue - ] = renderers.UnreadableValue() - + name: Union[str, interfaces.renderers.BaseAbsentValue] = ( + renderers.UnreadableValue() + ) + # this pointer needs to be checked against native_layer_name because the object may # be instantiated from a primary (virtual) layer or a memory (physical) layer. if self._context.layers[self.vol.native_layer_name].is_valid(self.DeviceObject): From 495d6466821c2b491edff3b4ba1834ff83cf1b45 Mon Sep 17 00:00:00 2001 From: hsarkey Date: Fri, 24 Jan 2025 13:53:21 -0500 Subject: [PATCH 017/120] Black and ruff fixes --- .../framework/symbols/windows/extensions/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 818c8c83f..814681a61 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -485,10 +485,8 @@ class FILE_OBJECT(objects.StructType, pool.ExecutiveObject): ].is_valid(self.FileName.Buffer) def file_name_with_device(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: - name: Union[str, interfaces.renderers.BaseAbsentValue] = ( - renderers.UnreadableValue() - ) - + name: Union[str, interfaces.renderers.BaseAbsentValue] = renderers.UnreadableValue() + # this pointer needs to be checked against native_layer_name because the object may # be instantiated from a primary (virtual) layer or a memory (physical) layer. if self._context.layers[self.vol.native_layer_name].is_valid(self.DeviceObject): From 830c28a7bb6e6014d5df63e558ac8a114de51f2c Mon Sep 17 00:00:00 2001 From: hsarkey Date: Fri, 24 Jan 2025 14:16:10 -0500 Subject: [PATCH 018/120] Updated with current black version --- volatility3/framework/symbols/windows/extensions/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 814681a61..cda2dd615 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -485,7 +485,9 @@ class FILE_OBJECT(objects.StructType, pool.ExecutiveObject): ].is_valid(self.FileName.Buffer) def file_name_with_device(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: - name: Union[str, interfaces.renderers.BaseAbsentValue] = renderers.UnreadableValue() + name: Union[str, interfaces.renderers.BaseAbsentValue] = ( + renderers.UnreadableValue() + ) # this pointer needs to be checked against native_layer_name because the object may # be instantiated from a primary (virtual) layer or a memory (physical) layer. From f75a4be0517f772ebe05aeaef4d0eb21fe9d98a4 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 26 Jan 2025 15:24:06 +0100 Subject: [PATCH 019/120] 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 009d48ce8..39df2ccc7 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -14,7 +14,7 @@ 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.symbols.linux.utilities import modules as linux_utilities_modules from volatility3.framework.constants import architectures vollog = logging.getLogger(__name__) @@ -78,8 +78,8 @@ class CheckFtrace(interfaces.plugins.PluginInterface): 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 @@ if the "hidden_modules" key is present in known_modules. 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 @@ if the "hidden_modules" key is present in known_modules. ) # 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 020/120] 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 692aa8d3a..f529a61ae 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -9,7 +9,7 @@ from volatility3.framework.symbols.linux import extensions 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 021/120] 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 39df2ccc7..c35a6f561 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -9,12 +9,12 @@ from typing import Dict, List, Iterable, Optional 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 @@ class CheckFtrace(interfaces.plugins.PluginInterface): 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 022/120] 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 c35a6f561..6e1a4e470 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 @@ class CheckFtrace(interfaces.plugins.PluginInterface): ), ] - @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 @@ if the "hidden_modules" key is present in known_modules. 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 023/120] 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 6e1a4e470..629f1bab8 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -214,7 +214,10 @@ if the "hidden_modules" key is present in known_modules. # 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 024/120] 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 629f1bab8..99961c931 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -150,6 +150,7 @@ if the "hidden_modules" key is present in known_modules. 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 @@ if the "hidden_modules" key is present in known_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) + 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 025/120] 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 f529a61ae..baaeff683 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 @@ class Modules(interfaces.configuration.VersionableInterface): 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 026/120] 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 99961c931..29216187c 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 6ef57d1e89b0b7853993600b643c3efeaeede218 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Mon, 27 Jan 2025 23:12:28 +0000 Subject: [PATCH 027/120] Windows: Allow get_pefile_obj to be shared --- .../framework/plugins/windows/pe_symbols.py | 9 ++-- .../plugins/windows/skeleton_key_check.py | 44 +++---------------- 2 files changed, 12 insertions(+), 41 deletions(-) diff --git a/volatility3/framework/plugins/windows/pe_symbols.py b/volatility3/framework/plugins/windows/pe_symbols.py index 21e657ab3..690e6f6ac 100644 --- a/volatility3/framework/plugins/windows/pe_symbols.py +++ b/volatility3/framework/plugins/windows/pe_symbols.py @@ -244,7 +244,7 @@ class PESymbols(interfaces.plugins.PluginInterface): _required_framework_version = (2, 7, 0) - _version = (1, 0, 0) + _version = (1, 1, 0) # used for special handling of the kernel PDB file. See later notes os_module_name = "ntoskrnl.exe" @@ -292,8 +292,9 @@ class PESymbols(interfaces.plugins.PluginInterface): ), ] - @staticmethod - def _get_pefile_obj( + @classmethod + def get_pefile_obj( + cls, context: interfaces.context.ContextInterface, pe_table_name: str, layer_name: str, @@ -484,7 +485,7 @@ class PESymbols(interfaces.plugins.PluginInterface): module_start = module_info[1] # we need a valid PE with an export table - pe_module = PESymbols._get_pefile_obj( + pe_module = PESymbols.get_pefile_obj( context, pe_table_name, layer_name, module_start ) if not pe_module: diff --git a/volatility3/framework/plugins/windows/skeleton_key_check.py b/volatility3/framework/plugins/windows/skeleton_key_check.py index 6ae07381a..d7bd02683 100644 --- a/volatility3/framework/plugins/windows/skeleton_key_check.py +++ b/volatility3/framework/plugins/windows/skeleton_key_check.py @@ -26,7 +26,7 @@ from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import intermed from volatility3.framework.symbols.windows import pdbutil from volatility3.framework.symbols.windows.extensions import pe -from volatility3.plugins.windows import pslist, vadinfo +from volatility3.plugins.windows import pslist, vadinfo, pe_symbols try: import capstone @@ -61,43 +61,11 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="pdbutil", component=pdbutil.PDBUtility, version=(1, 0, 0) ), + requirements.VersionRequirement( + name="pe_symbols", component=pe_symbols.PESymbols, version=(1, 1, 0) + ), ] - def _get_pefile_obj( - self, pe_table_name: str, layer_name: str, base_address: int - ) -> pefile.PE: - """ - Attempts to pefile object from the bytes of the PE file - - Args: - pe_table_name: name of the pe types table - layer_name: name of the lsass.exe process layer - base_address: base address of cryptdll.dll in lsass.exe - - Returns: - the constructed pefile object - """ - pe_data = io.BytesIO() - - try: - dos_header = self.context.object( - pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER", - offset=base_address, - layer_name=layer_name, - ) - - for offset, data in dos_header.reconstruct(): - pe_data.seek(offset) - pe_data.write(data) - - pe_ret = pefile.PE(data=pe_data.getvalue(), fast_load=True) - - except exceptions.InvalidAddressException: - vollog.debug("Unable to reconstruct cryptdll.dll in memory") - pe_ret = None - - return pe_ret - def _check_for_skeleton_key_vad( self, csystem: interfaces.objects.ObjectInterface, @@ -497,7 +465,9 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface): self.context, self.config_path, "windows", "pe", class_types=pe.class_types ) - cryptdll = self._get_pefile_obj(pe_table_name, proc_layer_name, cryptdll_base) + cryptdll = pe_symbols.PESymbols.get_pefile_obj( + self.context, pe_table_name, proc_layer_name, cryptdll_base + ) if not cryptdll: return None From 10743b101929cea944197996feead44d4d81fd98 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Mon, 27 Jan 2025 23:14:02 +0000 Subject: [PATCH 028/120] Windows: Fix ruff issues in skeleton_key_check --- volatility3/framework/plugins/windows/skeleton_key_check.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/windows/skeleton_key_check.py b/volatility3/framework/plugins/windows/skeleton_key_check.py index d7bd02683..ce5bb41f4 100644 --- a/volatility3/framework/plugins/windows/skeleton_key_check.py +++ b/volatility3/framework/plugins/windows/skeleton_key_check.py @@ -11,14 +11,13 @@ # # https://volatility-labs.blogspot.com/2021/10/memory-forensics-r-illustrated.html -import io import logging from typing import Iterable, Tuple, List, Optional import pefile from volatility3.framework import interfaces, symbols, exceptions -from volatility3.framework import renderers, constants +from volatility3.framework import renderers from volatility3.framework.configuration import requirements from volatility3.framework.layers import scanners from volatility3.framework.objects import utility From b055848576697547fc2bc139738f7c3a4d70fa13 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 29 Jan 2025 12:04:32 +0100 Subject: [PATCH 029/120] 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 29216187c..da88d1de1 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 9218e0e07f92ffb61b17f14336f36440092c1083 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Fri, 31 Jan 2025 18:47:11 +1100 Subject: [PATCH 030/120] fix double null-termination search --- volatility3/framework/objects/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/objects/__init__.py b/volatility3/framework/objects/__init__.py index 869d4dae6..39ce6f59f 100644 --- a/volatility3/framework/objects/__init__.py +++ b/volatility3/framework/objects/__init__.py @@ -356,8 +356,9 @@ class String(PrimitiveObject, str): ), **params, ) - if value.find("\x00") >= 0: - value = value[: value.find("\x00")] + index = value.find("\x00") + if index >= 0: + value = value[:index] return value class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): From b91724c3ef7a5fa22f639a6c495afdce736ee5d9 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Fri, 31 Jan 2025 18:48:52 +1100 Subject: [PATCH 031/120] Replace *_to_string() for a block reader implementation for better performance. Add new address_to_string() helper --- volatility3/framework/objects/utility.py | 102 +++++++++++++++++++++-- 1 file changed, 94 insertions(+), 8 deletions(-) diff --git a/volatility3/framework/objects/utility.py b/volatility3/framework/objects/utility.py index 0bc285517..dc450c0b3 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -29,9 +29,23 @@ def bswap_64(value: int) -> int: def array_to_string( - array: "objects.Array", count: Optional[int] = None, errors: str = "replace" -) -> interfaces.objects.ObjectInterface: - """Takes a volatility Array of characters and returns a string.""" + array: "objects.Array", + count: Optional[int] = None, + errors: str = "replace", + block_size=32, +) -> str: + """Takes a Volatility 'Array' of characters and returns a Python string. + + Args: + array: The Volatility `Array` object containing character elements. + count: Optional maximum number of characters to convert. If None, the function + processes the entire array. + errors: Specifies error handling behavior for decoding, defaulting to "replace". + block_size: Reading block size. Defaults to 32 + + Returns: + A decoded string representation of the character array. + """ # TODO: Consider checking the Array's target is a native char if not isinstance(array, objects.Array): raise TypeError("Array_to_string takes an Array of char") @@ -39,19 +53,91 @@ def array_to_string( if count is None: count = array.vol.count - return array.cast("string", max_length=count, errors=errors) + return address_to_string( + context=array._context, + layer_name=array.vol.layer_name, + address=array.vol.offset, + count=count, + errors=errors, + block_size=block_size, + ) -def pointer_to_string(pointer: "objects.Pointer", count: int, errors: str = "replace"): - """Takes a volatility Pointer to characters and returns a string.""" +def pointer_to_string( + pointer: "objects.Pointer", + count: int, + errors: str = "replace", + block_size=32, +) -> str: + """Takes a Volatility 'Pointer' to characters and returns a Python string. + + Args: + pointer: A `Pointer` object containing character elements. + count: Optional maximum number of characters to convert. If None, the function + processes the entire array. + errors: Specifies error handling behavior for decoding, defaulting to "replace". + block_size: Reading block size. Defaults to 32 + + Returns: + A decoded string representation of the data referenced by the pointer. + """ if not isinstance(pointer, objects.Pointer): raise TypeError("pointer_to_string takes a Pointer") if count < 1: raise ValueError("pointer_to_string requires a positive count") - char = pointer.dereference() - return char.cast("string", max_length=count, errors=errors) + return address_to_string( + context=pointer._context, + layer_name=pointer.vol.layer_name, + address=pointer, + count=count, + errors=errors, + block_size=block_size, + ) + + +def address_to_string( + context: interfaces.context.ContextInterface, + layer_name: str, + address: int, + count: int, + errors: str = "replace", + block_size=32, +) -> str: + """Reads a null-terminated string from a given specified memory address, processing + it in blocks for efficiency. + + Args: + context: The context used to retrieve memory layers and symbol tables + layer_name: The name of the memory layer to read from + address: The address where the string is located in memory + count: The number of bytes to read + errors: The error handling scheme to use for encoding errors. Defaults to "replace" + block_size: Reading block size. Defaults to 32 + + Returns: + The decoded string extracted from memory. + """ + if not isinstance(address, int): + raise TypeError("It takes an int") + + if count < 1: + raise ValueError("It requires a positive count") + + layer = context.layers[layer_name] + text = b"" + while len(text) <= count: + current_block_size = min(count - len(text), block_size) + temp_text = layer.read(address + len(text), current_block_size) + idx = temp_text.find(b"\x00") + if idx != -1: + temp_text = temp_text[:idx] + text += temp_text + break + text += temp_text + + return text.decode(errors=errors) def array_of_pointers( From c82ef0cfd2a34873b989cdc87afed5783c92d34b Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Fri, 31 Jan 2025 19:29:33 +1100 Subject: [PATCH 032/120] intel: address translation: performance improvements caching by page address --- volatility3/framework/layers/intel.py | 44 +++++++++++++++++++++------ 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/volatility3/framework/layers/intel.py b/volatility3/framework/layers/intel.py index 7c2c72ac1..b6f59fee1 100644 --- a/volatility3/framework/layers/intel.py +++ b/volatility3/framework/layers/intel.py @@ -186,6 +186,31 @@ class Intel(linear.LinearlyMappedLayer): Returns the translated entry value """ + offset &= self.address_mask + + if not (self.minimum_address <= offset <= self.maximum_address): + raise exceptions.InvalidAddressException( + offset, f"Address {offset:#x} outside virtual address range" + ) + + page_address = offset & self.page_mask + return self._translate_page(page_address) + + @functools.lru_cache(maxsize=1024) + def _translate_page(self, page_address: int) -> int: + """Translates a page address based on paging tables. + + Args: + page_address: The page base address + + Returns: + the translated entry value + """ + if page_address & ~self.page_mask != 0: + raise exceptions.InvalidAddressException( + page_address, + f"Invalid page address {page_address:#x}. The address must be aligned to the page size", + ) # Setup the entry and how far we are through the offset # Position maintains the number of bits left to process # We or with 0x1 to ensure our page_map_offset is always valid @@ -193,11 +218,13 @@ class Intel(linear.LinearlyMappedLayer): entry = self._initial_entry if not ( - self.minimum_address <= (offset & self.address_mask) <= self.maximum_address + self.minimum_address + <= (page_address & self.address_mask) + <= self.maximum_address ): raise exceptions.PagedInvalidAddressException( self.name, - offset, + page_address, position + 1, entry, "Entry outside virtual address range: " + hex(entry), @@ -209,7 +236,7 @@ class Intel(linear.LinearlyMappedLayer): if not self._page_is_valid(entry): raise exceptions.PagedInvalidAddressException( self.name, - offset, + page_address, position + 1, entry, "Page Fault at entry " + hex(entry) + " in table " + name, @@ -225,7 +252,7 @@ class Intel(linear.LinearlyMappedLayer): # Figure out how much of the offset we should be using start = position position -= size - index = self._mask(offset, start, position + 1) >> (position + 1) + index = self._mask(page_address, start, position + 1) >> (position + 1) # Grab the base address of the table we'll be getting the next entry from base_address = self._mask( @@ -236,17 +263,15 @@ class Intel(linear.LinearlyMappedLayer): if table is None: raise exceptions.PagedInvalidAddressException( self.name, - offset, + page_address, position + 1, entry, "Page Fault at entry " + hex(entry) + " in table " + name, ) # Read the data for the next entry - entry_data = table[ - (index << self._index_shift) : (index << self._index_shift) - + self._entry_size - ] + entry_data_start = index << self._index_shift + entry_data = table[entry_data_start : entry_data_start + self._entry_size] if INTEL_TRANSLATION_DEBUGGING: vollog.log( @@ -259,7 +284,6 @@ class Intel(linear.LinearlyMappedLayer): return entry, position - @functools.lru_cache(maxsize=1025) def _get_valid_table(self, base_address: int) -> Optional[bytes]: """Extracts the table, validates it and returns it if it's valid.""" table = self._context.layers.read( From 815b2fe918241ab2847512cfc7e9980b0a9e50ed Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Fri, 31 Jan 2025 19:49:04 +1100 Subject: [PATCH 033/120] Fix bug in address_to_string() helper --- volatility3/framework/objects/utility.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/objects/utility.py b/volatility3/framework/objects/utility.py index dc450c0b3..93216743c 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -127,7 +127,7 @@ def address_to_string( layer = context.layers[layer_name] text = b"" - while len(text) <= count: + while len(text) < count: current_block_size = min(count - len(text), block_size) temp_text = layer.read(address + len(text), current_block_size) idx = temp_text.find(b"\x00") From 68c8b2389a7373d321afabe24f035024add1d012 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Fri, 31 Jan 2025 10:00:16 +0000 Subject: [PATCH 034/120] Make error messages a little more descriptive --- volatility3/framework/objects/utility.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/objects/utility.py b/volatility3/framework/objects/utility.py index 93216743c..500c0e9a5 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -120,10 +120,10 @@ def address_to_string( The decoded string extracted from memory. """ if not isinstance(address, int): - raise TypeError("It takes an int") + raise TypeError("Address must be a valid integer") if count < 1: - raise ValueError("It requires a positive count") + raise ValueError("Count must be greater than 0") layer = context.layers[layer_name] text = b"" From e9d1831a7cbc7dd27e60d4d772a09a6df4e767c5 Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 31 Jan 2025 14:45:28 +0000 Subject: [PATCH 035/120] Windows: update get_commit_charge with CommitCharge fix by BeanBagKing --- volatility3/framework/symbols/windows/extensions/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index ff6d14a8c..26007a37a 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -262,7 +262,10 @@ class MMVAD_SHORT(objects.StructType): def get_commit_charge(self): """Get the VAD's commit charge (number of committed pages)""" - if self.has_member("u1") and self.u1.has_member("VadFlags1"): + if self.has_member("CommitCharge"): + return self.CommitCharge + + elif self.has_member("u1") and self.u1.has_member("VadFlags1"): return self.u1.VadFlags1.CommitCharge elif self.has_member("u") and self.u.has_member("VadFlags"): From 55b27a68d5918abdba557db3aa0dcf02b4b2eae9 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 31 Jan 2025 18:10:12 +0000 Subject: [PATCH 036/120] Add mnt_parent check to kernel version validation --- volatility3/framework/symbols/linux/extensions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 8893e6e52..232e905b8 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1574,7 +1574,7 @@ class vfsmount(objects.StructType): 'True' if the kernel lacks the 'mount' struct, typically indicating kernel < 3.3. """ - return not self._context.symbol_space.has_type("mount") + return (not self._context.symbol_space.has_type("mount")) and self.has_member("mnt_parent") def is_equal(self, vfsmount_ptr) -> bool: """Helper to make sure it is comparing two pointers to 'vfsmount'. From 1132bd98304abf87b5ce1812309344047f5a907c Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 31 Jan 2025 18:11:22 +0000 Subject: [PATCH 037/120] Add mnt_parent check to kernel version validation --- volatility3/framework/symbols/linux/extensions/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 232e905b8..7103a2068 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1574,7 +1574,9 @@ class vfsmount(objects.StructType): 'True' if the kernel lacks the 'mount' struct, typically indicating kernel < 3.3. """ - return (not self._context.symbol_space.has_type("mount")) and self.has_member("mnt_parent") + return (not self._context.symbol_space.has_type("mount")) and self.has_member( + "mnt_parent" + ) def is_equal(self, vfsmount_ptr) -> bool: """Helper to make sure it is comparing two pointers to 'vfsmount'. From cb3542f76a64eee879ecaece45af310f15c531fb Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 31 Jan 2025 20:40:51 +0000 Subject: [PATCH 038/120] Catch invalid address exception for invalid sock values --- volatility3/framework/plugins/linux/sockstat.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/sockstat.py b/volatility3/framework/plugins/linux/sockstat.py index 764c04563..914c251d0 100644 --- a/volatility3/framework/plugins/linux/sockstat.py +++ b/volatility3/framework/plugins/linux/sockstat.py @@ -538,8 +538,11 @@ class Sockstat(plugins.PluginInterface): continue sock = socket.sk.dereference() - sock_type = sock.get_type() - family = sock.get_family() + try: + sock_type = sock.get_type() + family = sock.get_family() + except exceptions.InvalidAddressException: + continue sock_handler = SockHandlers(vmlinux, task) sock_fields = sock_handler.process_sock(sock) From 4d43ac9a6dc51c6b2855a907b452185d4eb0366a Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 31 Jan 2025 20:46:16 +0000 Subject: [PATCH 039/120] Catch invalid address exception for invalid sock values --- volatility3/framework/plugins/linux/sockstat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/sockstat.py b/volatility3/framework/plugins/linux/sockstat.py index 914c251d0..3d2df655b 100644 --- a/volatility3/framework/plugins/linux/sockstat.py +++ b/volatility3/framework/plugins/linux/sockstat.py @@ -541,11 +541,11 @@ class Sockstat(plugins.PluginInterface): try: sock_type = sock.get_type() family = sock.get_family() + sock_handler = SockHandlers(vmlinux, task) + sock_fields = sock_handler.process_sock(sock) except exceptions.InvalidAddressException: continue - sock_handler = SockHandlers(vmlinux, task) - sock_fields = sock_handler.process_sock(sock) if not sock_fields: continue From 644b967c624a6a3f1f57e735770e0f56604a1d11 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 31 Jan 2025 21:31:07 +0000 Subject: [PATCH 040/120] Prevent backtraces when kthread full name is smeared --- .../framework/plugins/linux/kthreads.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/volatility3/framework/plugins/linux/kthreads.py b/volatility3/framework/plugins/linux/kthreads.py index bd0e895a4..674eae1e5 100644 --- a/volatility3/framework/plugins/linux/kthreads.py +++ b/volatility3/framework/plugins/linux/kthreads.py @@ -72,28 +72,35 @@ class Kthreads(plugins.PluginInterface): if task.has_member("worker_private"): # kernels >= 5.17 e32cf5dfbe227b355776948b2c9b5691b84d1cbd - ktread_base_pointer = task.worker_private + kthread_base_pointer = task.worker_private else: # 5.8 <= kernels < 5.17 in 52782c92ac85c4e393eb4a903a62e6c24afa633f threadfn # was added to struct kthread. task.set_child_tid is safe on those versions. - ktread_base_pointer = task.set_child_tid + kthread_base_pointer = task.set_child_tid - if not ktread_base_pointer.is_readable(): + if not kthread_base_pointer.is_readable(): continue - kthread = ktread_base_pointer.dereference().cast("kthread") + kthread = kthread_base_pointer.dereference().cast("kthread") threadfn = kthread.threadfn if not (threadfn and threadfn.is_readable()): continue task_name = utility.array_to_string(task.comm) + thread_name = task_name + # kernels >= 5.17 in d6986ce24fc00b0638bd29efe8fb7ba7619ed2aa full_name was added to kthread - thread_name = ( - utility.pointer_to_string(kthread.full_name, count=255) - if kthread.has_member("full_name") - else task_name - ) + if kthread.has_member("full_name"): + try: + thread_name = utility.pointer_to_string( + kthread.full_name, count=255 + ) + except exceptions.InvalidAddressException: + vollog.debug( + f"full_name pointer for thread at {kthread.vol.offset:#x} is paged out." + ) + module_name, symbol_name = ( linux_utilities_modules.Modules.lookup_module_address( self.context, vmlinux.name, handlers, threadfn From efbc410c4d3507f84f304e3385e3b9473b3fd1be Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 31 Jan 2025 21:43:32 +0000 Subject: [PATCH 041/120] Add missing pointer validation check in mountinfo --- volatility3/framework/plugins/linux/mountinfo.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/mountinfo.py b/volatility3/framework/plugins/linux/mountinfo.py index 47d8705c8..c56ced489 100644 --- a/volatility3/framework/plugins/linux/mountinfo.py +++ b/volatility3/framework/plugins/linux/mountinfo.py @@ -93,11 +93,14 @@ class MountInfo(plugins.PluginInterface): return None mnt_root_path = mnt_root.path() - superblock = mnt.get_mnt_sb() mnt_id: int = mnt.mnt_id parent_id: int = mnt.mnt_parent.mnt_id + superblock = mnt.get_mnt_sb() + if not (superblock and superblock.is_readable()): + return None + st_dev = f"{superblock.major}:{superblock.minor}" mnt_opts: List[str] = [] From 3b3331a58d42e2fe71433892e7b632cdfd63db84 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 31 Jan 2025 22:32:35 +0000 Subject: [PATCH 042/120] Add smear checks and missing absolute flag to walk_internal_list --- .../framework/symbols/linux/__init__.py | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index afdfee39c..6f9ccdadc 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -430,13 +430,36 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): @classmethod def walk_internal_list(cls, vmlinux, struct_name, list_member, list_start): + count = 0 + seen = set() + while list_start: + if list_start.vol.offset in seen: + vollog.debug( + "walk_internal_list: Repeat entry found. Stopping enumeration" + ) + break + seen.add(list_start.vol.offset) + + if not (list_start and list_start.is_readable()): + break + list_struct = vmlinux.object( - object_type=struct_name, offset=list_start.vol.offset + object_type=struct_name, offset=list_start.vol.offset, absolute=True ) + yield list_struct + list_start = getattr(list_struct, list_member) + if count == 4096: + vollog.debug( + f"walk_internal_list: Breaking list enumeration at {count}" + ) + break + + count += 1 + @classmethod def container_of( cls, From 941e40c368b79a36ee59a2cdf860aad83d489d15 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 31 Jan 2025 22:59:32 +0000 Subject: [PATCH 043/120] Fix get_name API, fix malfind --- volatility3/framework/plugins/linux/elfs.py | 2 +- volatility3/framework/plugins/linux/malfind.py | 8 +++++--- volatility3/framework/plugins/linux/proc.py | 2 +- .../framework/symbols/linux/extensions/__init__.py | 8 +++++++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/plugins/linux/elfs.py b/volatility3/framework/plugins/linux/elfs.py index 0d1c9c2dd..b9dcc3cca 100644 --- a/volatility3/framework/plugins/linux/elfs.py +++ b/volatility3/framework/plugins/linux/elfs.py @@ -177,7 +177,7 @@ class Elfs(plugins.PluginInterface): name, format_hints.Hex(vma.vm_start), format_hints.Hex(vma.vm_end), - path, + path or renderers.NotAvailableValue(), file_output, ), ) diff --git a/volatility3/framework/plugins/linux/malfind.py b/volatility3/framework/plugins/linux/malfind.py index e45688e97..a6e739bbf 100644 --- a/volatility3/framework/plugins/linux/malfind.py +++ b/volatility3/framework/plugins/linux/malfind.py @@ -56,10 +56,10 @@ class Malfind(interfaces.plugins.PluginInterface): ) if ( vma.is_suspicious(proc_layer) - and vma.get_name(self.context, task) != "[vdso]" + and vma_name != "[vdso]" ): data = proc_layer.read(vma.vm_start, 64, pad=True) - yield vma, data + yield vma, vma_name, data def _generator(self, tasks): # determine if we're on a 32 or 64 bit kernel @@ -71,7 +71,7 @@ class Malfind(interfaces.plugins.PluginInterface): for task in tasks: process_name = utility.array_to_string(task.comm) - for vma, data in self._list_injections(task): + for vma, vma_name, data in self._list_injections(task): if is_32bit_arch: architecture = "intel" else: @@ -88,6 +88,7 @@ class Malfind(interfaces.plugins.PluginInterface): process_name, format_hints.Hex(vma.vm_start), format_hints.Hex(vma.vm_end), + vma_name or renderers.NotAvailableValue(), vma.get_protection(), format_hints.HexBytes(data), disasm, @@ -103,6 +104,7 @@ class Malfind(interfaces.plugins.PluginInterface): ("Process", str), ("Start", format_hints.Hex), ("End", format_hints.Hex), + ("Path", str), ("Protection", str), ("Hexdump", format_hints.HexBytes), ("Disasm", interfaces.renderers.Disassembly), diff --git a/volatility3/framework/plugins/linux/proc.py b/volatility3/framework/plugins/linux/proc.py index 23d6605b7..5acba6594 100644 --- a/volatility3/framework/plugins/linux/proc.py +++ b/volatility3/framework/plugins/linux/proc.py @@ -246,7 +246,7 @@ class Maps(plugins.PluginInterface): major, minor, inode_num, - path, + path or renderers.NotAvailableValue(), file_output, ), ) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 8893e6e52..5ac391da1 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1056,7 +1056,7 @@ class vm_area_struct(objects.StructType): parent_layer = self._context.layers[self.vol.layer_name] return self.vm_pgoff << parent_layer.page_shift - def get_name(self, context, task): + def _do_get_name(self, context, task) -> str: if self.vm_file != 0: fname = linux.LinuxUtilities.path_for_file(context, task, self.vm_file) elif self.vm_start <= task.mm.start_brk and self.vm_end >= task.mm.brk: @@ -1072,6 +1072,12 @@ class vm_area_struct(objects.StructType): fname = "Anonymous Mapping" return fname + def get_name(self, context, task) -> Optional[str]: + try: + return self._do_get_name(context, task) + except exceptions.InvalidAddressException: + return None + # used by malfind def is_suspicious(self, proclayer=None): ret = False From b81d2a27810681ebe810490afff9638d6b3c4d1d Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 31 Jan 2025 23:03:43 +0000 Subject: [PATCH 044/120] Fix get_name API, fix malfind --- volatility3/framework/plugins/linux/malfind.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/volatility3/framework/plugins/linux/malfind.py b/volatility3/framework/plugins/linux/malfind.py index a6e739bbf..7d8dd7f18 100644 --- a/volatility3/framework/plugins/linux/malfind.py +++ b/volatility3/framework/plugins/linux/malfind.py @@ -54,10 +54,7 @@ class Malfind(interfaces.plugins.PluginInterface): vollog.debug( f"Injections : processing PID {task.pid} : VMA {vma_name} : {hex(vma.vm_start)}-{hex(vma.vm_end)}" ) - if ( - vma.is_suspicious(proc_layer) - and vma_name != "[vdso]" - ): + if vma.is_suspicious(proc_layer) and vma_name != "[vdso]": data = proc_layer.read(vma.vm_start, 64, pad=True) yield vma, vma_name, data From 1d1af696ffdd7c0d1cfc033c8d19a1975ae4e2a0 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Fri, 31 Jan 2025 17:26:36 -0600 Subject: [PATCH 045/120] Windows: Handles - catch exception in handle iteration An `InvalidAddressException` can occur inside of `__iter__` when iterating over the handle table (the exact exception occurs when creating the subtype in `objects.Array.__getitem__`. This changes the handle code to do a manual iteration over the sequence using the array length and indexes, catch the exception, log the index, and continue. In the test sample that prompted this change, the exception occurred on the access of the very last item in the array. closes #1573 --- volatility3/framework/plugins/windows/handles.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/windows/handles.py b/volatility3/framework/plugins/windows/handles.py index 6a391fe35..0c7958bac 100644 --- a/volatility3/framework/plugins/windows/handles.py +++ b/volatility3/framework/plugins/windows/handles.py @@ -243,7 +243,12 @@ class Handles(interfaces.plugins.PluginInterface): layer_object = self.context.layers[virtual] masked_offset = offset & layer_object.maximum_address - for entry in table: + for i in range(len(table)): + try: + entry = table[i] + except exceptions.InvalidAddressException: + vollog.debug(f"Failed to get handle table entry at index {i}") + continue # This triggered a backtrace in many testing samples # in the level == 0 path # The code above this calls `is_valid` on the `offset` From 60dc0c04f4dc6deea067a9fad0db9bbddacfbe48 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 31 Jan 2025 22:37:21 -0600 Subject: [PATCH 046/120] Address feedback. Add doc strings --- .../framework/symbols/linux/__init__.py | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 6f9ccdadc..aa89e7a16 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -7,7 +7,7 @@ import contextlib import functools import logging from abc import ABC, abstractmethod -from typing import Iterator, List, Tuple, Optional, Union, Dict +from typing import List, Tuple, Optional, Union, Dict, Generator, Iterator import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules from volatility3 import framework @@ -429,7 +429,28 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): ) @classmethod - def walk_internal_list(cls, vmlinux, struct_name, list_member, list_start): + def walk_internal_list( + cls, + vmlinux: interfaces.context.ModuleInterface, + struct_name: str, + list_member: str, + list_start: interfaces.objects.ObjectInterface, + max_count: int = 4096, + ) -> Generator[interfaces.objects.ObjectInterface, None, None]: + """ + An API that provides generic, smear-resistant enumeration of embedded lists + + Args: + vmlinux: + struct_name: name of the structure of the list elements + list_member: name of the list_member holding the internal list + list_start: Starting (head) member of the list + max_count: Optional maximum amount of list elements that will be yielded + + Returns: + Instances of `struct_name` + """ + count = 0 seen = set() @@ -452,9 +473,9 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): list_start = getattr(list_struct, list_member) - if count == 4096: + if count == max_count: vollog.debug( - f"walk_internal_list: Breaking list enumeration at {count}" + f"walk_internal_list: Breaking list enumeration at maximum allowed count of {count}" ) break From 18a69b941775ac97dae38b7352004fef32e77017 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sat, 1 Feb 2025 16:23:59 +1100 Subject: [PATCH 047/120] linux: add latched RB-trees implementation --- .../framework/symbols/linux/__init__.py | 2 + .../symbols/linux/extensions/__init__.py | 87 ++++++++++++++++++- 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index afdfee39c..53c7c4c88 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -87,6 +87,8 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable): # Only found in 6.1+ kernels self.optional_set_type_class("maple_tree", extensions.maple_tree) + self.optional_set_type_class("latch_tree_root", extensions.latch_tree_root) + class LinuxUtilities(interfaces.configuration.VersionableInterface): """Class with multiple useful linux functions.""" diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 7103a2068..0f7ddfac3 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -10,7 +10,17 @@ import binascii import stat import datetime import socket as socket_module -from typing import Generator, Iterable, Iterator, Optional, Tuple, List, Union, Dict +from typing import ( + Generator, + Iterable, + Iterator, + Optional, + Tuple, + List, + Union, + Dict, + Callable, +) from volatility3.framework import constants, exceptions, objects, interfaces, symbols from volatility3.framework.renderers import conversion @@ -2979,3 +2989,78 @@ class scatterlist(objects.StructType): physical_layer = self._context.layers[physical_layer_name] for sg in self.for_each_sg(): yield from physical_layer.read(sg.dma_address, sg._sg_dma_len()) + + +class latch_tree_root(objects.StructType): + """Latched RB-trees implementation""" + + @functools.cached_property + def _vmlinux(self): + return linux.LinuxUtilities.get_module_from_volobj_type(self._context, self) + + @functools.lru_cache + def _get_type_cached(self, name): + return self._vmlinux.get_type(name) + + def _get_lt_node_from_rb_node( + self, rb_node, index + ) -> Optional[interfaces.objects.ObjectInterface]: + """Gets the latch tree node from the RBTree node. + Based on __lt_from_rb() + """ + # Unfortunately, we cannot use our LinuxUtilities.container_of() here, since the + # member is indexed by the 'index' variable: + # ltn = container_of(node, struct latch_tree_node, node[idx]) + pointer_size = self._get_type_cached("pointer").size + type_dec = self._get_type_cached("latch_tree_node") + member_offset = type_dec.relative_child_offset("node") + index * pointer_size + container_addr = rb_node.vol.offset - member_offset + + return self._vmlinux.object( + object_type="latch_tree_node", offset=container_addr, absolute=True + ) + + def find( + self, key: int, comp_function: Callable + ) -> Optional[interfaces.objects.ObjectInterface]: + """Returns a pointer to the node matching key or None. + + Based on latch_tree_find() and __lt_find() + + Args: + key (int): Typically an address + comp_function: Callback comparison function to provide the order between the + search key and an element. It's works like the kernel's latch_tree_ops::comp + i.e.: comp_function(key, latch_tree_node) + + Returns: + latch_tree_node: A pointer to the node matching key or None. + """ + # latch_tree_root >= 4.2 ade3f510f93a5613b672febe88eff8ea7f1c63b7 + + # Use the lowest sequence bit as an index for picking which data copy to read + if self.seq.has_member("seqcount"): + # kernels >= 5.10 0c9794c8b6781eb7dad8e19b78c5d4557790597a + sequence = self.seq.seqcount.sequence + elif self.seq.has_member("sequence"): + # 4.2 <= kernel < 5.10 + sequence = self.seq.sequence + else: + raise AttributeError("Unsupported sequence type implementation") + + idx = sequence & 1 + + rb_node_ptr = self.tree[idx].rb_node + while rb_node_ptr and rb_node_ptr.is_readable(): + rb_node = rb_node_ptr.dereference() + lt_node = self._get_lt_node_from_rb_node(rb_node, idx) + c = comp_function(key, lt_node) + if c < 0: + rb_node_ptr = rb_node.rb_left + elif c > 0: + rb_node_ptr = rb_node.rb_right + else: + return lt_node + + return None + From dba90ac2174017f24f136fec74b96797da1766e7 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sat, 1 Feb 2025 16:30:51 +1100 Subject: [PATCH 048/120] linux: Add support for module symbol types --- .../symbols/linux/extensions/__init__.py | 51 ++++++++++++++----- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 0f7ddfac3..5364fcbda 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -176,37 +176,43 @@ class module(generic.GenericIntelProcess): """Get the name of the module as a string""" return utility.array_to_string(self.name) - def _get_sect_count(self, grp): + def _get_sect_count(self, grp) -> int: """Try to determine the number of valid sections""" + symbol_table_name = self.get_symbol_table_name() arr = self._context.object( - self.get_symbol_table_name() + constants.BANG + "array", + symbol_table_name + constants.BANG + "array", layer_name=self.vol.layer_name, offset=grp.attrs, subtype=self._context.symbol_space.get_type( - self.get_symbol_table_name() + constants.BANG + "pointer" + symbol_table_name + constants.BANG + "pointer" ), count=25, ) idx = 0 - while arr[idx]: + while arr[idx] and arr[idx].is_readable(): idx = idx + 1 return idx - def get_sections(self): - """Get sections of the module""" + @functools.cached_property + def number_of_sections(self) -> int: if self.sect_attrs.has_member("nsections"): - num_sects = self.sect_attrs.nsections - else: - num_sects = self._get_sect_count(self.sect_attrs.grp) + return self.sect_attrs.nsections + + return self._get_sect_count(self.sect_attrs.grp) + + def get_sections(self) -> Iterable[interfaces.objects.ObjectInterface]: + """Get a list of section attributes for the given module.""" + + symbol_table_name = self.get_symbol_table_name() arr = self._context.object( - self.get_symbol_table_name() + constants.BANG + "array", + symbol_table_name + constants.BANG + "array", layer_name=self.vol.layer_name, offset=self.sect_attrs.attrs.vol.offset, subtype=self._context.symbol_space.get_type( - self.get_symbol_table_name() + constants.BANG + "module_sect_attr" + symbol_table_name + constants.BANG + "module_sect_attr" ), - count=num_sects, + count=self.number_of_sections, ) yield from arr @@ -309,6 +315,27 @@ class module(generic.GenericIntelProcess): raise AttributeError("Unable to get strtab") + @property + def section_typetab(self): + if self.has_member("kallsyms") and self.kallsyms.has_member("typetab"): + # kernels >= 4.5 8244062ef1e54502ef55f54cced659913f244c3e: kallsyms was added + # kernels >= 5.2 1c7651f43777cdd59c1aaa82c87324d3e7438c7b: types have its own array + return self.kallsyms.typetab + + raise AttributeError("Unable to get typetab section, it needs a kernel >= 5.2") + + def get_symbol_type(self, symbol, symbol_index): + if self.has_member("kallsyms") and self.kallsyms.has_member("typetab"): + # kernels >= 5.2 1c7651f43777cdd59c1aaa82c87324d3e7438c7b types have its own array + layer = self._context.layers[self.vol.layer_name] + sym_type = layer.read(self.section_typetab + symbol_index, 1) + sym_type = sym_type.decode("utf-8", errors="ignore") + else: + # kernels < 5.2 the type was stored in the st_info + sym_type = chr(symbol.st_info) + + return sym_type + class task_struct(generic.GenericIntelProcess): def is_valid(self) -> bool: From 88b32b080340ef9e94c7cab19334e536aadfb6c1 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sat, 1 Feb 2025 16:32:14 +1100 Subject: [PATCH 049/120] linux: task_struct object extension: Add helper to obtain the task state --- .../framework/symbols/linux/extensions/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 5364fcbda..24260a798 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -517,6 +517,15 @@ class task_struct(generic.GenericIntelProcess): else None ) + @property + def state(self): + if self.has_member("__state"): + return self.member("__state") + elif self.has_member("state"): + return self.member("state") + else: + raise AttributeError("Unsupported task_struct: Cannot find state") + def _get_task_start_time(self) -> datetime.timedelta: """Returns the task's monotonic start_time as a timedelta. From 0e0daffc45827f82fdd992f1573e9448987cdd5f Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sat, 1 Feb 2025 16:33:57 +1100 Subject: [PATCH 050/120] linux: Add kernel_symbol object extension --- .../framework/symbols/linux/__init__.py | 1 + .../symbols/linux/extensions/__init__.py | 61 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 53c7c4c88..f3880e4db 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -88,6 +88,7 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable): self.optional_set_type_class("maple_tree", extensions.maple_tree) self.optional_set_type_class("latch_tree_root", extensions.latch_tree_root) + self.optional_set_type_class("kernel_symbol", extensions.kernel_symbol) class LinuxUtilities(interfaces.configuration.VersionableInterface): diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 24260a798..41a7d9288 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -3100,3 +3100,64 @@ class latch_tree_root(objects.StructType): return None + +class kernel_symbol(objects.StructType): + + def _offset_to_ptr(self, off) -> int: + layer = self._context.layers[self.vol.layer_name] + long_mask = (1 << layer.bits_per_register) - 1 + return (self.vol.offset + off) & long_mask + + @property + def name(self) -> str: + if self.has_member("name_offset"): + # kernel >= 4.19 and CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y + # See 7290d58095712a89f845e1bca05334796dd49ed2 + name_offset = self._offset_to_ptr(self.name_offset) + elif self.has_member("name"): + # kernel < 4.19 or CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=n + name_offset = self.member("name") + else: + raise AttributeError("Unsupported kernel_symbol type implementation") + + layer = self._context.layers[self.vol.layer_name] + name_bytes = layer.read(name_offset, linux_constants.KSYM_NAME_LEN) + + idx = name_bytes.find(b"\x00") + if idx != -1: + name_bytes = name_bytes[:idx] + + return name_bytes.decode("utf-8", errors="ignore") + + @property + def value(self) -> int: + if self.has_member("value_offset"): + # kernel >= 4.19 and CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y + # See 7290d58095712a89f845e1bca05334796dd49ed2 + return self._offset_to_ptr(self.value_offset) + elif self.has_member("value"): + # kernel < 4.19 or CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=n + return self.member("value") + + raise AttributeError("Unsupported kernel_symbol type implementation") + + @property + def namespace(self) -> str: + if self.has_member("namespace_offset"): + # kernel >= 4.19 and CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y + # See 7290d58095712a89f845e1bca05334796dd49ed2 + namespace_offset = self._offset_to_ptr(self.namespace_offset) + elif self.has_member("namespace"): + # kernel < 4.19 or CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=n + namespace_offset = self.member("namespace") + else: + raise AttributeError("Unsupported kernel_symbol type implementation") + + layer = self._context.layers[self.vol.layer_name] + namespace_bytes = layer.read(namespace_offset, linux_constants.KSYM_NAME_LEN) + + idx = namespace_bytes.find(b"\x00") + if idx != -1: + namespace_bytes = namespace_bytes[:idx] + + return namespace_bytes.decode("utf-8", errors="ignore") From e2431eebdf99b806019203981f9c2cd76b919e39 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sat, 1 Feb 2025 16:35:40 +1100 Subject: [PATCH 051/120] linux: bpf_prog: Add methods to get the program address and its memory regions --- .../symbols/linux/extensions/__init__.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 41a7d9288..4aff317a5 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -2103,6 +2103,10 @@ class xdp_sock(objects.StructType): class bpf_prog(objects.StructType): + _BPF_PROG_CHUNK_SHIFT = 6 + _BPF_PROG_CHUNK_SIZE = 1 << _BPF_PROG_CHUNK_SHIFT + _BPF_PROG_CHUNK_MASK = ~(_BPF_PROG_CHUNK_SIZE - 1) + def get_type(self) -> Union[str, None]: """Returns a string with the eBPF program type""" @@ -2147,6 +2151,58 @@ class bpf_prog(objects.StructType): return self.aux.get_name() + def bpf_jit_binary_hdr_address(self) -> int: + """Return the jitted BPF program start address + Based on bpf_jit_binary_hdr() + + Returns: + The BPF program address + """ + vmlinux = linux.LinuxUtilities.get_module_from_volobj_type(self._context, self) + vmlinux_layer = vmlinux.context.layers[vmlinux.layer_name] + + # In 5.18 (33c9805860e584b194199cab1a1e81f4e6395408) <= kernels < 6.0 (1d5f82d9dd477d5c66e0214a68c3e4f308eadd6d) + # 'bpf_prog_aux' has a 'use_bpf_prog_pack' member + bpf_prog_aux_has_use_bpf_prog_pack = vmlinux.get_type( + "bpf_prog_aux" + ).has_member("use_bpf_prog_pack") + if bpf_prog_aux_has_use_bpf_prog_pack and self.aux.use_bpf_prog_pack: + long_mask = (1 << vmlinux_layer.bits_per_register) - 1 + addr_mask = self._BPF_PROG_CHUNK_MASK & long_mask + else: + addr_mask = vmlinux_layer.page_mask + + real_start = self.bpf_func + return real_start & addr_mask + + def get_address_region(self) -> Tuple[int, int]: + """Returns the start and end memory addresses of the BPF program. + Based on bpf_get_prog_addr_region() + + Returns: + A tuple with the addresses representing the memory range (start, end) of the BPF program. + """ + vmlinux = linux.LinuxUtilities.get_module_from_volobj_type(self._context, self) + vmlinux_layer = vmlinux.context.layers[vmlinux.layer_name] + # Based on bpf_get_prog_addr_region() + bpf_start_address = self.bpf_jit_binary_hdr_address() + + if vmlinux.has_type("bpf_binary_header"): + # kernels >= 3.11 314beb9bcabfd6b4542ccbced2402af2c6f6142a + bpf_binary_header = vmlinux.object( + object_type="bpf_binary_header", offset=bpf_start_address, absolute=True + ) + pages = bpf_binary_header.pages + else: + # kernels < 3.11 The first member is always the size + pages = vmlinux.object( + object_type="unsigned int", offset=bpf_start_address, absolute=True + ) + + bpf_end_address = bpf_start_address + pages * vmlinux_layer.page_size + + return bpf_start_address, bpf_end_address + class bpf_prog_aux(objects.StructType): def get_name(self) -> Union[str, None]: From 7e4b548e1735a4ea1e6c41b244f71e075a6790df Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sat, 1 Feb 2025 16:36:56 +1100 Subject: [PATCH 052/120] linux: task_struct object extension: Add method to get the task address space layer, even if its a kernel thread --- .../framework/symbols/linux/extensions/__init__.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 4aff317a5..fd5e85057 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -407,6 +407,19 @@ class task_struct(generic.GenericIntelProcess): self._context, dtb, config_prefix, preferred_name ) + def get_address_space_layer( + self, + ) -> Optional[interfaces.layers.TranslationLayerInterface]: + """Returns the task layer for this task's address space.""" + + task_layer_name = ( + self.vol.layer_name if self.is_kernel_thread else self.add_process_layer() + ) + if not task_layer_name: + return None + + return self._context.layers[task_layer_name] + def get_process_memory_sections( self, heap_only: bool = False ) -> Generator[Tuple[int, int], None, None]: From 6174687204840618da1784400913a5a7fa4b9ff4 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sat, 1 Feb 2025 16:39:45 +1100 Subject: [PATCH 053/120] linux: Introduce Kallsyms API --- .../framework/constants/linux/__init__.py | 21 + .../framework/symbols/linux/kallsyms.py | 1636 +++++++++++++++++ 2 files changed, 1657 insertions(+) create mode 100644 volatility3/framework/symbols/linux/kallsyms.py diff --git a/volatility3/framework/constants/linux/__init__.py b/volatility3/framework/constants/linux/__init__.py index f7f3faf87..99867b1fd 100644 --- a/volatility3/framework/constants/linux/__init__.py +++ b/volatility3/framework/constants/linux/__init__.py @@ -356,6 +356,27 @@ MODULE_MINIMUM_SIZE = 4096 # Kallsyms KSYM_NAME_LEN = 512 +NM_TYPES_DESC = { + "a": "Symbol is absolute and doesn't change during linking", + "b": "Symbol in the BSS section, typically holding zero-initialized or uninitialized data", + "c": "Symbol is common, typically holding uninitialized data", + "d": "Symbol is in the initialized data section", + "g": "Symbol is in an initialized data section for small objects", + "i": "Symbol is an indirect reference to another symbol", + "N": "Symbol is a debugging symbol", + "n": "Symbol is in a non-data, non-code, non-debug read-only section", + "p": "Symbol is in a stack unwind section", + "r": "Symbol is in a read only data section", + "s": "Symbol is in an uninitialized or zero-initialized data section for small objects", + "t": "Symbol is in the text (code) section", + "U": "Symbol is undefined", + "u": "Symbol is a unique global symbol", + "V": "Symbol is a weak object, with a default value", + "v": "Symbol is a weak object", + "W": "Symbol is a weak symbol but not marked as a weak object symbol, with a default value", + "w": "Symbol is a weak symbol but not marked as a weak object symbol", + "?": "Symbol type is unknown", +} # VMCOREINFO VMCOREINFO_MAGIC = b"VMCOREINFO\x00" diff --git a/volatility3/framework/symbols/linux/kallsyms.py b/volatility3/framework/symbols/linux/kallsyms.py new file mode 100644 index 000000000..5310c8393 --- /dev/null +++ b/volatility3/framework/symbols/linux/kallsyms.py @@ -0,0 +1,1636 @@ +# 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 +# +import dataclasses +import functools +import logging +from typing import Iterator, List, Optional, Tuple + +from volatility3.framework import constants, exceptions, interfaces +from volatility3.framework.configuration import requirements +from volatility3.framework.constants import linux as linux_constants +from volatility3.framework.objects import utility +from volatility3.framework.symbols import linux +from volatility3.plugins.linux import lsmod + +vollog = logging.getLogger(__name__) + + +@dataclasses.dataclass +class KASConfig: + """Kallsyms configuration class""" + + num_syms_address: int + names_address: int + token_table_address: int + token_index_address: int + offsets_address: int + relative_base_address: int + _stext: int + + # Usually not in VMCOREINFO, these are found during the bootstrap stage. + # If an ISF is available, they are fetched from there instead. + markers_address: int = None + addresses_address: int = None + _sinittext: int = None + _einittext: int = None + _etext: int = None + _end: int = None + mod_tree: int = None + module_addr_min: int = None + module_addr_max: int = None + start_ksymtab: int = None + stop_ksymtab: int = None + bpf_tree_address: int = None + seqs_of_names_address: int = None + + num_syms_type_size: int = None + markers_type_size: int = None + kernel_symbol_size: int = None + + @classmethod + def _get_symbol_address(cls, context, layer_name, module_name, symbol_name): + vmlinux = context.modules[module_name] + if not vmlinux.has_symbol(symbol_name): + return None + + layer = context.layers[layer_name] + address = vmlinux.get_symbol(symbol_name).address + address += layer.config["kernel_virtual_offset"] + return address + + @classmethod + def new_from_isf(cls, context, layer_name, module_name): + vmlinux = context.modules[module_name] + + # kallsyms_num_syms and kallsyms_markers types were updated from a unsigned long + # to unsigned int in 4.20 80ffbaa5b1bd98e80e3239a3b8cfda2da433009a + num_syms_type_size = vmlinux.get_symbol("kallsyms_num_syms").type.size + kernel_symbol_size = vmlinux.get_type("kernel_symbol").size + + def get_symbol_address(symbol_name): + return cls._get_symbol_address( + context, layer_name, module_name, symbol_name + ) + + kas_config = KASConfig( + num_syms_address=get_symbol_address("kallsyms_num_syms"), + names_address=get_symbol_address("kallsyms_names"), + token_table_address=get_symbol_address("kallsyms_token_table"), + token_index_address=get_symbol_address("kallsyms_token_index"), + offsets_address=get_symbol_address("kallsyms_offsets"), + relative_base_address=get_symbol_address("kallsyms_relative_base"), + markers_address=get_symbol_address("kallsyms_markers"), + addresses_address=get_symbol_address("kallsyms_addresses"), + _sinittext=get_symbol_address("_sinittext"), + _einittext=get_symbol_address("_einittext"), + _stext=get_symbol_address("_stext"), + _etext=get_symbol_address("_etext"), + _end=get_symbol_address("_end"), + mod_tree=get_symbol_address("mod_tree"), + module_addr_min=get_symbol_address("module_addr_min"), + module_addr_max=get_symbol_address("module_addr_max"), + start_ksymtab=get_symbol_address("__start___ksymtab"), + stop_ksymtab=get_symbol_address("__stop___ksymtab"), + bpf_tree_address=get_symbol_address("bpf_tree"), + seqs_of_names_address=get_symbol_address("kallsyms_seqs_of_names"), + num_syms_type_size=num_syms_type_size, + markers_type_size=num_syms_type_size, + kernel_symbol_size=kernel_symbol_size, + ) + return kas_config + + +class _KallsymsIO: + """Helper to interpret a memory address as a file pointer. + + For internal use within the Kallsyms API; external use is discouraged. + """ + + def __init__( + self, + context: interfaces.context.ContextInterface, + layer_name: str, + base=0, + endian="little", + ): + self._context = context + self._layer_name = layer_name + self._base = base + self._position = base + self._endian = endian + + def read(self, size: int) -> bytes: + """Return 'size' bytes from the current postion""" + layer = self._context.layers[self._layer_name] + buf = layer.read(offset=self._position, length=size) + self._position += size + return buf + + def read_str(self, size: int) -> str: + """Returns 'size' bytes as a string from the current position.""" + return self.read(size).decode() + + def read_int(self, size: int, signed: bool = False) -> int: + """Returns the integer stored in the current position using 'size' bytes. + Args: + size: Number of bytes to use for the int. + signed: Integer sign. + + Returns: + The integer stored in the current position. + """ + return int.from_bytes( + self.read(size), + byteorder=self._endian, + signed=signed, + ) + + def seek(self, offset: int) -> None: + """Seek the pointer to the given offset, based on the base address. + + Args: + offset: offset from the base address + """ + self._position = self._base + offset + + +@dataclasses.dataclass +class KASSymbolBasic: + name: str + type: str + + +@dataclasses.dataclass +class KASSymbol(KASSymbolBasic): + address: int + size: int + module_name: str + exported: bool = False + subsystem: str = None + + def __str__(self): + return ( + f"name:{self.name}, type:{self.type}, address:{self.address:#x}, " + f"size:{self.size}, exported:{self.exported}, subsystem:{self.subsystem}" + ) + + def set_exported_from_type(self) -> None: + """Updates the 'export' member based on the symbol's type. + + This method evaluates the symbol's type and sets the 'export' member + to indicate whether the object is exported. This code and Linux kernel follows + the nm symbol type logic. + """ + # As per the "nm" man page: + # If lowercase, the symbol is usually local; if uppercase, the symbol is + # global (external). There are however a few lowercase symbols that are shown + # for special global symbols ("u", "v" and "w"). + self.exported = bool(self.type.isupper() or self.type in ("u", "v", "w")) + + @functools.cached_property + def type_description(self) -> Optional[str]: + """Returns the interpreted meaning of the symbol type based on the nm tool. + + Returns: + A string with the type description. + """ + # If a symbol type exists with the original case, get it + symbol_type_description = linux_constants.NM_TYPES_DESC.get(self.type, None) + if symbol_type_description: + return symbol_type_description + + # Otherwise, use the lowercase version + symbol_type_description = linux_constants.NM_TYPES_DESC.get( + self.type.lower(), None + ) + return symbol_type_description + + +@dataclasses.dataclass +class KASFilter: + name: str + type: str + + +class Kallsyms(interfaces.configuration.VersionableInterface): + """Kallsyms API class""" + + _required_framework_version = (2, 19, 0) + _version = (1, 0, 0) + + # Internal kernel core constants + _CORE_SUBSYSTEM_NAME = "core" + _CORE_MODULE_NAME = "kernel" + + # Internal module constants + _MODULE_SUBSYSTEM_NAME = "module" + + # Internal FTrace constants + _FTRACE_SUBSYSTEM_NAME = "ftrace" + _FTRACE_MODULE_SYM_TYPE = "T" + _FTRACE_TRAMPOLINE_MODULE_NAME = "__builtin__ftrace" + _FTRACE_TRAMPOLINE_SYM = "ftrace_trampoline" + _FTRACE_TRAMPOLINE_SYM_TYPE = "t" + + # Internal BPF constants + _BPF_SUBSYSTEM_NAME = "bpf" + _BPF_MODULE_NAME = "bpf" + _BPF_SYM_TYPE = "t" + + def __init__( + self, + context: interfaces.context.ContextInterface, + layer_name: str, + module_name: str, + kas_config: KASConfig = None, + progress_callback: constants.ProgressCallback = None, + ) -> None: + """Initialize the Kallsyms API + + Args: + context: The context used to access memory layers and symbols + layer_name: The name of layer within the context in which the module exists + module_name: The name of the kernel module on which to operate + kas_config: The KAllSyms configuration + progress_callback: Method that is called periodically during scanning to + update progress + """ + super().__init__() + + self._assert_versions() + + self._context = context + self._layer_name = layer_name + self._module_name = module_name + self._kas_config = kas_config + self._progress_callback = progress_callback + if progress_callback and not callable(progress_callback): + raise TypeError("Progress_callback is not callable") + + if not kas_config: + self._kas_config = KASConfig.new_from_isf( + context=context, + layer_name=layer_name, + module_name=module_name, + ) + + layer = self._context.layers[self._layer_name] + # FIXME: The layer lacks this information. Could there be a better alternative? + self._endian = "little" if layer._entry_format[0] == "<" else "big" + self._long_size = layer.bits_per_register // 8 + + self._kallsyms_num_syms = None + self._kallsyms_relative_base = None + + self._kallsyms_token_index_address = None + self._kallsyms_offsets_address = None + self._kallsyms_names_io = _KallsymsIO( + context=self._context, + layer_name=self._layer_name, + base=self._kas_config.names_address, + endian=self._endian, + ) + + self._kallsyms_token_table_io = _KallsymsIO( + context=self._context, + layer_name=self._layer_name, + base=self._kas_config.token_table_address, + endian=self._endian, + ) + + self._bootstrap() + + @classmethod + def _assert_versions(cls) -> None: + """Verify versions of shared dependencies""" + lsmod_version_required = (2, 0, 0) + if not requirements.VersionRequirement.matches_required( + lsmod_version_required, lsmod.Lsmod.version + ): + raise exceptions.VolatilityException( + "Lsmod version not suitable: " + f"required {lsmod_version_required} found {lsmod.Lsmod.version}", + ) + + return None + + def _read_bytes(self, address: int, size: int) -> bytes: + layer = self._context.layers[self._layer_name] + return layer.read(address, size).decode() + + def _read_int(self, address: int, size: int, signed: bool = False) -> int: + layer = self._context.layers[self._layer_name] + return int.from_bytes( + layer.read(address, size), + byteorder=self._endian, + signed=signed, + ) + + def _bootstrap(self) -> None: + layer = self._context.layers[self._layer_name] + # kallsyms_num_syms and kallsyms_markers[] types were updated from a unsigned long + # to unsigned int in 4.20 80ffbaa5b1bd98e80e3239a3b8cfda2da433009a + self._kallsyms_num_syms = self._read_int( + self._kas_config.num_syms_address, + self._kas_config.num_syms_type_size, + signed=False, + ) + + if self._kas_config.relative_base_address: + # kernels >= 4.6 + self._kallsyms_relative_base = ( + self._read_int( + self._kas_config.relative_base_address, + self._long_size, + signed=False, + ) + & layer.address_mask + ) + + self._kallsyms_offsets_address = self._kas_config.offsets_address + self._kallsyms_token_index_address = self._kas_config.token_index_address + + # Preload the kallsyms_token_index array + short_size = 2 + self._kallsyms_token_index = [ + self._read_int( + self._kallsyms_token_index_address + index * short_size, + short_size, + signed=False, + ) + for index in range(256) + ] + + def _get_symbol( + self, + offset, + index, + filters: List[KASFilter] = None, + ) -> Optional[Tuple[KASSymbol, int]]: + kassymbolbasic, compressed_length = self._expand_symbol(offset, filters) + kassymbol = None + if kassymbolbasic: + sym_addr = self._get_symbol_address_by_index(index=index) + _, sym_size = self._get_symbol_pos(sym_addr) + + kassymbol = KASSymbol( + name=kassymbolbasic.name, + type=kassymbolbasic.type, + address=sym_addr, + size=sym_size, + module_name=self._CORE_MODULE_NAME, + subsystem=self._CORE_SUBSYSTEM_NAME, + ) + kassymbol.set_exported_from_type() + return kassymbol, compressed_length + + def get_core_symbols( + self, + progress_callback: constants.ProgressCallback = None, + ) -> Iterator[KASSymbol]: + """Yield each kernel core symbol + + Args: + progress_callback: Method that is called periodically during scanning to + update progress + + Based on kallsyms_on_each_symbol() + + Yields: + KASSymbol objects + """ + current_offset = 0 + for sym_idx in range(self._kallsyms_num_syms): + kassymbol, compressed_length = self._get_symbol(current_offset, sym_idx) + if kassymbol: + yield kassymbol + + if progress_callback: + progress_callback( + (sym_idx / self._kallsyms_num_syms) * 100, + "Populating Kallsyms core symbols", + ) + + current_offset += compressed_length + 1 + + def _expand_symbol( + self, + offset: int, + filters: List[KASFilter] = None, + ) -> Tuple[KASSymbolBasic, int]: + """Expand a compressed symbol using its offset in the stream + Based on kallsyms_expand_symbol() + + Args: + offset: Symbol offset in the kallsyms arrays. + filters: List of KASFilter filters + + Returns: + A tuple with a KASSymbolBasic object and the symbol name's compressed length. + """ + filters = filters if filters is not None else [] + type_filters = tuple(kassymbolfilter.type for kassymbolfilter in filters) + + self._kallsyms_names_io.seek(offset) + # The compressed symbol length is in the first byte + compressed_length = self._kallsyms_names_io.read_int(size=1) + if compressed_length & 0x80 != 0: + # kernels >= 6.1 73bbb94466fd3f8b313eeb0b0467314a262dddb3 + # MSB 1 means a 'big' symbol, we need an extra byte + lower_byte = compressed_length + upper_byte = self._kallsyms_names_io.read_int(size=1) + compressed_length = (upper_byte << 7) | (lower_byte & 0x7F) + + abort_decompression = False + sym_type = None + sym_name = "" + for _ in range(compressed_length): + token_index_index = self._kallsyms_names_io.read_int(size=1) + token_index = self._kallsyms_token_index[token_index_index] + self._kallsyms_token_table_io.seek(token_index) + token = self._kallsyms_token_table_io.read_str(1) + while token != "\x00": + if not sym_type: + sym_type = token + # We got the symbol type, we can abort this immediatelly + if type_filters and sym_type not in type_filters: + abort_decompression = True + break + else: + sym_name += token + for kassymbolfilter in filters: + if kassymbolfilter.type is not None: + if ( + sym_type == kassymbolfilter.type + and kassymbolfilter.name.startswith(sym_name) + ): + break + elif kassymbolfilter.name.startswith(sym_name): + break + + else: + if filters: + abort_decompression = True + + token = self._kallsyms_token_table_io.read_str(1) + + if abort_decompression: + break + + kassymbolbasic = ( + KASSymbolBasic(name=sym_name, type=sym_type) + if not abort_decompression + else None + ) + return kassymbolbasic, compressed_length + + def _get_symbol_address_by_index(self, index: int) -> int: + """Return symbol address based on the symbol index in the kallsyms arrays. + Based on kallsyms_sym_address() + + Args: + index: Symbol index + + Returns: + Symbol address + """ + if self._kallsyms_offsets_address: + # kernels >= 4.6 - Addresses are relative to kallsyms_relative_base + # It assumes: CONFIG_KALLSYMS_BASE_RELATIVE=y and CONFIG_KALLSYMS_ABSOLUTE_PERCPU=y + signed_int_size = 4 + sym_offset_ptr = self._kallsyms_offsets_address + (index * signed_int_size) + sym_addr = self._read_int(sym_offset_ptr, signed_int_size, signed=True) + + if sym_addr < 0: + # Negative offsets are relative to kallsyms_relative_base - 1 + return self._kallsyms_relative_base - 1 - sym_addr + + # Positive offsets are absolute values + return sym_addr + elif self._kas_config.addresses_address: + # kernels < 4.6 - Addresses are absolute + # unsigned long kallsyms_addresses[] + kallsyms_address = self._read_int( + self._kas_config.addresses_address + (index * self._long_size), + self._long_size, + signed=False, + ) + return kallsyms_address + else: + raise exceptions.VolatilityException("Unsupported kernel") + + @functools.lru_cache + def _get_symbol_pos(self, address: int) -> Tuple[int, int]: + """Returns the symbol position in the kallsyms arrays and its size.""" + low = 0 + high = self._kallsyms_num_syms + + while high - low > 1: + mid = low + (high - low) // 2 + if self._get_symbol_address_by_index(mid) <= address: + low = mid + else: + high = mid + + # Search for the first aliased symbol. *Aliased symbols* are symbols with the same address. + while low and self._get_symbol_address_by_index( + low - 1 + ) == self._get_symbol_address_by_index(low): + low -= 1 + + symbol_start = self._get_symbol_address_by_index(low) + symbol_end = 0 + + # Search for next non-aliased symbol. + for idx in range(low + 1, self._kallsyms_num_syms): + if self._get_symbol_address_by_index(idx) > symbol_start: + symbol_end = self._get_symbol_address_by_index(idx) + break + + # pylint: disable=protected-access + # If no next symbol is found, we default to using the end of the section + if not symbol_end: + if self._is_kernel_inittext(address): + symbol_end = self._kas_config._einittext + elif self._kas_config._end is not None: + # Assume CONFIG_KALLSYMS_ALL=y. Otherwise, symbol_end will be _etext + symbol_end = self._kas_config._end + else: + symbol_end = self._kas_config._etext + + symbol_size = symbol_end - symbol_start + + return low, symbol_size + + @functools.lru_cache + def _get_symbol_offset(self, index: int) -> int: + """Find the offset on the compressed stream given the index in the kallsyms array. + + Based on get_symbol_offset + + Returns: + Offset on the compressed stream + """ + + # Use the nearest marker, placed every 256 positions + kallsyms_markers_pos_ptr = ( + self._kas_config.markers_address + + (index >> 8) * self._kas_config.markers_type_size + ) + kallsyms_markers_pos = self._read_int( + kallsyms_markers_pos_ptr, self._kas_config.markers_type_size, signed=False + ) + name_addr = self._kas_config.names_address + kallsyms_markers_pos + + # Scan symbols sequentially until the target. Each symbol uses a + # [][ bytes of data] format, so we skip symbols by adding their length + # to the pointer value. + for _ in range(index & 0xFF): + compressed_length = self._read_int(name_addr, 1) + if compressed_length & 0x80 != 0: + # kernels >= 6.1 73bbb94466fd3f8b313eeb0b0467314a262dddb3 + # MSB 1 means a 'big' symbol, we need an extra byte + lower_byte = compressed_length + upper_byte = self._kallsyms_names_io.read_int(size=1) + compressed_length = (upper_byte << 7) | (lower_byte & 0x7F) + + name_addr += compressed_length + 1 + + return name_addr - self._kas_config.names_address + + def _is_kernel_inittext(self, addr: int) -> bool: + # pylint: disable=protected-access + if not (self._kas_config._sinittext and self._kas_config._einittext): + # We don't know + return False + + return self._kas_config._sinittext <= addr < self._kas_config._einittext + + def _is_kernel_text(self, addr: int) -> bool: + # pylint: disable=protected-access + return self._kas_config._stext <= addr < self._kas_config._etext + + def _is_core_ksym_addr(self, addr: int) -> bool: + return self._is_kernel_text(addr) or self._is_kernel_inittext(addr) + + def lookup_address(self, address: int) -> Optional[KASSymbol]: + """Search for a symbol by its memory address. + + This function scans kernel core, module symbols, BPF symbols, and Ftrace symbols + to locate the first symbol matching the specified address. Note that multiple + symbols (aliased symbols) can share the same memory address, so this method + returns the first match found. + + Based on kallsyms_lookup. + + Args: + address: The memory address to search for. + + Returns: + The matching symbol if found, or None if no match is found. + """ + layer = self._context.layers[self._layer_name] + address &= layer.address_mask + + kassymbol = self.core_lookup_address(address) + if not kassymbol: + kassymbol = self.module_lookup_address(address) + + if not kassymbol: + kassymbol = self.bpf_lookup_address(address) + + if not kassymbol: + kassymbol = self.ftrace_lookup_address(address) + + return kassymbol + + def core_lookup_address(self, address: int) -> Optional[KASSymbol]: + """Search for a symbol by its memory address within the kernel core. + + Based on kallsyms_lookup_buildid. + + Args: + address: The memory address to search for. + + Returns: + The matching symbol if found, or None if no match is found. + """ + layer = self._context.layers[self._layer_name] + address &= layer.address_mask + + if not self._is_core_ksym_addr(address): + return None + + pos, sym_size = self._get_symbol_pos(address) + offset = self._get_symbol_offset(pos) + sym_address = self._get_symbol_address_by_index(pos) + kassymbolbasic, _compressed_length = self._expand_symbol(offset) + + if not kassymbolbasic: + return None + + kas_symbol = KASSymbol( + name=kassymbolbasic.name, + type=kassymbolbasic.type, + address=sym_address, + size=sym_size, + module_name=self._CORE_MODULE_NAME, + subsystem=self._CORE_SUBSYSTEM_NAME, + ) + kas_symbol.set_exported_from_type() + return kas_symbol + + def _is_symbol_exported( + self, + name: int, + address: int, + module: Optional[interfaces.objects.ObjectInterface] = None, + ) -> bool: + """Check if the address belongs to an exported symbol. + If a module object is provided, it searches in that module symbols. + Otherwise, it searches in the global symbols. + + Bases on is_exported + + Args: + name: Symbol name + address: Symbol address + module: Module object. Defaults to None. + + Returns: + True if the symbol is exported; otherwise, returns False + """ + if module: + if module.num_syms <= 0: + return False + + start_mod_ksymtab = module.syms + stop_mod_ksymtab = ( + start_mod_ksymtab + + module.num_syms * self._kas_config.kernel_symbol_size + ) + kernel_symbol = self._find_exported_symbol_in_range( + name, start_mod_ksymtab, stop_mod_ksymtab + ) + else: + # Search the not GPL modules + kernel_symbol = self._find_exported_symbol_in_range( + name, + self._kas_config.start_ksymtab, + self._kas_config.stop_ksymtab, + ) + + return kernel_symbol is not None and kernel_symbol.value == address + + def _elfsym_to_kassymbol( + self, + module: interfaces.objects.ObjectInterface, + elf_sym_obj: interfaces.objects.ObjectInterface, + elf_sym_index: int, + subsystem: str = None, + ) -> Optional[KASSymbol]: + """Returns a KASSymbol from a ElfSym + + Args: + module: Module object + elf_sym_obj: ElfSym object + elf_sym_index: ElfSym index + subsystem: Name of the sub-subtem: core, module, bpf, ftrace, etc + + Returns: + A KASSymbol object + """ + layer = self._context.layers[self._layer_name] + sym_name = elf_sym_obj.get_name() + if not sym_name: + return None + + # Normalize sym.st_value offset, which is an address pointing to the symbol value + sym_address = elf_sym_obj.st_value & layer.address_mask + sym_type = module.get_symbol_type(elf_sym_obj, elf_sym_index) + + kas_symbol = KASSymbol( + name=sym_name, + type=sym_type, + address=sym_address, + size=elf_sym_obj.st_size, + module_name=module.get_name(), + exported=False, + subsystem=subsystem, + ) + kas_symbol.set_exported_from_type() + return kas_symbol + + def _is_module_ksym_address(self, address: int) -> bool: + return self._modules_address_min <= address <= self._modules_address_max + + def module_lookup_address( + self, + address: int, + module: Optional[interfaces.objects.ObjectInterface] = None, + ) -> Optional[KASSymbol]: + """Search for a symbol within kernel modules based on its memory address. + If a module object is provided, it will only search in that module. Otherwise, + it will try to first find the module to where the provided address belong to. + + Based on module_address_lookup. + + Args: + address: The memory address of the symbol to search for + module [optional]: The module to search within. If not provided, the module + containing the address will be automatically determined + + Returns: + The matching KASSymbol if found; otherwise, returns None + """ + if not self._is_module_ksym_address(address): + return None + + module = module or self.get_module_by_address(address) + if not module: + return None + + kassymbol = self._find_address_in_module_symbols(module, address) + if not kassymbol: + return None + + return kassymbol + + def _find_address_in_module_symbols( + self, + module: interfaces.objects.ObjectInterface, + address: int, + ) -> Optional[KASSymbol]: + """Find the symbol corresponding to a given address within a module. + + Based on find_kallsyms_symbol + + Args: + module: The module where the address belongs to + address: The memory address to search for + + Returns: + The matching KASSymbol if found; otherwise, returns None + """ + layer = self._context.layers[self._layer_name] + for elf_sym_idx, elf_sym in enumerate(module.get_symbols()): + if not elf_sym.get_name(): + continue + + sym_address_start = elf_sym.st_value & layer.address_mask + sym_address_end = sym_address_start + elf_sym.st_size + + if sym_address_start <= address < sym_address_end: + return self._elfsym_to_kassymbol( + module, elf_sym, elf_sym_idx, subsystem=self._MODULE_SUBSYSTEM_NAME + ) + + return None + + @functools.lru_cache + def _get_modules_memory_boundaries(self) -> Tuple[int, int]: + """Determine the boundaries of the module allocation area + + Returns: + A tuple containing the minimum and maximum addresses for the kernel module + allocation area. + """ + + if self._kas_config.mod_tree: + # Kernel >= 5.19 58d208de3e8d87dbe196caf0b57cc58c7a3836ca + mod_tree_address = self._kas_config.mod_tree + vmlinux = self._context.modules[self._module_name] + mod_tree = vmlinux.object( + object_type="mod_tree_root", + offset=mod_tree_address, + absolute=True, + ) + addr_min, addr_max = mod_tree.addr_min, mod_tree.addr_max + elif self._kas_config.module_addr_min and self._kas_config.module_addr_max: + # 2.6.27 <= kernel < 5.19 3a642e99babe0617febb6f402e1e063479f489db + kas_config = self._kas_config + addr_min, addr_max = kas_config.module_addr_min, kas_config.module_addr_max + else: + raise exceptions.VolatilityException( + "Cannot find the module memory allocation area. Unsupported kernel" + ) + + layer = self._context.layers[self._layer_name] + return addr_min & layer.address_mask, addr_max & layer.address_mask + + @functools.cached_property + def _modules_address_min(self): + address_min, _address_max = self._get_modules_memory_boundaries() + return address_min + + @functools.cached_property + def _modules_address_max(self): + _address_min, address_max = self._get_modules_memory_boundaries() + return address_max + + def get_module_by_address( + self, address: int + ) -> Optional[interfaces.objects.ObjectInterface]: + """Searches for the module that contains the given memory address within its range. + It uses a latch tree for optimized address range searching. + + Based on __module_address() + + Args: + address: The module memory address to search for. + + Returns: + The matching module if found; otherwise, returns None + """ + if not self._is_module_ksym_address(address): + return None + + return self._search_module_by_address(address) + + @functools.lru_cache + def _get_type_cache(self, name: str): + vmlinux = self._context.modules[self._module_name] + return vmlinux.get_type(name) + + def _mod_tree_comp( + self, address: int, latch_tree_node: interfaces.objects.ObjectInterface + ) -> int: + vmlinux = self._context.modules[self._module_name] + + module_memory_mtn_offset = self._get_type_cache( + "module_memory" + ).relative_child_offset("mtn") + mod_tree_node_mod_offset = self._get_type_cache( + "mod_tree_node" + ).relative_child_offset("mod") + + module_memory_offset = ( + latch_tree_node.vol.offset + + module_memory_mtn_offset + + mod_tree_node_mod_offset + ) + + module_memory = vmlinux.object( + object_type="module_memory", + offset=module_memory_offset, + absolute=True, + ) + start = module_memory.base + end = start + module_memory.size + + if address < start: + return -1 + elif address >= end: + return 1 + else: + return 0 + + def _search_module_by_address( + self, address: int + ) -> Optional[interfaces.objects.ObjectInterface]: + """Searches for the module that contains the given memory address within its range. + It uses a latch tree for optimized address range searching. + + Based on mod_find + + Args: + address: The module memory address to search for + + Returns: + The matching module if found; otherwise, returns None + """ + vmlinux = self._context.modules[self._module_name] + if self._kas_config.mod_tree: + mod_tree_address = self._kas_config.mod_tree + mod_tree = vmlinux.object( + object_type="mod_tree_root", + offset=mod_tree_address, + absolute=True, + ) + latch_tree_root = mod_tree.root + latch_tree_node = latch_tree_root.find(address, self._mod_tree_comp) + if latch_tree_node: + mod_tree_node = linux.LinuxUtilities.container_of( + latch_tree_node.vol.offset, "mod_tree_node", "node", vmlinux + ) + module_ptr = mod_tree_node.mod + if not module_ptr.is_readable(): + vollog.error("Something went wrong") + return None + + return module_ptr.dereference() + else: + raise NotImplementedError("FIXME") + + return None + + def _find_exported_symbol_in_range( + self, name: str, start: int, stop: int + ) -> Optional[interfaces.objects.ObjectInterface]: + """Find an exported symbol within a specified range of kernel symbols. + + Based on lookup_exported_symbol + + Args: + name: Symbol name + start: Start address + stop: Stop address + + Returns: + The matching kernel_symbol object if found, or None if no match is found. + """ + + num_elems = (stop - start) // self._kas_config.kernel_symbol_size + + return self._search_kernel_symbol_object_by_name( + name, + base=start, + num_elems=num_elems, + ) + + def _cmp_kernel_symbol_name( + self, + name: str, + kernel_symbol: interfaces.objects.ObjectInterface, + ) -> int: + return self._cmp_symbol_name(name, kernel_symbol.name) + + def _cmp_symbol_name( + self, + name: str, + other: str, + ) -> int: + if name == other: + return 0 + elif name < other: + return -1 + else: + return 1 + + def _search_kernel_symbol_object_by_name( + self, name: str, base: int, num_elems: int + ) -> Optional[interfaces.objects.ObjectInterface]: + """Search a kernel_symbol by name using binary search. + + Based on bsearch / __inline_bsearch() + + Args: + name: Symbol name + base: Base address + num_elems: Number of elements + + Returns: + A kernel_symbol object + """ + vmlinux = self._context.modules[self._module_name] + while num_elems > 0: + pivot = base + (num_elems // 2) * self._kas_config.kernel_symbol_size + + kernel_symbol_pivot = vmlinux.object( + object_type="kernel_symbol", + offset=pivot, + absolute=True, + ) + + result = self._cmp_kernel_symbol_name(name, kernel_symbol_pivot) + if result == 0: + return kernel_symbol_pivot + elif result > 0: + base = pivot + self._kas_config.kernel_symbol_size + num_elems -= 1 + + num_elems = num_elems // 2 + + return None + + def get_modules_symbols(self, name: str = None) -> Iterator[KASSymbol]: + """Yield each symbol from the kernel modules. + This function iterates over the symbols of the kernel modules and yields them as + KASSymbol objects. + + name (optional): If specified, the symbol name used to filter the symbols. + + Yields: + KASSymbol objects + """ + layer = self._context.layers[self._layer_name] + for module in lsmod.Lsmod.list_modules(self._context, self._module_name): + module_name = utility.array_to_string(module.name) + for elf_sym_idx, elf_sym_obj in enumerate(module.get_symbols()): + sym_name = elf_sym_obj.get_name() + if not sym_name: + continue + + if name and name != sym_name: + continue + + # Normalize sym.st_value offset, which is an address pointing to the symbol value + sym_address = elf_sym_obj.st_value & layer.address_mask + sym_size = elf_sym_obj.st_size + sym_type = module.get_symbol_type(elf_sym_obj, elf_sym_idx) + is_exported = self._is_symbol_exported(sym_name, sym_address, module) + sym_type = sym_type.upper() if is_exported else sym_type.lower() + + yield KASSymbol( + name=sym_name, + type=sym_type, + address=sym_address, + size=sym_size, + exported=is_exported, + module_name=module_name, + subsystem=self._MODULE_SUBSYSTEM_NAME, + ) + + def _ftrace_mod_get_symbols(self, address: int = None) -> Iterator[KASSymbol]: + """Yield each symbol from the ftrace modules. + This function iterates over the symbols of the ftrace modules and yields them as + KASSymbol objects. + + Based on ftrace_mod_get_kallsym + + Args: + address (optional): Address to filter symbols by + + Yields: + KASSymbol objects + """ + vmlinux = self._context.modules[self._module_name] + layer = self._context.layers[self._layer_name] + if not ( + vmlinux.has_type("ftrace_mod_map") and vmlinux.has_type("ftrace_mod_func") + ): + # kernel < 4.15 aba4b5c22cbac296f4081a0476d0c55828f135b4 + vollog.warning( + "Unsupported Ftrace kallsyms implementation. Ignore this if it's a kernel < 4.15" + ) + return None + + symbol_table_name = vmlinux.symbol_table_name + ftrace_mod_map_symname = f"{symbol_table_name}{constants.BANG}ftrace_mod_map" + ftrace_mod_func_symname = f"{symbol_table_name}{constants.BANG}ftrace_mod_func" + ftrace_mod_maps = vmlinux.object_from_symbol("ftrace_mod_maps") + for mod_map in ftrace_mod_maps.to_list(ftrace_mod_map_symname, "list"): + for mod_func in mod_map.funcs.to_list(ftrace_mod_func_symname, "list"): + sym_name = utility.pointer_to_string( + mod_func.name, count=linux_constants.KSYM_NAME_LEN + ) + sym_addr = mod_func.ip & layer.address_mask + sym_size = mod_func.size + if address is not None and not ( + sym_addr <= address < sym_addr + sym_size + ): + continue + + module_name = utility.array_to_string(mod_map.mod.name) + kas_symbol = KASSymbol( + name=sym_name, + type=self._FTRACE_MODULE_SYM_TYPE, + address=sym_addr, + size=sym_size, + module_name=module_name, + subsystem=self._FTRACE_SUBSYSTEM_NAME, + ) + kas_symbol.set_exported_from_type() + yield kas_symbol + + def _ftrace_get_trampoline_symbols( + self, address: int = None + ) -> Iterator[KASSymbol]: + """Yield each symbol from the ftrace trampoline. + + Based on ftrace_get_trampoline_kallsym + + Args: + address (optional): Address to filter symbols by + + Yields: + KASSymbol objects + """ + # See kernel's ftrace_get_trampoline_kallsym() + vmlinux = self._context.modules[self._module_name] + if not vmlinux.has_type("ftrace_ops"): + # kernels < 2.6.27 16444a8a40d4c7b4f6de34af0cae1f76a4f6c901 + return None + + if not vmlinux.has_symbol("ftrace_ops_trampoline_list"): + # kernels < 5.9 fc0ea795f53c8d7040fa42471f74fe51d78d0834 + return None + + symbol_table_name = vmlinux.symbol_table_name + ftrace_ops_symname = f"{symbol_table_name}{constants.BANG}ftrace_ops" + ftrace_ops_trampoline_list = vmlinux.object_from_symbol( + "ftrace_ops_trampoline_list" + ) + + for ftrace_op in ftrace_ops_trampoline_list.to_list(ftrace_ops_symname, "list"): + sym_name = self._FTRACE_TRAMPOLINE_SYM + sym_addr = ftrace_op.trampoline + sym_size = ftrace_op.trampoline_size + + if address is not None and not (sym_addr <= address < sym_addr + sym_size): + continue + + kas_symbol = KASSymbol( + name=sym_name, + type=self._FTRACE_TRAMPOLINE_SYM_TYPE, + address=sym_addr, + size=sym_size, + module_name=self._FTRACE_TRAMPOLINE_MODULE_NAME, + subsystem=self._FTRACE_SUBSYSTEM_NAME, + ) + kas_symbol.set_exported_from_type() + yield kas_symbol + + def get_ftrace_symbols(self) -> Iterator[KASSymbol]: + """Yield each kernel ftrace symbol + + Yields: + KASSymbol objects + """ + yield from self._ftrace_mod_get_symbols() + yield from self._ftrace_get_trampoline_symbols() + + def get_bpf_symbols(self) -> Iterator[KASSymbol]: + """Yield each kernel BPF symbol + + Based on bpf_get_kallsym() + + Yields: + KASSymbol objects + """ + vmlinux = self._context.modules[self._module_name] + if vmlinux.has_type("bpf_ksym"): + # kernels >= 5.8 + list_type, list_head_member = "bpf_ksym", "lnode" + elif vmlinux.has_type("bpf_prog_aux"): + # 3.18 <= kernels < 5.8 + list_type, list_head_member = "bpf_prog_aux", "ksym_lnode" + else: + # kernels < 3.18 + vollog.warning( + "Unsupported BPF kallsysms implementation. Don't worry if kernel < 3.18" + ) + return None + + symbol_table_name = vmlinux.symbol_table_name + list_type_symname = f"{symbol_table_name}{constants.BANG}{list_type}" + + layer = self._context.layers[self._layer_name] + + # Even when bpf_jit_kallsyms is disabled (/proc/sys/net/core/bpf_jit_kallsyms = 0), + # this function will still be able to gather the symbols. + bpf_kallsyms_list = vmlinux.object_from_symbol("bpf_kallsyms") + for elem in bpf_kallsyms_list.to_list(list_type_symname, list_head_member): + # See kernel's bpf_get_kallsym() + if list_type == "bpf_ksym": + # kernels >= 5.8 + bpf_ksym = elem + sym_name = utility.array_to_string(bpf_ksym.name) + sym_addr = bpf_ksym.start + sym_size = bpf_ksym.end - bpf_ksym.start + else: + # list_type == "bpf_prog_aux" 3.18 <= kernels < 5.8 + bpf_prog_aux = elem + bpf_prog = bpf_prog_aux.prog + sym_name = bpf_prog.get_name() + sym_addr = bpf_prog.bpf_func + sym_start, sym_end = bpf_prog.get_address_region() + sym_size = sym_end - sym_start + + # The following are also hardcoded in the Linux kernel + # see kernel's get_ksymbol_bpf(), bpf_get_kallsym() and BPF_SYM_ELF_TYPE + module_name = self._BPF_MODULE_NAME + sym_type = self._BPF_SYM_TYPE + sym_addr &= layer.address_mask + + kas_symbol = KASSymbol( + name=sym_name, + type=sym_type, + address=sym_addr, + size=sym_size, + module_name=module_name, + subsystem=self._BPF_SUBSYSTEM_NAME, + ) + kas_symbol.set_exported_from_type() + yield kas_symbol + + def get_all_symbols(self) -> Iterator[KASSymbol]: + """Enumerates each kallsym symbol + + Yields: + KASSymbol objects + """ + yield from self.get_core_symbols() + yield from self.get_modules_symbols() + yield from self.get_ftrace_symbols() + yield from self.get_bpf_symbols() + + def bpf_lookup_address(self, address: int) -> Optional[KASSymbol]: + """Search for a BPF symbol based on its memory address. + + Based on bpf_address_lookup() and __bpf_address_lookup() + + Args: + address: The memory address to search for + + Returns: + The matching KASSymbol if found; otherwise, returns None + """ + vmlinux = self._context.modules[self._module_name] + + if vmlinux.has_type("bpf_ksym"): + # kernels >= 5.7 535911c80ad4f5801700e9d827a1985bbff41519 + bpf_ksym = self._find_bpf_ksym(address) + if not bpf_ksym: + return None + symbol_start = bpf_ksym.start + symbol_end = bpf_ksym.end + sym_name = utility.array_to_string(bpf_ksym.name) + sym_size = symbol_end - symbol_start + elif vmlinux.has_type("latch_tree_root") and vmlinux.get_type( + "bpf_prog_aux" + ).child_template("ksym_tnode"): + # For 4.11 <= kernels < 5.7 + # latch_tree_root was added in kernels 4.2 ade3f510f93a5613b672febe88eff8ea7f1c63b7 + # BPF kallsyms support was added in kernels 4.11 74451e66d516c55e309e8d89a4a1e7596e46aacd + bpf_prog = self._find_bpf_prog(address) + if not bpf_prog: + return None + + symbol_start, symbol_end = bpf_prog.get_addr_region() + sym_name = bpf_prog.get_name() + sym_size = symbol_end - symbol_start + else: + # kernel < 4.11 + vollog.warning( + "Unsupported BPF kallsyms implementation. Ignore this if it's a kernel < 4.11" + ) + return None + + layer = self._context.layers[self._layer_name] + symbol_start &= layer.address_mask + + kas_symbol = KASSymbol( + name=sym_name, + type=self._BPF_SYM_TYPE, + address=symbol_start, + size=sym_size, + module_name=self._BPF_MODULE_NAME, + subsystem=self._BPF_SUBSYSTEM_NAME, + ) + kas_symbol.set_exported_from_type() + return kas_symbol + + def _find_bpf_prog( + self, address: int + ) -> Optional[interfaces.objects.ObjectInterface]: + """Search for a BPF program based on its address. + Based on __bpf_address_lookup & bpf_prog_kallsyms_find() for kernels < 5.7 + + Args: + address: The BPF symbol address to search for + + Returns: + A bpf_prog object if found; otherwise, returns None. + """ + vmlinux = self._context.modules[self._module_name] + if not self._kas_config.bpf_tree_address: + return None + + bpf_latch_tree_root = vmlinux.object( + object_type="latch_tree_root", + offset=self._kas_config.bpf_tree_address, + absolute=True, + ) + latch_tree_node = bpf_latch_tree_root.find( + address, self._bpf_tree_comp_bpf_prog_aux + ) + + if not latch_tree_node: + return None + + bpf_prog_aux = linux.LinuxUtilities.container_of( + latch_tree_node.vol.offset, "bpf_prog_aux", "ksym_tnode", vmlinux + ) + bpf_prog = bpf_prog_aux.prog + return bpf_prog + + def _bpf_tree_comp_bpf_prog_aux( + self, address: int, latch_tree_node: interfaces.objects.ObjectInterface + ) -> int: + """Comparison function used by _find_bpf_prog() + Based on bpf_tree_comp for kernels < 5.7 + + Args: + address: The memory address to search for + latch_tree_node: A latch tree node + + Returns: + 0: equal, >0: key is greater, <0: key is less than this bpf_prog + """ + vmlinux = self._context.modules[self._module_name] + layer = self._context.layers[self._layer_name] + bpf_prog_aux = linux.LinuxUtilities.container_of( + latch_tree_node.vol.offset, "bpf_prog_aux", "ksym_tnode", vmlinux + ) + bpf_prog = bpf_prog_aux.prog + bpf_start, bpf_end = bpf_prog.get_address_region() + bpf_start &= layer.address_mask + bpf_end &= layer.address_mask + + if address < bpf_start: + return -1 + elif address > bpf_end: + # Keep 'key > end' instead of 'key >= end'. This detects return addresses + # within the program when the final instruction in a stack trace is a call. + return 1 + else: + return 0 + + def _find_bpf_ksym( + self, address: int + ) -> Optional[interfaces.objects.ObjectInterface]: + """Search for the respective bpf_ksym based on a symbol address. + Based on __bpf_address_lookup & bpf_ksym_find() for kernels >= 5.7 + + Args: + address: The memory address to search for + + Returns: + A bpf_ksym object if found; otherwise, returns None. + """ + vmlinux = self._context.modules[self._module_name] + if not self._kas_config.bpf_tree_address: + return None + + bpf_latch_tree_root = vmlinux.object( + object_type="latch_tree_root", + offset=self._kas_config.bpf_tree_address, + absolute=True, + ) + latch_tree_node = bpf_latch_tree_root.find( + address, self._bpf_tree_comp_bpf_ksym + ) + if not latch_tree_node: + return None + + bpf_ksym = linux.LinuxUtilities.container_of( + latch_tree_node.vol.offset, "bpf_ksym", "tnode", vmlinux + ) + return bpf_ksym + + def _bpf_tree_comp_bpf_ksym( + self, address: int, latch_tree_node: interfaces.objects.ObjectInterface + ) -> int: + """Comparison function used by _find_bpf_ksym. + + Based on bpf_tree_comp in kernels >= 5.7 + + Args: + address: The memory address to search for + latch_tree_node: A latch tree node + + Returns: + 0: equal, >0: key is greater, <0: key is less than this bpf_prog + """ + # + vmlinux = self._context.modules[self._module_name] + layer = self._context.layers[self._layer_name] + bpf_ksym = linux.LinuxUtilities.container_of( + latch_tree_node.vol.offset, "bpf_ksym", "tnode", vmlinux + ) + bpf_start = bpf_ksym.start & layer.address_mask + bpf_end = bpf_ksym.end & layer.address_mask + + if address < bpf_start: + return -1 + elif address > bpf_end: + # Keep 'address > bpf_end' instead of 'address >= bpf_end'. This detects return + # addresses within the program when the final instruction in a stack trace is a call. + return 1 + else: + return 0 + + def ftrace_lookup_address(self, address: int) -> Optional[KASSymbol]: + """Search for a ftrace symbol based on its address. + + Based on ftrace_mod_address_lookup() + + Args: + address: The memory address to search for + + Returns: + The matching KASSymbol if found, or None if no match is found. + """ + + # Filter by address and return only the first matching result. + for kassymbol in self._ftrace_mod_get_symbols(address): + return kassymbol + + for kassymbol in self._ftrace_get_trampoline_symbols(address): + return kassymbol + + return None + + def _core_lookup_name_slow(self, name) -> Optional[KASSymbol]: + """Search a core symbol by name + + Based on kallsyms_lookup_name in kernels < 6.2 + + Args: + name: The symbol name to search for. + + Returns: + A KASSymbol object + """ + # kernels < 6.2 60443c88f3a89fd303a9e8c0e84895910675c316 + current_offset = 0 + for sym_idx in range(self._kallsyms_num_syms): + kassymbol, compressed_length = self._get_symbol(current_offset, sym_idx) + if kassymbol and name == kassymbol.name: + return kassymbol + + current_offset += compressed_length + 1 + + return None + + @functools.cached_property + def _kallsyms_seqs_of_names(self): + vmlinux = self._context.modules[self._module_name] + symbol_table_name = vmlinux.symbol_table_name + unsigned_char_symname = symbol_table_name + constants.BANG + "unsigned char" + # See 19bd8981dc2ee35fdc81ab1b0104b607c917d470: 3 bytes per index + array_size = 3 * self._kallsyms_num_syms + kallsyms_seqs_of_names = vmlinux.object( + object_type="array", + offset=self._kas_config.seqs_of_names_address, + subtype=vmlinux.get_type(unsigned_char_symname), + count=array_size, + absolute=True, + ) + return kallsyms_seqs_of_names + + def _get_symbol_seq(self, index: int) -> int: + # See 19bd8981dc2ee35fdc81ab1b0104b607c917d470 + bits = 3 + seq = 0 + for i in range(bits): + seq = (seq << 8) | self._kallsyms_seqs_of_names[bits * index + i] + return seq + + def _get_symbol_by_index(self, index) -> Tuple[KASSymbolBasic, int]: + seq = self._get_symbol_seq(index) + offset = self._get_symbol_offset(seq) + kassymbolbasic, _compressed_length = self._expand_symbol(offset) + return kassymbolbasic + + def _lookup_name_index(self, name: str) -> Optional[int]: + # based on kallsyms_lookup_names + high = self._kallsyms_num_syms - 1 + low = 0 + + while low <= high: + mid = (low + high) // 2 + kassymbolbasic = self._get_symbol_by_index(mid) + if not kassymbolbasic: + return None + + ret = self._cmp_symbol_name(name, kassymbolbasic.name) + if ret > 0: + low = mid + 1 + elif ret < 0: + high = mid - 1 + else: + break + + if low > high: + # Not found + return None + + low = mid + while low: + kassymbolbasic = self._get_symbol_by_index(low - 1) + if not kassymbolbasic: + return None + if self._cmp_symbol_name(name, kassymbolbasic.name) != 0: + return low + low -= 1 + + return None + + def _core_lookup_name_fast(self, name: str) -> Optional[KASSymbol]: + """Search a core symbol by name + + Based on kallsyms_lookup_name in kernels >= 6.2 + + Args: + name: The symbol name to search for + + Returns: + A KASSymbol object + """ + # kernels >= 6.2 60443c88f3a89fd303a9e8c0e84895910675c316 + index = self._lookup_name_index(name) + if not index: + return None + + seq = self._get_symbol_seq(index) + offset = self._get_symbol_offset(seq) + kassymbolbasic, _compressed_length = self._expand_symbol(offset) + sym_address = self._get_symbol_address_by_index(seq) + _seq, sym_size = self._get_symbol_pos(sym_address) + + kas_symbol = KASSymbol( + name=kassymbolbasic.name, + type=kassymbolbasic.type, + address=sym_address, + size=sym_size, + module_name=self._CORE_MODULE_NAME, + subsystem=self._CORE_SUBSYSTEM_NAME, + ) + kas_symbol.set_exported_from_type() + return kas_symbol + + def _kallsyms_lookup_name_modules(self, name: str) -> Optional[KASSymbol]: + """_summary_ + + Based on module_kallsyms_lookup_name + + Args: + name: The symbol name to search for. + + Returns: + A KASSymbol object + """ + for kassymbol in self.get_modules_symbols(name): + if name == kassymbol.name: + # First match only + return kassymbol + return None + + def lookup_name(self, name: str) -> Optional[KASSymbol]: + """Search symbols by name. + WARNING: This function is super slow. The kernel does not index the symbols by + name, so the it is a linear search. + + Based on kallsyms_lookup_name + + Args: + name: The symbol name to search for. + + Returns: + A KASSymbol object + """ + if self._kas_config.seqs_of_names_address: + # kernels >= 6.2: + # 60443c88f3a89fd303a9e8c0e84895910675c316 and 19bd8981dc2ee35fdc81ab1b0104b607c917d470 + kassymbol = self._core_lookup_name_fast(name) + else: + # kernels < 6.2 + kassymbol = self._core_lookup_name_slow(name) + + if kassymbol: + return kassymbol + + return self._kallsyms_lookup_name_modules(name) From 97135a992a4eb01c07f3bd0eae50a172ef96389b Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sat, 1 Feb 2025 16:42:18 +1100 Subject: [PATCH 054/120] linux: add Kallsyms plugin --- .../framework/plugins/linux/kallsyms.py | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 volatility3/framework/plugins/linux/kallsyms.py diff --git a/volatility3/framework/plugins/linux/kallsyms.py b/volatility3/framework/plugins/linux/kallsyms.py new file mode 100644 index 000000000..9c48ed8b8 --- /dev/null +++ b/volatility3/framework/plugins/linux/kallsyms.py @@ -0,0 +1,89 @@ +# 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 +# +import logging +from typing import List, Union + +from volatility3.framework import interfaces, renderers +from volatility3.framework.interfaces import plugins +from volatility3.framework.configuration import requirements +from volatility3.framework.renderers import format_hints +from volatility3.framework.constants import architectures +from volatility3.framework.symbols.linux import kallsyms + + +vollog = logging.getLogger(__name__) + + +class Kallsyms(plugins.PluginInterface): + """Kallsyms symbols enumeration plugin""" + + _required_framework_version = (2, 19, 0) + + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=architectures.LINUX_ARCHS, + ), + requirements.VersionRequirement( + name="Kallsyms", component=kallsyms.Kallsyms, version=(1, 0, 0) + ), + ] + + def _get_symbol_size( + self, kassymbol: kallsyms.KASSymbol + ) -> Union[int, interfaces.renderers.BaseAbsentValue]: + # Symbol sizes are calculated using the address of the next non-aliased + # symbol or the end of the kernel text area _end/_etext. However, some kernel + # symbols live beyond that area. For these symbols, the size will be negative, + # resulting in incorrect values. Unfortunately, there isn't much that can be done + # in such cases. + # See comments on .init.scratch in arch/x86/kernel/vmlinux.lds.S for details + return kassymbol.size if kassymbol.size >= 0 else renderers.NotAvailableValue() + + def _generator(self): + module_name = self.config["kernel"] + vmlinux = self.context.modules[module_name] + + kas = kallsyms.Kallsyms( + context=self.context, + layer_name=vmlinux.layer_name, + module_name=self.config["kernel"], + ) + + for kassymbol in kas.get_all_symbols(): + # Symbol sizes are calculated using the address of the next non-aliased + # symbol or the end of the kernel text area _end/_etext. However, some kernel + # symbols are located beyond that area, which causes this method to fail for + # the last symbol, resulting in a negative size. + # See comments on .init.scratch in arch/x86/kernel/vmlinux.lds.S for details + symbol_size = self._get_symbol_size(kassymbol) + fields = ( + format_hints.Hex(kassymbol.address), + kassymbol.type, + symbol_size, + kassymbol.exported, + kassymbol.subsystem, + kassymbol.module_name, + kassymbol.name, + kassymbol.type_description or renderers.NotAvailableValue(), + ) + yield 0, fields + + def run(self): + headers = [ + ("Addr", format_hints.Hex), + ("Type", str), + ("Size", int), + ("Exported", bool), + ("SubSystem", str), + ("ModuleName", str), + ("SymbolName", str), + ("Description", str), + ] + return renderers.TreeGrid(headers, self._generator()) From d665fcc2b5596c5571d302f44f529515b0465822 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sat, 1 Feb 2025 17:37:24 +1100 Subject: [PATCH 055/120] linux: kallsyms plugin: Enable filtering of symbols by subsystem --- .../framework/plugins/linux/kallsyms.py | 87 +++++++++++++++---- 1 file changed, 68 insertions(+), 19 deletions(-) diff --git a/volatility3/framework/plugins/linux/kallsyms.py b/volatility3/framework/plugins/linux/kallsyms.py index 9c48ed8b8..c97fafbcf 100644 --- a/volatility3/framework/plugins/linux/kallsyms.py +++ b/volatility3/framework/plugins/linux/kallsyms.py @@ -16,7 +16,12 @@ vollog = logging.getLogger(__name__) class Kallsyms(plugins.PluginInterface): - """Kallsyms symbols enumeration plugin""" + """Kallsyms symbols enumeration plugin. + + If no arguments are provided, all symbols are included: core, modules, ftrace, and BPF. + Alternatively, you can use any combination of --only-core, --only-modules, --only-ftrace, + and --only-bpf to customize the output. + """ _required_framework_version = (2, 19, 0) @@ -33,6 +38,30 @@ class Kallsyms(plugins.PluginInterface): requirements.VersionRequirement( name="Kallsyms", component=kallsyms.Kallsyms, version=(1, 0, 0) ), + requirements.BooleanRequirement( + name="only_core", + description="Include core symbols", + default=False, + optional=True, + ), + requirements.BooleanRequirement( + name="only_modules", + description="Include module symbols", + default=False, + optional=True, + ), + requirements.BooleanRequirement( + name="only_ftrace", + description="Include ftrace symbols", + default=False, + optional=True, + ), + requirements.BooleanRequirement( + name="only_bpf", + description="Include bpf symbols", + default=False, + optional=True, + ), ] def _get_symbol_size( @@ -56,24 +85,44 @@ class Kallsyms(plugins.PluginInterface): module_name=self.config["kernel"], ) - for kassymbol in kas.get_all_symbols(): - # Symbol sizes are calculated using the address of the next non-aliased - # symbol or the end of the kernel text area _end/_etext. However, some kernel - # symbols are located beyond that area, which causes this method to fail for - # the last symbol, resulting in a negative size. - # See comments on .init.scratch in arch/x86/kernel/vmlinux.lds.S for details - symbol_size = self._get_symbol_size(kassymbol) - fields = ( - format_hints.Hex(kassymbol.address), - kassymbol.type, - symbol_size, - kassymbol.exported, - kassymbol.subsystem, - kassymbol.module_name, - kassymbol.name, - kassymbol.type_description or renderers.NotAvailableValue(), - ) - yield 0, fields + only_core = self.config.get("only_core", False) + only_modules = self.config.get("only_modules", False) + only_ftrace = self.config.get("only_ftrace", False) + only_bpf = self.config.get("only_bpf", False) + + symbols_flags = (only_core, only_modules, only_ftrace, only_bpf) + if not any(symbols_flags): + only_core = only_modules = only_ftrace = only_bpf = True + + symbol_geneators = [] + if only_core: + symbol_geneators.append(kas.get_core_symbols()) + if only_modules: + symbol_geneators.append(kas.get_modules_symbols()) + if only_ftrace: + symbol_geneators.append(kas.get_ftrace_symbols()) + if only_bpf: + symbol_geneators.append(kas.get_bpf_symbols()) + + for symbols_generator in symbol_geneators: + for kassymbol in symbols_generator: + # Symbol sizes are calculated using the address of the next non-aliased + # symbol or the end of the kernel text area _end/_etext. However, some kernel + # symbols are located beyond that area, which causes this method to fail for + # the last symbol, resulting in a negative size. + # See comments on .init.scratch in arch/x86/kernel/vmlinux.lds.S for details + symbol_size = self._get_symbol_size(kassymbol) + fields = ( + format_hints.Hex(kassymbol.address), + kassymbol.type, + symbol_size, + kassymbol.exported, + kassymbol.subsystem, + kassymbol.module_name, + kassymbol.name, + kassymbol.type_description or renderers.NotAvailableValue(), + ) + yield 0, fields def run(self): headers = [ From fbba4c76751f07ff5a9127ebb8128a89c5c5b58e Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sat, 1 Feb 2025 17:53:39 +1100 Subject: [PATCH 056/120] linux: new pscallstack plugin: add a poor man's process stack call enumeration plugin to showcase the power of the kallsyms API --- .../framework/plugins/linux/pscallstack.py | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 volatility3/framework/plugins/linux/pscallstack.py diff --git a/volatility3/framework/plugins/linux/pscallstack.py b/volatility3/framework/plugins/linux/pscallstack.py new file mode 100644 index 000000000..468a7e1ba --- /dev/null +++ b/volatility3/framework/plugins/linux/pscallstack.py @@ -0,0 +1,196 @@ +# 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 +# +import logging +import dataclasses +from typing import List, Iterator + +from volatility3.framework import interfaces, renderers, exceptions +from volatility3.framework.configuration import requirements +from volatility3.framework.interfaces import plugins +from volatility3.framework.renderers import format_hints +from volatility3.framework.constants import architectures +from volatility3.framework.objects import utility +from volatility3.framework.symbols.linux import kallsyms +from volatility3.plugins.linux import pslist + +vollog = logging.getLogger(__name__) + + +@dataclasses.dataclass +class StackEntry: + position: int + address: int + value: int + name: str = renderers.NotAvailableValue() + type: str = renderers.NotAvailableValue() + module: str = renderers.NotAvailableValue() + + +class PsCallStack(plugins.PluginInterface): + """Enumerates the call stack of each task""" + + _required_framework_version = (2, 19, 0) + + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=architectures.LINUX_ARCHS, + ), + requirements.VersionRequirement( + name="Kallsyms", component=kallsyms.Kallsyms, version=(1, 0, 0) + ), + requirements.PluginRequirement( + name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + ), + requirements.ListRequirement( + name="pid", + description="Filter on specific process IDs", + element_type=int, + optional=True, + ), + requirements.BooleanRequirement( + name="unresolved", + description="Include unresolved stack values", + default=False, + optional=True, + ), + ] + + @classmethod + def get_task_callstack( + cls, + context: interfaces.context.ContextInterface, + module_name: str, + task: interfaces.objects.ObjectInterface, + kas: kallsyms.Kallsyms = None, + include_unresolved=False, + ) -> Iterator[StackEntry]: + """Retrieves the call stack for a given task + + Args: + context: The context used to access memory layers and symbols + module_name: The name of the kernel module on which to operate + task: The task object whose stack is being retrieved + kas: Kallsyms instance for symbol resolution. If not provided or None, a new + instance will be created each time + include_unresolved: If True, includes stack values that could not be resolved + to known symbols. Defaults to False. + + Yields: + StackEntry objects + """ + task_layer = task.get_address_space_layer() + if not task_layer: + return None + + vmlinux = context.modules[module_name] + vmlinux_layer = context.layers[vmlinux.layer_name] + + if not kas: + kas = kallsyms.Kallsyms( + context=context, + layer_name=vmlinux.layer_name, + module_name=module_name, + ) + + pointer_size = vmlinux.get_type("pointer").size + + thread_size_order = 2 # Safe since kernel 3.15 + # thread_size_order +=1 # If CONFIG_KASAN is enabled in kernels >= 4.0, default: DISABLED + # thread_size_order +=1 # If CONFIG_KASAN_EXTRA is enabled in kernels >= 4.19, default: DISABLED + thread_size = vmlinux_layer.page_size << thread_size_order + task_base_of_stack = vmlinux_layer.canonicalize(task.stack) + task_top_of_stack = task_base_of_stack + thread_size + + byte_order = task.files.vol.data_format.byteorder + rsp_start = task.thread.sp + if not (task_base_of_stack <= rsp_start < task_top_of_stack): + raise exceptions.VolatilityException( + f"Invalid stack pointer {rsp_start:#x} for task {task.pid}" + ) + + current_sp = rsp_start + idx = 0 + while current_sp < task_top_of_stack: + stack_value_bytes = task_layer.read(current_sp, pointer_size) + stack_value = int.from_bytes(stack_value_bytes, byteorder=byte_order) + + kassymbol = kas.lookup_address(stack_value) + if kassymbol: + module_name = kassymbol.module_name or renderers.NotAvailableValue() + yield StackEntry( + position=idx, + address=current_sp, + value=stack_value, + name=kassymbol.name, + type=kassymbol.type, + module=module_name, + ) + elif include_unresolved: + yield StackEntry( + position=idx, + address=current_sp, + value=stack_value, + ) + + idx += 1 + current_sp += pointer_size + + def _generator(self): + module_name = self.config["kernel"] + vmlinux = self.context.modules[module_name] + + kas = kallsyms.Kallsyms( + context=self.context, + layer_name=vmlinux.layer_name, + module_name=self.config["kernel"], + ) + + include_unresolved = self.config.get("unresolved", False) + + pids = self.config.get("pid", None) + filter_func = pslist.PsList.create_pid_filter(pids) + for task in pslist.PsList.list_tasks( + self.context, vmlinux.name, filter_func=filter_func, include_threads=True + ): + task_name = utility.array_to_string(task.comm) + + for stack_entry in self.get_task_callstack( + context=self.context, + module_name=vmlinux.name, + task=task, + kas=kas, + include_unresolved=include_unresolved, + ): + fields = ( + task.pid, + task_name, + stack_entry.position, + format_hints.Hex(stack_entry.address), + format_hints.Hex(stack_entry.value), + stack_entry.name, + stack_entry.type, + stack_entry.module, + ) + yield 0, fields + + def run(self): + return renderers.TreeGrid( + [ + ("TID", int), + ("Comm", str), + ("Position", int), + ("Address", format_hints.Hex), + ("Value", format_hints.Hex), + ("Name", str), + ("Type", str), + ("Module", str), + ], + self._generator(), + ) From 4469ab49c80f5756fe150246c5645a569476294f Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sat, 1 Feb 2025 18:12:51 +1100 Subject: [PATCH 057/120] linux: kallsyms api: move the unsupport implementation message to the info log level --- volatility3/framework/symbols/linux/kallsyms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/symbols/linux/kallsyms.py b/volatility3/framework/symbols/linux/kallsyms.py index 5310c8393..a113dd6b1 100644 --- a/volatility3/framework/symbols/linux/kallsyms.py +++ b/volatility3/framework/symbols/linux/kallsyms.py @@ -1101,7 +1101,7 @@ class Kallsyms(interfaces.configuration.VersionableInterface): vmlinux.has_type("ftrace_mod_map") and vmlinux.has_type("ftrace_mod_func") ): # kernel < 4.15 aba4b5c22cbac296f4081a0476d0c55828f135b4 - vollog.warning( + vollog.info( "Unsupported Ftrace kallsyms implementation. Ignore this if it's a kernel < 4.15" ) return None @@ -1208,7 +1208,7 @@ class Kallsyms(interfaces.configuration.VersionableInterface): list_type, list_head_member = "bpf_prog_aux", "ksym_lnode" else: # kernels < 3.18 - vollog.warning( + vollog.info( "Unsupported BPF kallsysms implementation. Don't worry if kernel < 3.18" ) return None @@ -1303,7 +1303,7 @@ class Kallsyms(interfaces.configuration.VersionableInterface): sym_size = symbol_end - symbol_start else: # kernel < 4.11 - vollog.warning( + vollog.info( "Unsupported BPF kallsyms implementation. Ignore this if it's a kernel < 4.11" ) return None From 9175c1b1e96240ebb2947309043ec0416e44da2d Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sat, 1 Feb 2025 20:43:01 +1100 Subject: [PATCH 058/120] linux: kallsyms api: ensure all the addresses are in the same range --- volatility3/framework/symbols/linux/kallsyms.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/kallsyms.py b/volatility3/framework/symbols/linux/kallsyms.py index a113dd6b1..493a82538 100644 --- a/volatility3/framework/symbols/linux/kallsyms.py +++ b/volatility3/framework/symbols/linux/kallsyms.py @@ -495,6 +495,7 @@ class Kallsyms(interfaces.configuration.VersionableInterface): Returns: Symbol address """ + layer = self._context.layers[self._layer_name] if self._kallsyms_offsets_address: # kernels >= 4.6 - Addresses are relative to kallsyms_relative_base # It assumes: CONFIG_KALLSYMS_BASE_RELATIVE=y and CONFIG_KALLSYMS_ABSOLUTE_PERCPU=y @@ -507,7 +508,7 @@ class Kallsyms(interfaces.configuration.VersionableInterface): return self._kallsyms_relative_base - 1 - sym_addr # Positive offsets are absolute values - return sym_addr + return sym_addr & layer.address_mask elif self._kas_config.addresses_address: # kernels < 4.6 - Addresses are absolute # unsigned long kallsyms_addresses[] @@ -516,7 +517,7 @@ class Kallsyms(interfaces.configuration.VersionableInterface): self._long_size, signed=False, ) - return kallsyms_address + return kallsyms_address & layer.address_mask else: raise exceptions.VolatilityException("Unsupported kernel") From 2af8b95ea43a701289e1738e3833f36df9b9e960 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sat, 1 Feb 2025 20:49:42 +1100 Subject: [PATCH 059/120] linux: module object extension: add method to know the module memory boundaries based on its symbols addresses and sizes --- .../symbols/linux/extensions/__init__.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index fd5e85057..cd7dc5e9d 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -270,6 +270,43 @@ class module(generic.GenericIntelProcess): sym_address = elf_sym_obj.st_value & layer.address_mask yield (sym_name, sym_address) + @functools.lru_cache + def get_module_address_boundaries(self) -> Tuple[int, int]: + """Return the module address boundaries based on its symbol addresses""" + + if not self.section_strtab or self.num_symtab < 1: + return None + + elf_table_name = self.get_elf_table_name() + symbol_table_name = self.get_symbol_table_name() + + is_64bit = symbols.symbol_table_is_64bit(self._context, symbol_table_name) + sym_name = "Elf64_Sym" if is_64bit else "Elf32_Sym" + sym_type = self._context.symbol_space.get_type( + elf_table_name + constants.BANG + sym_name + ) + elf_syms = self._context.object( + symbol_table_name + constants.BANG + "array", + layer_name=self.vol.layer_name, + offset=self.section_symtab, + subtype=sym_type, + count=self.num_symtab, + ) + # They should be sorted, but just in case + elf_syms_sorted = sorted(elf_syms, key=lambda x: x.st_value) + + layer = self._context.layers[self.vol.layer_name] + + # The first elf_sym is null + first_symbol = elf_syms_sorted[1] + last_symbol = elf_syms_sorted[-1] + minimum_address = first_symbol.st_value & layer.address_mask + maximum_address = ( + last_symbol.st_value & layer.address_mask + last_symbol.st_size + ) + + return minimum_address, maximum_address + def get_symbol(self, wanted_sym_name) -> Optional[int]: """Get symbol address for a given symbol name""" for sym_name, sym_address in self.get_symbols_names_and_addresses(): From 0ba331f34c4dbfe67997b3155402745081c5baeb Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sat, 1 Feb 2025 20:53:57 +1100 Subject: [PATCH 060/120] linux: kallsyms api: Added support for older kernels lacking mod_tree. Enhanced module address search performance by using the new module memory boundaries function --- .../framework/symbols/linux/kallsyms.py | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/volatility3/framework/symbols/linux/kallsyms.py b/volatility3/framework/symbols/linux/kallsyms.py index 493a82538..264c13d3e 100644 --- a/volatility3/framework/symbols/linux/kallsyms.py +++ b/volatility3/framework/symbols/linux/kallsyms.py @@ -788,15 +788,27 @@ class Kallsyms(interfaces.configuration.VersionableInterface): if not self._is_module_ksym_address(address): return None - module = module or self.get_module_by_address(address) + module = module or self._get_module_by_address(address) if not module: + # This may occur if the kernel lacks the mod_tree implementation. + for ( + cur_module, + minimum_address, + maximum_address, + ) in self._module_memory_region: + if minimum_address <= address < maximum_address: + module = cur_module + break + + if not module: + # We couldn't find the module return None kassymbol = self._find_address_in_module_symbols(module, address) - if not kassymbol: - return None + if kassymbol: + return kassymbol - return kassymbol + return None def _find_address_in_module_symbols( self, @@ -814,6 +826,15 @@ class Kallsyms(interfaces.configuration.VersionableInterface): Returns: The matching KASSymbol if found; otherwise, returns None """ + # Before walking all the symbols, ensure the address belongs to this module + module_boundaries = module.get_module_address_boundaries() + if not module_boundaries: + return None + + minimum_address, maximum_address = module_boundaries + if not (minimum_address <= address < maximum_address): + return None + layer = self._context.layers[self._layer_name] for elf_sym_idx, elf_sym in enumerate(module.get_symbols()): if not elf_sym.get_name(): @@ -829,6 +850,18 @@ class Kallsyms(interfaces.configuration.VersionableInterface): return None + @functools.cached_property + def _module_memory_region( + self, + ) -> List[Tuple[interfaces.objects.ObjectInterface, int, int]]: + modules_region = [] + for module in lsmod.Lsmod.list_modules(self._context, self._module_name): + minimum_address, maximum_address = module.get_module_address_boundaries() + module_region = module, minimum_address, maximum_address + modules_region.append(module_region) + + return modules_region + @functools.lru_cache def _get_modules_memory_boundaries(self) -> Tuple[int, int]: """Determine the boundaries of the module allocation area @@ -870,7 +903,7 @@ class Kallsyms(interfaces.configuration.VersionableInterface): _address_min, address_max = self._get_modules_memory_boundaries() return address_max - def get_module_by_address( + def _get_module_by_address( self, address: int ) -> Optional[interfaces.objects.ObjectInterface]: """Searches for the module that contains the given memory address within its range. @@ -957,12 +990,10 @@ class Kallsyms(interfaces.configuration.VersionableInterface): ) module_ptr = mod_tree_node.mod if not module_ptr.is_readable(): - vollog.error("Something went wrong") + vollog.warning("Modules latch tree seems corrupt") return None return module_ptr.dereference() - else: - raise NotImplementedError("FIXME") return None From 1da15d13529f372a7afdef396aaeae0b7d57fac5 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sat, 1 Feb 2025 20:54:51 +1100 Subject: [PATCH 061/120] linux: pscallstack plugin: Fix canonical addresses --- volatility3/framework/plugins/linux/pscallstack.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/pscallstack.py b/volatility3/framework/plugins/linux/pscallstack.py index 468a7e1ba..8931ca581 100644 --- a/volatility3/framework/plugins/linux/pscallstack.py +++ b/volatility3/framework/plugins/linux/pscallstack.py @@ -122,11 +122,13 @@ class PsCallStack(plugins.PluginInterface): stack_value = int.from_bytes(stack_value_bytes, byteorder=byte_order) kassymbol = kas.lookup_address(stack_value) + sp_address = current_sp & vmlinux_layer.address_mask + stack_value &= vmlinux_layer.address_mask if kassymbol: module_name = kassymbol.module_name or renderers.NotAvailableValue() yield StackEntry( position=idx, - address=current_sp, + address=sp_address, value=stack_value, name=kassymbol.name, type=kassymbol.type, @@ -135,7 +137,7 @@ class PsCallStack(plugins.PluginInterface): elif include_unresolved: yield StackEntry( position=idx, - address=current_sp, + address=sp_address, value=stack_value, ) From df310c0d92cc274ef6bc49d97decaacca31f0553 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sat, 1 Feb 2025 12:27:49 +0000 Subject: [PATCH 062/120] Manually revert 74a834b This should resolve the issue experienced #1590 was trying to resolve. --- volatility3/framework/symbols/linux/extensions/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 7103a2068..c24b495d8 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1574,9 +1574,7 @@ class vfsmount(objects.StructType): 'True' if the kernel lacks the 'mount' struct, typically indicating kernel < 3.3. """ - return (not self._context.symbol_space.has_type("mount")) and self.has_member( - "mnt_parent" - ) + return self.has_member("mnt_parent") def is_equal(self, vfsmount_ptr) -> bool: """Helper to make sure it is comparing two pointers to 'vfsmount'. From 80f6d0dad685bbcf2b7195c5d077d5ce83840a59 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sun, 2 Feb 2025 11:33:34 +1100 Subject: [PATCH 063/120] linux: Add kallsyms plugin testcase --- test/test_volatility.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/test_volatility.py b/test/test_volatility.py index 8676d1f3e..5b57dd59d 100644 --- a/test/test_volatility.py +++ b/test/test_volatility.py @@ -842,6 +842,27 @@ def test_linux_hidden_modules(image, volatility, python): assert out.count(b"\n") >= 4 +def test_linux_kallsyms(image, volatility, python): + rc, out, _err = runvol_plugin( + "linux.kallsyms.Kallsyms", + image, + volatility, + python, + pluginargs=["--only-modules"], + ) + # linux-sample-1.bin has no hidden modules. + # This validates that plugin requirements are met and exceptions are not raised. + assert rc == 0 + assert out.count(b"\n") > 1000 + + # Addr Type Size Exported SubSystem ModuleName SymbolName Description + # 0xffffa009eba9 t 28 False module usbcore usb_mon_register Symbol is in the text (code) section + assert re.search( + rb"0xffffa009eba9\s+t\s+28\s+False\s+module\s+usbcore\s+usb_mon_register\s+Symbol is in the text \(code\) section", + out, + ) + + # MAC From 5347926ab6b0800fa90673bed09a830fb4d1a73c Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sun, 2 Feb 2025 11:34:31 +1100 Subject: [PATCH 064/120] linux: Add pscallstack plugin testcase --- test/test_volatility.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/test_volatility.py b/test/test_volatility.py index 5b57dd59d..bc63ab356 100644 --- a/test/test_volatility.py +++ b/test/test_volatility.py @@ -863,6 +863,26 @@ def test_linux_kallsyms(image, volatility, python): ) +def test_linux_pscallstack(image, volatility, python): + rc, out, _err = runvol_plugin( + "linux.pscallstack.PsCallStack", + image, + volatility, + python, + pluginargs=["--pid", "1"], + ) + + assert rc == 0 + assert out.count(b"\n") > 30 + + # TID Comm Position Address Value Name Type Module + # 1 init 39 0x88001f999a40 0xffff81109039 do_select T kernel + assert re.search( + rb"1\s+init\s+39\s+0x88001f999a40.*?0xffff81109039\s+do_select\s+T\s+kernel", + out, + ) + + # MAC From bf0271cd5765d656cc9a3844bb8b5c7e69e650ec Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sun, 2 Feb 2025 12:29:01 +1100 Subject: [PATCH 065/120] linux: kallsyms plugin: simplify argument by removing the "only-" prefix, which was also causing confusion. --- test/test_volatility.py | 2 +- .../framework/plugins/linux/kallsyms.py | 34 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/test/test_volatility.py b/test/test_volatility.py index bc63ab356..d5b59b15c 100644 --- a/test/test_volatility.py +++ b/test/test_volatility.py @@ -848,7 +848,7 @@ def test_linux_kallsyms(image, volatility, python): image, volatility, python, - pluginargs=["--only-modules"], + pluginargs=["--modules"], ) # linux-sample-1.bin has no hidden modules. # This validates that plugin requirements are met and exceptions are not raised. diff --git a/volatility3/framework/plugins/linux/kallsyms.py b/volatility3/framework/plugins/linux/kallsyms.py index c97fafbcf..c575c54e7 100644 --- a/volatility3/framework/plugins/linux/kallsyms.py +++ b/volatility3/framework/plugins/linux/kallsyms.py @@ -19,8 +19,8 @@ class Kallsyms(plugins.PluginInterface): """Kallsyms symbols enumeration plugin. If no arguments are provided, all symbols are included: core, modules, ftrace, and BPF. - Alternatively, you can use any combination of --only-core, --only-modules, --only-ftrace, - and --only-bpf to customize the output. + Alternatively, you can use any combination of --core, --modules, --ftrace, and --bpf + to customize the output. """ _required_framework_version = (2, 19, 0) @@ -39,26 +39,26 @@ class Kallsyms(plugins.PluginInterface): name="Kallsyms", component=kallsyms.Kallsyms, version=(1, 0, 0) ), requirements.BooleanRequirement( - name="only_core", + name="core", description="Include core symbols", default=False, optional=True, ), requirements.BooleanRequirement( - name="only_modules", + name="modules", description="Include module symbols", default=False, optional=True, ), requirements.BooleanRequirement( - name="only_ftrace", + name="ftrace", description="Include ftrace symbols", default=False, optional=True, ), requirements.BooleanRequirement( - name="only_bpf", - description="Include bpf symbols", + name="bpf", + description="Include BPF symbols", default=False, optional=True, ), @@ -85,23 +85,23 @@ class Kallsyms(plugins.PluginInterface): module_name=self.config["kernel"], ) - only_core = self.config.get("only_core", False) - only_modules = self.config.get("only_modules", False) - only_ftrace = self.config.get("only_ftrace", False) - only_bpf = self.config.get("only_bpf", False) + include_core = self.config.get("core", False) + include_modules = self.config.get("modules", False) + include_ftrace = self.config.get("ftrace", False) + include_bpf = self.config.get("bpf", False) - symbols_flags = (only_core, only_modules, only_ftrace, only_bpf) + symbols_flags = (include_core, include_modules, include_ftrace, include_bpf) if not any(symbols_flags): - only_core = only_modules = only_ftrace = only_bpf = True + include_core = include_modules = include_ftrace = include_bpf = True symbol_geneators = [] - if only_core: + if include_core: symbol_geneators.append(kas.get_core_symbols()) - if only_modules: + if include_modules: symbol_geneators.append(kas.get_modules_symbols()) - if only_ftrace: + if include_ftrace: symbol_geneators.append(kas.get_ftrace_symbols()) - if only_bpf: + if include_bpf: symbol_geneators.append(kas.get_bpf_symbols()) for symbols_generator in symbol_geneators: From 144a7cc3f58ede3828f4f50d6e05abe6082fc91c Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 2 Feb 2025 16:07:27 +0000 Subject: [PATCH 066/120] Add error checking when accessing the KVO Also bumps hivescan to take a module name rather than a layer_name/symbol_table combo. --- .../framework/configuration/requirements.py | 5 +-- volatility3/framework/interfaces/context.py | 8 ++++ .../framework/plugins/windows/bigpools.py | 8 +++- .../framework/plugins/windows/callbacks.py | 38 +++++++++++++++---- .../framework/plugins/windows/handles.py | 16 ++++++-- volatility3/framework/plugins/windows/info.py | 10 +++-- .../framework/plugins/windows/modules.py | 8 +++- .../framework/plugins/windows/pslist.py | 8 +++- .../framework/plugins/windows/psscan.py | 9 +++-- .../plugins/windows/registry/hivelist.py | 14 ++++--- .../plugins/windows/registry/hivescan.py | 31 +++++++-------- volatility3/framework/plugins/windows/ssdt.py | 5 +-- .../plugins/windows/unloadedmodules.py | 8 +++- .../framework/plugins/windows/vadinfo.py | 8 +++- .../framework/plugins/windows/virtmap.py | 3 +- .../symbols/windows/extensions/__init__.py | 29 ++++++++++---- 16 files changed, 143 insertions(+), 65 deletions(-) diff --git a/volatility3/framework/configuration/requirements.py b/volatility3/framework/configuration/requirements.py index 3e3608000..5dd8cc9b5 100644 --- a/volatility3/framework/configuration/requirements.py +++ b/volatility3/framework/configuration/requirements.py @@ -638,13 +638,10 @@ class ModuleRequirement( self.add_requirement( TranslationLayerRequirement(name="layer_name", architectures=architectures) ) - self.add_requirement(SymbolTableRequirement(name="symbol_table_name")) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: - return [ - IntRequirement(name="offset"), - ] + return interfaces.context.ModuleInterface.get_requirements() def unsatisfied( self, context: "interfaces.context.ContextInterface", config_path: str diff --git a/volatility3/framework/interfaces/context.py b/volatility3/framework/interfaces/context.py index 723f2fd46..e7c3e579f 100644 --- a/volatility3/framework/interfaces/context.py +++ b/volatility3/framework/interfaces/context.py @@ -158,6 +158,14 @@ class ModuleInterface(interfaces.configuration.ConfigurableInterface): super().__init__(context, config_path) self._module_name = name + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # Can't include the translation layer without knowing the architectures + return [ + SymbolTableRequirement(name="symbol_table_name"), + IntRequirement(name="offset"), + ] + @property def _layer_name(self) -> str: return self.config["layer_name"] diff --git a/volatility3/framework/plugins/windows/bigpools.py b/volatility3/framework/plugins/windows/bigpools.py index 393c2a417..f6217ac50 100644 --- a/volatility3/framework/plugins/windows/bigpools.py +++ b/volatility3/framework/plugins/windows/bigpools.py @@ -21,7 +21,7 @@ class BigPools(interfaces.plugins.PluginInterface): """List big page pools.""" _required_framework_version = (2, 0, 0) - _version = (1, 1, 0) + _version = (1, 1, 1) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -66,7 +66,11 @@ class BigPools(interfaces.plugins.PluginInterface): Yields: A big page pool object """ - kvo = context.layers[layer_name].config["kernel_virtual_offset"] + kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) big_page_table_offset = ntkrnlmp.get_symbol("PoolBigPageTable").address diff --git a/volatility3/framework/plugins/windows/callbacks.py b/volatility3/framework/plugins/windows/callbacks.py index 414a8814a..9d54f4331 100644 --- a/volatility3/framework/plugins/windows/callbacks.py +++ b/volatility3/framework/plugins/windows/callbacks.py @@ -28,7 +28,7 @@ class Callbacks(interfaces.plugins.PluginInterface): """Lists kernel callbacks and notification routines.""" _required_framework_version = (2, 0, 0) - _version = (2, 0, 0) + _version = (2, 0, 1) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -361,7 +361,11 @@ class Callbacks(interfaces.plugins.PluginInterface): A name, location and optional detail string """ - kvo = context.layers[layer_name].config["kernel_virtual_offset"] + kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) is_vista_or_later = versions.is_vista_or_later( @@ -418,7 +422,11 @@ class Callbacks(interfaces.plugins.PluginInterface): Lists all registry callbacks from the old format via the CmpCallBackVector. """ - kvo = context.layers[layer_name].config["kernel_virtual_offset"] + kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) full_type_name = ( callback_table_name + constants.BANG + "_EX_CALLBACK_ROUTINE_BLOCK" @@ -465,7 +473,11 @@ class Callbacks(interfaces.plugins.PluginInterface): Lists all registry callbacks via the CallbackListHead. """ - kvo = context.layers[layer_name].config["kernel_virtual_offset"] + kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) full_type_name = callback_table_name + constants.BANG + "_CM_CALLBACK_ENTRY" @@ -506,7 +518,11 @@ class Callbacks(interfaces.plugins.PluginInterface): A name, location and optional detail string """ - kvo = context.layers[layer_name].config["kernel_virtual_offset"] + kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) if ntkrnlmp.has_symbol("CmpCallBackVector") and ntkrnlmp.has_symbol( @@ -562,7 +578,11 @@ class Callbacks(interfaces.plugins.PluginInterface): A name, location and optional detail string """ - kvo = context.layers[layer_name].config["kernel_virtual_offset"] + kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) try: @@ -626,7 +646,11 @@ class Callbacks(interfaces.plugins.PluginInterface): A name, location and optional detail string """ - kvo = context.layers[layer_name].config["kernel_virtual_offset"] + kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) try: diff --git a/volatility3/framework/plugins/windows/handles.py b/volatility3/framework/plugins/windows/handles.py index 6a391fe35..384528f0a 100644 --- a/volatility3/framework/plugins/windows/handles.py +++ b/volatility3/framework/plugins/windows/handles.py @@ -18,7 +18,7 @@ class Handles(interfaces.plugins.PluginInterface): """Lists process open handles.""" _required_framework_version = (2, 0, 0) - _version = (2, 0, 0) + _version = (2, 0, 1) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -144,7 +144,11 @@ class Handles(interfaces.plugins.PluginInterface): type_map: Dict[int, str] = {} - kvo = context.layers[layer_name].config["kernel_virtual_offset"] + kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) try: @@ -202,7 +206,11 @@ class Handles(interfaces.plugins.PluginInterface): except exceptions.SymbolError: return None - kvo = context.layers[layer_name].config["kernel_virtual_offset"] + kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) return context.object( symbol_table + constants.BANG + "unsigned int", layer_name, @@ -216,7 +224,7 @@ class Handles(interfaces.plugins.PluginInterface): kernel = self.context.modules[self.config["kernel"]] virtual = kernel.layer_name - kvo = self.context.layers[virtual].config["kernel_virtual_offset"] + kvo = kernel.offset ntkrnlmp = self.context.module( kernel.symbol_table_name, layer_name=virtual, offset=kvo diff --git a/volatility3/framework/plugins/windows/info.py b/volatility3/framework/plugins/windows/info.py index 137d29c22..efaf1f737 100644 --- a/volatility3/framework/plugins/windows/info.py +++ b/volatility3/framework/plugins/windows/info.py @@ -17,7 +17,7 @@ class Info(plugins.PluginInterface): """Show OS & kernel details of the memory sample being analyzed.""" _required_framework_version = (2, 0, 0) - _version = (1, 0, 0) + _version = (1, 0, 1) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -68,7 +68,9 @@ class Info(plugins.PluginInterface): if not isinstance(virtual_layer, layers.intel.Intel): raise TypeError("Virtual Layer is not an intel layer") - kvo = virtual_layer.config["kernel_virtual_offset"] + kvo = virtual_layer.config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError("Intel layer has no kernel virtual offset defined") ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) return ntkrnlmp @@ -166,7 +168,9 @@ class Info(plugins.PluginInterface): if not isinstance(virtual_layer, layers.intel.Intel): raise TypeError("Virtual Layer is not an intel layer") - kvo = virtual_layer.config["kernel_virtual_offset"] + kvo = virtual_layer.config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError("Intel layer has no kernel virtual offset defined") pe_table_name = intermed.IntermediateSymbolTable.create( context, diff --git a/volatility3/framework/plugins/windows/modules.py b/volatility3/framework/plugins/windows/modules.py index 85eb474a8..0dfa5a7e8 100644 --- a/volatility3/framework/plugins/windows/modules.py +++ b/volatility3/framework/plugins/windows/modules.py @@ -18,7 +18,7 @@ class Modules(interfaces.plugins.PluginInterface): """Lists the loaded kernel modules.""" _required_framework_version = (2, 0, 0) - _version = (2, 0, 0) + _version = (2, 0, 1) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -247,7 +247,11 @@ class Modules(interfaces.plugins.PluginInterface): A list of Modules as retrieved from PsLoadedModuleList """ - kvo = context.layers[layer_name].config["kernel_virtual_offset"] + kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) try: diff --git a/volatility3/framework/plugins/windows/pslist.py b/volatility3/framework/plugins/windows/pslist.py index 579a235d8..3e8be08a4 100644 --- a/volatility3/framework/plugins/windows/pslist.py +++ b/volatility3/framework/plugins/windows/pslist.py @@ -22,7 +22,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): """Lists the processes present in a particular windows memory image.""" _required_framework_version = (2, 0, 0) - _version = (2, 0, 0) + _version = (2, 0, 1) PHYSICAL_DEFAULT = False @classmethod @@ -226,7 +226,11 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): """ # We only use the object factory to demonstrate how to use one - kvo = context.layers[layer_name].config["kernel_virtual_offset"] + kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) ps_aph_offset = ntkrnlmp.get_symbol("PsActiveProcessHead").address diff --git a/volatility3/framework/plugins/windows/psscan.py b/volatility3/framework/plugins/windows/psscan.py index cdf344ee6..21671eb9b 100644 --- a/volatility3/framework/plugins/windows/psscan.py +++ b/volatility3/framework/plugins/windows/psscan.py @@ -23,7 +23,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): """Scans for processes present in a particular windows memory image.""" _required_framework_version = (2, 3, 1) - _version = (1, 1, 0) + _version = (1, 1, 1) @classmethod def get_requirements(cls): @@ -194,9 +194,12 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): # If it's WinXP->8.1 we have now a physical process address. # We'll use the first thread to bounce back to the virtual process - kvo = context.layers[layer_name].config["kernel_virtual_offset"] + kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) - tleoffset = ntkrnlmp.get_type("_ETHREAD").relative_child_offset( "ThreadListEntry" ) diff --git a/volatility3/framework/plugins/windows/registry/hivelist.py b/volatility3/framework/plugins/windows/registry/hivelist.py index 91a99a9fb..2963a7b8b 100644 --- a/volatility3/framework/plugins/windows/registry/hivelist.py +++ b/volatility3/framework/plugins/windows/registry/hivelist.py @@ -41,7 +41,7 @@ class HiveGenerator: class HiveList(interfaces.plugins.PluginInterface): """Lists the registry hives present in a particular memory image.""" - _version = (1, 0, 0) + _version = (1, 0, 1) _required_framework_version = (2, 0, 0) @classmethod @@ -59,7 +59,7 @@ class HiveList(interfaces.plugins.PluginInterface): default=None, ), requirements.PluginRequirement( - name="hivescan", plugin=hivescan.HiveScan, version=(1, 0, 0) + name="hivescan", plugin=hivescan.HiveScan, version=(2, 0, 0) ), requirements.BooleanRequirement( name="dump", @@ -215,7 +215,11 @@ class HiveList(interfaces.plugins.PluginInterface): """ # We only use the object factory to demonstrate how to use one - kvo = context.layers[layer_name].config["kernel_virtual_offset"] + kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) list_head = ntkrnlmp.get_symbol("CmpHiveListHead").address @@ -278,9 +282,7 @@ class HiveList(interfaces.plugins.PluginInterface): f"Hivelist failed traversing backwards at {hex(backward_invalid)}, a different " "location from forwards, revert to scanning" ) - for hive in hivescan.HiveScan.scan_hives( - context, layer_name, symbol_table - ): + for hive in hivescan.HiveScan.scan_hives(context, ntkrnlmp.name): try: if hive.HiveList.Flink: start_hive_offset = hive.HiveList.Flink - reloff diff --git a/volatility3/framework/plugins/windows/registry/hivescan.py b/volatility3/framework/plugins/windows/registry/hivescan.py index 6e0171a78..58ed63b4e 100644 --- a/volatility3/framework/plugins/windows/registry/hivescan.py +++ b/volatility3/framework/plugins/windows/registry/hivescan.py @@ -15,7 +15,7 @@ class HiveScan(interfaces.plugins.PluginInterface): """Scans for registry hives present in a particular windows memory image.""" _required_framework_version = (2, 0, 0) - _version = (1, 0, 0) + _version = (2, 0, 0) @classmethod def get_requirements(cls): @@ -35,10 +35,7 @@ class HiveScan(interfaces.plugins.PluginInterface): @classmethod def scan_hives( - cls, - context: interfaces.context.ContextInterface, - layer_name: str, - symbol_table: str, + cls, context: interfaces.context.ContextInterface, kernel_name: str ) -> Iterable[interfaces.objects.ObjectInterface]: """Scans for hives using the poolscanner module and constraints or bigpools module with tag. @@ -51,17 +48,21 @@ class HiveScan(interfaces.plugins.PluginInterface): A list of Hive objects as found from the `layer_name` layer based on Hive pool signatures """ - is_64bit = symbols.symbol_table_is_64bit(context, symbol_table) + kernel = context.modules[kernel_name] + + is_64bit = symbols.symbol_table_is_64bit(context, kernel.symbol_table_name) is_windows_8_1_or_later = versions.is_windows_8_1_or_later( - context=context, symbol_table=symbol_table + context=context, symbol_table=kernel.symbol_table_name ) if is_windows_8_1_or_later and is_64bit: - kvo = context.layers[layer_name].config["kernel_virtual_offset"] - ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) + ntkrnlmp = kernel for pool in bigpools.BigPools.list_big_pools( - context, layer_name=layer_name, symbol_table=symbol_table, tags=["CM10"] + context, + layer_name=kernel.layer_name, + symbol_table=kernel.symbol_table_name, + tags=["CM10"], ): cmhive = ntkrnlmp.object( object_type="_CMHIVE", offset=pool.Va, absolute=True @@ -70,21 +71,17 @@ class HiveScan(interfaces.plugins.PluginInterface): else: constraints = poolscanner.PoolScanner.builtin_constraints( - symbol_table, [b"CM10"] + kernel.symbol_table_name, [b"CM10"] ) for result in poolscanner.PoolScanner.generate_pool_scan( - context, layer_name, symbol_table, constraints + context, kernel.layer_name, kernel.symbol_table_name, constraints ): _constraint, mem_object, _header = result yield mem_object def _generator(self): - kernel = self.context.modules[self.config["kernel"]] - - for hive in self.scan_hives( - self.context, kernel.layer_name, kernel.symbol_table_name - ): + for hive in self.scan_hives(self.context, self.config["kernel"]): yield (0, (format_hints.Hex(hive.vol.offset),)) def run(self): diff --git a/volatility3/framework/plugins/windows/ssdt.py b/volatility3/framework/plugins/windows/ssdt.py index 1fcb6cc91..483a1b2ff 100644 --- a/volatility3/framework/plugins/windows/ssdt.py +++ b/volatility3/framework/plugins/windows/ssdt.py @@ -19,7 +19,7 @@ class SSDT(plugins.PluginInterface): """Lists the system call table.""" _required_framework_version = (2, 0, 0) - _version = (1, 0, 0) + _version = (1, 0, 1) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -89,9 +89,8 @@ class SSDT(plugins.PluginInterface): self.context, layer_name, kernel.symbol_table_name ) - kvo = self.context.layers[layer_name].config["kernel_virtual_offset"] ntkrnlmp = self.context.module( - kernel.symbol_table_name, layer_name=layer_name, offset=kvo + kernel.symbol_table_name, layer_name=kernel.offset, offset=kvo ) # this is just one way to enumerate the native (NT) service table. diff --git a/volatility3/framework/plugins/windows/unloadedmodules.py b/volatility3/framework/plugins/windows/unloadedmodules.py index d9f104ae8..855f0730b 100644 --- a/volatility3/framework/plugins/windows/unloadedmodules.py +++ b/volatility3/framework/plugins/windows/unloadedmodules.py @@ -22,7 +22,7 @@ class UnloadedModules(interfaces.plugins.PluginInterface, timeliner.TimeLinerInt """Lists the unloaded kernel modules.""" _required_framework_version = (2, 0, 0) - _version = (1, 0, 1) + _version = (1, 0, 2) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -88,7 +88,11 @@ class UnloadedModules(interfaces.plugins.PluginInterface, timeliner.TimeLinerInt A list of Unloaded Modules as retrieved from MmUnloadedDrivers """ - kvo = context.layers[layer_name].config["kernel_virtual_offset"] + kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) unloadedmodules_offset = ntkrnlmp.get_symbol("MmUnloadedDrivers").address unloadedmodules = ntkrnlmp.object( diff --git a/volatility3/framework/plugins/windows/vadinfo.py b/volatility3/framework/plugins/windows/vadinfo.py index 0c4a8aaca..f309aa3fd 100644 --- a/volatility3/framework/plugins/windows/vadinfo.py +++ b/volatility3/framework/plugins/windows/vadinfo.py @@ -34,7 +34,7 @@ class VadInfo(interfaces.plugins.PluginInterface): """Lists process memory ranges.""" _required_framework_version = (2, 4, 0) - _version = (2, 0, 0) + _version = (2, 0, 1) MAXSIZE_DEFAULT = 1024 * 1024 * 1024 # 1 Gb def __init__(self, *args, **kwargs): @@ -99,7 +99,11 @@ class VadInfo(interfaces.plugins.PluginInterface): symbol_table: The name of the table containing the kernel symbols """ - kvo = context.layers[layer_name].config["kernel_virtual_offset"] + kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) addr = ntkrnlmp.get_symbol("MmProtectToValue").address values = ntkrnlmp.object( diff --git a/volatility3/framework/plugins/windows/virtmap.py b/volatility3/framework/plugins/windows/virtmap.py index e02cca89e..f37d5790a 100644 --- a/volatility3/framework/plugins/windows/virtmap.py +++ b/volatility3/framework/plugins/windows/virtmap.py @@ -17,6 +17,7 @@ class VirtMap(interfaces.plugins.PluginInterface): """Lists virtual mapped sections.""" _required_framework_version = (2, 0, 0) + _version = (1, 0, 1) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -147,7 +148,7 @@ class VirtMap(interfaces.plugins.PluginInterface): module = self.context.module( kernel.symbol_table_name, layer_name=layer.name, - offset=layer.config["kernel_virtual_offset"], + offset=kernel.offset, ) return renderers.TreeGrid( diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 593097d25..595b63256 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -838,9 +838,14 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): return renderers.NotApplicableValue() symbol_table_name = self.get_symbol_table_name() - kvo = self._context.layers[self.vol.native_layer_name].config[ - "kernel_virtual_offset" - ] + kvo = self._context.layers[self.vol.native_layer_name].config.get( + "kernel_virtual_offset", None + ) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) + ntkrnlmp = self._context.module( symbol_table_name, layer_name=self.vol.native_layer_name, @@ -1030,7 +1035,13 @@ class TOKEN(objects.StructType): if self.UserAndGroupCount < 0xFFFF: layer_name = self.vol.layer_name - kvo = self._context.layers[layer_name].config["kernel_virtual_offset"] + kvo = self._context.layers[layer_name].config.get( + "kernel_virtual_offset", None + ) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) symbol_table = self.get_symbol_table_name() ntkrnlmp = self._context.module( symbol_table, layer_name=layer_name, offset=kvo @@ -1132,9 +1143,13 @@ class KTIMER(objects.StructType): def get_dpc(self): """Return Dpc, and if Windows 7 or later, decode it""" symbol_table_name = self.get_symbol_table_name() - kvo = self._context.layers[self.vol.native_layer_name].config[ - "kernel_virtual_offset" - ] + kvo = self._context.layers[self.vol.native_layer_name].config.get( + "kernel_virtual_offset", None + ) + if not kvo: + raise ValueError( + "Intel layer does not have an associatd kernel virtual offset, failing" + ) ntkrnlmp = self._context.module( symbol_table_name, layer_name=self.vol.native_layer_name, From fbb9627d36e03a7e222593c1f5bd3dede196e2b1 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 2 Feb 2025 16:18:42 +0000 Subject: [PATCH 067/120] Keep the advanced configuration requirements out of the interfaces --- volatility3/framework/configuration/requirements.py | 5 ++++- volatility3/framework/interfaces/context.py | 8 -------- volatility3/framework/plugins/windows/ssdt.py | 5 ++--- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/volatility3/framework/configuration/requirements.py b/volatility3/framework/configuration/requirements.py index 5dd8cc9b5..aa16c6090 100644 --- a/volatility3/framework/configuration/requirements.py +++ b/volatility3/framework/configuration/requirements.py @@ -641,7 +641,10 @@ class ModuleRequirement( @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: - return interfaces.context.ModuleInterface.get_requirements() + return [ + IntRequirement(name="offset"), + SymbolTableRequirement(name="symbol_table_name"), + ] def unsatisfied( self, context: "interfaces.context.ContextInterface", config_path: str diff --git a/volatility3/framework/interfaces/context.py b/volatility3/framework/interfaces/context.py index e7c3e579f..723f2fd46 100644 --- a/volatility3/framework/interfaces/context.py +++ b/volatility3/framework/interfaces/context.py @@ -158,14 +158,6 @@ class ModuleInterface(interfaces.configuration.ConfigurableInterface): super().__init__(context, config_path) self._module_name = name - @classmethod - def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: - # Can't include the translation layer without knowing the architectures - return [ - SymbolTableRequirement(name="symbol_table_name"), - IntRequirement(name="offset"), - ] - @property def _layer_name(self) -> str: return self.config["layer_name"] diff --git a/volatility3/framework/plugins/windows/ssdt.py b/volatility3/framework/plugins/windows/ssdt.py index 483a1b2ff..d6ec11286 100644 --- a/volatility3/framework/plugins/windows/ssdt.py +++ b/volatility3/framework/plugins/windows/ssdt.py @@ -89,9 +89,8 @@ class SSDT(plugins.PluginInterface): self.context, layer_name, kernel.symbol_table_name ) - ntkrnlmp = self.context.module( - kernel.symbol_table_name, layer_name=kernel.offset, offset=kvo - ) + ntkrnlmp = kernel + kvo = kernel.offset # this is just one way to enumerate the native (NT) service table. # to do the same thing for the Win32K service table, we would need Win32K.sys symbol support From 54ce358e8ec4ea79aa360cd9c4dd7140fc5e18b3 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 3 Feb 2025 07:37:38 +1100 Subject: [PATCH 068/120] framework: minor version 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 f2403cf4a..cf7c7b51a 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 = 19 # Number of changes that only add to the interface +VERSION_MINOR = 20 # Number of changes that only add to the interface VERSION_PATCH = 0 # Number of changes that do not change the interface VERSION_SUFFIX = "" From 19fed1964790592691d2c6f482b871998ffef402 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 3 Feb 2025 07:38:43 +1100 Subject: [PATCH 069/120] linux: kallsyms plugin: fix variable name typo --- volatility3/framework/plugins/linux/kallsyms.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/plugins/linux/kallsyms.py b/volatility3/framework/plugins/linux/kallsyms.py index c575c54e7..1665fffb2 100644 --- a/volatility3/framework/plugins/linux/kallsyms.py +++ b/volatility3/framework/plugins/linux/kallsyms.py @@ -94,17 +94,17 @@ class Kallsyms(plugins.PluginInterface): if not any(symbols_flags): include_core = include_modules = include_ftrace = include_bpf = True - symbol_geneators = [] + symbol_generators = [] if include_core: - symbol_geneators.append(kas.get_core_symbols()) + symbol_generators.append(kas.get_core_symbols()) if include_modules: - symbol_geneators.append(kas.get_modules_symbols()) + symbol_generators.append(kas.get_modules_symbols()) if include_ftrace: - symbol_geneators.append(kas.get_ftrace_symbols()) + symbol_generators.append(kas.get_ftrace_symbols()) if include_bpf: - symbol_geneators.append(kas.get_bpf_symbols()) + symbol_generators.append(kas.get_bpf_symbols()) - for symbols_generator in symbol_geneators: + for symbols_generator in symbol_generators: for kassymbol in symbols_generator: # Symbol sizes are calculated using the address of the next non-aliased # symbol or the end of the kernel text area _end/_etext. However, some kernel From 13b8526206a355a51509419c8ecab441b817e8ed Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 3 Feb 2025 07:40:17 +1100 Subject: [PATCH 070/120] linux: kallsyms API: reuse module_name variable --- volatility3/framework/plugins/linux/kallsyms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/kallsyms.py b/volatility3/framework/plugins/linux/kallsyms.py index 1665fffb2..7dd4f06e6 100644 --- a/volatility3/framework/plugins/linux/kallsyms.py +++ b/volatility3/framework/plugins/linux/kallsyms.py @@ -82,7 +82,7 @@ class Kallsyms(plugins.PluginInterface): kas = kallsyms.Kallsyms( context=self.context, layer_name=vmlinux.layer_name, - module_name=self.config["kernel"], + module_name=module_name, ) include_core = self.config.get("core", False) From e222b069ef3afc8c8d52da18c67e0d4d9cdfacdb Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 3 Feb 2025 07:50:21 +1100 Subject: [PATCH 071/120] linux: module extension object: add typing info to _get_sect_count() --- volatility3/framework/symbols/linux/extensions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index cd7dc5e9d..de08ecf50 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -176,7 +176,7 @@ class module(generic.GenericIntelProcess): """Get the name of the module as a string""" return utility.array_to_string(self.name) - def _get_sect_count(self, grp) -> int: + def _get_sect_count(self, grp: interfaces.objects.ObjectInterface) -> int: """Try to determine the number of valid sections""" symbol_table_name = self.get_symbol_table_name() arr = self._context.object( From 348720c89a0ea9477555f799fc6885e1246ff1e1 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 3 Feb 2025 07:55:11 +1100 Subject: [PATCH 072/120] linux: module extension object: add docstring to get_symbol_type() --- .../framework/symbols/linux/extensions/__init__.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index de08ecf50..a075524b4 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -361,7 +361,18 @@ class module(generic.GenericIntelProcess): raise AttributeError("Unable to get typetab section, it needs a kernel >= 5.2") - def get_symbol_type(self, symbol, symbol_index): + def get_symbol_type( + self, symbol: interfaces.objects.ObjectInterface, symbol_index: int + ) -> str: + """Determines the type of a given ELF symbol. + + Args: + symbol: The ELF symbol object (elf_sym) + symbol_index: The index of the symbol within the type table + + Returns: + A single-character string representing the symbol type + """ if self.has_member("kallsyms") and self.kallsyms.has_member("typetab"): # kernels >= 5.2 1c7651f43777cdd59c1aaa82c87324d3e7438c7b types have its own array layer = self._context.layers[self.vol.layer_name] From cef87e014cf6db0933a05e9c7839b41b8f6ec3f7 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 3 Feb 2025 07:59:57 +1100 Subject: [PATCH 073/120] linux: kernel_symbol object extension: move properties to getters --- .../framework/symbols/linux/extensions/__init__.py | 9 +++------ volatility3/framework/symbols/linux/kallsyms.py | 4 ++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index a075524b4..d65358f06 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -3225,8 +3225,7 @@ class kernel_symbol(objects.StructType): long_mask = (1 << layer.bits_per_register) - 1 return (self.vol.offset + off) & long_mask - @property - def name(self) -> str: + def get_name(self) -> str: if self.has_member("name_offset"): # kernel >= 4.19 and CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y # See 7290d58095712a89f845e1bca05334796dd49ed2 @@ -3246,8 +3245,7 @@ class kernel_symbol(objects.StructType): return name_bytes.decode("utf-8", errors="ignore") - @property - def value(self) -> int: + def get_value(self) -> int: if self.has_member("value_offset"): # kernel >= 4.19 and CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y # See 7290d58095712a89f845e1bca05334796dd49ed2 @@ -3258,8 +3256,7 @@ class kernel_symbol(objects.StructType): raise AttributeError("Unsupported kernel_symbol type implementation") - @property - def namespace(self) -> str: + def get_namespace(self) -> str: if self.has_member("namespace_offset"): # kernel >= 4.19 and CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y # See 7290d58095712a89f845e1bca05334796dd49ed2 diff --git a/volatility3/framework/symbols/linux/kallsyms.py b/volatility3/framework/symbols/linux/kallsyms.py index 264c13d3e..298725a7a 100644 --- a/volatility3/framework/symbols/linux/kallsyms.py +++ b/volatility3/framework/symbols/linux/kallsyms.py @@ -722,7 +722,7 @@ class Kallsyms(interfaces.configuration.VersionableInterface): self._kas_config.stop_ksymtab, ) - return kernel_symbol is not None and kernel_symbol.value == address + return kernel_symbol is not None and kernel_symbol.get_value() == address def _elfsym_to_kassymbol( self, @@ -1026,7 +1026,7 @@ class Kallsyms(interfaces.configuration.VersionableInterface): name: str, kernel_symbol: interfaces.objects.ObjectInterface, ) -> int: - return self._cmp_symbol_name(name, kernel_symbol.name) + return self._cmp_symbol_name(name, kernel_symbol.get_name()) def _cmp_symbol_name( self, From c01f3c5556141102eac1a4ca65c8f63fabacb253 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 5 Feb 2025 19:17:25 +0100 Subject: [PATCH 074/120] 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 baaeff683..d03e76c88 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -35,13 +35,16 @@ class Modules(interfaces.configuration.VersionableInterface): 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 075/120] 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 da88d1de1..75b4d3cca 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -221,6 +221,12 @@ if the "hidden_modules" key is present in known_modules. 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 @@ if the "hidden_modules" key is present in known_modules. 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 29567a777b8fce7e12e403f5823a618ddd96d404 Mon Sep 17 00:00:00 2001 From: hsarkey Date: Wed, 5 Feb 2025 15:10:36 -0500 Subject: [PATCH 076/120] Updates to address the dlllist wow64 upgrades --- .../symbols/windows/extensions/__init__.py | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index cda2dd615..9adde07b7 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -776,7 +776,7 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): ) return peb - def get_peb32(self) -> interfaces.objects.ObjectInterface: + def get_peb32(self) -> Optional[interfaces.objects.ObjectInterface]: """Constructs a PEB32 object""" if constants.BANG not in self.vol.type_name: raise ValueError( @@ -834,6 +834,14 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): ) return peb32 + def set_types(self, peb) -> str: + ldr_data = self._context.symbol_space.get_type( + self._32bit_table_name + constants.BANG + "_PEB_LDR_DATA" + ) + peb.Ldr = peb.Ldr.cast("pointer", subtype=ldr_data) + sym_table = self._32bit_table_name + return sym_table + def load_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: """Generator for DLLs in the order that they were loaded.""" try: @@ -844,12 +852,10 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): for peb in pebs: if peb: sym_table = self.get_symbol_table_name() - if peb.Ldr.vol.type_name.endswith("unsigned long"): - ldr_data = self._context.symbol_space.get_type( - self._32bit_table_name + constants.BANG + "_PEB_LDR_DATA" - ) - peb.Ldr = peb.Ldr.cast("pointer", subtype=ldr_data) - sym_table = self._32bit_table_name + if peb.Ldr.vol.type_name.split(constants.BANG)[-1] == ( + "unsigned long" + ): + sym_table = self.set_types(peb) yield from peb.Ldr.InLoadOrderModuleList.to_list( f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY", "InLoadOrderLinks", @@ -868,12 +874,10 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): for peb in pebs: if peb: sym_table = self.get_symbol_table_name() - if peb.Ldr.vol.type_name.endswith("unsigned long"): - ldr_data = self._context.symbol_space.get_type( - self._32bit_table_name + constants.BANG + "_PEB_LDR_DATA" - ) - peb.Ldr = peb.Ldr.cast("pointer", subtype=ldr_data) - sym_table = self._32bit_table_name + if peb.Ldr.vol.type_name.split(constants.BANG)[-1] == ( + "unsigned long" + ): + sym_table = self.set_types(peb) yield from peb.Ldr.InInitializationOrderModuleList.to_list( f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY", "InInitializationOrderLinks", @@ -891,12 +895,10 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): for peb in pebs: if peb: sym_table = self.get_symbol_table_name() - if peb.Ldr.vol.type_name.endswith("unsigned long"): - ldr_data = self._context.symbol_space.get_type( - self._32bit_table_name + constants.BANG + "_PEB_LDR_DATA" - ) - peb.Ldr = peb.Ldr.cast("pointer", subtype=ldr_data) - sym_table = self._32bit_table_name + if peb.Ldr.vol.type_name.split(constants.BANG)[-1] == ( + "unsigned long" + ): + sym_table = self.set_types(peb) yield from peb.Ldr.InMemoryOrderModuleList.to_list( f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY", "InMemoryOrderLinks", From f5e8ed2457f7d276359028cb6a10b8547f5c4f94 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 5 Feb 2025 21:42:13 +0100 Subject: [PATCH 077/120] 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 75b4d3cca..6690769b7 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 @@ vollog = logging.getLogger(__name__) # 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 @@ if the "hidden_modules" key is present in known_modules. 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, From 6100d7756f4919ffb6679564bfeee784b7eeadf2 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 7 Feb 2025 13:05:41 -0600 Subject: [PATCH 078/120] Add function typing --- volatility3/framework/plugins/linux/malfind.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/malfind.py b/volatility3/framework/plugins/linux/malfind.py index 7d8dd7f18..85cfaed31 100644 --- a/volatility3/framework/plugins/linux/malfind.py +++ b/volatility3/framework/plugins/linux/malfind.py @@ -2,7 +2,7 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # -from typing import List +from typing import List, Tuple, Optional import logging from volatility3.framework import interfaces from volatility3.framework import renderers, symbols @@ -39,7 +39,7 @@ class Malfind(interfaces.plugins.PluginInterface): ), ] - def _list_injections(self, task): + def _list_injections(self, task) -> Tuple[interfaces.objects.ObjectInterface, Optional[str], bytes]: """Generate memory regions for a process that may contain injected code.""" From d372f04effff791321f34917dde960805cf698c1 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 3 Jan 2025 19:03:32 +0000 Subject: [PATCH 079/120] Add proper exception handling in file descriptor enumeration --- volatility3/framework/symbols/linux/__init__.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index c8a22c7f5..f1a618804 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -339,15 +339,23 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): symbol_table: str, task: interfaces.objects.ObjectInterface, ): - # task.files can be null - if not (task.files and task.files.is_readable()): + try: + files = task.files + except exceptions.InvalidAddressException: + return None + + if not files.is_readable(): + return None + + try: + fd_table = files.get_fds() + except exceptions.InvalidAddressException: return None - fd_table = task.files.get_fds() if fd_table == 0: return None - max_fds = task.files.get_max_fds() + max_fds = files.get_max_fds() # corruption check if max_fds > 500000: From 968241aeddcca340c47a1ca6ffa0061fbf7f70d1 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 31 Jan 2025 18:03:02 +0000 Subject: [PATCH 080/120] Address feedback --- volatility3/framework/symbols/linux/__init__.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index f1a618804..314522eee 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -341,13 +341,6 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): ): try: files = task.files - except exceptions.InvalidAddressException: - return None - - if not files.is_readable(): - return None - - try: fd_table = files.get_fds() except exceptions.InvalidAddressException: return None From 9c5b01693256d802384f99c8677f0a3c2264f6fd Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 7 Feb 2025 13:11:41 -0600 Subject: [PATCH 081/120] Move all initial access into try/except block --- volatility3/framework/symbols/linux/__init__.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 314522eee..f6687a5e4 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -342,14 +342,13 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): try: files = task.files fd_table = files.get_fds() + if fd_table == 0: + return None + + max_fds = files.get_max_fds() except exceptions.InvalidAddressException: return None - if fd_table == 0: - return None - - max_fds = files.get_max_fds() - # corruption check if max_fds > 500000: return None From 4c2d21d0867b8beaa25d9b9c09361a639fdaf6b2 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 7 Feb 2025 13:15:31 -0600 Subject: [PATCH 082/120] bump patch number --- 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 cf7c7b51a..3aca23898 100644 --- a/volatility3/framework/constants/_version.py +++ b/volatility3/framework/constants/_version.py @@ -1,7 +1,7 @@ # We use the SemVer 2.0.0 versioning scheme VERSION_MAJOR = 2 # Number of releases of the library with a breaking change VERSION_MINOR = 20 # Number of changes that only add to the interface -VERSION_PATCH = 0 # Number of changes that do not change the interface +VERSION_PATCH = 1 # Number of changes that do not change the interface VERSION_SUFFIX = "" PACKAGE_VERSION = ( From ac8eeec52e0eb0118091e8db04b233b387e8c29b Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 7 Feb 2025 13:17:10 -0600 Subject: [PATCH 083/120] Fixes for black --- volatility3/framework/plugins/linux/malfind.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/malfind.py b/volatility3/framework/plugins/linux/malfind.py index 85cfaed31..297116890 100644 --- a/volatility3/framework/plugins/linux/malfind.py +++ b/volatility3/framework/plugins/linux/malfind.py @@ -39,7 +39,9 @@ class Malfind(interfaces.plugins.PluginInterface): ), ] - def _list_injections(self, task) -> Tuple[interfaces.objects.ObjectInterface, Optional[str], bytes]: + def _list_injections( + self, task + ) -> Tuple[interfaces.objects.ObjectInterface, Optional[str], bytes]: """Generate memory regions for a process that may contain injected code.""" From 13278121e3199f6b4746cf214765ed7e261fbdfd Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 7 Feb 2025 13:33:33 -0600 Subject: [PATCH 084/120] Catch symbolerror for when a kernel does not have ns_common #1594 --- volatility3/framework/symbols/linux/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index c8a22c7f5..f59587ca7 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -276,7 +276,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): ns_ops = ns_common.ops pre_name = utility.pointer_to_string(ns_ops.name, 255) - except IndexError: + except (exceptions.SymbolError, IndexError): pre_name = "" else: pre_name = f" {sym}" From 8c617d6b3fcc8a029696a0065d00d9c63738072c Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 7 Feb 2025 15:06:32 -0600 Subject: [PATCH 085/120] Make a generic DLL enumeration function that ensures the base address is set and we return as many entries as possible #1475 --- .../symbols/windows/extensions/__init__.py | 109 +++++++++--------- 1 file changed, 52 insertions(+), 57 deletions(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 230d59f95..fb03d304a 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -491,9 +491,9 @@ class FILE_OBJECT(objects.StructType, pool.ExecutiveObject): ].is_valid(self.FileName.Buffer) def file_name_with_device(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: - name: Union[str, interfaces.renderers.BaseAbsentValue] = ( - renderers.UnreadableValue() - ) + name: Union[ + str, interfaces.renderers.BaseAbsentValue + ] = renderers.UnreadableValue() # this pointer needs to be checked against native_layer_name because the object may # be instantiated from a primary (virtual) layer or a memory (physical) layer. @@ -848,69 +848,64 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): sym_table = self._32bit_table_name return sym_table + def _walk_ldr_list( + self, list_member: str, link_member: str + ) -> Iterable[interfaces.objects.ObjectInterface]: + """ + Walks LDR_DATA_TABLEs and enforces the entries at least have a valid base address + This function also breaks up exception handling as much as possible to ensure the + most data is returned as possible + """ + pebs = [] + + try: + peb = self.get_peb() + if peb: + pebs.append(peb) + except exceptions.InvalidAddressException: + vollog.debug(f"Process at {self.vol.offset:#x} has invalid PEB") + + try: + peb32 = self.get_peb32() + if peb32: + pebs.append(peb32) + except exceptions.InvalidAddressException: + vollog.debug(f"Process at {self.vol.offset:#x} has invalid 32 bit PEB") + + for peb in pebs: + sym_table = self.get_symbol_table_name() + if peb.Ldr.vol.type_name.split(constants.BANG)[-1] == ("unsigned long"): + sym_table = self.set_types(peb) + + for ldr in peb.Ldr.member(list_member).to_list( + f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY", link_member + ): + try: + # Several samples in testing crashed from DLLs being returned + # where DllBase was on the next page and that page was not in memory + # Not being able to retrieve the base makes the entry pretty useless + # So we enforce here its presence + ldr.DllBase + yield ldr + except exceptions.InvalidAddressException: + continue + def load_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: """Generator for DLLs in the order that they were loaded.""" - try: - pebs = [ - self.get_peb(), - self.get_peb32(), - ] - for peb in pebs: - if peb: - sym_table = self.get_symbol_table_name() - if peb.Ldr.vol.type_name.split(constants.BANG)[-1] == ( - "unsigned long" - ): - sym_table = self.set_types(peb) - yield from peb.Ldr.InLoadOrderModuleList.to_list( - f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY", - "InLoadOrderLinks", - ) - except exceptions.InvalidAddressException: - return None + + yield from self._walk_ldr_list("InLoadOrderModuleList", "InLoadOrderLinks") def init_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: """Generator for DLLs in the order that they were initialized""" - try: - pebs = [ - self.get_peb(), - self.get_peb32(), - ] - for peb in pebs: - if peb: - sym_table = self.get_symbol_table_name() - if peb.Ldr.vol.type_name.split(constants.BANG)[-1] == ( - "unsigned long" - ): - sym_table = self.set_types(peb) - yield from peb.Ldr.InInitializationOrderModuleList.to_list( - f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY", - "InInitializationOrderLinks", - ) - except exceptions.InvalidAddressException: - return None + yield from self._walk_ldr_list( + "InInitializationOrderModuleList", "InInitializationOrderLinks" + ) def mem_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: """Generator for DLLs in the order that they appear in memory""" - try: - pebs = [ - self.get_peb(), - self.get_peb32(), - ] - for peb in pebs: - if peb: - sym_table = self.get_symbol_table_name() - if peb.Ldr.vol.type_name.split(constants.BANG)[-1] == ( - "unsigned long" - ): - sym_table = self.set_types(peb) - yield from peb.Ldr.InMemoryOrderModuleList.to_list( - f"{sym_table}{constants.BANG}" + "_LDR_DATA_TABLE_ENTRY", - "InMemoryOrderLinks", - ) - except exceptions.InvalidAddressException: - return None + + yield from self._walk_ldr_list("InMemoryOrderModuleList", "InMemoryOrderLinks") def get_handle_count(self): try: From 67c001ab50564204ff7be56f43c57212c2420a3a Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 7 Feb 2025 15:12:06 -0600 Subject: [PATCH 086/120] Update for black --- .../framework/symbols/windows/extensions/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index fb03d304a..1d6040265 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -491,9 +491,9 @@ class FILE_OBJECT(objects.StructType, pool.ExecutiveObject): ].is_valid(self.FileName.Buffer) def file_name_with_device(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: - name: Union[ - str, interfaces.renderers.BaseAbsentValue - ] = renderers.UnreadableValue() + name: Union[str, interfaces.renderers.BaseAbsentValue] = ( + renderers.UnreadableValue() + ) # this pointer needs to be checked against native_layer_name because the object may # be instantiated from a primary (virtual) layer or a memory (physical) layer. From b78b7a5d9babca813dea3673840260f2cb7f3407 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 7 Feb 2025 16:33:16 -0600 Subject: [PATCH 087/120] Switch virtual and physical addresses to lists to support dumping multiple files at once #1319 --- .../framework/plugins/windows/dumpfiles.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/plugins/windows/dumpfiles.py b/volatility3/framework/plugins/windows/dumpfiles.py index 64d9be4db..b10c519e7 100755 --- a/volatility3/framework/plugins/windows/dumpfiles.py +++ b/volatility3/framework/plugins/windows/dumpfiles.py @@ -44,13 +44,15 @@ class DumpFiles(interfaces.plugins.PluginInterface): description="Process ID to include (all other processes are excluded)", optional=True, ), - requirements.IntRequirement( + requirements.ListRequirement( name="virtaddr", + element_type=int, description="Dump a single _FILE_OBJECT at this virtual address", optional=True, ), - requirements.IntRequirement( + requirements.ListRequirement( name="physaddr", + element_type=int, description="Dump a single _FILE_OBJECT at this physical address", optional=True, ), @@ -318,6 +320,7 @@ class DumpFiles(interfaces.plugins.PluginInterface): ) elif offsets: + # Now process any offsets explicitly requested by the user. for offset, is_virtual in offsets: try: @@ -355,10 +358,14 @@ class DumpFiles(interfaces.plugins.PluginInterface): ): raise ValueError("Cannot use filter flag with an address flag") - if self.config.get("virtaddr", None) is not None: - offsets.append((self.config["virtaddr"], True)) - elif self.config.get("physaddr", None) is not None: - offsets.append((self.config["physaddr"], False)) + if self.config.get("virtaddr"): + for virtaddr in self.config["virtaddr"]: + offsets.append((virtaddr, True)) + + elif self.config.get("physaddr"): + for physaddr in self.config["physaddr"]: + offsets.append((physaddr, False)) + else: filter_func = pslist.PsList.create_pid_filter( [self.config.get("pid", None)] From df55b7890dca4a73f6c8e6dd10b6994fc3264276 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 7 Feb 2025 18:05:16 -0600 Subject: [PATCH 088/120] Prevent yielding smeared/broken modules from the unloaded module list --- .../framework/plugins/windows/unloadedmodules.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/windows/unloadedmodules.py b/volatility3/framework/plugins/windows/unloadedmodules.py index d9f104ae8..a579ac7e8 100644 --- a/volatility3/framework/plugins/windows/unloadedmodules.py +++ b/volatility3/framework/plugins/windows/unloadedmodules.py @@ -117,7 +117,18 @@ class UnloadedModules(interfaces.plugins.PluginInterface, timeliner.TimeLinerInt ) unloadedmodules_array.UnloadedDrivers.count = unloaded_count - yield from unloadedmodules_array.UnloadedDrivers + for driver in unloadedmodules_array.UnloadedDrivers: + # Mass testing led to dozens of samples backtracing on this plugin when + # accessing members of modules coming out this list + # Given how often temporary drivers load and unload on Win10+, I + # assume the chance for smear is very high + try: + driver.StartAddress + driver.EndAddress + driver.CurrentTime + yield driver + except exceptions.InvalidAddressException: + continue def _generator(self): kernel = self.context.modules[self.config["kernel"]] From 0abe258765a0f84d2df015a446cf20fd7c5bc7ce Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sat, 8 Feb 2025 15:41:02 +0100 Subject: [PATCH 089/120] initial linux.tracing.tracepoints.CheckTracepoints --- .../plugins/linux/tracing/tracepoints.py | 305 ++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 volatility3/framework/plugins/linux/tracing/tracepoints.py diff --git a/volatility3/framework/plugins/linux/tracing/tracepoints.py b/volatility3/framework/plugins/linux/tracing/tracepoints.py new file mode 100644 index 000000000..2a79b90d6 --- /dev/null +++ b/volatility3/framework/plugins/linux/tracing/tracepoints.py @@ -0,0 +1,305 @@ +# 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 Dict, Iterable, List, Optional +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, NotAvailableValue, TreeGrid +from volatility3.framework.symbols.linux import extensions +from volatility3.framework.objects import utility +from volatility3.framework.constants import architectures + +vollog = logging.getLogger(__name__) + + +@dataclass +class ParsedTracepointFunc: + """Parsed tracepoint_func struct, containing a selection of forensics valuable + informations.""" + + tracepoint_name: str + tracepoint_address: int + probe_name: str + probe_address: int + probe_priority: int + module_name: str + module_address: int + + +class CheckTracepoints(interfaces.plugins.PluginInterface): + """Detect tracepoints hooking + + Investigate the tracepoints subsystem to uncover kernel attached probes, which can be leveraged + to hook kernel functions and modify their behaviour.""" + + _version = (1, 0, 0) + _required_framework_version = (2, 19, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=architectures.LINUX_ARCHS, + ), + requirements.VersionRequirement( + name="linux_utilities_modules", + component=linux_utilities_modules.Modules, + version=(1, 1, 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), + ), + ] + + @classmethod + def iterate_tracepoint_funcs( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + tracepoint: interfaces.objects.ObjectInterface, + ) -> Optional[Iterable[interfaces.objects.ObjectInterface]]: + """Extract probes represented by tracepoint_func structs from a + tracepoint funcs member. + + Args: + tracepoint: The tracepoint struct to parse + + Yields: + An iterable of tracepoint_func structs + """ + + layer = context.layers[layer_name] + # Ignore tracepoints without attached probes + if not tracepoint.funcs.is_readable(): + return None + + current_tracepoint_func = tracepoint.funcs.dereference() + # Inspired by kernel's debug_print_probes() + while ( + layer.is_valid(current_tracepoint_func.vol.offset) + and current_tracepoint_func.func.is_readable() + ): + yield current_tracepoint_func + current_tracepoint_func = context.object( + tracepoint.get_symbol_table_name() + constants.BANG + "tracepoint_func", + layer_name, + current_tracepoint_func.vol.offset + current_tracepoint_func.vol.size, + ) + + @classmethod + def parse_tracepoint( + cls, + context: interfaces.context.ContextInterface, + kernel_name: str, + known_modules: Dict[str, List[extensions.module]], + tracepoint: interfaces.objects.ObjectInterface, + run_hidden_modules: bool = True, + ) -> Optional[Iterable[ParsedTracepointFunc]]: + """Parse a tracepoint struct to highlight tracepoints kernel hooking. + + Args: + 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. + + 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 + ): + probe_handler_address = tracepoint_func.func + probe_handler_symbol = module_address = module_name = None + + # Try to lookup within the known modules if the probe_handler address fits + module = linux_utilities_modules.Modules.module_lookup_by_address( + context, + kernel.layer_name, + modxview.Modxview.flatten_run_modules_results(known_modules), + probe_handler_address, + ) + # Run hidden_modules plugin if a probe handler origin couldn't be determined (only done once, results are re-used afterwards) + if ( + module is None + and run_hidden_modules + and "hidden_modules" not in known_modules + ): + vollog.info( + "A probe handler module origin could not be determined. hidden_modules plugin will be run to detect additional modules.", + ) + known_modules_addresses = set( + kernel_layer.canonicalize(module.vol.offset) + 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["hidden_modules"] = list( + hidden_modules.Hidden_modules.get_hidden_modules( + context, + kernel_name, + known_modules_addresses, + modules_memory_boundaries, + ) + ) + # Lookup the updated list to see if hidden_modules was able + # to find the missing module + module = linux_utilities_modules.Modules.module_lookup_by_address( + context, + kernel.layer_name, + modxview.Modxview.flatten_run_modules_results(known_modules), + probe_handler_address, + ) + + # Fetch more information about the module + if module is not None: + module_address = module.vol.offset + module_name = module.get_name() + probe_handler_symbol = module.get_symbol_by_address( + probe_handler_address + ) + else: + vollog.warning( + f"Could not determine tracepoint@{tracepoint.vol.offset:#x} probe handler {probe_handler_address:#x} module origin.", + ) + + yield ParsedTracepointFunc( + utility.pointer_to_string(tracepoint.name, count=512), + tracepoint.vol.offset, + probe_handler_symbol, + probe_handler_address, + tracepoint_func.prio, + module_name, + module_address, + ) + + @classmethod + def iterate_tracepoints_array( + cls, context: interfaces.context.ContextInterface, kernel_name: str + ) -> List[interfaces.objects.ObjectInterface]: + """Iterate over (tracepoint_ptr_t *)__start___tracepoints_ptrs. + Handles CONFIG_HAVE_ARCH_PREL32_RELOCATIONS. + + Returns: + A list of tracepoint structs + """ + + kernel = context.modules[kernel_name] + + tracepoints = [] + tracepoints_start = kernel.object_from_symbol("__start___tracepoints_ptrs") + tracepoints_end = kernel.object_from_symbol("__stop___tracepoints_ptrs") + tracepoints_array_size = ( + tracepoints_end.vol.offset - tracepoints_start.vol.offset + ) + # kernel's tracepoint_ptr_deref() and tracepoint_ptr_t + # adjust depending on the use of relocated pointers + # or not + config_have_arch_prel32_relocations = ( + tracepoints_start.vol.subtype.type_name + == kernel.symbol_table_name + constants.BANG + "int" + ) + if config_have_arch_prel32_relocations: + tracepoints_relative_offsets = tracepoints_start.cast( + "array", + count=tracepoints_array_size // kernel.get_type("int").size, + subtype=kernel.get_type("int"), + ) + for relative_offset in tracepoints_relative_offsets: + tracepoint = kernel.object( + "tracepoint", + relative_offset + relative_offset.vol.offset, + absolute=True, + ) + tracepoints.append(tracepoint) + else: + tracepoints = utility.array_of_pointers( + tracepoints_start, + tracepoints_array_size // kernel.get_type("pointer").size, + kernel.symbol_table_name + constants.BANG + "tracepoint", + context, + ) + + return tracepoints + + def _generator(self): + kernel_name = self.config["kernel"] + kernel = self.context.modules[kernel_name] + 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.', + ) + + known_modules = modxview.Modxview.run_modules_scanners( + self.context, kernel_name, run_hidden_modules=False + ) + tracepoints = self.iterate_tracepoints_array(self.context, kernel_name) + + for tracepoint in tracepoints: + if not kernel_layer.is_valid(tracepoint.vol.offset): + continue + + for tracepoint_parsed in self.parse_tracepoint( + self.context, kernel_name, known_modules, tracepoint + ): + formatted_results = ( + tracepoint_parsed.tracepoint_name, + 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.module_name or NotAvailableValue(), + ( + format_hints.Hex(tracepoint_parsed.module_address) + if tracepoint_parsed.module_address is not None + else NotAvailableValue() + ), + ) + yield ( + 0, + formatted_results, + ) + + def run(self): + columns = [ + ("tracepoint", str), + ("tracepoint address", format_hints.Hex), + ("Probe", str), + ("Probe address", format_hints.Hex), + ("Probe priority", int), + ("Module", str), + ("Module address", format_hints.Hex), + ] + + return TreeGrid( + columns, + self._generator(), + ) From 83dde6aec31fd79d349644a3b0ac317b73d1cb7f Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 9 Feb 2025 12:26:42 +0100 Subject: [PATCH 090/120] improve prel32 comments --- .../framework/plugins/linux/tracing/tracepoints.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/linux/tracing/tracepoints.py b/volatility3/framework/plugins/linux/tracing/tracepoints.py index 2a79b90d6..89bec4598 100644 --- a/volatility3/framework/plugins/linux/tracing/tracepoints.py +++ b/volatility3/framework/plugins/linux/tracing/tracepoints.py @@ -217,8 +217,10 @@ if the "hidden_modules" key is present in known_modules. tracepoints_end.vol.offset - tracepoints_start.vol.offset ) # kernel's tracepoint_ptr_deref() and tracepoint_ptr_t - # adjust depending on the use of relocated pointers - # or not + # adjust depending on the use of PC-relative addressing + # or not. + # Relocation is commonly used to store pointers as offsets + # relative to their own address rather than absolute addresses/pointers. config_have_arch_prel32_relocations = ( tracepoints_start.vol.subtype.type_name == kernel.symbol_table_name + constants.BANG + "int" @@ -230,9 +232,13 @@ if the "hidden_modules" key is present in known_modules. subtype=kernel.get_type("int"), ) for relative_offset in tracepoints_relative_offsets: + # relative_offset is the value stored at relative_offset.vol.offset + # See kernel's offset_to_ptr(). Example: + # 0xffff9da125e0 = 0x7af138 + 0xffff9d2634a8 + absolute_address = relative_offset + relative_offset.vol.offset tracepoint = kernel.object( "tracepoint", - relative_offset + relative_offset.vol.offset, + absolute_address, absolute=True, ) tracepoints.append(tracepoint) From a9691fbe77b72bed2cd5b12471d8f5deaae88813 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 9 Feb 2025 12:41:23 +0100 Subject: [PATCH 091/120] remove unnecessary object creation --- volatility3/framework/plugins/linux/tracing/tracepoints.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/linux/tracing/tracepoints.py b/volatility3/framework/plugins/linux/tracing/tracepoints.py index 89bec4598..247e139d5 100644 --- a/volatility3/framework/plugins/linux/tracing/tracepoints.py +++ b/volatility3/framework/plugins/linux/tracing/tracepoints.py @@ -212,10 +212,10 @@ if the "hidden_modules" key is present in known_modules. tracepoints = [] tracepoints_start = kernel.object_from_symbol("__start___tracepoints_ptrs") - tracepoints_end = kernel.object_from_symbol("__stop___tracepoints_ptrs") - tracepoints_array_size = ( - tracepoints_end.vol.offset - tracepoints_start.vol.offset + tracepoints_end = kernel.get_absolute_symbol_address( + "__stop___tracepoints_ptrs" ) + tracepoints_array_size = tracepoints_end - tracepoints_start.vol.offset # kernel's tracepoint_ptr_deref() and tracepoint_ptr_t # adjust depending on the use of PC-relative addressing # or not. From 8d23d5b80810655475233030ff441ee63a2f2865 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 9 Feb 2025 13:41:46 +0100 Subject: [PATCH 092/120] use architectures.LINUX_ARCHS --- volatility3/framework/plugins/linux/pagecache.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 4d1250255..48e3da9c1 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -6,8 +6,9 @@ import math import logging import datetime from dataclasses import dataclass, astuple -from typing import List, Set, Type, Iterable, Tuple +from typing import IO, List, Set, Type, Iterable, Tuple +from volatility3.framework.constants import architectures from volatility3.framework import renderers, interfaces, exceptions from volatility3.framework.renderers import format_hints from volatility3.framework.interfaces import plugins @@ -112,7 +113,7 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface): requirements.ModuleRequirement( name="kernel", description="Linux kernel", - architectures=["Intel32", "Intel64"], + architectures=architectures.LINUX_ARCHS, ), requirements.PluginRequirement( name="mountinfo", plugin=mountinfo.MountInfo, version=(1, 2, 0) @@ -413,7 +414,7 @@ class InodePages(plugins.PluginInterface): requirements.ModuleRequirement( name="kernel", description="Linux kernel", - architectures=["Intel32", "Intel64"], + architectures=architectures.LINUX_ARCHS, ), requirements.PluginRequirement( name="files", plugin=Files, version=(1, 0, 0) From cd9ba823a1719c734f0ebf8f983182389eba87b3 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 9 Feb 2025 13:42:47 +0100 Subject: [PATCH 093/120] add inode_size and format_symlink to InodeUser --- volatility3/framework/plugins/linux/pagecache.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 48e3da9c1..3fa8cf181 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -38,6 +38,11 @@ class InodeUser: modification_time: str change_time: str path: str + inode_size: int + + @classmethod + def format_symlink(cls, symlink_source: str, symlink_dest: str) -> str: + return f"{symlink_source} -> {symlink_dest}" @dataclass @@ -81,6 +86,7 @@ class InodeInternal: access_time_dt = self.inode.get_access_time() modification_time_dt = self.inode.get_modification_time() change_time_dt = self.inode.get_change_time() + inode_size = int(self.inode.i_size) inode_user = InodeUser( superblock_addr=superblock_addr, @@ -96,6 +102,7 @@ class InodeInternal: modification_time=modification_time_dt, change_time=change_time_dt, path=self.path, + inode_size=inode_size, ) return inode_user @@ -394,6 +401,7 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface): ("ModificationTime", datetime.datetime), ("ChangeTime", datetime.datetime), ("FilePath", str), + ("InodeSize", int), ] return renderers.TreeGrid( From b9d4605b8ac93382d1d4542fbe15d2e2d7466758 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 9 Feb 2025 13:44:16 +0100 Subject: [PATCH 094/120] switch to InodeUser.format_symlink --- volatility3/framework/plugins/linux/pagecache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 3fa8cf181..c2bf6fcca 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -162,10 +162,10 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface): and inode.i_link and inode.i_link.is_readable() ): - i_link_str = inode.i_link.dereference().cast( + symlink_dest = inode.i_link.dereference().cast( "string", max_length=255, encoding="utf-8", errors="replace" ) - symlink_path = f"{symlink_path} -> {i_link_str}" + symlink_path = InodeUser.format_symlink(symlink_path, symlink_dest) return symlink_path From 8d9d0f5689a31cadcf40b3125f0f8d67ea668a29 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 9 Feb 2025 13:45:23 +0100 Subject: [PATCH 095/120] add and use follow_symlinks parameter --- volatility3/framework/plugins/linux/pagecache.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index c2bf6fcca..5f3e3e558 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -226,12 +226,14 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface): cls, context: interfaces.context.ContextInterface, vmlinux_module_name: str, + follow_symlinks: bool = True, ) -> Iterable[InodeInternal]: """Retrieves the inodes from the superblocks Args: context: The context that the plugin will operate within vmlinux_module_name: The name of the kernel module on which to operate + follow_symlinks: Whether to follow symlinks or not Yields: An InodeInternal object @@ -311,7 +313,8 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface): continue seen_inodes.add(file_inode_ptr) - file_path = cls._follow_symlink(file_inode_ptr, file_path) + if follow_symlinks: + file_path = cls._follow_symlink(file_inode_ptr, file_path) inode_in = InodeInternal( superblock=superblock, mountpoint=mountpoint, From 03f6fa95332da64a15f7176eb6e5419518424998 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 9 Feb 2025 13:45:46 +0100 Subject: [PATCH 096/120] 1.0.3 -> 1.1.0 Files bump --- volatility3/framework/plugins/linux/pagecache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 5f3e3e558..702c5986e 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -112,7 +112,7 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface): _required_framework_version = (2, 0, 0) - _version = (1, 0, 3) + _version = (1, 1, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: From 223f2d69bbcd4a7877e642b7a50b70355731551b Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 9 Feb 2025 13:47:37 +0100 Subject: [PATCH 097/120] switch to context and layer_name calling convention --- volatility3/framework/plugins/linux/pagecache.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 702c5986e..5f12d7228 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -451,14 +451,17 @@ class InodePages(plugins.PluginInterface): @classmethod def write_inode_content_to_file( cls, + context: interfaces.context.ContextInterface, + layer_name: str, inode: interfaces.objects.ObjectInterface, filename: str, open_method: Type[interfaces.plugins.FileHandlerInterface], - vmlinux_layer: interfaces.layers.TranslationLayerInterface, ) -> None: """Extracts the inode's contents from the page cache and saves them to a file Args: + context: The context on which to operate + layer_name: The name of the layer on which to operate inode: The inode to dump filename: Filename for writing the inode content open_method: class for constructing output files @@ -587,7 +590,7 @@ class InodePages(plugins.PluginInterface): filename = open_method.sanitize_filename(f"inode_0x{inode_address:x}.dmp") vollog.info("[*] Writing inode at 0x%x to '%s'", inode_address, filename) self.write_inode_content_to_file( - inode, filename, open_method, vmlinux_layer + self.context, vmlinux_layer.name, inode, filename, open_method ) else: yield from self._generate_inode_fields(inode, vmlinux_layer) From 826281b3b903790874f9c48ed501b16b8243f867 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 9 Feb 2025 13:48:59 +0100 Subject: [PATCH 098/120] add and use write_inode_content_to_stream --- .../framework/plugins/linux/pagecache.py | 75 ++++++++++++------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 5f12d7228..05544bc91 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -465,51 +465,68 @@ class InodePages(plugins.PluginInterface): inode: The inode to dump filename: Filename for writing the inode content open_method: class for constructing output files - vmlinux_layer: The kernel layer to obtain the page size + """ + try: + with open_method(filename) as file_obj: + cls.write_inode_content_to_stream(context, layer_name, inode, file_obj) + except OSError as e: + vollog.error("Unable to write to file (%s): %s", filename, e) + + @classmethod + def write_inode_content_to_stream( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + inode: interfaces.objects.ObjectInterface, + stream: IO, + ) -> None: + """Extracts the inode's contents from the page cache and saves them to a stream + + Args: + context: The context on which to operate + layer_name: The name of the layer on which to operate + inode: The inode to dump + stream: An IO stream to write to, typically FileHandlerInterface or BytesIO """ if not inode.is_reg: vollog.error("The inode is not a regular file") return None - # By using truncate/seek, provided the filesystem supports it, a sparse file will be + layer = context.layers[layer_name] + # By using truncate/seek, provided the filesystem supports it, and the + # stream is a File interface, a sparse file will be # created, saving both disk space and I/O time. # Additionally, using the page index will guarantee that each page is written at the # appropriate file position. inode_size = inode.i_size try: - file_initialized = False - with open_method(filename) as file_obj: - for page_idx, page_content in inode.get_contents(): - current_fp = page_idx * vmlinux_layer.page_size - max_length = inode_size - current_fp - page_bytes_len = min(max_length, len(page_content)) - if ( - current_fp >= inode_size - or current_fp + page_bytes_len > inode_size - ): - vollog.error( - "Page out of file bounds: inode 0x%x, inode size %d, page index %d", - inode.vol.offset, - inode_size, - page_idx, - ) - continue - page_bytes = page_content[:page_bytes_len] + stream_initialized = False + for page_idx, page_content in inode.get_contents(): + current_fp = page_idx * layer.page_size + max_length = inode_size - current_fp + page_bytes_len = min(max_length, len(page_content)) + if current_fp >= inode_size or current_fp + page_bytes_len > inode_size: + vollog.error( + "Page out of file bounds: inode 0x%x, inode size %d, page index %d", + inode.vol.offset, + inode_size, + page_idx, + ) + continue + page_bytes = page_content[:page_bytes_len] - if not file_initialized: - # Lazy initialization to avoid truncating the file until we are - # certain there is something to write - file_obj.truncate(inode_size) - file_initialized = True + if not stream_initialized: + # Lazy initialization to avoid truncating the stream until we are + # certain there is something to write + stream.truncate(inode_size) + stream_initialized = True - file_obj.seek(current_fp) - file_obj.write(page_bytes) + stream.seek(current_fp) + stream.write(page_bytes) except exceptions.LinuxPageCacheException: vollog.error( f"Error dumping cached pages for inode at {inode.vol.offset:#x}" ) - except OSError as e: - vollog.error("Unable to write to file (%s): %s", filename, e) def _generate_inode_fields( self, From f1b34df26ba76d67a10e3169db866cf5e459b18a Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 9 Feb 2025 13:49:24 +0100 Subject: [PATCH 099/120] 2.0.2 -> 3.0.0 InodePages bump --- volatility3/framework/plugins/linux/pagecache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 05544bc91..de02ad022 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -417,7 +417,7 @@ class InodePages(plugins.PluginInterface): _required_framework_version = (2, 0, 0) - _version = (2, 0, 2) + _version = (3, 0, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: From 444376cbbad407bbe00cb4ed3cb51b6ae07ec489 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 9 Feb 2025 15:39:49 +0000 Subject: [PATCH 100/120] Fix typos and try setting back requirement change --- volatility3/framework/configuration/requirements.py | 2 +- volatility3/framework/plugins/windows/bigpools.py | 2 +- volatility3/framework/plugins/windows/callbacks.py | 12 ++++++------ volatility3/framework/plugins/windows/handles.py | 4 ++-- volatility3/framework/plugins/windows/modules.py | 2 +- volatility3/framework/plugins/windows/pslist.py | 2 +- volatility3/framework/plugins/windows/psscan.py | 2 +- .../framework/plugins/windows/registry/hivelist.py | 2 +- .../framework/plugins/windows/unloadedmodules.py | 2 +- volatility3/framework/plugins/windows/vadinfo.py | 2 +- .../framework/symbols/windows/extensions/__init__.py | 6 +++--- 11 files changed, 19 insertions(+), 19 deletions(-) diff --git a/volatility3/framework/configuration/requirements.py b/volatility3/framework/configuration/requirements.py index aa16c6090..3e3608000 100644 --- a/volatility3/framework/configuration/requirements.py +++ b/volatility3/framework/configuration/requirements.py @@ -638,12 +638,12 @@ class ModuleRequirement( self.add_requirement( TranslationLayerRequirement(name="layer_name", architectures=architectures) ) + self.add_requirement(SymbolTableRequirement(name="symbol_table_name")) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: return [ IntRequirement(name="offset"), - SymbolTableRequirement(name="symbol_table_name"), ] def unsatisfied( diff --git a/volatility3/framework/plugins/windows/bigpools.py b/volatility3/framework/plugins/windows/bigpools.py index f6217ac50..9da702ae0 100644 --- a/volatility3/framework/plugins/windows/bigpools.py +++ b/volatility3/framework/plugins/windows/bigpools.py @@ -69,7 +69,7 @@ class BigPools(interfaces.plugins.PluginInterface): kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) diff --git a/volatility3/framework/plugins/windows/callbacks.py b/volatility3/framework/plugins/windows/callbacks.py index 9d54f4331..7bb90863d 100644 --- a/volatility3/framework/plugins/windows/callbacks.py +++ b/volatility3/framework/plugins/windows/callbacks.py @@ -364,7 +364,7 @@ class Callbacks(interfaces.plugins.PluginInterface): kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) @@ -425,7 +425,7 @@ class Callbacks(interfaces.plugins.PluginInterface): kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) full_type_name = ( @@ -476,7 +476,7 @@ class Callbacks(interfaces.plugins.PluginInterface): kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) full_type_name = callback_table_name + constants.BANG + "_CM_CALLBACK_ENTRY" @@ -521,7 +521,7 @@ class Callbacks(interfaces.plugins.PluginInterface): kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) @@ -581,7 +581,7 @@ class Callbacks(interfaces.plugins.PluginInterface): kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) @@ -649,7 +649,7 @@ class Callbacks(interfaces.plugins.PluginInterface): kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) diff --git a/volatility3/framework/plugins/windows/handles.py b/volatility3/framework/plugins/windows/handles.py index 384528f0a..276ce5d51 100644 --- a/volatility3/framework/plugins/windows/handles.py +++ b/volatility3/framework/plugins/windows/handles.py @@ -147,7 +147,7 @@ class Handles(interfaces.plugins.PluginInterface): kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) @@ -209,7 +209,7 @@ class Handles(interfaces.plugins.PluginInterface): kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) return context.object( symbol_table + constants.BANG + "unsigned int", diff --git a/volatility3/framework/plugins/windows/modules.py b/volatility3/framework/plugins/windows/modules.py index 0dfa5a7e8..62a622491 100644 --- a/volatility3/framework/plugins/windows/modules.py +++ b/volatility3/framework/plugins/windows/modules.py @@ -250,7 +250,7 @@ class Modules(interfaces.plugins.PluginInterface): kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) diff --git a/volatility3/framework/plugins/windows/pslist.py b/volatility3/framework/plugins/windows/pslist.py index 3e8be08a4..3d3f12869 100644 --- a/volatility3/framework/plugins/windows/pslist.py +++ b/volatility3/framework/plugins/windows/pslist.py @@ -229,7 +229,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) diff --git a/volatility3/framework/plugins/windows/psscan.py b/volatility3/framework/plugins/windows/psscan.py index 21671eb9b..81e5fb792 100644 --- a/volatility3/framework/plugins/windows/psscan.py +++ b/volatility3/framework/plugins/windows/psscan.py @@ -197,7 +197,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) tleoffset = ntkrnlmp.get_type("_ETHREAD").relative_child_offset( diff --git a/volatility3/framework/plugins/windows/registry/hivelist.py b/volatility3/framework/plugins/windows/registry/hivelist.py index 2963a7b8b..36be35a68 100644 --- a/volatility3/framework/plugins/windows/registry/hivelist.py +++ b/volatility3/framework/plugins/windows/registry/hivelist.py @@ -218,7 +218,7 @@ class HiveList(interfaces.plugins.PluginInterface): kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) diff --git a/volatility3/framework/plugins/windows/unloadedmodules.py b/volatility3/framework/plugins/windows/unloadedmodules.py index 855f0730b..4359199c1 100644 --- a/volatility3/framework/plugins/windows/unloadedmodules.py +++ b/volatility3/framework/plugins/windows/unloadedmodules.py @@ -91,7 +91,7 @@ class UnloadedModules(interfaces.plugins.PluginInterface, timeliner.TimeLinerInt kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) unloadedmodules_offset = ntkrnlmp.get_symbol("MmUnloadedDrivers").address diff --git a/volatility3/framework/plugins/windows/vadinfo.py b/volatility3/framework/plugins/windows/vadinfo.py index f309aa3fd..35bf54d98 100644 --- a/volatility3/framework/plugins/windows/vadinfo.py +++ b/volatility3/framework/plugins/windows/vadinfo.py @@ -102,7 +102,7 @@ class VadInfo(interfaces.plugins.PluginInterface): kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) addr = ntkrnlmp.get_symbol("MmProtectToValue").address diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 595b63256..4e7d6d920 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -843,7 +843,7 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): ) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) ntkrnlmp = self._context.module( @@ -1040,7 +1040,7 @@ class TOKEN(objects.StructType): ) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) symbol_table = self.get_symbol_table_name() ntkrnlmp = self._context.module( @@ -1148,7 +1148,7 @@ class KTIMER(objects.StructType): ) if not kvo: raise ValueError( - "Intel layer does not have an associatd kernel virtual offset, failing" + "Intel layer does not have an associated kernel virtual offset, failing" ) ntkrnlmp = self._context.module( symbol_table_name, From 64d8a675a70f873577c78d63a784e0b82b8f48ba Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Mon, 10 Feb 2025 11:49:23 +0100 Subject: [PATCH 101/120] initial linux.pagecache.recoverfs --- .../framework/plugins/linux/pagecache.py | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index de02ad022..d0c085719 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -5,8 +5,11 @@ import math import logging import datetime +import time +import tarfile from dataclasses import dataclass, astuple from typing import IO, List, Set, Type, Iterable, Tuple +from io import BytesIO from volatility3.framework.constants import architectures from volatility3.framework import renderers, interfaces, exceptions @@ -625,3 +628,211 @@ class InodePages(plugins.PluginInterface): return renderers.TreeGrid( headers, Files.format_fields_with_headers(headers, self._generator()) ) + + +class RecoverFs(plugins.PluginInterface): + """Recovers the cached filesystem (directories, files, symlinks) into a compressed tarball. + + Metadata aren't replicated to extracted objects and timestamps are set to the plugin run time. To prevent extraction errors related to long paths, please consider using https://github.com/mxmlnkn/ratarmount. + To mount: + "ratarmount recovered_fs.tar.gz ./recovered_fs_mounted/". + To unmount: + "umount ./recovered_fs_mounted/". + """ + + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=architectures.LINUX_ARCHS, + ), + requirements.PluginRequirement( + name="files", plugin=Files, version=(1, 1, 0) + ), + requirements.PluginRequirement( + name="inodepages", plugin=InodePages, version=(3, 0, 0) + ), + requirements.ChoiceRequirement( + name="compression_format", + description="Compression format (default: gz)", + choices=["gz", "bz2", "xz"], + default="gz", + optional=True, + ), + ] + + @classmethod + def _tar_add_reg_inode( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + tar: tarfile.TarFile, + reg_inode_in: InodeInternal, + mtime: float = None, + ) -> int: + """Extracts a REG inode content and writes it to a TarFile object. + + Args: + context: The context on which to operate + layer_name: The name of the layer on which to operate + tar: The TarFile object to write to + reg_inode_in: The inode to extract content from + mtime: The modification time to set the TarInfo object to + + Returns: + The number of extracted bytes + """ + inode_content_buffer = BytesIO() + InodePages.write_inode_content_to_stream( + context, layer_name, reg_inode_in.inode, inode_content_buffer + ) + inode_content_buffer.seek(0) + handle_buffer_size = inode_content_buffer.getbuffer().nbytes + + tar_info = tarfile.TarInfo(reg_inode_in.path) + # The tarfile module only has read support for sparse files: + # https://docs.python.org/3.12/library/tarfile.html#tarfile.LNKTYPE:~:text=and%20longlink%20extensions%2C-,read%2Donly%20support,-for%20all%20variants + tar_info.type = tarfile.REGTYPE + tar_info.size = handle_buffer_size + tar_info.mode = 0o444 + if mtime is not None: + tar_info.mtime = mtime + tar.addfile(tar_info, inode_content_buffer) + + return handle_buffer_size + + @classmethod + def _tar_add_dir_inode( + cls, + tar: tarfile.TarFile, + reg_dir_in: InodeInternal, + mtime: float = None, + ) -> None: + """Adds a directory path to a TarFile object, based on a DIR inode. + + Args: + tar: The TarFile object to write to + reg_dir_in: The inode to base the new directory on + mtime: The modification time to set the TarInfo object to + """ + tar_info = tarfile.TarInfo(reg_dir_in.path) + tar_info.type = tarfile.DIRTYPE + tar_info.mode = 0o755 + if mtime is not None: + tar_info.mtime = mtime + tar.addfile(tar_info) + + @classmethod + def _tar_add_lnk( + cls, + tar: tarfile.TarFile, + symlink_source: str, + symlink_dest: str, + mtime: float = None, + ) -> None: + """Adds a symlink to a TarFile object. + + Args: + tar: The TarFile object to write to + symlink_source: The symlink source path + symlink_dest: The symlink target/destination + mtime: The modification time to set the TarInfo object to + """ + # Patch symlinks pointing to absolute paths, + # to prevent referencing the host filesystem. + if symlink_dest.startswith("/"): + inode_depth = symlink_source.strip("/").count("/") + symlink_dest = "../" * inode_depth + symlink_dest.lstrip("/") + + tar_info = tarfile.TarInfo(symlink_source) + tar_info.type = tarfile.SYMTYPE + tar_info.linkname = symlink_dest + tar_info.mode = 0o444 + if mtime is not None: + tar_info.mtime = mtime + tar.addfile(tar_info) + + def _generator(self): + vmlinux_module_name = self.config["kernel"] + vmlinux = self.context.modules[vmlinux_module_name] + vmlinux_layer = self.context.layers[vmlinux.layer_name] + tar_buffer = BytesIO() + tar = tarfile.open( + fileobj=tar_buffer, + mode=f"w:{self.config['compression_format']}", + ) + # Set a unique timestamp for all extracted files + mtime = time.time() + + inodes_iter = Files.get_inodes( + context=self.context, + vmlinux_module_name=vmlinux_module_name, + follow_symlinks=False, + ) + visited_paths = set() + for inode_in in inodes_iter: + if inode_in.path in visited_paths: + continue + visited_paths.add(inode_in.path) + extracted_file_size = renderers.NotApplicableValue() + + # Inodes parent directory is yielded first, which + # ensures that a file parent path will exist beforehand. + # tarfile will take care of creating it anyway. + if inode_in.inode.is_reg: + extracted_file_size = self._tar_add_reg_inode( + self.context, vmlinux_layer.name, tar, inode_in, mtime + ) + elif inode_in.inode.is_dir: + self._tar_add_dir_inode(tar, inode_in, mtime) + elif ( + inode_in.inode.is_link + and inode_in.inode.has_member("i_link") + and inode_in.inode.i_link + and inode_in.inode.i_link.is_readable() + ): + symlink_dest = inode_in.inode.i_link.dereference().cast( + "string", max_length=255, encoding="utf-8", errors="replace" + ) + self._tar_add_lnk(tar, inode_in.path, symlink_dest, mtime) + # Set path to a user friendly representation before yielding + inode_in.path = InodeUser.format_symlink(inode_in.path, symlink_dest) + else: + continue + + inode_out = inode_in.to_user(vmlinux_layer) + yield (0, astuple(inode_out) + (extracted_file_size,)) + + tar.close() + tar_buffer.seek(0) + output_filename = f"recovered_fs.tar.{self.config['compression_format']}" + with self.open(output_filename) as f: + f.write(tar_buffer.getvalue()) + + def run(self): + headers = [ + ("SuperblockAddr", format_hints.Hex), + ("MountPoint", str), + ("Device", str), + ("InodeNum", int), + ("InodeAddr", format_hints.Hex), + ("FileType", str), + ("InodePages", int), + ("CachedPages", int), + ("FileMode", str), + ("AccessTime", datetime.datetime), + ("ModificationTime", datetime.datetime), + ("ChangeTime", datetime.datetime), + ("FilePath", str), + ("InodeSize", int), + ("Recovered FileSize", int), + ] + + return renderers.TreeGrid( + headers, Files.format_fields_with_headers(headers, self._generator()) + ) From 773231280933319e8433932521298808d618b146 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 11 Feb 2025 17:45:12 +0100 Subject: [PATCH 102/120] switch property to functools.cached_property --- volatility3/framework/symbols/linux/extensions/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index b4dc1ac20..d5959823a 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1057,11 +1057,11 @@ class super_block(objects.StructType): SB_LAZYTIME: "lazytime", } - @property + @functools.cached_property def major(self) -> int: return self.s_dev >> self.MINORBITS - @property + @functools.cached_property def minor(self) -> int: return self.s_dev & ((1 << self.MINORBITS) - 1) From ec61da6dac21fb2a285f300daf9b471f9017eee3 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 11 Feb 2025 17:45:31 +0100 Subject: [PATCH 103/120] add uuid property to super_block --- .../symbols/linux/extensions/__init__.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index d5959823a..927858ca7 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -10,6 +10,7 @@ import binascii import stat import datetime import socket as socket_module +import uuid from typing import ( Generator, Iterable, @@ -1065,6 +1066,20 @@ class super_block(objects.StructType): def minor(self) -> int: return self.s_dev & ((1 << self.MINORBITS) - 1) + @functools.cached_property + def uuid(self) -> str: + if not self.has_member("s_uuid"): + raise AttributeError( + "super_block struct does not support s_uuid direct attribute access, probably indicating a kernel version < 2.6.39-rc1." + ) + + if self.s_uuid.has_member("b"): + uuid_as_ints = self.s_uuid.b + else: + uuid_as_ints = self.s_uuid + + return str(uuid.UUID(bytes=bytes(uuid_as_ints))) + def get_flags_access(self) -> str: return "ro" if self.s_flags & self.SB_RDONLY else "rw" From f82fd5375351dd554275eea04422a817b65a58ad Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 11 Feb 2025 17:46:03 +0100 Subject: [PATCH 104/120] 2.20.1 -> 2.21.0 bump --- volatility3/framework/constants/_version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/constants/_version.py b/volatility3/framework/constants/_version.py index 3aca23898..0393a9669 100644 --- a/volatility3/framework/constants/_version.py +++ b/volatility3/framework/constants/_version.py @@ -1,7 +1,7 @@ # We use the SemVer 2.0.0 versioning scheme VERSION_MAJOR = 2 # Number of releases of the library with a breaking change -VERSION_MINOR = 20 # Number of changes that only add to the interface -VERSION_PATCH = 1 # Number of changes that do not change the interface +VERSION_MINOR = 21 # Number of changes that only add to the interface +VERSION_PATCH = 0 # Number of changes that do not change the interface VERSION_SUFFIX = "" PACKAGE_VERSION = ( From 999f3d0d2ce67aa8d41ee03af10045c9f42cb6b0 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 11 Feb 2025 17:48:34 +0100 Subject: [PATCH 105/120] add superblock or device numbers path prepending --- .../framework/plugins/linux/pagecache.py | 55 +++++++++++++------ 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index d0c085719..2a137fc89 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -633,11 +633,8 @@ class InodePages(plugins.PluginInterface): class RecoverFs(plugins.PluginInterface): """Recovers the cached filesystem (directories, files, symlinks) into a compressed tarball. - Metadata aren't replicated to extracted objects and timestamps are set to the plugin run time. To prevent extraction errors related to long paths, please consider using https://github.com/mxmlnkn/ratarmount. - To mount: - "ratarmount recovered_fs.tar.gz ./recovered_fs_mounted/". - To unmount: - "umount ./recovered_fs_mounted/". + Details: level 0 directories are named after the UUID of the parent superblock; metadata aren't replicated to extracted objects; objects modification time is set to the plugin run time. + Troubleshooting: to fix extraction errors related to long paths, please consider using https://github.com/mxmlnkn/ratarmount. """ _version = (1, 0, 0) @@ -673,6 +670,7 @@ class RecoverFs(plugins.PluginInterface): layer_name: str, tar: tarfile.TarFile, reg_inode_in: InodeInternal, + path_prefix: str = "", mtime: float = None, ) -> int: """Extracts a REG inode content and writes it to a TarFile object. @@ -682,6 +680,7 @@ class RecoverFs(plugins.PluginInterface): layer_name: The name of the layer on which to operate tar: The TarFile object to write to reg_inode_in: The inode to extract content from + path_prefix: A custom path prefix to prepend the inode path with mtime: The modification time to set the TarInfo object to Returns: @@ -694,7 +693,7 @@ class RecoverFs(plugins.PluginInterface): inode_content_buffer.seek(0) handle_buffer_size = inode_content_buffer.getbuffer().nbytes - tar_info = tarfile.TarInfo(reg_inode_in.path) + tar_info = tarfile.TarInfo(path_prefix + reg_inode_in.path) # The tarfile module only has read support for sparse files: # https://docs.python.org/3.12/library/tarfile.html#tarfile.LNKTYPE:~:text=and%20longlink%20extensions%2C-,read%2Donly%20support,-for%20all%20variants tar_info.type = tarfile.REGTYPE @@ -707,20 +706,20 @@ class RecoverFs(plugins.PluginInterface): return handle_buffer_size @classmethod - def _tar_add_dir_inode( + def _tar_add_dir( cls, tar: tarfile.TarFile, - reg_dir_in: InodeInternal, + directory_path: str, mtime: float = None, ) -> None: """Adds a directory path to a TarFile object, based on a DIR inode. Args: tar: The TarFile object to write to - reg_dir_in: The inode to base the new directory on + directory_path: The directory path to create mtime: The modification time to set the TarInfo object to """ - tar_info = tarfile.TarInfo(reg_dir_in.path) + tar_info = tarfile.TarInfo(directory_path) tar_info.type = tarfile.DIRTYPE tar_info.mode = 0o755 if mtime is not None: @@ -774,11 +773,30 @@ class RecoverFs(plugins.PluginInterface): vmlinux_module_name=vmlinux_module_name, follow_symlinks=False, ) - visited_paths = set() + + # Prefix paths by the super_block uuid's to prevent overlaps. + # Switch to device major and device minor for older kernels (< 2.6.39-rc1). + uuid_as_prefix = vmlinux.get_type("super_block").has_member("s_uuid") + if not uuid_as_prefix: + vollog.warning( + "super_block struct does not support s_uuid attribute. Consequently, level 0 directories won't refer to the superblock uuid's, but to its device_major:device_minor numbers." + ) + + visited_paths = seen_prefixes = set() for inode_in in inodes_iter: - if inode_in.path in visited_paths: + if uuid_as_prefix: + prefix = f"/{inode_in.superblock.uuid}" + else: + prefix = f"/{inode_in.superblock.major}:{inode_in.superblock.minor}" + prefixed_path = prefix + inode_in.path + + if prefixed_path in visited_paths: continue - visited_paths.add(inode_in.path) + elif prefix not in seen_prefixes: + self._tar_add_dir(tar, prefix, mtime) + seen_prefixes.add(prefix) + + visited_paths.add(prefixed_path) extracted_file_size = renderers.NotApplicableValue() # Inodes parent directory is yielded first, which @@ -786,10 +804,15 @@ class RecoverFs(plugins.PluginInterface): # tarfile will take care of creating it anyway. if inode_in.inode.is_reg: extracted_file_size = self._tar_add_reg_inode( - self.context, vmlinux_layer.name, tar, inode_in, mtime + self.context, + vmlinux_layer.name, + tar, + inode_in, + prefix, + mtime, ) elif inode_in.inode.is_dir: - self._tar_add_dir_inode(tar, inode_in, mtime) + self._tar_add_dir(tar, prefixed_path, mtime) elif ( inode_in.inode.is_link and inode_in.inode.has_member("i_link") @@ -799,7 +822,7 @@ class RecoverFs(plugins.PluginInterface): symlink_dest = inode_in.inode.i_link.dereference().cast( "string", max_length=255, encoding="utf-8", errors="replace" ) - self._tar_add_lnk(tar, inode_in.path, symlink_dest, mtime) + self._tar_add_lnk(tar, prefixed_path, symlink_dest, mtime) # Set path to a user friendly representation before yielding inode_in.path = InodeUser.format_symlink(inode_in.path, symlink_dest) else: From de42e58bfa96cf9208fe8e426716056ff44b2c31 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 11 Feb 2025 17:54:19 +0100 Subject: [PATCH 106/120] require framework v2.21.0 for recoverfs --- volatility3/framework/plugins/linux/pagecache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 2a137fc89..d2c201bbe 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -638,7 +638,7 @@ class RecoverFs(plugins.PluginInterface): """ _version = (1, 0, 0) - _required_framework_version = (2, 0, 0) + _required_framework_version = (2, 21, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: From 1530e62a8626793edb1ac29a92f6273fcdc7ce1f Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Tue, 11 Feb 2025 17:59:19 +0100 Subject: [PATCH 107/120] typo --- volatility3/framework/plugins/linux/pagecache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index d2c201bbe..730c6d6e6 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -774,7 +774,7 @@ class RecoverFs(plugins.PluginInterface): follow_symlinks=False, ) - # Prefix paths by the super_block uuid's to prevent overlaps. + # Prefix paths with the superblock UUID's to prevent overlaps. # Switch to device major and device minor for older kernels (< 2.6.39-rc1). uuid_as_prefix = vmlinux.get_type("super_block").has_member("s_uuid") if not uuid_as_prefix: From 443cfd25158e8e2a114ea6c504280729dd8dcf9b Mon Sep 17 00:00:00 2001 From: David McDonald Date: Wed, 12 Feb 2025 10:02:31 -0600 Subject: [PATCH 108/120] Windows Envars: Fix unbound locals Technically, these were protected against `UnboundLocalError` exceptions through the use of the `sys` and `ntuser` boolean variables, but this really isn't the best way to prevent that from happening, and type-checkers still warn about the potentially unbound locals. This fix instead uses the `sys` and `ntuser` variables to hold the registry keys, preinitializing them to `None` and checking their value before attempting to access instance methods. --- .../framework/plugins/windows/envars.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/volatility3/framework/plugins/windows/envars.py b/volatility3/framework/plugins/windows/envars.py index 48e1ef671..4c98ffe5e 100644 --- a/volatility3/framework/plugins/windows/envars.py +++ b/volatility3/framework/plugins/windows/envars.py @@ -67,24 +67,20 @@ class Envars(interfaces.plugins.PluginInterface): symbol_table=kernel.symbol_table_name, hive_offsets=None, ): - sys = False - ntuser = False - ## The global variables + sys = None try: - key = hive.get_key( + sys = hive.get_key( "CurrentControlSet\\Control\\Session Manager\\Environment" ) - sys = True except (KeyError, registry.RegistryFormatException): with contextlib.suppress(KeyError, registry.RegistryFormatException): - key = hive.get_key( + sys = hive.get_key( "ControlSet001\\Control\\Session Manager\\Environment" ) - sys = True if sys: with contextlib.suppress(KeyError, registry.RegistryFormatException): - for node in key.get_values(): + for node in sys.get_values(): try: value_node_name = node.get_name() if value_node_name: @@ -99,13 +95,13 @@ class Envars(interfaces.plugins.PluginInterface): ) continue + ntuser = None ## The user-specific variables with contextlib.suppress(KeyError, registry.RegistryFormatException): - key = hive.get_key("Environment") - ntuser = True + ntuser = hive.get_key("Environment") if ntuser: with contextlib.suppress(KeyError, registry.RegistryFormatException): - for node in key.get_values(): + for node in ntuser.get_values(): try: value_node_name = node.get_name() if value_node_name: From bcbfc27d4fc9364fe925abc57fa667e1bfb721ec Mon Sep 17 00:00:00 2001 From: David McDonald Date: Wed, 12 Feb 2025 10:11:23 -0600 Subject: [PATCH 109/120] Windows Cmdline: Clean up output The strings being used as return values here would (IMO) be better as debug statements, with the plugin returning `renderers.UnreadableValue()` for any of the `InvalidAddressException` code paths. --- volatility3/framework/plugins/windows/cmdline.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/windows/cmdline.py b/volatility3/framework/plugins/windows/cmdline.py index 9bd9eda0e..bad333a4c 100644 --- a/volatility3/framework/plugins/windows/cmdline.py +++ b/volatility3/framework/plugins/windows/cmdline.py @@ -70,6 +70,7 @@ class CmdLine(interfaces.plugins.PluginInterface): for proc in procs: process_name = utility.array_to_string(proc.ImageFileName) proc_id = "Unknown" + result_text = None try: proc_id = proc.UniqueProcessId @@ -78,13 +79,22 @@ class CmdLine(interfaces.plugins.PluginInterface): ) except exceptions.SwappedInvalidAddressException as exp: - result_text = f"Required memory at {exp.invalid_address:#x} is inaccessible (swapped)" + vollog.debug( + f"Required memory at {exp.invalid_address:#x} is inaccessible (swapped)" + ) except exceptions.PagedInvalidAddressException as exp: - result_text = f"Required memory at {exp.invalid_address:#x} is not valid (process exited?)" + vollog.debug( + f"Required memory at {exp.invalid_address:#x} is not valid (process exited?)" + ) except exceptions.InvalidAddressException as exp: - result_text = f"Process {proc_id}: Required memory at {exp.invalid_address:#x} is not valid (incomplete layer {exp.layer_name}?)" + vollog.debug( + f"Process {proc_id}: Required memory at {exp.invalid_address:#x} is not valid (incomplete layer {exp.layer_name}?)" + ) + + if not result_text: + result_text = renderers.UnreadableValue() yield (0, (proc.UniqueProcessId, process_name, result_text)) From 4712cafa4befc72d451acb71ea370fb1ac21f0d9 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Wed, 12 Feb 2025 10:22:03 -0600 Subject: [PATCH 110/120] Windows Envars: Remove extra config check Tiny bit of cleanup here - there's no need to re-check the config for `SILENT` for each variable. We can just initialize it once as either an empty list or the calculated list of silent vars depending on the config value. --- volatility3/framework/plugins/windows/envars.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/plugins/windows/envars.py b/volatility3/framework/plugins/windows/envars.py index 48e1ef671..f76b00f7a 100644 --- a/volatility3/framework/plugins/windows/envars.py +++ b/volatility3/framework/plugins/windows/envars.py @@ -200,15 +200,13 @@ class Envars(interfaces.plugins.PluginInterface): return values def _generator(self, data): - silent_vars = [] - if self.config.get("SILENT", None): - silent_vars = self._get_silent_vars() + silent_vars = self._get_silent_vars() if self.config.get("SILENT") else [] for task in data: for var, val in task.environment_variables(): - if self.config.get("silent", None): - if var in silent_vars: - continue + if var in silent_vars: + continue + yield ( 0, ( From 9b78099a0871bbee02ea534d0384c1119c1dac31 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 14 Feb 2025 19:24:26 +0000 Subject: [PATCH 111/120] Enforce kernel boundaries correctly. Fix bugs. Closes #1474 --- .../framework/plugins/windows/modules.py | 30 +++++++++++++- .../plugins/windows/orphan_kernel_threads.py | 41 ++++++++++++------- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/volatility3/framework/plugins/windows/modules.py b/volatility3/framework/plugins/windows/modules.py index 62a622491..75b15217a 100644 --- a/volatility3/framework/plugins/windows/modules.py +++ b/volatility3/framework/plugins/windows/modules.py @@ -4,7 +4,7 @@ import logging from typing import Generator, Iterable, List, Optional -from volatility3.framework import constants, exceptions, interfaces, renderers +from volatility3.framework import symbols, constants, exceptions, interfaces, renderers from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import intermed @@ -18,7 +18,7 @@ class Modules(interfaces.plugins.PluginInterface): """Lists the loaded kernel modules.""" _required_framework_version = (2, 0, 0) - _version = (2, 0, 1) + _version = (2, 0, 2) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -127,6 +127,32 @@ class Modules(interfaces.plugins.PluginInterface): file_output, ) + @classmethod + def get_kernel_space_start(cls, context, layer_name: str, module_name: str) -> int: + """ + Returns the starting address of the kernel address space + + This method allows plugins that analyze kernel data structures to quickly detect + smeared or otherwise invalid data as many pointers must point into the kernel or + access during runtime would crash the system + """ + module = context.modules[module_name] + + if symbols.symbol_table_is_64bit(context, module.symbol_table_name): + object_type = "unsigned long long" + else: + object_type = "unsigned long" + + range_start_offset = module.get_symbol("MmSystemRangeStart").address + + kernel_space_start = module.object( + object_type=object_type, offset=range_start_offset + ) + + layer = context.layers[layer_name] + + return kernel_space_start & layer.address_mask + @classmethod def get_session_layers( cls, diff --git a/volatility3/framework/plugins/windows/orphan_kernel_threads.py b/volatility3/framework/plugins/windows/orphan_kernel_threads.py index f4901dc8c..7a865c675 100644 --- a/volatility3/framework/plugins/windows/orphan_kernel_threads.py +++ b/volatility3/framework/plugins/windows/orphan_kernel_threads.py @@ -5,9 +5,9 @@ import logging from typing import List, Generator -from volatility3.framework import interfaces, symbols +from volatility3.framework import interfaces, exceptions from volatility3.framework.configuration import requirements -from volatility3.plugins.windows import thrdscan, ssdt +from volatility3.plugins.windows import thrdscan, ssdt, modules vollog = logging.getLogger(__name__) @@ -37,6 +37,9 @@ class Threads(thrdscan.ThrdScan): requirements.PluginRequirement( name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0) ), + requirements.PluginRequirement( + name="modules", plugin=modules.Modules, version=(2, 0, 2) + ), ] @classmethod @@ -56,24 +59,27 @@ class Threads(thrdscan.ThrdScan): """ module = context.modules[module_name] layer_name = module.layer_name - symbol_table = module.symbol_table_name + symbol_table_name = module.symbol_table_name collection = ssdt.SSDT.build_module_collection( - context, layer_name, symbol_table + context, layer_name, symbol_table_name ) - # FIXME - use a proper constant once established - # used to filter out smeared pointers - if symbols.symbol_table_is_64bit(context, symbol_table): - kernel_start = 0xFFFFF80000000000 - else: - kernel_start = 0x80000000 + kernel_space_start = modules.Modules.get_kernel_space_start( + context, layer_name, module_name + ) for thread in thrdscan.ThrdScan.scan_threads(context, module_name): - # we don't want smeared or terminated threads + # We don't want smeared or terminated threads + # So we access the owning process (which could also be terminated or smeared) + # Plus check the start address holding page try: proc = thread.owning_process() - except AttributeError: + pid = proc.UniqueProcessId + ppid = proc.InheritedFromUniqueProcessId + + thread_start = thread.StartAddress + except (AttributeError, exceptions.InvalidAddressException): continue # we only care about kernel threads, 4 = System @@ -81,14 +87,19 @@ class Threads(thrdscan.ThrdScan): # such as bit fields and flags are not stable in Win10+ # so we check if the thread is from the kernel itself or one its child # kernel processes (MemCompression, Regsitry, ...) - if proc.UniqueProcessId != 4 and proc.InheritedFromUniqueProcessId != 4: + if pid != 4 and ppid != 4: continue - if thread.StartAddress < kernel_start: + # if the thread has an exit time or terminated (4) state, then skip it + if thread.ExitTime.QuadPart > 0 or thread.Tcb.State == 4: + continue + + # threads pointing into userland, which is from smeared or terminated threads + if thread_start < kernel_space_start: continue module_symbols = list( - collection.get_module_symbols_by_absolute_location(thread.StartAddress) + collection.get_module_symbols_by_absolute_location(thread_start) ) # alert on threads that do not map to a module From 92db0e3f08006501f37ea1ae3379f7382d025efa Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 14 Feb 2025 20:42:00 +0000 Subject: [PATCH 112/120] Address feedback --- volatility3/framework/plugins/windows/modules.py | 6 +++--- .../framework/plugins/windows/orphan_kernel_threads.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/plugins/windows/modules.py b/volatility3/framework/plugins/windows/modules.py index 75b15217a..a21a87bbd 100644 --- a/volatility3/framework/plugins/windows/modules.py +++ b/volatility3/framework/plugins/windows/modules.py @@ -18,7 +18,7 @@ class Modules(interfaces.plugins.PluginInterface): """Lists the loaded kernel modules.""" _required_framework_version = (2, 0, 0) - _version = (2, 0, 2) + _version = (2, 1, 0) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -128,7 +128,7 @@ class Modules(interfaces.plugins.PluginInterface): ) @classmethod - def get_kernel_space_start(cls, context, layer_name: str, module_name: str) -> int: + def get_kernel_space_start(cls, context, module_name: str) -> int: """ Returns the starting address of the kernel address space @@ -149,7 +149,7 @@ class Modules(interfaces.plugins.PluginInterface): object_type=object_type, offset=range_start_offset ) - layer = context.layers[layer_name] + layer = context.layers[module.layer_name] return kernel_space_start & layer.address_mask diff --git a/volatility3/framework/plugins/windows/orphan_kernel_threads.py b/volatility3/framework/plugins/windows/orphan_kernel_threads.py index 7a865c675..18e087553 100644 --- a/volatility3/framework/plugins/windows/orphan_kernel_threads.py +++ b/volatility3/framework/plugins/windows/orphan_kernel_threads.py @@ -38,7 +38,7 @@ class Threads(thrdscan.ThrdScan): name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0) ), requirements.PluginRequirement( - name="modules", plugin=modules.Modules, version=(2, 0, 2) + name="modules", plugin=modules.Modules, version=(2, 1, 0) ), ] @@ -66,7 +66,7 @@ class Threads(thrdscan.ThrdScan): ) kernel_space_start = modules.Modules.get_kernel_space_start( - context, layer_name, module_name + context, module_name ) for thread in thrdscan.ThrdScan.scan_threads(context, module_name): From 6babe158f0ad9d02a6221694babe457c426d8016 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sat, 15 Feb 2025 00:35:31 +0000 Subject: [PATCH 113/120] Address feedback --- .../framework/plugins/windows/dumpfiles.py | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/volatility3/framework/plugins/windows/dumpfiles.py b/volatility3/framework/plugins/windows/dumpfiles.py index b10c519e7..9ce9e3141 100755 --- a/volatility3/framework/plugins/windows/dumpfiles.py +++ b/volatility3/framework/plugins/windows/dumpfiles.py @@ -47,13 +47,13 @@ class DumpFiles(interfaces.plugins.PluginInterface): requirements.ListRequirement( name="virtaddr", element_type=int, - description="Dump a single _FILE_OBJECT at this virtual address", + description="Dump the _FILE_OBJECTs at the given virtual address(es)", optional=True, ), requirements.ListRequirement( name="physaddr", element_type=int, - description="Dump a single _FILE_OBJECT at this physical address", + description="Dump a single _FILE_OBJECTs at the given physical address(es)", optional=True, ), requirements.StringRequirement( @@ -320,25 +320,24 @@ class DumpFiles(interfaces.plugins.PluginInterface): ) elif offsets: + virtual_layer_name = kernel.layer_name - # Now process any offsets explicitly requested by the user. + #FIXME - change this after standard access to physical layer + physical_layer_name = self.context.layers[virtual_layer_name].config[ + "memory_layer" + ] + + # Now process any offsets explicitly requested by the user. for offset, is_virtual in offsets: try: - layer_name = kernel.layer_name - # switch to a memory layer if the user provided --physaddr instead of --virtaddr - if not is_virtual: - layer_name = self.context.layers[layer_name].config[ - "memory_layer" - ] - file_obj = self.context.object( kernel.symbol_table_name + constants.BANG + "_FILE_OBJECT", - layer_name=layer_name, - native_layer_name=kernel.layer_name, + layer_name=virtual_layer_name if is_virtual else physical_layer_name, + native_layer_name=virtual_layer_name, offset=offset, ) for result in self.process_file_object( - self.context, kernel.layer_name, self.open, file_obj + self.context, virtual_layer_name, self.open, file_obj ): yield (0, result) except exceptions.InvalidAddressException: @@ -362,11 +361,11 @@ class DumpFiles(interfaces.plugins.PluginInterface): for virtaddr in self.config["virtaddr"]: offsets.append((virtaddr, True)) - elif self.config.get("physaddr"): + if self.config.get("physaddr"): for physaddr in self.config["physaddr"]: offsets.append((physaddr, False)) - else: + if not offsets: filter_func = pslist.PsList.create_pid_filter( [self.config.get("pid", None)] ) From 49b40eb30a31618630ac9106b171a4771f3594d7 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sat, 15 Feb 2025 00:36:12 +0000 Subject: [PATCH 114/120] Make black happy --- volatility3/framework/plugins/windows/dumpfiles.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/windows/dumpfiles.py b/volatility3/framework/plugins/windows/dumpfiles.py index 9ce9e3141..42f245800 100755 --- a/volatility3/framework/plugins/windows/dumpfiles.py +++ b/volatility3/framework/plugins/windows/dumpfiles.py @@ -322,17 +322,19 @@ class DumpFiles(interfaces.plugins.PluginInterface): elif offsets: virtual_layer_name = kernel.layer_name - #FIXME - change this after standard access to physical layer + # FIXME - change this after standard access to physical layer physical_layer_name = self.context.layers[virtual_layer_name].config[ "memory_layer" ] - # Now process any offsets explicitly requested by the user. + # Now process any offsets explicitly requested by the user. for offset, is_virtual in offsets: try: file_obj = self.context.object( kernel.symbol_table_name + constants.BANG + "_FILE_OBJECT", - layer_name=virtual_layer_name if is_virtual else physical_layer_name, + layer_name=( + virtual_layer_name if is_virtual else physical_layer_name + ), native_layer_name=virtual_layer_name, offset=offset, ) From 9484430036cb6cbe61ee9ee78da418b21ebe6226 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sat, 15 Feb 2025 13:16:58 +0100 Subject: [PATCH 115/120] convert private classmethods to simple methods --- volatility3/framework/plugins/linux/pagecache.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 730c6d6e6..4b00e456b 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -663,9 +663,8 @@ class RecoverFs(plugins.PluginInterface): ), ] - @classmethod def _tar_add_reg_inode( - cls, + self, context: interfaces.context.ContextInterface, layer_name: str, tar: tarfile.TarFile, @@ -705,9 +704,8 @@ class RecoverFs(plugins.PluginInterface): return handle_buffer_size - @classmethod def _tar_add_dir( - cls, + self, tar: tarfile.TarFile, directory_path: str, mtime: float = None, @@ -726,9 +724,8 @@ class RecoverFs(plugins.PluginInterface): tar_info.mtime = mtime tar.addfile(tar_info) - @classmethod def _tar_add_lnk( - cls, + self, tar: tarfile.TarFile, symlink_source: str, symlink_dest: str, From 51b66f7573ef568e2873909b0e1eac234ab69ceb Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sat, 15 Feb 2025 14:14:02 +0100 Subject: [PATCH 116/120] add symlink details in description --- volatility3/framework/plugins/linux/pagecache.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 4b00e456b..5ebdf67d9 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -633,7 +633,8 @@ class InodePages(plugins.PluginInterface): class RecoverFs(plugins.PluginInterface): """Recovers the cached filesystem (directories, files, symlinks) into a compressed tarball. - Details: level 0 directories are named after the UUID of the parent superblock; metadata aren't replicated to extracted objects; objects modification time is set to the plugin run time. + Details: level 0 directories are named after the UUID of the parent superblock; metadata aren't replicated to extracted objects; objects modification time is set to the plugin run time; absolute symlinks + are converted to relative symlinks to prevent referencing the analyst filesystem. Troubleshooting: to fix extraction errors related to long paths, please consider using https://github.com/mxmlnkn/ratarmount. """ From c2f8697cc589385bea752905ec8b975a19ef0e3b Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sat, 15 Feb 2025 15:24:53 +0100 Subject: [PATCH 117/120] normalize symlinks by relying on purepath --- .../framework/plugins/linux/pagecache.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 5ebdf67d9..29883cb9e 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -10,9 +10,10 @@ import tarfile from dataclasses import dataclass, astuple from typing import IO, List, Set, Type, Iterable, Tuple from io import BytesIO +from pathlib import PurePath from volatility3.framework.constants import architectures -from volatility3.framework import renderers, interfaces, exceptions +from volatility3.framework import constants, renderers, interfaces, exceptions from volatility3.framework.renderers import format_hints from volatility3.framework.interfaces import plugins from volatility3.framework.configuration import requirements @@ -730,6 +731,7 @@ class RecoverFs(plugins.PluginInterface): tar: tarfile.TarFile, symlink_source: str, symlink_dest: str, + symlink_source_prefix: str = "", mtime: float = None, ) -> None: """Adds a symlink to a TarFile object. @@ -738,15 +740,21 @@ class RecoverFs(plugins.PluginInterface): tar: The TarFile object to write to symlink_source: The symlink source path symlink_dest: The symlink target/destination + symlink_source_prefix: A custom path prefix to prepend the symlink source with mtime: The modification time to set the TarInfo object to """ # Patch symlinks pointing to absolute paths, # to prevent referencing the host filesystem. if symlink_dest.startswith("/"): - inode_depth = symlink_source.strip("/").count("/") - symlink_dest = "../" * inode_depth + symlink_dest.lstrip("/") - - tar_info = tarfile.TarInfo(symlink_source) + relative_dest = PurePath(symlink_dest).relative_to(PurePath("/")) + # Remove the leading "/" to prevent an extra undesired "../" in the output + symlink_dest = ( + PurePath( + *[".."] * len(PurePath(symlink_source.lstrip("/")).parent.parts) + ) + / relative_dest + ).as_posix() + tar_info = tarfile.TarInfo(symlink_source_prefix + symlink_source) tar_info.type = tarfile.SYMTYPE tar_info.linkname = symlink_dest tar_info.mode = 0o444 @@ -820,7 +828,7 @@ class RecoverFs(plugins.PluginInterface): symlink_dest = inode_in.inode.i_link.dereference().cast( "string", max_length=255, encoding="utf-8", errors="replace" ) - self._tar_add_lnk(tar, prefixed_path, symlink_dest, mtime) + self._tar_add_lnk(tar, inode_in.path, symlink_dest, prefix, mtime) # Set path to a user friendly representation before yielding inode_in.path = InodeUser.format_symlink(inode_in.path, symlink_dest) else: From b86e54e4278e18159be1ae9e44584e2c6b975f26 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sat, 15 Feb 2025 15:25:26 +0100 Subject: [PATCH 118/120] extra checks and debug messages --- .../framework/plugins/linux/pagecache.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 29883cb9e..6d9b0c69b 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -790,13 +790,34 @@ class RecoverFs(plugins.PluginInterface): visited_paths = seen_prefixes = set() for inode_in in inodes_iter: + + # Code is slightly duplicated here with the if-block below. + # However this prevents unneeded tar manipulation if fifo + # or sock inodes comes through for example. + if not ( + inode_in.inode.is_reg or inode_in.inode.is_dir or inode_in.inode.is_link + ): + continue + + if not inode_in.path.startswith("/"): + vollog.debug( + f'Skipping processing of potentially smeared "{inode_in.path}" inode name as it does not starts with a "/".' + ) + continue + + # Construct the output path if uuid_as_prefix: prefix = f"/{inode_in.superblock.uuid}" else: prefix = f"/{inode_in.superblock.major}:{inode_in.superblock.minor}" prefixed_path = prefix + inode_in.path + # Sanity check for already processed paths if prefixed_path in visited_paths: + vollog.log( + constants.LOGLEVEL_VV, + f'Already processed prefixed inode path: "{prefixed_path}".', + ) continue elif prefix not in seen_prefixes: self._tar_add_dir(tar, prefix, mtime) From 2bf0a26c7371cc1d1843acaa8c2338f2bf75dd87 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sat, 15 Feb 2025 15:28:23 +0100 Subject: [PATCH 119/120] typos --- volatility3/framework/plugins/linux/pagecache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 6d9b0c69b..7a1cf2506 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -635,7 +635,7 @@ class RecoverFs(plugins.PluginInterface): """Recovers the cached filesystem (directories, files, symlinks) into a compressed tarball. Details: level 0 directories are named after the UUID of the parent superblock; metadata aren't replicated to extracted objects; objects modification time is set to the plugin run time; absolute symlinks - are converted to relative symlinks to prevent referencing the analyst filesystem. + are converted to relative symlinks to prevent referencing the analyst's filesystem. Troubleshooting: to fix extraction errors related to long paths, please consider using https://github.com/mxmlnkn/ratarmount. """ @@ -793,7 +793,7 @@ class RecoverFs(plugins.PluginInterface): # Code is slightly duplicated here with the if-block below. # However this prevents unneeded tar manipulation if fifo - # or sock inodes comes through for example. + # or sock inodes come through for example. if not ( inode_in.inode.is_reg or inode_in.inode.is_dir or inode_in.inode.is_link ): From d5976146fb53428a6af7a1a4be128f4e21349201 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 16 Feb 2025 18:24:23 +0000 Subject: [PATCH 120/120] Layers: Restore get_valid_table caching Partially restore performance as per #1618 --- volatility3/framework/layers/intel.py | 1 + 1 file changed, 1 insertion(+) diff --git a/volatility3/framework/layers/intel.py b/volatility3/framework/layers/intel.py index b6f59fee1..55b930177 100644 --- a/volatility3/framework/layers/intel.py +++ b/volatility3/framework/layers/intel.py @@ -284,6 +284,7 @@ class Intel(linear.LinearlyMappedLayer): return entry, position + @functools.lru_cache(maxsize=1025) def _get_valid_table(self, base_address: int) -> Optional[bytes]: """Extracts the table, validates it and returns it if it's valid.""" table = self._context.layers.read(