Linux: Rework module calls to parameterize rather than pull in methods

This commit is contained in:
Mike Auty
2025-11-19 16:26:54 +00:00
parent 3e2d831be3
commit 81f135f315
4 changed files with 145 additions and 72 deletions
+37 -8
View File
@@ -4,10 +4,10 @@
"""A module containing a plugin that lists loaded kernel modules."""
import logging
from typing import Iterable, List
from typing import List, Iterable
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
from volatility3.framework import deprecation, interfaces
from volatility3.framework import constants, interfaces, deprecation, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
@@ -18,27 +18,41 @@ class Lsmod(plugins.PluginInterface):
"""Lists loaded kernel modules."""
_required_framework_version = (2, 0, 0)
_version = (3, 0, 1)
_version = (3, 0, 3)
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=constants.architectures.LINUX_ARCHS,
),
requirements.VersionRequirement(
name="linux_utilities_modules",
component=linux_utilities_modules.Modules,
version=(3, 0, 0),
),
requirements.VersionRequirement(
name="linux_utilities_modules_module_display_plugin",
component=linux_utilities_modules.ModuleDisplayPlugin,
version=(1, 0, 0),
version=(2, 0, 0),
),
] + linux_utilities_modules.ModuleDisplayPlugin.get_requirements()
requirements.BooleanRequirement(
name="dump",
description="Extract listed modules",
default=False,
optional=True,
),
]
@classmethod
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.list_modules,
replacement_version=(3, 0, 0),
removal_date="2026-03-25",
removal_date="2025-09-25",
)
def list_modules(
cls, context: interfaces.context.ContextInterface, vmlinux_module_name: str
@@ -46,3 +60,18 @@ class Lsmod(plugins.PluginInterface):
return linux_utilities_modules.Modules.list_modules(
context, vmlinux_module_name
)
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,
)
@@ -3,19 +3,18 @@
#
import logging
from typing import Dict, Generator, List
from typing import List, Dict, Generator
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
from volatility3.framework import deprecation, interfaces
from volatility3.framework import constants, interfaces, deprecation, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.framework.symbols.linux import extensions
vollog = logging.getLogger(__name__)
class Check_modules(plugins.PluginInterface):
class Check_modules(interfaces.plugins.PluginInterface):
"""Compares module list to sysfs info, if available"""
_version = (3, 0, 1)
@@ -23,7 +22,7 @@ class Check_modules(plugins.PluginInterface):
@classmethod
def compare_kset_and_lsmod(
cls, context: str, vmlinux_name: str
cls, context: interfaces.context.ContextInterface, vmlinux_name: str
) -> Generator[extensions.module, None, None]:
kset_modules = linux_utilities_modules.Modules.get_kset_modules(
context=context, vmlinux_name=vmlinux_name
@@ -39,13 +38,16 @@ class Check_modules(plugins.PluginInterface):
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 [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=constants.architectures.LINUX_ARCHS,
),
requirements.VersionRequirement(
name="modules",
component=linux_utilities_modules.Modules,
@@ -54,17 +56,38 @@ class Check_modules(plugins.PluginInterface):
requirements.VersionRequirement(
name="linux_utilities_modules_module_display_plugin",
component=linux_utilities_modules.ModuleDisplayPlugin,
version=(1, 0, 0),
version=(2, 0, 0),
),
] + linux_utilities_modules.ModuleDisplayPlugin.get_requirements()
requirements.BooleanRequirement(
name="dump",
description="Extract listed modules",
default=False,
optional=True,
),
]
@classmethod
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.get_kset_modules,
removal_date="2026-03-25",
removal_date="2025-09-25",
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 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,
)
@@ -2,15 +2,20 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import Iterable, List, Set, Tuple
from volatility3.framework import deprecation, exceptions, interfaces
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.symbols.linux import extensions
from typing import List, Set, Tuple, Iterable, Generator
from volatility3.framework.symbols.linux.utilities import (
modules as linux_utilities_modules,
)
from volatility3.framework import (
constants,
interfaces,
exceptions,
deprecation,
renderers,
)
from volatility3.framework.configuration import requirements
from volatility3.framework.symbols.linux import extensions
from volatility3.framework.interfaces import plugins
vollog = logging.getLogger(__name__)
@@ -19,12 +24,12 @@ class Hidden_modules(plugins.PluginInterface):
"""Carves memory to find hidden kernel modules"""
_required_framework_version = (2, 25, 0)
_version = (3, 0, 2)
_version = (3, 0, 3)
@classmethod
def find_hidden_modules(
cls, context, vmlinux_module_name: str
) -> extensions.module:
) -> Generator[extensions.module, None, None]:
if context.symbol_space.verify_table_versions(
"dwarf2json", lambda version, _: (not version) or version < (0, 8, 0)
):
@@ -82,29 +87,38 @@ class Hidden_modules(plugins.PluginInterface):
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.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=(1, 0, 0),
version=(2, 0, 0),
),
requirements.VersionRequirement(
name="linux_utilities_modules",
component=linux_utilities_modules.Modules,
version=(3, 0, 1),
),
] + linux_utilities_modules.ModuleDisplayPlugin.get_requirements()
requirements.BooleanRequirement(
name="dump",
description="Extract listed modules",
default=False,
optional=True,
),
]
@staticmethod
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.get_modules_memory_boundaries,
removal_date="2026-03-25",
removal_date="2025-09-25",
replacement_version=(3, 0, 0),
)
def get_modules_memory_boundaries(
@@ -117,7 +131,7 @@ class Hidden_modules(plugins.PluginInterface):
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.get_module_address_alignment,
removal_date="2026-03-25",
removal_date="2025-09-25",
replacement_version=(3, 0, 0),
)
@classmethod
@@ -145,13 +159,13 @@ class Hidden_modules(plugins.PluginInterface):
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.get_hidden_modules,
removal_date="2026-03-25",
removal_date="2025-09-25",
replacement_version=(3, 0, 0),
)
@staticmethod
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.validate_alignment_patterns,
removal_date="2026-03-25",
removal_date="2025-09-25",
replacement_version=(3, 0, 0),
)
def _validate_alignment_patterns(
@@ -196,3 +210,18 @@ class Hidden_modules(plugins.PluginInterface):
)
}
return known_module_addresses
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,
)
@@ -1,6 +1,7 @@
import logging
import warnings
from typing import (
Callable,
Iterable,
Iterator,
List,
@@ -23,7 +24,6 @@ from volatility3.framework import (
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
@@ -382,7 +382,7 @@ class Modules(interfaces.configuration.VersionableInterface):
vmlinux_module_name: str,
known_module_addresses: Set[int],
modules_memory_boundaries: Tuple,
) -> Iterable[interfaces.objects.ObjectInterface]:
) -> Iterable[extensions.module]:
"""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
@@ -919,19 +919,11 @@ class ModuleDisplayPlugin(interfaces.configuration.VersionableInterface):
The constructor of the plugin must call super() with the `implementation` set
"""
_version = (1, 0, 1)
_required_framework_version = (2, 0, 0)
framework.require_interface_version(*_required_framework_version)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=architectures.LINUX_ARCHS,
),
requirements.VersionRequirement(
name="linux_utilities_modules",
component=Modules,
@@ -940,25 +932,29 @@ class ModuleDisplayPlugin(interfaces.configuration.VersionableInterface):
requirements.VersionRequirement(
name="linux-tainting", component=tainting.Tainting, version=(1, 0, 0)
),
requirements.BooleanRequirement(
name="dump",
description="Extract listed modules",
default=False,
optional=True,
),
]
def generator(self):
@classmethod
def generate_results(
cls,
context: interfaces.context.ContextInterface,
implementation: Callable[
[interfaces.context.ContextInterface, str], Iterable[extensions.module]
],
kernel_module_name: str,
dump: bool,
open_implementation: Optional[interfaces.plugins.FileHandlerInterface],
):
"""
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"]):
for module in implementation(context, kernel_module_name):
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}"
f"Unable to recover name for module {module.vol.offset:#x} from implementation {implementation}"
)
continue
@@ -968,21 +964,21 @@ class ModuleDisplayPlugin(interfaces.configuration.VersionableInterface):
taints = ",".join(
tainting.Tainting.get_taints_parsed(
self.context, self.config["kernel"], module.taints, True
context, kernel_module_name, module.taints, True
)
)
parameters_iter = Modules.get_load_parameters(
self.context, self.config["kernel"], module
context, kernel_module_name, module
)
parameters = ", ".join([f"{key}={value}" for key, value in parameters_iter])
file_name = renderers.NotApplicableValue()
if self.config["dump"]:
if open_implementation:
elf_data = linux_utilities_module_extract.ModuleExtract.extract_module(
self.context, self.config["kernel"], module
context, kernel_module_name, module
)
if not elf_data:
vollog.warning(
@@ -990,11 +986,11 @@ class ModuleDisplayPlugin(interfaces.configuration.VersionableInterface):
)
file_name = renderers.NotAvailableValue()
else:
file_name = self.open.sanitize_filename(
file_name = open_implementation.sanitize_filename(
f"kernel_module.{name}.{module.vol.offset:#x}.elf"
)
with self.open(file_name) as file_handle:
with open_implementation(file_name) as file_handle:
file_handle.write(elf_data)
yield (
@@ -1009,15 +1005,11 @@ class ModuleDisplayPlugin(interfaces.configuration.VersionableInterface):
),
)
def run(self):
return renderers.TreeGrid(
[
("Offset", format_hints.Hex),
("Module Name", str),
("Code Size", format_hints.Hex),
("Taints", str),
("Load Arguments", str),
("File Output", str),
],
self._generator(),
)
columns_results = [
("Offset", format_hints.Hex),
("Module Name", str),
("Code Size", format_hints.Hex),
("Taints", str),
("Load Arguments", str),
("File Output", str),
]