Windows PoolScan: Adds method

This updates the poolscanner plugin with an additional method,
`generate_pool_scan_extended`, and does the corresponding minor version
bump.

Co-authored-by: Andrew Case <andrew@dfir.org>
This commit is contained in:
David McDonald
2025-03-05 17:59:38 -06:00
co-authored by Andrew Case
parent be8b7580dd
commit 5ed31d3133
@@ -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]:
@@ -331,11 +332,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: str,
object_symbol_table: str,
constraints: List[PoolConstraint],
) -> Generator[
Tuple[
@@ -347,49 +349,60 @@ 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,
)
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,
)
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)
is_windows_8_or_later = versions.is_windows_8_or_later(
context, kernel_symbol_table
)
# 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, kernel_symbol_table):
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, 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,
)
for mem_object in mem_objects:
@@ -398,6 +411,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 +432,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,