mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-08-31 04:09:40 +02:00
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:
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user