From 48a5736a576a5b9c6119d64d85a46c8cc370bc31 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Thu, 13 Mar 2025 21:09:38 +0000 Subject: [PATCH 01/78] Properly reconstruct strings from memory buffers and allow for plugin-specified encodings --- volatility3/framework/constants/_version.py | 2 +- volatility3/framework/objects/utility.py | 27 ++++++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/volatility3/framework/constants/_version.py b/volatility3/framework/constants/_version.py index aa8e8936f..1ea59c068 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 = 23 # Number of changes that only add to the interface +VERSION_MINOR = 24 # Number of changes that only add to the interface VERSION_PATCH = 0 # Number of changes that do not change the interface VERSION_SUFFIX = "" diff --git a/volatility3/framework/objects/utility.py b/volatility3/framework/objects/utility.py index 500c0e9a5..1fcd305f7 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -2,6 +2,7 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # +import re from typing import Optional, Union from volatility3.framework import interfaces, objects, constants @@ -33,6 +34,7 @@ def array_to_string( count: Optional[int] = None, errors: str = "replace", block_size=32, + encoding="utf-8", ) -> str: """Takes a Volatility 'Array' of characters and returns a Python string. @@ -60,6 +62,7 @@ def array_to_string( count=count, errors=errors, block_size=block_size, + encoding=encoding, ) @@ -68,6 +71,7 @@ def pointer_to_string( count: int, errors: str = "replace", block_size=32, + encoding="utf-8", ) -> str: """Takes a Volatility 'Pointer' to characters and returns a Python string. @@ -94,6 +98,7 @@ def pointer_to_string( count=count, errors=errors, block_size=block_size, + encoding=encoding, ) @@ -104,6 +109,7 @@ def address_to_string( count: int, errors: str = "replace", block_size=32, + encoding="utf-8", ) -> str: """Reads a null-terminated string from a given specified memory address, processing it in blocks for efficiency. @@ -126,18 +132,17 @@ def address_to_string( raise ValueError("Count must be greater than 0") 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) + # Purposely do not catch exception + data = layer.read(address, count) + + decoded_data = data.decode(encoding=encoding, errors=errors) + try: + idx = re.search("\ufffd|\x00", decoded_data).start() + except AttributeError: + idx = len(decoded_data) + + return decoded_data[:idx] def array_of_pointers( From 6f9f5c34b9767ed8dd531da0b0ba85c42fcc6fab Mon Sep 17 00:00:00 2001 From: David McDonald Date: Mon, 10 Mar 2025 11:03:18 -0500 Subject: [PATCH 02/78] Feature: Add support for IPython in volshell --- volatility3/cli/volshell/generic.py | 70 +++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 2321408fe..8f915ac4a 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -8,6 +8,7 @@ import random import string import struct import sys +import textwrap from typing import Any, Dict, Iterable, List, Optional, Tuple, Type, Union from urllib import parse, request @@ -23,6 +24,14 @@ try: except ImportError: has_capstone = False +try: + from IPython import terminal + from traitlets import config as traitlets_config + + has_ipython = True +except ImportError: + has_ipython = False + class Volshell(interfaces.plugins.PluginInterface): """Shell environment to directly interact with a memory image.""" @@ -69,43 +78,66 @@ class Volshell(interfaces.plugins.PluginInterface): """ # Try to enable tab completion - try: - import readline - except ImportError: - pass - else: - import rlcompleter + if not has_ipython: + try: + import readline + except ImportError: + pass + else: + import rlcompleter - completer = rlcompleter.Completer(namespace=self._construct_locals_dict()) - readline.set_completer(completer.complete) - readline.parse_and_bind("tab: complete") - print("Readline imported successfully") + completer = rlcompleter.Completer( + namespace=self._construct_locals_dict() + ) + readline.set_completer(completer.complete) + readline.parse_and_bind("tab: complete") + print("Readline imported successfully") # TODO: provide help, consider generic functions (pslist?) and/or providing windows/linux functions mode = self.__module__.split(".")[-1] mode = mode[0].upper() + mode[1:] - banner = f""" - Call help() to see available functions + banner = textwrap.dedent( + f""" + Call help() to see available functions - Volshell mode : {mode} - Current Layer : {self.current_layer} - Current Symbol Table : {self.current_symbol_table} - Current Kernel Name : {self.current_kernel_name} -""" + Volshell mode : {mode} + Current Layer : {self.current_layer} + Current Symbol Table : {self.current_symbol_table} + Current Kernel Name : {self.current_kernel_name} + """ + ) sys.ps1 = f"({self.current_layer}) >>> " # Dict self._construct_locals_dict() will have priority on keys combined_locals = additional_locals.copy() combined_locals.update(self._construct_locals_dict()) - self.__console = code.InteractiveConsole(locals=combined_locals) + if has_ipython: + + class LayerNamePrompt(terminal.prompts.Prompts): + def in_prompt_tokens(self, cli=None): + slf = self.shell.user_ns.get("self") + layer_name = slf.current_layer if slf else "no_layer" + return [(terminal.prompts.Token.Prompt, f"[{layer_name}]> ")] + + c = traitlets_config.Config() + c.TerminalInteractiveShell.prompts_class = LayerNamePrompt + c.InteractiveShellEmbed.banner2 = banner + self.__console = terminal.embed.InteractiveShellEmbed( + config=c, user_ns=combined_locals + ) + else: + self.__console = code.InteractiveConsole(locals=combined_locals) # Since we have to do work to add the option only once for all different modes of volshell, we can't # rely on the default having been set if self.config.get("script", None) is not None: self.run_script(location=self.config["script"]) - self.__console.interact(banner=banner) + if has_ipython: + self.__console() + else: + self.__console.interact(banner=banner) return renderers.TreeGrid([("Terminating", str)], None) From fa2a93ade493380e94c1391a980777b477daeae8 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Thu, 13 Mar 2025 17:09:56 -0500 Subject: [PATCH 03/78] Volshell: Fix import logic around readline + rlcomplete --- volatility3/cli/volshell/generic.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 8f915ac4a..c45f9d1d4 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -81,9 +81,6 @@ class Volshell(interfaces.plugins.PluginInterface): if not has_ipython: try: import readline - except ImportError: - pass - else: import rlcompleter completer = rlcompleter.Completer( @@ -92,6 +89,8 @@ class Volshell(interfaces.plugins.PluginInterface): readline.set_completer(completer.complete) readline.parse_and_bind("tab: complete") print("Readline imported successfully") + except ImportError: + pass # TODO: provide help, consider generic functions (pslist?) and/or providing windows/linux functions From c3123e883944e27b89d1231fb823ba4e0ea328e0 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Thu, 13 Mar 2025 17:17:17 -0500 Subject: [PATCH 04/78] Volshell: Handle script running in ipython shell --- volatility3/cli/volshell/generic.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index c45f9d1d4..c86e221f7 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -539,10 +539,11 @@ class Volshell(interfaces.plugins.PluginInterface): location = "file:" + request.pathname2url(location) print(f"Running code from {location}\n") accessor = resources.ResourceAccessor() - with accessor.open(url=location) as fp: - self.__console.runsource( - io.TextIOWrapper(fp, encoding="utf-8").read(), symbol="exec" - ) + with io.TextIOWrapper(accessor.open(url=location), encoding="utf-8") as fp: + if has_ipython: + self.__console.ex(fp.read()) + else: + self.__console.runsource(fp.read(), symbol="exec") print("\nCode complete") def load_file(self, location: str): From d76288f99407c93b719c130b9da6ba86471446db Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Thu, 13 Mar 2025 22:53:51 +0000 Subject: [PATCH 05/78] Update nearly all callers of now deprecated Linux kernel APIs --- .../framework/plugins/linux/check_idt.py | 23 ++++++------ .../framework/plugins/linux/hidden_modules.py | 8 ++-- .../plugins/linux/keyboard_notifiers.py | 34 ++++++++++------- .../framework/plugins/linux/kthreads.py | 37 +++++++++---------- .../framework/plugins/linux/tty_check.py | 32 +++++++++------- 5 files changed, 69 insertions(+), 65 deletions(-) diff --git a/volatility3/framework/plugins/linux/check_idt.py b/volatility3/framework/plugins/linux/check_idt.py index dbdb0e9be..c85f291cc 100644 --- a/volatility3/framework/plugins/linux/check_idt.py +++ b/volatility3/framework/plugins/linux/check_idt.py @@ -10,7 +10,6 @@ from volatility3.framework import interfaces, renderers, symbols from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import linux -from volatility3.plugins.linux import lsmod vollog = logging.getLogger(__name__) @@ -39,9 +38,6 @@ class Check_idt(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) - ), ] @staticmethod @@ -82,10 +78,8 @@ class Check_idt(interfaces.plugins.PluginInterface): vmlinux = self.context.modules[self.config["kernel"]] - modules = lsmod.Lsmod.list_modules(self.context, vmlinux.name) - - handlers = linux.LinuxUtilities.generate_kernel_handler_info( - self.context, vmlinux.name, modules + known_modules = linux_utilities_modules.Modules.run_modules_scanners( + self.context, self.config["kernel"], run_hidden_modules=True ) idt_table_size = 256 @@ -134,19 +128,24 @@ class Check_idt(interfaces.plugins.PluginInterface): module_name = renderers.NotAvailableValue() symbol_name = renderers.NotAvailableValue() else: - module_name, symbol_name = ( - linux_utilities_modules.Modules.lookup_module_address( - self.context, vmlinux.name, handlers, idt_addr + module_info, symbol_name = ( + linux_utilities_modules.Modules.module_lookup_by_address( + self.context, vmlinux.name, known_modules, idt_addr ) ) + if module_info: + module_name = module_info.name + else: + module_name = renderers.NotAvailableValue() + yield ( 0, [ format_hints.Hex(i), format_hints.Hex(idt_addr), module_name, - symbol_name, + symbol_name or renderers.NotAvailableValue(), ], ) diff --git a/volatility3/framework/plugins/linux/hidden_modules.py b/volatility3/framework/plugins/linux/hidden_modules.py index 9891bf138..8999126b1 100644 --- a/volatility3/framework/plugins/linux/hidden_modules.py +++ b/volatility3/framework/plugins/linux/hidden_modules.py @@ -10,7 +10,6 @@ from volatility3.framework import renderers, interfaces, exceptions, deprecation from volatility3.framework.constants import architectures from volatility3.framework.renderers import format_hints from volatility3.framework.configuration import requirements -from volatility3.plugins.linux import lsmod vollog = logging.getLogger(__name__) @@ -29,9 +28,6 @@ class Hidden_modules(interfaces.plugins.PluginInterface): description="Linux kernel", architectures=architectures.LINUX_ARCHS, ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) - ), requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, @@ -163,7 +159,9 @@ class Hidden_modules(interfaces.plugins.PluginInterface): known_module_addresses = { vmlinux_layer.canonicalize(module.vol.offset) - for module in lsmod.Lsmod.list_modules(context, vmlinux_module_name) + for module in linux_utilities_modules.Modules.list_modules( + context, vmlinux_module_name + ) } return known_module_addresses diff --git a/volatility3/framework/plugins/linux/keyboard_notifiers.py b/volatility3/framework/plugins/linux/keyboard_notifiers.py index 8fd2846c1..beebc4248 100644 --- a/volatility3/framework/plugins/linux/keyboard_notifiers.py +++ b/volatility3/framework/plugins/linux/keyboard_notifiers.py @@ -9,7 +9,6 @@ from volatility3.framework import interfaces, renderers, exceptions from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import linux -from volatility3.plugins.linux import lsmod vollog = logging.getLogger(__name__) @@ -32,9 +31,6 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface): component=linux_utilities_modules.Modules, version=(2, 0, 0), ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) - ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) ), @@ -43,12 +39,6 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface): def _generator(self): vmlinux = self.context.modules[self.config["kernel"]] - modules = lsmod.Lsmod.list_modules(self.context, vmlinux.name) - - handlers = linux.LinuxUtilities.generate_kernel_handler_info( - self.context, vmlinux.name, modules - ) - try: knl_addr = vmlinux.object_from_symbol("keyboard_notifier_list") except exceptions.SymbolError: @@ -65,6 +55,10 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface): vollog.error("The head of the keyboard notifier list is paged out.") return + known_modules = linux_utilities_modules.Modules.run_modules_scanners( + self.context, self.config["kernel"], run_hidden_modules=True + ) + knl = vmlinux.object( object_type="atomic_notifier_head", offset=knl_addr.vol.offset, @@ -76,13 +70,25 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface): ): call_addr = call_back.notifier_call - module_name, symbol_name = ( - linux_utilities_modules.Modules.lookup_module_address( - self.context, vmlinux.name, handlers, call_addr + module_info, symbol_name = ( + linux_utilities_modules.Modules.module_lookup_by_address( + self.context, vmlinux.name, known_modules, call_addr ) ) - yield (0, [format_hints.Hex(call_addr), module_name, symbol_name]) + if module_info: + module_name = module_info.name + else: + module_name = renderers.NotAvailableValue() + + yield ( + 0, + [ + format_hints.Hex(call_addr), + module_name, + symbol_name or renderers.NotAvailableValue(), + ], + ) def run(self): return renderers.TreeGrid( diff --git a/volatility3/framework/plugins/linux/kthreads.py b/volatility3/framework/plugins/linux/kthreads.py index 60d24f06e..99a1b57a9 100644 --- a/volatility3/framework/plugins/linux/kthreads.py +++ b/volatility3/framework/plugins/linux/kthreads.py @@ -5,14 +5,14 @@ import logging from typing import List import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules -from volatility3.framework import constants, exceptions, interfaces, renderers +from volatility3.framework import exceptions, interfaces, renderers from volatility3.framework.configuration import requirements from volatility3.framework.interfaces import plugins from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import linux from volatility3.framework.constants import architectures from volatility3.framework.objects import utility -from volatility3.plugins.linux import pslist, lsmod +from volatility3.plugins.linux import pslist vollog = logging.getLogger(__name__) @@ -42,28 +42,22 @@ class Kthreads(plugins.PluginInterface): requirements.PluginRequirement( name="pslist", plugin=pslist.PsList, version=(4, 0, 0) ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) - ), ] def _generator(self): vmlinux = self.context.modules[self.config["kernel"]] - modules = lsmod.Lsmod.list_modules(self.context, vmlinux.name) - handlers = linux.LinuxUtilities.generate_kernel_handler_info( - self.context, vmlinux.name, modules - ) - - kthread_type = vmlinux.get_type( - vmlinux.symbol_table_name + constants.BANG + "kthread" - ) + kthread_type = vmlinux.get_type("kthread") if not kthread_type.has_member("threadfn"): raise exceptions.VolatilityException( "Unsupported kthread implementation. This plugin only works with kernels >= 5.8" ) + known_modules = linux_utilities_modules.Modules.run_modules_scanners( + self.context, self.config["kernel"], run_hidden_modules=True + ) + for task in pslist.PsList.list_tasks( self.context, vmlinux.name, include_threads=True ): @@ -86,9 +80,7 @@ class Kthreads(plugins.PluginInterface): if not (threadfn and threadfn.is_readable()): continue - task_name = utility.array_to_string(task.comm) - - thread_name = task_name + thread_name = utility.array_to_string(task.comm) # kernels >= 5.17 in d6986ce24fc00b0638bd29efe8fb7ba7619ed2aa full_name was added to kthread if kthread.has_member("full_name"): @@ -101,18 +93,23 @@ class Kthreads(plugins.PluginInterface): 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 + module_info, symbol_name = ( + linux_utilities_modules.Modules.module_lookup_by_address( + self.context, vmlinux.name, known_modules, threadfn ) ) + if module_info: + module_name = module_info.name + else: + module_name = renderers.NotAvailableValue() + fields = [ task.pid, thread_name, format_hints.Hex(threadfn), module_name, - symbol_name, + symbol_name or renderers.NotAvailableValue(), ] yield 0, fields diff --git a/volatility3/framework/plugins/linux/tty_check.py b/volatility3/framework/plugins/linux/tty_check.py index 281d46eda..b45272eb6 100644 --- a/volatility3/framework/plugins/linux/tty_check.py +++ b/volatility3/framework/plugins/linux/tty_check.py @@ -12,7 +12,6 @@ from volatility3.framework.interfaces import plugins from volatility3.framework.objects import utility from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import linux -from volatility3.plugins.linux import lsmod vollog = logging.getLogger(__name__) @@ -35,9 +34,6 @@ class tty_check(plugins.PluginInterface): component=linux_utilities_modules.Modules, version=(2, 0, 0), ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) - ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) ), @@ -46,12 +42,6 @@ class tty_check(plugins.PluginInterface): def _generator(self): vmlinux = self.context.modules[self.config["kernel"]] - modules = lsmod.Lsmod.list_modules(self.context, vmlinux.name) - - handlers = linux.LinuxUtilities.generate_kernel_handler_info( - self.context, vmlinux.name, modules - ) - try: tty_drivers = vmlinux.object_from_symbol("tty_drivers").cast("list_head") except exceptions.SymbolError: @@ -64,6 +54,10 @@ class tty_check(plugins.PluginInterface): "This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt." ) + known_modules = linux_utilities_modules.Modules.run_modules_scanners( + self.context, self.config["kernel"], run_hidden_modules=True + ) + for tty in tty_drivers.to_list( vmlinux.symbol_table_name + constants.BANG + "tty_driver", "tty_drivers" ): @@ -87,13 +81,23 @@ class tty_check(plugins.PluginInterface): except exceptions.InvalidAddressException: continue - module_name, symbol_name = ( - linux_utilities_modules.Modules.lookup_module_address( - self.context, vmlinux.name, handlers, recv_buf + module_info, symbol_name = ( + linux_utilities_modules.Modules.module_lookup_by_address( + self.context, vmlinux.name, known_modules, recv_buf ) ) - yield (0, (name, format_hints.Hex(recv_buf), module_name, symbol_name)) + if module_info: + module_name = module_info.name + else: + module_name = renderers.NotAvailableValue() + + yield 0, ( + name, + format_hints.Hex(recv_buf), + module_name, + symbol_name or renderers.NotAvailableValue(), + ) def run(self): return renderers.TreeGrid( From 39d39292f0a0c2b1429041713696d3c5e0190978 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Thu, 13 Mar 2025 22:55:47 +0000 Subject: [PATCH 06/78] Update symbol splitting to handle symbols without attached module name --- 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 0eeb2b33c..ef20ed044 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -98,7 +98,7 @@ class Modules(interfaces.configuration.VersionableInterface): module = kernel.object("module", offset=module.offset, absolute=True) symbol_name = module.get_symbol_by_address(target_address) - if symbol_name: + if symbol_name and symbol_name.find(constants.BANG) != -1: symbol_name = symbol_name.split(constants.BANG)[1] return match, symbol_name From c318e7c32664352f036aeed1453a8ed40ad6c90f Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 14 Mar 2025 01:52:16 +0000 Subject: [PATCH 07/78] Make configurable to sources --- .../framework/plugins/linux/check_idt.py | 6 +- .../framework/plugins/linux/check_modules.py | 2 +- .../framework/plugins/linux/hidden_modules.py | 2 +- .../plugins/linux/keyboard_notifiers.py | 6 +- .../framework/plugins/linux/kthreads.py | 6 +- volatility3/framework/plugins/linux/lsmod.py | 2 +- .../framework/plugins/linux/modxview.py | 15 +- .../framework/plugins/linux/netfilter.py | 2 +- .../framework/plugins/linux/tracing/ftrace.py | 6 +- .../plugins/linux/tracing/tracepoints.py | 6 +- .../framework/plugins/linux/tty_check.py | 6 +- .../symbols/linux/utilities/modules.py | 207 ++++++++++++++---- 12 files changed, 199 insertions(+), 67 deletions(-) diff --git a/volatility3/framework/plugins/linux/check_idt.py b/volatility3/framework/plugins/linux/check_idt.py index c85f291cc..119531836 100644 --- a/volatility3/framework/plugins/linux/check_idt.py +++ b/volatility3/framework/plugins/linux/check_idt.py @@ -33,7 +33,7 @@ class Check_idt(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) @@ -79,7 +79,9 @@ class Check_idt(interfaces.plugins.PluginInterface): vmlinux = self.context.modules[self.config["kernel"]] known_modules = linux_utilities_modules.Modules.run_modules_scanners( - self.context, self.config["kernel"], run_hidden_modules=True + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_sources=linux_utilities_modules.Modules.all_sources_identifier, ) idt_table_size = 256 diff --git a/volatility3/framework/plugins/linux/check_modules.py b/volatility3/framework/plugins/linux/check_modules.py index 76f75ec50..40f3f638c 100644 --- a/volatility3/framework/plugins/linux/check_modules.py +++ b/volatility3/framework/plugins/linux/check_modules.py @@ -33,7 +33,7 @@ class Check_modules(plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), ), ] diff --git a/volatility3/framework/plugins/linux/hidden_modules.py b/volatility3/framework/plugins/linux/hidden_modules.py index 8999126b1..5be8e7174 100644 --- a/volatility3/framework/plugins/linux/hidden_modules.py +++ b/volatility3/framework/plugins/linux/hidden_modules.py @@ -31,7 +31,7 @@ class Hidden_modules(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), ), ] diff --git a/volatility3/framework/plugins/linux/keyboard_notifiers.py b/volatility3/framework/plugins/linux/keyboard_notifiers.py index beebc4248..b4f9dd3ca 100644 --- a/volatility3/framework/plugins/linux/keyboard_notifiers.py +++ b/volatility3/framework/plugins/linux/keyboard_notifiers.py @@ -29,7 +29,7 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) @@ -56,7 +56,9 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface): return known_modules = linux_utilities_modules.Modules.run_modules_scanners( - self.context, self.config["kernel"], run_hidden_modules=True + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_sources=linux_utilities_modules.Modules.all_sources_identifier, ) knl = vmlinux.object( diff --git a/volatility3/framework/plugins/linux/kthreads.py b/volatility3/framework/plugins/linux/kthreads.py index 99a1b57a9..7d6abcecc 100644 --- a/volatility3/framework/plugins/linux/kthreads.py +++ b/volatility3/framework/plugins/linux/kthreads.py @@ -34,7 +34,7 @@ class Kthreads(plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0) @@ -55,7 +55,9 @@ class Kthreads(plugins.PluginInterface): ) known_modules = linux_utilities_modules.Modules.run_modules_scanners( - self.context, self.config["kernel"], run_hidden_modules=True + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_sources=linux_utilities_modules.Modules.all_sources_identifier, ) for task in pslist.PsList.list_tasks( diff --git a/volatility3/framework/plugins/linux/lsmod.py b/volatility3/framework/plugins/linux/lsmod.py index b4f881801..a3ace9a24 100644 --- a/volatility3/framework/plugins/linux/lsmod.py +++ b/volatility3/framework/plugins/linux/lsmod.py @@ -33,7 +33,7 @@ class Lsmod(plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), ), ] diff --git a/volatility3/framework/plugins/linux/modxview.py b/volatility3/framework/plugins/linux/modxview.py index f6a6f7727..4373ffb7c 100644 --- a/volatility3/framework/plugins/linux/modxview.py +++ b/volatility3/framework/plugins/linux/modxview.py @@ -34,7 +34,7 @@ spot modules presence and taints.""" requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), ), requirements.VersionRequirement( name="linux-tainting", component=tainting.Tainting, version=(1, 0, 0) @@ -93,13 +93,22 @@ spot modules presence and taints.""" kernel = self.context.modules[kernel_name] + wanted_sources = [ + linux_utilities_modules.Modules.source_lsmod_identifier, + linux_utilities_modules.Modules.source_sysfs_identifier, + linux_utilities_modules.Modules.source_hidden_identifier, + ] + run_results = linux_utilities_modules.Modules.run_modules_scanners( - self.context, kernel_name, flatten=False + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_sources=wanted_sources, + flatten=False, ) aggregated_modules = {} # We want to be explicit on the plugins results we are interested in - for plugin_name in ["lsmod", "check_modules", "hidden_modules"]: + for plugin_name in wanted_sources: # Iterate over each recovered module for mod_info in run_results[plugin_name]: # Use offsets as unique keys, whether a module diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index 9c0055a54..e8a33be61 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -726,7 +726,7 @@ class Netfilter(interfaces.plugins.PluginInterface): _version = (1, 1, 1) - _required_linux_utilities_modules_version = (2, 0, 0) + _required_linux_utilities_modules_version = (3, 0, 0) _required_linuxutils_version = (2, 1, 0) _required_lsmod_version = (2, 0, 0) _required_linuxnet_version = (1, 0, 0) diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index 17766cc74..146230f02 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -79,7 +79,7 @@ class CheckFtrace(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), ), requirements.BooleanRequirement( name="show_ftrace_flags", @@ -223,7 +223,9 @@ class CheckFtrace(interfaces.plugins.PluginInterface): return known_modules = linux_utilities_modules.Modules.run_modules_scanners( - self.context, kernel_name, run_hidden_modules=True + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_sources=linux_utilities_modules.Modules.all_sources_identifier, ) for ftrace_ops in self.iterate_ftrace_ops_list(self.context, kernel_name): diff --git a/volatility3/framework/plugins/linux/tracing/tracepoints.py b/volatility3/framework/plugins/linux/tracing/tracepoints.py index fe6d11af9..83903b19b 100644 --- a/volatility3/framework/plugins/linux/tracing/tracepoints.py +++ b/volatility3/framework/plugins/linux/tracing/tracepoints.py @@ -52,7 +52,7 @@ class CheckTracepoints(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), ), ] @@ -229,7 +229,9 @@ class CheckTracepoints(interfaces.plugins.PluginInterface): return known_modules = linux_utilities_modules.Modules.run_modules_scanners( - self.context, kernel_name, run_hidden_modules=False + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_sources=linux_utilities_modules.Modules.all_sources_identifier, ) tracepoints = self.iterate_tracepoints_array(self.context, kernel_name) diff --git a/volatility3/framework/plugins/linux/tty_check.py b/volatility3/framework/plugins/linux/tty_check.py index b45272eb6..f4b581756 100644 --- a/volatility3/framework/plugins/linux/tty_check.py +++ b/volatility3/framework/plugins/linux/tty_check.py @@ -32,7 +32,7 @@ class tty_check(plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) @@ -55,7 +55,9 @@ class tty_check(plugins.PluginInterface): ) known_modules = linux_utilities_modules.Modules.run_modules_scanners( - self.context, self.config["kernel"], run_hidden_modules=True + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_sources=linux_utilities_modules.Modules.all_sources_identifier, ) for tty in tty_drivers.to_list( diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index 0eeb2b33c..dac52dce3 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -19,11 +19,26 @@ vollog = logging.getLogger(__name__) class Modules(interfaces.configuration.VersionableInterface): """Kernel modules related utilities.""" - _version = (2, 0, 0) + _version = (3, 0, 0) _required_framework_version = (2, 0, 0) framework.require_interface_version(*_required_framework_version) + # Valid sources of kernel modules to send to `run_module_scanners` + source_kernel_identifier = "kernel" + source_lsmod_identifier = "lsmod" + source_sysfs_identifier = "check_modules" + source_hidden_identifier = "hidden_modules" + + # With few exceptions, rootkit checking plugins want all sources + # This provides a stable identifier as new sources are added over time + all_sources_identifier = [ + source_kernel_identifier, + source_lsmod_identifier, + source_sysfs_identifier, + source_hidden_identifier, + ] + class ModuleInfo(NamedTuple): """ Used to track the name and boundary of a kernel module @@ -34,13 +49,13 @@ class Modules(interfaces.configuration.VersionableInterface): start: int end: int - @staticmethod + @classmethod def module_lookup_by_address( + cls, context: interfaces.context.ContextInterface, kernel_module_name: str, modules: Iterable[ModuleInfo], target_address: int, - run_hidden_modules: bool = True, ) -> Optional[Tuple[ModuleInfo, Optional[str]]]: """ Determine if a target address lies in a module memory space. @@ -195,7 +210,7 @@ class Modules(interfaces.configuration.VersionableInterface): def get_kernel_module_info( context: interfaces.context.ContextInterface, kernel_module_name: str, - ) -> ModuleInfo: + ) -> Iterator[ModuleInfo]: """ Returns a ModuleInfo instance that encodes the kernel This is required to map function pointers to the kerenl executable @@ -210,77 +225,173 @@ class Modules(interfaces.configuration.VersionableInterface): end_addr = kernel.object_from_symbol("_etext") end_addr = end_addr.vol.offset & address_mask - return Modules.ModuleInfo( - start_addr, constants.linux.KERNEL_NAME, start_addr, end_addr + return [ + Modules.ModuleInfo( + start_addr, constants.linux.KERNEL_NAME, start_addr, end_addr + ) + ] + + @classmethod + def _get_hidden_modules_results( + cls, + context: str, + kernel_module_name: str, + run_results: Dict[str, List[ModuleInfo]], + ): + known_modules_addresses = set() + + kernel = context.modules[kernel_module_name] + + # Walk each sources' results + for results in run_results.values(): + for modinfo in results: + address = context.layers[kernel.layer_name].canonicalize(modinfo.start) + known_modules_addresses.add(address) + + modules_memory_boundaries = cls.get_modules_memory_boundaries( + context, kernel_module_name ) + hidden_results = [] + + address_mask = context.layers[kernel.layer_name].address_mask + + for module in cls.get_hidden_modules( + context, + kernel_module_name, + known_modules_addresses, + modules_memory_boundaries, + ): + modinfo = cls.get_module_info_for_module(address_mask, module) + if modinfo: + hidden_results.append(modinfo) + + return hidden_results + + @classmethod + def _get_list_modules( + cls, context: interfaces.context.ContextInterface, kernel_module_name: str + ) -> List[ModuleInfo]: + """ + Gather `module` instances from lsmod + """ + yield from cls.list_modules(context, kernel_module_name) + + @classmethod + def _get_sysfs_modules( + cls, context: interfaces.context.ContextInterface, kernel_module_name: str + ) -> List[ModuleInfo]: + """ + Gather the `module` instances from sysfs + """ + kernel = context.modules[kernel_module_name] + + sysfs_modules: dict = cls.get_kset_modules(context, kernel_module_name) + + for m_offset in sysfs_modules.values(): + yield kernel.object(object_type="module", offset=m_offset, absolute=True) + + @classmethod + def _validated_sources(cls, caller_wanted_sources) -> List[str]: + """ + Called by `run_modules_scanners` to validate the caller supplied sources list + An exception is thrown if an empty source list is given or a list containing an invalid source + """ + if not caller_wanted_sources: + raise ValueError("`caller_wanted_sources` must have at least one source.") + + if ( + len(caller_wanted_sources) == 1 + and caller_wanted_sources[0] == Modules.source_hidden_identifier + ): + raise ValueError( + f"{Modules.source_hidden_identifier} cannot be the only source or there is nothing to compare against." + ) + + wanted_sources = [] + + for source in caller_wanted_sources: + if source not in Modules.all_sources_identifier: + raise ValueError( + f"Invalid source sent through `caller_wanted_sources`: {source}" + ) + + wanted_sources.append(source) + + return wanted_sources + @classmethod def run_modules_scanners( cls, context: interfaces.context.ContextInterface, - kernel_name: str, - run_hidden_modules: bool = True, + kernel_module_name: str, + caller_wanted_sources: List[str], flatten: bool = True, ) -> Dict[str, List[ModuleInfo]]: """Run module scanning plugins and aggregate the results. It is designed to not operate any inter-plugin results triage. + Rules for `caller_wanted_sources`: + + If `Modules.all_sources_identifier` is specified then every source will be populated + + If `Modules.source_hidden_identifier` is in the list, then at least one other sources must be + specified so a comparison will be populated + + If empty or an invalid source is specified then a ValueError is thrown + Args: - run_hidden_modules: specify if the hidden_modules plugin should be run + called_wanted_sources: The list of sources to gather modules. + flatten: Whether to de-duplicate modules across sources Returns: Dictionary mapping each plugin to its corresponding result """ - kernel = context.modules[kernel_name] + module_gatherers = { + Modules.source_kernel_identifier: cls.get_kernel_module_info, + Modules.source_lsmod_identifier: cls._get_list_modules, + Modules.source_sysfs_identifier: cls._get_sysfs_modules, + } + + kernel = context.modules[kernel_module_name] address_mask = context.layers[kernel.layer_name].address_mask + wanted_sources = Modules._validated_sources(caller_wanted_sources) + run_results = {} - # the kernel module boundaries - run_results["kernel"] = [cls.get_kernel_module_info(context, kernel_name)] + run_hidden_modules = False - # lsmod - run_results["lsmod"] = [] + # Special case hidden modules since it gathers modules on its own + if Modules.source_hidden_identifier in wanted_sources: + run_hidden_modules = True + wanted_sources.remove(Modules.source_hidden_identifier) - for module in cls.list_modules(context, kernel_name): - modinfo = cls.get_module_info_for_module(address_mask, module) - if modinfo: - run_results["lsmod"].append(modinfo) + # Walk each source, gathering modules + for wanted_source in wanted_sources: + run_results[wanted_source] = [] - # check_modules - run_results["check_modules"] = [] + gatherer = module_gatherers[wanted_source] - sysfs_modules: dict = cls.get_kset_modules(context, kernel_name) + # process each module coming from back the current source + for module in gatherer(context, kernel_module_name): + # the kernel sends back a ModuleInfo directly + if wanted_source == Modules.source_kernel_identifier: + modinfo = module + else: + modinfo = cls.get_module_info_for_module(address_mask, module) - for m_offset in sysfs_modules.values(): - module = kernel.object(object_type="module", offset=m_offset, absolute=True) - modinfo = cls.get_module_info_for_module(address_mask, module) - if modinfo: - run_results["check_modules"].append(modinfo) - - # hidden_modules - if run_hidden_modules: - known_modules_addresses = set( - context.layers[kernel.layer_name].canonicalize(modinfo.start) - for modinfo in run_results["kernel"] - + run_results["lsmod"] - + run_results["check_modules"] - ) - modules_memory_boundaries = cls.get_modules_memory_boundaries( - context, kernel_name - ) - run_results["hidden_modules"] = [] - - for module in cls.get_hidden_modules( - context, - kernel_name, - known_modules_addresses, - modules_memory_boundaries, - ): - modinfo = cls.get_module_info_for_module(address_mask, module) if modinfo: - run_results["hidden_modules"].append(modinfo) + run_results[wanted_source].append(modinfo) + + # run hidden modules against the other sources + if run_hidden_modules: + run_results[Modules.source_hidden_identifier] = ( + cls._get_hidden_modules_results( + context, kernel_module_name, run_results + ) + ) if flatten: return cls.flatten_run_modules_results(run_results) From 4606495ded4f71c2bdd9066b260556947729e618 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 14 Mar 2025 02:09:08 +0000 Subject: [PATCH 08/78] Bump dep versions --- volatility3/framework/plugins/linux/check_modules.py | 2 +- volatility3/framework/plugins/linux/hidden_modules.py | 8 ++++---- volatility3/framework/plugins/linux/lsmod.py | 2 +- volatility3/framework/plugins/linux/modxview.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/volatility3/framework/plugins/linux/check_modules.py b/volatility3/framework/plugins/linux/check_modules.py index 40f3f638c..44cb568e6 100644 --- a/volatility3/framework/plugins/linux/check_modules.py +++ b/volatility3/framework/plugins/linux/check_modules.py @@ -41,7 +41,7 @@ class Check_modules(plugins.PluginInterface): @deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.get_kset_modules, removal_date="2025-09-25", - replacement_version=(2, 0, 0), + replacement_version=(3, 0, 0), ) def get_kset_modules( cls, context: interfaces.context.ContextInterface, vmlinux_name: str diff --git a/volatility3/framework/plugins/linux/hidden_modules.py b/volatility3/framework/plugins/linux/hidden_modules.py index 5be8e7174..985d4cfcb 100644 --- a/volatility3/framework/plugins/linux/hidden_modules.py +++ b/volatility3/framework/plugins/linux/hidden_modules.py @@ -39,7 +39,7 @@ class Hidden_modules(interfaces.plugins.PluginInterface): @deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.get_modules_memory_boundaries, removal_date="2025-09-25", - replacement_version=(2, 0, 0), + replacement_version=(3, 0, 0), ) def get_modules_memory_boundaries( context: interfaces.context.ContextInterface, @@ -52,7 +52,7 @@ class Hidden_modules(interfaces.plugins.PluginInterface): @deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.get_module_address_alignment, removal_date="2025-09-25", - replacement_version=(2, 0, 0), + replacement_version=(3, 0, 0), ) @classmethod def _get_module_address_alignment( @@ -80,7 +80,7 @@ class Hidden_modules(interfaces.plugins.PluginInterface): @deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.get_hidden_modules, removal_date="2025-09-25", - replacement_version=(2, 0, 0), + replacement_version=(3, 0, 0), ) @classmethod def get_hidden_modules( @@ -120,7 +120,7 @@ class Hidden_modules(interfaces.plugins.PluginInterface): @deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.validate_alignment_patterns, removal_date="2025-09-25", - replacement_version=(2, 0, 0), + replacement_version=(3, 0, 0), ) def _validate_alignment_patterns( addresses: Iterable[int], diff --git a/volatility3/framework/plugins/linux/lsmod.py b/volatility3/framework/plugins/linux/lsmod.py index a3ace9a24..466bfa0b4 100644 --- a/volatility3/framework/plugins/linux/lsmod.py +++ b/volatility3/framework/plugins/linux/lsmod.py @@ -40,7 +40,7 @@ class Lsmod(plugins.PluginInterface): @classmethod @deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.list_modules, - replacement_version=(2, 0, 0), + replacement_version=(3, 0, 0), removal_date="2025-09-25", ) def list_modules( diff --git a/volatility3/framework/plugins/linux/modxview.py b/volatility3/framework/plugins/linux/modxview.py index 4373ffb7c..e43672974 100644 --- a/volatility3/framework/plugins/linux/modxview.py +++ b/volatility3/framework/plugins/linux/modxview.py @@ -50,7 +50,7 @@ spot modules presence and taints.""" @classmethod @deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.flatten_run_modules_results, - replacement_version=(2, 0, 0), + replacement_version=(3, 0, 0), removal_date="2025-09-25", ) def flatten_run_modules_results( @@ -73,7 +73,7 @@ spot modules presence and taints.""" @classmethod @deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.run_modules_scanners, - replacement_version=(2, 0, 0), + replacement_version=(3, 0, 0), removal_date="2025-09-25", ) def run_modules_scanners( From 7ab62365fbbaf3184942eed0a13848cd8781ef00 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 14 Mar 2025 03:05:11 +0000 Subject: [PATCH 09/78] Vastly improve string reading while keeping intended behaviour --- volatility3/framework/objects/utility.py | 97 +++++++++++++++++++++--- 1 file changed, 87 insertions(+), 10 deletions(-) diff --git a/volatility3/framework/objects/utility.py b/volatility3/framework/objects/utility.py index 1fcd305f7..a1ad4fdf5 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -2,10 +2,9 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # -import re from typing import Optional, Union -from volatility3.framework import interfaces, objects, constants +from volatility3.framework import interfaces, objects, constants, exceptions def rol(value: int, count: int, max_bits: int = 64) -> int: @@ -102,6 +101,64 @@ def pointer_to_string( ) +def gather_contiguous_bytes_from_address(layer, address: int, count: int) -> bytes: + """ + This method reconstructs a string from memory while also carefully examining each page + + It goes page-by-page reading the bytes. This is done by calculating page boundaries + and then only reading one page at a time. + + If a page is missing, the code initially catches the exception. + If data is non-empty (meaning at least one read succeeded), then we return what was read + If the first page fails, then we re-raise the exception + """ + + data = b"" + + left_to_read = count + + # read as many pages as possible that are contiguous + # if the first page is missed, we re-raise the InvalidAddressException + # if we have at least 1 page that was read succesfully, + # then we try to construct a string from it + while left_to_read > 0: + # compute aligned address of current page and next the page + aligned = address & ~0xFFF + next_page = aligned + 0xFFF + 1 + + # all fits on the current page, last read + if address + left_to_read < next_page: + try: + data += layer.read(address, left_to_read) + except exceptions.InvalidAddressException: + # if we have data, just break the loop + if data: + break + # Raise if no data was read as this means the first page was invalid + else: + raise + + left_to_read = 0 + + else: + # how many bytes are left on the current page + len_to_read = next_page - address + + try: + data += layer.read(address, len_to_read) + except exceptions.InvalidAddressException: + if data: + break + # Raise if no data was read as this means the first page was invalid + else: + raise + + address += len_to_read + left_to_read -= len_to_read + + return data + + def address_to_string( context: interfaces.context.ContextInterface, layer_name: str, @@ -131,18 +188,38 @@ def address_to_string( if count < 1: raise ValueError("Count must be greater than 0") + encodings = {"utf8": 1, "utf16": 2, "utf32": 4} + if encoding not in encodings: + raise ValueError( + f"Encoding ({encoding} is invalid. Must be one of {[e for e in encodings]}." + ) + layer = context.layers[layer_name] - # Purposely do not catch exception - data = layer.read(address, count) + data = gather_contiguous_bytes_from_address(layer, address, count) - decoded_data = data.decode(encoding=encoding, errors=errors) - try: - idx = re.search("\ufffd|\x00", decoded_data).start() - except AttributeError: - idx = len(decoded_data) + # we need to find the ending nulls, which the amount of nulls varies based on encoding + ending_nulls = b"\x00" * encodings[encoding] - return decoded_data[:idx] + end_idx = data.find(ending_nulls) + # send back the bytes even if the ending nulls aren't found (can be on the next page) + if end_idx == -1: + return data + + # cut at the nulls + data = data[:end_idx] + + # For utf16 and utf32, just looking for the nulls cuts the final null from the string when its ascii characters + # This occurs as the string 'vol.py' in utf-16 will look like this, with two ending nulls: + # "v\x00o\x00l\x00.\x00p\x00y\x00\x00\x00" + # By cutting at the first \x00\x00, we are taking the second byte of the character for 'y' + # With real unicode strings this character can be non-zero + # This check and added null, pads out the last byte(s) to the width of each character to avoid this issue + end_size = len(ending_nulls) + if len(data) > end_size and len(data) % end_size != 0: + data += b"\x00" * (end_size - (len(data) % end_size)) + + return data.decode(encoding=encoding, errors=errors) def array_of_pointers( From 36b3c2885974e7c697a160bfc8820800e680286b Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 14 Mar 2025 03:12:06 +0000 Subject: [PATCH 10/78] Update encodings --- volatility3/framework/objects/utility.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/objects/utility.py b/volatility3/framework/objects/utility.py index a1ad4fdf5..13031e1f1 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -188,7 +188,14 @@ def address_to_string( if count < 1: raise ValueError("Count must be greater than 0") - encodings = {"utf8": 1, "utf16": 2, "utf32": 4} + encodings = { + "utf-8": 1, + "utf8": 1, + "utf-16": 2, + "utf16": 2, + "utf32": 4, + "utf-32": 4, + } if encoding not in encodings: raise ValueError( f"Encoding ({encoding} is invalid. Must be one of {[e for e in encodings]}." From 4225adce56d1c73ff0194d944b948ad6befa2b87 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 14 Mar 2025 16:32:54 +0000 Subject: [PATCH 11/78] Convert to .mapping and let Python had all encodings --- volatility3/framework/objects/utility.py | 131 ++++++++++------------- 1 file changed, 58 insertions(+), 73 deletions(-) diff --git a/volatility3/framework/objects/utility.py b/volatility3/framework/objects/utility.py index 13031e1f1..b014e37fa 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -2,6 +2,8 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # +import re + from typing import Optional, Union from volatility3.framework import interfaces, objects, constants, exceptions @@ -101,7 +103,9 @@ def pointer_to_string( ) -def gather_contiguous_bytes_from_address(layer, address: int, count: int) -> bytes: +def gather_contiguous_bytes_from_address( + context, data_layer, starting_address: int, count: int +) -> bytes: """ This method reconstructs a string from memory while also carefully examining each page @@ -115,50 +119,65 @@ def gather_contiguous_bytes_from_address(layer, address: int, count: int) -> byt data = b"" - left_to_read = count + last_address = None - # read as many pages as possible that are contiguous - # if the first page is missed, we re-raise the InvalidAddressException - # if we have at least 1 page that was read succesfully, - # then we try to construct a string from it - while left_to_read > 0: - # compute aligned address of current page and next the page - aligned = address & ~0xFFF - next_page = aligned + 0xFFF + 1 + for address, length, _, _, _ in data_layer.mapping( + offset=starting_address, length=count, ignore_errors=True + ): + # Used to track when we hit a paged out page + if not last_address: + last_address = address + length - # all fits on the current page, last read - if address + left_to_read < next_page: - try: - data += layer.read(address, left_to_read) - except exceptions.InvalidAddressException: - # if we have data, just break the loop - if data: - break - # Raise if no data was read as this means the first page was invalid - else: - raise + # we hit a swapped out page + elif last_address and last_address != address: + break - left_to_read = 0 + data += data_layer.read(address, length) - else: - # how many bytes are left on the current page - len_to_read = next_page - address - - try: - data += layer.read(address, len_to_read) - except exceptions.InvalidAddressException: - if data: - break - # Raise if no data was read as this means the first page was invalid - else: - raise - - address += len_to_read - left_to_read -= len_to_read + # if we were able to read from the first page, we want to try and construct the string + # if the first page fails -> throw exception + if data: + return data + else: + raise exceptions.InvalidAddressException( + layer_name=data_layer, invalid_address=starting_address + ) return data +def bytes_to_decoded_string(data: bytes, encoding: str, errors: str) -> bytes: + """ + This function takes a bytes buffer that contains at a string of unknown + length starting at the first byte, and returns the properly decoded string + + It starts by using Python's `bytes.decode` to attempt to decode the entire string + It then finds the termination character (\ufffd or \x00) and splices the string + Finally, it returns this spliced string after its been decoded with the + caller-specified encoding + """ + # this is the standard byte used to replace bad unicode characters + unicode_replacement_char = "\ufffd" + + # used to find the terminating byte + termination_re = re.compile(f"{unicode_replacement_char}|\x00") + + # run over the entire string, letting Python replace invalid characters + full_decoded_string = data.decode(encoding=encoding, errors="replace") + + # stop at the first terminating character or get the whole string if not found + try: + idx = termination_re.search(full_decoded_string).start() + except AttributeError: + idx = len(full_decoded_string) + + # cut at terminating byte, if found + data = data[:idx] + + # return with caller-specified encoding and errors + return data.decode(encoding=encoding, errors=errors) + + def address_to_string( context: interfaces.context.ContextInterface, layer_name: str, @@ -188,45 +207,11 @@ def address_to_string( if count < 1: raise ValueError("Count must be greater than 0") - encodings = { - "utf-8": 1, - "utf8": 1, - "utf-16": 2, - "utf16": 2, - "utf32": 4, - "utf-32": 4, - } - if encoding not in encodings: - raise ValueError( - f"Encoding ({encoding} is invalid. Must be one of {[e for e in encodings]}." - ) - layer = context.layers[layer_name] - data = gather_contiguous_bytes_from_address(layer, address, count) + data = gather_contiguous_bytes_from_address(context, layer, address, count) - # we need to find the ending nulls, which the amount of nulls varies based on encoding - ending_nulls = b"\x00" * encodings[encoding] - - end_idx = data.find(ending_nulls) - # send back the bytes even if the ending nulls aren't found (can be on the next page) - if end_idx == -1: - return data - - # cut at the nulls - data = data[:end_idx] - - # For utf16 and utf32, just looking for the nulls cuts the final null from the string when its ascii characters - # This occurs as the string 'vol.py' in utf-16 will look like this, with two ending nulls: - # "v\x00o\x00l\x00.\x00p\x00y\x00\x00\x00" - # By cutting at the first \x00\x00, we are taking the second byte of the character for 'y' - # With real unicode strings this character can be non-zero - # This check and added null, pads out the last byte(s) to the width of each character to avoid this issue - end_size = len(ending_nulls) - if len(data) > end_size and len(data) % end_size != 0: - data += b"\x00" * (end_size - (len(data) % end_size)) - - return data.decode(encoding=encoding, errors=errors) + return bytes_to_decoded_string(data=data, errors=errors, encoding=encoding) def array_of_pointers( From 196bf8187dbb777f0ad43b2c9d18a0d9e069fe7d Mon Sep 17 00:00:00 2001 From: David McDonald Date: Fri, 14 Mar 2025 12:21:11 -0500 Subject: [PATCH 12/78] Volshell: Address comments from code review - Add failure message when readline or rlcompleter can't be imported - Fix unclosed file handle in context manager --- volatility3/cli/volshell/generic.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index c86e221f7..143e26500 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -90,7 +90,9 @@ class Volshell(interfaces.plugins.PluginInterface): readline.parse_and_bind("tab: complete") print("Readline imported successfully") except ImportError: - pass + print( + "Readline or rlcompleter module could not be imported. Tab completion will not be available." + ) # TODO: provide help, consider generic functions (pslist?) and/or providing windows/linux functions @@ -539,7 +541,9 @@ class Volshell(interfaces.plugins.PluginInterface): location = "file:" + request.pathname2url(location) print(f"Running code from {location}\n") accessor = resources.ResourceAccessor() - with io.TextIOWrapper(accessor.open(url=location), encoding="utf-8") as fp: + with accessor.open(url=location) as handle, io.TextIOWrapper( + handle, encoding="utf-8" + ) as fp: if has_ipython: self.__console.ex(fp.read()) else: From cf8bb3dfbc12897445220d24912a43bc1cea75ac Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 14 Mar 2025 20:27:25 +0000 Subject: [PATCH 13/78] Adjust run_modules_scanners to avoid special handling of hidden modules, adjust modxview to new API, add classes and version requirements on module gathering interface --- .../framework/plugins/linux/check_idt.py | 7 +- .../plugins/linux/keyboard_notifiers.py | 7 +- .../framework/plugins/linux/kthreads.py | 7 +- .../framework/plugins/linux/modxview.py | 43 +-- .../framework/plugins/linux/tracing/ftrace.py | 16 +- .../plugins/linux/tracing/tracepoints.py | 13 +- .../framework/plugins/linux/tty_check.py | 7 +- .../symbols/linux/utilities/modules.py | 333 +++++++++--------- 8 files changed, 229 insertions(+), 204 deletions(-) diff --git a/volatility3/framework/plugins/linux/check_idt.py b/volatility3/framework/plugins/linux/check_idt.py index 119531836..e199d98d3 100644 --- a/volatility3/framework/plugins/linux/check_idt.py +++ b/volatility3/framework/plugins/linux/check_idt.py @@ -35,6 +35,11 @@ class Check_idt(interfaces.plugins.PluginInterface): component=linux_utilities_modules.Modules, version=(3, 0, 0), ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) ), @@ -81,7 +86,7 @@ class Check_idt(interfaces.plugins.PluginInterface): known_modules = linux_utilities_modules.Modules.run_modules_scanners( context=self.context, kernel_module_name=self.config["kernel"], - caller_wanted_sources=linux_utilities_modules.Modules.all_sources_identifier, + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, ) idt_table_size = 256 diff --git a/volatility3/framework/plugins/linux/keyboard_notifiers.py b/volatility3/framework/plugins/linux/keyboard_notifiers.py index b4f9dd3ca..215704350 100644 --- a/volatility3/framework/plugins/linux/keyboard_notifiers.py +++ b/volatility3/framework/plugins/linux/keyboard_notifiers.py @@ -31,6 +31,11 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface): component=linux_utilities_modules.Modules, version=(3, 0, 0), ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) ), @@ -58,7 +63,7 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface): known_modules = linux_utilities_modules.Modules.run_modules_scanners( context=self.context, kernel_module_name=self.config["kernel"], - caller_wanted_sources=linux_utilities_modules.Modules.all_sources_identifier, + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, ) knl = vmlinux.object( diff --git a/volatility3/framework/plugins/linux/kthreads.py b/volatility3/framework/plugins/linux/kthreads.py index 7d6abcecc..06e94b221 100644 --- a/volatility3/framework/plugins/linux/kthreads.py +++ b/volatility3/framework/plugins/linux/kthreads.py @@ -36,6 +36,11 @@ class Kthreads(plugins.PluginInterface): component=linux_utilities_modules.Modules, version=(3, 0, 0), ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0) ), @@ -57,7 +62,7 @@ class Kthreads(plugins.PluginInterface): known_modules = linux_utilities_modules.Modules.run_modules_scanners( context=self.context, kernel_module_name=self.config["kernel"], - caller_wanted_sources=linux_utilities_modules.Modules.all_sources_identifier, + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, ) for task in pslist.PsList.list_tasks( diff --git a/volatility3/framework/plugins/linux/modxview.py b/volatility3/framework/plugins/linux/modxview.py index e43672974..b04eb74ca 100644 --- a/volatility3/framework/plugins/linux/modxview.py +++ b/volatility3/framework/plugins/linux/modxview.py @@ -36,6 +36,11 @@ spot modules presence and taints.""" component=linux_utilities_modules.Modules, version=(3, 0, 0), ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="linux-tainting", component=tainting.Tainting, version=(1, 0, 0) ), @@ -89,44 +94,42 @@ spot modules presence and taints.""" ) def _generator(self): - kernel_name = self.config["kernel"] + kernel = self.context.modules[self.config["kernel"]] - kernel = self.context.modules[kernel_name] - - wanted_sources = [ - linux_utilities_modules.Modules.source_lsmod_identifier, - linux_utilities_modules.Modules.source_sysfs_identifier, - linux_utilities_modules.Modules.source_hidden_identifier, + wanted_gatherers = [ + linux_utilities_modules.ModuleGathererLsmod, + linux_utilities_modules.ModuleGathererSysFs, + linux_utilities_modules.ModuleGathererScanner, ] run_results = linux_utilities_modules.Modules.run_modules_scanners( context=self.context, kernel_module_name=self.config["kernel"], - caller_wanted_sources=wanted_sources, + caller_wanted_gatherers=wanted_gatherers, flatten=False, ) aggregated_modules = {} # We want to be explicit on the plugins results we are interested in - for plugin_name in wanted_sources: + for gatherer in wanted_gatherers: # Iterate over each recovered module - for mod_info in run_results[plugin_name]: + for mod_info in run_results[gatherer]: # Use offsets as unique keys, whether a module # appears in many plugin runs or not if aggregated_modules.get(mod_info.offset, None) is not None: # Append the plugin to the list of originating plugins - aggregated_modules[mod_info.offset].append(plugin_name) + aggregated_modules[mod_info.offset].append(gatherer) else: - aggregated_modules[mod_info.offset] = [plugin_name] + aggregated_modules[mod_info.offset] = [gatherer] - for module_offset, originating_plugins in aggregated_modules.items(): - # Tainting parsing capabilities applied to the module + for module_offset, gatherers in aggregated_modules.items(): module = kernel.object("module", offset=module_offset, absolute=True) + # Tainting parsing capabilities applied to the module if self.config.get("plain_taints"): taints = tainting.Tainting.get_taints_as_plain_string( self.context, - kernel_name, + self.config["kernel"], module.taints, True, ) @@ -134,7 +137,7 @@ spot modules presence and taints.""" taints = ",".join( tainting.Tainting.get_taints_parsed( self.context, - kernel_name, + self.config["kernel"], module.taints, True, ) @@ -145,9 +148,9 @@ spot modules presence and taints.""" ( module.get_name() or NotAvailableValue(), format_hints.Hex(module_offset), - "lsmod" in originating_plugins, - "check_modules" in originating_plugins, - "hidden_modules" in originating_plugins, + linux_utilities_modules.ModuleGathererLsmod in gatherers, + linux_utilities_modules.ModuleGathererSysFs in gatherers, + linux_utilities_modules.ModuleGathererScanner in gatherers, taints or NotAvailableValue(), ), ) @@ -158,7 +161,7 @@ spot modules presence and taints.""" ("Address", format_hints.Hex), ("In procfs", bool), ("In sysfs", bool), - ("Hidden", bool), + ("In scan", bool), ("Taints", str), ] diff --git a/volatility3/framework/plugins/linux/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index 146230f02..c5e4f9ef8 100644 --- a/volatility3/framework/plugins/linux/tracing/ftrace.py +++ b/volatility3/framework/plugins/linux/tracing/ftrace.py @@ -5,7 +5,7 @@ # Public researches: https://i.blackhat.com/USA21/Wednesday-Handouts/us-21-Fixing-A-Memory-Forensics-Blind-Spot-Linux-Kernel-Tracing-wp.pdf import logging -from typing import Dict, List, Generator +from typing import List, Generator from enum import Enum from dataclasses import dataclass @@ -65,7 +65,7 @@ class CheckFtrace(interfaces.plugins.PluginInterface): Investigate the ftrace infrastructure to uncover kernel attached callbacks, which can be leveraged to hook kernel functions and modify their behaviour.""" - _version = (3, 0, 0) + _version = (4, 0, 0) _required_framework_version = (2, 19, 0) @classmethod @@ -81,6 +81,11 @@ class CheckFtrace(interfaces.plugins.PluginInterface): component=linux_utilities_modules.Modules, version=(3, 0, 0), ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), + ), requirements.BooleanRequirement( name="show_ftrace_flags", description="Show ftrace flags associated with an ftrace_ops struct", @@ -127,9 +132,8 @@ class CheckFtrace(interfaces.plugins.PluginInterface): cls, context: interfaces.context.ContextInterface, kernel_module_name: str, - known_modules: Dict[str, List[linux_utilities_modules.Modules.ModuleInfo]], + known_modules: List[linux_utilities_modules.ModuleInfo], ftrace_ops: interfaces.objects.ObjectInterface, - run_hidden_modules: bool = True, ) -> Generator[ParsedFtraceOps, None, None]: """Parse an ftrace_ops struct to highlight ftrace kernel hooking. Iterates over embedded ftrace_func_entry entries, which point to hooked memory areas. @@ -137,8 +141,6 @@ class CheckFtrace(interfaces.plugins.PluginInterface): Args: known_modules: A dict of known modules, used to locate callbacks origin. Typically obtained through run_modules_scanners(). ftrace_ops: The ftrace_ops 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 ParsedFtraceOps dataclasses, containing a selection of useful fields (callback, hook, module) related to an ftrace_ops struct @@ -225,7 +227,7 @@ class CheckFtrace(interfaces.plugins.PluginInterface): known_modules = linux_utilities_modules.Modules.run_modules_scanners( context=self.context, kernel_module_name=self.config["kernel"], - caller_wanted_sources=linux_utilities_modules.Modules.all_sources_identifier, + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, ) for ftrace_ops in self.iterate_ftrace_ops_list(self.context, kernel_name): diff --git a/volatility3/framework/plugins/linux/tracing/tracepoints.py b/volatility3/framework/plugins/linux/tracing/tracepoints.py index 83903b19b..9d4a4a2e3 100644 --- a/volatility3/framework/plugins/linux/tracing/tracepoints.py +++ b/volatility3/framework/plugins/linux/tracing/tracepoints.py @@ -5,7 +5,7 @@ # Public researches: https://i.blackhat.com/USA21/Wednesday-Handouts/us-21-Fixing-A-Memory-Forensics-Blind-Spot-Linux-Kernel-Tracing-wp.pdf import logging -from typing import Dict, Iterable, List, Optional +from typing import Iterable, List, Optional from dataclasses import dataclass import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules @@ -38,7 +38,7 @@ class CheckTracepoints(interfaces.plugins.PluginInterface): 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) + _version = (2, 0, 0) _required_framework_version = (2, 19, 0) @classmethod @@ -54,6 +54,11 @@ class CheckTracepoints(interfaces.plugins.PluginInterface): component=linux_utilities_modules.Modules, version=(3, 0, 0), ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), + ), ] @classmethod @@ -96,7 +101,7 @@ class CheckTracepoints(interfaces.plugins.PluginInterface): cls, context: interfaces.context.ContextInterface, kernel_module_name: str, - known_modules: Dict[str, List[linux_utilities_modules.Modules.ModuleInfo]], + known_modules: List[linux_utilities_modules.ModuleInfo], tracepoint: interfaces.objects.ObjectInterface, run_hidden_modules: bool = True, ) -> Optional[Iterable[ParsedTracepointFunc]]: @@ -231,7 +236,7 @@ class CheckTracepoints(interfaces.plugins.PluginInterface): known_modules = linux_utilities_modules.Modules.run_modules_scanners( context=self.context, kernel_module_name=self.config["kernel"], - caller_wanted_sources=linux_utilities_modules.Modules.all_sources_identifier, + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, ) tracepoints = self.iterate_tracepoints_array(self.context, kernel_name) diff --git a/volatility3/framework/plugins/linux/tty_check.py b/volatility3/framework/plugins/linux/tty_check.py index f4b581756..7d30b84ee 100644 --- a/volatility3/framework/plugins/linux/tty_check.py +++ b/volatility3/framework/plugins/linux/tty_check.py @@ -34,6 +34,11 @@ class tty_check(plugins.PluginInterface): component=linux_utilities_modules.Modules, version=(3, 0, 0), ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) ), @@ -57,7 +62,7 @@ class tty_check(plugins.PluginInterface): known_modules = linux_utilities_modules.Modules.run_modules_scanners( context=self.context, kernel_module_name=self.config["kernel"], - caller_wanted_sources=linux_utilities_modules.Modules.all_sources_identifier, + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, ) for tty in tty_drivers.to_list( diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index dac52dce3..a930bef20 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -1,6 +1,18 @@ import logging import warnings -from typing import Iterable, Iterator, List, Optional, Tuple, NamedTuple, Dict, Set +from typing import ( + Iterable, + Iterator, + List, + Optional, + Tuple, + NamedTuple, + Dict, + Set, + Generator, + Union, +) +from abc import ABCMeta, abstractmethod from volatility3 import framework from volatility3.framework import ( @@ -10,12 +22,44 @@ from volatility3.framework import ( exceptions, objects, ) + from volatility3.framework.objects import utility from volatility3.framework.symbols.linux import extensions vollog = logging.getLogger(__name__) +class ModuleInfo(NamedTuple): + """ + Used to track the name and boundary of a kernel module + """ + + offset: int + name: str + start: int + end: int + + +class ModuleGathererInterface( + interfaces.configuration.VersionableInterface, metaclass=ABCMeta +): + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + + framework.require_interface_version(*_required_framework_version) + + gatherer_return_type = Generator[Union[ModuleInfo, "extensions.module"], None, None] + + @classmethod + @abstractmethod + def gather_modules( + cls, context: interfaces.context.ContextInterface, kernel_module_name: str + ) -> gatherer_return_type: + """ + This method must return a generator (yield) of each `gatherer_return_type` found from its source + """ + + class Modules(interfaces.configuration.VersionableInterface): """Kernel modules related utilities.""" @@ -24,31 +68,6 @@ class Modules(interfaces.configuration.VersionableInterface): framework.require_interface_version(*_required_framework_version) - # Valid sources of kernel modules to send to `run_module_scanners` - source_kernel_identifier = "kernel" - source_lsmod_identifier = "lsmod" - source_sysfs_identifier = "check_modules" - source_hidden_identifier = "hidden_modules" - - # With few exceptions, rootkit checking plugins want all sources - # This provides a stable identifier as new sources are added over time - all_sources_identifier = [ - source_kernel_identifier, - source_lsmod_identifier, - source_sysfs_identifier, - source_hidden_identifier, - ] - - class ModuleInfo(NamedTuple): - """ - Used to track the name and boundary of a kernel module - """ - - offset: int - name: str - start: int - end: int - @classmethod def module_lookup_by_address( cls, @@ -204,138 +223,41 @@ class Modules(interfaces.configuration.VersionableInterface): end = start + module.get_core_size() - return Modules.ModuleInfo(module.vol.offset, mod_name, start, end) - - @staticmethod - def get_kernel_module_info( - context: interfaces.context.ContextInterface, - kernel_module_name: str, - ) -> Iterator[ModuleInfo]: - """ - Returns a ModuleInfo instance that encodes the kernel - This is required to map function pointers to the kerenl executable - """ - kernel = context.modules[kernel_module_name] - - address_mask = context.layers[kernel.layer_name].address_mask - - start_addr = kernel.object_from_symbol("_text") - start_addr = start_addr.vol.offset & address_mask - - end_addr = kernel.object_from_symbol("_etext") - end_addr = end_addr.vol.offset & address_mask - - return [ - Modules.ModuleInfo( - start_addr, constants.linux.KERNEL_NAME, start_addr, end_addr - ) - ] + return ModuleInfo(module.vol.offset, mod_name, start, end) @classmethod - def _get_hidden_modules_results( - cls, - context: str, - kernel_module_name: str, - run_results: Dict[str, List[ModuleInfo]], - ): - known_modules_addresses = set() - - kernel = context.modules[kernel_module_name] - - # Walk each sources' results - for results in run_results.values(): - for modinfo in results: - address = context.layers[kernel.layer_name].canonicalize(modinfo.start) - known_modules_addresses.add(address) - - modules_memory_boundaries = cls.get_modules_memory_boundaries( - context, kernel_module_name - ) - - hidden_results = [] - - address_mask = context.layers[kernel.layer_name].address_mask - - for module in cls.get_hidden_modules( - context, - kernel_module_name, - known_modules_addresses, - modules_memory_boundaries, - ): - modinfo = cls.get_module_info_for_module(address_mask, module) - if modinfo: - hidden_results.append(modinfo) - - return hidden_results - - @classmethod - def _get_list_modules( - cls, context: interfaces.context.ContextInterface, kernel_module_name: str - ) -> List[ModuleInfo]: + def _validate_gatherers(cls, caller_wanted_gatherers) -> List[str]: """ - Gather `module` instances from lsmod + Called by `run_modules_scanners` to validate the caller supplied gatherers list + An exception is thrown if an empty gatherers list is given or a list containing an invalid source """ - yield from cls.list_modules(context, kernel_module_name) - - @classmethod - def _get_sysfs_modules( - cls, context: interfaces.context.ContextInterface, kernel_module_name: str - ) -> List[ModuleInfo]: - """ - Gather the `module` instances from sysfs - """ - kernel = context.modules[kernel_module_name] - - sysfs_modules: dict = cls.get_kset_modules(context, kernel_module_name) - - for m_offset in sysfs_modules.values(): - yield kernel.object(object_type="module", offset=m_offset, absolute=True) - - @classmethod - def _validated_sources(cls, caller_wanted_sources) -> List[str]: - """ - Called by `run_modules_scanners` to validate the caller supplied sources list - An exception is thrown if an empty source list is given or a list containing an invalid source - """ - if not caller_wanted_sources: - raise ValueError("`caller_wanted_sources` must have at least one source.") - - if ( - len(caller_wanted_sources) == 1 - and caller_wanted_sources[0] == Modules.source_hidden_identifier - ): + if not caller_wanted_gatherers: raise ValueError( - f"{Modules.source_hidden_identifier} cannot be the only source or there is nothing to compare against." + "`caller_wanted_gatherers` must have at least one gatherer." ) - wanted_sources = [] - - for source in caller_wanted_sources: - if source not in Modules.all_sources_identifier: + for gatherer in caller_wanted_gatherers: + if gatherer not in ModuleGatherers.all_gatherers_identifier: raise ValueError( - f"Invalid source sent through `caller_wanted_sources`: {source}" + f"Invalid gatherer sent through `caller_wanted_gatherers`: {gatherer}" ) - wanted_sources.append(source) - - return wanted_sources - @classmethod def run_modules_scanners( cls, context: interfaces.context.ContextInterface, kernel_module_name: str, - caller_wanted_sources: List[str], + caller_wanted_gatherers: List[ModuleGathererInterface], flatten: bool = True, - ) -> Dict[str, List[ModuleInfo]]: + ) -> Dict[ModuleGathererInterface, List[ModuleInfo]]: """Run module scanning plugins and aggregate the results. It is designed to not operate any inter-plugin results triage. Rules for `caller_wanted_sources`: - If `Modules.all_sources_identifier` is specified then every source will be populated + If `ModuleGathers.all_gathers_identifier` is specified then every source will be populated - If `Modules.source_hidden_identifier` is in the list, then at least one other sources must be + If `ModuleGathers.Scanner` is in the list, then at least one other sources must be specified so a comparison will be populated If empty or an invalid source is specified then a ValueError is thrown @@ -346,52 +268,30 @@ class Modules(interfaces.configuration.VersionableInterface): Returns: Dictionary mapping each plugin to its corresponding result """ - - module_gatherers = { - Modules.source_kernel_identifier: cls.get_kernel_module_info, - Modules.source_lsmod_identifier: cls._get_list_modules, - Modules.source_sysfs_identifier: cls._get_sysfs_modules, - } + # Throws ValueError if invalid gatherers sent in + Modules._validate_gatherers(caller_wanted_gatherers) kernel = context.modules[kernel_module_name] address_mask = context.layers[kernel.layer_name].address_mask - wanted_sources = Modules._validated_sources(caller_wanted_sources) + run_results: Dict[ModuleGathererInterface, List[ModuleInfo]] = {} - run_results = {} - - run_hidden_modules = False - - # Special case hidden modules since it gathers modules on its own - if Modules.source_hidden_identifier in wanted_sources: - run_hidden_modules = True - wanted_sources.remove(Modules.source_hidden_identifier) - - # Walk each source, gathering modules - for wanted_source in wanted_sources: - run_results[wanted_source] = [] - - gatherer = module_gatherers[wanted_source] + # Walk each source gathering modules + for gatherer in caller_wanted_gatherers: + run_results[gatherer] = [] # process each module coming from back the current source - for module in gatherer(context, kernel_module_name): + for module in gatherer.gather_modules(context, kernel_module_name): + # the kernel sends back a ModuleInfo directly - if wanted_source == Modules.source_kernel_identifier: + if gatherer == ModuleGathererKernel: modinfo = module else: modinfo = cls.get_module_info_for_module(address_mask, module) if modinfo: - run_results[wanted_source].append(modinfo) - - # run hidden modules against the other sources - if run_hidden_modules: - run_results[Modules.source_hidden_identifier] = ( - cls._get_hidden_modules_results( - context, kernel_module_name, run_results - ) - ) + run_results[gatherer].append(modinfo) if flatten: return cls.flatten_run_modules_results(run_results) @@ -449,7 +349,7 @@ class Modules(interfaces.configuration.VersionableInterface): Returns: List of ModuleInfo objects """ - uniq_modules: List[Modules.ModuleInfo] = [] + uniq_modules: List[ModuleInfo] = [] seen_addresses: int = set() @@ -645,3 +545,98 @@ class Modules(interfaces.configuration.VersionableInterface): True if all the addresses meet the alignment """ return all(addr % address_alignment == 0 for addr in addresses) + + +class ModuleGathererLsmod(ModuleGathererInterface): + """ + Gathers modules from the main kernel list + """ + + @classmethod + def gather_modules( + cls, context: interfaces.context.ContextInterface, kernel_module_name: str + ) -> ModuleGathererInterface.gatherer_return_type: + yield from Modules.list_modules(context, kernel_module_name) + + +class ModuleGathererSysFs(ModuleGathererInterface): + """ + Gathers modules from the sysfs /sys/modules objects + """ + + @classmethod + def gather_modules( + cls, context: interfaces.context.ContextInterface, kernel_module_name: str + ) -> ModuleGathererInterface.gatherer_return_type: + kernel = context.modules[kernel_module_name] + + sysfs_modules: dict = Modules.get_kset_modules(context, kernel_module_name) + + for m_offset in sysfs_modules.values(): + yield kernel.object(object_type="module", offset=m_offset, absolute=True) + + +class ModuleGathererScanner(ModuleGathererInterface): + """ + Gathers modules by scanning memory + """ + + @classmethod + def gather_modules( + cls, context: interfaces.context.ContextInterface, kernel_module_name: str + ) -> ModuleGathererInterface.gatherer_return_type: + modules_memory_boundaries = Modules.get_modules_memory_boundaries( + context, kernel_module_name + ) + + # Send in an empty list to not filter on any modules + yield from Modules.get_hidden_modules( + context=context, + vmlinux_module_name=kernel_module_name, + known_module_addresses=[], + modules_memory_boundaries=modules_memory_boundaries, + ) + + +class ModuleGathererKernel(ModuleGathererInterface): + """ + Creates a ModuleInfo instance for the kernel so that plugins + can determine when function pointers reference the kernel + """ + + @classmethod + def gather_modules( + cls, context: interfaces.context.ContextInterface, kernel_module_name: str + ) -> ModuleGathererInterface.gatherer_return_type: + """ + Returns a ModuleInfo instance that encodes the kernel + This is required to map function pointers to the kerenl executable + """ + kernel = context.modules[kernel_module_name] + + address_mask = context.layers[kernel.layer_name].address_mask + + start_addr = kernel.object_from_symbol("_text") + start_addr = start_addr.vol.offset & address_mask + + end_addr = kernel.object_from_symbol("_etext") + end_addr = end_addr.vol.offset & address_mask + + yield ModuleInfo(start_addr, constants.linux.KERNEL_NAME, start_addr, end_addr) + + +class ModuleGatherers(interfaces.configuration.VersionableInterface): + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + + framework.require_interface_version(*_required_framework_version) + + # Valid sources of cores kernel module gatherers to send to `run_module_scanners` + # With few exceptions, rootkit checking plugins want all sources + # This provides a stable identifier as new sources are added over time + all_gatherers_identifier = [ + ModuleGathererLsmod, + ModuleGathererSysFs, + ModuleGathererScanner, + ModuleGathererKernel, + ] From 2e93b8ed0982de47d0745975fb516b1b580f84b2 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 14 Mar 2025 20:43:12 +0000 Subject: [PATCH 14/78] Prevent CodeQL from losing its mind. Properly checking for instances of the interface --- .../symbols/linux/utilities/modules.py | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index a930bef20..ee48ebc28 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -225,23 +225,6 @@ class Modules(interfaces.configuration.VersionableInterface): return ModuleInfo(module.vol.offset, mod_name, start, end) - @classmethod - def _validate_gatherers(cls, caller_wanted_gatherers) -> List[str]: - """ - Called by `run_modules_scanners` to validate the caller supplied gatherers list - An exception is thrown if an empty gatherers list is given or a list containing an invalid source - """ - if not caller_wanted_gatherers: - raise ValueError( - "`caller_wanted_gatherers` must have at least one gatherer." - ) - - for gatherer in caller_wanted_gatherers: - if gatherer not in ModuleGatherers.all_gatherers_identifier: - raise ValueError( - f"Invalid gatherer sent through `caller_wanted_gatherers`: {gatherer}" - ) - @classmethod def run_modules_scanners( cls, @@ -268,8 +251,20 @@ class Modules(interfaces.configuration.VersionableInterface): Returns: Dictionary mapping each plugin to its corresponding result """ - # Throws ValueError if invalid gatherers sent in - Modules._validate_gatherers(caller_wanted_gatherers) + if not caller_wanted_gatherers: + raise ValueError( + "`caller_wanted_gatherers` must have at least one gatherer." + ) + + if not isinstance(caller_wanted_gatherers, Iterable): + raise ValueError("`caller_wanted_gatherers` must be iterable") + + for gatherer in caller_wanted_gatherers: + if not issubclass(gatherer, ModuleGathererInterface): + raise ValueError( + f"Invalid gatherer sent through `caller_wanted_gatherers`: {gatherer}" + ) + kernel = context.modules[kernel_module_name] From f5ae7928a300e1eeec2c19fd67b9d7d883a9d2e1 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 14 Mar 2025 20:45:14 +0000 Subject: [PATCH 15/78] Black fix --- volatility3/framework/symbols/linux/utilities/modules.py | 1 - 1 file changed, 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index ee48ebc28..c3700ae60 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -265,7 +265,6 @@ class Modules(interfaces.configuration.VersionableInterface): f"Invalid gatherer sent through `caller_wanted_gatherers`: {gatherer}" ) - kernel = context.modules[kernel_module_name] address_mask = context.layers[kernel.layer_name].address_mask From f38ddaf7155dce6c9171ad052bbfb6db0c9be3a8 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 14 Mar 2025 20:53:02 +0000 Subject: [PATCH 16/78] Fix string scanning code --- volatility3/framework/objects/utility.py | 30 ++++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/volatility3/framework/objects/utility.py b/volatility3/framework/objects/utility.py index b014e37fa..f1ee701bf 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -119,20 +119,26 @@ def gather_contiguous_bytes_from_address( data = b"" - last_address = None + if isinstance(data_layer, interfaces.layers.TranslationLayerInterface): + last_address = None + + for address, length, _, _, _ in data_layer.mapping( + offset=starting_address, length=count, ignore_errors=True + ): + # Used to track when we hit a paged out page + if not last_address: + last_address = address + length + + # we hit a swapped out page + elif last_address and last_address != address: + break + + data += data_layer.read(address, length) - for address, length, _, _, _ in data_layer.mapping( - offset=starting_address, length=count, ignore_errors=True - ): - # Used to track when we hit a paged out page - if not last_address: last_address = address + length - # we hit a swapped out page - elif last_address and last_address != address: - break - - data += data_layer.read(address, length) + elif starting_address + count < data_layer.maximum_address: + data = data_layer.read(starting_address, count) # if we were able to read from the first page, we want to try and construct the string # if the first page fails -> throw exception @@ -143,8 +149,6 @@ def gather_contiguous_bytes_from_address( layer_name=data_layer, invalid_address=starting_address ) - return data - def bytes_to_decoded_string(data: bytes, encoding: str, errors: str) -> bytes: """ From bb0a17004f004b6eac4113a3e9377acdc4bc6e6c Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 14 Mar 2025 20:59:15 +0000 Subject: [PATCH 17/78] Add return_truncated for plugin-specified handling of truncated strings --- volatility3/framework/objects/utility.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/objects/utility.py b/volatility3/framework/objects/utility.py index f1ee701bf..a29b65875 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -150,8 +150,19 @@ def gather_contiguous_bytes_from_address( ) -def bytes_to_decoded_string(data: bytes, encoding: str, errors: str) -> bytes: +def bytes_to_decoded_string( + data: bytes, encoding: str, errors: str, return_truncated: bool = True +) -> bytes: """ + Args: + data: The `bytes` buffer containing the string of a string at offset 0 + encoding: An encoding value for the encoding paramater of `bytes.decode` + errors: An errors value for the errors parameter of `bytes.decode` + return_truncated: Dictates whether truncated strings should be returned or + if a ValueError should be thrown if a truncated (broken) string was decoded + Returns: + bytes: The decoded string starting at offset of data + This function takes a bytes buffer that contains at a string of unknown length starting at the first byte, and returns the properly decoded string @@ -173,7 +184,12 @@ def bytes_to_decoded_string(data: bytes, encoding: str, errors: str) -> bytes: try: idx = termination_re.search(full_decoded_string).start() except AttributeError: - idx = len(full_decoded_string) + if return_truncated: + idx = len(full_decoded_string) + else: + raise ValueError( + "return_truncated set to False and truncated string decoded." + ) # cut at terminating byte, if found data = data[:idx] From a0f3cba6f6d71648a4feb884502491b773815255 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 14 Mar 2025 21:08:15 +0000 Subject: [PATCH 18/78] Fix stale comments --- volatility3/framework/symbols/linux/utilities/modules.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index c3700ae60..957d7b5b4 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -237,14 +237,9 @@ class Modules(interfaces.configuration.VersionableInterface): to not operate any inter-plugin results triage. Rules for `caller_wanted_sources`: - - If `ModuleGathers.all_gathers_identifier` is specified then every source will be populated - - If `ModuleGathers.Scanner` is in the list, then at least one other sources must be - specified so a comparison will be populated + If `ModuleGatherers.all_gathers_identifier` is specified then every source will be populated If empty or an invalid source is specified then a ValueError is thrown - Args: called_wanted_sources: The list of sources to gather modules. flatten: Whether to de-duplicate modules across sources From b2ec21fcf28e657f42f178c27c144af7d1d7893e Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 14 Mar 2025 21:15:03 +0000 Subject: [PATCH 19/78] Simplify last_address handling --- volatility3/framework/objects/utility.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/objects/utility.py b/volatility3/framework/objects/utility.py index a29b65875..018b4a138 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -120,17 +120,13 @@ def gather_contiguous_bytes_from_address( data = b"" if isinstance(data_layer, interfaces.layers.TranslationLayerInterface): - last_address = None + last_address = starting_address for address, length, _, _, _ in data_layer.mapping( offset=starting_address, length=count, ignore_errors=True ): - # Used to track when we hit a paged out page - if not last_address: - last_address = address + length - # we hit a swapped out page - elif last_address and last_address != address: + if last_address != address: break data += data_layer.read(address, length) From 53e32e36f6128fa3ec53e77f3e7fb859cbc1d173 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 14 Mar 2025 22:34:06 +0000 Subject: [PATCH 20/78] Add versioning on gatherers, make check non-specific to kernel gatherer, add requirements to ModuleGatherers --- .../symbols/linux/utilities/modules.py | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index 957d7b5b4..70b45fbd9 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -23,6 +23,7 @@ from volatility3.framework import ( objects, ) +from volatility3.framework.configuration import requirements from volatility3.framework.objects import utility from volatility3.framework.symbols.linux import extensions @@ -274,7 +275,7 @@ class Modules(interfaces.configuration.VersionableInterface): for module in gatherer.gather_modules(context, kernel_module_name): # the kernel sends back a ModuleInfo directly - if gatherer == ModuleGathererKernel: + if isinstance(module, ModuleInfo): modinfo = module else: modinfo = cls.get_module_info_for_module(address_mask, module) @@ -541,6 +542,10 @@ class ModuleGathererLsmod(ModuleGathererInterface): Gathers modules from the main kernel list """ + _version = (1, 0, 0) + + name = "Lsmod" + @classmethod def gather_modules( cls, context: interfaces.context.ContextInterface, kernel_module_name: str @@ -553,6 +558,10 @@ class ModuleGathererSysFs(ModuleGathererInterface): Gathers modules from the sysfs /sys/modules objects """ + _version = (1, 0, 0) + + name = "SysFs" + @classmethod def gather_modules( cls, context: interfaces.context.ContextInterface, kernel_module_name: str @@ -570,6 +579,10 @@ class ModuleGathererScanner(ModuleGathererInterface): Gathers modules by scanning memory """ + _version = (1, 0, 0) + + name = "Scanner" + @classmethod def gather_modules( cls, context: interfaces.context.ContextInterface, kernel_module_name: str @@ -593,6 +606,10 @@ class ModuleGathererKernel(ModuleGathererInterface): can determine when function pointers reference the kernel """ + _version = (1, 0, 0) + + name = "kernel" + @classmethod def gather_modules( cls, context: interfaces.context.ContextInterface, kernel_module_name: str @@ -614,7 +631,10 @@ class ModuleGathererKernel(ModuleGathererInterface): yield ModuleInfo(start_addr, constants.linux.KERNEL_NAME, start_addr, end_addr) -class ModuleGatherers(interfaces.configuration.VersionableInterface): +class ModuleGatherers( + interfaces.configuration.VersionableInterface, + interfaces.configuration.ConfigurableInterface, +): _version = (1, 0, 0) _required_framework_version = (2, 0, 0) @@ -629,3 +649,19 @@ class ModuleGatherers(interfaces.configuration.VersionableInterface): ModuleGathererScanner, ModuleGathererKernel, ] + + @classmethod + def get_requirements(cls): + reqs = [] + + # for now, all versions are 1, this will be broken out if/when that changes + for gatherer in ModuleGatherers.all_gatherers_identifier: + reqs.append( + requirements.VersionRequirement( + name=gatherer.name.replace(" ", ""), + component=gatherer, + version=(1, 0, 0), + ) + ) + + return reqs From 674cc045d21694647857f9d47ca3ff94f49a8c51 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sat, 15 Mar 2025 17:22:31 +0000 Subject: [PATCH 21/78] Update to using str dictionary key. Add requirements for each gatherer in modxview. Validate names are unique while sanity checking. --- .../framework/plugins/linux/modxview.py | 24 ++++++++++----- .../symbols/linux/utilities/modules.py | 30 ++++++++++++++----- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/volatility3/framework/plugins/linux/modxview.py b/volatility3/framework/plugins/linux/modxview.py index b04eb74ca..cf31a3a33 100644 --- a/volatility3/framework/plugins/linux/modxview.py +++ b/volatility3/framework/plugins/linux/modxview.py @@ -38,7 +38,17 @@ spot modules presence and taints.""" ), requirements.VersionRequirement( name="linux_utilities_module_gatherers", - component=linux_utilities_modules.ModuleGatherers, + component=linux_utilities_modules.ModuleGathererLsmod, + version=(1, 0, 0), + ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGathererSysFs, + version=(1, 0, 0), + ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGathererScanner, version=(1, 0, 0), ), requirements.VersionRequirement( @@ -113,14 +123,14 @@ spot modules presence and taints.""" # We want to be explicit on the plugins results we are interested in for gatherer in wanted_gatherers: # Iterate over each recovered module - for mod_info in run_results[gatherer]: + for mod_info in run_results[gatherer.name]: # Use offsets as unique keys, whether a module # appears in many plugin runs or not if aggregated_modules.get(mod_info.offset, None) is not None: # Append the plugin to the list of originating plugins - aggregated_modules[mod_info.offset].append(gatherer) + aggregated_modules[mod_info.offset].append(gatherer.name) else: - aggregated_modules[mod_info.offset] = [gatherer] + aggregated_modules[mod_info.offset] = [gatherer.name] for module_offset, gatherers in aggregated_modules.items(): module = kernel.object("module", offset=module_offset, absolute=True) @@ -148,9 +158,9 @@ spot modules presence and taints.""" ( module.get_name() or NotAvailableValue(), format_hints.Hex(module_offset), - linux_utilities_modules.ModuleGathererLsmod in gatherers, - linux_utilities_modules.ModuleGathererSysFs in gatherers, - linux_utilities_modules.ModuleGathererScanner in gatherers, + linux_utilities_modules.ModuleGathererLsmod.name in gatherers, + linux_utilities_modules.ModuleGathererSysFs.name in gatherers, + linux_utilities_modules.ModuleGathererScanner.name in gatherers, taints or NotAvailableValue(), ), ) diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index 70b45fbd9..eeee98a01 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -233,19 +233,21 @@ class Modules(interfaces.configuration.VersionableInterface): kernel_module_name: str, caller_wanted_gatherers: List[ModuleGathererInterface], flatten: bool = True, - ) -> Dict[ModuleGathererInterface, List[ModuleInfo]]: + ) -> Dict[str, List[ModuleInfo]]: """Run module scanning plugins and aggregate the results. It is designed to not operate any inter-plugin results triage. - Rules for `caller_wanted_sources`: + Rules for `caller_wanted_gatherers`: If `ModuleGatherers.all_gathers_identifier` is specified then every source will be populated - If empty or an invalid source is specified then a ValueError is thrown + If empty or an invalid gatherer is specified then a ValueError is thrown + + All gatherer names must be unique Args: called_wanted_sources: The list of sources to gather modules. - flatten: Whether to de-duplicate modules across sources + flatten: Whether to de-duplicate modules across gatherers Returns: - Dictionary mapping each plugin to its corresponding result + Dictionary mapping each gatherer to its corresponding result """ if not caller_wanted_gatherers: raise ValueError( @@ -255,12 +257,26 @@ class Modules(interfaces.configuration.VersionableInterface): if not isinstance(caller_wanted_gatherers, Iterable): raise ValueError("`caller_wanted_gatherers` must be iterable") + seen_names = set() + for gatherer in caller_wanted_gatherers: if not issubclass(gatherer, ModuleGathererInterface): raise ValueError( f"Invalid gatherer sent through `caller_wanted_gatherers`: {gatherer}" ) + if not hasattr(gatherer, "name"): + raise ValueError( + f"{gatherer} does not have a name attribute, which is required." + ) + + if gatherer.name in seen_names: + raise ValueError( + f"{gatherer} has a name {gatherer.name} which has already been processed. Names must be unique." + ) + + seen_names.add(gatherer.name) + kernel = context.modules[kernel_module_name] address_mask = context.layers[kernel.layer_name].address_mask @@ -269,7 +285,7 @@ class Modules(interfaces.configuration.VersionableInterface): # Walk each source gathering modules for gatherer in caller_wanted_gatherers: - run_results[gatherer] = [] + run_results[gatherer.name] = [] # process each module coming from back the current source for module in gatherer.gather_modules(context, kernel_module_name): @@ -281,7 +297,7 @@ class Modules(interfaces.configuration.VersionableInterface): modinfo = cls.get_module_info_for_module(address_mask, module) if modinfo: - run_results[gatherer].append(modinfo) + run_results[gatherer.name].append(modinfo) if flatten: return cls.flatten_run_modules_results(run_results) From 0f5dd10aa9ba8370c8153dd6d36fb600dd03f888 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sat, 15 Mar 2025 13:23:01 -0500 Subject: [PATCH 22/78] Apply suggestions from code review Co-authored-by: ikelos --- volatility3/framework/plugins/linux/modxview.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/linux/modxview.py b/volatility3/framework/plugins/linux/modxview.py index cf31a3a33..ed21acfd1 100644 --- a/volatility3/framework/plugins/linux/modxview.py +++ b/volatility3/framework/plugins/linux/modxview.py @@ -37,17 +37,17 @@ spot modules presence and taints.""" version=(3, 0, 0), ), requirements.VersionRequirement( - name="linux_utilities_module_gatherers", + name="linux_utilities_module_gatherer_lsmod", component=linux_utilities_modules.ModuleGathererLsmod, version=(1, 0, 0), ), requirements.VersionRequirement( - name="linux_utilities_module_gatherers", + name="linux_utilities_module_gatherer_sysfs", component=linux_utilities_modules.ModuleGathererSysFs, version=(1, 0, 0), ), requirements.VersionRequirement( - name="linux_utilities_module_gatherers", + name="linux_utilities_module_gatherer_scanner", component=linux_utilities_modules.ModuleGathererScanner, version=(1, 0, 0), ), From e79f03691a1aa84afa3e2a1fe5fc9746709a5d70 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sat, 15 Mar 2025 19:01:52 +0000 Subject: [PATCH 23/78] Add name to interface and validate it in processing loop --- volatility3/framework/symbols/linux/utilities/modules.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index eeee98a01..b711f4e2c 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -51,6 +51,9 @@ class ModuleGathererInterface( gatherer_return_type = Generator[Union[ModuleInfo, "extensions.module"], None, None] + # Must be set to a unique, descriptive name of the gathering technique or data structure source + name = None + @classmethod @abstractmethod def gather_modules( @@ -265,9 +268,9 @@ class Modules(interfaces.configuration.VersionableInterface): f"Invalid gatherer sent through `caller_wanted_gatherers`: {gatherer}" ) - if not hasattr(gatherer, "name"): + if gatherer.name is None or len(gatherer.name) == 0: raise ValueError( - f"{gatherer} does not have a name attribute, which is required." + f"{gatherer} does not have a valid name attribute, which is required. It must be a non-zero length string." ) if gatherer.name in seen_names: From 9da147df1b6b79a5c9ab0a67ed3593829bb6c598 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sat, 15 Mar 2025 19:10:00 +0000 Subject: [PATCH 24/78] simplify check --- 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 b711f4e2c..52dfe77e4 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -268,7 +268,7 @@ class Modules(interfaces.configuration.VersionableInterface): f"Invalid gatherer sent through `caller_wanted_gatherers`: {gatherer}" ) - if gatherer.name is None or len(gatherer.name) == 0: + if not gatherer.name: raise ValueError( f"{gatherer} does not have a valid name attribute, which is required. It must be a non-zero length string." ) From 9550db82a99d8ac5220048605152092ee92db0cd Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sat, 15 Mar 2025 20:31:01 +0000 Subject: [PATCH 25/78] Bump the ruff action --- .github/workflows/ruff.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml index 77e3aa864..98a05a616 100644 --- a/.github/workflows/ruff.yaml +++ b/.github/workflows/ruff.yaml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: astral-sh/ruff-action@v1 + - uses: astral-sh/ruff-action@v3.2.1 with: args: check src: "." From fbb4003a3224a6fc24f0a872b0b4253d778697c3 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sat, 15 Mar 2025 20:59:14 +0000 Subject: [PATCH 28/78] Fix broken truncation from Vol3 bytes to string conversion --- 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 018b4a138..473c5a349 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -188,7 +188,7 @@ def bytes_to_decoded_string( ) # cut at terminating byte, if found - data = data[:idx] + data = bytes(full_decoded_string[:idx], encoding=encoding) # return with caller-specified encoding and errors return data.decode(encoding=encoding, errors=errors) From 2d2228b06e89bc08ae53aba05059488327ad5ec2 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sat, 15 Mar 2025 21:25:39 +0000 Subject: [PATCH 29/78] Fix signature --- 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 473c5a349..57d1bca4c 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -105,7 +105,7 @@ def pointer_to_string( def gather_contiguous_bytes_from_address( context, data_layer, starting_address: int, count: int -) -> bytes: +) -> str: """ This method reconstructs a string from memory while also carefully examining each page From bad34a112aabfc4c7a0e3eb29f3d7716fa839faf Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sat, 15 Mar 2025 22:58:07 +0000 Subject: [PATCH 30/78] Add the windows plugin and associated extensions updates --- .../framework/plugins/windows/windows.py | 137 ++++++++++++ .../symbols/windows/extensions/gui.py | 199 +++++++++++++++++- .../windows/gui/gui-win10-10586-x64.json | 6 + .../windows/gui/gui-win10-15063-x64.json | 6 + .../windows/gui/gui-win10-16299-x64.json | 6 + .../windows/gui/gui-win10-17134-x64.json | 10 +- .../windows/gui/gui-win10-17735-x64.json | 10 +- .../windows/gui/gui-win10-17763-x64.json | 10 +- .../windows/gui/gui-win10-18362-x64.json | 10 +- .../windows/gui/gui-win10-19041-x64.json | 10 +- .../windows/gui/gui-win10-19577-x64.json | 10 +- .../symbols/windows/gui/gui-win7sp0-x64.json | 6 + .../symbols/windows/gui/gui-win7sp1-x64.json | 6 + .../symbols/windows/gui/gui-win8-x64.json | 6 + 14 files changed, 418 insertions(+), 14 deletions(-) create mode 100644 volatility3/framework/plugins/windows/windows.py diff --git a/volatility3/framework/plugins/windows/windows.py b/volatility3/framework/plugins/windows/windows.py new file mode 100644 index 000000000..5e3059e45 --- /dev/null +++ b/volatility3/framework/plugins/windows/windows.py @@ -0,0 +1,137 @@ +# 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, Iterable + +from volatility3.framework import interfaces, renderers, exceptions +from volatility3.framework.objects import utility +from volatility3.framework.configuration import requirements +from volatility3.framework.renderers import format_hints +from volatility3.plugins.windows import windowstations + +vollog = logging.getLogger(__name__) + + +class Windows(interfaces.plugins.PluginInterface): + """Enumerates the Windows of Desktop instances""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # Since we're calling the plugin, make sure we have the plugin's requirements + return [ + requirements.ModuleRequirement( + name="kernel", + description="Windows kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.VersionRequirement( + name="windowstations", + component=windowstations.WindowStations, + version=(1, 0, 0), + ), + ] + + @classmethod + def list_windows( + cls, + context: interfaces.context.ContextInterface, + config_path: str, + kernel_module_name: str, + ) -> Iterable[interfaces.objects.ObjectInterface]: + """ + Enumerates the desktops of each window station + For each found, enumerates its windows within the desktop + """ + kernel = context.modules[kernel_module_name] + + for ( + winsta, + station_name, + session_id, + ) in windowstations.WindowStations.scan_window_stations( + context, config_path, kernel_module_name + ): + # for each window station, walk its list of desktops + for desktop, desktop_name in winsta.desktops(kernel.symbol_table_name): + try: + top_window = desktop.pDeskInfo.spwnd + except exceptions.InvalidAddressException: + vollog.debug( + f"Desktop with name {desktop_name} in window station {station_name} has a broken window pointer." + ) + continue + + for window, window_name in desktop.windows(top_window): + yield station_name, desktop_name, window, window_name + + def _generator(self): + kernel_name = self.config["kernel"] + + # call the implementation for finding windows and gather attributes + for station_name, desktop_name, window, window_name in self.list_windows( + self.context, self.config_path, kernel_name + ): + # We need a valid process and session id for the window to display it + process = window.get_process() + process_name = None + if process: + try: + process_name = utility.array_to_string(process.ImageFileName) + process_pid = process.UniqueProcessId + except exceptions.InvalidAddressException: + vollog.debug( + f"Unable to read name and pid of the process for window {window.vol.offset:#x}" + ) + + if process_name is None: + vollog.warning( + f"Invalid process reference for the process hosting window {window.vol.offset:#x}" + ) + continue + + sess_id = window.get_session_id() + if sess_id is None: + vollog.debug( + f"Unable to read session id of the process for window {window.vol.offset:#x} in process {process_name}" + ) + continue + + # procedures can be empty, but if set, should be a valid pointer + window_proc = window.get_window_procedure() + if window_proc is None or window_proc == 0 or window_proc > 0x1000: + window_proc = format_hints.Hex(window_proc) + else: + vollog.warning( + f"Invalid window procedure for the window {window.vol.offset:#x}" + ) + continue + + yield 0, ( + format_hints.Hex(window.vol.offset), + station_name, + sess_id, + desktop_name, + window_name or renderers.NotAvailableValue(), + window_proc, + process_name, + process_pid, + ) + + def run(self): + return renderers.TreeGrid( + [ + ("Offset", format_hints.Hex), + ("Station", str), + ("Session", int), + ("Desktop", str), + ("Window", str), + ("Procedure", format_hints.Hex), + ("Process", str), + ("PID", int), + ], + self._generator(), + ) diff --git a/volatility3/framework/symbols/windows/extensions/gui.py b/volatility3/framework/symbols/windows/extensions/gui.py index 92693dba5..559fda095 100644 --- a/volatility3/framework/symbols/windows/extensions/gui.py +++ b/volatility3/framework/symbols/windows/extensions/gui.py @@ -2,13 +2,17 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # -from typing import Optional, Tuple, Iterator +import logging +from typing import Optional, Tuple, Iterator, Generator from volatility3.framework import exceptions, constants, interfaces from volatility3.framework import objects from volatility3.framework.objects import utility +from volatility3.framework.symbols.windows import extensions from volatility3.framework.symbols.windows.extensions import pool +vollog = logging.getLogger(__name__) + class tagWINDOWSTATION(objects.StructType, pool.ExecutiveObject): def is_valid(self) -> bool: @@ -77,7 +81,7 @@ class tagWINDOWSTATION(objects.StructType, pool.ExecutiveObject): class tagDESKTOP(objects.StructType, pool.ExecutiveObject): def is_valid(self) -> bool: """ - Enforce a valid sid + owning window station + Enforce a valid sid + name """ sid = self.get_session_id() @@ -89,12 +93,18 @@ class tagDESKTOP(objects.StructType, pool.ExecutiveObject): return False def get_window_station(self) -> Optional["tagWINDOWSTATION"]: + """ + Attempts to return the window station for this desktop + """ try: return self.rpwinstaParent.dereference() except exceptions.InvalidAddressException: return None def get_session_id(self) -> Optional[int]: + """ + Attempts to return the session ID for this desktop + """ winsta = self.get_window_station() if winsta: return winsta.get_session_id() @@ -120,8 +130,193 @@ class tagDESKTOP(objects.StructType, pool.ExecutiveObject): yield thread, process_name, process_pid + def _do_get_windows( + self, window, max_windows + ) -> Generator[Tuple[interfaces.objects.ObjectInterface, str], None, None]: + """ + Recusively walks and yields the adjacent and child windows + """ + seen_windows = set() + seen_children = set() + + if window.vol.offset == 0: + return + + yield window, window.get_name() + + seen_windows.add(window) + + # Walk adjacent windows + while len(seen_windows) < max_windows: + try: + window = window.spwndNext.dereference() + except exceptions.InvalidAddressException: + break + + if window.vol.offset == 0: + break + + if window.vol.offset in seen_windows: + break + + yield window, window.get_name() + + seen_windows.add(window) + + # Walk children windows and recursively yield them + for window in seen_windows: + child = window + + while len(seen_windows) + len(seen_children) < max_windows: + try: + child = child.spwndChild + except exceptions.InvalidAddressException: + break + + if child.vol.offset == 0: + break + + if child in seen_children: + break + seen_children.add(child) + + yield from self._do_get_windows(child, max_windows) + + def windows( + self, window, max_windows=10000 + ) -> Generator[Tuple[interfaces.objects.ObjectInterface, str], None, None]: + """ + Enumerates all windows adjacent to and children of `window` + + Args: + window: The window to enumerate windows from + + Returns: + A generator of tuples containing the window and its name + """ + seen_windows = set() + + for window, window_name in self._do_get_windows(window, max_windows): + if window.vol.offset in seen_windows: + continue + + seen_windows.add(window.vol.offset) + + yield window, window_name + + if len(seen_windows) == max_windows: + break + + +class tagWND(objects.StructType, pool.ExecutiveObject): + + def is_valid(self) -> bool: + """ + Enforce a valid sid + """ + sid = self.get_session_id() + + return sid is not None and 0 <= sid < 256 + + def get_name(self) -> Optional[str]: + """ + directName appeared in later Windows 10 versions and is pointer + strName is a unicode string directly in the structure + """ + if self.has_member("directName"): + try: + return utility.pointer_to_string( + self.directName, count=256, encoding="utf16" + ) + except exceptions.InvalidAddressException: + vollog.debug( + f"directname for window at {self.vol.offset:#x} in layer {self.vol.layer_name} is invalid" + ) + + try: + return self.strName.get_string() + except exceptions.InvalidAddressException: + vollog.debug( + f"strName for window at {self.vol.offset:#x} in layer {self.vol.layer_name} is invalid" + ) + + return None + + def get_session_id(self) -> Optional[int]: + """ + Uses its tagDESKTOP pointer to find its session + """ + desktop = self.get_desktop() + if desktop: + return desktop.get_session_id() + + return None + + def get_desktop(self) -> Optional[tagDESKTOP]: + """ + Attempts to return the host desktop (tagDESKTOP) for this window + """ + try: + return self.head.rpdesk.dereference() + except exceptions.InvalidAddressException: + vollog.debug( + f"Reading the desktop pointer for window {self.vol.offset:#x} caused a page fault" + ) + return None + + def get_process(self) -> Optional["extensions.EPROCESS"]: + """ + Attempts to return the host process (_EPROCESS) for this window + """ + try: + return self.head.pti.ppi.Process.dereference() + except exceptions.InvalidAddressException: + vollog.debug( + f"Reading the process pointer for window {self.vol.offset:#x} caused a page fault" + ) + return None + + def get_window_procedure(self): + """ + Attempts to return the window procedure for this windows + """ + try: + # >= 17134 + if hasattr(self, "subPointer"): + return self.subPointer.lpfnWndProc + else: + return self.lpfnWndProc + except exceptions.InvalidAddressException: + vollog.debug(f"Invalid window procedure for window {self.vol.offset:#x}") + return None + + +# This is copy/paste from UNICODE_STRING in `symbols/windows/extensions/__init__.py` +# The versioning of modules would get very ugly if we let different modules share implementations +# across different data structures +class LARGE_UNICODE_STRING(objects.StructType): + """A class for Windows unicode string structures.""" + + def get_string(self) -> interfaces.objects.ObjectInterface: + # We explicitly do *not* catch errors here, we allow an exception to be thrown + # (otherwise there's no way to determine anything went wrong) + # It's up to the user of this method to catch exceptions + + # We manually construct an object rather than casting a dereferenced pointer in case + # the buffer length is 0 and the pointer is a NULL pointer + return self._context.object( + self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "string", + layer_name=self.Buffer.vol.native_layer_name, + offset=self.Buffer, + max_length=self.Length, + errors="replace", + encoding="utf16", + ) + class_types = { "tagWINDOWSTATION": tagWINDOWSTATION, "tagDESKTOP": tagDESKTOP, + "tagWND": tagWND, + "_LARGE_UNICODE_STRING": LARGE_UNICODE_STRING, } diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-10586-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-10586-x64.json index a542d4778..0308f4c7d 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-10586-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-10586-x64.json @@ -18036,6 +18036,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-15063-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-15063-x64.json index 7c4af02e1..a69cbb7c3 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-15063-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-15063-x64.json @@ -18036,6 +18036,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-16299-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-16299-x64.json index 1ff5fdcd9..54e8aeec3 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-16299-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-16299-x64.json @@ -18036,6 +18036,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-17134-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-17134-x64.json index f74c8dd5b..48b97a1c2 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-17134-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-17134-x64.json @@ -12464,8 +12464,8 @@ "directName": { "type": { "subtype": { - "kind": "struct", - "name": "nt_symbols!String" + "kind": "base", + "name": "char" }, "kind": "pointer" }, @@ -18079,6 +18079,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-17735-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-17735-x64.json index 88e419100..affaf8731 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-17735-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-17735-x64.json @@ -12464,8 +12464,8 @@ "directName": { "type": { "subtype": { - "kind": "struct", - "name": "nt_symbols!String" + "kind": "base", + "name": "char" }, "kind": "pointer" }, @@ -18079,6 +18079,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-17763-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-17763-x64.json index ed183f39b..33db6c28d 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-17763-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-17763-x64.json @@ -12464,8 +12464,8 @@ "directName": { "type": { "subtype": { - "kind": "struct", - "name": "nt_symbols!String" + "kind": "base", + "name": "char" }, "kind": "pointer" }, @@ -18079,6 +18079,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-18362-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-18362-x64.json index f568eedbe..bb1c74f7f 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-18362-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-18362-x64.json @@ -12464,8 +12464,8 @@ "directName": { "type": { "subtype": { - "kind": "struct", - "name": "nt_symbols!String" + "kind": "base", + "name": "char" }, "kind": "pointer" }, @@ -18079,6 +18079,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-19041-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-19041-x64.json index be4341cfd..74868f1a7 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-19041-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-19041-x64.json @@ -12464,8 +12464,8 @@ "directName": { "type": { "subtype": { - "kind": "struct", - "name": "nt_symbols!String" + "kind": "base", + "name": "char" }, "kind": "pointer" }, @@ -18079,6 +18079,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-19577-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-19577-x64.json index 68692dcf4..e718710cf 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-19577-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-19577-x64.json @@ -12464,8 +12464,8 @@ "directName": { "type": { "subtype": { - "kind": "struct", - "name": "nt_symbols!String" + "kind": "base", + "name": "char" }, "kind": "pointer" }, @@ -18079,6 +18079,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win7sp0-x64.json b/volatility3/framework/symbols/windows/gui/gui-win7sp0-x64.json index ec81241b2..9b413baaa 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win7sp0-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win7sp0-x64.json @@ -18619,6 +18619,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win7sp1-x64.json b/volatility3/framework/symbols/windows/gui/gui-win7sp1-x64.json index ae844e535..b856506a7 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win7sp1-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win7sp1-x64.json @@ -17985,6 +17985,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win8-x64.json b/volatility3/framework/symbols/windows/gui/gui-win8-x64.json index e7581413f..8662bf62b 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win8-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win8-x64.json @@ -17992,6 +17992,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", From d22d513716804bb4af002cefbe8cbeed2afcadb1 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sun, 16 Mar 2025 16:40:53 +0000 Subject: [PATCH 31/78] Re-type the correct function --- 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 57d1bca4c..ef702060c 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -105,7 +105,7 @@ def pointer_to_string( def gather_contiguous_bytes_from_address( context, data_layer, starting_address: int, count: int -) -> str: +) -> bytes: """ This method reconstructs a string from memory while also carefully examining each page @@ -148,7 +148,7 @@ def gather_contiguous_bytes_from_address( def bytes_to_decoded_string( data: bytes, encoding: str, errors: str, return_truncated: bool = True -) -> bytes: +) -> str: """ Args: data: The `bytes` buffer containing the string of a string at offset 0 From c8869d87cd447e258ae2f9172f2db158a75d841f Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sun, 16 Mar 2025 17:15:43 +0000 Subject: [PATCH 32/78] Version the GUI extensions. Correctly check windows procedure --- .../framework/plugins/windows/windows.py | 6 +- .../symbols/windows/extensions/gui.py | 502 +++++++++--------- 2 files changed, 260 insertions(+), 248 deletions(-) diff --git a/volatility3/framework/plugins/windows/windows.py b/volatility3/framework/plugins/windows/windows.py index 5e3059e45..a7c350ea9 100644 --- a/volatility3/framework/plugins/windows/windows.py +++ b/volatility3/framework/plugins/windows/windows.py @@ -102,11 +102,13 @@ class Windows(interfaces.plugins.PluginInterface): # procedures can be empty, but if set, should be a valid pointer window_proc = window.get_window_procedure() - if window_proc is None or window_proc == 0 or window_proc > 0x1000: + if window_proc is None: + window_proc = renderers.NotAvailableValue() + elif window_proc == 0 or window_proc > 0x1000: window_proc = format_hints.Hex(window_proc) else: vollog.warning( - f"Invalid window procedure for the window {window.vol.offset:#x}" + f"Invalid window procedure {window_proc} for the window {window.vol.offset:#x}" ) continue diff --git a/volatility3/framework/symbols/windows/extensions/gui.py b/volatility3/framework/symbols/windows/extensions/gui.py index 559fda095..48ec2eba4 100644 --- a/volatility3/framework/symbols/windows/extensions/gui.py +++ b/volatility3/framework/symbols/windows/extensions/gui.py @@ -5,6 +5,7 @@ import logging from typing import Optional, Tuple, Iterator, Generator +from volatility3 import framework from volatility3.framework import exceptions, constants, interfaces from volatility3.framework import objects from volatility3.framework.objects import utility @@ -13,310 +14,319 @@ from volatility3.framework.symbols.windows.extensions import pool vollog = logging.getLogger(__name__) +class GUIExtensions(interfaces.configuration.VersionableInterface): + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) -class tagWINDOWSTATION(objects.StructType, pool.ExecutiveObject): - def is_valid(self) -> bool: - sid = self.get_session_id() - return sid is not None and 0 <= sid < 256 + framework.require_interface_version(*_required_framework_version) - def get_session_id(self) -> Optional[int]: - try: - return self.dwSessionId - except exceptions.InvalidAddressException: - return None + class tagWINDOWSTATION(objects.StructType, pool.ExecutiveObject): + def is_valid(self) -> bool: + sid = self.get_session_id() + return sid is not None and 0 <= sid < 256 - def traverse(self, max_stations: int = 15): - """ - Traverses the window stations referenced in the list of stations - """ - seen = set() - - # include the first window station - yield self - - while len(seen) < max_stations: + def get_session_id(self) -> Optional[int]: try: - winsta = self.rpwinstaNext.dereference() + return self.dwSessionId except exceptions.InvalidAddressException: - break + return None - if winsta.vol.offset in seen: - break + def traverse(self, max_stations: int = 15): + """ + Traverses the window stations referenced in the list of stations + """ + seen = set() - yield winsta + # include the first window station + yield self - seen.add(winsta.vol.offset) + while len(seen) < max_stations: + try: + winsta = self.rpwinstaNext.dereference() + except exceptions.InvalidAddressException: + break + + if winsta.vol.offset in seen: + break + + yield winsta + + seen.add(winsta.vol.offset) + + def get_info(self, kernel_symbol_table_name) -> Optional[Tuple[str, int]]: + try: + name = self.get_name(kernel_symbol_table_name) + session_id = self.get_session_id() + except exceptions.InvalidAddressException: + return None, None + + # attempt to avoid smear + if session_id is not None and session_id < 256 and name and len(name) > 1: + return name, session_id - def get_info(self, kernel_symbol_table_name) -> Optional[Tuple[str, int]]: - try: - name = self.get_name(kernel_symbol_table_name) - session_id = self.get_session_id() - except exceptions.InvalidAddressException: return None, None - # attempt to avoid smear - if session_id is not None and session_id < 256 and name and len(name) > 1: - return name, session_id + def desktops(self, symbol_table_name, max_desktops: int = 12): + seen = set() - return None, None + while len(seen) < max_desktops: + try: + desktop = self.rpdeskList.dereference() + name = desktop.get_name(symbol_table_name) + except exceptions.InvalidAddressException: + break - def desktops(self, symbol_table_name, max_desktops: int = 12): - seen = set() + if desktop.vol.offset in seen: + break - while len(seen) < max_desktops: + yield desktop, name + + seen.add(desktop.vol.offset) + + + class tagDESKTOP(objects.StructType, pool.ExecutiveObject): + def is_valid(self) -> bool: + """ + Enforce a valid session ID and Window station + We aren't interested in terminated desktops as there are so many pointers + going from station -> desktop -> windows, that we would just be processing junk. + Even if the pointers were still in tact by some miracle, its not that helpful to + have a floating desktop appear in the output as you can't do much with it. + """ + sid = self.get_session_id() + + valid_sid = sid is not None and 0 <= sid < 256 + + if valid_sid: + return self.get_window_station() is not None + + return False + + def get_window_station(self) -> Optional["tagWINDOWSTATION"]: + """ + Attempts to return the window station for this desktop + """ try: - desktop = self.rpdeskList.dereference() - name = desktop.get_name(symbol_table_name) + return self.rpwinstaParent.dereference() except exceptions.InvalidAddressException: - break + return None - if desktop.vol.offset in seen: - break + def get_session_id(self) -> Optional[int]: + """ + Attempts to return the session ID for this desktop + """ + winsta = self.get_window_station() + if winsta: + return winsta.get_session_id() - yield desktop, name - - seen.add(desktop.vol.offset) - - -class tagDESKTOP(objects.StructType, pool.ExecutiveObject): - def is_valid(self) -> bool: - """ - Enforce a valid sid + name - """ - sid = self.get_session_id() - - valid_sid = sid is not None and 0 <= sid < 256 - - if valid_sid: - return self.get_window_station() is not None - - return False - - def get_window_station(self) -> Optional["tagWINDOWSTATION"]: - """ - Attempts to return the window station for this desktop - """ - try: - return self.rpwinstaParent.dereference() - except exceptions.InvalidAddressException: return None - def get_session_id(self) -> Optional[int]: - """ - Attempts to return the session ID for this desktop - """ - winsta = self.get_window_station() - if winsta: - return winsta.get_session_id() + def get_threads( + self, + ) -> Iterator[Tuple[interfaces.objects.ObjectInterface, str, int]]: + """ + Returns the threads of each desktop along with owning process information + """ + symbol_table_name = self.vol.type_name.split(constants.BANG)[0] - return None + for thread in self.PtiList.to_list( + symbol_table_name + constants.BANG + "tagTHREADINFO", "PtiLink" + ): + try: + process_name = utility.array_to_string(thread.ppi.Process.ImageFileName) + process_pid = thread.ppi.Process.UniqueProcessId + except exceptions.InvalidAddressException: + continue - def get_threads( - self, - ) -> Iterator[Tuple[interfaces.objects.ObjectInterface, str, int]]: - """ - Returns the threads of each desktop along with owning process information - """ - symbol_table_name = self.vol.type_name.split(constants.BANG)[0] + yield thread, process_name, process_pid - for thread in self.PtiList.to_list( - symbol_table_name + constants.BANG + "tagTHREADINFO", "PtiLink" - ): - try: - process_name = utility.array_to_string(thread.ppi.Process.ImageFileName) - process_pid = thread.ppi.Process.UniqueProcessId - except exceptions.InvalidAddressException: - continue + def _do_get_windows( + self, window, max_windows + ) -> Generator[Tuple[interfaces.objects.ObjectInterface, str], None, None]: + """ + Recusively walks and yields the adjacent and child windows + """ + seen_windows = set() + seen_children = set() - yield thread, process_name, process_pid - - def _do_get_windows( - self, window, max_windows - ) -> Generator[Tuple[interfaces.objects.ObjectInterface, str], None, None]: - """ - Recusively walks and yields the adjacent and child windows - """ - seen_windows = set() - seen_children = set() - - if window.vol.offset == 0: - return - - yield window, window.get_name() - - seen_windows.add(window) - - # Walk adjacent windows - while len(seen_windows) < max_windows: - try: - window = window.spwndNext.dereference() - except exceptions.InvalidAddressException: - break - - if window.vol.offset == 0: - break - - if window.vol.offset in seen_windows: - break + if not window.vol.offset: + return yield window, window.get_name() seen_windows.add(window) - # Walk children windows and recursively yield them - for window in seen_windows: - child = window - - while len(seen_windows) + len(seen_children) < max_windows: + # Walk adjacent windows + while len(seen_windows) < max_windows: try: - child = child.spwndChild + window = window.spwndNext.dereference() except exceptions.InvalidAddressException: break - if child.vol.offset == 0: + if not window.vol.offset: break - if child in seen_children: + if window.vol.offset in seen_windows: break - seen_children.add(child) - yield from self._do_get_windows(child, max_windows) + yield window, window.get_name() - def windows( - self, window, max_windows=10000 - ) -> Generator[Tuple[interfaces.objects.ObjectInterface, str], None, None]: - """ - Enumerates all windows adjacent to and children of `window` + seen_windows.add(window) - Args: - window: The window to enumerate windows from + # Walk children windows and recursively yield them + for window in seen_windows: + child = window - Returns: - A generator of tuples containing the window and its name - """ - seen_windows = set() + while len(seen_windows) + len(seen_children) < max_windows: + try: + child = child.spwndChild + except exceptions.InvalidAddressException: + break - for window, window_name in self._do_get_windows(window, max_windows): - if window.vol.offset in seen_windows: - continue + if not child.vol.offset: + break - seen_windows.add(window.vol.offset) + if child in seen_children: + break + seen_children.add(child) - yield window, window_name + yield from self._do_get_windows(child, max_windows) - if len(seen_windows) == max_windows: - break + def windows( + self, window, max_windows=10000 + ) -> Generator[Tuple[interfaces.objects.ObjectInterface, str], None, None]: + """ + Enumerates all windows adjacent to and children of `window` + + Args: + window: The window to enumerate windows from + + Returns: + A generator of tuples containing the window and its name + """ + seen_windows = set() + + for window, window_name in self._do_get_windows(window, max_windows): + if window.vol.offset in seen_windows: + continue + + seen_windows.add(window.vol.offset) + + yield window, window_name + + if len(seen_windows) == max_windows: + break -class tagWND(objects.StructType, pool.ExecutiveObject): + class tagWND(objects.StructType, pool.ExecutiveObject): - def is_valid(self) -> bool: - """ - Enforce a valid sid - """ - sid = self.get_session_id() + def is_valid(self) -> bool: + """ + Enforce a valid sid + """ + sid = self.get_session_id() - return sid is not None and 0 <= sid < 256 + return sid is not None and 0 <= sid < 256 + + def get_name(self) -> Optional[str]: + """ + directName appeared in later Windows 10 versions and is pointer + strName is a unicode string directly in the structure + """ + if self.has_member("directName"): + try: + return utility.pointer_to_string( + self.directName, count=256, encoding="utf16" + ) + except exceptions.InvalidAddressException: + vollog.debug( + f"directname for window at {self.vol.offset:#x} in layer {self.vol.layer_name} is invalid" + ) - def get_name(self) -> Optional[str]: - """ - directName appeared in later Windows 10 versions and is pointer - strName is a unicode string directly in the structure - """ - if self.has_member("directName"): try: - return utility.pointer_to_string( - self.directName, count=256, encoding="utf16" - ) + return self.strName.get_string() except exceptions.InvalidAddressException: vollog.debug( - f"directname for window at {self.vol.offset:#x} in layer {self.vol.layer_name} is invalid" + f"strName for window at {self.vol.offset:#x} in layer {self.vol.layer_name} is invalid" ) - try: - return self.strName.get_string() - except exceptions.InvalidAddressException: - vollog.debug( - f"strName for window at {self.vol.offset:#x} in layer {self.vol.layer_name} is invalid" - ) - - return None - - def get_session_id(self) -> Optional[int]: - """ - Uses its tagDESKTOP pointer to find its session - """ - desktop = self.get_desktop() - if desktop: - return desktop.get_session_id() - - return None - - def get_desktop(self) -> Optional[tagDESKTOP]: - """ - Attempts to return the host desktop (tagDESKTOP) for this window - """ - try: - return self.head.rpdesk.dereference() - except exceptions.InvalidAddressException: - vollog.debug( - f"Reading the desktop pointer for window {self.vol.offset:#x} caused a page fault" - ) return None - def get_process(self) -> Optional["extensions.EPROCESS"]: - """ - Attempts to return the host process (_EPROCESS) for this window - """ - try: - return self.head.pti.ppi.Process.dereference() - except exceptions.InvalidAddressException: - vollog.debug( - f"Reading the process pointer for window {self.vol.offset:#x} caused a page fault" + def get_session_id(self) -> Optional[int]: + """ + Uses its tagDESKTOP pointer to find its session + """ + desktop = self.get_desktop() + if desktop: + return desktop.get_session_id() + + return None + + def get_desktop(self) -> Optional["GUIExtensions.tagDESKTOP"]: + """ + Attempts to return the host desktop (tagDESKTOP) for this window + """ + try: + return self.head.rpdesk.dereference() + except exceptions.InvalidAddressException: + vollog.debug( + f"Reading the desktop pointer for window {self.vol.offset:#x} caused a page fault" + ) + return None + + def get_process(self) -> Optional["extensions.EPROCESS"]: + """ + Attempts to return the host process (_EPROCESS) for this window + """ + try: + return self.head.pti.ppi.Process.dereference() + except exceptions.InvalidAddressException: + vollog.debug( + f"Reading the process pointer for window {self.vol.offset:#x} caused a page fault" + ) + return None + + def get_window_procedure(self): + """ + Attempts to return the window procedure for this windows + """ + try: + # >= 17134 + if hasattr(self, "subPointer"): + return self.subPointer.lpfnWndProc + else: + return self.lpfnWndProc + except exceptions.InvalidAddressException: + vollog.debug(f"Invalid window procedure for window {self.vol.offset:#x}") + return None + + + # This is copy/paste from UNICODE_STRING in `symbols/windows/extensions/__init__.py` + # The versioning of modules would get very ugly if we let different modules share implementations + # across different data structures + class LARGE_UNICODE_STRING(objects.StructType): + """A class for Windows unicode string structures.""" + + def get_string(self) -> interfaces.objects.ObjectInterface: + # We explicitly do *not* catch errors here, we allow an exception to be thrown + # (otherwise there's no way to determine anything went wrong) + # It's up to the user of this method to catch exceptions + + # We manually construct an object rather than casting a dereferenced pointer in case + # the buffer length is 0 and the pointer is a NULL pointer + return self._context.object( + self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "string", + layer_name=self.Buffer.vol.native_layer_name, + offset=self.Buffer, + max_length=self.Length, + errors="replace", + encoding="utf16", ) - return None - - def get_window_procedure(self): - """ - Attempts to return the window procedure for this windows - """ - try: - # >= 17134 - if hasattr(self, "subPointer"): - return self.subPointer.lpfnWndProc - else: - return self.lpfnWndProc - except exceptions.InvalidAddressException: - vollog.debug(f"Invalid window procedure for window {self.vol.offset:#x}") - return None - - -# This is copy/paste from UNICODE_STRING in `symbols/windows/extensions/__init__.py` -# The versioning of modules would get very ugly if we let different modules share implementations -# across different data structures -class LARGE_UNICODE_STRING(objects.StructType): - """A class for Windows unicode string structures.""" - - def get_string(self) -> interfaces.objects.ObjectInterface: - # We explicitly do *not* catch errors here, we allow an exception to be thrown - # (otherwise there's no way to determine anything went wrong) - # It's up to the user of this method to catch exceptions - - # We manually construct an object rather than casting a dereferenced pointer in case - # the buffer length is 0 and the pointer is a NULL pointer - return self._context.object( - self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "string", - layer_name=self.Buffer.vol.native_layer_name, - offset=self.Buffer, - max_length=self.Length, - errors="replace", - encoding="utf16", - ) class_types = { - "tagWINDOWSTATION": tagWINDOWSTATION, - "tagDESKTOP": tagDESKTOP, - "tagWND": tagWND, - "_LARGE_UNICODE_STRING": LARGE_UNICODE_STRING, + "tagWINDOWSTATION": GUIExtensions.tagWINDOWSTATION, + "tagDESKTOP": GUIExtensions.tagDESKTOP, + "tagWND": GUIExtensions.tagWND, + "_LARGE_UNICODE_STRING": GUIExtensions.LARGE_UNICODE_STRING, } From 7e5a34ae49df25acc76da4bb93336fc7550e1c21 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sun, 16 Mar 2025 17:17:46 +0000 Subject: [PATCH 33/78] Black and Ruff fixes --- .../framework/symbols/windows/extensions/gui.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/volatility3/framework/symbols/windows/extensions/gui.py b/volatility3/framework/symbols/windows/extensions/gui.py index 48ec2eba4..f811f4313 100644 --- a/volatility3/framework/symbols/windows/extensions/gui.py +++ b/volatility3/framework/symbols/windows/extensions/gui.py @@ -14,6 +14,7 @@ from volatility3.framework.symbols.windows.extensions import pool vollog = logging.getLogger(__name__) + class GUIExtensions(interfaces.configuration.VersionableInterface): _version = (1, 0, 0) _required_framework_version = (2, 0, 0) @@ -83,14 +84,13 @@ class GUIExtensions(interfaces.configuration.VersionableInterface): seen.add(desktop.vol.offset) - class tagDESKTOP(objects.StructType, pool.ExecutiveObject): def is_valid(self) -> bool: """ Enforce a valid session ID and Window station We aren't interested in terminated desktops as there are so many pointers going from station -> desktop -> windows, that we would just be processing junk. - Even if the pointers were still in tact by some miracle, its not that helpful to + Even if the pointers were still in tact by some miracle, its not that helpful to have a floating desktop appear in the output as you can't do much with it. """ sid = self.get_session_id() @@ -102,7 +102,7 @@ class GUIExtensions(interfaces.configuration.VersionableInterface): return False - def get_window_station(self) -> Optional["tagWINDOWSTATION"]: + def get_window_station(self) -> Optional["GUIExtensions.tagWINDOWSTATION"]: """ Attempts to return the window station for this desktop """ @@ -133,7 +133,9 @@ class GUIExtensions(interfaces.configuration.VersionableInterface): symbol_table_name + constants.BANG + "tagTHREADINFO", "PtiLink" ): try: - process_name = utility.array_to_string(thread.ppi.Process.ImageFileName) + process_name = utility.array_to_string( + thread.ppi.Process.ImageFileName + ) process_pid = thread.ppi.Process.UniqueProcessId except exceptions.InvalidAddressException: continue @@ -217,7 +219,6 @@ class GUIExtensions(interfaces.configuration.VersionableInterface): if len(seen_windows) == max_windows: break - class tagWND(objects.StructType, pool.ExecutiveObject): def is_valid(self) -> bool: @@ -297,10 +298,11 @@ class GUIExtensions(interfaces.configuration.VersionableInterface): else: return self.lpfnWndProc except exceptions.InvalidAddressException: - vollog.debug(f"Invalid window procedure for window {self.vol.offset:#x}") + vollog.debug( + f"Invalid window procedure for window {self.vol.offset:#x}" + ) return None - # This is copy/paste from UNICODE_STRING in `symbols/windows/extensions/__init__.py` # The versioning of modules would get very ugly if we let different modules share implementations # across different data structures From 4e1ddadb4080b6269521ab40e73c160706a28880 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sun, 16 Mar 2025 17:53:55 +0000 Subject: [PATCH 34/78] Fix version requirements --- volatility3/framework/plugins/windows/deskscan.py | 4 ++++ volatility3/framework/plugins/windows/desktops.py | 4 ++++ volatility3/framework/plugins/windows/windows.py | 8 ++++++-- volatility3/framework/plugins/windows/windowstations.py | 5 ++++- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/windows/deskscan.py b/volatility3/framework/plugins/windows/deskscan.py index 6a8ff9e65..2db9c8234 100644 --- a/volatility3/framework/plugins/windows/deskscan.py +++ b/volatility3/framework/plugins/windows/deskscan.py @@ -8,6 +8,7 @@ from volatility3.framework import interfaces from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints from volatility3.plugins.windows import desktops, windowstations +from volatility3.framework.symbols.windows.extensions import gui vollog = logging.getLogger(__name__) @@ -39,6 +40,9 @@ class DeskScan(desktops.Desktops): plugin=windowstations.WindowStations, version=(1, 0, 0), ), + requirements.VersionRequirement( + name="GUIExtensions", component=gui.GUIExtensions, version=(1, 0, 0) + ), ] @classmethod diff --git a/volatility3/framework/plugins/windows/desktops.py b/volatility3/framework/plugins/windows/desktops.py index 1085ff36d..dd1d238d6 100644 --- a/volatility3/framework/plugins/windows/desktops.py +++ b/volatility3/framework/plugins/windows/desktops.py @@ -8,6 +8,7 @@ from volatility3.framework import interfaces, renderers from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints from volatility3.plugins.windows import windowstations +from volatility3.framework.symbols.windows.extensions import gui vollog = logging.getLogger(__name__) @@ -36,6 +37,9 @@ class Desktops(interfaces.plugins.PluginInterface): plugin=windowstations.WindowStations, version=(1, 0, 0), ), + requirements.VersionRequirement( + name="GUIExtensions", component=gui.GUIExtensions, version=(1, 0, 0) + ), ] @classmethod diff --git a/volatility3/framework/plugins/windows/windows.py b/volatility3/framework/plugins/windows/windows.py index a7c350ea9..ade1df9dc 100644 --- a/volatility3/framework/plugins/windows/windows.py +++ b/volatility3/framework/plugins/windows/windows.py @@ -9,6 +9,7 @@ from volatility3.framework.objects import utility from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints from volatility3.plugins.windows import windowstations +from volatility3.framework.symbols.windows.extensions import gui vollog = logging.getLogger(__name__) @@ -33,6 +34,9 @@ class Windows(interfaces.plugins.PluginInterface): component=windowstations.WindowStations, version=(1, 0, 0), ), + requirements.VersionRequirement( + name="GUIExtensions", component=gui.GUIExtensions, version=(1, 0, 0) + ), ] @classmethod @@ -88,7 +92,7 @@ class Windows(interfaces.plugins.PluginInterface): ) if process_name is None: - vollog.warning( + vollog.debug( f"Invalid process reference for the process hosting window {window.vol.offset:#x}" ) continue @@ -107,7 +111,7 @@ class Windows(interfaces.plugins.PluginInterface): elif window_proc == 0 or window_proc > 0x1000: window_proc = format_hints.Hex(window_proc) else: - vollog.warning( + vollog.debug( f"Invalid window procedure {window_proc} for the window {window.vol.offset:#x}" ) continue diff --git a/volatility3/framework/plugins/windows/windowstations.py b/volatility3/framework/plugins/windows/windowstations.py index cd02938cd..ad1709bfa 100644 --- a/volatility3/framework/plugins/windows/windowstations.py +++ b/volatility3/framework/plugins/windows/windowstations.py @@ -10,8 +10,8 @@ from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import intermed from volatility3.framework.symbols.windows import versions -from volatility3.framework.symbols.windows.extensions import gui from volatility3.plugins.windows import poolscanner, modules +from volatility3.framework.symbols.windows.extensions import gui vollog = logging.getLogger(__name__) @@ -52,6 +52,9 @@ class WindowStations(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="modules", component=modules.Modules, version=(3, 0, 0) ), + requirements.VersionRequirement( + name="GUIExtensions", component=gui.GUIExtensions, version=(1, 0, 0) + ), ] @staticmethod From dae430ff5872784d7a750f60cf3eb36bc77ef043 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sun, 16 Mar 2025 22:19:41 +0000 Subject: [PATCH 35/78] Address feedback --- volatility3/framework/plugins/windows/deskscan.py | 4 ---- volatility3/framework/plugins/windows/desktops.py | 4 ---- volatility3/framework/plugins/windows/windows.py | 4 ---- .../framework/plugins/windows/windowstations.py | 2 +- .../framework/symbols/windows/extensions/gui.py | 13 ++++++------- 5 files changed, 7 insertions(+), 20 deletions(-) diff --git a/volatility3/framework/plugins/windows/deskscan.py b/volatility3/framework/plugins/windows/deskscan.py index 2db9c8234..6a8ff9e65 100644 --- a/volatility3/framework/plugins/windows/deskscan.py +++ b/volatility3/framework/plugins/windows/deskscan.py @@ -8,7 +8,6 @@ from volatility3.framework import interfaces from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints from volatility3.plugins.windows import desktops, windowstations -from volatility3.framework.symbols.windows.extensions import gui vollog = logging.getLogger(__name__) @@ -40,9 +39,6 @@ class DeskScan(desktops.Desktops): plugin=windowstations.WindowStations, version=(1, 0, 0), ), - requirements.VersionRequirement( - name="GUIExtensions", component=gui.GUIExtensions, version=(1, 0, 0) - ), ] @classmethod diff --git a/volatility3/framework/plugins/windows/desktops.py b/volatility3/framework/plugins/windows/desktops.py index dd1d238d6..1085ff36d 100644 --- a/volatility3/framework/plugins/windows/desktops.py +++ b/volatility3/framework/plugins/windows/desktops.py @@ -8,7 +8,6 @@ from volatility3.framework import interfaces, renderers from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints from volatility3.plugins.windows import windowstations -from volatility3.framework.symbols.windows.extensions import gui vollog = logging.getLogger(__name__) @@ -37,9 +36,6 @@ class Desktops(interfaces.plugins.PluginInterface): plugin=windowstations.WindowStations, version=(1, 0, 0), ), - requirements.VersionRequirement( - name="GUIExtensions", component=gui.GUIExtensions, version=(1, 0, 0) - ), ] @classmethod diff --git a/volatility3/framework/plugins/windows/windows.py b/volatility3/framework/plugins/windows/windows.py index ade1df9dc..9d4317df9 100644 --- a/volatility3/framework/plugins/windows/windows.py +++ b/volatility3/framework/plugins/windows/windows.py @@ -9,7 +9,6 @@ from volatility3.framework.objects import utility from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints from volatility3.plugins.windows import windowstations -from volatility3.framework.symbols.windows.extensions import gui vollog = logging.getLogger(__name__) @@ -34,9 +33,6 @@ class Windows(interfaces.plugins.PluginInterface): component=windowstations.WindowStations, version=(1, 0, 0), ), - requirements.VersionRequirement( - name="GUIExtensions", component=gui.GUIExtensions, version=(1, 0, 0) - ), ] @classmethod diff --git a/volatility3/framework/plugins/windows/windowstations.py b/volatility3/framework/plugins/windows/windowstations.py index ad1709bfa..1f95b0531 100644 --- a/volatility3/framework/plugins/windows/windowstations.py +++ b/volatility3/framework/plugins/windows/windowstations.py @@ -99,7 +99,7 @@ class WindowStations(interfaces.plugins.PluginInterface): config_path=config_path, sub_path=os.path.join("windows", "gui"), filename=symbol_filename, - class_types=gui.class_types, + class_types=gui.GUIExtensions.class_types, table_mapping=table_mapping, ) diff --git a/volatility3/framework/symbols/windows/extensions/gui.py b/volatility3/framework/symbols/windows/extensions/gui.py index f811f4313..d1835631f 100644 --- a/volatility3/framework/symbols/windows/extensions/gui.py +++ b/volatility3/framework/symbols/windows/extensions/gui.py @@ -325,10 +325,9 @@ class GUIExtensions(interfaces.configuration.VersionableInterface): encoding="utf16", ) - -class_types = { - "tagWINDOWSTATION": GUIExtensions.tagWINDOWSTATION, - "tagDESKTOP": GUIExtensions.tagDESKTOP, - "tagWND": GUIExtensions.tagWND, - "_LARGE_UNICODE_STRING": GUIExtensions.LARGE_UNICODE_STRING, -} + class_types = { + "tagWINDOWSTATION": tagWINDOWSTATION, + "tagDESKTOP": tagDESKTOP, + "tagWND": tagWND, + "_LARGE_UNICODE_STRING": LARGE_UNICODE_STRING, + } From 04cd65ef2b439914277d3ca47d4b3d4975b4fced Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 17 Mar 2025 15:23:44 +0000 Subject: [PATCH 36/78] Update netfilter to current rookit detection API and update displayed columns to current standards --- .../framework/plugins/linux/netfilter.py | 83 ++++++++++--------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index e8a33be61..9079adef9 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -13,12 +13,11 @@ from volatility3.framework import ( interfaces, renderers, exceptions, + deprecation, ) from volatility3.framework.renderers import format_hints from volatility3.framework.configuration import requirements -from volatility3.framework.symbols import linux from volatility3.framework.symbols.linux import network -from volatility3.plugins.linux import lsmod vollog = logging.getLogger(__name__) @@ -82,22 +81,18 @@ class AbstractNetfilter(ABC): self.ptr_size = self.vmlinux.get_type("pointer").size self.list_head_size = self.vmlinux.get_type("list_head").size - lsmod_required_version = Netfilter._required_lsmod_version - lsmod_current_version = lsmod.Lsmod.version + linuxutils_modulegatherers_required_version = ( + Netfilter._required_linuxutils_gatherers_version + ) + linuxutils_modulegatherers_current_version = ( + linux_utilities_modules.ModuleGatherers.version + ) if not requirements.VersionRequirement.matches_required( - lsmod_required_version, lsmod_current_version + linuxutils_modulegatherers_required_version, + linuxutils_modulegatherers_current_version, ): raise exceptions.PluginRequirementException( - f"linux.lsmod.Lsmod version not suitable: required {lsmod_required_version} found {lsmod_current_version}" - ) - - linuxutils_required_version = Netfilter._required_linuxutils_version - linuxutils_current_version = linux.LinuxUtilities.version - if not requirements.VersionRequirement.matches_required( - linuxutils_required_version, linuxutils_current_version - ): - raise exceptions.PluginRequirementException( - f"linux.LinuxUtilities version not suitable: required {linuxutils_required_version} found {linuxutils_current_version}" + f"linux_utilities_modules.ModuleGatherer version not suitable: required {linuxutils_modulegatherers_required_version} found {linuxutils_modulegatherers_current_version}" ) linux_net_required_version = Netfilter._required_linuxnet_version @@ -123,12 +118,13 @@ class AbstractNetfilter(ABC): f"linux_utilities_modules.Modules version not suitable: required {linux_utilities_modules_required_version} found {linux_utilities_modules_current_version}" ) - symbol_table = self._context.symbol_space[self.vmlinux.symbol_table_name] + symbol_table = context.symbol_space[self.vmlinux.symbol_table_name] network.NetSymbols.apply(symbol_table) - modules = lsmod.Lsmod.list_modules(context, kernel_module_name) - self.handlers = linux.LinuxUtilities.generate_kernel_handler_info( - context, kernel_module_name, modules + self.handlers = linux_utilities_modules.Modules.run_modules_scanners( + context=context, + kernel_module_name=kernel_module_name, + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, ) @classmethod @@ -217,10 +213,17 @@ class AbstractNetfilter(ABC): priority = int(hook_ops.priority) hook_ops_hook = hook_ops.hook - module_name = self.get_module_name_for_address(hook_ops_hook) - hooked = module_name is None + module_info, symbol_name = ( + linux_utilities_modules.Modules.module_lookup_by_address( + self._context, + self.vmlinux.name, + self.handlers, + hook_ops_hook, + ) + ) + hooked = module_info is None - yield netns, proto_name, hook_name, priority, hook_ops_hook, module_name, hooked + yield netns, proto_name, hook_name, priority, hook_ops_hook, module_info, symbol_name, hooked @classmethod @abstractmethod @@ -300,6 +303,10 @@ class AbstractNetfilter(ABC): # in other parts of the kernel source code. return ("IPV4", "ARP", "BRIDGE", "IPV6", "DECNET") + @deprecation.method_being_removed( + removal_date="2025-09-25", + message="Callers to this method should adapt `linux_utilities_modules.Modules.run_module_scanners`", + ) def get_module_name_for_address(self, addr) -> str: """Helper to obtain the module and symbol name in the format needed for the output of this plugin. @@ -724,11 +731,10 @@ class Netfilter(interfaces.plugins.PluginInterface): _required_framework_version = (2, 22, 0) - _version = (1, 1, 1) + _version = (2, 0, 0) _required_linux_utilities_modules_version = (3, 0, 0) - _required_linuxutils_version = (2, 1, 0) - _required_lsmod_version = (2, 0, 0) + _required_linuxutils_gatherers_version = (1, 0, 0) _required_linuxnet_version = (1, 0, 0) @classmethod @@ -740,17 +746,9 @@ class Netfilter(interfaces.plugins.PluginInterface): architectures=["Intel32", "Intel64"], ), requirements.VersionRequirement( - name="linux_utilities_modules", - component=linux_utilities_modules.Modules, - version=cls._required_linux_utilities_modules_version, - ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=cls._required_lsmod_version - ), - requirements.VersionRequirement( - name="linuxutils", - component=linux.LinuxUtilities, - version=cls._required_linuxutils_version, + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=cls._required_linuxutils_gatherers_version, ), requirements.VersionRequirement( name="linuxnet", @@ -766,16 +764,24 @@ class Netfilter(interfaces.plugins.PluginInterface): hook_name, priority, hook_func, - module_name, + module_info, + symbol_name, hooked, ) = fields + + if module_info: + module_name = module_info.name + else: + module_name = renderers.NotAvailableValue() + return ( netns, proto_name, hook_name, priority, format_hints.Hex(hook_func), - module_name or renderers.NotAvailableValue(), + module_name, + symbol_name or renderers.NotAvailableValue(), str(hooked), ) @@ -794,6 +800,7 @@ class Netfilter(interfaces.plugins.PluginInterface): ("Priority", int), ("Handler", format_hints.Hex), ("Module", str), + ("Symbol", str), ("Is Hooked", str), ] return renderers.TreeGrid(headers, self._generator()) From 18abe9c50815b2e6186f0cf6201b2411b617272f Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 17 Mar 2025 15:25:43 +0000 Subject: [PATCH 37/78] Fix first set of ELF parsing unhandled smear protection --- .../framework/symbols/linux/extensions/__init__.py | 8 ++++++-- volatility3/framework/symbols/linux/extensions/elf.py | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 39605cf52..dd0f23d34 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -256,14 +256,18 @@ class module(generic.GenericIntelProcess): elf_sym_obj.cached_strtab = self.section_strtab yield elf_sym_obj - def get_symbols_names_and_addresses(self) -> Iterable[Tuple[str, int]]: + def get_symbols_names_and_addresses(self, max_symbols: int = 4096) -> Iterable[Tuple[str, int]]: """Get names and addresses for each symbol of the module Yields: A tuple for each symbol containing the symbol name and its corresponding value """ layer = self._context.layers[self.vol.layer_name] - for elf_sym_obj in self.get_symbols(): + for iteration_counter, elf_sym_obj in enumerate(self.get_symbols()): + if iteration_counter > max_symbols: + vollog.debug(f"Hit maximum symbols ({max_symbols}) for ELF at {self.vol.offset:#x} in layer {self.vol.layer_name}") + return + sym_name = elf_sym_obj.get_name() if not sym_name: continue diff --git a/volatility3/framework/symbols/linux/extensions/elf.py b/volatility3/framework/symbols/linux/extensions/elf.py index 7105a05ea..564439c64 100644 --- a/volatility3/framework/symbols/linux/extensions/elf.py +++ b/volatility3/framework/symbols/linux/extensions/elf.py @@ -329,7 +329,10 @@ class elf_sym(objects.StructType): def get_name(self) -> Optional[str]: """Returns the symbol name""" - addr = self._cached_strtab + self.st_name + try: + addr = self._cached_strtab + self.st_name + except exceptions.InvalidAddressException: + return None layer = self._context.layers[self.vol.layer_name] name_bytes = layer.read(addr, self._MAX_NAME_LENGTH, pad=True) From 4b403d50ef291920cb50aa7008ab6ce96c1280d9 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 17 Mar 2025 15:29:28 +0000 Subject: [PATCH 38/78] Fix first set of ELF parsing unhandled smear protection --- .../framework/symbols/linux/extensions/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index dd0f23d34..2094d63da 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -256,7 +256,9 @@ class module(generic.GenericIntelProcess): elf_sym_obj.cached_strtab = self.section_strtab yield elf_sym_obj - def get_symbols_names_and_addresses(self, max_symbols: int = 4096) -> Iterable[Tuple[str, int]]: + def get_symbols_names_and_addresses( + self, max_symbols: int = 4096 + ) -> Iterable[Tuple[str, int]]: """Get names and addresses for each symbol of the module Yields: @@ -265,7 +267,9 @@ class module(generic.GenericIntelProcess): layer = self._context.layers[self.vol.layer_name] for iteration_counter, elf_sym_obj in enumerate(self.get_symbols()): if iteration_counter > max_symbols: - vollog.debug(f"Hit maximum symbols ({max_symbols}) for ELF at {self.vol.offset:#x} in layer {self.vol.layer_name}") + vollog.debug( + f"Hit maximum symbols ({max_symbols}) for ELF at {self.vol.offset:#x} in layer {self.vol.layer_name}" + ) return sym_name = elf_sym_obj.get_name() From f697287784e5a2901e8c16eecd82db0c8641ed77 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 17 Mar 2025 19:09:05 +0000 Subject: [PATCH 39/78] Prevent backtrace on corrupt system call table entry --- volatility3/framework/plugins/linux/check_syscall.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/check_syscall.py b/volatility3/framework/plugins/linux/check_syscall.py index 9ffd4c497..724a67810 100644 --- a/volatility3/framework/plugins/linux/check_syscall.py +++ b/volatility3/framework/plugins/linux/check_syscall.py @@ -172,8 +172,11 @@ class Check_syscall(plugins.PluginInterface): count=tblsz, ) - for i, call_addr in enumerate(table): - if not call_addr: + for i in range(len(table)): + try: + call_addr = table[i] + except exceptions.InvalidAddressException: + vollog.debug(f"Failed to get system call table entry at index {i}") continue symbols = list(vmlinux.get_symbols_by_absolute_location(call_addr)) From ee35fa1ef9b90520931812b06ecb619ce8672366 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 17 Mar 2025 19:10:23 +0000 Subject: [PATCH 40/78] Prevent backtrace on smeared iomem entry --- volatility3/framework/plugins/linux/iomem.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/linux/iomem.py b/volatility3/framework/plugins/linux/iomem.py index 6732084db..5be6627fc 100644 --- a/volatility3/framework/plugins/linux/iomem.py +++ b/volatility3/framework/plugins/linux/iomem.py @@ -59,7 +59,7 @@ class IOMem(interfaces.plugins.PluginInterface): f"Unable to create resource object at {resource_offset:#x}. This resource, " "its sibling, and any of it's children and will be missing from the output." ) - return None + return # get name with protection against smear as following a pointer try: @@ -71,6 +71,15 @@ class IOMem(interfaces.plugins.PluginInterface): ) name = renderers.UnreadableValue() + try: + start = resource.start + end = resource.end + except exceptions.InvalidAddressException: + vollog.warning( + f"Unable to follow pointer to start and end for resource object at {resource_offset:#x}. Skipping entry." + ) + return + # mark this resource as seen in the seen set. Normally this should not be needed but will protect # against possible infinite loops. Warn the user if an infinite loop would have happened. if resource_offset in seen: @@ -79,12 +88,12 @@ class IOMem(interfaces.plugins.PluginInterface): "this should not normally occur. No further results from related resources will be " "displayed to protect against infinite loops." ) - return None + return else: seen.add(resource_offset) # yield information on this resource - yield depth, (name, resource.start, resource.end) + yield depth, (name, start, end) # process child resource if this exists if resource.child != 0: From b8a427c13063d55fdc52688c8fe6b2e1f09d84a1 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 17 Mar 2025 17:08:27 -0500 Subject: [PATCH 41/78] Fix bugs in kallsyms and the related pscallstack found in testing and switch calls to deprecated functions --- .../framework/plugins/linux/kallsyms.py | 2 + .../framework/plugins/linux/pscallstack.py | 10 +- .../symbols/linux/extensions/__init__.py | 17 +- .../framework/symbols/linux/kallsyms.py | 164 ++++++++++++------ 4 files changed, 137 insertions(+), 56 deletions(-) diff --git a/volatility3/framework/plugins/linux/kallsyms.py b/volatility3/framework/plugins/linux/kallsyms.py index 7dd4f06e6..47861d91a 100644 --- a/volatility3/framework/plugins/linux/kallsyms.py +++ b/volatility3/framework/plugins/linux/kallsyms.py @@ -106,6 +106,8 @@ class Kallsyms(plugins.PluginInterface): for symbols_generator in symbol_generators: for kassymbol in symbols_generator: + if not kassymbol: + continue # 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 diff --git a/volatility3/framework/plugins/linux/pscallstack.py b/volatility3/framework/plugins/linux/pscallstack.py index 8931ca581..6d7a24942 100644 --- a/volatility3/framework/plugins/linux/pscallstack.py +++ b/volatility3/framework/plugins/linux/pscallstack.py @@ -118,9 +118,15 @@ class PsCallStack(plugins.PluginInterface): current_sp = rsp_start idx = 0 while current_sp < task_top_of_stack: - stack_value_bytes = task_layer.read(current_sp, pointer_size) + try: + stack_value_bytes = task_layer.read(current_sp, pointer_size) + except exceptions.InvalidAddressException: + break stack_value = int.from_bytes(stack_value_bytes, byteorder=byte_order) - + if not stack_value: + idx += 1 + current_sp += pointer_size + continue kassymbol = kas.lookup_address(stack_value) sp_address = current_sp & vmlinux_layer.address_mask stack_value &= vmlinux_layer.address_mask diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 2094d63da..a4e04d950 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1994,7 +1994,10 @@ class bpf_prog(objects.StructType): # 'prog_aux' was added in kernels 3.18 return None - return self.aux.get_name() + try: + return self.aux.get_name() + except exceptions.InvalidAddressException: + return None def bpf_jit_binary_hdr_address(self) -> int: """Return the jitted BPF program start address @@ -2056,11 +2059,13 @@ class bpf_prog_aux(objects.StructType): # 'name' was added in kernels 4.15 return None - if not self.name: + try: + if not self.name: + return None + return utility.array_to_string(self.name) + except exceptions.InvalidAddressException: return None - return utility.array_to_string(self.name) - class cred(objects.StructType): # struct cred was added in kernels 2.6.29 @@ -2996,7 +3001,9 @@ class latch_tree_root(objects.StructType): 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: + if c is None: + return None + elif c < 0: rb_node_ptr = rb_node.rb_left elif c > 0: rb_node_ptr = rb_node.rb_right diff --git a/volatility3/framework/symbols/linux/kallsyms.py b/volatility3/framework/symbols/linux/kallsyms.py index 298725a7a..368169757 100644 --- a/volatility3/framework/symbols/linux/kallsyms.py +++ b/volatility3/framework/symbols/linux/kallsyms.py @@ -6,12 +6,12 @@ import functools import logging from typing import Iterator, List, Optional, Tuple +import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules 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__) @@ -304,28 +304,35 @@ class Kallsyms(interfaces.configuration.VersionableInterface): @classmethod def _assert_versions(cls) -> None: """Verify versions of shared dependencies""" - lsmod_version_required = (2, 0, 0) + linux_utilities_modules_version_required = (3, 0, 0) if not requirements.VersionRequirement.matches_required( - lsmod_version_required, lsmod.Lsmod.version + linux_utilities_modules_version_required, + linux_utilities_modules.Modules.version, ): raise exceptions.VolatilityException( - "Lsmod version not suitable: " - f"required {lsmod_version_required} found {lsmod.Lsmod.version}", + "linux_utilities_modules.Modules version not suitable: " + f"required {linux_utilities_modules_version_required} found {linux_utilities_modules.Modules.version}", ) return None - def _read_bytes(self, address: int, size: int) -> bytes: + def _read_bytes(self, address: int, size: int) -> Optional[bytes]: layer = self._context.layers[self._layer_name] - return layer.read(address, size).decode() + try: + return layer.read(address, size).decode() + except exceptions.InvalidAddressException: + return None - def _read_int(self, address: int, size: int, signed: bool = False) -> int: + def _read_int(self, address: int, size: int, signed: bool = False) -> Optional[int]: layer = self._context.layers[self._layer_name] - return int.from_bytes( - layer.read(address, size), - byteorder=self._endian, - signed=signed, - ) + try: + return int.from_bytes( + layer.read(address, size), + byteorder=self._endian, + signed=signed, + ) + except exceptions.InvalidAddressException: + return None def _bootstrap(self) -> None: layer = self._context.layers[self._layer_name] @@ -402,7 +409,20 @@ class Kallsyms(interfaces.configuration.VersionableInterface): """ current_offset = 0 for sym_idx in range(self._kallsyms_num_syms): - kassymbol, compressed_length = self._get_symbol(current_offset, sym_idx) + try: + kassymbol, compressed_length = self._get_symbol(current_offset, sym_idx) + except exceptions.InvalidAddressException: + vollog.debug( + f"Unable to reconstruct core symbol at offset {current_offset:#x} and index {sym_idx}" + ) + continue + + if compressed_length is None: + vollog.debug( + f"Unable to reconstruct compressed_length at offset {current_offset:#x} and index {sym_idx}" + ) + break + if kassymbol: yield kassymbol @@ -485,7 +505,7 @@ class Kallsyms(interfaces.configuration.VersionableInterface): ) return kassymbolbasic, compressed_length - def _get_symbol_address_by_index(self, index: int) -> int: + def _get_symbol_address_by_index(self, index: int) -> Optional[int]: """Return symbol address based on the symbol index in the kallsyms arrays. Based on kallsyms_sym_address() @@ -502,6 +522,8 @@ class Kallsyms(interfaces.configuration.VersionableInterface): 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 is None: + return None if sym_addr < 0: # Negative offsets are relative to kallsyms_relative_base - 1 @@ -517,35 +539,56 @@ class Kallsyms(interfaces.configuration.VersionableInterface): self._long_size, signed=False, ) + if kallsyms_address is None: + return None + return kallsyms_address & layer.address_mask else: raise exceptions.VolatilityException("Unsupported kernel") @functools.lru_cache - def _get_symbol_pos(self, address: int) -> Tuple[int, int]: + def _get_symbol_pos(self, address: int) -> Optional[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: + symbol_index = self._get_symbol_address_by_index(mid) + if symbol_index is None: + return None, None + elif symbol_index <= address: low = mid else: high = mid + # prevent accidental bleed through + symbol_index = None + # 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 + while low: + symbol_index = self._get_symbol_address_by_index(low - 1) + if symbol_index is None: + return None, None + + if symbol_index == self._get_symbol_address_by_index(low): + low -= 1 + else: + break symbol_start = self._get_symbol_address_by_index(low) + if symbol_start is None: + return None, None + 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_index = self._get_symbol_address_by_index(idx) + if symbol_index is None: + return None, None + + if symbol_index > symbol_start: symbol_end = self._get_symbol_address_by_index(idx) break @@ -664,6 +707,8 @@ class Kallsyms(interfaces.configuration.VersionableInterface): return None pos, sym_size = self._get_symbol_pos(address) + if pos is None: + return None offset = self._get_symbol_offset(pos) sym_address = self._get_symbol_address_by_index(pos) kassymbolbasic, _compressed_length = self._expand_symbol(offset) @@ -855,7 +900,9 @@ class Kallsyms(interfaces.configuration.VersionableInterface): self, ) -> List[Tuple[interfaces.objects.ObjectInterface, int, int]]: modules_region = [] - for module in lsmod.Lsmod.list_modules(self._context, self._module_name): + for module in linux_utilities_modules.Modules.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) @@ -923,21 +970,35 @@ class Kallsyms(interfaces.configuration.VersionableInterface): return self._search_module_by_address(address) @functools.lru_cache - def _get_type_cache(self, name: str): + def _get_type_cache(self, name: str) -> Optional[interfaces.objects.Template]: vmlinux = self._context.modules[self._module_name] - return vmlinux.get_type(name) + try: + return vmlinux.get_type(name) + except exceptions.SymbolError: + return None def _mod_tree_comp( self, address: int, latch_tree_node: interfaces.objects.ObjectInterface - ) -> int: + ) -> Optional[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_mtn = self._get_type_cache("module_memory") + if not module_memory_mtn: + vollog.debug( + "`module_memory` symbol not present in the symbol table. Cannot proceed." + ) + return None + + module_memory_mtn_offset = module_memory_mtn.relative_child_offset("mtn") + + mod_tree_node_mod = self._get_type_cache("mod_tree_node") + if not mod_tree_node_mod: + vollog.debug( + "`mod_tree_node` symbol not present in the symbol table. Cannot proceed." + ) + return None + + mod_tree_node_mod_offset = mod_tree_node_mod.relative_child_offset("mod") module_memory_offset = ( latch_tree_node.vol.offset @@ -1087,7 +1148,9 @@ class Kallsyms(interfaces.configuration.VersionableInterface): KASSymbol objects """ layer = self._context.layers[self._layer_name] - for module in lsmod.Lsmod.list_modules(self._context, self._module_name): + for module in linux_utilities_modules.Modules.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() @@ -1254,21 +1317,24 @@ class Kallsyms(interfaces.configuration.VersionableInterface): # 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 + try: + # 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 + except exceptions.InvalidAddressException: + continue # The following are also hardcoded in the Linux kernel # see kernel's get_ksymbol_bpf(), bpf_get_kallsym() and BPF_SYM_ELF_TYPE @@ -1322,7 +1388,7 @@ class Kallsyms(interfaces.configuration.VersionableInterface): sym_size = symbol_end - symbol_start elif vmlinux.has_type("latch_tree_root") and vmlinux.get_type( "bpf_prog_aux" - ).child_template("ksym_tnode"): + ).has_member("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 From 1eacddc79c2975fe1e53167f86bec365c68636b9 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 17 Mar 2025 17:45:13 -0500 Subject: [PATCH 42/78] Add needed checks to prevent backtraces in ELF parsing --- .../symbols/linux/extensions/__init__.py | 72 ++++++++++++------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 2094d63da..524f2a7c6 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -337,39 +337,55 @@ class module(generic.GenericIntelProcess): @property def section_symtab(self): - if self.has_member("kallsyms"): - return self.kallsyms.symtab - elif self.has_member("symtab"): - return self.symtab + try: + if self.has_member("kallsyms"): + return self.kallsyms.symtab + elif self.has_member("symtab"): + return self.symtab + except exceptions.InvalidAddressException: + vollog.debug(f"Page fault encountered when accessing symtab of ELF at {self.vol.offset:#x} in {self.vol.layer_name}") + return None raise AttributeError("Unable to get symtab") @property def num_symtab(self): - if self.has_member("kallsyms"): - return int(self.kallsyms.num_symtab) - elif self.has_member("num_symtab"): - return int(self.member("num_symtab")) + try: + if self.has_member("kallsyms"): + return int(self.kallsyms.num_symtab) + elif self.has_member("num_symtab"): + return int(self.member("num_symtab")) + except exceptions.InvalidAddressException: + vollog.debug(f"Page fault encountered when accessing num_symtab of ELF at {self.vol.offset:#x} in {self.vol.layer_name}") + return None raise AttributeError("Unable to determine number of symbols") @property def section_strtab(self): - # Newer kernels - if self.has_member("kallsyms"): - return self.kallsyms.strtab - # Older kernels - elif self.has_member("strtab"): - return self.strtab + try: + # Newer kernels + if self.has_member("kallsyms"): + return self.kallsyms.strtab + # Older kernels + elif self.has_member("strtab"): + return self.strtab + except exceptions.InvalidAddressException: + vollog.debug(f"Page fault encountered when accessing strtab of ELF at {self.vol.offset:#x} in {self.vol.layer_name}") + return None 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 + try: + 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 + except exceptions.InvalidAddressException: + vollog.debug(f"Page fault encountered when accessing typetab of ELF at {self.vol.offset:#x} in {self.vol.layer_name}") + return None raise AttributeError("Unable to get typetab section, it needs a kernel >= 5.2") @@ -385,14 +401,18 @@ class module(generic.GenericIntelProcess): 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] - 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) + try: + 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) + except exceptions.InvalidAddressException: + vollog.debug(f"Page fault encountered when accessing symbol type of index {symbol_index} of ELF at {self.vol.offset:#x} in {self.vol.layer_name}") + return None return sym_type From e48d2972a6e44bb01fc5c2f4eafa28b91d1e4071 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 17 Mar 2025 17:46:25 -0500 Subject: [PATCH 43/78] Add needed checks to prevent backtraces in ELF parsing --- .../symbols/linux/extensions/__init__.py | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 524f2a7c6..959d33c60 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -343,7 +343,9 @@ class module(generic.GenericIntelProcess): elif self.has_member("symtab"): return self.symtab except exceptions.InvalidAddressException: - vollog.debug(f"Page fault encountered when accessing symtab of ELF at {self.vol.offset:#x} in {self.vol.layer_name}") + vollog.debug( + f"Page fault encountered when accessing symtab of ELF at {self.vol.offset:#x} in {self.vol.layer_name}" + ) return None raise AttributeError("Unable to get symtab") @@ -356,7 +358,9 @@ class module(generic.GenericIntelProcess): elif self.has_member("num_symtab"): return int(self.member("num_symtab")) except exceptions.InvalidAddressException: - vollog.debug(f"Page fault encountered when accessing num_symtab of ELF at {self.vol.offset:#x} in {self.vol.layer_name}") + vollog.debug( + f"Page fault encountered when accessing num_symtab of ELF at {self.vol.offset:#x} in {self.vol.layer_name}" + ) return None raise AttributeError("Unable to determine number of symbols") @@ -371,7 +375,9 @@ class module(generic.GenericIntelProcess): elif self.has_member("strtab"): return self.strtab except exceptions.InvalidAddressException: - vollog.debug(f"Page fault encountered when accessing strtab of ELF at {self.vol.offset:#x} in {self.vol.layer_name}") + vollog.debug( + f"Page fault encountered when accessing strtab of ELF at {self.vol.offset:#x} in {self.vol.layer_name}" + ) return None raise AttributeError("Unable to get strtab") @@ -384,7 +390,9 @@ class module(generic.GenericIntelProcess): # kernels >= 5.2 1c7651f43777cdd59c1aaa82c87324d3e7438c7b: types have its own array return self.kallsyms.typetab except exceptions.InvalidAddressException: - vollog.debug(f"Page fault encountered when accessing typetab of ELF at {self.vol.offset:#x} in {self.vol.layer_name}") + vollog.debug( + f"Page fault encountered when accessing typetab of ELF at {self.vol.offset:#x} in {self.vol.layer_name}" + ) return None raise AttributeError("Unable to get typetab section, it needs a kernel >= 5.2") @@ -411,7 +419,9 @@ class module(generic.GenericIntelProcess): # kernels < 5.2 the type was stored in the st_info sym_type = chr(symbol.st_info) except exceptions.InvalidAddressException: - vollog.debug(f"Page fault encountered when accessing symbol type of index {symbol_index} of ELF at {self.vol.offset:#x} in {self.vol.layer_name}") + vollog.debug( + f"Page fault encountered when accessing symbol type of index {symbol_index} of ELF at {self.vol.offset:#x} in {self.vol.layer_name}" + ) return None return sym_type From 07b74fd8e30753612610b712514ca1f5e5a0b93a Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 17 Mar 2025 18:28:13 -0500 Subject: [PATCH 44/78] Add typing to functions in modules class --- .../symbols/linux/extensions/__init__.py | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 959d33c60..6767650dc 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -99,13 +99,13 @@ class module(generic.GenericIntelProcess): return self.mem[module_mem_index] - def _get_mem_size(self, mod_mem_type_name): + def _get_mem_size(self, mod_mem_type_name) -> int: return self._get_mem_type(mod_mem_type_name).size - def _get_mem_base(self, mod_mem_type_name): + def _get_mem_base(self, mod_mem_type_name) -> int: return self._get_mem_type(mod_mem_type_name).base - def get_module_base(self): + def get_module_base(self) -> int: if self.has_member("mem"): # kernels 6.4+ return self._get_mem_base("MOD_TEXT") elif self.has_member("core_layout"): @@ -115,7 +115,7 @@ class module(generic.GenericIntelProcess): raise AttributeError("Unable to get module base") - def get_init_size(self): + def get_init_size(self) -> int: if self.has_member("mem"): # kernels 6.4+ return ( self._get_mem_size("MOD_INIT_TEXT") @@ -129,7 +129,7 @@ class module(generic.GenericIntelProcess): raise AttributeError("Unable to determine .init section size of module") - def get_core_size(self): + def get_core_size(self) -> int: if self.has_member("mem"): # kernels 6.4+ return ( self._get_mem_size("MOD_TEXT") @@ -144,7 +144,7 @@ class module(generic.GenericIntelProcess): raise AttributeError("Unable to determine core size of module") - def get_core_text_size(self): + def get_core_text_size(self) -> int: if self.has_member("mem"): # kernels 6.4+ return self._get_mem_size("MOD_TEXT") elif self.has_member("core_layout"): @@ -154,7 +154,7 @@ class module(generic.GenericIntelProcess): raise AttributeError("Unable to determine core text size of module") - def get_module_core(self): + def get_module_core(self) -> objects.Pointer: if self.has_member("mem"): # kernels 6.4+ return self._get_mem_base("MOD_TEXT") elif self.has_member("core_layout"): @@ -163,7 +163,7 @@ class module(generic.GenericIntelProcess): return self.module_core raise AttributeError("Unable to get module core") - def get_module_init(self): + def get_module_init(self) -> objects.Pointer: if self.has_member("mem"): # kernels 6.4+ return self._get_mem_base("MOD_INIT_TEXT") elif self.has_member("init_layout"): @@ -172,9 +172,12 @@ class module(generic.GenericIntelProcess): return self.module_init raise AttributeError("Unable to get module init") - def get_name(self): + def get_name(self) -> Optional[str]: """Get the name of the module as a string""" - return utility.array_to_string(self.name) + try: + return utility.array_to_string(self.name) + except exceptions.InvalidAddressException: + return None def _get_sect_count(self, grp: interfaces.objects.ObjectInterface) -> int: """Try to determine the number of valid sections""" @@ -336,7 +339,7 @@ class module(generic.GenericIntelProcess): return None @property - def section_symtab(self): + def section_symtab(self) -> Optional[interfaces.objects.ObjectInterface]: try: if self.has_member("kallsyms"): return self.kallsyms.symtab @@ -351,7 +354,7 @@ class module(generic.GenericIntelProcess): raise AttributeError("Unable to get symtab") @property - def num_symtab(self): + def num_symtab(self) -> Optional[int]: try: if self.has_member("kallsyms"): return int(self.kallsyms.num_symtab) @@ -366,7 +369,7 @@ class module(generic.GenericIntelProcess): raise AttributeError("Unable to determine number of symbols") @property - def section_strtab(self): + def section_strtab(self) -> Optional[interfaces.objects.ObjectInterface]: try: # Newer kernels if self.has_member("kallsyms"): @@ -383,7 +386,7 @@ class module(generic.GenericIntelProcess): raise AttributeError("Unable to get strtab") @property - def section_typetab(self): + def section_typetab(self) -> Optional[interfaces.objects.ObjectInterface]: try: if self.has_member("kallsyms") and self.kallsyms.has_member("typetab"): # kernels >= 4.5 8244062ef1e54502ef55f54cced659913f244c3e: kallsyms was added @@ -399,7 +402,7 @@ class module(generic.GenericIntelProcess): def get_symbol_type( self, symbol: interfaces.objects.ObjectInterface, symbol_index: int - ) -> str: + ) -> Optional[str]: """Determines the type of a given ELF symbol. Args: From 7146b45fa7571da210be13c2c341f2a3a5c52133 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 17 Mar 2025 17:41:35 +0000 Subject: [PATCH 45/78] Create versioned parent class for all plugins that enumerate Linux kernel modules. Convert plugins to new method. --- .../framework/plugins/linux/check_modules.py | 36 ++++---- .../framework/plugins/linux/hidden_modules.py | 66 +++++++------- volatility3/framework/plugins/linux/lsmod.py | 45 +++------- .../symbols/linux/utilities/modules.py | 85 ++++++++++++++++++- 4 files changed, 145 insertions(+), 87 deletions(-) diff --git a/volatility3/framework/plugins/linux/check_modules.py b/volatility3/framework/plugins/linux/check_modules.py index 44cb568e6..246f2450d 100644 --- a/volatility3/framework/plugins/linux/check_modules.py +++ b/volatility3/framework/plugins/linux/check_modules.py @@ -3,25 +3,26 @@ # import logging -from typing import List, Dict +from typing import List, Dict, Generator import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules -from volatility3.framework import interfaces, renderers, deprecation +from volatility3.framework import interfaces, deprecation from volatility3.framework.configuration import requirements -from volatility3.framework.interfaces import plugins from volatility3.framework.objects import utility -from volatility3.framework.renderers import format_hints from volatility3.framework.symbols.linux import extensions vollog = logging.getLogger(__name__) -class Check_modules(plugins.PluginInterface): +class Check_modules(linux_utilities_modules.ModuleDisplayPlugin): """Compares module list to sysfs info, if available""" - _version = (2, 0, 0) + _version = (3, 0, 0) _required_framework_version = (2, 0, 0) + def __init__(self, *args, **kwargs): + super().__init__(self.compare_kset_and_lsmod, *args, **kwargs) + @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: return [ @@ -31,9 +32,9 @@ class Check_modules(plugins.PluginInterface): architectures=["Intel32", "Intel64"], ), requirements.VersionRequirement( - name="linux_utilities_modules", - component=linux_utilities_modules.Modules, - version=(3, 0, 0), + name="linux_utilities_modules_module_display_plugin", + component=linux_utilities_modules.ModuleDisplayPlugin, + version=(1, 0, 0), ), ] @@ -48,23 +49,20 @@ class Check_modules(plugins.PluginInterface): ) -> Dict[str, extensions.module]: return linux_utilities_modules.Modules.get_kset_modules(context, vmlinux_name) - def _generator(self): + @classmethod + def compare_kset_and_lsmod( + cls, context: str, vmlinux_name: str + ) -> Generator[extensions.module, None, None]: kset_modules = linux_utilities_modules.Modules.get_kset_modules( - self.context, self.config["kernel"] + context=context, vmlinux_name=vmlinux_name ) lsmod_modules = set( str(utility.array_to_string(modules.name)) for modules in linux_utilities_modules.Modules.list_modules( - self.context, self.config["kernel"] + context=context, vmlinux_module_name=vmlinux_name ) ) for mod_name in set(kset_modules.keys()).difference(lsmod_modules): - yield (0, (format_hints.Hex(kset_modules[mod_name]), str(mod_name))) - - def run(self): - return renderers.TreeGrid( - [("Module Address", format_hints.Hex), ("Module Name", str)], - self._generator(), - ) + yield kset_modules[mod_name] diff --git a/volatility3/framework/plugins/linux/hidden_modules.py b/volatility3/framework/plugins/linux/hidden_modules.py index 985d4cfcb..dd473a8f7 100644 --- a/volatility3/framework/plugins/linux/hidden_modules.py +++ b/volatility3/framework/plugins/linux/hidden_modules.py @@ -6,19 +6,22 @@ from typing import List, Set, Tuple, Iterable from volatility3.framework.symbols.linux.utilities import ( modules as linux_utilities_modules, ) -from volatility3.framework import renderers, interfaces, exceptions, deprecation +from volatility3.framework import interfaces, exceptions, deprecation from volatility3.framework.constants import architectures -from volatility3.framework.renderers import format_hints from volatility3.framework.configuration import requirements +from volatility3.framework.symbols.linux import extensions vollog = logging.getLogger(__name__) -class Hidden_modules(interfaces.plugins.PluginInterface): +class Hidden_modules(linux_utilities_modules.ModuleDisplayPlugin): """Carves memory to find hidden kernel modules""" _required_framework_version = (2, 10, 0) - _version = (2, 0, 0) + _version = (3, 0, 0) + + def __init__(self, *args, **kwargs): + super().__init__(self.find_hidden_modules, *args, **kwargs) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -29,9 +32,9 @@ class Hidden_modules(interfaces.plugins.PluginInterface): architectures=architectures.LINUX_ARCHS, ), requirements.VersionRequirement( - name="linux_utilities_modules", - component=linux_utilities_modules.Modules, - version=(3, 0, 0), + name="linux_utilities_modules_module_display_plugin", + component=linux_utilities_modules.ModuleDisplayPlugin, + version=(1, 0, 0), ), ] @@ -165,38 +168,29 @@ class Hidden_modules(interfaces.plugins.PluginInterface): } return known_module_addresses - def _generator(self): - vmlinux_module_name = self.config["kernel"] - known_module_addresses = self.get_lsmod_module_addresses( - self.context, vmlinux_module_name - ) - modules_memory_boundaries = ( - linux_utilities_modules.Modules.get_modules_memory_boundaries( - self.context, vmlinux_module_name - ) - ) - - for module in linux_utilities_modules.Modules.get_hidden_modules( - self.context, - vmlinux_module_name, - known_module_addresses, - modules_memory_boundaries, - ): - module_addr = module.vol.offset - module_name = module.get_name() or renderers.NotAvailableValue() - fields = (format_hints.Hex(module_addr), module_name) - yield (0, fields) - - def run(self): - if self.context.symbol_space.verify_table_versions( + @classmethod + def find_hidden_modules( + cls, context, vmlinux_module_name: str + ) -> extensions.module: + if context.symbol_space.verify_table_versions( "dwarf2json", lambda version, _: (not version) or version < (0, 8, 0) ): raise exceptions.SymbolSpaceError( "Invalid symbol table, please ensure the ISF table produced by dwarf2json was created with version 0.8.0 or later" ) - headers = [ - ("Address", format_hints.Hex), - ("Name", str), - ] - return renderers.TreeGrid(headers, self._generator()) + known_module_addresses = cls.get_lsmod_module_addresses( + context, vmlinux_module_name + ) + modules_memory_boundaries = ( + linux_utilities_modules.Modules.get_modules_memory_boundaries( + context, vmlinux_module_name + ) + ) + + yield from linux_utilities_modules.Modules.get_hidden_modules( + context, + vmlinux_module_name, + known_module_addresses, + modules_memory_boundaries, + ) diff --git a/volatility3/framework/plugins/linux/lsmod.py b/volatility3/framework/plugins/linux/lsmod.py index 466bfa0b4..30d494d55 100644 --- a/volatility3/framework/plugins/linux/lsmod.py +++ b/volatility3/framework/plugins/linux/lsmod.py @@ -7,20 +7,20 @@ import logging from typing import List, Iterable import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules -from volatility3.framework import exceptions, renderers, interfaces, deprecation +from volatility3.framework import interfaces, deprecation from volatility3.framework.configuration import requirements -from volatility3.framework.interfaces import plugins -from volatility3.framework.objects import utility -from volatility3.framework.renderers import format_hints vollog = logging.getLogger(__name__) -class Lsmod(plugins.PluginInterface): +class Lsmod(linux_utilities_modules.ModuleDisplayPlugin): """Lists loaded kernel modules.""" _required_framework_version = (2, 0, 0) - _version = (2, 0, 0) + _version = (3, 0, 0) + + def __init__(self, *args, **kwargs): + super().__init__(linux_utilities_modules.ModuleGathererLsmod, *args, **kwargs) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -31,9 +31,14 @@ class Lsmod(plugins.PluginInterface): architectures=["Intel32", "Intel64"], ), requirements.VersionRequirement( - name="linux_utilities_modules", - component=linux_utilities_modules.Modules, - version=(3, 0, 0), + name="linux_utilities_modules_gatherers_lsmod", + component=linux_utilities_modules.ModuleGathererLsmod, + version=(1, 0, 0), + ), + requirements.VersionRequirement( + name="linux_utilities_modules_module_display_plugin", + component=linux_utilities_modules.ModuleDisplayPlugin, + version=(1, 0, 0), ), ] @@ -49,25 +54,3 @@ class Lsmod(plugins.PluginInterface): return linux_utilities_modules.Modules.list_modules( context, vmlinux_module_name ) - - def _generator(self): - try: - for module in linux_utilities_modules.Modules.list_modules( - self.context, self.config["kernel"] - ): - mod_size = module.get_init_size() + module.get_core_size() - - mod_name = utility.array_to_string(module.name) - - yield 0, (format_hints.Hex(module.vol.offset), mod_name, mod_size) - - except exceptions.SymbolError: - vollog.warning( - "The required symbol 'module' is not present in symbol table. Please check that kernel modules are enabled for the system under analysis." - ) - - def run(self): - return renderers.TreeGrid( - [("Offset", format_hints.Hex), ("Name", str), ("Size", int)], - self._generator(), - ) diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index 8f1b5b67d..a2f6a942b 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -21,11 +21,15 @@ from volatility3.framework import ( deprecation, exceptions, objects, + renderers, ) - +from volatility3.framework.constants import architectures +from volatility3.framework.renderers import format_hints from volatility3.framework.configuration import requirements from volatility3.framework.objects import utility from volatility3.framework.symbols.linux import extensions +from volatility3.framework.interfaces import plugins +from volatility3.framework.symbols.linux.utilities import tainting vollog = logging.getLogger(__name__) @@ -684,3 +688,82 @@ class ModuleGatherers( ) return reqs + + +class ModuleDisplayPlugin(plugins.PluginInterface): + """ + Plugins that enumerate kernel modules (lsmod, check_modules, etc.) + must inherit from this class to have unified output columns across plugins. + The constructor of the plugin must call super() with the `implementation` set + """ + + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + + framework.require_interface_version(*_required_framework_version) + + def __init__(self, implementation, *args, **kwargs): + super().__init__(*args, **kwargs) + self.implementation = implementation + + @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=Modules, + version=(3, 0, 0), + ), + requirements.VersionRequirement( + name="linux-tainting", component=tainting.Tainting, version=(1, 0, 0) + ), + ] + + def _generator(self): + """ + Uses the implementation set in the constructor call to produce consistent output fields + across module gathering plugins + """ + for module in self.implementation(self.context, self.config["kernel"]): + try: + name = utility.array_to_string(module.name) + except exceptions.InvalidAddressException: + vollog.debug( + f"Unable to recover name for module {module.vol.offset:#x} from implementation {self.implementation}" + ) + continue + + code_size = format_hints.Hex( + module.get_init_size() + module.get_core_size() + ) + + taints = ",".join( + tainting.Tainting.get_taints_parsed( + self.context, self.config["kernel"], module.taints, True + ) + ) + + yield 0, ( + format_hints.Hex(module.vol.offset), + name, + format_hints.Hex(code_size), + taints, + renderers.NotAvailableValue(), # will become the load arguments after this inital conversion is merged + ) + + def run(self): + return renderers.TreeGrid( + [ + ("Offset", format_hints.Hex), + ("Module Name", str), + ("Code Size", format_hints.Hex), + ("Taints", str), + ("Load Arguments", str), + ], + self._generator(), + ) From 971f06996b54deda6cab6e0d48b01146c83ad519 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 17 Mar 2025 18:02:03 +0000 Subject: [PATCH 46/78] bump version on kallsyms --- volatility3/framework/symbols/linux/kallsyms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/volatility3/framework/symbols/linux/kallsyms.py b/volatility3/framework/symbols/linux/kallsyms.py index 368169757..945342ea9 100644 --- a/volatility3/framework/symbols/linux/kallsyms.py +++ b/volatility3/framework/symbols/linux/kallsyms.py @@ -305,6 +305,7 @@ class Kallsyms(interfaces.configuration.VersionableInterface): def _assert_versions(cls) -> None: """Verify versions of shared dependencies""" linux_utilities_modules_version_required = (3, 0, 0) + if not requirements.VersionRequirement.matches_required( linux_utilities_modules_version_required, linux_utilities_modules.Modules.version, From f906bde338d8298c69d3e8db6faa6664f935a25b Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 17 Mar 2025 18:22:19 +0000 Subject: [PATCH 47/78] change lmsod call --- volatility3/framework/plugins/linux/lsmod.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/volatility3/framework/plugins/linux/lsmod.py b/volatility3/framework/plugins/linux/lsmod.py index 30d494d55..d01a25afa 100644 --- a/volatility3/framework/plugins/linux/lsmod.py +++ b/volatility3/framework/plugins/linux/lsmod.py @@ -20,7 +20,7 @@ class Lsmod(linux_utilities_modules.ModuleDisplayPlugin): _version = (3, 0, 0) def __init__(self, *args, **kwargs): - super().__init__(linux_utilities_modules.ModuleGathererLsmod, *args, **kwargs) + super().__init__(linux_utilities_modules.Modules.list_modules, *args, **kwargs) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -31,9 +31,9 @@ class Lsmod(linux_utilities_modules.ModuleDisplayPlugin): architectures=["Intel32", "Intel64"], ), requirements.VersionRequirement( - name="linux_utilities_modules_gatherers_lsmod", - component=linux_utilities_modules.ModuleGathererLsmod, - version=(1, 0, 0), + name="linux_utilities_modules", + component=linux_utilities_modules.Modules, + version=(3, 0, 0), ), requirements.VersionRequirement( name="linux_utilities_modules_module_display_plugin", From 00c4a13567d2fb2bc14aa291bb9ab7c7aab02a75 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 17 Mar 2025 18:58:55 -0500 Subject: [PATCH 48/78] remove errant space --- volatility3/framework/symbols/linux/kallsyms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/kallsyms.py b/volatility3/framework/symbols/linux/kallsyms.py index 945342ea9..368169757 100644 --- a/volatility3/framework/symbols/linux/kallsyms.py +++ b/volatility3/framework/symbols/linux/kallsyms.py @@ -305,7 +305,6 @@ class Kallsyms(interfaces.configuration.VersionableInterface): def _assert_versions(cls) -> None: """Verify versions of shared dependencies""" linux_utilities_modules_version_required = (3, 0, 0) - if not requirements.VersionRequirement.matches_required( linux_utilities_modules_version_required, linux_utilities_modules.Modules.version, From 93be148534308a47e06ceefbecf64ccdeaba50ee Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 17 Mar 2025 19:04:02 -0500 Subject: [PATCH 49/78] Change how the inheritance is performed --- volatility3/framework/plugins/linux/check_modules.py | 5 ++++- volatility3/framework/plugins/linux/hidden_modules.py | 5 ++++- volatility3/framework/plugins/linux/lsmod.py | 3 ++- volatility3/framework/symbols/linux/utilities/modules.py | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/volatility3/framework/plugins/linux/check_modules.py b/volatility3/framework/plugins/linux/check_modules.py index 246f2450d..55865deb8 100644 --- a/volatility3/framework/plugins/linux/check_modules.py +++ b/volatility3/framework/plugins/linux/check_modules.py @@ -10,11 +10,14 @@ from volatility3.framework import interfaces, deprecation from volatility3.framework.configuration import requirements from volatility3.framework.objects import utility from volatility3.framework.symbols.linux import extensions +from volatility3.framework.interfaces import plugins vollog = logging.getLogger(__name__) -class Check_modules(linux_utilities_modules.ModuleDisplayPlugin): +class Check_modules( + linux_utilities_modules.ModuleDisplayPlugin, plugins.PluginInterface +): """Compares module list to sysfs info, if available""" _version = (3, 0, 0) diff --git a/volatility3/framework/plugins/linux/hidden_modules.py b/volatility3/framework/plugins/linux/hidden_modules.py index dd473a8f7..56b36e2cb 100644 --- a/volatility3/framework/plugins/linux/hidden_modules.py +++ b/volatility3/framework/plugins/linux/hidden_modules.py @@ -10,11 +10,14 @@ from volatility3.framework import interfaces, exceptions, deprecation from volatility3.framework.constants import architectures from volatility3.framework.configuration import requirements from volatility3.framework.symbols.linux import extensions +from volatility3.framework.interfaces import plugins vollog = logging.getLogger(__name__) -class Hidden_modules(linux_utilities_modules.ModuleDisplayPlugin): +class Hidden_modules( + linux_utilities_modules.ModuleDisplayPlugin, plugins.PluginInterface +): """Carves memory to find hidden kernel modules""" _required_framework_version = (2, 10, 0) diff --git a/volatility3/framework/plugins/linux/lsmod.py b/volatility3/framework/plugins/linux/lsmod.py index d01a25afa..71f36ecc9 100644 --- a/volatility3/framework/plugins/linux/lsmod.py +++ b/volatility3/framework/plugins/linux/lsmod.py @@ -9,11 +9,12 @@ from typing import List, Iterable import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules from volatility3.framework import interfaces, deprecation from volatility3.framework.configuration import requirements +from volatility3.framework.interfaces import plugins vollog = logging.getLogger(__name__) -class Lsmod(linux_utilities_modules.ModuleDisplayPlugin): +class Lsmod(linux_utilities_modules.ModuleDisplayPlugin, plugins.PluginInterface): """Lists loaded kernel modules.""" _required_framework_version = (2, 0, 0) diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index a2f6a942b..b5b5b28a2 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -690,7 +690,7 @@ class ModuleGatherers( return reqs -class ModuleDisplayPlugin(plugins.PluginInterface): +class ModuleDisplayPlugin(interfaces.configuration.VersionableInterface): """ Plugins that enumerate kernel modules (lsmod, check_modules, etc.) must inherit from this class to have unified output columns across plugins. From 0667a408364394dc56f7bcf080f5c26c92171c39 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 17 Mar 2025 19:06:02 -0500 Subject: [PATCH 50/78] Removed unused import --- volatility3/framework/symbols/linux/utilities/modules.py | 1 - 1 file changed, 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index b5b5b28a2..b8466d097 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -28,7 +28,6 @@ from volatility3.framework.renderers import format_hints from volatility3.framework.configuration import requirements from volatility3.framework.objects import utility from volatility3.framework.symbols.linux import extensions -from volatility3.framework.interfaces import plugins from volatility3.framework.symbols.linux.utilities import tainting vollog = logging.getLogger(__name__) From 06a4c5639533657d32eda56b927aa0a826406829 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 17 Mar 2025 20:15:18 -0500 Subject: [PATCH 51/78] Update for new accessing method --- .../framework/plugins/linux/check_modules.py | 45 ++++++----- .../framework/plugins/linux/hidden_modules.py | 77 +++++++++---------- volatility3/framework/plugins/linux/lsmod.py | 7 +- .../symbols/linux/utilities/modules.py | 6 +- 4 files changed, 65 insertions(+), 70 deletions(-) diff --git a/volatility3/framework/plugins/linux/check_modules.py b/volatility3/framework/plugins/linux/check_modules.py index 55865deb8..5a43bf899 100644 --- a/volatility3/framework/plugins/linux/check_modules.py +++ b/volatility3/framework/plugins/linux/check_modules.py @@ -15,16 +15,33 @@ from volatility3.framework.interfaces import plugins vollog = logging.getLogger(__name__) -class Check_modules( - linux_utilities_modules.ModuleDisplayPlugin, plugins.PluginInterface -): +class Check_modules(plugins.PluginInterface): """Compares module list to sysfs info, if available""" _version = (3, 0, 0) _required_framework_version = (2, 0, 0) - def __init__(self, *args, **kwargs): - super().__init__(self.compare_kset_and_lsmod, *args, **kwargs) + @classmethod + def compare_kset_and_lsmod( + cls, context: str, vmlinux_name: str + ) -> Generator[extensions.module, None, None]: + kset_modules = linux_utilities_modules.Modules.get_kset_modules( + context=context, vmlinux_name=vmlinux_name + ) + + lsmod_modules = set( + str(utility.array_to_string(modules.name)) + for modules in linux_utilities_modules.Modules.list_modules( + context=context, vmlinux_module_name=vmlinux_name + ) + ) + + for mod_name in set(kset_modules.keys()).difference(lsmod_modules): + yield kset_modules[mod_name] + + run = linux_utilities_modules.ModuleDisplayPlugin.run + _generator = linux_utilities_modules.ModuleDisplayPlugin.generator + implementation = compare_kset_and_lsmod @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -51,21 +68,3 @@ class Check_modules( cls, context: interfaces.context.ContextInterface, vmlinux_name: str ) -> Dict[str, extensions.module]: return linux_utilities_modules.Modules.get_kset_modules(context, vmlinux_name) - - @classmethod - def compare_kset_and_lsmod( - cls, context: str, vmlinux_name: str - ) -> Generator[extensions.module, None, None]: - kset_modules = linux_utilities_modules.Modules.get_kset_modules( - context=context, vmlinux_name=vmlinux_name - ) - - lsmod_modules = set( - str(utility.array_to_string(modules.name)) - for modules in linux_utilities_modules.Modules.list_modules( - context=context, vmlinux_module_name=vmlinux_name - ) - ) - - for mod_name in set(kset_modules.keys()).difference(lsmod_modules): - yield kset_modules[mod_name] diff --git a/volatility3/framework/plugins/linux/hidden_modules.py b/volatility3/framework/plugins/linux/hidden_modules.py index 56b36e2cb..c6f5d749e 100644 --- a/volatility3/framework/plugins/linux/hidden_modules.py +++ b/volatility3/framework/plugins/linux/hidden_modules.py @@ -15,16 +15,49 @@ from volatility3.framework.interfaces import plugins vollog = logging.getLogger(__name__) -class Hidden_modules( - linux_utilities_modules.ModuleDisplayPlugin, plugins.PluginInterface -): +class Hidden_modules(plugins.PluginInterface): """Carves memory to find hidden kernel modules""" _required_framework_version = (2, 10, 0) _version = (3, 0, 0) - def __init__(self, *args, **kwargs): - super().__init__(self.find_hidden_modules, *args, **kwargs) + @classmethod + def get_hidden_modules( + cls, + context: interfaces.context.ContextInterface, + vmlinux_module_name: str, + known_module_addresses: Set[int], + modules_memory_boundaries: Tuple, + ) -> Iterable[interfaces.objects.ObjectInterface]: + """Enumerate hidden modules by taking advantage of memory address alignment patterns + + This technique is much faster and uses less memory than the traditional scan method + in Volatility2, but it doesn't work with older kernels. + + From kernels 4.2 struct module allocation are aligned to the L1 cache line size. + In i386/amd64/arm64 this is typically 64 bytes. However, this can be changed in + the Linux kernel configuration via CONFIG_X86_L1_CACHE_SHIFT. The alignment can + also be obtained from the DWARF info i.e. DW_AT_alignment<64>, but dwarf2json + doesn't support this feature yet. + In kernels < 4.2, alignment attributes are absent in the struct module, meaning + alignment cannot be guaranteed. Therefore, for older kernels, it's better to use + the traditional scan technique. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + vmlinux_module_name: The name of the kernel module on which to operate + known_module_addresses: Set with known module addresses + modules_memory_boundaries: Minimum and maximum address boundaries for module allocation. + Yields: + module objects + """ + return linux_utilities_modules.get_hidden_modules( + vmlinux_module_name, known_module_addresses, modules_memory_boundaries + ) + + run = linux_utilities_modules.ModuleDisplayPlugin.run + _generator = linux_utilities_modules.ModuleDisplayPlugin.generator + implementation = linux_utilities_modules.Modules.list_modules @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -88,40 +121,6 @@ class Hidden_modules( removal_date="2025-09-25", replacement_version=(3, 0, 0), ) - @classmethod - def get_hidden_modules( - cls, - context: interfaces.context.ContextInterface, - vmlinux_module_name: str, - known_module_addresses: Set[int], - modules_memory_boundaries: Tuple, - ) -> Iterable[interfaces.objects.ObjectInterface]: - """Enumerate hidden modules by taking advantage of memory address alignment patterns - - This technique is much faster and uses less memory than the traditional scan method - in Volatility2, but it doesn't work with older kernels. - - From kernels 4.2 struct module allocation are aligned to the L1 cache line size. - In i386/amd64/arm64 this is typically 64 bytes. However, this can be changed in - the Linux kernel configuration via CONFIG_X86_L1_CACHE_SHIFT. The alignment can - also be obtained from the DWARF info i.e. DW_AT_alignment<64>, but dwarf2json - doesn't support this feature yet. - In kernels < 4.2, alignment attributes are absent in the struct module, meaning - alignment cannot be guaranteed. Therefore, for older kernels, it's better to use - the traditional scan technique. - - Args: - context: The context to retrieve required elements (layers, symbol tables) from - vmlinux_module_name: The name of the kernel module on which to operate - known_module_addresses: Set with known module addresses - modules_memory_boundaries: Minimum and maximum address boundaries for module allocation. - Yields: - module objects - """ - return linux_utilities_modules.get_hidden_modules( - vmlinux_module_name, known_module_addresses, modules_memory_boundaries - ) - @staticmethod @deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.validate_alignment_patterns, diff --git a/volatility3/framework/plugins/linux/lsmod.py b/volatility3/framework/plugins/linux/lsmod.py index 71f36ecc9..3029d2541 100644 --- a/volatility3/framework/plugins/linux/lsmod.py +++ b/volatility3/framework/plugins/linux/lsmod.py @@ -14,14 +14,15 @@ from volatility3.framework.interfaces import plugins vollog = logging.getLogger(__name__) -class Lsmod(linux_utilities_modules.ModuleDisplayPlugin, plugins.PluginInterface): +class Lsmod(plugins.PluginInterface): """Lists loaded kernel modules.""" _required_framework_version = (2, 0, 0) _version = (3, 0, 0) - def __init__(self, *args, **kwargs): - super().__init__(linux_utilities_modules.Modules.list_modules, *args, **kwargs) + run = linux_utilities_modules.ModuleDisplayPlugin.run + _generator = linux_utilities_modules.ModuleDisplayPlugin.generator + implementation = linux_utilities_modules.Modules.list_modules @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index b8466d097..0b4dea997 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -701,10 +701,6 @@ class ModuleDisplayPlugin(interfaces.configuration.VersionableInterface): framework.require_interface_version(*_required_framework_version) - def __init__(self, implementation, *args, **kwargs): - super().__init__(*args, **kwargs) - self.implementation = implementation - @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: return [ @@ -723,7 +719,7 @@ class ModuleDisplayPlugin(interfaces.configuration.VersionableInterface): ), ] - def _generator(self): + def generator(self): """ Uses the implementation set in the constructor call to produce consistent output fields across module gathering plugins From 2795c7cdd2ad2899508faf423656581958eadec0 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Wed, 19 Mar 2025 15:36:18 -0500 Subject: [PATCH 52/78] Windows: Fix raw Dpc offset calculation The original code was still returning this as a pointer that ended up dereferenced in later steps. However, this pointer value actually needs to be cast to an `unsigned long long` and decoded first. --- .../symbols/windows/extensions/__init__.py | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 933178c91..091d6ceb5 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -22,9 +22,8 @@ from volatility3.framework.interfaces.objects import ObjectInterface from volatility3.framework.layers import intel from volatility3.framework.objects import utility from volatility3.framework.renderers import conversion -from volatility3.framework.symbols import generic +from volatility3.framework.symbols import generic, windows from volatility3.framework.symbols.windows.extensions import pool -from volatility3.framework.symbols import windows vollog = logging.getLogger(__name__) @@ -1222,17 +1221,13 @@ class KTIMER(objects.StructType): return "-" def get_raw_dpc(self): - """Returns the encoded DPC since it may not look like a pointer after encoding""" - symbol_table_name = self.get_symbol_table_name() - pointer_type = self._context.symbol_space.get_type( - symbol_table_name + constants.BANG + "pointer" - ) - - return self._context.object( - object_type=pointer_type, - layer_name=self.vol.layer_name, - offset=self.Dpc.vol.offset, - ) + """Returns the encoded DPC as an unsigned long long since the pointer is actually encoded""" + if symbols.symbol_table_is_64bit( + context=self._context, symbol_table_name=self.get_symbol_table_name() + ): + return self.Dpc.cast("unsigned long long") + else: + return self.Dpc.cast("unsigned long") def valid_type(self): return self.Header.Type in self.VALID_TYPES From bfe50889b0c51088615db29dd86ca50f1b0ca852 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Wed, 19 Mar 2025 17:32:09 -0500 Subject: [PATCH 53/78] Add the recovery and reporting of LKM load parameters --- .../symbols/linux/utilities/modules.py | 237 +++++++++++++++++- 1 file changed, 234 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index 0b4dea997..f987c352e 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -70,7 +70,7 @@ class ModuleGathererInterface( class Modules(interfaces.configuration.VersionableInterface): """Kernel modules related utilities.""" - _version = (3, 0, 0) + _version = (3, 0, 1) _required_framework_version = (2, 0, 0) framework.require_interface_version(*_required_framework_version) @@ -558,6 +558,231 @@ class Modules(interfaces.configuration.VersionableInterface): """ return all(addr % address_alignment == 0 for addr in addresses) + @classmethod + def _get_param_handlers( + cls, context: interfaces.context.ContextInterface, vmlinux_name: str + ) -> Tuple[Dict[int, str], Dict[str, Optional[int]]]: + """ + This function builds the dictionaries needed to map kernel parameters to their types + We need these values and information to properly decode each parameter to its input representation + """ + kernel = context.modules[vmlinux_name] + + # All the integer type parameters + pairs = { + "param_get_invbool": "int", + "param_get_bool": "int", + "param_get_int": "int", + "param_get_ulong": "long unsigned int", + "param_get_ullong": "long long unsigned int", + "param_get_long": "long int", + "param_get_uint": "unsigned int", + "param_get_ushort": "short unsigned int", + "param_get_short": "short int", + "param_get_byte": "char", + } + + int_handlers: Dict[int, str] = {} + + for sym_name, val_type in pairs.items(): + try: + sym_address = kernel.get_absolute_symbol_address(sym_name) + except exceptions.SymbolError: + continue + + int_handlers[sym_address] = val_type + + # Strings, arrays, booleans + getters = { + "param_get_string": None, + "param_array_get": None, + "param_get_charp": None, + "param_get_bool": None, + "param_get_invbool": None, + } + + for sym_name in getters: + try: + sym_address = kernel.get_absolute_symbol_address(sym_name) + except exceptions.SymbolError: + continue + + getters[sym_name] = sym_address + + return int_handlers, getters + + @classmethod + def _get_param_val( + cls, + context: interfaces.context.ContextInterface, + vmlinux_name: str, + int_handlers, + getters, + module, + param, + ) -> Optional[Union[str, int]]: + """ + Properly determines the type of a parameter and decodes based on the type. + The type is determined by examining its `get` function, which will be a pointer to + predefined operations handler for particular parameter types. + """ + + # Attempt to retrieve the `get` pointer. Bail if smeared + try: + if hasattr(param, "get"): + param_func = param.get + else: + param_func = param.ops.get + + except exceptions.InvalidAddressException: + return None + + if not param_func: + return None + + kernel = context.modules[vmlinux_name] + + # For arrays, recusively get the value of each member as the type can be different + if param_func == getters["param_array_get"]: + array = param.arr + + if array.num: + max_index = array.num.dereference() + else: + max_index = array.member("max") + + if max_index > 32: + vollog.debug( + f"Skipping array parameter with invalid index for module {module.vol.offset:#x}" + ) + return None + + element_vals = [] + for i in range(max_index): + kp = kernel.object( + object_type="kernel_param", + offset=array.elem + (array.elemsize * i), + absolute=True, + ) + + element_vals.append( + cls._get_param_val( + context, vmlinux_name, int_handlers, getters, module, kp + ) + ) + + # nothing was gathered + if not element_vals: + return None + + return ",".join([str(ele) for ele in element_vals]) + + # strings types + elif param_func in [getters["param_get_string"], getters["param_get_charp"]]: + try: + if param_func == getters["param_get_string"]: + count = param.member("str").maxlen + else: + count = 256 + + return utility.pointer_to_string(param.member("str"), count=count) + except exceptions.InvalidAddressException: + vollog.debug( + f"Skipping string parameter with invalid address for module {module.vol.offset:#x}" + ) + return None + + # The integer handles, which also encompass boolean handlers + elif param_func in int_handlers: + try: + int_value = kernel.object( + object_type=int_handlers[param_func], offset=param.arg + ) + except exceptions.InvalidAddressException: + vollog.debug( + f"Skipping {int_handlers[param_func]} parameter with invalid address for module {module.vol.offset:#x}" + ) + return None + + if param_func == getters["param_get_bool"]: + if int_value == 0: + return "N" + else: + return "Y" + elif param_func == getters["param_get_invbool"]: + if int_value == 0: + return "Y" + else: + return "N" + else: + return int_value + + else: + handler_symbol = kernel.get_symbols_by_absolute_location(param_func) + + msg = f"Unknown kernel parameter handling function ({handler_symbol}) at address {param_func:#x} for module at {module.vol.offset:#x}" + + # If a new kernel has a handler symbol we don't support then we want to always see that information + # If the handler doesn't map to a kernel symbol then its smeared/invalid + if handler_symbol: + vollog.warning(msg) + else: + vollog.debug(msg) + + return None + + @classmethod + def get_load_parameters( + cls, + context: interfaces.context.ContextInterface, + vmlinux_name: str, + module: extensions.module, + ) -> Generator[Tuple[str, Optional[Union[str, int]]], None, None]: + """ + Recovers the load parameters of the given kernel module + Returns a tuple (key,value) for each parameter + """ + if not hasattr(module, "kp"): + vollog.debug( + "kp member missing for struct module. Cannot recover parameters." + ) + return None + + if module.num_kp > 128: + vollog.debug( + f"Smeared number of parameters ({module.num_kp}) found for module at offset {module.vol.offset:#x}" + ) + return None + + kernel = context.modules[vmlinux_name] + + int_handlers, getters = cls._get_param_handlers(context, vmlinux_name) + + # Build the array of parameters + param_array = kernel.object( + object_type="array", + offset=module.kp.dereference().vol.offset, + subtype=kernel.get_type("kernel_param"), + count=module.num_kp, + absolute=True, + ) + + for i in range(len(param_array)): + try: + param = param_array[i] + name = utility.pointer_to_string(param.name, count=32) + except exceptions.InvalidAddressException: + vollog.debug( + f"Smeared load parameter module at offset {module.vol.offset:#x}" + ) + continue + + value = cls._get_param_val( + context, vmlinux_name, int_handlers, getters, module, param + ) + + yield name, value + class ModuleGathererLsmod(ModuleGathererInterface): """ @@ -712,7 +937,7 @@ class ModuleDisplayPlugin(interfaces.configuration.VersionableInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=Modules, - version=(3, 0, 0), + version=(3, 0, 1), ), requirements.VersionRequirement( name="linux-tainting", component=tainting.Tainting, version=(1, 0, 0) @@ -743,12 +968,18 @@ class ModuleDisplayPlugin(interfaces.configuration.VersionableInterface): ) ) + parameters_iter = Modules.get_load_parameters( + self.context, self.config["kernel"], module + ) + + parameters = ", ".join([f"{key}={value}" for key, value in parameters_iter]) + yield 0, ( format_hints.Hex(module.vol.offset), name, format_hints.Hex(code_size), taints, - renderers.NotAvailableValue(), # will become the load arguments after this inital conversion is merged + parameters, ) def run(self): From 741a4ea8097a2c1211425a99660eb08ab6f055a4 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Wed, 19 Mar 2025 23:49:45 +0000 Subject: [PATCH 54/78] Hopefully final round of kallsym fixes --- .../framework/plugins/linux/kallsyms.py | 8 +++-- .../symbols/linux/extensions/__init__.py | 24 +++++++++++-- .../framework/symbols/linux/kallsyms.py | 36 ++++++++++++++----- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/volatility3/framework/plugins/linux/kallsyms.py b/volatility3/framework/plugins/linux/kallsyms.py index 47861d91a..54f1adc70 100644 --- a/volatility3/framework/plugins/linux/kallsyms.py +++ b/volatility3/framework/plugins/linux/kallsyms.py @@ -73,6 +73,9 @@ class Kallsyms(plugins.PluginInterface): # 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 + if not kassymbol or not kassymbol.size: + return renderers.NotAvailableValue() + return kassymbol.size if kassymbol.size >= 0 else renderers.NotAvailableValue() def _generator(self): @@ -95,6 +98,7 @@ class Kallsyms(plugins.PluginInterface): include_core = include_modules = include_ftrace = include_bpf = True symbol_generators = [] + if include_core: symbol_generators.append(kas.get_core_symbols()) if include_modules: @@ -116,9 +120,9 @@ class Kallsyms(plugins.PluginInterface): symbol_size = self._get_symbol_size(kassymbol) fields = ( format_hints.Hex(kassymbol.address), - kassymbol.type, + kassymbol.type or renderers.NotAvailableValue(), symbol_size, - kassymbol.exported, + kassymbol.exported or renderers.NotAvailableValue(), kassymbol.subsystem, kassymbol.module_name, kassymbol.name, diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index ae958411a..e998b55dd 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -3053,7 +3053,7 @@ class kernel_symbol(objects.StructType): long_mask = (1 << layer.bits_per_register) - 1 return (self.vol.offset + off) & long_mask - def get_name(self) -> str: + def _do_get_name(self) -> str: if self.has_member("name_offset"): # kernel >= 4.19 and CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y # See 7290d58095712a89f845e1bca05334796dd49ed2 @@ -3073,7 +3073,13 @@ class kernel_symbol(objects.StructType): return name_bytes.decode("utf-8", errors="ignore") - def get_value(self) -> int: + def get_name(self) -> Optional[str]: + try: + return self._do_get_name() + except exceptions.InvalidAddressException: + return None + + def _do_get_value(self) -> int: if self.has_member("value_offset"): # kernel >= 4.19 and CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y # See 7290d58095712a89f845e1bca05334796dd49ed2 @@ -3084,7 +3090,13 @@ class kernel_symbol(objects.StructType): raise AttributeError("Unsupported kernel_symbol type implementation") - def get_namespace(self) -> str: + def _do_get_value(self) -> Optional[int]: + try: + return self._do_get_value() + except exceptions.InvalidAddressException: + return None + + def _do_get_namespace(self) -> str: if self.has_member("namespace_offset"): # kernel >= 4.19 and CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y # See 7290d58095712a89f845e1bca05334796dd49ed2 @@ -3103,3 +3115,9 @@ class kernel_symbol(objects.StructType): namespace_bytes = namespace_bytes[:idx] return namespace_bytes.decode("utf-8", errors="ignore") + + def get_namespace(self) -> Optional[str]: + try: + return self._do_get_namespace() + except exceptions.InvalidAddressException: + return None diff --git a/volatility3/framework/symbols/linux/kallsyms.py b/volatility3/framework/symbols/linux/kallsyms.py index 368169757..35aba3ca5 100644 --- a/volatility3/framework/symbols/linux/kallsyms.py +++ b/volatility3/framework/symbols/linux/kallsyms.py @@ -186,7 +186,10 @@ class KASSymbol(KASSymbolBasic): # 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")) + if self.type: + self.exported = bool(self.type.isupper() or self.type in ("u", "v", "w")) + else: + self.exported = None @functools.cached_property def type_description(self) -> Optional[str]: @@ -200,10 +203,12 @@ class KASSymbol(KASSymbolBasic): 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 - ) + if self.type: + # Otherwise, use the lowercase version + symbol_type_description = linux_constants.NM_TYPES_DESC.get( + self.type.lower(), None + ) + return symbol_type_description @@ -767,7 +772,13 @@ class Kallsyms(interfaces.configuration.VersionableInterface): self._kas_config.stop_ksymtab, ) - return kernel_symbol is not None and kernel_symbol.get_value() == address + if kernel_symbol is not None: + if hasattr(kernel_symbol, "get_value"): + return kernel_symbol.get_value() == address + else: + return kernel_symbol.vol.offset == address + + return None def _elfsym_to_kassymbol( self, @@ -1094,7 +1105,9 @@ class Kallsyms(interfaces.configuration.VersionableInterface): name: str, other: str, ) -> int: - if name == other: + if name is None or other is None: + return None + elif name == other: return 0 elif name < other: return -1 @@ -1315,7 +1328,14 @@ class Kallsyms(interfaces.configuration.VersionableInterface): # 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") + try: + bpf_kallsyms_list = vmlinux.object_from_symbol("bpf_kallsyms") + except exceptions.SymbolError: + vollog.debug( + "`bpf_kallsyms` symbol not present in the symbol table. Cannot proceed." + ) + return None + for elem in bpf_kallsyms_list.to_list(list_type_symname, list_head_member): try: # See kernel's bpf_get_kallsym() From 508cbd3a17d4d970abfa7415a785d28ad1df4e9e Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Wed, 19 Mar 2025 23:51:29 +0000 Subject: [PATCH 55/78] Fix function name --- 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 e998b55dd..b9ce84ed1 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -3090,7 +3090,7 @@ class kernel_symbol(objects.StructType): raise AttributeError("Unsupported kernel_symbol type implementation") - def _do_get_value(self) -> Optional[int]: + def get_value(self) -> Optional[int]: try: return self._do_get_value() except exceptions.InvalidAddressException: From 548657c309591b5592e0ea28e38bd29ee1eb991c Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Thu, 20 Mar 2025 15:02:22 +0000 Subject: [PATCH 56/78] Change None check to remove False booleans --- volatility3/framework/plugins/linux/kallsyms.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/kallsyms.py b/volatility3/framework/plugins/linux/kallsyms.py index 54f1adc70..c8bca03f7 100644 --- a/volatility3/framework/plugins/linux/kallsyms.py +++ b/volatility3/framework/plugins/linux/kallsyms.py @@ -118,11 +118,17 @@ class Kallsyms(plugins.PluginInterface): # 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) + + if kassymbol.exported is None: + exported = renderers.NotAvailableValue() + else: + exported = kassymbol.exported + fields = ( format_hints.Hex(kassymbol.address), kassymbol.type or renderers.NotAvailableValue(), symbol_size, - kassymbol.exported or renderers.NotAvailableValue(), + exported, kassymbol.subsystem, kassymbol.module_name, kassymbol.name, From 7b9fb916722a0678b666be0f02dd59b13c769951 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Thu, 20 Mar 2025 14:23:24 -0500 Subject: [PATCH 57/78] Objects: create `get_raw_value()` method for Pointer This creates a `get_raw_value()` method for the `Pointer` class that allows users to access the raw (unmasked) value of a pointer. This was required in order to decode the encoded `Dpc` pointer that is part of the `_KTIMER` Windows type. Addition of this type was favored over a cast to `unsigned long` or `unsigned long long` due to the potential for future instability of this type due to compiler changes. See https://github.com/volatilityfoundation/volatility3/issues/1041 for further discussion around the conversion of `log unsigned int` to `unsigned long` in `clang`. See https://github.com/volatilityfoundation/volatility3/pull/1177#discussion_r1650049299 for the original discussion around how to access this pointer in the `Timers` plugin. --- volatility3/framework/objects/__init__.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/volatility3/framework/objects/__init__.py b/volatility3/framework/objects/__init__.py index 39ce6f59f..9dd30db63 100644 --- a/volatility3/framework/objects/__init__.py +++ b/volatility3/framework/objects/__init__.py @@ -410,6 +410,19 @@ class Pointer(Integer): value = int.from_bytes(data, byteorder=endian, signed=signed) return value & mask + def get_raw_value(self) -> int: + formats = { + 4: "I", + 8: "Q", + } + length = self.vol.data_format.length + endian = self.vol.data_format.byteorder + raw_data = self._context.layers[self.vol.layer_name].read( + self.vol.offset, length + ) + struct_format = ("<" if endian == "little" else ">") + formats[length] + return struct.unpack(struct_format, raw_data)[0] + def dereference( self, layer_name: Optional[str] = None ) -> interfaces.objects.ObjectInterface: From a8ea3aae011827b174760ecfa05de42a49e33fca Mon Sep 17 00:00:00 2001 From: David McDonald Date: Thu, 20 Mar 2025 14:32:04 -0500 Subject: [PATCH 58/78] Extensions: Removes the `get_raw_dpc` method from `KTIMER` This removes the `get_raw_dpc` method from the `KTIMER` extension class. This method was inaccurate in that it actually returns the masked pointer value instead of the full 64-bit value encoded in that member, which is required in order to correctly decode the 'real' pointer. The invocation of `get_raw_dpc()` was replaced with `self.Dpc.get_raw_value()`, which was added in the previous commit. --- .../framework/symbols/windows/extensions/__init__.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 091d6ceb5..75608cfc6 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -1220,15 +1220,6 @@ class KTIMER(objects.StructType): return "Yes" return "-" - def get_raw_dpc(self): - """Returns the encoded DPC as an unsigned long long since the pointer is actually encoded""" - if symbols.symbol_table_is_64bit( - context=self._context, symbol_table_name=self.get_symbol_table_name() - ): - return self.Dpc.cast("unsigned long long") - else: - return self.Dpc.cast("unsigned long") - def valid_type(self): return self.Header.Type in self.VALID_TYPES @@ -1263,7 +1254,7 @@ class KTIMER(objects.StructType): ) low_byte = (wait_never) & 0xFF - entry = utility.rol(self.get_raw_dpc() ^ wait_never, low_byte) + entry = utility.rol(self.Dpc.get_raw_value() ^ wait_never, low_byte) swap_xor = self._context.layers[self.vol.native_layer_name].canonicalize( self.vol.offset ) From 144fd3139ae18a4aa785c5e4df3c323b24a67692 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Thu, 20 Mar 2025 14:38:19 -0500 Subject: [PATCH 59/78] Framework: Minor version bump Made an additive change to `Pointer` by adding the `get_raw_value()` method, so bumping the minor version here. The `get_raw_dpc()` method was removed from the `KTIMER` extension class, which is currently unversioned. --- 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 1ea59c068..f5da4c75b 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 = 24 # Number of changes that only add to the interface +VERSION_MINOR = 25 # Number of changes that only add to the interface VERSION_PATCH = 0 # Number of changes that do not change the interface VERSION_SUFFIX = "" From 1e175b5d3bf25dbc674bc2bda800c82b737cca70 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Thu, 20 Mar 2025 15:11:37 -0500 Subject: [PATCH 60/78] Objects: rework new `get_raw_value()` method Per code review recommendations, splits the `_unmarshall` classmethod into two components, one of which retrieves the raw value, and the other that returns the masked pointer. The `get_raw_value` method now calls the `_get_raw_value` classmethod using its instance information. --- volatility3/framework/objects/__init__.py | 35 ++++++++++++++--------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/volatility3/framework/objects/__init__.py b/volatility3/framework/objects/__init__.py index 9dd30db63..b863e103b 100644 --- a/volatility3/framework/objects/__init__.py +++ b/volatility3/framework/objects/__init__.py @@ -402,26 +402,35 @@ class Pointer(Integer): pointer should be recast. The "pointer" must always live within the space (even if the data provided is invalid). """ + mask = context.layers[object_info.native_layer_name].address_mask + new = ( + cls._get_raw_value( + context, data_format, object_info.layer_name, object_info.offset + ) + & mask + ) + return new + + @classmethod + def _get_raw_value( + cls, + context: interfaces.context.ContextInterface, + data_format: DataFormatInfo, + layer_name: str, + offset: int, + ) -> int: length, endian, signed = data_format if signed: raise ValueError("Pointers cannot have signed values") - mask = context.layers[object_info.native_layer_name].address_mask - data = context.layers.read(object_info.layer_name, object_info.offset, length) + data = context.layers.read(layer_name, offset, length) value = int.from_bytes(data, byteorder=endian, signed=signed) - return value & mask + return value def get_raw_value(self) -> int: - formats = { - 4: "I", - 8: "Q", - } - length = self.vol.data_format.length - endian = self.vol.data_format.byteorder - raw_data = self._context.layers[self.vol.layer_name].read( - self.vol.offset, length + raw = self._get_raw_value( + self._context, self.vol.data_format, self.vol.layer_name, self.vol.offset ) - struct_format = ("<" if endian == "little" else ">") + formats[length] - return struct.unpack(struct_format, raw_data)[0] + return raw def dereference( self, layer_name: Optional[str] = None From c4589a51d51d441838812f32ef1a97b9c328d8dc Mon Sep 17 00:00:00 2001 From: David McDonald Date: Thu, 20 Mar 2025 15:13:26 -0500 Subject: [PATCH 61/78] Timers: Adds debug log statement to catch-all exception --- volatility3/framework/plugins/windows/timers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/windows/timers.py b/volatility3/framework/plugins/windows/timers.py index 1f100bf1c..07313c004 100644 --- a/volatility3/framework/plugins/windows/timers.py +++ b/volatility3/framework/plugins/windows/timers.py @@ -131,6 +131,7 @@ class Timers(interfaces.plugins.PluginInterface): ): if not timer.valid_type(): continue + try: dpc = timer.get_dpc() if dpc == 0: @@ -138,7 +139,10 @@ class Timers(interfaces.plugins.PluginInterface): if dpc.DeferredRoutine == 0: continue deferred_routine = dpc.DeferredRoutine - except Exception: + except Exception as exc: + vollog.debug( + f"Failed to get _KTIMER.Dpc: {exc.__class__.__name__} {str(exc)}" + ) continue module_symbols = list( From d097d6abeb278de346601c6ee7570aa5383bbb73 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Thu, 20 Mar 2025 15:18:08 -0500 Subject: [PATCH 62/78] Timers: convert general Exception to InvalidAddressException --- volatility3/framework/plugins/windows/timers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/windows/timers.py b/volatility3/framework/plugins/windows/timers.py index 07313c004..f530a4c7b 100644 --- a/volatility3/framework/plugins/windows/timers.py +++ b/volatility3/framework/plugins/windows/timers.py @@ -11,6 +11,7 @@ from volatility3.framework import ( interfaces, constants, symbols, + exceptions, ) from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints @@ -139,9 +140,9 @@ class Timers(interfaces.plugins.PluginInterface): if dpc.DeferredRoutine == 0: continue deferred_routine = dpc.DeferredRoutine - except Exception as exc: + except exceptions.InvalidAddressException as exc: vollog.debug( - f"Failed to get _KTIMER.Dpc: {exc.__class__.__name__} {str(exc)}" + f"Failed to get _KTIMER.Dpc due to {exc.__class__.__name__}" ) continue From 6763031df87d7f830117c593d9132b60b781cb4a Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 21 Mar 2025 20:28:23 +0000 Subject: [PATCH 63/78] Add performance event plugin to detect eBPF malware --- .../plugins/linux/tracing/perf_events.py | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 volatility3/framework/plugins/linux/tracing/perf_events.py diff --git a/volatility3/framework/plugins/linux/tracing/perf_events.py b/volatility3/framework/plugins/linux/tracing/perf_events.py new file mode 100644 index 000000000..5d629bd21 --- /dev/null +++ b/volatility3/framework/plugins/linux/tracing/perf_events.py @@ -0,0 +1,138 @@ +# 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, Tuple, Generator, Optional + +from volatility3.framework import renderers, interfaces, constants, exceptions +from volatility3.framework.renderers import format_hints +from volatility3.framework.configuration import requirements +from volatility3.framework.interfaces import plugins +from volatility3.framework.objects import utility +from volatility3.plugins.linux import pslist + +vollog = logging.getLogger(__name__) + + +class PerfEvents(plugins.PluginInterface): + """Lists performance events for each process.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.PluginRequirement( + name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + ), + ] + + @classmethod + def list_perf_events(cls, context, vmlinux_module_name: str) -> Generator[ + Tuple[ + interfaces.objects.ObjectInterface, + interfaces.objects.ObjectInterface, + Optional[str], + Optional[str], + Optional[str], + Optional[int], + ], + None, + None, + ]: + """ + Walks the `perf_event_list` of each `task_struct` and reports valid event structures found + This plugin is one of several to detect eBPF based malware + + Args: + context: + vmlinux_module_name: + + Returns: + A tuple of the task struct, performance event object, event name, program name, full name, and program address + """ + vmlinux = context.modules[vmlinux_module_name] + + if not vmlinux.has_type("perf_event") or not vmlinux.get_type( + "perf_event" + ).has_member("owner_entry"): + vollog.warning( + "This kernel does not have performance events enabled (CONFIG_PERF_EVENTS). Cannot proceed." + ) + return + + for task in pslist.PsList.list_tasks( + context, vmlinux_module_name, include_threads=True + ): + + # walk the list of perf_event entries for this process + for event in task.perf_event_list.to_list( + vmlinux.symbol_table_name + constants.BANG + "perf_event", "owner_entry" + ): + # if the names are smeared then bail + try: + event_name = utility.pointer_to_string(event.pmu.name, count=64) + try: + full_name = utility.array_to_string( + event.prog.aux.ksym.name, count=512 + ) + except AttributeError: + full_name = renderers.NotApplicableValue() + + program_name = utility.array_to_string(event.prog.aux.name) + except exceptions.InvalidAddressException: + continue + + # if the kernel has the prog member then ensure it is not 0 + if hasattr(event, "prog"): + program_address = event.prog + if program_address == 0: + continue + + program_address = format_hints.Hex(program_address) + + else: + program_address = renderers.NotAvailableValue() + + yield task, event_name, program_name, full_name, program_address + + def _generator(self): + for ( + task, + event_name, + program_name, + full_name, + program_address, + ) in self.list_perf_events(self.context, self.config["kernel"]): + task_name = utility.array_to_string(task.comm) + + yield ( + 0, + ( + task.pid, + task_name, + event_name, + program_name, + full_name, + program_address, + ), + ) + + def run(self) -> renderers.TreeGrid: + return renderers.TreeGrid( + [ + ("PID", int), + ("Process", str), + ("Event", str), + ("Short Program Name", str), + ("Full Name", str), + ("Address", format_hints.Hex), + ], + self._generator(), + ) From bb39081e33ba1991233a0093a6748af9d19f6636 Mon Sep 17 00:00:00 2001 From: Odysseas Stavrou Date: Sun, 23 Mar 2025 22:29:16 +0200 Subject: [PATCH 64/78] Volshell: Add byteorder argument for display_* functions --- volatility3/cli/volshell/generic.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 143e26500..71b2173c6 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -310,23 +310,25 @@ class Volshell(interfaces.plugins.PluginInterface): self._display_data(offset, remaining_data) def display_quadwords( - self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None + self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None, byteorder="@" ): """Displays quad-word values (8 bytes) and corresponding ASCII characters""" remaining_data = self._read_data(offset, count=count, layer_name=layer_name) - self._display_data(offset, remaining_data, format_string="Q") + self._display_data(offset, remaining_data, format_string=f"{byteorder}Q") def display_doublewords( - self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None + self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None, byteorder="@" ): """Displays double-word values (4 bytes) and corresponding ASCII characters""" remaining_data = self._read_data(offset, count=count, layer_name=layer_name) - self._display_data(offset, remaining_data, format_string="I") + self._display_data(offset, remaining_data, format_string=f"{byteorder}I") - def display_words(self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None): + def display_words( + self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None, byteorder="@" + ): """Displays word values (2 bytes) and corresponding ASCII characters""" remaining_data = self._read_data(offset, count=count, layer_name=layer_name) - self._display_data(offset, remaining_data, format_string="H") + self._display_data(offset, remaining_data, format_string=f"{byteorder}H") def regex_scan(self, pattern, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None): """Scans for regex pattern in layer using RegExScanner.""" From 57c07631b057fac49bedd9fb8f8c3c5c063be868 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sun, 23 Mar 2025 21:23:42 -0500 Subject: [PATCH 65/78] Add win32 start address listing. Add paths for both thread starting address types --- .../plugins/windows/orphan_kernel_threads.py | 2 +- .../framework/plugins/windows/psxview.py | 2 +- .../plugins/windows/suspicious_threads.py | 6 +- .../framework/plugins/windows/thrdscan.py | 85 +++++++++++++++++-- .../framework/plugins/windows/threads.py | 2 +- 5 files changed, 85 insertions(+), 12 deletions(-) diff --git a/volatility3/framework/plugins/windows/orphan_kernel_threads.py b/volatility3/framework/plugins/windows/orphan_kernel_threads.py index 151fe88c9..0f556dd1e 100644 --- a/volatility3/framework/plugins/windows/orphan_kernel_threads.py +++ b/volatility3/framework/plugins/windows/orphan_kernel_threads.py @@ -34,7 +34,7 @@ class Threads(thrdscan.ThrdScan): architectures=["Intel32", "Intel64"], ), requirements.PluginRequirement( - name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 1, 0) + name="thrdscan", plugin=thrdscan.ThrdScan, version=(2, 0, 0) ), requirements.PluginRequirement( name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0) diff --git a/volatility3/framework/plugins/windows/psxview.py b/volatility3/framework/plugins/windows/psxview.py index 7329588cc..7c3444f70 100644 --- a/volatility3/framework/plugins/windows/psxview.py +++ b/volatility3/framework/plugins/windows/psxview.py @@ -55,7 +55,7 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter name="psscan", component=psscan.PsScan, version=(2, 0, 0) ), requirements.VersionRequirement( - name="thrdscan", component=thrdscan.ThrdScan, version=(1, 0, 0) + name="thrdscan", component=thrdscan.ThrdScan, version=(2, 0, 0) ), requirements.VersionRequirement( name="handles", component=handles.Handles, version=(3, 0, 0) diff --git a/volatility3/framework/plugins/windows/suspicious_threads.py b/volatility3/framework/plugins/windows/suspicious_threads.py index c98b06792..eabc637c8 100644 --- a/volatility3/framework/plugins/windows/suspicious_threads.py +++ b/volatility3/framework/plugins/windows/suspicious_threads.py @@ -35,7 +35,7 @@ class SuspiciousThreads(interfaces.plugins.PluginInterface): optional=True, ), requirements.VersionRequirement( - name="thrdscan", component=thrdscan.ThrdScan, version=(1, 1, 0) + name="thrdscan", component=thrdscan.ThrdScan, version=(2, 0, 0) ), requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(3, 0, 0) @@ -181,11 +181,11 @@ class SuspiciousThreads(interfaces.plugins.PluginInterface): if not info: continue - _, _, tid, start_address, _, _ = info + _, _, tid, start_address, _, win32_start_address, _, _, _ = info addresses = [ (start_address, "Start"), - (thread.Win32StartAddress, "Win32Start"), + (win32_start_address, "Win32Start"), ] for address, context in addresses: diff --git a/volatility3/framework/plugins/windows/thrdscan.py b/volatility3/framework/plugins/windows/thrdscan.py index 369db1fd8..38fba1ff4 100644 --- a/volatility3/framework/plugins/windows/thrdscan.py +++ b/volatility3/framework/plugins/windows/thrdscan.py @@ -3,12 +3,12 @@ ## import logging import datetime -from typing import Callable, Iterable +from typing import Callable, Iterable, Tuple, Optional, Dict from volatility3.framework import renderers, interfaces, exceptions from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints -from volatility3.plugins.windows import poolscanner +from volatility3.plugins.windows import poolscanner, pe_symbols from volatility3.plugins import timeliner vollog = logging.getLogger(__name__) @@ -19,7 +19,7 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) # version 2.6.0 adds support for scanning for 'Ethread' structures by pool tags _required_framework_version = (2, 6, 0) - _version = (1, 1, 0) + _version = (2, 0, 0) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -67,27 +67,74 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) yield mem_object @classmethod - def gather_thread_info(cls, ethread): + def gather_thread_info( + cls, + ethread: interfaces.objects.ObjectInterface, + vads_cache: Dict[int, pe_symbols.ranges_type] = None, + ) -> Tuple[ + int, + int, + int, + int, + Optional[str], + int, + Optional[str], + Optional[datetime.datetime], + Optional[datetime.datetime], + ]: try: thread_offset = ethread.vol.offset owner_proc_pid = ethread.Cid.UniqueProcess thread_tid = ethread.Cid.UniqueThread thread_start_addr = ethread.StartAddress + thread_win32start_addr = ethread.Win32StartAddress thread_create_time = ( ethread.get_create_time() ) # datetime.datetime object / volatility3.framework.renderers.UnparsableValue object thread_exit_time = ( ethread.get_exit_time() ) # datetime.datetime object / volatility3.framework.renderers.UnparsableValue object + + owner_proc = None + if vads_cache is not None: + owner_proc = ethread.owning_process() except exceptions.InvalidAddressException: vollog.debug(f"Thread invalid address {ethread.vol.offset:#x}") return None + if vads_cache is not None: + vads = pe_symbols.PESymbols.get_vads_for_process_cache( + vads_cache, owner_proc + ) + # no vads = terminated/smeared, pid 4 = kernel = don't check VADs + if ( + owner_proc_pid != 4 + and owner_proc.InheritedFromUniqueProcessId != 4 + and (not vads or len(vads) < 5) + ): + vollog.debug( + f"No vads for process at {owner_proc.vol.offset:#x}. Skipping thread at {ethread.vol.offset:#x}" + ) + return None + + start_path = pe_symbols.PESymbols.filepath_for_address( + vads, thread_start_addr + ) + win32start_path = pe_symbols.PESymbols.filepath_for_address( + vads, thread_win32start_addr + ) + else: + start_path = None + win32start_path = None + return ( format_hints.Hex(thread_offset), owner_proc_pid, thread_tid, format_hints.Hex(thread_start_addr), + start_path, + format_hints.Hex(thread_win32start_addr), + win32start_path, thread_create_time, thread_exit_time, ) @@ -95,11 +142,34 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) def _generator(self, filter_func: Callable): kernel_name = self.config["kernel"] + vads_cache: Dict[int, pe_symbols.ranges_type] = {} + for ethread in self.implementation(self.context, kernel_name): - info = self.gather_thread_info(ethread) + info = self.gather_thread_info(ethread, vads_cache) if info: - yield (0, info) + ( + offset, + pid, + tid, + start_addr, + start_path, + win32start_addr, + win32start_path, + create_time, + exit_time, + ) = info + yield 0, ( + offset, + pid, + tid, + start_addr, + start_path or renderers.NotAvailableValue(), + win32start_addr, + win32start_path or renderers.NotAvailableValue(), + create_time, + exit_time, + ) def generate_timeline(self): filt_func = self.filter_func(self.config) @@ -145,6 +215,9 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) ("PID", int), ("TID", int), ("StartAddress", format_hints.Hex), + ("StartPath", str), + ("Win32StartAddress", format_hints.Hex), + ("Win32StartPath", str), ("CreateTime", datetime.datetime), ("ExitTime", datetime.datetime), ], diff --git a/volatility3/framework/plugins/windows/threads.py b/volatility3/framework/plugins/windows/threads.py index 77062e8c6..d0bb26e2a 100644 --- a/volatility3/framework/plugins/windows/threads.py +++ b/volatility3/framework/plugins/windows/threads.py @@ -32,7 +32,7 @@ class Threads(thrdscan.ThrdScan): architectures=["Intel32", "Intel64"], ), requirements.PluginRequirement( - name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 1, 0) + name="thrdscan", plugin=thrdscan.ThrdScan, version=(2, 0, 0) ), ] From e3d35aa425fe08c4ae5566b5843ec92ee05c841c Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 24 Mar 2025 02:49:31 +0000 Subject: [PATCH 66/78] update from feedback --- .../plugins/linux/tracing/perf_events.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/volatility3/framework/plugins/linux/tracing/perf_events.py b/volatility3/framework/plugins/linux/tracing/perf_events.py index 5d629bd21..3e0a40579 100644 --- a/volatility3/framework/plugins/linux/tracing/perf_events.py +++ b/volatility3/framework/plugins/linux/tracing/perf_events.py @@ -83,7 +83,7 @@ class PerfEvents(plugins.PluginInterface): event.prog.aux.ksym.name, count=512 ) except AttributeError: - full_name = renderers.NotApplicableValue() + full_name = None program_name = utility.array_to_string(event.prog.aux.name) except exceptions.InvalidAddressException: @@ -95,10 +95,8 @@ class PerfEvents(plugins.PluginInterface): if program_address == 0: continue - program_address = format_hints.Hex(program_address) - else: - program_address = renderers.NotAvailableValue() + program_address = None yield task, event_name, program_name, full_name, program_address @@ -112,14 +110,23 @@ class PerfEvents(plugins.PluginInterface): ) in self.list_perf_events(self.context, self.config["kernel"]): task_name = utility.array_to_string(task.comm) + # We at least need one useful string... + if event_name is None and program_name is None and full_name is None: + continue + + if program_address is not None: + program_address = format_hints.Hex(program_address) + else: + program_address = renderers.NotAvailableValue() + yield ( 0, ( task.pid, task_name, - event_name, - program_name, - full_name, + event_name or renderers.NotAvailableValue(), + program_name or renderers.NotAvailableValue(), + full_name or renderers.NotAvailableValue(), program_address, ), ) From 36b66f00fe27476d75abffe635f1ddece3ad7c5d Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 24 Mar 2025 05:05:08 +0000 Subject: [PATCH 67/78] Add delete on close detection to process ghosting. Update plugin to current coding flow --- .../plugins/windows/processghosting.py | 193 ++++++++++++++---- 1 file changed, 154 insertions(+), 39 deletions(-) diff --git a/volatility3/framework/plugins/windows/processghosting.py b/volatility3/framework/plugins/windows/processghosting.py index 5bc6bc5a3..28af57053 100644 --- a/volatility3/framework/plugins/windows/processghosting.py +++ b/volatility3/framework/plugins/windows/processghosting.py @@ -2,21 +2,23 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # import logging -import contextlib + +from typing import Optional, Tuple, Generator, Dict from volatility3.framework import interfaces, exceptions from volatility3.framework import renderers from volatility3.framework.configuration import requirements from volatility3.framework.objects import utility from volatility3.framework.renderers import format_hints -from volatility3.plugins.windows import pslist +from volatility3.plugins.windows import pslist, vadinfo vollog = logging.getLogger(__name__) class ProcessGhosting(interfaces.plugins.PluginInterface): - """Lists processes whose DeletePending bit is set or whose FILE_OBJECT is set to 0""" + """Lists processes whose DeletePending bit is set or whose FILE_OBJECT is set to 0 or Vads that are DeleteOnClose""" + _version = (1, 0, 0) _required_framework_version = (2, 4, 0) @classmethod @@ -33,52 +35,163 @@ class ProcessGhosting(interfaces.plugins.PluginInterface): ), ] + @classmethod + def _process_checks( + cls, + proc: interfaces.objects.ObjectInterface, + mapped_files: Dict[int, Tuple[str, interfaces.objects.ObjectInterface]], + ) -> Generator[ + Tuple[int, Optional[int], Optional[int], int, Optional[str]], None, None + ]: + """ + Checks the EPROCESS for signs of ghosting + """ + if not proc.has_member("ImageFilePointer"): + return + + delete_pending = None + + # if it is 0 then its a side effect of process ghosting + if proc.ImageFilePointer.vol.offset != 0: + try: + file_object = proc.ImageFilePointer + delete_pending = file_object.DeletePending + file_object = file_object.dereference().vol.offset + except exceptions.InvalidAddressException: + file_object = 0 + + # ImageFilePointer equal to 0 means process ghosting or similar techniques were used + else: + file_object = 0 + + # delete_pending besides 0 or 1 = smear + if isinstance(delete_pending, int) and delete_pending not in [0, 1]: + vollog.debug( + f"Invalid delete_pending value {delete_pending} found for process {proc.UniqueProcessId}" + ) + delete_pending = None + + if file_object == 0 or delete_pending == 1: + yield file_object, delete_pending, None, proc.SectionBaseAddress + + @classmethod + def _vad_checks( + cls, control_area: interfaces.objects.ObjectInterface, vad_path: str + ) -> Generator[Tuple[int, Optional[int], Optional[int]], None, None]: + """ + Checks the control area for delete on close or delete pending being set + """ + try: + file_object = control_area.FilePointer.dereference().cast("_FILE_OBJECT") + except exceptions.InvalidAddressException: + return + + try: + delete_on_close = control_area.u.Flags.DeleteOnClose + except exceptions.InvalidAddressException: + delete_on_close = None + + if delete_on_close and vad_path.lower().endswith((".exe", ".dll")): + yield file_object.vol.offset, None, delete_on_close + + try: + delete_pending = file_object.DeletePending + except exceptions.InvalidAddressException: + delete_pending = None + + if delete_pending and delete_pending == 1: + yield file_object.vol.offset, delete_pending, None + + @classmethod + def check_for_ghosting( + cls, + proc: interfaces.objects.ObjectInterface, + mapped_files: Dict[int, Tuple[str, interfaces.objects.ObjectInterface]], + ) -> Generator[ + Tuple[int, Optional[int], Optional[int], int, Optional[str]], None, None + ]: + """ + Returns process or vad info for ghosting files + + Args: + proc: + mapped_files: A dictionary mapping vad base addreses to the path and vad instance for the process + + Return: + A Generator of tuples of the file object address, the delete pending state, delete on close state, base address of the VAD, and the path + """ + # check the direct file object of the process + yield from cls._process_checks(proc, mapped_files) + + # walk each vad, check if it is pending delete or has its delete on close bit set + for vad_base, (path, vad) in mapped_files.items(): + # these checks have no meaning for private memory areas + if vad.get_private_memory() == 1: + continue + + try: + if vad.has_member("ControlArea"): + control_area = vad.ControlArea + elif vad.has_member("Subsection"): + control_area = vad.Subsection.ControlArea + # We got here from a short vad, likely smear + else: + continue + except exceptions.InvalidAddressException: + vollog.debug( + f"Unable to get control area for vad at base {vad_base:#x} for process with pid {proc.UniqueProcessId}" + ) + continue + + for file_object_address, delete_pending, delete_on_close in cls._vad_checks( + control_area, path + ): + yield format_hints.Hex( + file_object_address + ), delete_pending, delete_on_close, vad_base + def _generator(self, procs): kernel = self.context.modules[self.config["kernel"]] - if not kernel.get_type("_EPROCESS").has_member("ImageFilePointer"): + has_imagefilepointer = kernel.get_type("_EPROCESS").has_member( + "ImageFilePointer" + ) + if not has_imagefilepointer: vollog.warning( - "This plugin only supports Windows 10 builds when the ImageFilePointer member of _EPROCESS is present" + "ImageFilePointer checks are only supported on Windows 10 builds when the ImageFilePointer member of _EPROCESS is present" ) - return for proc in procs: - delete_pending = renderers.UnreadableValue() process_name = utility.array_to_string(proc.ImageFileName) + pid = proc.UniqueProcessId - # if it is 0 then its a side effect of process ghosting - if proc.ImageFilePointer.vol.offset != 0: - try: - file_object = proc.ImageFilePointer - delete_pending = file_object.DeletePending - except exceptions.InvalidAddressException: - file_object = 0 + # base address -> (file path, VAD instance) + mapped_files: Dict[int, Tuple[str, interfaces.objects.ObjectInterface]] = {} + for vad in vadinfo.VadInfo.list_vads(proc): + path = vad.get_file_name() + if isinstance(path, str): + mapped_files[vad.get_start()] = (path, vad) - # ImageFilePointer equal to 0 means process ghosting or similar techniques were used - else: - file_object = 0 + for ( + file_object_address, + delete_pending, + delete_on_close, + base_address, + ) in self.check_for_ghosting(proc, mapped_files): + vad_info = mapped_files.get(base_address) + if vad_info: + path = vad_info[0] + else: + path = renderers.NotAvailableValue() - if isinstance(delete_pending, int) and delete_pending not in [0, 1]: - vollog.debug( - f"Invalid delete_pending value {delete_pending} found for {process_name} {proc.UniqueProcessId}" - ) - - # delete_pending besides 0 or 1 = smear - if file_object == 0 or delete_pending == 1: - path = renderers.UnreadableValue() - if file_object: - with contextlib.suppress(exceptions.InvalidAddressException): - path = file_object.FileName.String - - yield ( - 0, - ( - proc.UniqueProcessId, - process_name, - format_hints.Hex(file_object), - delete_pending, - path, - ), + yield 0, ( + pid, + process_name, + format_hints.Hex(file_object_address), + delete_pending or renderers.NotApplicableValue(), + delete_on_close or renderers.NotApplicableValue(), + format_hints.Hex(base_address), + path, ) def run(self): @@ -89,7 +202,9 @@ class ProcessGhosting(interfaces.plugins.PluginInterface): ("PID", int), ("Process", str), ("FILE_OBJECT", format_hints.Hex), - ("DeletePending", str), + ("DeletePending", int), + ("DeleteOnClose", int), + ("Base", format_hints.Hex), ("Path", str), ], self._generator( From 003c139597b93b649b688181781d3eb326bb18bf Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 24 Mar 2025 15:10:37 +0000 Subject: [PATCH 68/78] Add a --script-only flag that exits after the given volshell script is completed --- volatility3/cli/volshell/generic.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index 143e26500..2f7c7662c 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -60,7 +60,13 @@ class Volshell(interfaces.plugins.PluginInterface): description="File to load and execute at start", default=None, optional=True, - ) + ), + requirements.BooleanRequirement( + name="script-only", + description="Exit volshell after the script specified in --script completes", + default=False, + optional=True, + ), ] return reqs + [ requirements.TranslationLayerRequirement( @@ -135,6 +141,9 @@ class Volshell(interfaces.plugins.PluginInterface): if self.config.get("script", None) is not None: self.run_script(location=self.config["script"]) + if self.config.get("script-only"): + exit() + if has_ipython: self.__console() else: From a6f9a0e95b3d8096a462894c8190479b2e293d52 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Mon, 24 Mar 2025 11:30:17 -0500 Subject: [PATCH 69/78] Framework: Replace PluginRequirements This replaces all uses of `requirements.PluginRequirements` with `requirements.VersionRequirement`. --- doc/source/simple-plugin.rst | 4 ++-- volatility3/cli/volshell/linux.py | 4 ++-- volatility3/cli/volshell/mac.py | 4 ++-- volatility3/cli/volshell/windows.py | 4 ++-- volatility3/framework/plugins/linux/bash.py | 4 ++-- volatility3/framework/plugins/linux/boottime.py | 4 ++-- .../framework/plugins/linux/capabilities.py | 4 ++-- .../framework/plugins/linux/check_creds.py | 4 ++-- volatility3/framework/plugins/linux/elfs.py | 4 ++-- volatility3/framework/plugins/linux/envars.py | 4 ++-- volatility3/framework/plugins/linux/kthreads.py | 4 ++-- .../framework/plugins/linux/library_list.py | 4 ++-- volatility3/framework/plugins/linux/lsof.py | 4 ++-- volatility3/framework/plugins/linux/malfind.py | 4 ++-- volatility3/framework/plugins/linux/mountinfo.py | 4 ++-- volatility3/framework/plugins/linux/pagecache.py | 16 ++++++++-------- .../framework/plugins/linux/pidhashtable.py | 4 ++-- volatility3/framework/plugins/linux/proc.py | 4 ++-- volatility3/framework/plugins/linux/psaux.py | 4 ++-- .../framework/plugins/linux/pscallstack.py | 4 ++-- volatility3/framework/plugins/linux/pslist.py | 4 ++-- volatility3/framework/plugins/linux/psscan.py | 4 ++-- volatility3/framework/plugins/linux/pstree.py | 4 ++-- volatility3/framework/plugins/linux/ptrace.py | 4 ++-- volatility3/framework/plugins/linux/sockstat.py | 8 ++++---- .../framework/plugins/linux/vmaregexscan.py | 4 ++-- .../framework/plugins/linux/vmayarascan.py | 8 ++++---- volatility3/framework/plugins/mac/bash.py | 4 ++-- .../framework/plugins/mac/check_syscall.py | 4 ++-- .../framework/plugins/mac/check_sysctl.py | 4 ++-- .../framework/plugins/mac/check_trap_table.py | 4 ++-- .../framework/plugins/mac/kauth_listeners.py | 10 ++++++---- .../framework/plugins/mac/kauth_scopes.py | 4 ++-- volatility3/framework/plugins/mac/kevents.py | 4 ++-- volatility3/framework/plugins/mac/list_files.py | 4 ++-- volatility3/framework/plugins/mac/lsof.py | 4 ++-- volatility3/framework/plugins/mac/malfind.py | 4 ++-- volatility3/framework/plugins/mac/netstat.py | 4 ++-- volatility3/framework/plugins/mac/proc_maps.py | 4 ++-- volatility3/framework/plugins/mac/psaux.py | 4 ++-- volatility3/framework/plugins/mac/pstree.py | 4 ++-- .../framework/plugins/mac/socket_filters.py | 4 ++-- volatility3/framework/plugins/mac/trustedbsd.py | 4 ++-- volatility3/framework/plugins/windows/amcache.py | 4 ++-- .../framework/plugins/windows/cachedump.py | 12 ++++++------ .../framework/plugins/windows/callbacks.py | 16 ++++++++-------- volatility3/framework/plugins/windows/cmdline.py | 4 ++-- volatility3/framework/plugins/windows/cmdscan.py | 4 ++-- .../framework/plugins/windows/consoles.py | 4 ++-- .../framework/plugins/windows/deskscan.py | 8 ++++---- .../framework/plugins/windows/desktops.py | 4 ++-- .../framework/plugins/windows/devicetree.py | 4 ++-- .../plugins/windows/direct_system_calls.py | 8 ++++---- .../framework/plugins/windows/driverirp.py | 12 ++++++------ .../framework/plugins/windows/drivermodule.py | 12 ++++++------ .../framework/plugins/windows/driverscan.py | 4 ++-- volatility3/framework/plugins/windows/envars.py | 8 ++++---- .../framework/plugins/windows/filescan.py | 4 ++-- .../framework/plugins/windows/getservicesids.py | 4 ++-- volatility3/framework/plugins/windows/getsids.py | 8 ++++---- volatility3/framework/plugins/windows/handles.py | 4 ++-- .../framework/plugins/windows/hashdump.py | 4 ++-- .../plugins/windows/indirect_system_calls.py | 8 ++++---- volatility3/framework/plugins/windows/memmap.py | 4 ++-- volatility3/framework/plugins/windows/mftscan.py | 8 ++++---- .../framework/plugins/windows/mutantscan.py | 4 ++-- .../plugins/windows/orphan_kernel_threads.py | 12 ++++++------ .../framework/plugins/windows/poolscanner.py | 4 ++-- .../framework/plugins/windows/privileges.py | 4 ++-- volatility3/framework/plugins/windows/psscan.py | 4 ++-- .../plugins/windows/registry/getcellroutine.py | 8 ++++---- .../plugins/windows/registry/hivelist.py | 4 ++-- .../plugins/windows/registry/hivescan.py | 8 ++++---- .../plugins/windows/registry/printkey.py | 4 ++-- .../plugins/windows/registry/userassist.py | 4 ++-- .../framework/plugins/windows/scheduled_tasks.py | 4 ++-- .../framework/plugins/windows/sessions.py | 4 ++-- .../framework/plugins/windows/shimcachemem.py | 4 ++-- volatility3/framework/plugins/windows/ssdt.py | 4 ++-- volatility3/framework/plugins/windows/strings.py | 4 ++-- volatility3/framework/plugins/windows/svclist.py | 4 ++-- volatility3/framework/plugins/windows/svcscan.py | 8 ++++---- .../framework/plugins/windows/thrdscan.py | 4 ++-- volatility3/framework/plugins/windows/threads.py | 4 ++-- .../plugins/windows/unhooked_system_calls.py | 4 ++-- volatility3/framework/plugins/windows/vadinfo.py | 4 ++-- .../framework/plugins/windows/vadregexscan.py | 4 ++-- volatility3/framework/plugins/windows/vadwalk.py | 8 ++++---- .../framework/plugins/windows/vadyarascan.py | 8 ++++---- volatility3/framework/plugins/windows/verinfo.py | 8 ++++---- .../plugins/windows/registry/certificates.py | 8 ++++---- 91 files changed, 244 insertions(+), 242 deletions(-) diff --git a/doc/source/simple-plugin.rst b/doc/source/simple-plugin.rst index a6916a027..d855e319d 100644 --- a/doc/source/simple-plugin.rst +++ b/doc/source/simple-plugin.rst @@ -53,9 +53,9 @@ to be able to run properly. Any that are defined as optional need not necessari description = "Process IDs to include (all other processes are excluded)", optional = True ), - requirements.PluginRequirement( + requirements.VersionRequirement( name = 'pslist', - plugin = pslist.PsList, + component = pslist.PsList, version = (2, 0, 0) ), ] diff --git a/volatility3/cli/volshell/linux.py b/volatility3/cli/volshell/linux.py index b3689c3ae..27c630614 100644 --- a/volatility3/cli/volshell/linux.py +++ b/volatility3/cli/volshell/linux.py @@ -30,8 +30,8 @@ class Volshell(generic.Volshell): requirements.ModuleRequirement( name="kernel", description="Linux kernel module" ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.IntRequirement( name="pid", description="Process ID", optional=True diff --git a/volatility3/cli/volshell/mac.py b/volatility3/cli/volshell/mac.py index 0ed35eb27..393eff20b 100644 --- a/volatility3/cli/volshell/mac.py +++ b/volatility3/cli/volshell/mac.py @@ -19,8 +19,8 @@ class Volshell(generic.Volshell): requirements.ModuleRequirement( name="kernel", description="Darwin kernel module" ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.IntRequirement( name="pid", description="Process ID", optional=True diff --git a/volatility3/cli/volshell/windows.py b/volatility3/cli/volshell/windows.py index c5bab3b74..ce5995648 100644 --- a/volatility3/cli/volshell/windows.py +++ b/volatility3/cli/volshell/windows.py @@ -17,8 +17,8 @@ class Volshell(generic.Volshell): def get_requirements(cls): return [ requirements.ModuleRequirement(name="kernel", description="Windows kernel"), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.IntRequirement( name="pid", description="Process ID", optional=True diff --git a/volatility3/framework/plugins/linux/bash.py b/volatility3/framework/plugins/linux/bash.py index 293c47224..fd73b4df2 100644 --- a/volatility3/framework/plugins/linux/bash.py +++ b/volatility3/framework/plugins/linux/bash.py @@ -32,8 +32,8 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/boottime.py b/volatility3/framework/plugins/linux/boottime.py index c57bdd65a..c1a75d478 100644 --- a/volatility3/framework/plugins/linux/boottime.py +++ b/volatility3/framework/plugins/linux/boottime.py @@ -25,8 +25,8 @@ class Boottime(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), ] diff --git a/volatility3/framework/plugins/linux/capabilities.py b/volatility3/framework/plugins/linux/capabilities.py index 1d0c60c11..dae6aac6a 100644 --- a/volatility3/framework/plugins/linux/capabilities.py +++ b/volatility3/framework/plugins/linux/capabilities.py @@ -60,8 +60,8 @@ class Capabilities(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pids", diff --git a/volatility3/framework/plugins/linux/check_creds.py b/volatility3/framework/plugins/linux/check_creds.py index 96f77ce4d..e2b84d679 100644 --- a/volatility3/framework/plugins/linux/check_creds.py +++ b/volatility3/framework/plugins/linux/check_creds.py @@ -22,8 +22,8 @@ class Check_creds(interfaces.plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), ] diff --git a/volatility3/framework/plugins/linux/elfs.py b/volatility3/framework/plugins/linux/elfs.py index b9dcc3cca..8b2759907 100644 --- a/volatility3/framework/plugins/linux/elfs.py +++ b/volatility3/framework/plugins/linux/elfs.py @@ -35,8 +35,8 @@ class Elfs(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/envars.py b/volatility3/framework/plugins/linux/envars.py index 0687caa9f..f4859cb49 100644 --- a/volatility3/framework/plugins/linux/envars.py +++ b/volatility3/framework/plugins/linux/envars.py @@ -29,8 +29,8 @@ class Envars(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/kthreads.py b/volatility3/framework/plugins/linux/kthreads.py index 06e94b221..4ed0e15b9 100644 --- a/volatility3/framework/plugins/linux/kthreads.py +++ b/volatility3/framework/plugins/linux/kthreads.py @@ -44,8 +44,8 @@ class Kthreads(plugins.PluginInterface): requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0) ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), ] diff --git a/volatility3/framework/plugins/linux/library_list.py b/volatility3/framework/plugins/linux/library_list.py index e251b5689..dedd77ade 100644 --- a/volatility3/framework/plugins/linux/library_list.py +++ b/volatility3/framework/plugins/linux/library_list.py @@ -31,8 +31,8 @@ class LibraryList(interfaces.plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pids", diff --git a/volatility3/framework/plugins/linux/lsof.py b/volatility3/framework/plugins/linux/lsof.py index 044e9238f..8e0143584 100644 --- a/volatility3/framework/plugins/linux/lsof.py +++ b/volatility3/framework/plugins/linux/lsof.py @@ -120,8 +120,8 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) diff --git a/volatility3/framework/plugins/linux/malfind.py b/volatility3/framework/plugins/linux/malfind.py index 8bbf3b89c..dad8b1f15 100644 --- a/volatility3/framework/plugins/linux/malfind.py +++ b/volatility3/framework/plugins/linux/malfind.py @@ -28,8 +28,8 @@ class Malfind(interfaces.plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/mountinfo.py b/volatility3/framework/plugins/linux/mountinfo.py index c56ced489..668b039db 100644 --- a/volatility3/framework/plugins/linux/mountinfo.py +++ b/volatility3/framework/plugins/linux/mountinfo.py @@ -46,8 +46,8 @@ class MountInfo(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 3d2db7fd7..879bc3288 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -126,8 +126,8 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface): description="Linux kernel", architectures=architectures.LINUX_ARCHS, ), - requirements.PluginRequirement( - name="mountinfo", plugin=mountinfo.MountInfo, version=(1, 2, 0) + requirements.VersionRequirement( + name="mountinfo", component=mountinfo.MountInfo, version=(1, 2, 0) ), requirements.ListRequirement( name="type", @@ -431,8 +431,8 @@ class InodePages(plugins.PluginInterface): description="Linux kernel", architectures=architectures.LINUX_ARCHS, ), - requirements.PluginRequirement( - name="files", plugin=Files, version=(1, 0, 0) + requirements.VersionRequirement( + name="files", component=Files, version=(1, 0, 0) ), requirements.StringRequirement( name="find", @@ -650,11 +650,11 @@ class RecoverFs(plugins.PluginInterface): description="Linux kernel", architectures=architectures.LINUX_ARCHS, ), - requirements.PluginRequirement( - name="files", plugin=Files, version=(1, 1, 0) + requirements.VersionRequirement( + name="files", component=Files, version=(1, 1, 0) ), - requirements.PluginRequirement( - name="inodepages", plugin=InodePages, version=(3, 0, 0) + requirements.VersionRequirement( + name="inodepages", component=InodePages, version=(3, 0, 0) ), requirements.BooleanRequirement( name="tmpfs_only", diff --git a/volatility3/framework/plugins/linux/pidhashtable.py b/volatility3/framework/plugins/linux/pidhashtable.py index 060b3928e..b4b1643e1 100644 --- a/volatility3/framework/plugins/linux/pidhashtable.py +++ b/volatility3/framework/plugins/linux/pidhashtable.py @@ -29,8 +29,8 @@ class PIDHashTable(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0) diff --git a/volatility3/framework/plugins/linux/proc.py b/volatility3/framework/plugins/linux/proc.py index 5acba6594..e9a126374 100644 --- a/volatility3/framework/plugins/linux/proc.py +++ b/volatility3/framework/plugins/linux/proc.py @@ -34,8 +34,8 @@ class Maps(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/psaux.py b/volatility3/framework/plugins/linux/psaux.py index 1a118dba6..e6653251c 100644 --- a/volatility3/framework/plugins/linux/psaux.py +++ b/volatility3/framework/plugins/linux/psaux.py @@ -26,8 +26,8 @@ class PsAux(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/pscallstack.py b/volatility3/framework/plugins/linux/pscallstack.py index 6d7a24942..c22e00161 100644 --- a/volatility3/framework/plugins/linux/pscallstack.py +++ b/volatility3/framework/plugins/linux/pscallstack.py @@ -45,8 +45,8 @@ class PsCallStack(plugins.PluginInterface): requirements.VersionRequirement( name="Kallsyms", component=kallsyms.Kallsyms, version=(1, 0, 0) ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/pslist.py b/volatility3/framework/plugins/linux/pslist.py index 4c42fc992..8296c82fa 100644 --- a/volatility3/framework/plugins/linux/pslist.py +++ b/volatility3/framework/plugins/linux/pslist.py @@ -44,8 +44,8 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="elfs", plugin=elfs.Elfs, version=(2, 0, 0) + requirements.VersionRequirement( + name="elfs", component=elfs.Elfs, version=(2, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/psscan.py b/volatility3/framework/plugins/linux/psscan.py index 6c4c5eb35..0813cebed 100644 --- a/volatility3/framework/plugins/linux/psscan.py +++ b/volatility3/framework/plugins/linux/psscan.py @@ -38,8 +38,8 @@ class PsScan(interfaces.plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), ] diff --git a/volatility3/framework/plugins/linux/pstree.py b/volatility3/framework/plugins/linux/pstree.py index c5290774b..e7bbdb8d5 100644 --- a/volatility3/framework/plugins/linux/pstree.py +++ b/volatility3/framework/plugins/linux/pstree.py @@ -27,8 +27,8 @@ class PsTree(interfaces.plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/ptrace.py b/volatility3/framework/plugins/linux/ptrace.py index 6493f22b9..356d5e72c 100644 --- a/volatility3/framework/plugins/linux/ptrace.py +++ b/volatility3/framework/plugins/linux/ptrace.py @@ -29,8 +29,8 @@ class Ptrace(plugins.PluginInterface): description="Linux kernel", architectures=architectures.LINUX_ARCHS, ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), ] diff --git a/volatility3/framework/plugins/linux/sockstat.py b/volatility3/framework/plugins/linux/sockstat.py index adbb5d6ea..da5d8cb8c 100644 --- a/volatility3/framework/plugins/linux/sockstat.py +++ b/volatility3/framework/plugins/linux/sockstat.py @@ -463,11 +463,11 @@ class Sockstat(plugins.PluginInterface): requirements.VersionRequirement( name="SockHandlers", component=SockHandlers, version=(4, 0, 0) ), - requirements.PluginRequirement( - name="lsof", plugin=lsof.Lsof, version=(2, 0, 0) + requirements.VersionRequirement( + name="lsof", component=lsof.Lsof, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) diff --git a/volatility3/framework/plugins/linux/vmaregexscan.py b/volatility3/framework/plugins/linux/vmaregexscan.py index 8fb96da1e..4c8ef5b8f 100644 --- a/volatility3/framework/plugins/linux/vmaregexscan.py +++ b/volatility3/framework/plugins/linux/vmaregexscan.py @@ -34,8 +34,8 @@ class VmaRegExScan(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py index e9e56dd0f..2f15a1e7e 100644 --- a/volatility3/framework/plugins/linux/vmayarascan.py +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -30,11 +30,11 @@ class VmaYaraScan(interfaces.plugins.PluginInterface): description="Process IDs to include (all other processes are excluded)", optional=True, ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) ), - requirements.PluginRequirement( - name="yarascan", plugin=yarascan.YaraScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="yarascan", component=yarascan.YaraScan, version=(2, 0, 0) ), requirements.VersionRequirement( name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) diff --git a/volatility3/framework/plugins/mac/bash.py b/volatility3/framework/plugins/mac/bash.py index 5be5e74d6..aca0aea0c 100644 --- a/volatility3/framework/plugins/mac/bash.py +++ b/volatility3/framework/plugins/mac/bash.py @@ -30,8 +30,8 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/mac/check_syscall.py b/volatility3/framework/plugins/mac/check_syscall.py index 5c22e6463..ed86b1a41 100644 --- a/volatility3/framework/plugins/mac/check_syscall.py +++ b/volatility3/framework/plugins/mac/check_syscall.py @@ -31,8 +31,8 @@ class Check_syscall(plugins.PluginInterface): requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 0, 0) ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="lsmod", component=lsmod.Lsmod, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/mac/check_sysctl.py b/volatility3/framework/plugins/mac/check_sysctl.py index ed3e34aea..d9c9a4dbd 100644 --- a/volatility3/framework/plugins/mac/check_sysctl.py +++ b/volatility3/framework/plugins/mac/check_sysctl.py @@ -33,8 +33,8 @@ class Check_sysctl(plugins.PluginInterface): requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 0, 0) ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="lsmod", component=lsmod.Lsmod, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/mac/check_trap_table.py b/volatility3/framework/plugins/mac/check_trap_table.py index 60f237208..6e0f4b8a9 100644 --- a/volatility3/framework/plugins/mac/check_trap_table.py +++ b/volatility3/framework/plugins/mac/check_trap_table.py @@ -29,8 +29,8 @@ class Check_trap_table(plugins.PluginInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="lsmod", component=lsmod.Lsmod, version=(2, 0, 0) ), requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 0, 0) diff --git a/volatility3/framework/plugins/mac/kauth_listeners.py b/volatility3/framework/plugins/mac/kauth_listeners.py index ed43bfb42..ca236c04a 100644 --- a/volatility3/framework/plugins/mac/kauth_listeners.py +++ b/volatility3/framework/plugins/mac/kauth_listeners.py @@ -26,11 +26,13 @@ class Kauth_listeners(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 1, 0) ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="lsmod", component=lsmod.Lsmod, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="kauth_scopes", plugin=kauth_scopes.Kauth_scopes, version=(2, 0, 0) + requirements.VersionRequirement( + name="kauth_scopes", + component=kauth_scopes.Kauth_scopes, + version=(2, 0, 0), ), ] diff --git a/volatility3/framework/plugins/mac/kauth_scopes.py b/volatility3/framework/plugins/mac/kauth_scopes.py index c2c473eac..6420d9955 100644 --- a/volatility3/framework/plugins/mac/kauth_scopes.py +++ b/volatility3/framework/plugins/mac/kauth_scopes.py @@ -31,8 +31,8 @@ class Kauth_scopes(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 1, 0) ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="lsmod", component=lsmod.Lsmod, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/mac/kevents.py b/volatility3/framework/plugins/mac/kevents.py index 41fde31ca..e36de8c84 100644 --- a/volatility3/framework/plugins/mac/kevents.py +++ b/volatility3/framework/plugins/mac/kevents.py @@ -71,8 +71,8 @@ class Kevents(interfaces.plugins.PluginInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 2, 0) diff --git a/volatility3/framework/plugins/mac/list_files.py b/volatility3/framework/plugins/mac/list_files.py index c18b0b7a2..bf3dcfce6 100644 --- a/volatility3/framework/plugins/mac/list_files.py +++ b/volatility3/framework/plugins/mac/list_files.py @@ -28,8 +28,8 @@ class List_Files(plugins.PluginInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="mount", plugin=mount.Mount, version=(2, 0, 0) + requirements.VersionRequirement( + name="mount", component=mount.Mount, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/mac/lsof.py b/volatility3/framework/plugins/mac/lsof.py index 6832b837f..3191aeff6 100644 --- a/volatility3/framework/plugins/mac/lsof.py +++ b/volatility3/framework/plugins/mac/lsof.py @@ -29,8 +29,8 @@ class Lsof(plugins.PluginInterface): requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 0, 0) ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/mac/malfind.py b/volatility3/framework/plugins/mac/malfind.py index 3094ada85..7d28c2d2a 100644 --- a/volatility3/framework/plugins/mac/malfind.py +++ b/volatility3/framework/plugins/mac/malfind.py @@ -23,8 +23,8 @@ class Malfind(interfaces.plugins.PluginInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/mac/netstat.py b/volatility3/framework/plugins/mac/netstat.py index 76bba25f6..2eb7132f2 100644 --- a/volatility3/framework/plugins/mac/netstat.py +++ b/volatility3/framework/plugins/mac/netstat.py @@ -29,8 +29,8 @@ class Netstat(plugins.PluginInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 0, 0) diff --git a/volatility3/framework/plugins/mac/proc_maps.py b/volatility3/framework/plugins/mac/proc_maps.py index bd905615d..87f3559ea 100644 --- a/volatility3/framework/plugins/mac/proc_maps.py +++ b/volatility3/framework/plugins/mac/proc_maps.py @@ -28,8 +28,8 @@ class Maps(interfaces.plugins.PluginInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/mac/psaux.py b/volatility3/framework/plugins/mac/psaux.py index 28c238263..ba9b7b5f6 100644 --- a/volatility3/framework/plugins/mac/psaux.py +++ b/volatility3/framework/plugins/mac/psaux.py @@ -24,8 +24,8 @@ class Psaux(plugins.PluginInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/mac/pstree.py b/volatility3/framework/plugins/mac/pstree.py index ad5bb309b..260029b11 100644 --- a/volatility3/framework/plugins/mac/pstree.py +++ b/volatility3/framework/plugins/mac/pstree.py @@ -28,8 +28,8 @@ class PsTree(plugins.PluginInterface): description="Kernel module for the OS", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/mac/socket_filters.py b/volatility3/framework/plugins/mac/socket_filters.py index 49e77163e..2675ccdd0 100644 --- a/volatility3/framework/plugins/mac/socket_filters.py +++ b/volatility3/framework/plugins/mac/socket_filters.py @@ -32,8 +32,8 @@ class Socket_filters(plugins.PluginInterface): requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 0, 0) ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="lsmod", component=lsmod.Lsmod, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/mac/trustedbsd.py b/volatility3/framework/plugins/mac/trustedbsd.py index a03e2a903..3d76a018b 100644 --- a/volatility3/framework/plugins/mac/trustedbsd.py +++ b/volatility3/framework/plugins/mac/trustedbsd.py @@ -33,8 +33,8 @@ class Trustedbsd(plugins.PluginInterface): requirements.VersionRequirement( name="macutils", component=mac.MacUtilities, version=(1, 3, 0) ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="lsmod", component=lsmod.Lsmod, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/amcache.py b/volatility3/framework/plugins/windows/amcache.py index 5920cd266..bea9cd8a1 100644 --- a/volatility3/framework/plugins/windows/amcache.py +++ b/volatility3/framework/plugins/windows/amcache.py @@ -231,8 +231,8 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/cachedump.py b/volatility3/framework/plugins/windows/cachedump.py index 7bc35945a..520dc8054 100644 --- a/volatility3/framework/plugins/windows/cachedump.py +++ b/volatility3/framework/plugins/windows/cachedump.py @@ -32,14 +32,14 @@ class Cachedump(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="lsadump", plugin=lsadump.Lsadump, version=(1, 0, 0) + requirements.VersionRequirement( + name="lsadump", component=lsadump.Lsadump, version=(1, 0, 0) ), - requirements.PluginRequirement( - name="hashdump", plugin=hashdump.Hashdump, version=(1, 1, 0) + requirements.VersionRequirement( + name="hashdump", component=hashdump.Hashdump, version=(1, 1, 0) ), ] diff --git a/volatility3/framework/plugins/windows/callbacks.py b/volatility3/framework/plugins/windows/callbacks.py index bb326fd41..bcdd37869 100644 --- a/volatility3/framework/plugins/windows/callbacks.py +++ b/volatility3/framework/plugins/windows/callbacks.py @@ -38,17 +38,17 @@ class Callbacks(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0) + requirements.VersionRequirement( + name="ssdt", component=ssdt.SSDT, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0) + requirements.VersionRequirement( + name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="driverirp", plugin=driverirp.DriverIrp, version=(1, 0, 0) + requirements.VersionRequirement( + name="driverirp", component=driverirp.DriverIrp, version=(1, 0, 0) ), - requirements.PluginRequirement( - name="handles", plugin=handles.Handles, version=(3, 0, 0) + requirements.VersionRequirement( + name="handles", component=handles.Handles, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/cmdline.py b/volatility3/framework/plugins/windows/cmdline.py index c095cff9e..733b06605 100644 --- a/volatility3/framework/plugins/windows/cmdline.py +++ b/volatility3/framework/plugins/windows/cmdline.py @@ -27,8 +27,8 @@ class CmdLine(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/windows/cmdscan.py b/volatility3/framework/plugins/windows/cmdscan.py index b7eab79fb..8c477b57d 100644 --- a/volatility3/framework/plugins/windows/cmdscan.py +++ b/volatility3/framework/plugins/windows/cmdscan.py @@ -38,8 +38,8 @@ class CmdScan(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="consoles", plugin=consoles.Consoles, version=(3, 0, 0) + requirements.VersionRequirement( + name="consoles", component=consoles.Consoles, version=(3, 0, 0) ), requirements.BooleanRequirement( name="no_registry", diff --git a/volatility3/framework/plugins/windows/consoles.py b/volatility3/framework/plugins/windows/consoles.py index a63b044d9..5a36cc796 100644 --- a/volatility3/framework/plugins/windows/consoles.py +++ b/volatility3/framework/plugins/windows/consoles.py @@ -48,8 +48,8 @@ class Consoles(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="verinfo", component=verinfo.VerInfo, version=(1, 0, 0) ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), requirements.BooleanRequirement( name="no_registry", diff --git a/volatility3/framework/plugins/windows/deskscan.py b/volatility3/framework/plugins/windows/deskscan.py index 6a8ff9e65..35430be5d 100644 --- a/volatility3/framework/plugins/windows/deskscan.py +++ b/volatility3/framework/plugins/windows/deskscan.py @@ -31,12 +31,12 @@ class DeskScan(desktops.Desktops): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="desktops", plugin=desktops.Desktops, version=(1, 0, 0) + requirements.VersionRequirement( + name="desktops", component=desktops.Desktops, version=(1, 0, 0) ), - requirements.PluginRequirement( + requirements.VersionRequirement( name="windowstations", - plugin=windowstations.WindowStations, + component=windowstations.WindowStations, version=(1, 0, 0), ), ] diff --git a/volatility3/framework/plugins/windows/desktops.py b/volatility3/framework/plugins/windows/desktops.py index 1085ff36d..c6557085e 100644 --- a/volatility3/framework/plugins/windows/desktops.py +++ b/volatility3/framework/plugins/windows/desktops.py @@ -31,9 +31,9 @@ class Desktops(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( + requirements.VersionRequirement( name="windowstations", - plugin=windowstations.WindowStations, + component=windowstations.WindowStations, version=(1, 0, 0), ), ] diff --git a/volatility3/framework/plugins/windows/devicetree.py b/volatility3/framework/plugins/windows/devicetree.py index 012a8750d..17ec1c451 100644 --- a/volatility3/framework/plugins/windows/devicetree.py +++ b/volatility3/framework/plugins/windows/devicetree.py @@ -89,8 +89,8 @@ class DeviceTree(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="driverscan", plugin=driverscan.DriverScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="driverscan", component=driverscan.DriverScan, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/direct_system_calls.py b/volatility3/framework/plugins/windows/direct_system_calls.py index 9d5b81507..60dbf728c 100644 --- a/volatility3/framework/plugins/windows/direct_system_calls.py +++ b/volatility3/framework/plugins/windows/direct_system_calls.py @@ -91,14 +91,14 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.VersionRequirement( name="yarascanner", component=yarascan.YaraScanner, version=(2, 1, 0) ), - requirements.PluginRequirement( - name="yarascan", plugin=yarascan.YaraScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="yarascan", component=yarascan.YaraScan, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/driverirp.py b/volatility3/framework/plugins/windows/driverirp.py index 20d8ac170..d5452fa9c 100644 --- a/volatility3/framework/plugins/windows/driverirp.py +++ b/volatility3/framework/plugins/windows/driverirp.py @@ -58,14 +58,14 @@ class DriverIrp(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0) + requirements.VersionRequirement( + name="ssdt", component=ssdt.SSDT, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="driverscan", plugin=driverscan.DriverScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="driverscan", component=driverscan.DriverScan, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="modules", plugin=modules.Modules, version=(3, 0, 0) + requirements.VersionRequirement( + name="modules", component=modules.Modules, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/drivermodule.py b/volatility3/framework/plugins/windows/drivermodule.py index 97e9e5b3c..c31fe2500 100644 --- a/volatility3/framework/plugins/windows/drivermodule.py +++ b/volatility3/framework/plugins/windows/drivermodule.py @@ -25,14 +25,14 @@ class DriverModule(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0) + requirements.VersionRequirement( + name="ssdt", component=ssdt.SSDT, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="driverscan", plugin=driverscan.DriverScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="driverscan", component=driverscan.DriverScan, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="modules", plugin=modules.Modules, version=(3, 0, 0) + requirements.VersionRequirement( + name="modules", component=modules.Modules, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/driverscan.py b/volatility3/framework/plugins/windows/driverscan.py index 57edfe0b6..e19ff555b 100644 --- a/volatility3/framework/plugins/windows/driverscan.py +++ b/volatility3/framework/plugins/windows/driverscan.py @@ -24,8 +24,8 @@ class DriverScan(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0) + requirements.VersionRequirement( + name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/envars.py b/volatility3/framework/plugins/windows/envars.py index 6360ca10b..6c07e797f 100644 --- a/volatility3/framework/plugins/windows/envars.py +++ b/volatility3/framework/plugins/windows/envars.py @@ -39,11 +39,11 @@ class Envars(interfaces.plugins.PluginInterface): description="Suppress common and non-persistent variables", optional=True, ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/filescan.py b/volatility3/framework/plugins/windows/filescan.py index e0c823756..f417e3e5e 100644 --- a/volatility3/framework/plugins/windows/filescan.py +++ b/volatility3/framework/plugins/windows/filescan.py @@ -24,8 +24,8 @@ class FileScan(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0) + requirements.VersionRequirement( + name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/getservicesids.py b/volatility3/framework/plugins/windows/getservicesids.py index 19a73fba8..c04472eab 100644 --- a/volatility3/framework/plugins/windows/getservicesids.py +++ b/volatility3/framework/plugins/windows/getservicesids.py @@ -68,8 +68,8 @@ class GetServiceSIDs(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/getsids.py b/volatility3/framework/plugins/windows/getsids.py index 786dc3394..27894646e 100644 --- a/volatility3/framework/plugins/windows/getsids.py +++ b/volatility3/framework/plugins/windows/getsids.py @@ -83,11 +83,11 @@ class GetSIDs(interfaces.plugins.PluginInterface): element_type=int, optional=True, ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/handles.py b/volatility3/framework/plugins/windows/handles.py index 9627b5caa..2f257772f 100644 --- a/volatility3/framework/plugins/windows/handles.py +++ b/volatility3/framework/plugins/windows/handles.py @@ -35,8 +35,8 @@ class Handles(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.VersionRequirement( name="psscan", component=psscan.PsScan, version=(2, 0, 0) diff --git a/volatility3/framework/plugins/windows/hashdump.py b/volatility3/framework/plugins/windows/hashdump.py index 68d5f834a..630aa1cfd 100644 --- a/volatility3/framework/plugins/windows/hashdump.py +++ b/volatility3/framework/plugins/windows/hashdump.py @@ -33,8 +33,8 @@ class Hashdump(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/indirect_system_calls.py b/volatility3/framework/plugins/windows/indirect_system_calls.py index 9f3fc4359..26216d2c3 100644 --- a/volatility3/framework/plugins/windows/indirect_system_calls.py +++ b/volatility3/framework/plugins/windows/indirect_system_calls.py @@ -46,12 +46,12 @@ class IndirectSystemCalls(direct_system_calls.DirectSystemCalls): requirements.VersionRequirement( name="yarascanner", component=yarascan.YaraScanner, version=(2, 1, 0) ), - requirements.PluginRequirement( - name="yarascan", plugin=yarascan.YaraScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="yarascan", component=yarascan.YaraScan, version=(2, 0, 0) ), - requirements.PluginRequirement( + requirements.VersionRequirement( name="direct_system_calls", - plugin=direct_system_calls.DirectSystemCalls, + component=direct_system_calls.DirectSystemCalls, version=(2, 0, 0), ), ] diff --git a/volatility3/framework/plugins/windows/memmap.py b/volatility3/framework/plugins/windows/memmap.py index 5a7bd1b9a..af4564259 100644 --- a/volatility3/framework/plugins/windows/memmap.py +++ b/volatility3/framework/plugins/windows/memmap.py @@ -27,8 +27,8 @@ class Memmap(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.IntRequirement( name="pid", diff --git a/volatility3/framework/plugins/windows/mftscan.py b/volatility3/framework/plugins/windows/mftscan.py index 2c5827a25..06d397010 100644 --- a/volatility3/framework/plugins/windows/mftscan.py +++ b/volatility3/framework/plugins/windows/mftscan.py @@ -333,8 +333,8 @@ class ADS(interfaces.plugins.PluginInterface): @classmethod def get_requirements(cls): return [ - requirements.PluginRequirement( - name="MFTScan", plugin=MFTScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="MFTScan", component=MFTScan, version=(2, 0, 0) ), requirements.TranslationLayerRequirement( name="primary", @@ -403,8 +403,8 @@ class ResidentData(interfaces.plugins.PluginInterface): @classmethod def get_requirements(cls): return [ - requirements.PluginRequirement( - name="MFTScan", plugin=MFTScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="MFTScan", component=MFTScan, version=(2, 0, 0) ), requirements.TranslationLayerRequirement( name="primary", diff --git a/volatility3/framework/plugins/windows/mutantscan.py b/volatility3/framework/plugins/windows/mutantscan.py index 38685677a..ba2824bfc 100644 --- a/volatility3/framework/plugins/windows/mutantscan.py +++ b/volatility3/framework/plugins/windows/mutantscan.py @@ -24,8 +24,8 @@ class MutantScan(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0) + requirements.VersionRequirement( + name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/orphan_kernel_threads.py b/volatility3/framework/plugins/windows/orphan_kernel_threads.py index 151fe88c9..5f26ae757 100644 --- a/volatility3/framework/plugins/windows/orphan_kernel_threads.py +++ b/volatility3/framework/plugins/windows/orphan_kernel_threads.py @@ -33,14 +33,14 @@ class Threads(thrdscan.ThrdScan): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 1, 0) + requirements.VersionRequirement( + name="thrdscan", component=thrdscan.ThrdScan, version=(1, 1, 0) ), - requirements.PluginRequirement( - name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0) + requirements.VersionRequirement( + name="ssdt", component=ssdt.SSDT, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="modules", plugin=modules.Modules, version=(3, 0, 0) + requirements.VersionRequirement( + name="modules", component=modules.Modules, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/poolscanner.py b/volatility3/framework/plugins/windows/poolscanner.py index 1f29a3aed..975ed2326 100644 --- a/volatility3/framework/plugins/windows/poolscanner.py +++ b/volatility3/framework/plugins/windows/poolscanner.py @@ -139,8 +139,8 @@ class PoolScanner(plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="handles", plugin=handles.Handles, version=(3, 0, 0) + requirements.VersionRequirement( + name="handles", component=handles.Handles, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/privileges.py b/volatility3/framework/plugins/windows/privileges.py index e41915442..6bc59bab6 100644 --- a/volatility3/framework/plugins/windows/privileges.py +++ b/volatility3/framework/plugins/windows/privileges.py @@ -60,8 +60,8 @@ class Privs(interfaces.plugins.PluginInterface): element_type=int, optional=True, ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/psscan.py b/volatility3/framework/plugins/windows/psscan.py index 99fa9640b..07f026c7c 100644 --- a/volatility3/framework/plugins/windows/psscan.py +++ b/volatility3/framework/plugins/windows/psscan.py @@ -33,8 +33,8 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.VersionRequirement( name="info", component=info.Info, version=(2, 0, 0) diff --git a/volatility3/framework/plugins/windows/registry/getcellroutine.py b/volatility3/framework/plugins/windows/registry/getcellroutine.py index 5be4254ba..5f3b1dcaa 100644 --- a/volatility3/framework/plugins/windows/registry/getcellroutine.py +++ b/volatility3/framework/plugins/windows/registry/getcellroutine.py @@ -27,11 +27,11 @@ class GetCellRoutine(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0) + requirements.VersionRequirement( + name="ssdt", component=ssdt.SSDT, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/registry/hivelist.py b/volatility3/framework/plugins/windows/registry/hivelist.py index fefd24b67..ec2fbc4c7 100644 --- a/volatility3/framework/plugins/windows/registry/hivelist.py +++ b/volatility3/framework/plugins/windows/registry/hivelist.py @@ -60,8 +60,8 @@ class HiveList(interfaces.plugins.PluginInterface): optional=True, default=None, ), - requirements.PluginRequirement( - name="hivescan", plugin=hivescan.HiveScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivescan", component=hivescan.HiveScan, version=(2, 0, 0) ), requirements.BooleanRequirement( name="dump", diff --git a/volatility3/framework/plugins/windows/registry/hivescan.py b/volatility3/framework/plugins/windows/registry/hivescan.py index 10843f8ab..2ebc52f53 100644 --- a/volatility3/framework/plugins/windows/registry/hivescan.py +++ b/volatility3/framework/plugins/windows/registry/hivescan.py @@ -25,11 +25,11 @@ class HiveScan(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0) + requirements.VersionRequirement( + name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="bigpools", plugin=bigpools.BigPools, version=(2, 0, 0) + requirements.VersionRequirement( + name="bigpools", component=bigpools.BigPools, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/registry/printkey.py b/volatility3/framework/plugins/windows/registry/printkey.py index c8b8f9cfb..6ca56b1bb 100644 --- a/volatility3/framework/plugins/windows/registry/printkey.py +++ b/volatility3/framework/plugins/windows/registry/printkey.py @@ -35,8 +35,8 @@ class PrintKey(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), requirements.IntRequirement( name="offset", description="Hive Offset", default=None, optional=True diff --git a/volatility3/framework/plugins/windows/registry/userassist.py b/volatility3/framework/plugins/windows/registry/userassist.py index ef51b91bf..7beeb7375 100644 --- a/volatility3/framework/plugins/windows/registry/userassist.py +++ b/volatility3/framework/plugins/windows/registry/userassist.py @@ -56,8 +56,8 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac requirements.IntRequirement( name="offset", description="Hive Offset", default=None, optional=True ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/scheduled_tasks.py b/volatility3/framework/plugins/windows/scheduled_tasks.py index ba54e19ec..951ac6e80 100644 --- a/volatility3/framework/plugins/windows/scheduled_tasks.py +++ b/volatility3/framework/plugins/windows/scheduled_tasks.py @@ -1123,8 +1123,8 @@ information about triggers, actions, run times, and creation times.""" description="Windows kernel", architectures=["Intel33", "Intel64"], ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/sessions.py b/volatility3/framework/plugins/windows/sessions.py index 73a537cd4..a21fa578d 100644 --- a/volatility3/framework/plugins/windows/sessions.py +++ b/volatility3/framework/plugins/windows/sessions.py @@ -27,8 +27,8 @@ class Sessions(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/windows/shimcachemem.py b/volatility3/framework/plugins/windows/shimcachemem.py index f26bf3d6b..eb2eb686b 100644 --- a/volatility3/framework/plugins/windows/shimcachemem.py +++ b/volatility3/framework/plugins/windows/shimcachemem.py @@ -64,8 +64,8 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.VersionRequirement( name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0) diff --git a/volatility3/framework/plugins/windows/ssdt.py b/volatility3/framework/plugins/windows/ssdt.py index ed7d1310d..b4fa39950 100644 --- a/volatility3/framework/plugins/windows/ssdt.py +++ b/volatility3/framework/plugins/windows/ssdt.py @@ -31,8 +31,8 @@ class SSDT(plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="modules", plugin=modules.Modules, version=(3, 0, 0) + requirements.VersionRequirement( + name="modules", component=modules.Modules, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/strings.py b/volatility3/framework/plugins/windows/strings.py index 46784e48a..9ea4ffed0 100644 --- a/volatility3/framework/plugins/windows/strings.py +++ b/volatility3/framework/plugins/windows/strings.py @@ -33,8 +33,8 @@ class Strings(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/windows/svclist.py b/volatility3/framework/plugins/windows/svclist.py index 00d4aa647..4a310c669 100644 --- a/volatility3/framework/plugins/windows/svclist.py +++ b/volatility3/framework/plugins/windows/svclist.py @@ -31,8 +31,8 @@ class SvcList(svcscan.SvcScan): def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: # Since we're calling the plugin, make sure we have the plugin's requirements return [ - requirements.PluginRequirement( - name="svcscan", plugin=svcscan.SvcScan, version=(4, 0, 0) + requirements.VersionRequirement( + name="svcscan", component=svcscan.SvcScan, version=(4, 0, 0) ), requirements.ModuleRequirement( name="kernel", diff --git a/volatility3/framework/plugins/windows/svcscan.py b/volatility3/framework/plugins/windows/svcscan.py index 80400ec5a..94ce02897 100644 --- a/volatility3/framework/plugins/windows/svcscan.py +++ b/volatility3/framework/plugins/windows/svcscan.py @@ -50,11 +50,11 @@ class SvcScan(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/thrdscan.py b/volatility3/framework/plugins/windows/thrdscan.py index 369db1fd8..5ba12736b 100644 --- a/volatility3/framework/plugins/windows/thrdscan.py +++ b/volatility3/framework/plugins/windows/thrdscan.py @@ -33,8 +33,8 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0) + requirements.VersionRequirement( + name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/threads.py b/volatility3/framework/plugins/windows/threads.py index 77062e8c6..789300306 100644 --- a/volatility3/framework/plugins/windows/threads.py +++ b/volatility3/framework/plugins/windows/threads.py @@ -31,8 +31,8 @@ class Threads(thrdscan.ThrdScan): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 1, 0) + requirements.VersionRequirement( + name="thrdscan", component=thrdscan.ThrdScan, version=(1, 1, 0) ), ] diff --git a/volatility3/framework/plugins/windows/unhooked_system_calls.py b/volatility3/framework/plugins/windows/unhooked_system_calls.py index 132cf4e4f..3ff0aa158 100644 --- a/volatility3/framework/plugins/windows/unhooked_system_calls.py +++ b/volatility3/framework/plugins/windows/unhooked_system_calls.py @@ -97,8 +97,8 @@ class unhooked_system_calls(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="pe_symbols", plugin=pe_symbols.PESymbols, version=(3, 0, 0) + requirements.VersionRequirement( + name="pe_symbols", component=pe_symbols.PESymbols, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/vadinfo.py b/volatility3/framework/plugins/windows/vadinfo.py index 2b1d3f4bc..22d42505f 100644 --- a/volatility3/framework/plugins/windows/vadinfo.py +++ b/volatility3/framework/plugins/windows/vadinfo.py @@ -63,8 +63,8 @@ class VadInfo(interfaces.plugins.PluginInterface): element_type=int, optional=True, ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.BooleanRequirement( name="dump", diff --git a/volatility3/framework/plugins/windows/vadregexscan.py b/volatility3/framework/plugins/windows/vadregexscan.py index 5d2356f54..9b666cbcb 100644 --- a/volatility3/framework/plugins/windows/vadregexscan.py +++ b/volatility3/framework/plugins/windows/vadregexscan.py @@ -32,8 +32,8 @@ class VadRegExScan(plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/windows/vadwalk.py b/volatility3/framework/plugins/windows/vadwalk.py index cc8105e0c..38b5d197e 100644 --- a/volatility3/framework/plugins/windows/vadwalk.py +++ b/volatility3/framework/plugins/windows/vadwalk.py @@ -28,11 +28,11 @@ class VadWalk(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="vadinfo", plugin=vadinfo.VadInfo, version=(2, 0, 0) + requirements.VersionRequirement( + name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/windows/vadyarascan.py b/volatility3/framework/plugins/windows/vadyarascan.py index a19206e22..b86869969 100644 --- a/volatility3/framework/plugins/windows/vadyarascan.py +++ b/volatility3/framework/plugins/windows/vadyarascan.py @@ -29,14 +29,14 @@ class VadYaraScan(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.VersionRequirement( name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="yarascan", plugin=yarascan.YaraScan, version=(2, 0, 0) + requirements.VersionRequirement( + name="yarascan", component=yarascan.YaraScan, version=(2, 0, 0) ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/windows/verinfo.py b/volatility3/framework/plugins/windows/verinfo.py index 49bf0b212..b5eba7ec6 100644 --- a/volatility3/framework/plugins/windows/verinfo.py +++ b/volatility3/framework/plugins/windows/verinfo.py @@ -42,11 +42,11 @@ class VerInfo(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(3, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), - requirements.PluginRequirement( - name="modules", plugin=modules.Modules, version=(3, 0, 0) + requirements.VersionRequirement( + name="modules", component=modules.Modules, version=(3, 0, 0) ), requirements.BooleanRequirement( name="extensive", diff --git a/volatility3/plugins/windows/registry/certificates.py b/volatility3/plugins/windows/registry/certificates.py index fd33d75a7..caf244f95 100644 --- a/volatility3/plugins/windows/registry/certificates.py +++ b/volatility3/plugins/windows/registry/certificates.py @@ -24,11 +24,11 @@ class Certificates(interfaces.plugins.PluginInterface): description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0) + requirements.VersionRequirement( + name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), - requirements.PluginRequirement( - name="printkey", plugin=printkey.PrintKey, version=(1, 0, 0) + requirements.VersionRequirement( + name="printkey", component=printkey.PrintKey, version=(1, 0, 0) ), requirements.BooleanRequirement( name="dump", From 166d0e0c14974419ad1d60448819d67492466d96 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 24 Mar 2025 19:37:59 +0000 Subject: [PATCH 70/78] Fix issue when pe_symbols limited to searching one process. Remove need to track symbol indexes. Provide much more useful debugging information. Fixes #1732 --- .../framework/plugins/windows/pe_symbols.py | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/volatility3/framework/plugins/windows/pe_symbols.py b/volatility3/framework/plugins/windows/pe_symbols.py index b26e8d113..1ce40e1f8 100644 --- a/volatility3/framework/plugins/windows/pe_symbols.py +++ b/volatility3/framework/plugins/windows/pe_symbols.py @@ -229,7 +229,6 @@ class ExportSymbolFinder(PESymbolFinder): Returns: address: the address of the symbol, if found """ - for export in self._symbol_module: sym_name = self._get_name(export) if sym_name and sym_name == name: @@ -413,8 +412,10 @@ class PESymbols(interfaces.plugins.PluginInterface): ) for mod_name, unresolved_symbols in missing_symbols.items(): - for symbol in unresolved_symbols: - vollog.debug(f"Unable to resolve symbol {symbol} in module {mod_name}") + for symbol_key, symbols in unresolved_symbols.items(): + vollog.debug( + f"Unable to resolve symbols {symbols} of type {symbol_key} in module {mod_name}" + ) return found_symbols @@ -632,7 +633,7 @@ class PESymbols(interfaces.plugins.PluginInterface): def _get_symbol_value( wanted_symbols: filter_module_info, symbol_resolver: PESymbolFinder, - ) -> Generator[Tuple[str, int, str, int], None, None]: + ) -> Generator[Tuple[str, str, int], None, None]: """ Enumerates the symbols specified as wanted by the calling plugin @@ -661,15 +662,25 @@ class PESymbols(interfaces.plugins.PluginInterface): # address or name if symbol_key in wanted_symbols: # walk each wanted address or name - for value_index, wanted_value in enumerate(wanted_symbols[symbol_key]): - symbol_value = symbol_getter(wanted_value) + # build dict in this function for debugging and tracking + all_wanted = [] + for wanted_value in wanted_symbols[symbol_key]: + all_wanted.append(wanted_value) + + for value_index, wanted_value in enumerate(all_wanted): + symbol_value = symbol_getter(wanted_value) if symbol_value: # yield out deleteion key, deletion index, symbol name, symbol address if symbol_key == wanted_names_identifier: - yield symbol_key, value_index, wanted_value, symbol_value # type: ignore + yield symbol_key, wanted_value, symbol_value # type: ignore else: - yield symbol_key, value_index, symbol_value, wanted_value # type: ignore + yield symbol_key, symbol_value, wanted_value # type: ignore + + for value in all_wanted: + vollog.debug( + f"Unable to resolve value {value} using getter {symbol_getter}" + ) @classmethod def _validate_wanted_modules( @@ -742,7 +753,7 @@ class PESymbols(interfaces.plugins.PluginInterface): PESymbols._find_symbols_through_exports, ] - found: found_symbols_module = [] + found_symbols: found_symbols_module = [] # the symbols wanted from this module by the caller wanted = wanted_modules[mod_name] @@ -760,12 +771,17 @@ class PESymbols(interfaces.plugins.PluginInterface): vollog.debug(f"Have resolver for method {method}") for ( symbol_key, - value_index, symbol_name, symbol_address, ) in PESymbols._get_symbol_value(remaining, symbol_resolver): - found.append((symbol_name, symbol_address)) - del remaining[symbol_key][value_index] + found_symbols.append((symbol_name, symbol_address)) + + if symbol_key == wanted_names_identifier: + to_remove = symbol_name + else: + to_remove = symbol_address + + remaining[symbol_key].remove(to_remove) # everything was resolved, stop this resolver # remove this key from the remaining symbols to resolve @@ -781,7 +797,7 @@ class PESymbols(interfaces.plugins.PluginInterface): if done_processing: break - return found, remaining + return found_symbols, remaining @classmethod def find_symbols( @@ -970,7 +986,8 @@ class PESymbols(interfaces.plugins.PluginInterface): Generator[Tuple[interfaces.objects.ObjectInterface, str, ranges_type]]: Yields tuple of process objects, layers, and VADs mapping files """ procs = pslist.PsList.list_processes( - context=context, kernel_module_name=kernel_module_name + context=context, + kernel_module_name=kernel_module_name, ) for proc in procs: From 0f098fc160b18e0ac840c5da5159bb2e14d392cc Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 24 Mar 2025 20:13:10 -0500 Subject: [PATCH 71/78] Address feedback --- volatility3/framework/plugins/windows/pe_symbols.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/windows/pe_symbols.py b/volatility3/framework/plugins/windows/pe_symbols.py index 1ce40e1f8..3a08a1002 100644 --- a/volatility3/framework/plugins/windows/pe_symbols.py +++ b/volatility3/framework/plugins/windows/pe_symbols.py @@ -642,7 +642,7 @@ class PESymbols(interfaces.plugins.PluginInterface): symbol_resolver: method in a layer to resolve the symbols Returns: - Tuple[str, int, str, int]: the index and value of the found symbol in the wanted list, and the name and address of resolved symbol + Tuple[str, str, int]: the symbol identifier (key) of the found symbol in the wanted list, and the name and address of resolved symbol """ if ( wanted_names_identifier not in wanted_symbols @@ -673,9 +673,9 @@ class PESymbols(interfaces.plugins.PluginInterface): if symbol_value: # yield out deleteion key, deletion index, symbol name, symbol address if symbol_key == wanted_names_identifier: - yield symbol_key, wanted_value, symbol_value # type: ignore + yield symbol_key, wanted_value, symbol_value else: - yield symbol_key, symbol_value, wanted_value # type: ignore + yield symbol_key, symbol_value, wanted_value for value in all_wanted: vollog.debug( From 1a2427b54b312e3a3283b877915ae32664a00e68 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 24 Mar 2025 20:18:54 -0500 Subject: [PATCH 72/78] Change column order --- volatility3/framework/plugins/windows/processghosting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/windows/processghosting.py b/volatility3/framework/plugins/windows/processghosting.py index 28af57053..023ee877a 100644 --- a/volatility3/framework/plugins/windows/processghosting.py +++ b/volatility3/framework/plugins/windows/processghosting.py @@ -187,10 +187,10 @@ class ProcessGhosting(interfaces.plugins.PluginInterface): yield 0, ( pid, process_name, + format_hints.Hex(base_address), format_hints.Hex(file_object_address), delete_pending or renderers.NotApplicableValue(), delete_on_close or renderers.NotApplicableValue(), - format_hints.Hex(base_address), path, ) @@ -201,10 +201,10 @@ class ProcessGhosting(interfaces.plugins.PluginInterface): [ ("PID", int), ("Process", str), + ("Base", format_hints.Hex), ("FILE_OBJECT", format_hints.Hex), ("DeletePending", int), ("DeleteOnClose", int), - ("Base", format_hints.Hex), ("Path", str), ], self._generator( From 3a4e622854708ebd0b8bb4bc48c57ceb2db61828 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Mon, 24 Mar 2025 20:19:59 -0500 Subject: [PATCH 73/78] Change pending checking and OS version --- volatility3/framework/plugins/windows/processghosting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/windows/processghosting.py b/volatility3/framework/plugins/windows/processghosting.py index 023ee877a..6e91a72cd 100644 --- a/volatility3/framework/plugins/windows/processghosting.py +++ b/volatility3/framework/plugins/windows/processghosting.py @@ -99,7 +99,7 @@ class ProcessGhosting(interfaces.plugins.PluginInterface): except exceptions.InvalidAddressException: delete_pending = None - if delete_pending and delete_pending == 1: + if delete_pending == 1: yield file_object.vol.offset, delete_pending, None @classmethod @@ -158,7 +158,7 @@ class ProcessGhosting(interfaces.plugins.PluginInterface): ) if not has_imagefilepointer: vollog.warning( - "ImageFilePointer checks are only supported on Windows 10 builds when the ImageFilePointer member of _EPROCESS is present" + "ImageFilePointer checks are only supported on Windows 10+ builds when the ImageFilePointer member of _EPROCESS is present" ) for proc in procs: From d5fc0502246609fb255c4222ae2f5208195e22bd Mon Sep 17 00:00:00 2001 From: David McDonald Date: Tue, 25 Mar 2025 17:25:07 -0500 Subject: [PATCH 74/78] Timeliner: add `VersionableInterface` superclass This adds `interfaces.configuration.VersionableInterface` as a superclass to `TimelinerInterface` in order to be consistent with other versioned interfaces such as `PluginInterface`. --- volatility3/framework/plugins/timeliner.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/timeliner.py b/volatility3/framework/plugins/timeliner.py index 6000704eb..f65868705 100644 --- a/volatility3/framework/plugins/timeliner.py +++ b/volatility3/framework/plugins/timeliner.py @@ -25,10 +25,14 @@ class TimeLinerType(enum.IntEnum): CHANGED = 4 -class TimeLinerInterface(metaclass=abc.ABCMeta): +class TimeLinerInterface( + interfaces.configuration.VersionableInterface, metaclass=abc.ABCMeta +): """Interface defining methods that timeliner will use to generate a body file.""" + _version = (1, 0, 0) + @abc.abstractmethod def generate_timeline( self, From 66992a5d9ae8839aff8e35582a0216764315c4a4 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Tue, 25 Mar 2025 17:42:35 -0500 Subject: [PATCH 75/78] Requirements: Insert missing version requirements This audits the entire codebase for missing `VersionRequirements` and adds them as needed. --- volatility3/framework/plugins/linux/bash.py | 5 +++++ volatility3/framework/plugins/linux/boottime.py | 5 +++++ volatility3/framework/plugins/linux/lsof.py | 5 +++++ volatility3/framework/plugins/linux/pagecache.py | 5 +++++ volatility3/framework/plugins/linux/pslist.py | 5 +++++ volatility3/framework/plugins/mac/bash.py | 5 +++++ volatility3/framework/plugins/windows/amcache.py | 5 +++++ volatility3/framework/plugins/windows/consoles.py | 5 ++++- volatility3/framework/plugins/windows/dlllist.py | 5 +++++ volatility3/framework/plugins/windows/driverscan.py | 3 +++ volatility3/framework/plugins/windows/mftscan.py | 8 ++++++++ volatility3/framework/plugins/windows/netscan.py | 5 +++++ volatility3/framework/plugins/windows/netstat.py | 5 +++++ volatility3/framework/plugins/windows/processghosting.py | 3 +++ volatility3/framework/plugins/windows/pslist.py | 5 +++++ volatility3/framework/plugins/windows/psscan.py | 5 +++++ .../framework/plugins/windows/registry/userassist.py | 5 +++++ volatility3/framework/plugins/windows/scheduled_tasks.py | 5 +++++ volatility3/framework/plugins/windows/sessions.py | 5 +++++ volatility3/framework/plugins/windows/shimcachemem.py | 5 +++++ volatility3/framework/plugins/windows/svclist.py | 3 +++ volatility3/framework/plugins/windows/symlinkscan.py | 5 +++++ volatility3/framework/plugins/windows/thrdscan.py | 8 ++++++++ volatility3/framework/plugins/windows/threads.py | 3 +++ volatility3/framework/plugins/windows/unloadedmodules.py | 8 ++++++++ 25 files changed, 125 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/bash.py b/volatility3/framework/plugins/linux/bash.py index fd73b4df2..2a63ac329 100644 --- a/volatility3/framework/plugins/linux/bash.py +++ b/volatility3/framework/plugins/linux/bash.py @@ -35,6 +35,11 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface): requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(4, 0, 0) ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.ListRequirement( name="pid", element_type=int, diff --git a/volatility3/framework/plugins/linux/boottime.py b/volatility3/framework/plugins/linux/boottime.py index c1a75d478..0b9abb856 100644 --- a/volatility3/framework/plugins/linux/boottime.py +++ b/volatility3/framework/plugins/linux/boottime.py @@ -25,6 +25,11 @@ class Boottime(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) description="Linux kernel", architectures=["Intel32", "Intel64"], ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(4, 0, 0) ), diff --git a/volatility3/framework/plugins/linux/lsof.py b/volatility3/framework/plugins/linux/lsof.py index 8e0143584..283eabca0 100644 --- a/volatility3/framework/plugins/linux/lsof.py +++ b/volatility3/framework/plugins/linux/lsof.py @@ -123,6 +123,11 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface): requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(4, 0, 0) ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) ), diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 879bc3288..0bb3b9263 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -129,6 +129,11 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface): requirements.VersionRequirement( name="mountinfo", component=mountinfo.MountInfo, version=(1, 2, 0) ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.ListRequirement( name="type", description="List of space-separated file type filters i.e. --type REG DIR", diff --git a/volatility3/framework/plugins/linux/pslist.py b/volatility3/framework/plugins/linux/pslist.py index 8296c82fa..2f0cc00b7 100644 --- a/volatility3/framework/plugins/linux/pslist.py +++ b/volatility3/framework/plugins/linux/pslist.py @@ -53,6 +53,11 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): element_type=int, optional=True, ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.BooleanRequirement( name="threads", description="Include user threads", diff --git a/volatility3/framework/plugins/mac/bash.py b/volatility3/framework/plugins/mac/bash.py index aca0aea0c..4cbade1cf 100644 --- a/volatility3/framework/plugins/mac/bash.py +++ b/volatility3/framework/plugins/mac/bash.py @@ -33,6 +33,11 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface): requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(3, 0, 0) ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.ListRequirement( name="pid", description="Filter on specific process IDs", diff --git a/volatility3/framework/plugins/windows/amcache.py b/volatility3/framework/plugins/windows/amcache.py index bea9cd8a1..4ac5554e0 100644 --- a/volatility3/framework/plugins/windows/amcache.py +++ b/volatility3/framework/plugins/windows/amcache.py @@ -234,6 +234,11 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): requirements.VersionRequirement( name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), ] def generate_timeline( diff --git a/volatility3/framework/plugins/windows/consoles.py b/volatility3/framework/plugins/windows/consoles.py index 5a36cc796..8999e1bab 100644 --- a/volatility3/framework/plugins/windows/consoles.py +++ b/volatility3/framework/plugins/windows/consoles.py @@ -46,7 +46,10 @@ class Consoles(interfaces.plugins.PluginInterface): name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.VersionRequirement( - name="verinfo", component=verinfo.VerInfo, version=(1, 0, 0) + name="verinfo", component=verinfo.VerInfo, version=(2, 0, 0) + ), + requirements.VersionRequirement( + name="info", component=info.Info, version=(1, 0, 0) ), requirements.VersionRequirement( name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) diff --git a/volatility3/framework/plugins/windows/dlllist.py b/volatility3/framework/plugins/windows/dlllist.py index b1c6f2f05..b851cf7fd 100644 --- a/volatility3/framework/plugins/windows/dlllist.py +++ b/volatility3/framework/plugins/windows/dlllist.py @@ -36,6 +36,11 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(3, 0, 0) ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="psscan", component=psscan.PsScan, version=(2, 0, 0) ), diff --git a/volatility3/framework/plugins/windows/driverscan.py b/volatility3/framework/plugins/windows/driverscan.py index e19ff555b..57d365d00 100644 --- a/volatility3/framework/plugins/windows/driverscan.py +++ b/volatility3/framework/plugins/windows/driverscan.py @@ -27,6 +27,9 @@ class DriverScan(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0) ), + requirements.VersionRequirement( + name="modules", component=modules.Modules, version=(3, 0, 0) + ), ] @classmethod diff --git a/volatility3/framework/plugins/windows/mftscan.py b/volatility3/framework/plugins/windows/mftscan.py index 06d397010..74cadc833 100644 --- a/volatility3/framework/plugins/windows/mftscan.py +++ b/volatility3/framework/plugins/windows/mftscan.py @@ -32,9 +32,17 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): description="Memory layer for the kernel", architectures=["Intel32", "Intel64"], ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="yarascanner", component=yarascan.YaraScanner, version=(2, 1, 0) ), + requirements.VersionRequirement( + name="yarascan", component=yarascan.YaraScan, version=(2, 0, 0) + ), ] @classmethod diff --git a/volatility3/framework/plugins/windows/netscan.py b/volatility3/framework/plugins/windows/netscan.py index 1ab748864..fa422e103 100644 --- a/volatility3/framework/plugins/windows/netscan.py +++ b/volatility3/framework/plugins/windows/netscan.py @@ -39,6 +39,11 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): requirements.VersionRequirement( name="info", component=info.Info, version=(2, 0, 0) ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="verinfo", component=verinfo.VerInfo, version=(1, 0, 0) ), diff --git a/volatility3/framework/plugins/windows/netstat.py b/volatility3/framework/plugins/windows/netstat.py index 655ef710a..cf7f5272a 100644 --- a/volatility3/framework/plugins/windows/netstat.py +++ b/volatility3/framework/plugins/windows/netstat.py @@ -39,6 +39,11 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): requirements.VersionRequirement( name="modules", component=modules.Modules, version=(3, 0, 0) ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="pdbutil", component=pdbutil.PDBUtility, version=(1, 0, 0) ), diff --git a/volatility3/framework/plugins/windows/processghosting.py b/volatility3/framework/plugins/windows/processghosting.py index 6e91a72cd..7e7f6d3cc 100644 --- a/volatility3/framework/plugins/windows/processghosting.py +++ b/volatility3/framework/plugins/windows/processghosting.py @@ -33,6 +33,9 @@ class ProcessGhosting(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(3, 0, 0) ), + requirements.VersionRequirement( + name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 1) + ), ] @classmethod diff --git a/volatility3/framework/plugins/windows/pslist.py b/volatility3/framework/plugins/windows/pslist.py index 1cb2c6356..b92fdf66c 100644 --- a/volatility3/framework/plugins/windows/pslist.py +++ b/volatility3/framework/plugins/windows/pslist.py @@ -41,6 +41,11 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): default=cls.PHYSICAL_DEFAULT, optional=True, ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.ListRequirement( name="pid", element_type=int, diff --git a/volatility3/framework/plugins/windows/psscan.py b/volatility3/framework/plugins/windows/psscan.py index 07f026c7c..ae37c20a1 100644 --- a/volatility3/framework/plugins/windows/psscan.py +++ b/volatility3/framework/plugins/windows/psscan.py @@ -36,6 +36,11 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(3, 0, 0) ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="info", component=info.Info, version=(2, 0, 0) ), diff --git a/volatility3/framework/plugins/windows/registry/userassist.py b/volatility3/framework/plugins/windows/registry/userassist.py index 7beeb7375..809d0b2b3 100644 --- a/volatility3/framework/plugins/windows/registry/userassist.py +++ b/volatility3/framework/plugins/windows/registry/userassist.py @@ -59,6 +59,11 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac requirements.VersionRequirement( name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), ] def parse_userassist_data(self, reg_val): diff --git a/volatility3/framework/plugins/windows/scheduled_tasks.py b/volatility3/framework/plugins/windows/scheduled_tasks.py index 951ac6e80..4247bda74 100644 --- a/volatility3/framework/plugins/windows/scheduled_tasks.py +++ b/volatility3/framework/plugins/windows/scheduled_tasks.py @@ -1126,6 +1126,11 @@ information about triggers, actions, run times, and creation times.""" requirements.VersionRequirement( name="hivelist", component=hivelist.HiveList, version=(2, 0, 0) ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), ] def generate_timeline( diff --git a/volatility3/framework/plugins/windows/sessions.py b/volatility3/framework/plugins/windows/sessions.py index a21fa578d..29d0b2104 100644 --- a/volatility3/framework/plugins/windows/sessions.py +++ b/volatility3/framework/plugins/windows/sessions.py @@ -30,6 +30,11 @@ class Sessions(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(3, 0, 0) ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.ListRequirement( name="pid", element_type=int, diff --git a/volatility3/framework/plugins/windows/shimcachemem.py b/volatility3/framework/plugins/windows/shimcachemem.py index eb2eb686b..7883dfba3 100644 --- a/volatility3/framework/plugins/windows/shimcachemem.py +++ b/volatility3/framework/plugins/windows/shimcachemem.py @@ -67,6 +67,11 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(3, 0, 0) ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0) ), diff --git a/volatility3/framework/plugins/windows/svclist.py b/volatility3/framework/plugins/windows/svclist.py index 4a310c669..24ac2278f 100644 --- a/volatility3/framework/plugins/windows/svclist.py +++ b/volatility3/framework/plugins/windows/svclist.py @@ -34,6 +34,9 @@ class SvcList(svcscan.SvcScan): requirements.VersionRequirement( name="svcscan", component=svcscan.SvcScan, version=(4, 0, 0) ), + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) + ), requirements.ModuleRequirement( name="kernel", description="Windows kernel", diff --git a/volatility3/framework/plugins/windows/symlinkscan.py b/volatility3/framework/plugins/windows/symlinkscan.py index 358ea130e..cdcb5d3d3 100644 --- a/volatility3/framework/plugins/windows/symlinkscan.py +++ b/volatility3/framework/plugins/windows/symlinkscan.py @@ -27,6 +27,11 @@ class SymlinkScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfa description="Windows kernel", architectures=["Intel32", "Intel64"], ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0) ), diff --git a/volatility3/framework/plugins/windows/thrdscan.py b/volatility3/framework/plugins/windows/thrdscan.py index 5bafe45a7..7020a1fa1 100644 --- a/volatility3/framework/plugins/windows/thrdscan.py +++ b/volatility3/framework/plugins/windows/thrdscan.py @@ -36,6 +36,14 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) requirements.VersionRequirement( name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0) ), + requirements.VersionRequirement( + name="pe_symbols", component=pe_symbols.PESymbols, version=(3, 0, 0) + ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), ] @classmethod diff --git a/volatility3/framework/plugins/windows/threads.py b/volatility3/framework/plugins/windows/threads.py index 64eb38f42..d040fa990 100644 --- a/volatility3/framework/plugins/windows/threads.py +++ b/volatility3/framework/plugins/windows/threads.py @@ -34,6 +34,9 @@ class Threads(thrdscan.ThrdScan): requirements.VersionRequirement( name="thrdscan", component=thrdscan.ThrdScan, version=(2, 0, 0) ), + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) + ), ] @classmethod diff --git a/volatility3/framework/plugins/windows/unloadedmodules.py b/volatility3/framework/plugins/windows/unloadedmodules.py index cadacf4ff..692f2c4a4 100644 --- a/volatility3/framework/plugins/windows/unloadedmodules.py +++ b/volatility3/framework/plugins/windows/unloadedmodules.py @@ -33,6 +33,14 @@ class UnloadedModules(interfaces.plugins.PluginInterface, timeliner.TimeLinerInt description="Windows kernel", architectures=["Intel32", "Intel64"], ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), + ), + requirements.VersionRequirement( + name="modules", component=modules.Modules, version=(3, 0, 0) + ), ] @classmethod From e52aea886ee49f73061432eed03b8d566f514e8f Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Wed, 26 Mar 2025 00:41:26 +0000 Subject: [PATCH 76/78] Fix checks in thrdscan that broke tests --- volatility3/framework/plugins/windows/thrdscan.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/volatility3/framework/plugins/windows/thrdscan.py b/volatility3/framework/plugins/windows/thrdscan.py index 7020a1fa1..387125899 100644 --- a/volatility3/framework/plugins/windows/thrdscan.py +++ b/volatility3/framework/plugins/windows/thrdscan.py @@ -110,18 +110,19 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) vollog.debug(f"Thread invalid address {ethread.vol.offset:#x}") return None + if owner_proc_pid == 4 or owner_proc.InheritedFromUniqueProcessId == 4: + vollog.debug( + f"Skipping kernel process with pid {owner_proc.InheritedFromUniqueProcessId}" + ) + return None + if vads_cache is not None: vads = pe_symbols.PESymbols.get_vads_for_process_cache( vads_cache, owner_proc ) - # no vads = terminated/smeared, pid 4 = kernel = don't check VADs - if ( - owner_proc_pid != 4 - and owner_proc.InheritedFromUniqueProcessId != 4 - and (not vads or len(vads) < 5) - ): + if not vads or len(vads) < 5: vollog.debug( - f"No vads for process at {owner_proc.vol.offset:#x}. Skipping thread at {ethread.vol.offset:#x}" + f"Not enough vads for process at {owner_proc.vol.offset:#x}. Skipping thread at {ethread.vol.offset:#x}" ) return None From 43ab95f4c5ea38f182bfe863bd5b425cbd9a70f4 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Wed, 26 Mar 2025 00:50:34 +0000 Subject: [PATCH 77/78] Change thrdscan from looking for kernel processes --- test/plugins/windows/windows.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/plugins/windows/windows.py b/test/plugins/windows/windows.py index ce05af0cd..f07c8b20c 100644 --- a/test/plugins/windows/windows.py +++ b/test/plugins/windows/windows.py @@ -189,9 +189,9 @@ class TestWindowsThrdscan: "windows.thrdscan.ThrdScan", image, volatility, python ) assert rc == 0 - assert out.find(b"\t4\t8") != -1 - assert out.find(b"\t4\t12") != -1 - assert out.find(b"\t4\t16") != -1 + assert out.find(b"\t1812\t2768\t0x7c810856") != -1 + assert out.find(b"\t840\t2964\t0x7c810856") != -1 + assert out.find(b"\t2536\t2552\t0x7c810856") != -1 class TestWindowsPrivileges: From 444305afc2cbf680a6097b7b9c17fd7be97d1ca3 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Wed, 26 Mar 2025 00:55:48 +0000 Subject: [PATCH 78/78] Handle kernel processes properly this time --- volatility3/framework/plugins/windows/thrdscan.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/volatility3/framework/plugins/windows/thrdscan.py b/volatility3/framework/plugins/windows/thrdscan.py index 387125899..0ac3d0c33 100644 --- a/volatility3/framework/plugins/windows/thrdscan.py +++ b/volatility3/framework/plugins/windows/thrdscan.py @@ -110,13 +110,12 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) vollog.debug(f"Thread invalid address {ethread.vol.offset:#x}") return None - if owner_proc_pid == 4 or owner_proc.InheritedFromUniqueProcessId == 4: - vollog.debug( - f"Skipping kernel process with pid {owner_proc.InheritedFromUniqueProcessId}" - ) - return None - - if vads_cache is not None: + # don't look for VADs in kernel threads, just let them get reported with empty paths + if ( + owner_proc_pid != 4 + and owner_proc.InheritedFromUniqueProcessId != 4 + and vads_cache is not None + ): vads = pe_symbols.PESymbols.get_vads_for_process_cache( vads_cache, owner_proc )