Source code for volatility3.plugins.linux.malware.hidden_modules

# This file is Copyright 2024 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 Generator, Iterable, List, Set, Tuple

from volatility3.framework import (
    constants,
    deprecation,
    exceptions,
    interfaces,
    renderers,
)
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.symbols.linux import extensions
from volatility3.framework.symbols.linux.utilities import (
    modules as linux_utilities_modules,
)

vollog = logging.getLogger(__name__)


[docs] class Hidden_modules(plugins.PluginInterface): """Carves memory to find hidden kernel modules""" _required_framework_version = (2, 25, 0) _version = (3, 0, 3)
[docs] @classmethod def find_hidden_modules( cls, context, vmlinux_module_name: str ) -> Generator[extensions.module, None, None]: 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" ) 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, )
[docs] @classmethod def get_hidden_modules( cls, context: interfaces.context.ContextInterface, vmlinux_module_name: str, known_module_addresses: Set[int], modules_memory_boundaries: Tuple, ) -> Iterable[interfaces.objects.ObjectInterface]: """Enumerate hidden modules by taking advantage of memory address alignment patterns This technique is much faster and uses less memory than the traditional scan method in Volatility2, but it doesn't work with older kernels. From kernels 4.2 struct module allocation are aligned to the L1 cache line size. In i386/amd64/arm64 this is typically 64 bytes. However, this can be changed in the Linux kernel configuration via CONFIG_X86_L1_CACHE_SHIFT. The alignment can also be obtained from the DWARF info i.e. DW_AT_alignment<64>, but dwarf2json doesn't support this feature yet. In kernels < 4.2, alignment attributes are absent in the struct module, meaning alignment cannot be guaranteed. Therefore, for older kernels, it's better to use the traditional scan technique. Args: context: The context to retrieve required elements (layers, symbol tables) from vmlinux_module_name: The name of the kernel module on which to operate known_module_addresses: Set with known module addresses modules_memory_boundaries: Minimum and maximum address boundaries for module allocation. Yields: module objects """ return linux_utilities_modules.get_hidden_modules( vmlinux_module_name, known_module_addresses, modules_memory_boundaries )
implementation = find_hidden_modules
[docs] @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: return [ requirements.ModuleRequirement( name="kernel", description="Linux kernel", architectures=constants.architectures.LINUX_ARCHS, ), requirements.VersionRequirement( name="linux_utilities_modules_module_display_plugin", component=linux_utilities_modules.ModuleDisplayPlugin, version=(2, 0, 0), ), requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, version=(3, 0, 1), ), requirements.BooleanRequirement( name="dump", description="Extract listed modules", default=False, optional=True, ), ]
[docs] @staticmethod @deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.get_modules_memory_boundaries, removal_date="2026-03-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="2026-03-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="2026-03-25", replacement_version=(3, 0, 0), ) @staticmethod @deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.validate_alignment_patterns, removal_date="2026-03-25", replacement_version=(3, 0, 0), ) def _validate_alignment_patterns( addresses: Iterable[int], address_alignment: int, ) -> bool: """Check if the memory addresses meet our alignments patterns Args: addresses: Iterable with the address values address_alignment: Number of bytes for alignment validation Returns: True if all the addresses meet the alignment """ return linux_utilities_modules.validate_alignment_patterns( addresses, address_alignment )
[docs] @classmethod def get_lsmod_module_addresses( cls, context: interfaces.context.ContextInterface, vmlinux_module_name: str, ) -> Set[int]: """Obtain a set the known module addresses from linux.lsmod plugin 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: A set containing known kernel module addresses """ vmlinux = context.modules[vmlinux_module_name] vmlinux_layer = context.layers[vmlinux.layer_name] known_module_addresses = { vmlinux_layer.canonicalize(module.vol.offset) for module in linux_utilities_modules.Modules.list_modules( context, vmlinux_module_name ) } return known_module_addresses
[docs] def run(self): return renderers.TreeGrid( linux_utilities_modules.ModuleDisplayPlugin.columns_results, self._generator(), )
def _generator(self): yield from linux_utilities_modules.ModuleDisplayPlugin.generate_results( self.context, self.implementation, self.config["kernel"], self.config["dump"], self.open, )