Merge pull request #1640 from volatilityfoundation/plugin/windows_gui

Add APIs and initial plugins for GUI support and fit Windows major APIs to current design flow
This commit is contained in:
ikelos
2025-03-06 00:46:02 +00:00
committed by GitHub
96 changed files with 226824 additions and 641 deletions
+3 -5
View File
@@ -198,7 +198,6 @@ that will be output as part of the :py:class:`~volatility3.framework.interfaces.
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None))
kernel = self.context.modules[self.config['kernel']]
return renderers.TreeGrid(
[
@@ -211,9 +210,8 @@ that will be output as part of the :py:class:`~volatility3.framework.interfaces.
],
self._generator(
pslist.PsList.list_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
context=self.context,
kernel_module_name=self.config['kernel'],
filter_func = filter_func
)
)
@@ -235,7 +233,7 @@ the :py:class:`~volatility3.plugins.windows.pslist.PsList` plugin. That plugin
so that other plugins can call it. As such, it takes all the necessary parameters rather than accessing them
from a configuration. Since it must be portable code, it takes a context, as well as the layer name,
symbol table and optionally a filter. In this instance we unconditionally
pass it the values from the configuration for the layer and symbol table from the kernel module object, constructed from
pass it the value from the configuration for the kernel module name, constructed from
the ``kernel`` configuration requirement. This will generate a list
of :py:class:`~volatility3.framework.symbols.windows.extensions.EPROCESS` objects, as provided by the :py:class:`~volatility.plugins.windows.pslist.PsList` plugin,
and is not covered here but is used as an example for how to share code across plugins
+2 -4
View File
@@ -18,7 +18,7 @@ class Volshell(generic.Volshell):
return [
requirements.ModuleRequirement(name="kernel", description="Windows kernel"),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.IntRequirement(
name="pid", description="Process ID", optional=True
@@ -39,9 +39,7 @@ class Volshell(generic.Volshell):
"""Returns a list of EPROCESS objects from the primary layer"""
# We always use the main kernel memory and associated symbols
return list(
pslist.PsList.list_processes(
self.context, self.current_layer, self.current_symbol_table
)
pslist.PsList.list_processes(self.context, self.current_kernel_name)
)
def get_process(self, pid=None, virtaddr=None, physaddr=None):
+1 -1
View File
@@ -1,6 +1,6 @@
# We use the SemVer 2.0.0 versioning scheme
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
VERSION_MINOR = 22 # Number of changes that only add to the interface
VERSION_MINOR = 23 # Number of changes that only add to the interface
VERSION_PATCH = 0 # Number of changes that do not change the interface
VERSION_SUFFIX = ""
+1 -1
View File
@@ -66,7 +66,7 @@ class RegistryHive(linear.LinearlyMappedLayer):
# Win10 17063 introduced the Registry process to map most hives. Check
# if it exists and update RegistryHive._base_layer
for proc in pslist.PsList.list_processes(
self.context, self.config["base_layer"], self.config["nt_symbols"]
context=self.context, kernel_module_name=self.config["kernel_module_name"]
):
proc_name = proc.ImageFileName.cast(
"string", max_length=proc.ImageFileName.vol.count, errors="replace"
+1 -1
View File
@@ -46,7 +46,7 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
def _generator(self, tasks):
vmlinux = self.context.modules[self.config["kernel"]]
is_32bit = not symbols.symbol_table_is_64bit(
self.context, vmlinux.symbol_table_name
context=self.context, symbol_table_name=vmlinux.symbol_table_name
)
if is_32bit:
pack_format = "I"
@@ -64,7 +64,7 @@ class Malfind(interfaces.plugins.PluginInterface):
# determine if we're on a 32 or 64 bit kernel
vmlinux = self.context.modules[self.config["kernel"]]
is_32bit_arch = not symbols.symbol_table_is_64bit(
self.context, vmlinux.symbol_table_name
context=self.context, symbol_table_name=vmlinux.symbol_table_name
)
for task in tasks:
@@ -84,7 +84,9 @@ class PsScan(interfaces.plugins.PluginInterface):
vmlinux = context.modules[vmlinux_module_name]
# check if this image is 32bit or 64bit
is_32bit = not symbols.symbol_table_is_64bit(context, vmlinux.symbol_table_name)
is_32bit = not symbols.symbol_table_is_64bit(
context=context, symbol_table_name=vmlinux.symbol_table_name
)
if is_32bit:
pack_format = "I"
else:
+1 -1
View File
@@ -44,7 +44,7 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
def _generator(self, tasks):
darwin = self.context.modules[self.config["kernel"]]
is_32bit = not symbols.symbol_table_is_64bit(
self.context, darwin.symbol_table_name
context=self.context, symbol_table_name=darwin.symbol_table_name
)
if is_32bit:
pack_format = "I"
@@ -218,7 +218,9 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Extract information on executed applications from the AmCache."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
# 2.0.0 - changed the signature of get_amcache_hive
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -230,7 +232,7 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
]
@@ -252,7 +254,7 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel: interfaces.context.ModuleInterface,
kernel_module_name: str,
) -> Optional[registry.RegistryHive]:
"""Retrieves the `Amcache.hve` registry hive from the kernel module, if it can be located."""
return next(
@@ -261,8 +263,7 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
base_config_path=interfaces.configuration.path_join(
config_path, "hivelist"
),
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=kernel_module_name,
filter_string="amcache",
),
None,
@@ -523,8 +524,6 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
)
def _generator(self) -> Iterator[Tuple[int, _AmcacheEntry]]:
kernel = self.context.modules[self.config["kernel"]]
def indented(
entry_gen: Iterable[_AmcacheEntry], indent: int = 0
) -> Iterator[Tuple[int, _AmcacheEntry]]:
@@ -533,7 +532,9 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# Building the dictionary ahead of time is much better for performance
# vs looking up each service's DLL individually.
amcache = self.get_amcache_hive(self.context, self.config_path, kernel)
amcache = self.get_amcache_hive(
self.context, self.config_path, self.config["kernel"]
)
if amcache is None:
return
@@ -33,7 +33,7 @@ class Cachedump(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="lsadump", plugin=lsadump.Lsadump, version=(1, 0, 0)
@@ -169,13 +169,11 @@ class Cachedump(interfaces.plugins.PluginInterface):
offset = self.config.get("offset", None)
syshive = sechive = None
kernel = self.context.modules[self.config["kernel"]]
for hive in hivelist.HiveList.list_hives(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
context=self.context,
base_config_path=self.config_path,
kernel_module_name=self.config["kernel"],
hive_offsets=None if offset is None else [offset],
):
if hive.get_name().split("\\")[-1].upper() == "SYSTEM":
@@ -39,7 +39,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
@@ -187,7 +187,9 @@ class Callbacks(interfaces.plugins.PluginInterface):
The name of the constructed symbol table
"""
native_types = context.symbol_space[nt_symbol_table].natives
is_64bit = symbols.symbol_table_is_64bit(context, nt_symbol_table)
is_64bit = symbols.symbol_table_is_64bit(
context=context, symbol_table_name=nt_symbol_table
)
table_mapping = {"nt_symbols": nt_symbol_table}
if is_64bit:
@@ -691,7 +693,8 @@ class Callbacks(interfaces.plugins.PluginInterface):
)
collection = ssdt.SSDT.build_module_collection(
self.context, kernel.layer_name, kernel.symbol_table_name
context=self.context,
kernel_module_name=self.config["kernel"],
)
callback_methods = (
@@ -2,7 +2,7 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import List
from typing import List, Optional
from volatility3.framework import constants, exceptions, renderers, interfaces
from volatility3.framework.configuration import requirements
@@ -28,7 +28,7 @@ class CmdLine(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -41,7 +41,7 @@ class CmdLine(interfaces.plugins.PluginInterface):
@classmethod
def get_cmdline(
cls, context: interfaces.context.ContextInterface, kernel_table_name: str, proc
):
) -> Optional[str]:
"""Extracts the cmdline from PEB
Args:
@@ -54,15 +54,16 @@ class CmdLine(interfaces.plugins.PluginInterface):
"""
proc_layer_name = proc.add_process_layer()
if not proc_layer_name:
return None
peb = context.object(
kernel_table_name + constants.BANG + "_PEB",
layer_name=proc_layer_name,
offset=proc.Peb,
)
result_text = peb.ProcessParameters.CommandLine.get_string()
return result_text
return peb.ProcessParameters.CommandLine.get_string()
def _generator(self, procs):
kernel = self.context.modules[self.config["kernel"]]
@@ -99,7 +100,6 @@ class CmdLine(interfaces.plugins.PluginInterface):
yield (0, (proc.UniqueProcessId, process_name, result_text))
def run(self):
kernel = self.context.modules[self.config["kernel"]]
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
return renderers.TreeGrid(
@@ -107,8 +107,7 @@ class CmdLine(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
@@ -36,10 +36,10 @@ class CmdScan(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.PluginRequirement(
name="consoles", plugin=consoles.Consoles, version=(1, 0, 0)
name="consoles", plugin=consoles.Consoles, version=(2, 0, 0)
),
requirements.BooleanRequirement(
name="no_registry",
@@ -286,12 +286,11 @@ class CmdScan(interfaces.plugins.PluginInterface):
if no_registry is False:
max_history, _ = consoles.Consoles.get_console_settings_from_registry(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
max_history,
[],
context=self.context,
config_path=self.config_path,
kernel_module_name=self.config["kernel"],
max_history=max_history,
max_buffers=[],
)
vollog.debug(f"Possible CommandHistorySize values: {max_history}")
@@ -360,8 +359,6 @@ class CmdScan(interfaces.plugins.PluginInterface):
return process_name != "conhost.exe"
def run(self):
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
("PID", int),
@@ -374,8 +371,7 @@ class CmdScan(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=self._conhost_proc_filter,
)
),
@@ -29,7 +29,9 @@ class Consoles(interfaces.plugins.PluginInterface):
"""Looks for Windows console buffers"""
_required_framework_version = (2, 4, 0)
_version = (1, 0, 0)
# 2.0.0 - change the signature of `get_console_settings_from_registry`
_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
@@ -41,13 +43,13 @@ class Consoles(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="verinfo", component=verinfo.VerInfo, version=(1, 0, 0)
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
requirements.BooleanRequirement(
name="no_registry",
@@ -147,7 +149,9 @@ class Consoles(interfaces.plugins.PluginInterface):
The filename of the symbol table to use and the associated class types.
"""
is_64bit = symbols.symbol_table_is_64bit(context, nt_symbol_table)
is_64bit = symbols.symbol_table_is_64bit(
context=context, symbol_table_name=nt_symbol_table
)
if is_64bit:
arch = "x64"
@@ -795,8 +799,7 @@ class Consoles(interfaces.plugins.PluginInterface):
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel_layer_name: str,
kernel_symbol_table_name: str,
kernel_module_name: str,
max_history: Set[int],
max_buffers: Set[int],
) -> Tuple[Set[int], Set[int]]:
@@ -825,8 +828,7 @@ class Consoles(interfaces.plugins.PluginInterface):
for hive in hivelist.HiveList.list_hives(
context=context,
base_config_path=config_path,
layer_name=kernel_layer_name,
symbol_table=kernel_symbol_table_name,
kernel_module_name=kernel_module_name,
hive_offsets=None,
):
try:
@@ -861,8 +863,7 @@ class Consoles(interfaces.plugins.PluginInterface):
max_history, max_buffers = self.get_console_settings_from_registry(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
max_history,
max_buffers,
)
@@ -933,8 +934,6 @@ class Consoles(interfaces.plugins.PluginInterface):
return process_name.lower() != "conhost.exe"
def run(self):
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
("PID", int),
@@ -947,8 +946,7 @@ class Consoles(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=self._conhost_proc_filter,
)
),
@@ -35,13 +35,13 @@ class DebugRegisters(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="threads", component=threads.Threads, version=(1, 0, 0)
name="threads", component=threads.Threads, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="pe_symbols", component=pe_symbols.PESymbols, version=(1, 0, 0)
name="pe_symbols", component=pe_symbols.PESymbols, version=(2, 0, 0)
),
]
@@ -118,9 +118,7 @@ class DebugRegisters(interfaces.plugins.PluginInterface):
proc_modules = None
procs = pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
context=self.context, kernel_module_name=self.config["kernel"]
)
for proc in procs:
@@ -140,7 +138,7 @@ class DebugRegisters(interfaces.plugins.PluginInterface):
# this lookup takes a while, so only perform if we need to
if not proc_modules:
proc_modules = pe_symbols.PESymbols.get_process_modules(
self.context, kernel.layer_name, kernel.symbol_table_name, None
self.context, self.config["kernel"], None
)
path_and_symbol = partial(
pe_symbols.PESymbols.path_and_symbol_for_address,
@@ -0,0 +1,82 @@
# This file is Copyright 2025 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 List, Iterable, Tuple
from volatility3.framework import interfaces
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.plugins.windows import desktops, windowstations
vollog = logging.getLogger(__name__)
class DeskScan(desktops.Desktops):
"""Scans for the Desktop instances of each Window Station"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.implementation = self.scan_desktops
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="desktops", plugin=desktops.Desktops, version=(1, 0, 0)
),
requirements.PluginRequirement(
name="windowstations",
plugin=windowstations.WindowStations,
version=(1, 0, 0),
),
]
@classmethod
def scan_desktops(
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel_module_name: str,
) -> Iterable[Tuple[int, str, int, str, str, int]]:
"""
Yields the information about each desktop and desktop thread needed for analysis
The tuple yielded includes the:
Virtual address of the desktop
The window station name
The session id
Desktop name
Process name
Process ID (PID)
"""
kernel = context.modules[kernel_module_name]
for desktop in windowstations.WindowStations.scan_gui_object(
context, config_path, kernel_module_name, b"Desk", "tagDESKTOP"
):
desktop_name = desktop.get_name(kernel.symbol_table_name)
if not desktop_name:
continue
winsta = desktop.get_window_station()
if not winsta:
continue
winsta_name, session_id = winsta.get_info(kernel.symbol_table_name)
if not winsta_name or session_id is None:
continue
for _thread, process_name, process_pid in desktop.get_threads():
yield format_hints.Hex(
desktop.vol.offset
), winsta_name, session_id, desktop_name, process_name, process_pid
@@ -0,0 +1,91 @@
# This file is Copyright 2025 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 List, Iterable
from volatility3.framework import interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.plugins.windows import windowstations
vollog = logging.getLogger(__name__)
class Desktops(interfaces.plugins.PluginInterface):
"""Enumerates the Desktop instances of each Window Station"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.implementation = self.list_desktops
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="windowstations",
plugin=windowstations.WindowStations,
version=(1, 0, 0),
),
]
@classmethod
def list_desktops(
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel_module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""
Uses `scan_window_stations` to find each window station
For each found, enumerates its desktops followed by the
threads of each desktop.
"""
kernel = context.modules[kernel_module_name]
for (
winsta,
station_name,
session_id,
) in windowstations.WindowStations.scan_window_stations(
context, config_path, kernel_module_name
):
# for each window station, walk its list of desktops
for desktop, desktop_name in winsta.desktops(kernel.symbol_table_name):
# for each desktop, walk its threads
for _thread, process_name, process_pid in desktop.get_threads():
yield format_hints.Hex(
desktop.vol.offset
), station_name, session_id, desktop_name, process_name, process_pid
def _generator(self):
kernel_name = self.config["kernel"]
# call the implementation for finding desktops
# yield the information, which will include the owning window station and process
for desktop_info in self.implementation(
self.context, self.config_path, kernel_name
):
yield 0, desktop_info
def run(self):
return renderers.TreeGrid(
[
("Offset", format_hints.Hex),
("Window Station", str),
("Session", int),
("Desktop", str),
("Process", str),
("PID", int),
],
self._generator(),
)
@@ -53,7 +53,9 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
"""Detects the Direct System Call technique used to bypass EDRs"""
_required_framework_version = (2, 4, 0)
_version = (1, 0, 1)
# 2.0.0 - changes signature of `get_tasks_to_scan`
_version = (2, 0, 0)
# DLLs that are expected to host system call invocations
valid_syscall_handlers = ("ntdll.dll", "win32u.dll")
@@ -90,7 +92,7 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="yarascanner", component=yarascan.YaraScanner, version=(2, 1, 0)
@@ -334,8 +336,7 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
def get_tasks_to_scan(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table_name: str,
kernel_module_name: str,
) -> Generator[
Tuple[interfaces.objects.ObjectInterface, str, str, str], None, None
]:
@@ -350,12 +351,15 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
# gather active processes
filter_func = pslist.PsList.create_active_process_filter()
is_32bit_arch = not symbols.symbol_table_is_64bit(context, symbol_table_name)
kernel = context.modules[kernel_module_name]
is_32bit_arch = not symbols.symbol_table_is_64bit(
context=context, symbol_table_name=kernel.symbol_table_name
)
for proc in pslist.PsList.list_processes(
context=context,
layer_name=layer_name,
symbol_table=symbol_table_name,
kernel_module_name=kernel_module_name,
filter_func=filter_func,
):
proc_name = utility.array_to_string(proc.ImageFileName)
@@ -426,10 +430,8 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
)
return
kernel = self.context.modules[self.config["kernel"]]
for proc, proc_name, proc_layer_name, architecture in self.get_tasks_to_scan(
self.context, kernel.layer_name, kernel.symbol_table_name
self.context, self.config["kernel"]
):
proc_layer = self.context.layers[proc_layer_name]
@@ -13,7 +13,7 @@ from volatility3.framework.renderers import conversion, format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows.extensions import pe
from volatility3.plugins import timeliner
from volatility3.plugins.windows import info, pslist, psscan, pedump
from volatility3.plugins.windows import info, pedump, pslist, psscan
vollog = logging.getLogger(__name__)
@@ -34,13 +34,13 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="psscan", component=psscan.PsScan, version=(1, 1, 0)
),
requirements.VersionRequirement(
name="pedump", component=pedump.PEDump, version=(1, 0, 0)
name="pedump", component=pedump.PEDump, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="info", component=info.Info, version=(1, 0, 0)
@@ -191,12 +191,9 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
)
def generate_timeline(self):
kernel = self.context.modules[self.config["kernel"]]
for row in self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
context=self.context, kernel_module_name=self.config["kernel"]
)
):
_depth, row_data = row
@@ -223,8 +220,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
else:
procs = pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
@@ -59,25 +59,25 @@ class DriverIrp(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="driverscan", plugin=driverscan.DriverScan, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="modules", plugin=modules.Modules, version=(2, 1, 0)
name="modules", plugin=modules.Modules, version=(3, 0, 0)
),
]
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
collection = ssdt.SSDT.build_module_collection(
self.context, kernel.layer_name, kernel.symbol_table_name
context=self.context,
kernel_module_name=self.config["kernel"],
)
kernel_space_start = modules.Modules.get_kernel_space_start(
self.context, self.config["kernel"]
context=self.context,
module_name=self.config["kernel"],
)
for driver in driverscan.DriverScan.scan_drivers(
@@ -26,13 +26,13 @@ class DriverModule(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="driverscan", plugin=driverscan.DriverScan, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="modules", plugin=modules.Modules, version=(2, 1, 0)
name="modules", plugin=modules.Modules, version=(3, 0, 0)
),
]
@@ -42,10 +42,9 @@ class DriverModule(interfaces.plugins.PluginInterface):
A common rootkit technique is to register drivers from modules that are hidden,
which allows us to detect the disconnect between a malicious driver and its hidden module.
"""
kernel = self.context.modules[self.config["kernel"]]
collection = ssdt.SSDT.build_module_collection(
self.context, kernel.layer_name, kernel.symbol_table_name
context=self.context,
kernel_module_name=self.config["kernel"],
)
kernel_space_start = modules.Modules.get_kernel_space_start(
@@ -68,7 +68,7 @@ class DumpFiles(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="handles", component=handles.Handles, version=(2, 0, 0)
@@ -352,7 +352,6 @@ class DumpFiles(interfaces.plugins.PluginInterface):
offsets = list()
# a list of processes matching the pid filter. all files for these process(es) will be dumped.
procs = list()
kernel = self.context.modules[self.config["kernel"]]
if self.config["filter"] and (
self.config["virtaddr"] or self.config["physaddr"]
@@ -372,9 +371,8 @@ class DumpFiles(interfaces.plugins.PluginInterface):
[self.config.get("pid", None)]
)
procs = pslist.PsList.list_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
context=self.context,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
@@ -40,10 +40,10 @@ class Envars(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
]
@@ -58,13 +58,11 @@ class Envars(interfaces.plugins.PluginInterface):
"""
values = []
kernel = self.context.modules[self.config["kernel"]]
for hive in hivelist.HiveList.list_hives(
context=self.context,
base_config_path=self.config_path,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
hive_offsets=None,
):
## The global variables
@@ -216,7 +214,6 @@ class Envars(interfaces.plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
@@ -229,8 +226,7 @@ class Envars(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
@@ -69,18 +69,16 @@ class GetServiceSIDs(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
]
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
# Get the system hive
for hive in hivelist.HiveList.list_hives(
context=self.context,
base_config_path=self.config_path,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_string="machine\\system",
hive_offsets=None,
):
@@ -84,10 +84,10 @@ class GetSIDs(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
]
@@ -101,14 +101,12 @@ class GetSIDs(interfaces.plugins.PluginInterface):
key = "Microsoft\\Windows NT\\CurrentVersion\\ProfileList"
val = "ProfileImagePath"
kernel = self.context.modules[self.config["kernel"]]
sids = {}
for hive in hivelist.HiveList.list_hives(
context=self.context,
base_config_path=self.config_path,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_string="config\\software",
hive_offsets=None,
):
@@ -217,15 +215,13 @@ class GetSIDs(interfaces.plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[("PID", int), ("Process", str), ("SID", str), ("Name", str)],
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
@@ -36,7 +36,7 @@ class Handles(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="psscan", component=psscan.PsScan, version=(1, 1, 0)
@@ -78,7 +78,7 @@ class Handles(interfaces.plugins.PluginInterface):
except AttributeError:
# starting with windows 8
is_64bit = symbols.symbol_table_is_64bit(
self.context, kernel.symbol_table_name
context=self.context, symbol_table_name=kernel.symbol_table_name
)
if is_64bit:
@@ -223,24 +223,17 @@ class Handles(interfaces.plugins.PluginInterface):
kernel = self.context.modules[self.config["kernel"]]
virtual = kernel.layer_name
kvo = kernel.offset
ntkrnlmp = self.context.module(
kernel.symbol_table_name, layer_name=virtual, offset=kvo
)
if level > 0:
subtype = ntkrnlmp.get_type("pointer")
subtype = kernel.get_type("pointer")
count = 0x1000 / subtype.size
else:
subtype = ntkrnlmp.get_type("_HANDLE_TABLE_ENTRY")
subtype = kernel.get_type("_HANDLE_TABLE_ENTRY")
count = 0x1000 / subtype.size
if not self.context.layers[virtual].is_valid(offset):
if not self.context.layers[kernel.layer_name].is_valid(offset):
return None
table = ntkrnlmp.object(
table = kernel.object(
object_type="array",
offset=offset,
subtype=subtype,
@@ -248,7 +241,7 @@ class Handles(interfaces.plugins.PluginInterface):
absolute=True,
)
layer_object = self.context.layers[virtual]
layer_object = self.context.layers[kernel.layer_name]
masked_offset = offset & layer_object.maximum_address
for i in range(len(table)):
@@ -262,7 +255,7 @@ class Handles(interfaces.plugins.PluginInterface):
# The code above this calls `is_valid` on the `offset`
# It is sent but then does not validate `entry` before
# sending it to `_get_item`
if not self.context.layers[virtual].is_valid(entry.vol.offset):
if not self.context.layers[kernel.layer_name].is_valid(entry.vol.offset):
continue
if level > 0:
@@ -394,8 +387,7 @@ class Handles(interfaces.plugins.PluginInterface):
else:
procs = pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
@@ -32,7 +32,7 @@ class Hashdump(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
]
@@ -593,12 +593,10 @@ class Hashdump(interfaces.plugins.PluginInterface):
offset = self.config.get("offset", None)
syshive = None
samhive = None
kernel = self.context.modules[self.config["kernel"]]
for hive in hivelist.HiveList.list_hives(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
context=self.context,
base_config_path=self.config_path,
kernel_module_name=self.config["kernel"],
hive_offsets=None if offset is None else [offset],
):
if hive.get_name().split("\\")[-1].upper() == "SYSTEM":
@@ -48,7 +48,7 @@ class HollowProcesses(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0)
@@ -205,7 +205,6 @@ class HollowProcesses(interfaces.plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
@@ -216,8 +215,7 @@ class HollowProcesses(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
+2 -5
View File
@@ -28,7 +28,7 @@ class IAT(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -126,8 +126,6 @@ class IAT(interfaces.plugins.PluginInterface):
continue
def run(self):
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
("PID", int),
@@ -140,8 +138,7 @@ class IAT(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=pslist.PsList.create_pid_filter(
self.config.get("pid", None)
),
@@ -9,7 +9,7 @@ from typing import List, Optional
from volatility3.framework import interfaces, exceptions
from volatility3.framework.configuration import requirements
from volatility3.plugins import yarascan
from volatility3.plugins.windows import pslist, direct_system_calls
from volatility3.plugins.windows import direct_system_calls
vollog = logging.getLogger(__name__)
@@ -43,9 +43,6 @@ class IndirectSystemCalls(direct_system_calls.DirectSystemCalls):
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="yarascanner", component=yarascan.YaraScanner, version=(2, 1, 0)
),
@@ -207,7 +207,14 @@ class Info(plugins.PluginInterface):
yield (0, ("Symbols", table.config["isf_url"]))
yield (
0,
("Is64Bit", str(symbols.symbol_table_is_64bit(self.context, symbol_table))),
(
"Is64Bit",
str(
symbols.symbol_table_is_64bit(
context=self.context, symbol_table_name=symbol_table
)
),
),
)
yield (
0,
@@ -36,16 +36,18 @@ class JobLinks(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
]
def _generator(self) -> Iterator[Tuple]:
kernel = self.context.modules[self.config["kernel"]]
memory = self.context.layers[kernel.layer_name]
for proc in pslist.PsList.list_processes(
self.context, kernel.layer_name, kernel.symbol_table_name
context=self.context,
kernel_module_name=self.config["kernel"],
):
try:
if not self.config["physical"]:
@@ -29,7 +29,7 @@ class LdrModules(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0)
@@ -107,7 +107,6 @@ class LdrModules(interfaces.plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
@@ -122,8 +121,7 @@ class LdrModules(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
@@ -36,7 +36,7 @@ class Lsadump(interfaces.plugins.PluginInterface):
name="hashdump", component=hashdump.Hashdump, version=(1, 1, 0)
),
requirements.VersionRequirement(
name="hivelist", component=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", component=hivelist.HiveList, version=(2, 0, 0)
),
]
@@ -209,13 +209,11 @@ class Lsadump(interfaces.plugins.PluginInterface):
def run(self):
offset = self.config.get("offset", None)
syshive = sechive = None
kernel = self.context.modules[self.config["kernel"]]
for hive in hivelist.HiveList.list_hives(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
context=self.context,
base_config_path=self.config_path,
kernel_module_name=self.config["kernel"],
hive_offsets=None if offset is None else [offset],
):
if hive.get_name().split("\\")[-1].upper() == "SYSTEM":
@@ -41,7 +41,7 @@ class Malfind(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0)
@@ -172,7 +172,7 @@ class Malfind(interfaces.plugins.PluginInterface):
}
is_32bit_arch = not symbols.symbol_table_is_64bit(
self.context, kernel.symbol_table_name
context=self.context, symbol_table_name=kernel.symbol_table_name
)
for proc in procs:
@@ -238,7 +238,6 @@ class Malfind(interfaces.plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
@@ -258,8 +257,7 @@ class Malfind(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
@@ -53,7 +53,9 @@ class MBRScan(interfaces.plugins.PluginInterface):
layer = self.context.layers[physical_layer_name]
architecture = (
"intel"
if not symbols.symbol_table_is_64bit(self.context, kernel.symbol_table_name)
if not symbols.symbol_table_is_64bit(
context=self.context, symbol_table_name=kernel.symbol_table_name
)
else "intel64"
)
@@ -28,7 +28,7 @@ class Memmap(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.IntRequirement(
name="pid",
@@ -97,7 +97,6 @@ class Memmap(interfaces.plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_pid_filter([self.config.get("pid", None)])
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
@@ -110,8 +109,7 @@ class Memmap(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
@@ -15,7 +15,9 @@ class ModScan(modules.Modules):
"""Scans for modules present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 0)
# 3.0.0 changed the signature of enumeration methods (scan_modules)
_version = (3, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -33,7 +35,7 @@ class ModScan(modules.Modules):
name="poolscanner", component=poolscanner.PoolScanner, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(2, 0, 0)
name="modules", component=modules.Modules, version=(3, 0, 0)
),
requirements.BooleanRequirement(
name="dump",
@@ -53,7 +55,7 @@ class ModScan(modules.Modules):
default=None,
),
requirements.VersionRequirement(
name="pedump", component=pedump.PEDump, version=(1, 0, 0)
name="pedump", component=pedump.PEDump, version=(2, 0, 0)
),
]
@@ -61,26 +63,25 @@ class ModScan(modules.Modules):
def scan_modules(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Scans for modules using the poolscanner module and constraints.
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
kernel_module_name: Name of the module for the kernel
Returns:
A list of Driver objects as found from the `layer_name` layer based on Driver pool signatures
A list of kernel module objects as found from the primary (kernel) layer based on module pool signatures
"""
kernel = context.modules[kernel_module_name]
constraints = poolscanner.PoolScanner.builtin_constraints(
symbol_table, [b"MmLd"]
kernel.symbol_table_name, [b"MmLd"]
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, symbol_table, constraints
context, kernel.layer_name, kernel.symbol_table_name, constraints
):
_constraint, mem_object, _header = result
yield mem_object
@@ -2,7 +2,7 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import Generator, Iterable, List, Optional
from typing import Generator, Iterable, List, Optional, Dict, Tuple
from volatility3.framework import symbols, constants, exceptions, interfaces, renderers
from volatility3.framework.configuration import requirements
@@ -18,7 +18,9 @@ class Modules(interfaces.plugins.PluginInterface):
"""Lists the loaded kernel modules."""
_required_framework_version = (2, 0, 0)
_version = (2, 1, 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)
@@ -33,7 +35,10 @@ class Modules(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="pedump", component=pedump.PEDump, version=(2, 0, 0)
),
requirements.BooleanRequirement(
name="dump",
@@ -52,9 +57,6 @@ class Modules(interfaces.plugins.PluginInterface):
optional=True,
default=None,
),
requirements.VersionRequirement(
name="pedump", component=pedump.PEDump, version=(1, 0, 0)
),
]
def dump_module(self, session_layers, pe_table_name, mod):
@@ -76,8 +78,6 @@ class Modules(interfaces.plugins.PluginInterface):
return file_output
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
pe_table_name = None
session_layers = None
@@ -92,12 +92,13 @@ class Modules(interfaces.plugins.PluginInterface):
session_layers = list(
self.get_session_layers(
self.context, kernel.layer_name, kernel.symbol_table_name
context=self.context,
kernel_module_name=self.config["kernel"],
)
)
for mod in self._enumeration_method(
self.context, kernel.layer_name, kernel.symbol_table_name
self.context, kernel_module_name=self.config["kernel"]
):
if self.config["base"] and self.config["base"] != mod.DllBase:
continue
@@ -139,7 +140,9 @@ class Modules(interfaces.plugins.PluginInterface):
module = context.modules[module_name]
# default is used if/when MmSystemRangeStart is paged out
if symbols.symbol_table_is_64bit(context, module.symbol_table_name):
if symbols.symbol_table_is_64bit(
context=context, symbol_table_name=module.symbol_table_name
):
object_type = "unsigned long long"
default_start = 0xFFFF800000000000
else:
@@ -163,33 +166,32 @@ class Modules(interfaces.plugins.PluginInterface):
return kernel_space_start & layer.address_mask
@classmethod
def get_session_layers(
def _do_get_session_layers(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
pids: Optional[List[int]] = None,
) -> Generator[str, None, 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
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
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 list of session layer names
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,
layer_name=layer_name,
symbol_table=symbol_table,
kernel_module_name=kernel_module_name,
filter_func=filter_func,
):
proc_id = "Unknown"
@@ -201,8 +203,8 @@ class Modules(interfaces.plugins.PluginInterface):
# not all processes have a valid session pointer.
try:
session_space = context.object(
symbol_table + constants.BANG + "_MM_SESSION_SPACE",
layer_name=layer_name,
kernel.symbol_table_name + constants.BANG + "_MM_SESSION_SPACE",
layer_name=kernel.layer_name,
offset=proc.Session,
)
session_id = session_space.SessionId
@@ -218,8 +220,10 @@ class Modules(interfaces.plugins.PluginInterface):
# create an unsigned long at that offset and use that
# instead.
session_id = context.object(
layer_name=layer_name,
object_type=symbol_table + constants.BANG + "unsigned long",
layer_name=kernel.layer_name,
object_type=kernel.symbol_table_name
+ constants.BANG
+ "unsigned long",
offset=proc.Session + 8,
)
@@ -235,8 +239,46 @@ class Modules(interfaces.plugins.PluginInterface):
# save the layer if we haven't seen the session yet
seen_ids.append(session_id)
yield session_id, proc_layer_name
@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
@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))
@classmethod
def find_session_layer(
cls,
@@ -268,39 +310,35 @@ class Modules(interfaces.plugins.PluginInterface):
def list_modules(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
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
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
kernel_module_name: The name of the module for the kernel
Returns:
A list of Modules as retrieved from PsLoadedModuleList
"""
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
kernel = context.modules[kernel_module_name]
if not kernel.offset:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
try:
# use this type if its available (starting with windows 10)
ldr_entry_type = ntkrnlmp.get_type("_KLDR_DATA_TABLE_ENTRY")
ldr_entry_type = kernel.get_type("_KLDR_DATA_TABLE_ENTRY")
except exceptions.SymbolError:
ldr_entry_type = ntkrnlmp.get_type("_LDR_DATA_TABLE_ENTRY")
ldr_entry_type = kernel.get_type("_LDR_DATA_TABLE_ENTRY")
type_name = ldr_entry_type.type_name.split(constants.BANG)[1]
list_head = ntkrnlmp.get_symbol("PsLoadedModuleList").address
list_entry = ntkrnlmp.object(object_type="_LIST_ENTRY", offset=list_head)
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 = ntkrnlmp.object(
module = kernel.object(
object_type=type_name, offset=list_entry.vol.offset - reloff, absolute=True
)
@@ -137,7 +137,9 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# therefore we determine the version based on the kernel version as testing
# with several windows versions has showed this to work out correctly.
is_64bit = symbols.symbol_table_is_64bit(context, nt_symbol_table)
is_64bit = symbols.symbol_table_is_64bit(
context=context, symbol_table_name=nt_symbol_table
)
is_18363_or_later = versions.is_win10_18363_or_later(
context=context, symbol_table=nt_symbol_table
@@ -21,7 +21,9 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Traverses network tracking structures present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
# 2.0.0 changed the signature of `get_tcpip_module`
_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
@@ -35,7 +37,7 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
name="netscan", component=netscan.NetScan, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(2, 0, 0)
name="modules", component=modules.Modules, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="pdbutil", component=pdbutil.PDBUtility, version=(1, 0, 0)
@@ -234,20 +236,18 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def get_tcpip_module(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
nt_symbols: str,
kernel_module_name: str,
) -> Optional[interfaces.objects.ObjectInterface]:
"""Uses `windows.modules` to find tcpip.sys in memory.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
nt_symbols: The name of the table containing the kernel symbols
kernel_module_name: The name of the module for the kernel
Returns:
The constructed tcpip.sys module object.
"""
for mod in modules.Modules.list_modules(context, layer_name, nt_symbols):
for mod in modules.Modules.list_modules(context, kernel_module_name):
if mod.BaseDllName.get_string() == "tcpip.sys":
vollog.debug(f"Found tcpip.sys image base @ 0x{mod.DllBase:x}")
return mod
@@ -319,7 +319,9 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
Returns:
The list of TCP endpoint objects from the `layer_name` layer's `PartitionTable`
"""
if symbols.symbol_table_is_64bit(context, net_symbol_table):
if symbols.symbol_table_is_64bit(
context=context, symbol_table_name=net_symbol_table
):
alignment = 0x10
else:
alignment = 8
@@ -630,9 +632,7 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
self.context, kernel.layer_name, kernel.symbol_table_name, self.config_path
)
tcpip_module = self.get_tcpip_module(
self.context, kernel.layer_name, kernel.symbol_table_name
)
tcpip_module = self.get_tcpip_module(self.context, self.config["kernel"])
if not tcpip_module:
vollog.error("Unable to locate symbols for the memory image's tcpip module")
@@ -647,6 +647,11 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
)
except exceptions.VolatilityException:
vollog.error("Unable to locate symbols for the memory image's tcpip module")
return
if not tcpip_symbol_table:
vollog.error("Unable to reconstruct symbol table for tcpip.sys")
return
for netw_obj in self.list_sockets(
self.context,
@@ -16,7 +16,9 @@ class Threads(thrdscan.ThrdScan):
"""Lists process threads"""
_required_framework_version = (2, 4, 0)
_version = (1, 0, 0)
# 2.0.0 - changed the signature of `list_orphan_kernel_threads`
_version = (2, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -35,10 +37,10 @@ class Threads(thrdscan.ThrdScan):
name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 1, 0)
),
requirements.PluginRequirement(
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="modules", plugin=modules.Modules, version=(2, 1, 0)
name="modules", plugin=modules.Modules, version=(3, 0, 0)
),
]
@@ -46,7 +48,7 @@ class Threads(thrdscan.ThrdScan):
def list_orphan_kernel_threads(
cls,
context: interfaces.context.ContextInterface,
module_name: str,
kernel_module_name: str,
) -> Generator[interfaces.objects.ObjectInterface, None, None]:
"""Yields thread objects of kernel threads that do not map to a module
@@ -57,19 +59,16 @@ class Threads(thrdscan.ThrdScan):
Returns:
A generator of thread objects of orphaned threads
"""
module = context.modules[module_name]
layer_name = module.layer_name
symbol_table_name = module.symbol_table_name
collection = ssdt.SSDT.build_module_collection(
context, layer_name, symbol_table_name
context=context,
kernel_module_name=kernel_module_name,
)
kernel_space_start = modules.Modules.get_kernel_space_start(
context, module_name
context, kernel_module_name
)
for thread in thrdscan.ThrdScan.scan_threads(context, module_name):
for thread in thrdscan.ThrdScan.scan_threads(context, kernel_module_name):
# We don't want smeared or terminated threads
# So we access the owning process (which could also be terminated or smeared)
# Plus check the start address holding page
@@ -244,7 +244,8 @@ class PESymbols(interfaces.plugins.PluginInterface):
_required_framework_version = (2, 7, 0)
_version = (1, 1, 0)
# 2.0.0 - changed signature of get_kernel_modules, get_all_vads_with_file_paths, addresses_for_process_symbols, get_process_modules
_version = (2, 0, 0)
# used for special handling of the kernel PDB file. See later notes
os_module_name = "ntoskrnl.exe"
@@ -259,10 +260,10 @@ class PESymbols(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(2, 0, 0)
name="modules", component=modules.Modules, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="pdbutil", component=pdbutil.PDBUtility, version=(1, 0, 0)
@@ -297,7 +298,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
cls,
context: interfaces.context.ContextInterface,
pe_table_name: str,
layer_name: str,
process_layer_name: str,
base_address: int,
) -> Optional[pefile.PE]:
"""
@@ -305,7 +306,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
Args:
pe_table_name: name of the pe types table
layer_name: name of the process layer
process_layer_name: name of the process layer
base_address: base address of the module
Returns:
@@ -317,7 +318,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
dos_header = context.object(
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
offset=base_address,
layer_name=layer_name,
layer_name=process_layer_name,
)
for offset, data in dos_header.reconstruct():
@@ -388,8 +389,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
cls,
context: interfaces.context.ContextInterface,
config_path: str,
layer_name: str,
symbol_table_name: str,
kernel_module_name: str,
symbols: filter_modules_type,
) -> found_symbols_type:
"""
@@ -405,7 +405,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
found_symbols_type: The dictionary of symbols that were resolved
"""
collected_modules = PESymbols.get_process_modules(
context, layer_name, symbol_table_name, symbols
context, kernel_module_name, symbols
)
found_symbols, missing_symbols = PESymbols.find_symbols(
@@ -483,12 +483,12 @@ class PESymbols(interfaces.plugins.PluginInterface):
instance for it
"""
layer_name = module_info[0]
process_layer_name = module_info[0]
module_start = module_info[1]
# we need a valid PE with an export table
pe_module = PESymbols.get_pefile_obj(
context, pe_table_name, layer_name, module_start
context, pe_table_name, process_layer_name, module_start
)
if not pe_module:
return None
@@ -500,7 +500,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
return None
return ExportSymbolFinder(
layer_name,
process_layer_name,
mod_name.lower(),
module_start,
pe_module.DIRECTORY_ENTRY_EXPORT.symbols,
@@ -783,8 +783,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
def get_kernel_modules(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
filter_modules: Optional[filter_modules_type],
) -> collected_modules_type:
"""
@@ -804,7 +803,9 @@ class PESymbols(interfaces.plugins.PluginInterface):
filter_modules_check = None
session_layers = list(
modules.Modules.get_session_layers(context, layer_name, symbol_table)
modules.Modules.get_session_layers(
context=context, kernel_module_name=kernel_module_name
)
)
# special handling for the kernel
@@ -813,7 +814,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
)
for index, mod in enumerate(
modules.Modules.list_modules(context, layer_name, symbol_table)
modules.Modules.list_modules(context, kernel_module_name)
):
try:
mod_name = str(mod.BaseDllName.get_string().lower())
@@ -906,8 +907,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
def get_all_vads_with_file_paths(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table_name: str,
kernel_module_name: str,
) -> Generator[
Tuple[interfaces.objects.ObjectInterface, str, ranges_type],
None,
@@ -920,9 +920,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
Generator[Tuple[interfaces.objects.ObjectInterface, str, ranges_type]]: Yields tuple of process objects, layers, and VADs mapping files
"""
procs = pslist.PsList.list_processes(
context=context,
layer_name=layer_name,
symbol_table=symbol_table_name,
context=context, kernel_module_name=kernel_module_name
)
for proc in procs:
@@ -939,8 +937,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
def get_process_modules(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
filter_modules: Optional[filter_modules_type],
) -> collected_modules_type:
"""
@@ -960,7 +957,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
filter_modules_check = None
for _proc, proc_layer_name, vads in PESymbols.get_all_vads_with_file_paths(
context, layer_name, symbol_table
context, kernel_module_name
):
for vad_start, vad_size, filepath in vads:
filename = PESymbols.filename_for_path(filepath)
@@ -977,8 +974,6 @@ class PESymbols(interfaces.plugins.PluginInterface):
return proc_modules
def _generator(self) -> Generator[Tuple[int, Tuple[str, str, int]], None, None]:
kernel = self.context.modules[self.config["kernel"]]
if self.config["symbols"]:
filter_module = {
self.config["module"].lower(): {
@@ -1003,7 +998,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
module_resolver = self.get_process_modules
collected_modules = module_resolver(
self.context, kernel.layer_name, kernel.symbol_table_name, filter_module
self.context, self.config["kernel"], filter_module
)
found_symbols, _missing_symbols = PESymbols.find_symbols(
@@ -3,7 +3,7 @@
#
import logging
import ntpath
from typing import List, Type, Optional
from typing import List, Type, Optional, Iterator, Tuple
from volatility3.framework import constants, exceptions, interfaces, renderers
from volatility3.framework.configuration import requirements
@@ -18,7 +18,9 @@ class PEDump(interfaces.plugins.PluginInterface):
"""Allows extracting PE Files from a specific address in a specific address space"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
# 2.0.0 - changed the signature of `dump_kernel_pe_at_base`
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -30,7 +32,10 @@ class PEDump(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(3, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -145,9 +150,19 @@ class PEDump(interfaces.plugins.PluginInterface):
)
@classmethod
def dump_kernel_pe_at_base(cls, context, kernel, pe_table_name, open_method, base):
def dump_kernel_pe_at_base(
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
pe_table_name: str,
open_method: Type[interfaces.plugins.FileHandlerInterface],
base: int,
) -> Iterator[Tuple[int, str, str]]:
"""
Extracts a PE file from kernel memory at the given base address
"""
session_layers = modules.Modules.get_session_layers(
context, kernel.layer_name, kernel.symbol_table_name
context=context, kernel_module_name=kernel_module_name
)
session_layer_name = modules.Modules.find_session_layer(
@@ -182,8 +197,7 @@ class PEDump(interfaces.plugins.PluginInterface):
for proc in pslist.PsList.list_processes(
context=context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=kernel.name,
filter_func=filter_func,
):
pid = proc.UniqueProcessId
@@ -224,7 +238,11 @@ class PEDump(interfaces.plugins.PluginInterface):
if self.config["kernel_module"]:
pe_files = self.dump_kernel_pe_at_base(
self.context, kernel, pe_table_name, self.open, self.config["base"]
context=self.context,
kernel_module_name=self.config["kernel"],
pe_table_name=pe_table_name,
open_method=self.open,
base=self.config["base"],
)
else:
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
@@ -79,6 +79,7 @@ class PoolHeaderScanner(interfaces.layers.ScannerInterface):
offset=offset - self._header_offset,
absolute=True,
)
constraint = self._constraint_lookup[pattern]
try:
# Size check
@@ -128,7 +129,7 @@ class PoolScanner(plugins.PluginInterface):
"""A generic pool scanner plugin."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
_version = (1, 1, 1)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -181,6 +182,36 @@ class PoolScanner(plugins.PluginInterface):
),
)
@staticmethod
def gui_poolscanner_constraints(
gui_table: str, tags_filter: Optional[List[bytes]] = None
) -> List[PoolConstraint]:
"""
Constraints for objects managed by the GUI subsystem (win32k*.sys)
"""
builtins = [
PoolConstraint(
b"Wind",
type_name=gui_table + constants.BANG + "tagWINDOWSTATION",
size=(0x90, None),
page_type=PoolType.PAGED,
object_type="WindowStation",
skip_type_test=True,
),
PoolConstraint(
b"Desk",
type_name=gui_table + constants.BANG + "tagDESKTOP",
page_type=PoolType.PAGED,
object_type="Desktop",
skip_type_test=True,
),
]
if not tags_filter:
return builtins
return [constraint for constraint in builtins if constraint.tag in tags_filter]
@classmethod
def builtin_constraints(
cls, symbol_table: str, tags_filter: Optional[List[bytes]] = None
@@ -331,11 +362,12 @@ class PoolScanner(plugins.PluginInterface):
return [constraint for constraint in builtins if constraint.tag in tags_filter]
@classmethod
def generate_pool_scan(
def generate_pool_scan_extended(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_layer_name: str,
kernel_symbol_table_name: str,
object_symbol_table_name: str,
constraints: List[PoolConstraint],
) -> Generator[
Tuple[
@@ -347,49 +379,66 @@ class PoolScanner(plugins.PluginInterface):
None,
]:
"""
The extended version of `generate_pool_scan` to support pool scanning for objects outside of the kernel (ntoskrnl).
This requires the symbol table of the object being scanned for.
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
kernel_layer_name: The name of the base kernel layer
kernel_symbol_table_name: The name of the table containing the kernel symbols
object_symbol_table_name: The name of the symbol table for the object being scanned for
constraints: List of pool constraints used to limit the scan results
Returns:
Iterable of tuples, containing the constraint that matched, the object from memory, the object header used to determine the object
"""
# get the object type map
type_map = handles.Handles.get_type_map(
context=context, layer_name=layer_name, symbol_table=symbol_table
context=context,
layer_name=kernel_layer_name,
symbol_table=kernel_symbol_table_name,
)
cookie = handles.Handles.find_cookie(
context=context, layer_name=layer_name, symbol_table=symbol_table
context=context,
layer_name=kernel_layer_name,
symbol_table=kernel_symbol_table_name,
)
is_windows_10 = versions.is_windows_10(context, symbol_table)
is_windows_8_or_later = versions.is_windows_8_or_later(context, symbol_table)
is_windows_10 = versions.is_windows_10(context, kernel_symbol_table_name)
is_windows_8_or_later = versions.is_windows_8_or_later(
context, kernel_symbol_table_name
)
# start off with the primary virtual layer
scan_layer = layer_name
scan_layer = kernel_layer_name
# switch to a non-virtual layer if necessary
if not is_windows_10:
scan_layer = context.layers[scan_layer].config["memory_layer"]
if symbols.symbol_table_is_64bit(context, symbol_table):
if symbols.symbol_table_is_64bit(
context=context, symbol_table_name=kernel_symbol_table_name
):
alignment = 0x10
else:
alignment = 8
# scan in the main kernel layer for the object(s)
for constraint, header in cls.pool_scan(
context, scan_layer, symbol_table, constraints, alignment=alignment
context,
scan_layer,
object_symbol_table_name,
constraints,
alignment=alignment,
):
# construct the object in its own layer, using its own types
mem_objects = header.get_object(
constraint=constraint,
use_top_down=is_windows_8_or_later,
native_layer_name=layer_name,
kernel_symbol_table=symbol_table,
native_layer_name=kernel_layer_name,
kernel_symbol_table=kernel_symbol_table_name,
)
for mem_object in mem_objects:
@@ -398,6 +447,7 @@ class PoolScanner(plugins.PluginInterface):
constants.LOGLEVEL_VVV,
f"Cannot create an instance of {constraint.type_name}",
)
continue
if constraint.object_type is not None and not constraint.skip_type_test:
@@ -418,6 +468,40 @@ class PoolScanner(plugins.PluginInterface):
yield constraint, mem_object, header
@classmethod
def generate_pool_scan(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
constraints: List[PoolConstraint],
) -> Generator[
Tuple[
PoolConstraint,
interfaces.objects.ObjectInterface,
interfaces.objects.ObjectInterface,
],
None,
None,
]:
"""
The original version of `generate_pool_scan` which is sufficient for objects in the kernel (ntoskrnl),
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
constraints: List of pool constraints used to limit the scan results
Returns:
Iterable of tuples, containing the constraint that matched, the object from memory, the object header used to determine the object
"""
# repeat the symbol table to match the original `generate_pool_scan` behaviour
yield from cls.generate_pool_scan_extended(
context, layer_name, symbol_table, symbol_table, constraints
)
@classmethod
def pool_scan(
cls,
@@ -483,7 +567,9 @@ class PoolScanner(plugins.PluginInterface):
except exceptions.SymbolError:
# We have to manually load a symbol table
if symbols.symbol_table_is_64bit(context, symbol_table):
if symbols.symbol_table_is_64bit(
context=context, symbol_table_name=symbol_table
):
is_win_7 = versions.is_windows_7(context, symbol_table)
if is_win_7:
pool_header_json_filename = "poolheader-x64-win7"
@@ -61,7 +61,7 @@ class Privs(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
]
@@ -107,7 +107,6 @@ class Privs(interfaces.plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
@@ -121,8 +120,7 @@ class Privs(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
@@ -29,7 +29,7 @@ class ProcessGhosting(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
]
@@ -83,7 +83,6 @@ class ProcessGhosting(interfaces.plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_active_process_filter()
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
@@ -96,8 +95,7 @@ class ProcessGhosting(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
+15 -16
View File
@@ -22,7 +22,9 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Lists the processes present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 1)
# 3.0.0 - changed signature for `list_processes`
_version = (3, 0, 0)
PHYSICAL_DEFAULT = False
@classmethod
@@ -206,35 +208,33 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def list_processes(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
filter_func: Callable[
[interfaces.objects.ObjectInterface], bool
] = lambda _: False,
) -> Iterator["extensions.EPROCESS"]:
"""Lists all the processes in the primary layer that are in the pid
"""Lists all the processes in the given layer that are in the pid
config option.
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
layer_iname: The name of the layer on which to operate
symbol_table_name: The name of the table containing the kernel symbols
filter_func: A function which takes an EPROCESS object and returns True if the process should be ignored/filtered
Returns:
The list of EPROCESS objects from the `layer_name` layer's PsActiveProcessHead list after filtering
"""
# We only use the object factory to demonstrate how to use one
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
kernel = context.modules[kernel_module_name]
if not kernel.offset:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ps_aph_offset = ntkrnlmp.get_symbol("PsActiveProcessHead").address
list_entry = ntkrnlmp.object(object_type="_LIST_ENTRY", offset=ps_aph_offset)
ps_aph_offset = kernel.get_symbol("PsActiveProcessHead").address
list_entry = kernel.object(object_type="_LIST_ENTRY", offset=ps_aph_offset)
# This is example code to demonstrate how to use symbol_space directly, rather than through a module:
#
@@ -247,10 +247,10 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# Note: "nt_symbols!_EPROCESS" could have been used, but would rely on the "nt_symbols" symbol table not already
# having been present. Strictly, the value of the requirement should be joined with the BANG character
# defined in the constants file
reloff = ntkrnlmp.get_type("_EPROCESS").relative_child_offset(
reloff = kernel.get_type("_EPROCESS").relative_child_offset(
"ActiveProcessLinks"
)
eproc = ntkrnlmp.object(
eproc = kernel.object(
object_type="_EPROCESS",
offset=list_entry.vol.offset - reloff,
absolute=True,
@@ -273,8 +273,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
for proc in self.list_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
filter_func=self.create_pid_filter(self.config.get("pid", None)),
):
if not self.config.get("physical", self.PHYSICAL_DEFAULT):
@@ -34,7 +34,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="info", component=info.Info, version=(1, 0, 0)
@@ -40,7 +40,7 @@ class PsTree(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -85,7 +85,7 @@ class PsTree(interfaces.plugins.PluginInterface):
kernel = self.context.modules[self.config["kernel"]]
for proc in pslist.PsList.list_processes(
self.context, kernel.layer_name, kernel.symbol_table_name
context=self.context, kernel_module_name=self.config["kernel"]
):
if not self.config.get("physical", pslist.PsList.PHYSICAL_DEFAULT):
offset = proc.vol.offset
@@ -53,7 +53,7 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
name="info", component=info.Info, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="psscan", component=psscan.PsScan, version=(1, 0, 0)
@@ -181,12 +181,9 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
layer_name = kernel.layer_name
symbol_table = kernel.symbol_table_name
kdbg_list_processes = list(
pslist.PsList.list_processes(
context=self.context, layer_name=layer_name, symbol_table=symbol_table
context=self.context, kernel_module_name=self.config["kernel"]
)
)
@@ -194,10 +191,12 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
processes: Dict[str, Dict[int, extensions.EPROCESS]] = {}
processes["pslist"] = self._check_pslist(kdbg_list_processes)
processes["psscan"] = self._check_psscan(layer_name, symbol_table)
processes["psscan"] = self._check_psscan(
kernel.layer_name, kernel.symbol_table_name
)
processes["thrdscan"] = self._check_thrdscan()
processes["csrss"] = self._check_csrss_handles(
kdbg_list_processes, layer_name, symbol_table
kdbg_list_processes, kernel.layer_name, kernel.symbol_table_name
)
# Unique set of all offsets from all sources
@@ -28,18 +28,16 @@ class GetCellRoutine(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0)
),
]
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
collection = ssdt.SSDT.build_module_collection(
self.context, kernel.layer_name, kernel.symbol_table_name
context=self.context, kernel_module_name=self.config["kernel"]
)
# walk each hive and validate that the GetCellRoutine handler
@@ -47,8 +45,7 @@ class GetCellRoutine(interfaces.plugins.PluginInterface):
for hive_object in hivelist.HiveList.list_hives(
context=self.context,
base_config_path=self.config_path,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
):
hive = hive_object.hive
@@ -41,9 +41,11 @@ class HiveGenerator:
class HiveList(interfaces.plugins.PluginInterface):
"""Lists the registry hives present in a particular memory image."""
_version = (1, 0, 1)
_required_framework_version = (2, 0, 0)
# 2.0.0 - changed the signature of list_hives
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
@@ -93,10 +95,9 @@ class HiveList(interfaces.plugins.PluginInterface):
# Construct the hive
hive = next(
self.list_hives(
self.context,
self.config_path,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
context=self.context,
base_config_path=self.config_path,
kernel_module_name=self.config["kernel"],
hive_offsets=[hive_object.vol.offset],
)
)
@@ -137,8 +138,7 @@ class HiveList(interfaces.plugins.PluginInterface):
cls,
context: interfaces.context.ContextInterface,
base_config_path: str,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
filter_string: Optional[str] = None,
hive_offsets: Optional[List[int]] = None,
) -> Iterator[registry.RegistryHive]:
@@ -148,20 +148,24 @@ class HiveList(interfaces.plugins.PluginInterface):
Args:
context: The context to retrieve required elements (layers, symbol tables) from
base_config_path: The configuration path for any settings required by the new table
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
kernel_module_name: The name of the module for the kernel
filter_string: An optional string which must be present in the hive name if specified
offset: An optional offset to specify a specific hive to iterate over (takes precedence over filter_string)
Yields:
A registry hive layer name
"""
kernel = context.modules[kernel_module_name]
if hive_offsets is None:
try:
hive_offsets = [
hive.vol.offset
for hive in cls.list_hive_objects(
context, layer_name, symbol_table, filter_string
context=context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
filter_string=filter_string,
)
]
except ImportError:
@@ -178,8 +182,9 @@ class HiveList(interfaces.plugins.PluginInterface):
context=context,
base_config_path=base_config_path,
hive_offset=hive_offset,
base_layer=layer_name,
nt_symbols=symbol_table,
base_layer=kernel.layer_name,
nt_symbols=kernel.symbol_table_name,
kernel_module_name=kernel_module_name,
)
try:
@@ -50,7 +50,9 @@ class HiveScan(interfaces.plugins.PluginInterface):
kernel = context.modules[kernel_name]
is_64bit = symbols.symbol_table_is_64bit(context, kernel.symbol_table_name)
is_64bit = symbols.symbol_table_is_64bit(
context=context, symbol_table_name=kernel.symbol_table_name
)
is_windows_8_1_or_later = versions.is_windows_8_1_or_later(
context=context, symbol_table=kernel.symbol_table_name
)
@@ -31,7 +31,7 @@ class PrintKey(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
requirements.IntRequirement(
name="offset", description="Hive Offset", default=None, optional=True
@@ -240,17 +240,14 @@ class PrintKey(interfaces.plugins.PluginInterface):
def _registry_walker(
self,
layer_name: str,
symbol_table: str,
hive_offsets: Optional[List[int]] = None,
key: Optional[str] = None,
recurse: bool = False,
):
for hive in hivelist.HiveList.list_hives(
self.context,
self.config_path,
layer_name=layer_name,
symbol_table=symbol_table,
context=self.context,
base_config_path=self.config_path,
kernel_module_name=self.config["kernel"],
hive_offsets=hive_offsets,
):
try:
@@ -292,7 +289,6 @@ class PrintKey(interfaces.plugins.PluginInterface):
def run(self):
offset = self.config.get("offset", None)
kernel = self.context.modules[self.config["kernel"]]
return TreeGrid(
columns=[
@@ -305,8 +301,6 @@ class PrintKey(interfaces.plugins.PluginInterface):
("Volatile", bool),
],
generator=self._registry_walker(
kernel.layer_name,
kernel.symbol_table_name,
hive_offsets=None if offset is None else [offset],
key=self.config.get("key", None),
recurse=self.config.get("recurse", None),
@@ -54,7 +54,7 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac
name="offset", description="Hive Offset", default=None, optional=True
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
]
@@ -295,7 +295,6 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac
hive_offsets = None
if self.config.get("offset", None) is not None:
hive_offsets = [self.config.get("offset", None)]
kernel = self.context.modules[self.config["kernel"]]
self._reg_table_name = intermed.IntermediateSymbolTable.create(
self.context, self._config_path, "windows", "registry"
@@ -305,8 +304,7 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac
for hive in hivelist.HiveList.list_hives(
context=self.context,
base_config_path=self.config_path,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_string="ntuser.dat",
hive_offsets=hive_offsets,
):
@@ -1103,7 +1103,7 @@ class ScheduledTasks(interfaces.plugins.PluginInterface, timeliner.TimeLinerInte
information about triggers, actions, run times, and creation times."""
_required_framework_version = (2, 11, 0)
_version = (1, 0, 0)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -1115,7 +1115,7 @@ information about triggers, actions, run times, and creation times."""
architectures=["Intel33", "Intel64"],
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
]
@@ -1135,7 +1135,7 @@ information about triggers, actions, run times, and creation times."""
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel: interfaces.context.ModuleInterface,
kernel_module_name: str,
) -> Optional[registry.RegistryHive]:
"""Retrieves the `Amcache.hve` registry hive from the kernel module, if it can be located."""
return next(
@@ -1144,8 +1144,7 @@ information about triggers, actions, run times, and creation times."""
base_config_path=interfaces.configuration.path_join(
config_path, "hivelist"
),
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=kernel_module_name,
filter_string="SOFTWARE",
),
None,
@@ -1348,7 +1347,7 @@ information about triggers, actions, run times, and creation times."""
args,
(
action_set.context
if action_set is not None
if (action_set is not None and action_set.context is not None)
else renderers.NotAvailableValue()
),
working_directory,
@@ -1356,11 +1355,11 @@ information about triggers, actions, run times, and creation times."""
)
def _generator(self) -> Iterator[Tuple[int, _ScheduledTaskEntry]]:
kernel = self.context.modules[self.config["kernel"]]
# Building the dictionary ahead of time is much better for performance
# vs looking up each service's DLL individually.
software_hive = self.get_software_hive(self.context, self.config_path, kernel)
software_hive = self.get_software_hive(
self.context, self.config_path, self.config["kernel"]
)
if software_hive is None:
vollog.warning("Failed to get SOFTWARE hive")
return
@@ -28,7 +28,7 @@ class Sessions(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -39,16 +39,14 @@ class Sessions(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
]
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
# Collect all the values as we will want to group them later
sessions = {}
for proc in pslist.PsList.list_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
context=self.context,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
):
session_id = proc.get_session_id()
@@ -65,13 +65,13 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(2, 0, 0)
name="modules", component=modules.Modules, version=(3, 0, 0)
),
]
@@ -79,7 +79,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
def create_shimcache_table(
cls,
context: interfaces.context.ContextInterface,
symbol_table: str,
symbol_table_name: str,
config_path: str,
) -> str:
"""Creates a shimcache symbol table
@@ -92,16 +92,18 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
Returns:
The name of the constructed shimcache table
"""
native_types = context.symbol_space[symbol_table].natives
is_64bit = symbols.symbol_table_is_64bit(context, symbol_table)
table_mapping = {"nt_symbols": symbol_table}
native_types = context.symbol_space[symbol_table_name].natives
is_64bit = symbols.symbol_table_is_64bit(
context=context, symbol_table_name=symbol_table_name
)
table_mapping = {"nt_symbols": symbol_table_name}
try:
symbol_filename = next(
filename
for version_check, for_64bit, filename in ShimcacheMem._win_version_file_map
if is_64bit == for_64bit
and version_check(context=context, symbol_table=symbol_table)
and version_check(context=context, symbol_table=symbol_table_name)
)
except StopIteration:
raise NotImplementedError("This version of Windows is not supported!")
@@ -122,8 +124,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
def find_shimcache_win_xp(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
kernel_symbol_table: str,
kernel_module_name: str,
shimcache_symbol_table: str,
) -> Iterator[shimcache.SHIM_CACHE_ENTRY]:
"""Attempts to find the shimcache in a Windows XP memory image
@@ -142,9 +143,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
seen = set()
for process in pslist.PsList.list_processes(
context, layer_name, kernel_symbol_table
):
for process in pslist.PsList.list_processes(context, kernel_module_name):
pid = process.UniqueProcessId
vollog.debug("checking process %d", pid)
for vad in vadinfo.VadInfo.list_vads(
@@ -219,8 +218,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel_layer_name: str,
nt_symbol_table: str,
kernel_module_name: str,
shimcache_symbol_table: str,
) -> Iterator[shimcache.SHIM_CACHE_ENTRY]:
"""Implements the algorithm to search for the shim cache on Windows 2000
@@ -239,31 +237,37 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
:param shimcache_symbol_table: The name of a symbol table containing the hand-crafted shimcache symbols
"""
kernel = context.modules[kernel_module_name]
data_sec = cls.get_module_section_range(
context,
config_path,
kernel_layer_name,
nt_symbol_table,
kernel_module_name,
cls.NT_KRNL_MODS,
".data",
)
mod_page = cls.get_module_section_range(
context,
config_path,
kernel_layer_name,
nt_symbol_table,
kernel_module_name,
cls.NT_KRNL_MODS,
"PAGE",
)
# We require both in order to accurately handle AVL table
if not (data_sec and mod_page):
return None
return
data_sec_offset, data_sec_size = data_sec
mod_page_offset, mod_page_size = mod_page
addr_size = 8 if symbols.symbol_table_is_64bit(context, nt_symbol_table) else 4
addr_size = (
8
if symbols.symbol_table_is_64bit(
context=context, symbol_table_name=kernel.symbol_table_name
)
else 4
)
shim_head = None
for offset in range(
@@ -272,8 +276,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
shim_head = cls.try_get_shim_head_at_offset(
context,
shimcache_symbol_table,
nt_symbol_table,
kernel_layer_name,
kernel_module_name,
mod_page_offset,
mod_page_offset + mod_page_size,
offset,
@@ -293,9 +296,8 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
def try_get_shim_head_at_offset(
cls,
context: interfaces.context.ContextInterface,
symbol_table: str,
kernel_symbol_table: str,
layer_name: str,
shimcache_symbol_table: str,
kernel_module_name: str,
mod_page_start: int,
mod_page_end: int,
offset: int,
@@ -307,9 +309,14 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
If a number of validity checks are passed, this method will return the `SHIM_CACHE_HEAD`
object. Otherwise, `None` is returned.
"""
kernel = context.modules[kernel_module_name]
# Check RTL_AVL_TABLE at offset
rtl_avl_table = context.object(
symbol_table + constants.BANG + "_RTL_AVL_TABLE", layer_name, offset
shimcache_symbol_table + constants.BANG + "_RTL_AVL_TABLE",
kernel.layer_name,
offset,
)
if not rtl_avl_table.is_valid(mod_page_start, mod_page_end):
return None
@@ -317,11 +324,13 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
vollog.debug(f"Candidate RTL_AVL_TABLE found at offset {offset:#x}")
ersrc_size = context.symbol_space.get_type(
kernel_symbol_table + constants.BANG + "_ERESOURCE"
kernel.symbol_table_name + constants.BANG + "_ERESOURCE"
).size
ersrc_alignment = (
0x20
if symbols.symbol_table_is_64bit(context, kernel_symbol_table)
if symbols.symbol_table_is_64bit(
context=context, symbol_table_name=kernel.symbol_table_name
)
else 0x10
# 0x20 if context.symbol_space.get_type("pointer").size == 8 else 0x10
)
@@ -334,8 +343,8 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
vollog.debug(f"Constructing ERESOURCE at {eresource_offset:#x}")
eresource = context.object(
kernel_symbol_table + constants.BANG + "_ERESOURCE",
layer_name,
kernel.symbol_table_name + constants.BANG + "_ERESOURCE",
kernel.layer_name,
eresource_offset,
)
if not eresource.is_valid():
@@ -344,12 +353,12 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
shim_head_offset = offset + rtl_avl_table.vol.size
if not context.layers[layer_name].is_valid(shim_head_offset):
if not context.layers[kernel.layer_name].is_valid(shim_head_offset):
return None
shim_head = context.object(
symbol_table + constants.BANG + "SHIM_CACHE_ENTRY",
layer_name,
shimcache_symbol_table + constants.BANG + "SHIM_CACHE_ENTRY",
kernel.layer_name,
shim_head_offset,
)
@@ -365,8 +374,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel_layer_name: str,
nt_symbol_table: str,
kernel_module_name: str,
shimcache_symbol_table: str,
) -> Iterator[shimcache.SHIM_CACHE_ENTRY]:
"""Attempts to locate and yield shimcache entries from a Windows 8 or later memory image.
@@ -376,10 +384,11 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
:param kernel_symbol_table: The name of an existing symbol table containing the kernel symbols
:param shimcache_symbol_table: The name of a symbol table containing the hand-crafted shimcache symbols
"""
kernel = context.modules[kernel_module_name]
is_8_1_or_later = versions.is_windows_8_1_or_later(
context, nt_symbol_table
) or versions.is_win10(context, nt_symbol_table)
context, kernel.symbol_table_name
) or versions.is_win10(context, kernel.symbol_table_name)
module_names = ["ahcache.sys"] if is_8_1_or_later else cls.NT_KRNL_MODS
vollog.debug(f"Searching for modules {module_names}")
@@ -387,16 +396,14 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
data_sec = cls.get_module_section_range(
context,
config_path,
kernel_layer_name,
nt_symbol_table,
kernel_module_name,
module_names,
".data",
)
mod_page = cls.get_module_section_range(
context,
config_path,
kernel_layer_name,
nt_symbol_table,
kernel_module_name,
module_names,
"PAGE",
)
@@ -419,12 +426,18 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
for offset in range(
data_sec_offset,
data_sec_offset + data_sec_size,
8 if symbols.symbol_table_is_64bit(context, nt_symbol_table) else 4,
(
8
if symbols.symbol_table_is_64bit(
context=context, symbol_table_name=kernel.symbol_table_name
)
else 4
),
):
vollog.debug(f"Building shim handle pointer at {offset:#x}")
shim_handle = context.object(
object_type=shimcache_symbol_table + constants.BANG + "pointer",
layer_name=kernel_layer_name,
layer_name=kernel.layer_name,
subtype=handle_type,
offset=offset,
)
@@ -445,7 +458,9 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
# On Windows 8 x64, the first cache contains the shim cache.
# On Windows 8 x86, 8.1 x86/x64, and 10, the second cache contains the shim cache.
if (
not symbols.symbol_table_is_64bit(context, nt_symbol_table)
not symbols.symbol_table_is_64bit(
context=context, symbol_table_name=kernel.symbol_table_name
)
and not is_8_1_or_later
):
valid_head = shim_heads[1]
@@ -474,8 +489,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
entries = self.find_shimcache_win_8_or_later(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
shimcache_table_name,
)
@@ -488,8 +502,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
entries = self.find_shimcache_win_2k3_to_7(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
shimcache_table_name,
)
@@ -499,8 +512,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
vollog.info("Finding shimcache entries for WinXP")
entries = self.find_shimcache_win_xp(
self._context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
shimcache_table_name,
)
else:
@@ -547,8 +559,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
cls,
context: interfaces.context.ContextInterface,
config_path: str,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
module_list: List[str],
section_name: str,
) -> Optional[Tuple[int, int]]:
@@ -566,14 +577,14 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
try:
krnl_mod = next(
module
for module in modules.Modules.list_modules(
context, layer_name, symbol_table
)
for module in modules.Modules.list_modules(context, kernel_module_name)
if module.BaseDllName.String in module_list
)
except StopIteration:
return None
kernel = context.modules[kernel_module_name]
pe_table_name = intermed.IntermediateSymbolTable.create(
context,
interfaces.configuration.path_join(config_path, "pe"),
@@ -585,7 +596,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
# code taken from Win32KBase._section_chunks (win32_core.py)
dos_header = context.object(
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
layer_name,
kernel.layer_name,
offset=krnl_mod.DllBase,
)
@@ -52,7 +52,7 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0)
@@ -61,7 +61,7 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface):
name="pdbutil", component=pdbutil.PDBUtility, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="pe_symbols", component=pe_symbols.PESymbols, version=(1, 1, 0)
name="pe_symbols", component=pe_symbols.PESymbols, version=(2, 0, 0)
),
]
@@ -568,7 +568,9 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface):
"""
kernel = self.context.modules[self.config["kernel"]]
if not symbols.symbol_table_is_64bit(self.context, kernel.symbol_table_name):
if not symbols.symbol_table_is_64bit(
context=self.context, symbol_table_name=kernel.symbol_table_name
):
vollog.info("This plugin only supports 64bit Windows memory samples")
return None
@@ -660,8 +662,6 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface):
return process_name != "lsass.exe"
def run(self):
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
("PID", int),
@@ -673,8 +673,7 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=self._lsass_proc_filter,
)
),
+14 -16
View File
@@ -19,7 +19,9 @@ class SSDT(plugins.PluginInterface):
"""Lists the system call table."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
# 2.0.0 - changed the signature of `build_module_collection`
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -30,7 +32,7 @@ class SSDT(plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="modules", plugin=modules.Modules, version=(2, 0, 0)
name="modules", plugin=modules.Modules, version=(3, 0, 0)
),
]
@@ -38,23 +40,23 @@ class SSDT(plugins.PluginInterface):
def build_module_collection(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> contexts.ModuleCollection:
"""Builds a collection of modules.
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
kernel_module_name: Name of the module for the kernel
Returns:
A Module collection of available modules based on `Modules.list_modules`
"""
mods = modules.Modules.list_modules(context, layer_name, symbol_table)
mods = modules.Modules.list_modules(context, kernel_module_name)
context_modules = []
kernel = context.modules[kernel_module_name]
for mod in mods:
try:
module_name_with_ext = mod.BaseDllName.get_string()
@@ -64,17 +66,13 @@ class SSDT(plugins.PluginInterface):
module_name = os.path.splitext(module_name_with_ext)[0]
symbol_table_name = None
if module_name in constants.windows.KERNEL_MODULE_NAMES:
symbol_table_name = symbol_table
context_module = contexts.SizedModule.create(
context=context,
module_name=module_name,
layer_name=layer_name,
layer_name=kernel.layer_name,
offset=mod.DllBase,
size=mod.SizeOfImage,
symbol_table_name=symbol_table_name,
symbol_table_name=kernel.symbol_table_name,
)
context_modules.append(context_module)
@@ -84,9 +82,9 @@ class SSDT(plugins.PluginInterface):
def _generator(self) -> Iterator[Tuple[int, Tuple[int, int, Any, Any]]]:
kernel = self.context.modules[self.config["kernel"]]
layer_name = kernel.layer_name
collection = self.build_module_collection(
self.context, layer_name, kernel.symbol_table_name
context=self.context,
kernel_module_name=self.config["kernel"],
)
ntkrnlmp = kernel
@@ -103,7 +101,7 @@ class SSDT(plugins.PluginInterface):
# on 64-bit systems the indexes are also 32-bits but they're offsets from the
# base address of the table and can be negative, so we need a signed data type
is_kernel_64 = symbols.symbol_table_is_64bit(
self.context, kernel.symbol_table_name
context=self.context, symbol_table_name=kernel.symbol_table_name
)
if is_kernel_64:
array_subtype = "long"
@@ -18,8 +18,11 @@ vollog = logging.getLogger(__name__)
class Strings(interfaces.plugins.PluginInterface):
"""Reads output from the strings command and indicates which process(es) each string belongs to."""
_version = (1, 2, 0)
_required_framework_version = (2, 0, 0)
# 2.0.0 - change signature of `generate_mapping`
_version = (2, 0, 0)
strings_pattern = re.compile(rb"^(?:\W*)([0-9]+)(?:\W*)(\w[\w\W]+)\n?")
@classmethod
@@ -31,7 +34,7 @@ class Strings(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -68,12 +71,10 @@ class Strings(interfaces.plugins.PluginInterface):
except ValueError:
vollog.error(f"Line in unrecognized format: line {count}")
line = strings_fp.readline()
kernel = self.context.modules[self.config["kernel"]]
revmap = self.generate_mapping(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
context=self.context,
kernel_module_name=self.config["kernel"],
progress_callback=self._progress_callback,
pid_list=self.config["pid"],
)
@@ -122,8 +123,7 @@ class Strings(interfaces.plugins.PluginInterface):
def generate_mapping(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
progress_callback: constants.ProgressCallback = None,
pid_list: Optional[List[int]] = None,
) -> Dict[int, Set[Tuple[str, int]]]:
@@ -132,8 +132,7 @@ class Strings(interfaces.plugins.PluginInterface):
Args:
context: the context for the method to run against
layer_name: the layer to map against the string lines
symbol_table: the name of the symbol table for the provided layer
kernel_module_name: the name of the module forthe kernel
progress_callback: an optional callable to display progress
pid_list: a lit of process IDs to consider when generating the reverse map
@@ -142,7 +141,9 @@ class Strings(interfaces.plugins.PluginInterface):
"""
filter = pslist.PsList.create_pid_filter(pid_list)
layer = context.layers[layer_name]
kernel = context.modules[kernel_module_name]
layer = context.layers[kernel.layer_name]
reverse_map: Dict[int, Set[Tuple[str, int]]] = dict()
if isinstance(layer, intel.Intel):
# We don't care about errors, we just wanted chunks that map correctly
@@ -161,7 +162,7 @@ class Strings(interfaces.plugins.PluginInterface):
# TODO: Include kernel modules
for process in pslist.PsList.list_processes(
context, layer_name, symbol_table
context=context, kernel_module_name=kernel_module_name
):
if not filter(process):
proc_id = "Unknown"
@@ -179,7 +180,7 @@ class Strings(interfaces.plugins.PluginInterface):
for mapval in proc_layer.mapping(
0x0, proc_layer.maximum_address, ignore_errors=True
):
mapped_offset, _, offset, mapped_size, maplayer = mapval
mapped_offset, _, offset, mapped_size, _maplayer = mapval
for val in range(
mapped_offset, mapped_offset + mapped_size, 0x1000
):
@@ -30,13 +30,13 @@ class SuspendedThreads(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="pe_symbols", component=pe_symbols.PESymbols, version=(1, 0, 0)
name="pe_symbols", component=pe_symbols.PESymbols, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="threads", component=threads.Threads, version=(1, 0, 0)
name="threads", component=threads.Threads, version=(2, 0, 0)
),
]
@@ -62,9 +62,7 @@ class SuspendedThreads(interfaces.plugins.PluginInterface):
# walk the threads of each process checking for suspended threads
for proc in pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
context=self.context, kernel_module_name=self.config["kernel"]
):
for thread in threads.Threads.list_threads(kernel, proc):
try:
@@ -96,7 +94,9 @@ class SuspendedThreads(interfaces.plugins.PluginInterface):
# will not have suspended threads
if not proc_modules:
proc_modules = pe_symbols.PESymbols.get_process_modules(
self.context, kernel.layer_name, kernel.symbol_table_name, None
context=self.context,
kernel_module_name=self.config["kernel"],
filter_modules=None,
)
path_and_symbol = functools.partial(
@@ -34,8 +34,14 @@ class SuspiciousThreads(interfaces.plugins.PluginInterface):
element_type=int,
optional=True,
),
requirements.PluginRequirement(
name="threads", plugin=threads.Threads, version=(1, 0, 0)
requirements.VersionRequirement(
name="thrdscan", component=thrdscan.ThrdScan, version=(1, 1, 0)
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="threads", component=threads.Threads, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0)
@@ -133,8 +139,7 @@ class SuspiciousThreads(interfaces.plugins.PluginInterface):
for proc in pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
):
ranges = self._get_ranges(kernel, all_ranges, proc)
@@ -26,6 +26,8 @@ class SvcDiff(svcscan.SvcScan):
_required_framework_version = (2, 4, 0)
_version = (2, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._enumeration_method = self.service_diff
@@ -40,10 +42,10 @@ class SvcDiff(svcscan.SvcScan):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="svclist", component=svclist.SvcList, version=(1, 0, 0)
name="svclist", component=svclist.SvcList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="svcscan", component=svcscan.SvcScan, version=(3, 0, 0)
name="svcscan", component=svcscan.SvcScan, version=(4, 0, 0)
),
]
@@ -51,8 +53,7 @@ class SvcDiff(svcscan.SvcScan):
def service_diff(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
service_table_name: str,
service_binary_dll_map,
filter_func,
@@ -61,10 +62,12 @@ class SvcDiff(svcscan.SvcScan):
On Windows 10 version 15063+ 64bit Windows memory samples, walk the services list
and scan for services then report differences
"""
kernel = context.modules[kernel_module_name]
if not symbols.symbol_table_is_64bit(
context, symbol_table
context=context, symbol_table_name=kernel.symbol_table_name
) or not versions.is_win10_15063_or_later(
context=context, symbol_table=symbol_table
context=context, symbol_table=kernel.symbol_table_name
):
vollog.warning(
"This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples"
@@ -78,8 +81,7 @@ class SvcDiff(svcscan.SvcScan):
# collect unique service names from scanning
for service in svcscan.SvcScan.service_scan(
context,
layer_name,
symbol_table,
kernel_module_name,
service_table_name,
service_binary_dll_map,
filter_func,
@@ -90,8 +92,7 @@ class SvcDiff(svcscan.SvcScan):
# collect services from listing walking
for service in svclist.SvcList.service_list(
context,
layer_name,
symbol_table,
kernel_module_name,
service_table_name,
service_binary_dll_map,
filter_func,
@@ -19,7 +19,9 @@ class SvcList(svcscan.SvcScan):
"""Lists services contained with the services.exe doubly linked list of services"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
# 2.0.0 - service_list signature changed
_version = (2, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -30,7 +32,7 @@ class SvcList(svcscan.SvcScan):
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.PluginRequirement(
name="svcscan", plugin=svcscan.SvcScan, version=(3, 0, 0)
name="svcscan", plugin=svcscan.SvcScan, version=(4, 0, 0)
),
requirements.ModuleRequirement(
name="kernel",
@@ -60,16 +62,17 @@ class SvcList(svcscan.SvcScan):
def service_list(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
service_table_name: str,
service_binary_dll_map,
filter_func,
):
kernel = context.modules[kernel_module_name]
if not symbols.symbol_table_is_64bit(
context, symbol_table
context=context, symbol_table_name=kernel.symbol_table_name
) or not versions.is_win10_15063_or_later(
context=context, symbol_table=symbol_table
context=context, symbol_table=kernel.symbol_table_name
):
vollog.warning(
"This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples"
@@ -78,19 +81,18 @@ class SvcList(svcscan.SvcScan):
for proc in pslist.PsList.list_processes(
context=context,
layer_name=layer_name,
symbol_table=symbol_table,
kernel_module_name=kernel_module_name,
filter_func=filter_func,
):
try:
layer_name = proc.add_process_layer()
proc_layer_name = proc.add_process_layer()
except exceptions.InvalidAddressException:
vollog.warning(
f"Unable to access memory of services.exe running with PID: {proc.UniqueProcessId}"
)
continue
layer = context.layers[layer_name]
proc_layer = context.layers[proc_layer_name]
exe_range = cls._get_exe_range(proc)
if not exe_range:
@@ -99,7 +101,7 @@ class SvcList(svcscan.SvcScan):
)
continue
for offset in layer.scan(
for offset in proc_layer.scan(
context=context,
scanner=scanners.BytesScanner(needle=b"Sc27"),
sections=exe_range,
@@ -108,6 +110,6 @@ class SvcList(svcscan.SvcScan):
context,
service_table_name,
service_binary_dll_map,
layer_name,
proc_layer_name,
offset,
)
@@ -4,7 +4,7 @@
import logging
import os
from typing import Dict, List, NamedTuple, Optional, Tuple, Union, cast
from typing import Dict, List, NamedTuple, Optional, Tuple, Union, cast, Callable
from volatility3.framework import (
constants,
@@ -35,7 +35,7 @@ class SvcScan(interfaces.plugins.PluginInterface):
"""Scans for windows services."""
_required_framework_version = (2, 0, 0)
_version = (3, 0, 2)
_version = (4, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -51,13 +51,13 @@ class SvcScan(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.PluginRequirement(
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
]
@@ -106,7 +106,7 @@ class SvcScan(interfaces.plugins.PluginInterface):
@staticmethod
def _create_service_table(
context: interfaces.context.ContextInterface,
symbol_table: str,
symbol_table_name: str,
config_path: str,
) -> str:
"""Constructs a symbol table containing the symbols for services
@@ -120,15 +120,17 @@ class SvcScan(interfaces.plugins.PluginInterface):
Returns:
A symbol table containing the symbols necessary for services
"""
native_types = context.symbol_space[symbol_table].natives
is_64bit = symbols.symbol_table_is_64bit(context, symbol_table)
native_types = context.symbol_space[symbol_table_name].natives
is_64bit = symbols.symbol_table_is_64bit(
context=context, symbol_table_name=symbol_table_name
)
try:
symbol_filename = next(
filename
for version_check, for_64bit, filename in SvcScan._win_version_file_map
if is_64bit == for_64bit
and version_check(context=context, symbol_table=symbol_table)
and version_check(context=context, symbol_table=symbol_table_name)
)
except StopIteration:
raise NotImplementedError("This version of Windows is not supported!")
@@ -144,15 +146,15 @@ class SvcScan(interfaces.plugins.PluginInterface):
@staticmethod
def _get_service_key(
context, config_path: str, layer_name: str, symbol_table: str
context, config_path: str, kernel_module_name: str
) -> Optional[objects.StructType]:
for hive in hivelist.HiveList.list_hives(
context=context,
base_config_path=interfaces.configuration.path_join(
config_path, "hivelist"
),
layer_name=layer_name,
symbol_table=symbol_table,
kernel_module_name=kernel_module_name,
filter_string="machine\\system",
):
# Get ControlSet\Services.
@@ -278,18 +280,19 @@ class SvcScan(interfaces.plugins.PluginInterface):
def service_scan(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
service_table_name: str,
service_binary_dll_map,
filter_func,
):
kernel = context.modules[kernel_module_name]
relative_tag_offset = context.symbol_space.get_type(
service_table_name + constants.BANG + "_SERVICE_RECORD"
).relative_child_offset("Tag")
is_vista_or_later = versions.is_vista_or_later(
context=context, symbol_table=symbol_table
context=context, symbol_table=kernel.symbol_table_name
)
if is_vista_or_later:
@@ -300,9 +303,8 @@ class SvcScan(interfaces.plugins.PluginInterface):
seen = []
for task in pslist.PsList.list_processes(
context=context,
layer_name=layer_name,
symbol_table=symbol_table,
context,
kernel_module_name=kernel_module_name,
filter_func=filter_func,
):
proc_id = "Unknown"
@@ -315,7 +317,7 @@ class SvcScan(interfaces.plugins.PluginInterface):
)
continue
layer = context.layers[proc_layer_name]
process_layer = context.layers[proc_layer_name]
# get process sections for scanning
sections = []
@@ -324,7 +326,7 @@ class SvcScan(interfaces.plugins.PluginInterface):
if vad.get_size():
sections.append((base, vad.get_size()))
for offset in layer.scan(
for offset in process_layer.scan(
context=context,
scanner=scanners.BytesScanner(needle=service_tag),
sections=sections,
@@ -360,18 +362,20 @@ class SvcScan(interfaces.plugins.PluginInterface):
yield service_record
@classmethod
def get_prereq_info(cls, context, config_path, layer_name: str, symbol_table: str):
def get_prereq_info(
cls, context, config_path: str, kernel_module_name: str
) -> Tuple[str, Dict, Callable]:
"""
Data structures and information needed to analyze service information
"""
kernel = context.modules[kernel_module_name]
service_table_name = cls._create_service_table(
context, symbol_table, config_path
context, kernel.symbol_table_name, config_path
)
services_key = cls._get_service_key(
context, config_path, layer_name, symbol_table
)
services_key = cls._get_service_key(context, config_path, kernel_module_name)
service_binary_dll_map = (
cls._get_service_binary_map(services_key)
@@ -384,16 +388,13 @@ class SvcScan(interfaces.plugins.PluginInterface):
return service_table_name, service_binary_dll_map, filter_func
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info(
self.context, self.config_path, kernel.layer_name, kernel.symbol_table_name
self.context, self.config_path, self.config["kernel"]
)
for record in self._enumeration_method(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
service_table_name,
service_binary_dll_map,
filter_func,
@@ -16,7 +16,7 @@ class Threads(thrdscan.ThrdScan):
"""Lists process threads"""
_required_framework_version = (2, 4, 0)
_version = (1, 0, 1)
_version = (2, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -59,16 +59,18 @@ class Threads(thrdscan.ThrdScan):
@classmethod
def list_process_threads(
cls, context: interfaces.context.ContextInterface, module_name: str
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Runs through all processes and lists threads for each process"""
module = context.modules[module_name]
layer_name = module.layer_name
symbol_table_name = module.symbol_table_name
module = context.modules[kernel_module_name]
filter_func = pslist.PsList.create_pid_filter(context.config.get("pid", None))
for proc in pslist.PsList.list_processes(
context=context,
layer_name=layer_name,
symbol_table=symbol_table_name,
kernel_module_name=kernel_module_name,
filter_func=filter_func,
):
yield from cls.list_threads(module, proc)
@@ -35,7 +35,7 @@ class Timers(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="kpcrs", plugin=kpcrs.KPCRs, version=(1, 0, 0)
@@ -122,15 +122,18 @@ class Timers(interfaces.plugins.PluginInterface):
def _generator(self) -> Iterator[Tuple]:
kernel = self.context.modules[self.config["kernel"]]
layer_name = kernel.layer_name
symbol_table = kernel.symbol_table_name
collection = ssdt.SSDT.build_module_collection(
self.context, kernel.layer_name, kernel.symbol_table_name
context=self.context,
kernel_module_name=self.config["kernel"],
)
# FIXME - the list_timers API is gross. Fix after GUI merge
for timer in self.list_timers(
self.context, self.config["kernel"], layer_name, symbol_table
self.context,
self.config["kernel"],
kernel.layer_name,
kernel.symbol_table_name,
):
if not timer.valid_type():
continue
@@ -33,7 +33,7 @@ class Passphrase(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(2, 0, 0)
name="modules", component=modules.Modules, version=(3, 0, 0)
),
requirements.IntRequirement(
name="min-length",
@@ -121,7 +121,7 @@ class Passphrase(interfaces.plugins.PluginInterface):
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
mods: Iterable[ObjectInterface] = modules.Modules.list_modules(
self.context, kernel.layer_name, kernel.symbol_table_name
self.context, self.config["kernel"]
)
truecrypt_module_base = next(
mod.DllBase
@@ -22,6 +22,7 @@ class unhooked_system_calls(interfaces.plugins.PluginInterface):
"""Looks for signs of Skeleton Key malware"""
_required_framework_version = (2, 4, 0)
_version = (2, 0, 0)
system_calls = {
"ntdll.dll": {
@@ -94,16 +95,16 @@ class unhooked_system_calls(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.PluginRequirement(
name="pe_symbols", plugin=pe_symbols.PESymbols, version=(1, 0, 0)
name="pe_symbols", plugin=pe_symbols.PESymbols, version=(2, 0, 0)
),
]
def _gather_code_bytes(
self,
kernel: interfaces.context.ModuleInterface,
kernel_module_name: str,
found_symbols: pe_symbols.found_symbols_type,
) -> _code_bytes_type:
"""
@@ -115,11 +116,7 @@ class unhooked_system_calls(interfaces.plugins.PluginInterface):
"""
code_bytes: unhooked_system_calls._code_bytes_type = {}
procs = pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
)
procs = pslist.PsList.list_processes(self.context, kernel_module_name)
for proc in procs:
try:
@@ -153,18 +150,15 @@ class unhooked_system_calls(interfaces.plugins.PluginInterface):
return code_bytes
def _generator(self) -> Generator[Tuple[int, Tuple[str, str, int]], None, None]:
kernel = self.context.modules[self.config["kernel"]]
found_symbols = pe_symbols.PESymbols.addresses_for_process_symbols(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
unhooked_system_calls.system_calls,
context=self.context,
config_path=self.config_path,
kernel_module_name=self.config["kernel"],
symbols=unhooked_system_calls.system_calls,
)
# code_bytes[dll_name][func_name][func_bytes]
code_bytes = self._gather_code_bytes(kernel, found_symbols)
code_bytes = self._gather_code_bytes(self.config["kernel"], found_symbols)
# walk the functions that were evaluated
for functions in code_bytes.values():
@@ -52,7 +52,9 @@ class UnloadedModules(interfaces.plugins.PluginInterface, timeliner.TimeLinerInt
The name of the constructed unloaded modules table
"""
native_types = context.symbol_space[symbol_table].natives
is_64bit = symbols.symbol_table_is_64bit(context, symbol_table)
is_64bit = symbols.symbol_table_is_64bit(
context=context, symbol_table_name=symbol_table
)
table_mapping = {"nt_symbols": symbol_table}
if is_64bit:
@@ -100,7 +102,9 @@ class UnloadedModules(interfaces.plugins.PluginInterface, timeliner.TimeLinerInt
offset=unloadedmodules_offset,
subtype="array",
)
is_64bit = symbols.symbol_table_is_64bit(context, symbol_table)
is_64bit = symbols.symbol_table_is_64bit(
context=context, symbol_table_name=symbol_table
)
if is_64bit:
unloaded_count_type = "unsigned long long"
@@ -64,7 +64,7 @@ class VadInfo(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.BooleanRequirement(
name="dump",
@@ -273,8 +273,6 @@ class VadInfo(interfaces.plugins.PluginInterface):
)
def run(self) -> renderers.TreeGrid:
kernel = self.context.modules[self.config["kernel"]]
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
return renderers.TreeGrid(
@@ -295,8 +293,7 @@ class VadInfo(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
@@ -33,7 +33,7 @@ class VadRegExScan(plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -111,11 +111,9 @@ class VadRegExScan(plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
kernel = self.context.modules[self.config["kernel"]]
procs = pslist.PsList.list_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
context=self.context,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
return renderers.TreeGrid(
@@ -29,7 +29,7 @@ class VadWalk(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.PluginRequirement(
name="vadinfo", plugin=vadinfo.VadInfo, version=(2, 0, 0)
@@ -67,7 +67,6 @@ class VadWalk(interfaces.plugins.PluginInterface):
)
def run(self) -> renderers.TreeGrid:
kernel = self.context.modules[self.config["kernel"]]
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
return renderers.TreeGrid(
@@ -85,8 +84,7 @@ class VadWalk(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
@@ -30,7 +30,7 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0)
@@ -53,8 +53,6 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
return yarascan_requirements + vadyarascan_requirements
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
rules = yarascan.YaraScan.process_yara_options(dict(self.config))
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
@@ -63,8 +61,7 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
for task in pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
):
layer_name = task.add_process_layer()
@@ -43,10 +43,10 @@ class VerInfo(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.PluginRequirement(
name="modules", plugin=modules.Modules, version=(2, 0, 0)
name="modules", plugin=modules.Modules, version=(3, 0, 0)
),
requirements.BooleanRequirement(
name="extensive",
@@ -253,19 +253,15 @@ class VerInfo(interfaces.plugins.PluginInterface):
)
def run(self):
kernel = self.context.modules[self.config["kernel"]]
procs = pslist.PsList.list_processes(
self.context, kernel.layer_name, kernel.symbol_table_name
context=self.context, kernel_module_name=self.config["kernel"]
)
mods = modules.Modules.list_modules(
self.context, kernel.layer_name, kernel.symbol_table_name
)
mods = modules.Modules.list_modules(self.context, self.config["kernel"])
# populate the session layers for kernel modules
session_layers = modules.Modules.get_session_layers(
self.context, kernel.layer_name, kernel.symbol_table_name
context=self.context, kernel_module_name=self.config["kernel"]
)
return renderers.TreeGrid(
@@ -0,0 +1,234 @@
# This file is Copyright 2025 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
import os
from typing import List, Tuple, Iterator, Generator, Dict
from volatility3.framework import interfaces, renderers, symbols, exceptions
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows import versions
from volatility3.framework.symbols.windows.extensions import gui
from volatility3.plugins.windows import poolscanner, modules
vollog = logging.getLogger(__name__)
class WindowStations(interfaces.plugins.PluginInterface):
"""Scans for top level Windows Stations"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
# These checks must be completed from newest -> oldest OS version.
_win_version_file_map: List[Tuple[versions.OsDistinguisher, str]] = [
(versions.is_win10_19577_or_later, "gui-win10-19577-x64"),
(versions.is_win10_19041_or_later, "gui-win10-19041-x64"),
(versions.is_win10_18362_or_later, "gui-win10-18362-x64"),
(versions.is_win10_17763_or_later, "gui-win10-17763-x64"),
(versions.is_win10_17134_or_later, "gui-win10-17134-x64"),
(versions.is_win10_16299_or_later, "gui-win10-16299-x64"),
(versions.is_win10_15063_or_later, "gui-win10-15063-x64"),
(versions.is_win10_10586_or_later, "gui-win10-10586-x64"),
(versions.is_windows_8_or_later, "gui-win8-x64"),
(versions.is_windows_7_sp1, "gui-win7sp1-x64"),
(versions.is_windows_7_sp0, "gui-win7sp0-x64"),
]
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
]
@staticmethod
def create_gui_table(
context: interfaces.context.ContextInterface,
symbol_table: str,
config_path: str,
) -> str:
"""Creates a symbol table for windows GUI types
Args:
context: The context to retrieve required elements (layers, symbol tables) from
symbol_table: The name of an existing symbol table containing the kernel symbols
config_path: The configuration path within the context of the symbol table to create
Returns:
The name of the constructed GUI table
"""
if not symbols.symbol_table_is_64bit(
context=context, symbol_table_name=symbol_table
):
raise NotImplementedError(
"This plugin only supports x64 versions of Windows"
)
table_mapping = {"nt_symbols": symbol_table}
try:
symbol_filename = next(
filename
for version_check, filename in WindowStations._win_version_file_map
if version_check(context=context, symbol_table=symbol_table)
)
except StopIteration:
raise NotImplementedError("This version of Windows is not supported!")
vollog.debug(f"Using GUI table {symbol_filename}")
return intermed.IntermediateSymbolTable.create(
context=context,
config_path=config_path,
sub_path=os.path.join("windows", "gui"),
filename=symbol_filename,
class_types=gui.class_types,
table_mapping=table_mapping,
)
@classmethod
def get_session_map(
cls,
context: interfaces.context.ContextInterface,
module_name: str,
gui_table_name: str,
) -> Dict[int, interfaces.context.ModuleInterface]:
"""
Walks each session layer and returns a dictionary that
maps session identifiers to a module in the session's layer
"""
session_map = modules.Modules.get_session_layers_map(context, module_name)
for session_id, session_layer in session_map.items():
session_module = context.module(
gui_table_name, layer_name=session_layer, offset=0
)
session_map[session_id] = session_module
return session_map
@classmethod
def scan_gui_object(
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel_module_name: str,
object_tag: bytes,
object_type: str,
) -> Generator[interfaces.objects.ObjectInterface, None, None]:
"""
An API that generically scans for GUI (win32*.sys) objects allocated in the pools (which is nearly all of them)
This function scans within the kernel space for the tags and then uses `get_session_map` to instantiate objects
in their correct session address space.
Args:
context:
config_path:
kernel_module_name:
object_tag: The 4 byte pool header tag to search for
object_type: The data structure of the GUI object within the pool
"""
kernel = context.modules[kernel_module_name]
gui_table_name = cls.create_gui_table(
context, kernel.symbol_table_name, config_path
)
constraints = poolscanner.PoolScanner.gui_poolscanner_constraints(
gui_table_name, [object_tag]
)
session_map = cls.get_session_map(context, kernel_module_name, gui_table_name)
for result in poolscanner.PoolScanner.generate_pool_scan_extended(
context=context,
kernel_layer_name=kernel.layer_name,
kernel_symbol_table_name=kernel.symbol_table_name,
object_symbol_table_name=gui_table_name,
constraints=constraints,
):
_constraint, mem_object, _header = result
# enforce that objects are in a valid session
# this prevents smear and also ensures future pointer
# dereferences are performed in the correct address space (layer)
try:
session_id = mem_object.get_session_id()
except exceptions.InvalidAddressException:
continue
if session_id is not None:
session_module = session_map.get(session_id, None)
if session_module:
# create the object its own address space (per-session)
yield session_module.object(
object_type=object_type, offset=mem_object.vol.offset
)
@classmethod
def scan_window_stations(
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel_module_name: str,
) -> Iterator[Tuple["gui.tagWINDOWSTATION", str, int]]:
"""
Scans for window stations through `scan_gui_object`
Yields each window station along with its name and session_id
"""
seen = set()
kernel = context.modules[kernel_module_name]
for scanned_winsta in cls.scan_gui_object(
context, config_path, kernel_module_name, b"Wind", "tagWINDOWSTATION"
):
# walk the list of each station found through scanning
for winsta in scanned_winsta.traverse():
if winsta.vol.offset in seen:
continue
seen.add(winsta.vol.offset)
# stations need to have a name and be in a session
name, session_id = winsta.get_info(kernel.symbol_table_name)
if name and session_id is not None:
yield winsta, name, session_id
def _generator(self):
"""
A wrapper around `scan_window_stations`
"""
for winsta, name, session_id in self.scan_window_stations(
self.context, self.config_path, self.config["kernel"]
):
yield (
0,
(
format_hints.Hex(winsta.vol.offset),
name,
session_id,
),
)
# Volatility 2 reported whether the station is interactive or not, but I could not determine if its algorithm
# is currently valid. I also did not see where the old code paths still checked the same bit mask
def run(self):
return renderers.TreeGrid(
[
("Offset", format_hints.Hex),
("Name", str),
("SessionId", int),
],
self._generator(),
)
@@ -237,7 +237,9 @@ class module(generic.GenericIntelProcess):
elf_table_name = self.get_elf_table_name()
symbol_table_name = self.get_symbol_table_name()
is_64bit = symbols.symbol_table_is_64bit(self._context, symbol_table_name)
is_64bit = symbols.symbol_table_is_64bit(
context=self._context, symbol_table_name=symbol_table_name
)
sym_name = "Elf64_Sym" if is_64bit else "Elf32_Sym"
sym_type = self._context.symbol_space.get_type(
elf_table_name + constants.BANG + sym_name
@@ -280,7 +282,9 @@ class module(generic.GenericIntelProcess):
elf_table_name = self.get_elf_table_name()
symbol_table_name = self.get_symbol_table_name()
is_64bit = symbols.symbol_table_is_64bit(self._context, symbol_table_name)
is_64bit = symbols.symbol_table_is_64bit(
context=self._context, symbol_table_name=symbol_table_name
)
sym_name = "Elf64_Sym" if is_64bit else "Elf32_Sym"
sym_type = self._context.symbol_space.get_type(
elf_table_name + constants.BANG + sym_name
@@ -53,7 +53,9 @@ class MMVAD_SHORT(objects.StructType):
# the offset is different on 32 and 64 bits
symbol_table_name = self.vol.type_name.split(constants.BANG)[0]
if not symbols.symbol_table_is_64bit(self._context, symbol_table_name):
if not symbols.symbol_table_is_64bit(
context=self._context, symbol_table_name=symbol_table_name
):
vad_address -= 4
else:
vad_address -= 12
@@ -389,7 +391,9 @@ class EX_FAST_REF(objects.StructType):
# the mask value is different on 32 and 64 bits
symbol_table_name = self.vol.type_name.split(constants.BANG)[0]
if not symbols.symbol_table_is_64bit(self._context, symbol_table_name):
if not symbols.symbol_table_is_64bit(
context=self._context, symbol_table_name=symbol_table_name
):
max_fast_ref = 7
else:
max_fast_ref = 15
@@ -874,6 +878,12 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject):
for peb in pebs:
sym_table = self.get_symbol_table_name()
# Fixes #1636
try:
peb.Ldr
except exceptions.InvalidAddressException:
continue
if peb.Ldr.vol.type_name.split(constants.BANG)[-1] == ("unsigned long"):
sym_table = self.set_types(peb)
@@ -1400,7 +1410,9 @@ class CONTROL_AREA(objects.StructType):
)
mmpte_size = mmpte_type.size
subsection = self.get_subsection()
is_64bit = symbols.symbol_table_is_64bit(self._context, symbol_table_name)
is_64bit = symbols.symbol_table_is_64bit(
context=self._context, symbol_table_name=symbol_table_name
)
is_pae = self._context.layers[self.vol.layer_name].metadata.get("pae", False)
# the sector_size is used as a multiplier to the StartingSector
@@ -0,0 +1,127 @@
# This file is Copyright 2025 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from typing import Optional, Tuple, Iterator
from volatility3.framework import exceptions, constants, interfaces
from volatility3.framework import objects
from volatility3.framework.objects import utility
from volatility3.framework.symbols.windows.extensions import pool
class tagWINDOWSTATION(objects.StructType, pool.ExecutiveObject):
def is_valid(self) -> bool:
sid = self.get_session_id()
return sid is not None and 0 <= sid < 256
def get_session_id(self) -> Optional[int]:
try:
return self.dwSessionId
except exceptions.InvalidAddressException:
return None
def traverse(self, max_stations: int = 15):
"""
Traverses the window stations referenced in the list of stations
"""
seen = set()
# include the first window station
yield self
while len(seen) < max_stations:
try:
winsta = self.rpwinstaNext.dereference()
except exceptions.InvalidAddressException:
break
if winsta.vol.offset in seen:
break
yield winsta
seen.add(winsta.vol.offset)
def get_info(self, kernel_symbol_table_name) -> Optional[Tuple[str, int]]:
try:
name = self.get_name(kernel_symbol_table_name)
session_id = self.get_session_id()
except exceptions.InvalidAddressException:
return None, None
# attempt to avoid smear
if session_id is not None and session_id < 256 and name and len(name) > 1:
return name, session_id
return None, None
def desktops(self, symbol_table_name, max_desktops: int = 12):
seen = set()
while len(seen) < max_desktops:
try:
desktop = self.rpdeskList.dereference()
name = desktop.get_name(symbol_table_name)
except exceptions.InvalidAddressException:
break
if desktop.vol.offset in seen:
break
yield desktop, name
seen.add(desktop.vol.offset)
class tagDESKTOP(objects.StructType, pool.ExecutiveObject):
def is_valid(self) -> bool:
"""
Enforce a valid sid + owning window station
"""
sid = self.get_session_id()
valid_sid = sid is not None and 0 <= sid < 256
if valid_sid:
return self.get_window_station() is not None
return False
def get_window_station(self) -> Optional["tagWINDOWSTATION"]:
try:
return self.rpwinstaParent.dereference()
except exceptions.InvalidAddressException:
return None
def get_session_id(self) -> Optional[int]:
winsta = self.get_window_station()
if winsta:
return winsta.get_session_id()
return None
def get_threads(
self,
) -> Iterator[Tuple[interfaces.objects.ObjectInterface, str, int]]:
"""
Returns the threads of each desktop along with owning process information
"""
symbol_table_name = self.vol.type_name.split(constants.BANG)[0]
for thread in self.PtiList.to_list(
symbol_table_name + constants.BANG + "tagTHREADINFO", "PtiLink"
):
try:
process_name = utility.array_to_string(thread.ppi.Process.ImageFileName)
process_pid = thread.ppi.Process.UniqueProcessId
except exceptions.InvalidAddressException:
continue
yield thread, process_name, process_pid
class_types = {
"tagWINDOWSTATION": tagWINDOWSTATION,
"tagDESKTOP": tagDESKTOP,
}
@@ -78,7 +78,9 @@ class POOL_HEADER(objects.StructType):
# otherwise we have an executive object in the pool
else:
if symbols.symbol_table_is_64bit(self._context, symbol_table_name):
if symbols.symbol_table_is_64bit(
context=self._context, symbol_table_name=symbol_table_name
):
alignment = 16
else:
alignment = 8
@@ -95,7 +97,7 @@ class POOL_HEADER(objects.StructType):
optional_headers,
lengths_of_optional_headers,
) = self._calculate_optional_header_lengths(
self._context, symbol_table_name
self._context, kernel_symbol_table
)
padding_available = (
None
@@ -326,12 +328,18 @@ class ExecutiveObject(interfaces.objects.ObjectInterface):
"""This is used as a "mixin" that provides all kernel executive objects
with a means of finding their own object header."""
def get_object_header(self) -> "OBJECT_HEADER":
def get_object_header(
self, symbol_table_name: Optional[str] = None
) -> "OBJECT_HEADER":
if constants.BANG not in self.vol.type_name:
raise ValueError(
f"Invalid symbol table name syntax (no {constants.BANG} found)"
)
symbol_table_name = self.vol.type_name.split(constants.BANG)[0]
# caller provided symbol table allows for scanning for objects from any module
if not symbol_table_name:
symbol_table_name = self.vol.type_name.split(constants.BANG)[0]
body_offset = self._context.symbol_space.get_type(
symbol_table_name + constants.BANG + "_OBJECT_HEADER"
).relative_child_offset("Body")
@@ -342,6 +350,12 @@ class ExecutiveObject(interfaces.objects.ObjectInterface):
native_layer_name=self.vol.native_layer_name,
)
def get_name(self, symbol_table_name: Optional[str] = None) -> Optional[str]:
try:
return self.get_object_header(symbol_table_name).get_name()
except exceptions.InvalidAddressException:
return None
class OBJECT_HEADER(objects.StructType):
"""A class for the headers for executive kernel objects, which contains
@@ -450,3 +464,22 @@ class OBJECT_HEADER(objects.StructType):
absolute=True,
)
return header
def get_name(self) -> Optional[str]:
"""
Attempts to get the name of the object
Sanity checks size members to avoid FPs
Returns None if any issues detected
"""
try:
name_info = self.NameInfo.Name
if (
name_info.Length == 0
or name_info.MaximumLength == 0
or name_info.Length > name_info.MaximumLength
):
return None
return name_info.String
except (ValueError, exceptions.InvalidAddressException):
return None
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,7 +1,7 @@
import logging
from typing import Callable, Tuple, List, Optional
from typing import Callable, List, Optional, Tuple
from volatility3.framework import interfaces, constants, exceptions
from volatility3.framework import constants, exceptions, interfaces
vollog = logging.getLogger(__name__)
@@ -88,24 +88,6 @@ class OsDistinguisher:
return True
is_windows_8_1_or_later = OsDistinguisher(
version_check=lambda x: x >= (6, 3),
fallback_checks=[("_KPRCB", "PendingTickFlags", True)],
)
is_vista_or_later = OsDistinguisher(
version_check=lambda x: x >= (6, 0),
fallback_checks=[("KdCopyDataBlock", None, True)],
)
is_win10 = OsDistinguisher(
version_check=lambda x: (10, 0) <= x,
fallback_checks=[
("ObHeaderCookie", None, True),
("_HANDLE_TABLE", "HandleCount", False),
],
)
is_windows_xp = OsDistinguisher(
version_check=lambda x: (5, 1) <= x < (5, 2),
fallback_checks=[
@@ -149,6 +131,32 @@ is_2003 = OsDistinguisher(
],
)
is_vista_or_later = OsDistinguisher(
version_check=lambda x: x >= (6, 0),
fallback_checks=[("KdCopyDataBlock", None, True)],
)
is_windows_8_1_or_later = OsDistinguisher(
version_check=lambda x: x >= (6, 3),
fallback_checks=[("_KPRCB", "PendingTickFlags", True)],
)
is_win10 = OsDistinguisher(
version_check=lambda x: (10, 0) <= x,
fallback_checks=[
("ObHeaderCookie", None, True),
("_HANDLE_TABLE", "HandleCount", False),
],
)
is_win10_10586_or_later = OsDistinguisher(
version_check=lambda x: x >= (10, 0, 10586),
fallback_checks=[
("_UNLOADED_DRIVERS", None, False),
("ObHeaderCookie", None, True),
],
)
is_win10_up_to_15063 = OsDistinguisher(
version_check=lambda x: (10, 0) <= x < (10, 0, 15063),
fallback_checks=[
@@ -187,6 +195,22 @@ is_win10_16299_or_later = OsDistinguisher(
],
)
is_win10_17134_or_later = OsDistinguisher(
version_check=lambda x: x >= (10, 0, 17134),
fallback_checks=[
("_EPROCESS", "ProcessFirstResume", True),
("_EPROCESS", "HighMemoryPriority", True),
],
)
is_win10_17735_or_later = OsDistinguisher(
version_check=lambda x: x >= (10, 0, 17735),
fallback_checks=[
("_EPROCESS", "VmProcessorHost", True),
("_EPROCESS", "VdmObjects", False),
],
)
is_win10_17763_or_later = OsDistinguisher(
version_check=lambda x: x >= (10, 0, 17763),
fallback_checks=[
@@ -218,6 +242,14 @@ is_win10_19041_or_later = OsDistinguisher(
],
)
is_win10_19577_or_later = OsDistinguisher(
version_check=lambda x: x >= (10, 0, 19577),
fallback_checks=[
("_EPROCESS", "PaeTop", False),
("_EPROCESS", "IdealProcessorAssignmentBlock", True),
],
)
is_win10_25398_or_later = OsDistinguisher(
version_check=lambda x: x >= (10, 0, 25398),
fallback_checks=[
@@ -235,6 +267,31 @@ is_windows_8_or_later = OsDistinguisher(
version_check=lambda x: x >= (6, 2),
fallback_checks=[("_HANDLE_TABLE", "HandleCount", False)],
)
is_windows_7_sp0 = OsDistinguisher(
version_check=lambda x: x == (6, 1, 7600),
fallback_checks=[
("_EPROCESS", "VdmObjects", True),
("_EPROCESS", "UmsScheduledThreads", False),
# Dropped after vista
("_EPROCESS", "QuotaUsage", False),
# Added win8
("_EPROCESS", "WnfContext", False),
],
)
is_windows_7_sp1 = OsDistinguisher(
version_check=lambda x: x == (6, 1, 7601),
fallback_checks=[
("_EPROCESS", "VdmObjects", False),
("_EPROCESS", "UmsScheduledThreads", True),
# Dropped after vista
("_EPROCESS", "QuotaUsage", False),
# Added win8
("_EPROCESS", "WnfContext", False),
],
)
# Technically, this is win7 or less
is_windows_7 = OsDistinguisher(
version_check=lambda x: x == (6, 1),
@@ -25,7 +25,7 @@ class Certificates(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="printkey", plugin=printkey.PrintKey, version=(1, 0, 0)
@@ -69,13 +69,10 @@ class Certificates(interfaces.plugins.PluginInterface):
return None
def _generator(self) -> Iterator[Tuple[int, Tuple[str, str, str, str]]]:
kernel = self.context.modules[self.config["kernel"]]
for hive in hivelist.HiveList.list_hives(
self.context,
context=self.context,
base_config_path=self.config_path,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
):
for top_key in [
"Microsoft\\SystemCertificates",