Source code for volatility3.plugins.windows.modules

# This file is Copyright 2019 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 Generator, Iterable, List, Optional, Dict, Tuple

from volatility3.framework import symbols, constants, exceptions, interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows.extensions import pe
from volatility3.plugins.windows import pedump, pslist

vollog = logging.getLogger(__name__)


[docs] class Modules(interfaces.plugins.PluginInterface): """Lists the loaded kernel modules.""" _required_framework_version = (2, 0, 0) # 3.0.0 - changed signature of get_session_layers, added get_session_layers_map _version = (3, 0, 0) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._enumeration_method = self.list_modules
[docs] @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: return [ requirements.ModuleRequirement( name="kernel", description="Windows kernel", architectures=["Intel32", "Intel64"], ), requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(3, 0, 0) ), requirements.VersionRequirement( name="pedump", component=pedump.PEDump, version=(2, 0, 0) ), requirements.BooleanRequirement( name="dump", description="Extract listed modules", default=False, optional=True, ), requirements.IntRequirement( name="base", description="Extract a single module with BASE address", optional=True, ), requirements.StringRequirement( name="name", description="module name/sub string", optional=True, default=None, ), ]
[docs] def dump_module(self, session_layers, pe_table_name, mod): session_layer_name = self.find_session_layer( self.context, session_layers, mod.DllBase ) file_output = f"Cannot find a viable session layer for {mod.DllBase:#x}" if session_layer_name: file_output = pedump.PEDump.dump_ldr_entry( self.context, pe_table_name, mod, self.open, layer_name=session_layer_name, ) if not file_output: file_output = "Error outputting file" return file_output
def _generator(self): pe_table_name = None session_layers = None if self.config["dump"]: pe_table_name = intermed.IntermediateSymbolTable.create( self.context, self.config_path, "windows", "pe", class_types=pe.class_types, ) session_layers = list( self.get_session_layers( context=self.context, kernel_module_name=self.config["kernel"], ) ) for mod in self._enumeration_method( self.context, kernel_module_name=self.config["kernel"] ): if self.config["base"] and self.config["base"] != mod.DllBase: continue try: BaseDllName = mod.BaseDllName.get_string() if self.config["name"] and self.config["name"] not in BaseDllName: continue except exceptions.InvalidAddressException: BaseDllName = interfaces.renderers.BaseAbsentValue() try: FullDllName = mod.FullDllName.get_string() except exceptions.InvalidAddressException: FullDllName = interfaces.renderers.BaseAbsentValue() file_output = "Disabled" if self.config["dump"]: file_output = self.dump_module(session_layers, pe_table_name, mod) yield ( 0, ( format_hints.Hex(mod.vol.offset), format_hints.Hex(mod.DllBase), format_hints.Hex(mod.SizeOfImage), BaseDllName, FullDllName, file_output, ), )
[docs] @classmethod def get_kernel_space_start(cls, context, module_name: str) -> int: """ Returns the starting address of the kernel address space This method allows plugins that analyze kernel data structures to quickly detect smeared or otherwise invalid data as many pointers must point into the kernel or access during runtime would crash the system """ module = context.modules[module_name] # default is used if/when MmSystemRangeStart is paged out if symbols.symbol_table_is_64bit( context=context, symbol_table_name=module.symbol_table_name ): object_type = "unsigned long long" default_start = 0xFFFF800000000000 else: object_type = "unsigned long" default_start = 0x80000000 range_start_offset = module.get_symbol("MmSystemRangeStart").address try: kernel_space_start = module.object( object_type=object_type, offset=range_start_offset ) except exceptions.InvalidAddressException: vollog.debug( f"Unable to read MmSystemRangeStart. Defaulting to {default_start:#x} for the kernel space start." ) kernel_space_start = default_start layer = context.layers[module.layer_name] return kernel_space_start & layer.address_mask
@classmethod def _do_get_session_layers( cls, context: interfaces.context.ContextInterface, kernel_module_name: str, pids: Optional[List[int]] = None, ) -> Generator[Tuple[int, str], None, None]: """Build a cache of possible virtual layers, in priority starting with the primary/kernel layer. Then keep one layer per session by cycling through the process list. Args: context: The context to retrieve required elements (layers, symbol tables) from kernel_module_name: The name of the module for the kernel pids: A list of process identifiers to include exclusively or None for no filter Returns: A generator of session layer names """ seen_ids: List[interfaces.objects.ObjectInterface] = [] filter_func = pslist.PsList.create_pid_filter(pids or []) kernel = context.modules[kernel_module_name] for proc in pslist.PsList.list_processes( context=context, kernel_module_name=kernel_module_name, filter_func=filter_func, ): proc_id = "Unknown" try: proc_id = proc.UniqueProcessId proc_layer_name = proc.add_process_layer() # create the session space object in the process' own layer. # not all processes have a valid session pointer. try: session_space = context.object( kernel.symbol_table_name + constants.BANG + "_MM_SESSION_SPACE", layer_name=kernel.layer_name, offset=proc.Session, ) session_id = session_space.SessionId except exceptions.SymbolError: # In Windows 11 24H2, the _MM_SESSION_SPACE type was # replaced with _PSP_SESSION_SPACE, and the kernel PDB # doesn't contain information about its members (otherwise, # we would just fall back to the new type). However, it # appears to be, for our purposes, functionally identical # to the _MM_SESSION_SPACE. Because _MM_SESSION_SPACE # stores its session ID at offset 8 as an unsigned long, we # create an unsigned long at that offset and use that # instead. session_id = context.object( layer_name=kernel.layer_name, object_type=kernel.symbol_table_name + constants.BANG + "unsigned long", offset=proc.Session + 8, ) if session_id in seen_ids: continue except exceptions.InvalidAddressException: vollog.log( constants.LOGLEVEL_VVV, f"Process {proc_id} does not have a valid Session or a layer could not be constructed for it", ) continue # save the layer if we haven't seen the session yet seen_ids.append(session_id) yield session_id, proc_layer_name
[docs] @classmethod def get_session_layers( cls, context: interfaces.context.ContextInterface, kernel_module_name: str, pids: Optional[List[int]] = None, ) -> Generator[str, None, None]: """ Args: context: The context to retrieve required elements (layers, symbol tables) from kernel_module_name: The name of the module for the kernel pids: A list of process identifiers to include exclusively or None for no filter Yields the names of the unique memory layers that map sessions """ for _session_id, proc_layer_name in cls._do_get_session_layers( context, kernel_module_name, pids ): yield proc_layer_name
[docs] @classmethod def get_session_layers_map( cls, context: interfaces.context.ContextInterface, kernel_module_name: str, pids: Optional[List[int]] = None, ) -> Dict[int, str]: """ Args: context: The context to retrieve required elements (layers, symbol tables) from kernel_module_name: The name of the module for the kernel pids: A list of process identifiers to include exclusively or None for no filter Wraps `_do_get_session_layers` to produce a dictionary where each key is a session_id and the value is the name of the layer for that session """ return dict(cls._do_get_session_layers(context, kernel_module_name, pids))
[docs] @classmethod def find_session_layer( cls, context: interfaces.context.ContextInterface, session_layers: Iterable[str], base_address: int, ): """Given a base address and a list of layer names, find a layer that can access the specified address. Args: context: The context to retrieve required elements (layers, symbol tables) from layer_name: The name of the layer on which to operate symbol_table: The name of the table containing the kernel symbols session_layers: A list of session layer names base_address: The base address to identify the layers that can access it Returns: Layer name or None if no layers that contain the base address can be found """ for layer_name in session_layers: if context.layers[layer_name].is_valid(base_address): return layer_name return None
[docs] @classmethod def list_modules( cls, context: interfaces.context.ContextInterface, kernel_module_name: str, ) -> Iterable[interfaces.objects.ObjectInterface]: """Lists all the modules in the primary layer. Args: context: The context to retrieve required elements (layers, symbol tables) from kernel_module_name: The name of the module for the kernel Returns: A list of Modules as retrieved from PsLoadedModuleList """ kernel = context.modules[kernel_module_name] if not kernel.offset: raise ValueError( "Intel layer does not have an associated kernel virtual offset, failing" ) try: # use this type if its available (starting with windows 10) ldr_entry_type = kernel.get_type("_KLDR_DATA_TABLE_ENTRY") except exceptions.SymbolError: ldr_entry_type = kernel.get_type("_LDR_DATA_TABLE_ENTRY") type_name = ldr_entry_type.type_name.split(constants.BANG)[1] list_head = kernel.get_symbol("PsLoadedModuleList").address list_entry = kernel.object(object_type="_LIST_ENTRY", offset=list_head) reloff = ldr_entry_type.relative_child_offset("InLoadOrderLinks") module = kernel.object( object_type=type_name, offset=list_entry.vol.offset - reloff, absolute=True ) yield from module.InLoadOrderLinks
[docs] def run(self): return renderers.TreeGrid( [ ("Offset", format_hints.Hex), ("Base", format_hints.Hex), ("Size", format_hints.Hex), ("Name", str), ("Path", str), ("File output", str), ], self._generator(), )