diff --git a/doc/source/getting-started-linux-tutorial.rst b/doc/source/getting-started-linux-tutorial.rst index 9a04ddcc1..3b1ca4c60 100644 --- a/doc/source/getting-started-linux-tutorial.rst +++ b/doc/source/getting-started-linux-tutorial.rst @@ -36,7 +36,7 @@ For plugin requests, please create an issue with a description of the requested $ python3 vol.py --help | grep -i linux. | head -n 5 banners.Banners Attempts to identify potential linux banners in an linux.bash.Bash Recovers bash command history from memory. - linux.check_afinfo.Check_afinfo + linux.malware.check_afinfo.Check_afinfo linux.check_creds.Check_creds linux.malware.check_idt.Check_idt diff --git a/test/plugins/linux/linux.py b/test/plugins/linux/linux.py index fad99ab60..4b4ebd916 100644 --- a/test/plugins/linux/linux.py +++ b/test/plugins/linux/linux.py @@ -426,7 +426,7 @@ class TestLinuxPageCacheInodepages: class TestLinuxCheckAfinfo: def test_linux_generic_check_afinfo(self, image, volatility, python): rc, out, _err = test_volatility.runvol_plugin( - "linux.check_afinfo.Check_afinfo", image, volatility, python + "linux.malware.check_afinfo.Check_afinfo", image, volatility, python ) # linux-sample-1.bin has no suspicious results. @@ -525,7 +525,7 @@ class TestLinuxHiddenModules: # TODO: this check should be specific, against a distinct infected sample image = LinuxSamples.LINUX_GENERIC.value.path rc, out, _err = test_volatility.runvol_plugin( - "linux.hidden_modules.Hidden_modules", image, volatility, python + "linux.malware.hidden_modules.Hidden_modules", image, volatility, python ) # linux-sample-1.bin has no hidden modules. diff --git a/volatility3/framework/plugins/linux/check_afinfo.py b/volatility3/framework/plugins/linux/check_afinfo.py index 47da21615..3f9e14161 100644 --- a/volatility3/framework/plugins/linux/check_afinfo.py +++ b/volatility3/framework/plugins/linux/check_afinfo.py @@ -1,215 +1,20 @@ -# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# 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 # -"""A module containing a plugin that verifies the operation function -pointers of network protocols.""" import logging -from typing import List, Tuple, Generator - -from volatility3.framework import exceptions, interfaces -from volatility3.framework import renderers -from volatility3.framework.configuration import requirements -from volatility3.framework.interfaces import plugins -from volatility3.framework.renderers import format_hints +from volatility3.framework import interfaces, deprecation +from volatility3.plugins.linux.malware import check_afinfo vollog = logging.getLogger(__name__) -class Check_afinfo(plugins.PluginInterface): - """Verifies the operation function pointers of network protocols.""" +class Check_afinfo( + interfaces.plugins.PluginInterface, + deprecation.PluginRenameClass, + replacement_class=check_afinfo.Check_afinfo, + removal_date="2026-06-07", +): + """Verifies the operation function pointers of network protocols (deprecated).""" _version = (1, 0, 0) _required_framework_version = (2, 0, 0) - - @classmethod - def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: - return [ - requirements.ModuleRequirement( - name="kernel", - description="Linux kernel", - architectures=["Intel32", "Intel64"], - ), - ] - - @classmethod - def _check_members( - cls, - context: interfaces.context.ContextInterface, - vmlinux_name: str, - var_ops: interfaces.objects.ObjectInterface, - var_name: str, - members: List[str], - ) -> Generator[Tuple[str, str, int], None, None]: - """ - Yields any members that are not pointing inside the kernel - """ - - vmlinux = context.modules[vmlinux_name] - - for check in members: - # redhat-specific garbage - if check.startswith("__UNIQUE_ID_rh_kabi_hide"): - continue - - # These structures have members like `write` and `next`, which are built in Python functions - addr = var_ops.member(attr=check) - - # Unimplemented handlers are set to 0 - if not addr: - continue - - if len(vmlinux.get_symbols_by_absolute_location(addr)) == 0: - yield var_name, check, addr - - @classmethod - def _check_pre_4_18_ops( - cls, - context: interfaces.context.ContextInterface, - vmlinux_name: str, - var_name: str, - var: interfaces.objects.ObjectInterface, - op_members: List[str], - seq_members: List[str], - ): - """ - Finds the correct way to reference `op_members` - """ - vmlinux = context.modules[vmlinux_name] - - if var.has_member("seq_fops"): - yield from cls._check_members( - context, vmlinux_name, var.seq_fops, var_name, op_members - ) - # newer kernels - if var.has_member("seq_ops"): - yield from cls._check_members( - context, vmlinux_name, var.seq_ops, var_name, seq_members - ) - - # this is the most commonly hooked member by rootkits, so a force a check on it - elif var.has_member("seq_show"): - if len(vmlinux.get_symbols_by_location(var.seq_show)) == 0: - yield var_name, "show", var.seq_show - else: - raise exceptions.VolatilityException( - "_check_afinfo_pre_4_18: Unable to find sequence operations members for checking." - ) - - @classmethod - def _check_afinfo_pre_4_18( - cls, - context: interfaces.context.ContextInterface, - vmlinux_name: str, - seq_members: str, - ) -> Generator[Tuple[str, str, int], None, None]: - """ - Checks the operations structures for network protocols of < 4.18 systems - """ - tcp = ("tcp_seq_afinfo", ["tcp6_seq_afinfo", "tcp4_seq_afinfo"]) - udp = ( - "udp_seq_afinfo", - [ - "udplite6_seq_afinfo", - "udp6_seq_afinfo", - "udplite4_seq_afinfo", - "udp4_seq_afinfo", - ], - ) - protocols = [tcp, udp] - - vmlinux = context.modules[vmlinux_name] - - op_members = vmlinux.get_type("file_operations").members - - # loop through all symbols - for struct_type, global_vars in protocols: - for global_var_name in global_vars: - # this will lookup fail for the IPv6 protocols on kernels without IPv6 support - try: - global_var = vmlinux.object_from_symbol(global_var_name) - except exceptions.SymbolError: - continue - - yield from cls._check_pre_4_18_ops( - context, - vmlinux_name, - global_var_name, - global_var, - op_members, - seq_members, - ) - - @classmethod - def _check_afinfo_post_4_18( - cls, - context: interfaces.context.ContextInterface, - vmlinux_name: str, - seq_members: str, - ) -> Generator[Tuple[str, str, int], None, None]: - """ - Checks the operations structures for network protocols of >= 4.18 systems - """ - vmlinux = context.modules[vmlinux_name] - - ops_structs = [ - "raw_seq_ops", - "udp_seq_ops", - "arp_seq_ops", - "unix_seq_ops", - "udp6_seq_ops", - "raw6_seq_ops", - "tcp_seq_ops", - "tcp4_seq_ops", - "tcp6_seq_ops", - "packet_seq_ops", - ] - - for protocol_ops_var in ops_structs: - # These will fail if the particular kernel doesn't have support for a protocol like IPv6 - try: - protocol_ops = vmlinux.object_from_symbol(protocol_ops_var) - except exceptions.SymbolError: - continue - - yield from cls._check_members( - context, vmlinux_name, protocol_ops, protocol_ops_var, seq_members - ) - - @classmethod - def check_afinfo( - cls, context: interfaces.context.ContextInterface, vmlinux_name - ) -> Generator[Tuple[str, str, int], None, None]: - """ - Walks the network protocol operations structures for common network protocols. - Reports any initialized operations members that do not point inside the kernel. - """ - vmlinux = context.modules[vmlinux_name] - - type_check = vmlinux.get_type("tcp_seq_afinfo") - if type_check.has_member("seq_fops"): - checker = cls._check_afinfo_pre_4_18 - else: - checker = cls._check_afinfo_post_4_18 - - seq_members = vmlinux.get_type("seq_operations").members - - yield from checker(context, vmlinux_name, seq_members) - - def _generator(self): - """ - A simple wrapper around `check_afino` - """ - for name, member, address in self.check_afinfo( - self.context, self.config["kernel"] - ): - yield 0, (name, member, format_hints.Hex(address)) - - def run(self): - return renderers.TreeGrid( - [ - ("Symbol Name", str), - ("Member", str), - ("Handler Address", format_hints.Hex), - ], - self._generator(), - ) diff --git a/volatility3/framework/plugins/linux/hidden_modules.py b/volatility3/framework/plugins/linux/hidden_modules.py index dcd602c5d..f7bdd6b0b 100644 --- a/volatility3/framework/plugins/linux/hidden_modules.py +++ b/volatility3/framework/plugins/linux/hidden_modules.py @@ -1,197 +1,20 @@ -# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0 +# 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, Set, Tuple, Iterable -from volatility3.framework.symbols.linux.utilities import ( - modules as linux_utilities_modules, -) -from volatility3.framework import interfaces, exceptions, deprecation -from volatility3.framework.configuration import requirements -from volatility3.framework.symbols.linux import extensions -from volatility3.framework.interfaces import plugins +from volatility3.framework import interfaces, deprecation +from volatility3.plugins.linux.malware import hidden_modules vollog = logging.getLogger(__name__) -class Hidden_modules(plugins.PluginInterface): - """Carves memory to find hidden kernel modules""" +class Hidden_modules( + interfaces.plugins.PluginInterface, + deprecation.PluginRenameClass, + replacement_class=hidden_modules.Hidden_modules, + removal_date="2026-06-07", +): + """Carves memory to find hidden kernel modules (deprecated).""" _required_framework_version = (2, 25, 0) _version = (3, 0, 2) - - @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" - ) - - 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, - ) - - @classmethod - def get_hidden_modules( - cls, - context: interfaces.context.ContextInterface, - vmlinux_module_name: str, - known_module_addresses: Set[int], - modules_memory_boundaries: Tuple, - ) -> Iterable[interfaces.objects.ObjectInterface]: - """Enumerate hidden modules by taking advantage of memory address alignment patterns - - This technique is much faster and uses less memory than the traditional scan method - in Volatility2, but it doesn't work with older kernels. - - From kernels 4.2 struct module allocation are aligned to the L1 cache line size. - In i386/amd64/arm64 this is typically 64 bytes. However, this can be changed in - the Linux kernel configuration via CONFIG_X86_L1_CACHE_SHIFT. The alignment can - also be obtained from the DWARF info i.e. DW_AT_alignment<64>, but dwarf2json - doesn't support this feature yet. - In kernels < 4.2, alignment attributes are absent in the struct module, meaning - alignment cannot be guaranteed. Therefore, for older kernels, it's better to use - the traditional scan technique. - - Args: - context: The context to retrieve required elements (layers, symbol tables) from - vmlinux_module_name: The name of the kernel module on which to operate - known_module_addresses: Set with known module addresses - modules_memory_boundaries: Minimum and maximum address boundaries for module allocation. - Yields: - module objects - """ - return linux_utilities_modules.get_hidden_modules( - vmlinux_module_name, known_module_addresses, modules_memory_boundaries - ) - - run = linux_utilities_modules.ModuleDisplayPlugin.run - _generator = linux_utilities_modules.ModuleDisplayPlugin.generator - implementation = find_hidden_modules - - @classmethod - def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: - return [ - requirements.VersionRequirement( - name="linux_utilities_modules_module_display_plugin", - component=linux_utilities_modules.ModuleDisplayPlugin, - version=(1, 0, 0), - ), - requirements.VersionRequirement( - name="linux_utilities_modules", - component=linux_utilities_modules.Modules, - version=(3, 0, 1), - ), - ] + linux_utilities_modules.ModuleDisplayPlugin.get_requirements() - - @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=(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 - ) - - @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 diff --git a/volatility3/framework/plugins/linux/malware/check_afinfo.py b/volatility3/framework/plugins/linux/malware/check_afinfo.py new file mode 100644 index 000000000..47da21615 --- /dev/null +++ b/volatility3/framework/plugins/linux/malware/check_afinfo.py @@ -0,0 +1,215 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""A module containing a plugin that verifies the operation function +pointers of network protocols.""" +import logging +from typing import List, Tuple, Generator + +from volatility3.framework import exceptions, interfaces +from volatility3.framework import renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.interfaces import plugins +from volatility3.framework.renderers import format_hints + +vollog = logging.getLogger(__name__) + + +class Check_afinfo(plugins.PluginInterface): + """Verifies the operation function pointers of network protocols.""" + + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=["Intel32", "Intel64"], + ), + ] + + @classmethod + def _check_members( + cls, + context: interfaces.context.ContextInterface, + vmlinux_name: str, + var_ops: interfaces.objects.ObjectInterface, + var_name: str, + members: List[str], + ) -> Generator[Tuple[str, str, int], None, None]: + """ + Yields any members that are not pointing inside the kernel + """ + + vmlinux = context.modules[vmlinux_name] + + for check in members: + # redhat-specific garbage + if check.startswith("__UNIQUE_ID_rh_kabi_hide"): + continue + + # These structures have members like `write` and `next`, which are built in Python functions + addr = var_ops.member(attr=check) + + # Unimplemented handlers are set to 0 + if not addr: + continue + + if len(vmlinux.get_symbols_by_absolute_location(addr)) == 0: + yield var_name, check, addr + + @classmethod + def _check_pre_4_18_ops( + cls, + context: interfaces.context.ContextInterface, + vmlinux_name: str, + var_name: str, + var: interfaces.objects.ObjectInterface, + op_members: List[str], + seq_members: List[str], + ): + """ + Finds the correct way to reference `op_members` + """ + vmlinux = context.modules[vmlinux_name] + + if var.has_member("seq_fops"): + yield from cls._check_members( + context, vmlinux_name, var.seq_fops, var_name, op_members + ) + # newer kernels + if var.has_member("seq_ops"): + yield from cls._check_members( + context, vmlinux_name, var.seq_ops, var_name, seq_members + ) + + # this is the most commonly hooked member by rootkits, so a force a check on it + elif var.has_member("seq_show"): + if len(vmlinux.get_symbols_by_location(var.seq_show)) == 0: + yield var_name, "show", var.seq_show + else: + raise exceptions.VolatilityException( + "_check_afinfo_pre_4_18: Unable to find sequence operations members for checking." + ) + + @classmethod + def _check_afinfo_pre_4_18( + cls, + context: interfaces.context.ContextInterface, + vmlinux_name: str, + seq_members: str, + ) -> Generator[Tuple[str, str, int], None, None]: + """ + Checks the operations structures for network protocols of < 4.18 systems + """ + tcp = ("tcp_seq_afinfo", ["tcp6_seq_afinfo", "tcp4_seq_afinfo"]) + udp = ( + "udp_seq_afinfo", + [ + "udplite6_seq_afinfo", + "udp6_seq_afinfo", + "udplite4_seq_afinfo", + "udp4_seq_afinfo", + ], + ) + protocols = [tcp, udp] + + vmlinux = context.modules[vmlinux_name] + + op_members = vmlinux.get_type("file_operations").members + + # loop through all symbols + for struct_type, global_vars in protocols: + for global_var_name in global_vars: + # this will lookup fail for the IPv6 protocols on kernels without IPv6 support + try: + global_var = vmlinux.object_from_symbol(global_var_name) + except exceptions.SymbolError: + continue + + yield from cls._check_pre_4_18_ops( + context, + vmlinux_name, + global_var_name, + global_var, + op_members, + seq_members, + ) + + @classmethod + def _check_afinfo_post_4_18( + cls, + context: interfaces.context.ContextInterface, + vmlinux_name: str, + seq_members: str, + ) -> Generator[Tuple[str, str, int], None, None]: + """ + Checks the operations structures for network protocols of >= 4.18 systems + """ + vmlinux = context.modules[vmlinux_name] + + ops_structs = [ + "raw_seq_ops", + "udp_seq_ops", + "arp_seq_ops", + "unix_seq_ops", + "udp6_seq_ops", + "raw6_seq_ops", + "tcp_seq_ops", + "tcp4_seq_ops", + "tcp6_seq_ops", + "packet_seq_ops", + ] + + for protocol_ops_var in ops_structs: + # These will fail if the particular kernel doesn't have support for a protocol like IPv6 + try: + protocol_ops = vmlinux.object_from_symbol(protocol_ops_var) + except exceptions.SymbolError: + continue + + yield from cls._check_members( + context, vmlinux_name, protocol_ops, protocol_ops_var, seq_members + ) + + @classmethod + def check_afinfo( + cls, context: interfaces.context.ContextInterface, vmlinux_name + ) -> Generator[Tuple[str, str, int], None, None]: + """ + Walks the network protocol operations structures for common network protocols. + Reports any initialized operations members that do not point inside the kernel. + """ + vmlinux = context.modules[vmlinux_name] + + type_check = vmlinux.get_type("tcp_seq_afinfo") + if type_check.has_member("seq_fops"): + checker = cls._check_afinfo_pre_4_18 + else: + checker = cls._check_afinfo_post_4_18 + + seq_members = vmlinux.get_type("seq_operations").members + + yield from checker(context, vmlinux_name, seq_members) + + def _generator(self): + """ + A simple wrapper around `check_afino` + """ + for name, member, address in self.check_afinfo( + self.context, self.config["kernel"] + ): + yield 0, (name, member, format_hints.Hex(address)) + + def run(self): + return renderers.TreeGrid( + [ + ("Symbol Name", str), + ("Member", str), + ("Handler Address", format_hints.Hex), + ], + self._generator(), + ) diff --git a/volatility3/framework/plugins/linux/malware/hidden_modules.py b/volatility3/framework/plugins/linux/malware/hidden_modules.py new file mode 100644 index 000000000..dcd602c5d --- /dev/null +++ b/volatility3/framework/plugins/linux/malware/hidden_modules.py @@ -0,0 +1,197 @@ +# 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 List, Set, Tuple, Iterable +from volatility3.framework.symbols.linux.utilities import ( + modules as linux_utilities_modules, +) +from volatility3.framework import interfaces, exceptions, deprecation +from volatility3.framework.configuration import requirements +from volatility3.framework.symbols.linux import extensions +from volatility3.framework.interfaces import plugins + +vollog = logging.getLogger(__name__) + + +class Hidden_modules(plugins.PluginInterface): + """Carves memory to find hidden kernel modules""" + + _required_framework_version = (2, 25, 0) + _version = (3, 0, 2) + + @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" + ) + + 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, + ) + + @classmethod + def get_hidden_modules( + cls, + context: interfaces.context.ContextInterface, + vmlinux_module_name: str, + known_module_addresses: Set[int], + modules_memory_boundaries: Tuple, + ) -> Iterable[interfaces.objects.ObjectInterface]: + """Enumerate hidden modules by taking advantage of memory address alignment patterns + + This technique is much faster and uses less memory than the traditional scan method + in Volatility2, but it doesn't work with older kernels. + + From kernels 4.2 struct module allocation are aligned to the L1 cache line size. + In i386/amd64/arm64 this is typically 64 bytes. However, this can be changed in + the Linux kernel configuration via CONFIG_X86_L1_CACHE_SHIFT. The alignment can + also be obtained from the DWARF info i.e. DW_AT_alignment<64>, but dwarf2json + doesn't support this feature yet. + In kernels < 4.2, alignment attributes are absent in the struct module, meaning + alignment cannot be guaranteed. Therefore, for older kernels, it's better to use + the traditional scan technique. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + vmlinux_module_name: The name of the kernel module on which to operate + known_module_addresses: Set with known module addresses + modules_memory_boundaries: Minimum and maximum address boundaries for module allocation. + Yields: + module objects + """ + return linux_utilities_modules.get_hidden_modules( + vmlinux_module_name, known_module_addresses, modules_memory_boundaries + ) + + run = linux_utilities_modules.ModuleDisplayPlugin.run + _generator = linux_utilities_modules.ModuleDisplayPlugin.generator + implementation = find_hidden_modules + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.VersionRequirement( + name="linux_utilities_modules_module_display_plugin", + component=linux_utilities_modules.ModuleDisplayPlugin, + version=(1, 0, 0), + ), + requirements.VersionRequirement( + name="linux_utilities_modules", + component=linux_utilities_modules.Modules, + version=(3, 0, 1), + ), + ] + linux_utilities_modules.ModuleDisplayPlugin.get_requirements() + + @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=(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 + ) + + @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