diff --git a/volatility3/framework/plugins/linux/malware/modxview.py b/volatility3/framework/plugins/linux/malware/modxview.py new file mode 100644 index 000000000..c1707d26f --- /dev/null +++ b/volatility3/framework/plugins/linux/malware/modxview.py @@ -0,0 +1,181 @@ +# 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, Dict, Iterator + +import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules + +from volatility3.framework import interfaces, deprecation, renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.renderers import format_hints +from volatility3.framework.symbols.linux import extensions +from volatility3.framework.constants import architectures +from volatility3.framework.symbols.linux.utilities import tainting + +vollog = logging.getLogger(__name__) + + +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) + + @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, + ), + ] + + @classmethod + @deprecation.deprecated_method( + replacement=linux_utilities_modules.Modules.flatten_run_modules_results, + replacement_version=(3, 0, 0), + removal_date="2025-09-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 + ) + + @classmethod + @deprecation.deprecated_method( + replacement=linux_utilities_modules.Modules.run_modules_scanners, + replacement_version=(3, 0, 0), + removal_date="2025-09-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(), + ), + ) + + 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(), + ) diff --git a/volatility3/framework/plugins/linux/modxview.py b/volatility3/framework/plugins/linux/modxview.py index c1707d26f..f710b1291 100644 --- a/volatility3/framework/plugins/linux/modxview.py +++ b/volatility3/framework/plugins/linux/modxview.py @@ -1,181 +1,22 @@ -# 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, Dict, Iterator - -import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules - -from volatility3.framework import interfaces, deprecation, renderers -from volatility3.framework.configuration import requirements -from volatility3.framework.renderers import format_hints -from volatility3.framework.symbols.linux import extensions -from volatility3.framework.constants import architectures -from volatility3.framework.symbols.linux.utilities import tainting +from volatility3.framework import interfaces, deprecation +from volatility3.plugins.linux.malware import modxview vollog = logging.getLogger(__name__) -class Modxview(interfaces.plugins.PluginInterface): +class Modxview( + interfaces.plugins.PluginInterface, + deprecation.PluginRenameClass, + replacement_class=modxview.Modxview, + removal_date="2026-06-07", +): + """Centralize lsmod, check_modules and hidden_modules results to efficiently \ -spot modules presence and taints.""" +spot modules presence and taints (deprecated).""" _version = (1, 0, 0) _required_framework_version = (2, 17, 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=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, - ), - ] - - @classmethod - @deprecation.deprecated_method( - replacement=linux_utilities_modules.Modules.flatten_run_modules_results, - replacement_version=(3, 0, 0), - removal_date="2025-09-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 - ) - - @classmethod - @deprecation.deprecated_method( - replacement=linux_utilities_modules.Modules.run_modules_scanners, - replacement_version=(3, 0, 0), - removal_date="2025-09-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(), - ), - ) - - 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(), - )