# 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 Dict, Iterator, List
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
from volatility3.framework import deprecation, interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.constants import architectures
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols.linux import extensions
from volatility3.framework.symbols.linux.utilities import tainting
vollog = logging.getLogger(__name__)
[docs]
class Modxview(interfaces.plugins.PluginInterface):
"""Centralize lsmod, check_modules and hidden_modules results to efficiently \
spot modules presence and taints."""
_version = (1, 0, 0)
_required_framework_version = (2, 17, 0)
[docs]
@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=linux_utilities_modules.Modules,
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)
),
requirements.BooleanRequirement(
name="plain_taints",
description="Display the plain taints string for each module.",
optional=True,
default=False,
),
]
[docs]
@classmethod
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.flatten_run_modules_results,
replacement_version=(3, 0, 0),
removal_date="2026-03-25",
)
def flatten_run_modules_results(
cls, run_results: Dict[str, List[extensions.module]], deduplicate: bool = True
) -> Iterator[extensions.module]:
"""Flatten a dictionary mapping plugin names and modules list, to a single merged list.
This is useful to get a generic lookup list of all the detected modules.
Args:
run_results: dictionary of plugin names mapping a list of detected modules
deduplicate: remove duplicate modules, based on their offsets
Returns:
Iterator of modules objects
"""
return linux_utilities_modules.Modules.flatten_run_modules_results(
run_results, deduplicate
)
[docs]
@classmethod
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.run_modules_scanners,
replacement_version=(3, 0, 0),
removal_date="2026-03-25",
)
def run_modules_scanners(
cls,
context: interfaces.context.ContextInterface,
kernel_name: str,
run_hidden_modules: bool = True,
) -> Dict[str, List[extensions.module]]:
"""Run module scanning plugins and aggregate the results. It is designed
to not operate any inter-plugin results triage."""
return linux_utilities_modules.Modules.run_modules_scanners(
context, kernel_name, run_hidden_modules
)
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
wanted_gatherers = [
linux_utilities_modules.ModuleGathererLsmod,
linux_utilities_modules.ModuleGathererSysFs,
linux_utilities_modules.ModuleGathererScanner,
]
run_results = linux_utilities_modules.Modules.run_modules_scanners(
context=self.context,
kernel_module_name=self.config["kernel"],
caller_wanted_gatherers=wanted_gatherers,
flatten=False,
)
aggregated_modules = {}
# We want to be explicit on the plugins results we are interested in
for gatherer in wanted_gatherers:
# Iterate over each recovered module
for mod_info in run_results[gatherer.name]:
# Use offsets as unique keys, whether a module
# appears in many plugin runs or not
if aggregated_modules.get(mod_info.offset, None) is not None:
# Append the plugin to the list of originating plugins
aggregated_modules[mod_info.offset].append(gatherer.name)
else:
aggregated_modules[mod_info.offset] = [gatherer.name]
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,
self.config["kernel"],
module.taints,
True,
)
else:
taints = ",".join(
tainting.Tainting.get_taints_parsed(
self.context,
self.config["kernel"],
module.taints,
True,
)
)
yield (
0,
(
module.get_name() or renderers.NotAvailableValue(),
format_hints.Hex(module_offset),
linux_utilities_modules.ModuleGathererLsmod.name in gatherers,
linux_utilities_modules.ModuleGathererSysFs.name in gatherers,
linux_utilities_modules.ModuleGathererScanner.name in gatherers,
taints or renderers.NotAvailableValue(),
),
)
[docs]
def run(self):
columns = [
("Name", str),
("Address", format_hints.Hex),
("In procfs", bool),
("In sysfs", bool),
("In scan", bool),
("Taints", str),
]
return renderers.TreeGrid(
columns,
self._generator(),
)