Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 29 additions & 29 deletions volatility3/framework/plugins/linux/check_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,46 @@
#

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__)


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 [
Expand All @@ -31,9 +52,9 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
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),
),
]

Expand All @@ -47,24 +68,3 @@ def get_kset_modules(
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(),
)
130 changes: 63 additions & 67 deletions volatility3/framework/plugins/linux/hidden_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,58 @@
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]:
Expand All @@ -29,9 +68,9 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would surely be better to define classmethod generator and run methods and then just proxy them at this point? We're really not getting anything from inheritance other than complexity?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can __init__ of each plugin just set .run and and ._generator to the shared function? and Where would that shared functons best live now? I was trying to avoid one plugin becoming the container (say lsmod) that the other plugins then need to import and depend on.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that should be ok, but you can just define it straight in the class I think? So

class Blah:
  run = linux_utilities_modules.ModuleDisplayPlugin.run

?

component=linux_utilities_modules.ModuleDisplayPlugin,
version=(1, 0, 0),
),
]

Expand Down Expand Up @@ -82,40 +121,6 @@ def _get_module_address_alignment(
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,
Expand Down Expand Up @@ -165,38 +170,29 @@ def get_lsmod_module_addresses(
}
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
@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"
)

known_module_addresses = cls.get_lsmod_module_addresses(
context, vmlinux_module_name
)
modules_memory_boundaries = (
linux_utilities_modules.Modules.get_modules_memory_boundaries(
self.context, vmlinux_module_name
context, vmlinux_module_name
)
)

for module in linux_utilities_modules.Modules.get_hidden_modules(
self.context,
yield from linux_utilities_modules.Modules.get_hidden_modules(
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(
"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())
)
37 changes: 11 additions & 26 deletions volatility3/framework/plugins/linux/lsmod.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@
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__)

Expand All @@ -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]:
Expand All @@ -35,6 +37,11 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
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
Expand All @@ -49,25 +56,3 @@ def list_modules(
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(),
)
Loading
Loading