Merge pull request #1719 from volatilityfoundation/linux_unifiy_module_gathering_output

Create versioned parent class for all plugins that enumerate Linux ke…
This commit is contained in:
ikelos
2025-03-18 08:18:15 +00:00
committed by GitHub
4 changed files with 185 additions and 126 deletions
@@ -3,15 +3,14 @@
#
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
from volatility3.framework.interfaces import plugins
vollog = logging.getLogger(__name__)
@@ -19,9 +18,31 @@ vollog = logging.getLogger(__name__)
class Check_modules(plugins.PluginInterface):
"""Compares module list to sysfs info, if available"""
_version = (2, 0, 0)
_version = (3, 0, 0)
_required_framework_version = (2, 0, 0)
@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(
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(
context=context, vmlinux_module_name=vmlinux_name
)
)
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 [
@@ -31,9 +52,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),
),
]
@@ -47,24 +68,3 @@ class Check_modules(plugins.PluginInterface):
cls, context: interfaces.context.ContextInterface, vmlinux_name: str
) -> Dict[str, extensions.module]:
return linux_utilities_modules.Modules.get_kset_modules(context, vmlinux_name)
def _generator(self):
kset_modules = linux_utilities_modules.Modules.get_kset_modules(
self.context, self.config["kernel"]
)
lsmod_modules = set(
str(utility.array_to_string(modules.name))
for modules in linux_utilities_modules.Modules.list_modules(
self.context, self.config["kernel"]
)
)
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(),
)
@@ -6,19 +6,58 @@ 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
from volatility3.framework.interfaces import plugins
vollog = logging.getLogger(__name__)
class Hidden_modules(interfaces.plugins.PluginInterface):
class Hidden_modules(plugins.PluginInterface):
"""Carves memory to find hidden kernel modules"""
_required_framework_version = (2, 10, 0)
_version = (2, 0, 0)
_version = (3, 0, 0)
@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 = linux_utilities_modules.Modules.list_modules
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -29,9 +68,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),
),
]
@@ -82,40 +121,6 @@ class Hidden_modules(interfaces.plugins.PluginInterface):
removal_date="2025-09-25",
replacement_version=(3, 0, 0),
)
@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
)
@staticmethod
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.validate_alignment_patterns,
@@ -165,38 +170,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,
)
+11 -26
View File
@@ -7,11 +7,9 @@ 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__)
@@ -20,7 +18,11 @@ class Lsmod(plugins.PluginInterface):
"""Lists loaded kernel modules."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 0)
_version = (3, 0, 0)
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]:
@@ -35,6 +37,11 @@ class Lsmod(plugins.PluginInterface):
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),
),
]
@classmethod
@@ -49,25 +56,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,14 @@ 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.symbols.linux.utilities import tainting
vollog = logging.getLogger(__name__)
@@ -684,3 +687,78 @@ class ModuleGatherers(
)
return reqs
class ModuleDisplayPlugin(interfaces.configuration.VersionableInterface):
"""
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)
@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(),
)