Create versioned parent class for all plugins that enumerate Linux kernel modules. Convert plugins to new method.

This commit is contained in:
Andrew Case
2025-03-17 18:56:05 -05:00
parent 93e2072509
commit 7146b45fa7
4 changed files with 145 additions and 87 deletions
@@ -3,25 +3,26 @@
#
import logging
from typing import List, Dict
from typing import List, Dict, Generator
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
from volatility3.framework import interfaces, renderers, deprecation
from volatility3.framework import interfaces, deprecation
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols.linux import extensions
vollog = logging.getLogger(__name__)
class Check_modules(plugins.PluginInterface):
class Check_modules(linux_utilities_modules.ModuleDisplayPlugin):
"""Compares module list to sysfs info, if available"""
_version = (2, 0, 0)
_version = (3, 0, 0)
_required_framework_version = (2, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(self.compare_kset_and_lsmod, *args, **kwargs)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
@@ -31,9 +32,9 @@ class Check_modules(plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="linux_utilities_modules",
component=linux_utilities_modules.Modules,
version=(3, 0, 0),
name="linux_utilities_modules_module_display_plugin",
component=linux_utilities_modules.ModuleDisplayPlugin,
version=(1, 0, 0),
),
]
@@ -48,23 +49,20 @@ class Check_modules(plugins.PluginInterface):
) -> Dict[str, extensions.module]:
return linux_utilities_modules.Modules.get_kset_modules(context, vmlinux_name)
def _generator(self):
@classmethod
def compare_kset_and_lsmod(
cls, context: str, vmlinux_name: str
) -> Generator[extensions.module, None, None]:
kset_modules = linux_utilities_modules.Modules.get_kset_modules(
self.context, self.config["kernel"]
context=context, vmlinux_name=vmlinux_name
)
lsmod_modules = set(
str(utility.array_to_string(modules.name))
for modules in linux_utilities_modules.Modules.list_modules(
self.context, self.config["kernel"]
context=context, vmlinux_module_name=vmlinux_name
)
)
for mod_name in set(kset_modules.keys()).difference(lsmod_modules):
yield (0, (format_hints.Hex(kset_modules[mod_name]), str(mod_name)))
def run(self):
return renderers.TreeGrid(
[("Module Address", format_hints.Hex), ("Module Name", str)],
self._generator(),
)
yield kset_modules[mod_name]
@@ -6,19 +6,22 @@ from typing import List, Set, Tuple, Iterable
from volatility3.framework.symbols.linux.utilities import (
modules as linux_utilities_modules,
)
from volatility3.framework import renderers, interfaces, exceptions, deprecation
from volatility3.framework import interfaces, exceptions, deprecation
from volatility3.framework.constants import architectures
from volatility3.framework.renderers import format_hints
from volatility3.framework.configuration import requirements
from volatility3.framework.symbols.linux import extensions
vollog = logging.getLogger(__name__)
class Hidden_modules(interfaces.plugins.PluginInterface):
class Hidden_modules(linux_utilities_modules.ModuleDisplayPlugin):
"""Carves memory to find hidden kernel modules"""
_required_framework_version = (2, 10, 0)
_version = (2, 0, 0)
_version = (3, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(self.find_hidden_modules, *args, **kwargs)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -29,9 +32,9 @@ class Hidden_modules(interfaces.plugins.PluginInterface):
architectures=architectures.LINUX_ARCHS,
),
requirements.VersionRequirement(
name="linux_utilities_modules",
component=linux_utilities_modules.Modules,
version=(3, 0, 0),
name="linux_utilities_modules_module_display_plugin",
component=linux_utilities_modules.ModuleDisplayPlugin,
version=(1, 0, 0),
),
]
@@ -165,38 +168,29 @@ class Hidden_modules(interfaces.plugins.PluginInterface):
}
return known_module_addresses
def _generator(self):
vmlinux_module_name = self.config["kernel"]
known_module_addresses = self.get_lsmod_module_addresses(
self.context, vmlinux_module_name
)
modules_memory_boundaries = (
linux_utilities_modules.Modules.get_modules_memory_boundaries(
self.context, vmlinux_module_name
)
)
for module in linux_utilities_modules.Modules.get_hidden_modules(
self.context,
vmlinux_module_name,
known_module_addresses,
modules_memory_boundaries,
):
module_addr = module.vol.offset
module_name = module.get_name() or renderers.NotAvailableValue()
fields = (format_hints.Hex(module_addr), module_name)
yield (0, fields)
def run(self):
if self.context.symbol_space.verify_table_versions(
@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"
)
headers = [
("Address", format_hints.Hex),
("Name", str),
]
return renderers.TreeGrid(headers, self._generator())
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,
)
+14 -31
View File
@@ -7,20 +7,20 @@ import logging
from typing import List, Iterable
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
from volatility3.framework import exceptions, renderers, interfaces, deprecation
from volatility3.framework import interfaces, deprecation
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
vollog = logging.getLogger(__name__)
class Lsmod(plugins.PluginInterface):
class Lsmod(linux_utilities_modules.ModuleDisplayPlugin):
"""Lists loaded kernel modules."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 0)
_version = (3, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(linux_utilities_modules.ModuleGathererLsmod, *args, **kwargs)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -31,9 +31,14 @@ class Lsmod(plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="linux_utilities_modules",
component=linux_utilities_modules.Modules,
version=(3, 0, 0),
name="linux_utilities_modules_gatherers_lsmod",
component=linux_utilities_modules.ModuleGathererLsmod,
version=(1, 0, 0),
),
requirements.VersionRequirement(
name="linux_utilities_modules_module_display_plugin",
component=linux_utilities_modules.ModuleDisplayPlugin,
version=(1, 0, 0),
),
]
@@ -49,25 +54,3 @@ class Lsmod(plugins.PluginInterface):
return linux_utilities_modules.Modules.list_modules(
context, vmlinux_module_name
)
def _generator(self):
try:
for module in linux_utilities_modules.Modules.list_modules(
self.context, self.config["kernel"]
):
mod_size = module.get_init_size() + module.get_core_size()
mod_name = utility.array_to_string(module.name)
yield 0, (format_hints.Hex(module.vol.offset), mod_name, mod_size)
except exceptions.SymbolError:
vollog.warning(
"The required symbol 'module' is not present in symbol table. Please check that kernel modules are enabled for the system under analysis."
)
def run(self):
return renderers.TreeGrid(
[("Offset", format_hints.Hex), ("Name", str), ("Size", int)],
self._generator(),
)
@@ -21,11 +21,15 @@ from volatility3.framework import (
deprecation,
exceptions,
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
from volatility3.framework.symbols.linux import extensions
from volatility3.framework.interfaces import plugins
from volatility3.framework.symbols.linux.utilities import tainting
vollog = logging.getLogger(__name__)
@@ -684,3 +688,82 @@ class ModuleGatherers(
)
return reqs
class ModuleDisplayPlugin(plugins.PluginInterface):
"""
Plugins that enumerate kernel modules (lsmod, check_modules, etc.)
must inherit from this class to have unified output columns across plugins.
The constructor of the plugin must call super() with the `implementation` set
"""
_version = (1, 0, 0)
_required_framework_version = (2, 0, 0)
framework.require_interface_version(*_required_framework_version)
def __init__(self, implementation, *args, **kwargs):
super().__init__(*args, **kwargs)
self.implementation = implementation
@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,
version=(3, 0, 0),
),
requirements.VersionRequirement(
name="linux-tainting", component=tainting.Tainting, version=(1, 0, 0)
),
]
def _generator(self):
"""
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"]):
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}"
)
continue
code_size = format_hints.Hex(
module.get_init_size() + module.get_core_size()
)
taints = ",".join(
tainting.Tainting.get_taints_parsed(
self.context, self.config["kernel"], module.taints, True
)
)
yield 0, (
format_hints.Hex(module.vol.offset),
name,
format_hints.Hex(code_size),
taints,
renderers.NotAvailableValue(), # will become the load arguments after this inital conversion is merged
)
def run(self):
return renderers.TreeGrid(
[
("Offset", format_hints.Hex),
("Module Name", str),
("Code Size", format_hints.Hex),
("Taints", str),
("Load Arguments", str),
],
self._generator(),
)