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: "." 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/generic.py b/volatility3/cli/volshell/generic.py index 2321408fe..3a5d514fe 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.""" @@ -51,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( @@ -69,43 +84,70 @@ 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 + 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") + except ImportError: + 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 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 self.config.get("script-only"): + exit() + + if has_ipython: + self.__console() + else: + self.__console.interact(banner=banner) return renderers.TreeGrid([("Terminating", str)], None) @@ -277,23 +319,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.""" @@ -508,10 +552,13 @@ 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 accessor.open(url=location) as handle, io.TextIOWrapper( + handle, 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): 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/constants/_version.py b/volatility3/framework/constants/_version.py index aa8e8936f..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 = 23 # 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 = "" diff --git a/volatility3/framework/objects/__init__.py b/volatility3/framework/objects/__init__.py index 39ce6f59f..b863e103b 100644 --- a/volatility3/framework/objects/__init__.py +++ b/volatility3/framework/objects/__init__.py @@ -402,13 +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: + raw = self._get_raw_value( + self._context, self.vol.data_format, self.vol.layer_name, self.vol.offset + ) + return raw def dereference( self, layer_name: Optional[str] = None diff --git a/volatility3/framework/objects/utility.py b/volatility3/framework/objects/utility.py index 500c0e9a5..ef702060c 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -2,9 +2,11 @@ # 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: @@ -33,6 +35,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 +63,7 @@ def array_to_string( count=count, errors=errors, block_size=block_size, + encoding=encoding, ) @@ -68,6 +72,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,9 +99,101 @@ def pointer_to_string( count=count, errors=errors, block_size=block_size, + encoding=encoding, ) +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 + + 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"" + + if isinstance(data_layer, interfaces.layers.TranslationLayerInterface): + last_address = starting_address + + for address, length, _, _, _ in data_layer.mapping( + offset=starting_address, length=count, ignore_errors=True + ): + # we hit a swapped out page + if last_address != address: + break + + data += data_layer.read(address, length) + + last_address = 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 + if data: + return data + else: + raise exceptions.InvalidAddressException( + layer_name=data_layer, invalid_address=starting_address + ) + + +def bytes_to_decoded_string( + data: bytes, encoding: str, errors: str, return_truncated: bool = True +) -> str: + """ + 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 + + 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: + 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 = bytes(full_decoded_string[:idx], encoding=encoding) + + # 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, @@ -104,6 +201,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 +224,10 @@ 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) + data = gather_contiguous_bytes_from_address(context, layer, address, count) + + return bytes_to_decoded_string(data=data, errors=errors, encoding=encoding) def array_of_pointers( diff --git a/volatility3/framework/plugins/linux/bash.py b/volatility3/framework/plugins/linux/bash.py index 293c47224..2a63ac329 100644 --- a/volatility3/framework/plugins/linux/bash.py +++ b/volatility3/framework/plugins/linux/bash.py @@ -32,8 +32,13 @@ 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.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/linux/boottime.py b/volatility3/framework/plugins/linux/boottime.py index c57bdd65a..0b9abb856 100644 --- a/volatility3/framework/plugins/linux/boottime.py +++ b/volatility3/framework/plugins/linux/boottime.py @@ -25,8 +25,13 @@ 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="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/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/check_idt.py b/volatility3/framework/plugins/linux/check_idt.py index dbdb0e9be..e199d98d3 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__) @@ -34,14 +33,16 @@ 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="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), ), 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 +83,10 @@ 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( + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, ) idt_table_size = 256 @@ -134,19 +135,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/check_modules.py b/volatility3/framework/plugins/linux/check_modules.py index 76f75ec50..5a43bf899 100644 --- a/volatility3/framework/plugins/linux/check_modules.py +++ b/volatility3/framework/plugins/linux/check_modules.py @@ -3,15 +3,14 @@ # 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 +from volatility3.framework.interfaces import plugins vollog = logging.getLogger(__name__) @@ -19,9 +18,31 @@ vollog = logging.getLogger(__name__) class Check_modules(plugins.PluginInterface): """Compares module list to sysfs info, if available""" - _version = (2, 0, 0) + _version = (3, 0, 0) _required_framework_version = (2, 0, 0) + @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]: return [ @@ -31,9 +52,9 @@ class Check_modules(plugins.PluginInterface): architectures=["Intel32", "Intel64"], ), requirements.VersionRequirement( - name="linux_utilities_modules", - component=linux_utilities_modules.Modules, - version=(2, 0, 0), + name="linux_utilities_modules_module_display_plugin", + component=linux_utilities_modules.ModuleDisplayPlugin, + version=(1, 0, 0), ), ] @@ -41,30 +62,9 @@ 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 ) -> Dict[str, extensions.module]: return linux_utilities_modules.Modules.get_kset_modules(context, vmlinux_name) - - def _generator(self): - kset_modules = linux_utilities_modules.Modules.get_kset_modules( - self.context, self.config["kernel"] - ) - - lsmod_modules = set( - str(utility.array_to_string(modules.name)) - for modules in linux_utilities_modules.Modules.list_modules( - self.context, self.config["kernel"] - ) - ) - - 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(), - ) 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)) 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/hidden_modules.py b/volatility3/framework/plugins/linux/hidden_modules.py index 9891bf138..c6f5d749e 100644 --- a/volatility3/framework/plugins/linux/hidden_modules.py +++ b/volatility3/framework/plugins/linux/hidden_modules.py @@ -6,86 +6,21 @@ 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.plugins.linux import lsmod +from volatility3.framework.symbols.linux import extensions +from volatility3.framework.interfaces import plugins vollog = logging.getLogger(__name__) -class Hidden_modules(interfaces.plugins.PluginInterface): +class Hidden_modules(plugins.PluginInterface): """Carves memory to find hidden kernel modules""" _required_framework_version = (2, 10, 0) - _version = (2, 0, 0) + _version = (3, 0, 0) - @classmethod - def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: - return [ - requirements.ModuleRequirement( - name="kernel", - description="Linux kernel", - architectures=architectures.LINUX_ARCHS, - ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) - ), - requirements.VersionRequirement( - name="linux_utilities_modules", - component=linux_utilities_modules.Modules, - version=(2, 0, 0), - ), - ] - - @staticmethod - @deprecation.deprecated_method( - replacement=linux_utilities_modules.Modules.get_modules_memory_boundaries, - removal_date="2025-09-25", - replacement_version=(2, 0, 0), - ) - def get_modules_memory_boundaries( - context: interfaces.context.ContextInterface, - vmlinux_module_name: str, - ) -> Tuple[int, int]: - return linux_utilities_modules.Modules.get_modules_memory_boundaries( - context, vmlinux_module_name - ) - - @deprecation.deprecated_method( - replacement=linux_utilities_modules.Modules.get_module_address_alignment, - removal_date="2025-09-25", - replacement_version=(2, 0, 0), - ) - @classmethod - def _get_module_address_alignment( - cls, - context: interfaces.context.ContextInterface, - vmlinux_module_name: str, - ) -> int: - """Obtain the module memory address alignment. - - struct module is aligned to the L1 cache line, which is typically 64 bytes for most - common i386/AMD64/ARM64 configurations. In some cases, it can be 128 bytes, but this - will still work. - - 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 - - Returns: - The struct module alignment - """ - return linux_utilities_modules.get_module_address_alignment( - context, vmlinux_module_name - ) - - @deprecation.deprecated_method( - replacement=linux_utilities_modules.Modules.get_hidden_modules, - removal_date="2025-09-25", - replacement_version=(2, 0, 0), - ) @classmethod def get_hidden_modules( cls, @@ -120,11 +55,77 @@ class Hidden_modules(interfaces.plugins.PluginInterface): 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]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=architectures.LINUX_ARCHS, + ), + requirements.VersionRequirement( + name="linux_utilities_modules_module_display_plugin", + component=linux_utilities_modules.ModuleDisplayPlugin, + version=(1, 0, 0), + ), + ] + + @staticmethod + @deprecation.deprecated_method( + replacement=linux_utilities_modules.Modules.get_modules_memory_boundaries, + removal_date="2025-09-25", + replacement_version=(3, 0, 0), + ) + def get_modules_memory_boundaries( + context: interfaces.context.ContextInterface, + vmlinux_module_name: str, + ) -> Tuple[int, int]: + return linux_utilities_modules.Modules.get_modules_memory_boundaries( + context, vmlinux_module_name + ) + + @deprecation.deprecated_method( + replacement=linux_utilities_modules.Modules.get_module_address_alignment, + removal_date="2025-09-25", + replacement_version=(3, 0, 0), + ) + @classmethod + def _get_module_address_alignment( + cls, + context: interfaces.context.ContextInterface, + vmlinux_module_name: str, + ) -> int: + """Obtain the module memory address alignment. + + struct module is aligned to the L1 cache line, which is typically 64 bytes for most + common i386/AMD64/ARM64 configurations. In some cases, it can be 128 bytes, but this + will still work. + + 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 + + Returns: + The struct module alignment + """ + return linux_utilities_modules.get_module_address_alignment( + context, vmlinux_module_name + ) + + @deprecation.deprecated_method( + replacement=linux_utilities_modules.Modules.get_hidden_modules, + removal_date="2025-09-25", + replacement_version=(3, 0, 0), + ) @staticmethod @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], @@ -163,42 +164,35 @@ 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 - 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/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: diff --git a/volatility3/framework/plugins/linux/kallsyms.py b/volatility3/framework/plugins/linux/kallsyms.py index 7dd4f06e6..c8bca03f7 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: @@ -106,17 +110,25 @@ 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 # 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, + kassymbol.type or renderers.NotAvailableValue(), symbol_size, - kassymbol.exported, + exported, kassymbol.subsystem, kassymbol.module_name, kassymbol.name, diff --git a/volatility3/framework/plugins/linux/keyboard_notifiers.py b/volatility3/framework/plugins/linux/keyboard_notifiers.py index 8fd2846c1..215704350 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__) @@ -30,10 +29,12 @@ 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.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 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) @@ -43,12 +44,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 +60,12 @@ 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( + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, + ) + knl = vmlinux.object( object_type="atomic_notifier_head", offset=knl_addr.vol.offset, @@ -76,13 +77,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..4ed0e15b9 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__) @@ -34,36 +34,37 @@ 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="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0) ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(4, 0, 0) - ), - requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 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( + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, + ) + for task in pslist.PsList.list_tasks( self.context, vmlinux.name, include_threads=True ): @@ -86,9 +87,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 +100,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/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/lsmod.py b/volatility3/framework/plugins/linux/lsmod.py index b4f881801..3029d2541 100644 --- a/volatility3/framework/plugins/linux/lsmod.py +++ b/volatility3/framework/plugins/linux/lsmod.py @@ -7,11 +7,9 @@ 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__) @@ -20,7 +18,11 @@ class Lsmod(plugins.PluginInterface): """Lists loaded kernel modules.""" _required_framework_version = (2, 0, 0) - _version = (2, 0, 0) + _version = (3, 0, 0) + + 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]: @@ -33,14 +35,19 @@ class Lsmod(plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(2, 0, 0), + version=(3, 0, 0), + ), + requirements.VersionRequirement( + name="linux_utilities_modules_module_display_plugin", + component=linux_utilities_modules.ModuleDisplayPlugin, + version=(1, 0, 0), ), ] @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( @@ -49,25 +56,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/plugins/linux/lsof.py b/volatility3/framework/plugins/linux/lsof.py index 044e9238f..283eabca0 100644 --- a/volatility3/framework/plugins/linux/lsof.py +++ b/volatility3/framework/plugins/linux/lsof.py @@ -120,8 +120,13 @@ 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="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/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/modxview.py b/volatility3/framework/plugins/linux/modxview.py index f6a6f7727..ed21acfd1 100644 --- a/volatility3/framework/plugins/linux/modxview.py +++ b/volatility3/framework/plugins/linux/modxview.py @@ -34,7 +34,22 @@ 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_utilities_module_gatherer_lsmod", + component=linux_utilities_modules.ModuleGathererLsmod, + version=(1, 0, 0), + ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherer_sysfs", + component=linux_utilities_modules.ModuleGathererSysFs, + version=(1, 0, 0), + ), + requirements.VersionRequirement( + name="linux_utilities_module_gatherer_scanner", + component=linux_utilities_modules.ModuleGathererScanner, + version=(1, 0, 0), ), requirements.VersionRequirement( name="linux-tainting", component=tainting.Tainting, version=(1, 0, 0) @@ -50,7 +65,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 +88,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( @@ -89,35 +104,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_gatherers = [ + linux_utilities_modules.ModuleGathererLsmod, + linux_utilities_modules.ModuleGathererSysFs, + linux_utilities_modules.ModuleGathererScanner, + ] 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_gatherers=wanted_gatherers, + 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 gatherer in wanted_gatherers: # Iterate over each recovered module - for mod_info in run_results[plugin_name]: + 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(plugin_name) + aggregated_modules[mod_info.offset].append(gatherer.name) else: - aggregated_modules[mod_info.offset] = [plugin_name] + aggregated_modules[mod_info.offset] = [gatherer.name] - 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, ) @@ -125,7 +147,7 @@ spot modules presence and taints.""" taints = ",".join( tainting.Tainting.get_taints_parsed( self.context, - kernel_name, + self.config["kernel"], module.taints, True, ) @@ -136,9 +158,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.name in gatherers, + linux_utilities_modules.ModuleGathererSysFs.name in gatherers, + linux_utilities_modules.ModuleGathererScanner.name in gatherers, taints or NotAvailableValue(), ), ) @@ -149,7 +171,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/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/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index 9c0055a54..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 = (2, 0, 0) - _required_linuxutils_version = (2, 1, 0) - _required_lsmod_version = (2, 0, 0) + _required_linux_utilities_modules_version = (3, 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()) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py index 3d2db7fd7..0bb3b9263 100644 --- a/volatility3/framework/plugins/linux/pagecache.py +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -126,8 +126,13 @@ 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.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), requirements.ListRequirement( name="type", @@ -431,8 +436,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 +655,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 8931ca581..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", @@ -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/plugins/linux/pslist.py b/volatility3/framework/plugins/linux/pslist.py index 4c42fc992..2f0cc00b7 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", @@ -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/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/tracing/ftrace.py b/volatility3/framework/plugins/linux/tracing/ftrace.py index 17766cc74..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 @@ -79,7 +79,12 @@ 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.VersionRequirement( + name="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), ), requirements.BooleanRequirement( name="show_ftrace_flags", @@ -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 @@ -223,7 +225,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_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/perf_events.py b/volatility3/framework/plugins/linux/tracing/perf_events.py new file mode 100644 index 000000000..3e0a40579 --- /dev/null +++ b/volatility3/framework/plugins/linux/tracing/perf_events.py @@ -0,0 +1,145 @@ +# 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 = None + + 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 + + else: + program_address = None + + 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) + + # 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 or renderers.NotAvailableValue(), + program_name or renderers.NotAvailableValue(), + full_name or renderers.NotAvailableValue(), + 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(), + ) diff --git a/volatility3/framework/plugins/linux/tracing/tracepoints.py b/volatility3/framework/plugins/linux/tracing/tracepoints.py index fe6d11af9..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 @@ -52,7 +52,12 @@ class CheckTracepoints(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="linux_utilities_module_gatherers", + component=linux_utilities_modules.ModuleGatherers, + version=(1, 0, 0), ), ] @@ -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]]: @@ -229,7 +234,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_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 281d46eda..7d30b84ee 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__) @@ -33,10 +32,12 @@ 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.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 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) @@ -46,12 +47,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 +59,12 @@ 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( + context=self.context, + kernel_module_name=self.config["kernel"], + caller_wanted_gatherers=linux_utilities_modules.ModuleGatherers.all_gatherers_identifier, + ) + for tty in tty_drivers.to_list( vmlinux.symbol_table_name + constants.BANG + "tty_driver", "tty_drivers" ): @@ -87,13 +88,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( 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..4cbade1cf 100644 --- a/volatility3/framework/plugins/mac/bash.py +++ b/volatility3/framework/plugins/mac/bash.py @@ -30,8 +30,13 @@ 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.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 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/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, diff --git a/volatility3/framework/plugins/windows/amcache.py b/volatility3/framework/plugins/windows/amcache.py index 5920cd266..4ac5554e0 100644 --- a/volatility3/framework/plugins/windows/amcache.py +++ b/volatility3/framework/plugins/windows/amcache.py @@ -231,8 +231,13 @@ 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) + ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 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..8999e1bab 100644 --- a/volatility3/framework/plugins/windows/consoles.py +++ b/volatility3/framework/plugins/windows/consoles.py @@ -46,10 +46,13 @@ 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.PluginRequirement( - name="hivelist", plugin=hivelist.HiveList, 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) ), 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/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/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..57d365d00 100644 --- a/volatility3/framework/plugins/windows/driverscan.py +++ b/volatility3/framework/plugins/windows/driverscan.py @@ -24,8 +24,11 @@ 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) + ), + requirements.VersionRequirement( + name="modules", component=modules.Modules, 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..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 @@ -333,8 +341,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 +411,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/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/orphan_kernel_threads.py b/volatility3/framework/plugins/windows/orphan_kernel_threads.py index 151fe88c9..b4dec0fc5 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=(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) ), - 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/pe_symbols.py b/volatility3/framework/plugins/windows/pe_symbols.py index b26e8d113..3a08a1002 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 @@ -641,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 @@ -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 else: - yield symbol_key, value_index, symbol_value, wanted_value # type: ignore + yield symbol_key, symbol_value, wanted_value + + 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: 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/processghosting.py b/volatility3/framework/plugins/windows/processghosting.py index 5bc6bc5a3..7e7f6d3cc 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 @@ -31,54 +33,168 @@ 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 + 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 == 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(base_address), + format_hints.Hex(file_object_address), + delete_pending or renderers.NotApplicableValue(), + delete_on_close or renderers.NotApplicableValue(), + path, ) def run(self): @@ -88,8 +204,10 @@ class ProcessGhosting(interfaces.plugins.PluginInterface): [ ("PID", int), ("Process", str), + ("Base", format_hints.Hex), ("FILE_OBJECT", format_hints.Hex), - ("DeletePending", str), + ("DeletePending", int), + ("DeleteOnClose", int), ("Path", str), ], self._generator( 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 99fa9640b..ae37c20a1 100644 --- a/volatility3/framework/plugins/windows/psscan.py +++ b/volatility3/framework/plugins/windows/psscan.py @@ -33,8 +33,13 @@ 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="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/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/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..809d0b2b3 100644 --- a/volatility3/framework/plugins/windows/registry/userassist.py +++ b/volatility3/framework/plugins/windows/registry/userassist.py @@ -56,8 +56,13 @@ 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) + ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), ] diff --git a/volatility3/framework/plugins/windows/scheduled_tasks.py b/volatility3/framework/plugins/windows/scheduled_tasks.py index ba54e19ec..4247bda74 100644 --- a/volatility3/framework/plugins/windows/scheduled_tasks.py +++ b/volatility3/framework/plugins/windows/scheduled_tasks.py @@ -1123,8 +1123,13 @@ 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) + ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), ] diff --git a/volatility3/framework/plugins/windows/sessions.py b/volatility3/framework/plugins/windows/sessions.py index 73a537cd4..29d0b2104 100644 --- a/volatility3/framework/plugins/windows/sessions.py +++ b/volatility3/framework/plugins/windows/sessions.py @@ -27,8 +27,13 @@ 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.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), requirements.ListRequirement( name="pid", diff --git a/volatility3/framework/plugins/windows/shimcachemem.py b/volatility3/framework/plugins/windows/shimcachemem.py index f26bf3d6b..7883dfba3 100644 --- a/volatility3/framework/plugins/windows/shimcachemem.py +++ b/volatility3/framework/plugins/windows/shimcachemem.py @@ -64,8 +64,13 @@ 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="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/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/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/svclist.py b/volatility3/framework/plugins/windows/svclist.py index 00d4aa647..24ac2278f 100644 --- a/volatility3/framework/plugins/windows/svclist.py +++ b/volatility3/framework/plugins/windows/svclist.py @@ -31,8 +31,11 @@ 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.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 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/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 369db1fd8..0ac3d0c33 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) @@ -33,8 +33,16 @@ 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) + ), + requirements.VersionRequirement( + name="pe_symbols", component=pe_symbols.PESymbols, version=(3, 0, 0) + ), + requirements.VersionRequirement( + name="timeliner", + component=timeliner.TimeLinerInterface, + version=(1, 0, 0), ), ] @@ -67,27 +75,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 + # 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 + ) + if not vads or len(vads) < 5: + vollog.debug( + f"Not enough 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 +150,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 +223,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..d040fa990 100644 --- a/volatility3/framework/plugins/windows/threads.py +++ b/volatility3/framework/plugins/windows/threads.py @@ -31,8 +31,11 @@ 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=(2, 0, 0) + ), + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(3, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/timers.py b/volatility3/framework/plugins/windows/timers.py index 1f100bf1c..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 @@ -131,6 +132,7 @@ class Timers(interfaces.plugins.PluginInterface): ): if not timer.valid_type(): continue + try: dpc = timer.get_dpc() if dpc == 0: @@ -138,7 +140,10 @@ class Timers(interfaces.plugins.PluginInterface): if dpc.DeferredRoutine == 0: continue deferred_routine = dpc.DeferredRoutine - except Exception: + except exceptions.InvalidAddressException as exc: + vollog.debug( + f"Failed to get _KTIMER.Dpc due to {exc.__class__.__name__}" + ) continue module_symbols = list( 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/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 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/framework/plugins/windows/windows.py b/volatility3/framework/plugins/windows/windows.py new file mode 100644 index 000000000..9d4317df9 --- /dev/null +++ b/volatility3/framework/plugins/windows/windows.py @@ -0,0 +1,139 @@ +# 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.debug( + 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: + window_proc = renderers.NotAvailableValue() + elif window_proc == 0 or window_proc > 0x1000: + window_proc = format_hints.Hex(window_proc) + else: + vollog.debug( + f"Invalid window procedure {window_proc} 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/plugins/windows/windowstations.py b/volatility3/framework/plugins/windows/windowstations.py index cd02938cd..1f95b0531 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 @@ -96,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/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 39605cf52..b9ce84ed1 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""" @@ -256,14 +259,22 @@ 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 @@ -328,46 +339,70 @@ class module(generic.GenericIntelProcess): return None @property - def section_symtab(self): - if self.has_member("kallsyms"): - return self.kallsyms.symtab - elif self.has_member("symtab"): - return self.symtab + def section_symtab(self) -> Optional[interfaces.objects.ObjectInterface]: + 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")) + def num_symtab(self) -> Optional[int]: + 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 + def section_strtab(self) -> Optional[interfaces.objects.ObjectInterface]: + 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 + 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 + # 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") def get_symbol_type( self, symbol: interfaces.objects.ObjectInterface, symbol_index: int - ) -> str: + ) -> Optional[str]: """Determines the type of a given ELF symbol. Args: @@ -377,14 +412,20 @@ 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 @@ -1986,7 +2027,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 @@ -2048,11 +2092,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 @@ -2988,7 +3034,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 @@ -3005,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 @@ -3025,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 @@ -3036,7 +3090,13 @@ class kernel_symbol(objects.StructType): raise AttributeError("Unsupported kernel_symbol type implementation") - def get_namespace(self) -> str: + def 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 @@ -3055,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/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) diff --git a/volatility3/framework/symbols/linux/kallsyms.py b/volatility3/framework/symbols/linux/kallsyms.py index 298725a7a..35aba3ca5 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__) @@ -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 @@ -304,28 +309,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 +414,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 +510,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 +527,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 +544,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 +712,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) @@ -722,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, @@ -855,7 +911,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 +981,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 @@ -1033,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 @@ -1087,7 +1161,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() @@ -1252,23 +1328,33 @@ 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): - # 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 +1408,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 diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index 0eeb2b33c..f987c352e 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 ( @@ -9,38 +21,67 @@ 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.symbols.linux.utilities import tainting 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] + + # Must be set to a unique, descriptive name of the gathering technique or data structure source + name = 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.""" - _version = (2, 0, 0) + _version = (3, 0, 1) _required_framework_version = (2, 0, 0) framework.require_interface_version(*_required_framework_version) - class ModuleInfo(NamedTuple): - """ - Used to track the name and boundary of a kernel module - """ - - offset: int - name: str - 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. @@ -98,7 +139,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 @@ -189,98 +230,80 @@ 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, - ) -> 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 run_modules_scanners( cls, context: interfaces.context.ContextInterface, - kernel_name: str, - run_hidden_modules: bool = True, + kernel_module_name: str, + caller_wanted_gatherers: List[ModuleGathererInterface], 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. - Args: - run_hidden_modules: specify if the hidden_modules plugin should be run - Returns: - Dictionary mapping each plugin to its corresponding result - """ + Rules for `caller_wanted_gatherers`: + If `ModuleGatherers.all_gathers_identifier` is specified then every source will be populated - kernel = context.modules[kernel_name] + 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 gatherers + Returns: + Dictionary mapping each gatherer to its corresponding result + """ + 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") + + 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 gatherer.name: + raise ValueError( + 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: + 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 - run_results = {} + run_results: Dict[ModuleGathererInterface, List[ModuleInfo]] = {} - # the kernel module boundaries - run_results["kernel"] = [cls.get_kernel_module_info(context, kernel_name)] + # Walk each source gathering modules + for gatherer in caller_wanted_gatherers: + run_results[gatherer.name] = [] - # lsmod - run_results["lsmod"] = [] + # process each module coming from back the current source + for module in gatherer.gather_modules(context, kernel_module_name): - 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) + # the kernel sends back a ModuleInfo directly + if isinstance(module, ModuleInfo): + modinfo = module + else: + modinfo = cls.get_module_info_for_module(address_mask, module) - # check_modules - run_results["check_modules"] = [] - - sysfs_modules: dict = cls.get_kset_modules(context, kernel_name) - - 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[gatherer.name].append(modinfo) if flatten: return cls.flatten_run_modules_results(run_results) @@ -338,7 +361,7 @@ class Modules(interfaces.configuration.VersionableInterface): Returns: List of ModuleInfo objects """ - uniq_modules: List[Modules.ModuleInfo] = [] + uniq_modules: List[ModuleInfo] = [] seen_addresses: int = set() @@ -534,3 +557,439 @@ class Modules(interfaces.configuration.VersionableInterface): True if all the addresses meet the alignment """ 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): + """ + 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 + ) -> ModuleGathererInterface.gatherer_return_type: + yield from Modules.list_modules(context, kernel_module_name) + + +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 + ) -> 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 + """ + + _version = (1, 0, 0) + + name = "Scanner" + + @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 + """ + + _version = (1, 0, 0) + + name = "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, + interfaces.configuration.ConfigurableInterface, +): + _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, + ] + + @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 + + +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. + 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) + + @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, 1), + ), + 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 + ) + ) + + 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, + parameters, + ) + + 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(), + ) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 933178c91..75608cfc6 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__) @@ -1221,19 +1220,6 @@ class KTIMER(objects.StructType): return "Yes" 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, - ) - def valid_type(self): return self.Header.Type in self.VALID_TYPES @@ -1268,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 ) diff --git a/volatility3/framework/symbols/windows/extensions/gui.py b/volatility3/framework/symbols/windows/extensions/gui.py index 92693dba5..d1835631f 100644 --- a/volatility3/framework/symbols/windows/extensions/gui.py +++ b/volatility3/framework/symbols/windows/extensions/gui.py @@ -2,126 +2,332 @@ # 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 import framework 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: - sid = self.get_session_id() - return sid is not None and 0 <= sid < 256 - def get_session_id(self) -> Optional[int]: - try: - return self.dwSessionId - except exceptions.InvalidAddressException: - return None +class GUIExtensions(interfaces.configuration.VersionableInterface): + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) - def traverse(self, max_stations: int = 15): - """ - Traverses the window stations referenced in the list of stations - """ - seen = set() + framework.require_interface_version(*_required_framework_version) - # include the first window station - yield self + 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 - 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["GUIExtensions.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 + owning window station - """ - 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"]: - try: - return self.rpwinstaParent.dereference() - except exceptions.InvalidAddressException: return None - def get_session_id(self) -> Optional[int]: - 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 + + 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 not window.vol.offset: + 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 not window.vol.offset: + 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 not child.vol.offset: + 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" + ) - 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 + return self.strName.get_string() except exceptions.InvalidAddressException: - continue + vollog.debug( + f"strName for window at {self.vol.offset:#x} in layer {self.vol.layer_name} is invalid" + ) - yield thread, process_name, process_pid + 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() -class_types = { - "tagWINDOWSTATION": tagWINDOWSTATION, - "tagDESKTOP": tagDESKTOP, -} + 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", + ) + + 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", 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",