Convert poolscanning APIs to new coding standards

This commit is contained in:
Andrew Case
2025-03-08 17:21:27 +00:00
parent 5347bb8168
commit f69b00b48c
22 changed files with 262 additions and 358 deletions
@@ -21,7 +21,7 @@ class BigPools(interfaces.plugins.PluginInterface):
"""List big page pools."""
_required_framework_version = (2, 0, 0)
_version = (1, 1, 1)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -50,8 +50,7 @@ class BigPools(interfaces.plugins.PluginInterface):
def list_big_pools(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
tags: Optional[list] = None,
show_free: bool = False,
):
@@ -59,19 +58,13 @@ class BigPools(interfaces.plugins.PluginInterface):
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
tags: An optional list of pool tags to filter big page pool tags by
Yields:
A big page pool object
"""
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ntkrnlmp = context.modules[kernel_module_name]
big_page_table_offset = ntkrnlmp.get_symbol("PoolBigPageTable").address
big_page_table = ntkrnlmp.object(
@@ -87,8 +80,10 @@ class BigPools(interfaces.plugins.PluginInterface):
big_page_table_type = ntkrnlmp.get_type("_POOL_TRACKER_BIG_PAGES")
except exceptions.SymbolError:
# We have to manually load a symbol table
is_vista_or_later = versions.is_vista_or_later(context, symbol_table)
is_win10 = versions.is_win10(context, symbol_table)
is_vista_or_later = versions.is_vista_or_later(
context, ntkrnlmp.symbol_table_name
)
is_win10 = versions.is_win10(context, ntkrnlmp.symbol_table_name)
if is_win10:
big_pools_json_filename = "bigpools-win10"
elif is_vista_or_later:
@@ -96,7 +91,7 @@ class BigPools(interfaces.plugins.PluginInterface):
else:
big_pools_json_filename = "bigpools"
if symbols.symbol_table_is_64bit(context, symbol_table):
if symbols.symbol_table_is_64bit(context, ntkrnlmp.symbol_table_name):
big_pools_json_filename += "-x64"
else:
big_pools_json_filename += "-x86"
@@ -104,16 +99,17 @@ class BigPools(interfaces.plugins.PluginInterface):
new_table_name = intermed.IntermediateSymbolTable.create(
context=context,
config_path=configuration.path_join(
context.symbol_space[symbol_table].config_path, "bigpools"
context.symbol_space[ntkrnlmp.symbol_table_name].config_path,
"bigpools",
),
sub_path=os.path.join("windows", "bigpools"),
filename=big_pools_json_filename,
table_mapping={"nt_symbols": symbol_table},
table_mapping={"nt_symbols": ntkrnlmp.symbol_table_name},
class_types={
"_POOL_TRACKER_BIG_PAGES": extensions.pool.POOL_TRACKER_BIG_PAGES
},
)
module = context.module(new_table_name, layer_name, offset=0)
module = context.module(new_table_name, ntkrnlmp.layer_name, offset=0)
big_page_table_type = module.get_type("_POOL_TRACKER_BIG_PAGES")
big_pools = ntkrnlmp.object(
@@ -136,12 +132,10 @@ class BigPools(interfaces.plugins.PluginInterface):
tags = [tag for tag in self.config["tags"].split(",")]
else:
tags = None
kernel = self.context.modules[self.config["kernel"]]
for big_pool in self.list_big_pools(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
tags=tags,
show_free=self.config.get("show-free"),
):
@@ -28,7 +28,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
"""Lists kernel callbacks and notification routines."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 1)
_version = (3, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -42,7 +42,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
name="poolscanner", plugin=poolscanner.PoolScanner, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="driverirp", plugin=driverirp.DriverIrp, version=(1, 0, 0)
@@ -211,8 +211,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
def scan(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
nt_symbol_table: str,
kernel_module_name: str,
callback_symbol_table: str,
) -> Iterable[
Tuple[
@@ -225,18 +224,21 @@ class Callbacks(interfaces.plugins.PluginInterface):
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
nt_symbol_table: The name of the table containing the kernel symbols
kernel_module_name: Name of the module for the kernel
callback_symbol_table: The name of the table containing the callback object symbols (_SHUTDOWN_PACKET etc.)
Returns:
A list of callback objects found by scanning the `layer_name` layer for callback pool signatures
"""
kernel = context.modules[kernel_module_name]
is_vista_or_later = versions.is_vista_or_later(
context=context, symbol_table=nt_symbol_table
context=context, symbol_table=kernel.symbol_table_name
)
type_map = handles.Handles.get_type_map(context, layer_name, nt_symbol_table)
type_map = handles.Handles.get_type_map(
context, kernel.layer_name, kernel.symbol_table_name
)
constraints = cls.create_callback_scan_constraints(
context, callback_symbol_table, is_vista_or_later
@@ -247,7 +249,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
mem_object,
_header,
) in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, nt_symbol_table, constraints
context, kernel_module_name, constraints
):
try:
if isinstance(mem_object, callbacks._SHUTDOWN_PACKET):
@@ -347,31 +349,24 @@ class Callbacks(interfaces.plugins.PluginInterface):
def list_notify_routines(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
callback_table_name: str,
) -> Iterable[Tuple[str, int, Optional[str]]]:
"""Lists all kernel notification routines.
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 of the kernel
callback_table_name: The name of the table containing the callback symbols
Yields:
A name, location and optional detail string
"""
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ntkrnlmp = context.modules[kernel_module_name]
is_vista_or_later = versions.is_vista_or_later(
context=context, symbol_table=symbol_table
context=context, symbol_table=ntkrnlmp.symbol_table_name
)
full_type_name = callback_table_name + constants.BANG + "_GENERIC_CALLBACK"
@@ -416,20 +411,14 @@ class Callbacks(interfaces.plugins.PluginInterface):
def _list_registry_callbacks_legacy(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
callback_table_name: str,
) -> Iterable[Tuple[str, int, None]]:
"""
Lists all registry callbacks from the old format via the CmpCallBackVector.
"""
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ntkrnlmp = context.modules[kernel_module_name]
full_type_name = (
callback_table_name + constants.BANG + "_EX_CALLBACK_ROUTINE_BLOCK"
)
@@ -467,20 +456,13 @@ class Callbacks(interfaces.plugins.PluginInterface):
def _list_registry_callbacks_new(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
callback_table_name: str,
) -> Iterable[Tuple[str, int, Optional[str]]]:
"""
Lists all registry callbacks via the CallbackListHead.
"""
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ntkrnlmp = context.modules[kernel_module_name]
full_type_name = callback_table_name + constants.BANG + "_CM_CALLBACK_ENTRY"
symbol_offset = ntkrnlmp.get_symbol("CallbackListHead").address
@@ -504,40 +486,33 @@ class Callbacks(interfaces.plugins.PluginInterface):
def list_registry_callbacks(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
callback_table_name: str,
) -> Iterable[Tuple[str, int, Optional[str]]]:
"""Lists all registry callbacks.
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 of the kernel
callback_table_name: The name of the table containing the callback symbols
Yields:
A name, location and optional detail string
"""
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ntkrnlmp = context.modules[kernel_module_name]
if ntkrnlmp.has_symbol("CmpCallBackVector") and ntkrnlmp.has_symbol(
"CmpCallBackCount"
):
yield from cls._list_registry_callbacks_legacy(
context, layer_name, symbol_table, callback_table_name
context, kernel_module_name, callback_table_name
)
elif ntkrnlmp.has_symbol("CallbackListHead") and ntkrnlmp.has_symbol(
"CmpCallBackCount"
):
yield from cls._list_registry_callbacks_new(
context, layer_name, symbol_table, callback_table_name
context, kernel_module_name, callback_table_name
)
else:
symbols_to_check = [
@@ -552,14 +527,11 @@ class Callbacks(interfaces.plugins.PluginInterface):
symbol_status = "exists"
vollog.debug(f"symbol {symbol_name} {symbol_status}.")
return None
@classmethod
def list_bugcheck_reason_callbacks(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
callback_table_name: str,
) -> Iterable[
Tuple[
@@ -572,20 +544,14 @@ class Callbacks(interfaces.plugins.PluginInterface):
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 of the kernel
callback_table_name: The name of the table containing the callback symbols
Yields:
A name, location and optional detail string
"""
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ntkrnlmp = context.modules[kernel_module_name]
try:
list_offset = ntkrnlmp.get_symbol(
@@ -599,11 +565,15 @@ class Callbacks(interfaces.plugins.PluginInterface):
callback_table_name + constants.BANG + "_KBUGCHECK_REASON_CALLBACK_RECORD"
)
callback_record = context.object(
object_type=full_type_name, offset=kvo + list_offset, layer_name=layer_name
object_type=full_type_name,
offset=ntkrnlmp.offset + list_offset,
layer_name=ntkrnlmp.layer_name,
)
for callback in callback_record.Entry:
if not context.layers[layer_name].is_valid(callback.CallbackRoutine, 64):
if not context.layers[ntkrnlmp.layer_name].is_valid(
callback.CallbackRoutine, 64
):
continue
try:
@@ -626,8 +596,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
def list_bugcheck_callbacks(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
callback_table_name: str,
) -> Iterable[
Tuple[
@@ -640,20 +609,13 @@ class Callbacks(interfaces.plugins.PluginInterface):
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 of the kernel
callback_table_name: The name of the table containing the callback symbols
Yields:
A name, location and optional detail string
"""
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ntkrnlmp = context.modules[kernel_module_name]
try:
list_offset = ntkrnlmp.get_symbol("KeBugCheckCallbackListHead").address
@@ -665,17 +627,20 @@ class Callbacks(interfaces.plugins.PluginInterface):
callback_table_name + constants.BANG + "_KBUGCHECK_CALLBACK_RECORD"
)
callback_record = context.object(
full_type_name, offset=kvo + list_offset, layer_name=layer_name
full_type_name,
offset=ntkrnlmp.offset + list_offset,
layer_name=ntkrnlmp.layer_name,
)
for callback in callback_record.Entry:
if not context.layers[layer_name].is_valid(callback.CallbackRoutine, 64):
if not context.layers[ntkrnlmp.layer_name].is_valid(
callback.CallbackRoutine, 64
):
continue
try:
component = context.object(
symbol_table + constants.BANG + "string",
layer_name=layer_name,
component = ntkrnlmp.object(
"string",
offset=callback.Component,
max_length=64,
errors="replace",
@@ -708,8 +673,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
for callback_method in callback_methods:
for callback_type, callback_address, callback_detail in callback_method(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
callback_symbol_table,
):
if callback_detail is None:
@@ -24,7 +24,7 @@ class CmdScan(interfaces.plugins.PluginInterface):
"""Looks for Windows Command History lists"""
_required_framework_version = (2, 4, 0)
_version = (1, 0, 0)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
@@ -39,7 +39,7 @@ class CmdScan(interfaces.plugins.PluginInterface):
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.PluginRequirement(
name="consoles", plugin=consoles.Consoles, version=(2, 0, 0)
name="consoles", plugin=consoles.Consoles, version=(3, 0, 0)
),
requirements.BooleanRequirement(
name="no_registry",
@@ -83,9 +83,8 @@ class CmdScan(interfaces.plugins.PluginInterface):
def get_command_history(
cls,
context: interfaces.context.ContextInterface,
kernel_layer_name: str,
kernel_symbol_table_name: str,
config_path: str,
kernel_module_name: str,
procs: Generator[interfaces.objects.ObjectInterface, None, None],
max_history: Set[int],
) -> Tuple[
@@ -97,8 +96,6 @@ class CmdScan(interfaces.plugins.PluginInterface):
Args:
context: The context to retrieve required elements (layers, symbol tables) from
kernel_layer_name: The name of the layer on which to operate
kernel_symbol_table_name: The name of the table containing the kernel symbols
config_path: The config path where to find symbol files
procs: List of process objects
max_history: An initial set of CommandHistorySize values
@@ -135,9 +132,8 @@ class CmdScan(interfaces.plugins.PluginInterface):
if conhost_symbol_table is None:
conhost_symbol_table = consoles.Consoles.create_conhost_symbol_table(
context,
kernel_layer_name,
kernel_symbol_table_name,
config_path,
kernel_module_name,
proc_layer_name,
conhostexe_base,
)
@@ -279,8 +275,6 @@ class CmdScan(interfaces.plugins.PluginInterface):
procs: the process list filtered to conhost.exe instances
"""
kernel = self.context.modules[self.config["kernel"]]
max_history = set(self.config.get("max_history", [50]))
no_registry = self.config.get("no_registry")
@@ -302,9 +296,8 @@ class CmdScan(interfaces.plugins.PluginInterface):
command_history_properties,
) in self.get_command_history(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config_path,
self.config["kernel"],
procs,
max_history,
):
@@ -31,7 +31,7 @@ class Consoles(interfaces.plugins.PluginInterface):
_required_framework_version = (2, 4, 0)
# 2.0.0 - change the signature of `get_console_settings_from_registry`
_version = (2, 0, 0)
_version = (3, 0, 0)
@classmethod
def get_requirements(cls):
@@ -128,9 +128,8 @@ class Consoles(interfaces.plugins.PluginInterface):
def determine_conhost_version(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
nt_symbol_table: str,
config_path: str,
kernel_module_name: str,
conhost_layer_name: str,
conhost_base: int,
) -> Tuple[Optional[str], Dict[str, Type]]:
@@ -139,9 +138,8 @@ class Consoles(interfaces.plugins.PluginInterface):
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
nt_symbol_table: The name of the table containing the kernel symbols
config_path: The config path where to find symbol files
kernel_module_name: The name of the module for the kernel
conhost_layer_name: The name of the conhot process memory layer
conhost_base: the base address of conhost.exe
@@ -149,8 +147,10 @@ class Consoles(interfaces.plugins.PluginInterface):
The filename of the symbol table to use and the associated class types.
"""
kernel = context.modules[kernel_module_name]
is_64bit = symbols.symbol_table_is_64bit(
context=context, symbol_table_name=nt_symbol_table
context=context, symbol_table_name=kernel.symbol_table_name
)
if is_64bit:
@@ -158,9 +158,9 @@ class Consoles(interfaces.plugins.PluginInterface):
else:
arch = "x86"
vers = info.Info.get_version_structure(context, layer_name, nt_symbol_table)
vers = info.Info.get_version_structure(context, kernel_module_name)
kuser = info.Info.get_kuser_structure(context, layer_name, nt_symbol_table)
kuser = info.Info.get_kuser_structure(context, kernel_module_name)
try:
vers_minor_version = int(vers.MinorVersion)
@@ -247,7 +247,7 @@ class Consoles(interfaces.plugins.PluginInterface):
)
except (exceptions.InvalidAddressException, TypeError, AttributeError):
# the following is IntelLayer specific and might need to be adapted to other architectures.
physical_layer_name = context.layers[layer_name].config.get(
physical_layer_name = context.layers[kernel.layer_name].config.get(
"memory_layer", None
)
if physical_layer_name:
@@ -318,9 +318,8 @@ class Consoles(interfaces.plugins.PluginInterface):
def create_conhost_symbol_table(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
nt_symbol_table: str,
config_path: str,
kernel_module_name: str,
conhost_layer_name: str,
conhost_base: int,
) -> str:
@@ -328,20 +327,20 @@ class Consoles(interfaces.plugins.PluginInterface):
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
nt_symbol_table: The name of the table containing the kernel symbols
config_path: The config path where to find symbol files
kernel_module_name: The name of the module of the kernel
Returns:
The name of the constructed symbol table
"""
table_mapping = {"nt_symbols": nt_symbol_table}
kernel = context.modules[kernel_module_name]
table_mapping = {"nt_symbols": kernel.symbol_table_name}
symbol_filename, class_types = cls.determine_conhost_version(
context,
layer_name,
nt_symbol_table,
config_path,
kernel_module_name,
conhost_layer_name,
conhost_base,
)
@@ -366,9 +365,8 @@ class Consoles(interfaces.plugins.PluginInterface):
def get_console_info(
cls,
context: interfaces.context.ContextInterface,
kernel_layer_name: str,
kernel_table_name: str,
config_path: str,
kernel_module_name: str,
procs: Generator[interfaces.objects.ObjectInterface, None, None],
max_history: Set[int],
max_buffers: Set[int],
@@ -385,9 +383,8 @@ class Consoles(interfaces.plugins.PluginInterface):
Args:
context: The context to retrieve required elements (layers, symbol tables) from
kernel_layer_name: The name of the layer on which to operate
kernel_table_name: The name of the table containing the kernel symbols
config_path: The config path where to find symbol files
kernel_module_name: The name of the module for the kernel
procs: list of process objects
max_history: an initial set of CommandHistorySize values
max_buffers: an initial list of HistoryBufferMax values
@@ -427,9 +424,8 @@ class Consoles(interfaces.plugins.PluginInterface):
if conhost_symbol_table is None:
conhost_symbol_table = cls.create_conhost_symbol_table(
context,
kernel_layer_name,
kernel_table_name,
config_path,
kernel_module_name,
proc_layer_name,
conhostexe_base,
)
@@ -486,7 +482,7 @@ class Consoles(interfaces.plugins.PluginInterface):
console_properties.append(
{
"level": 1,
"name": "_CONSOLE_INFORMATION.ScreenX",
"kernel_module_nameme": "_CONSOLE_INFORMATION.ScreenX",
"address": console_info.ScreenX.vol.offset,
"data": console_info.ScreenX,
}
@@ -810,8 +806,7 @@ class Consoles(interfaces.plugins.PluginInterface):
Args:
context: The context to retrieve required elements (layers, symbol tables) from
config_path: The config path where to find symbol files
kernel_layer_name: The name of the layer on which to operate
kernel_symbol_table_name: The name of the table containing the kernel symbols
kernel_module_name: The name of the module for the kernel
max_history: an initial set of CommandHistorySize values
max_buffers: an initial list of HistoryBufferMax values
@@ -853,8 +848,6 @@ class Consoles(interfaces.plugins.PluginInterface):
procs: the process list filtered to conhost.exe instances
"""
kernel = self.context.modules[self.config["kernel"]]
max_history = set(self.config.get("max_history", [50]))
max_buffers = set(self.config.get("max_buffers", [4]))
no_registry = self.config.get("no_registry")
@@ -874,9 +867,8 @@ class Consoles(interfaces.plugins.PluginInterface):
proc = None
for proc, console_info, console_properties in self.get_console_info(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config_path,
self.config["kernel"],
procs,
max_history,
max_buffers,
@@ -37,13 +37,13 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="psscan", component=psscan.PsScan, version=(1, 1, 0)
name="psscan", component=psscan.PsScan, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="pedump", component=pedump.PEDump, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="info", component=info.Info, version=(1, 0, 0)
name="info", component=info.Info, version=(2, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -85,11 +85,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
self.context, self.config_path, "windows", "pe", class_types=pe.class_types
)
kernel = self.context.modules[self.config["kernel"]]
kuser = info.Info.get_kuser_structure(
self.context, kernel.layer_name, kernel.symbol_table_name
)
kuser = info.Info.get_kuser_structure(self.context, self.config["kernel"])
nt_major_version = int(kuser.NtMajorVersion)
nt_minor_version = int(kuser.NtMinorVersion)
@@ -209,8 +205,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
if self.config["offset"]:
procs = psscan.PsScan.scan_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
filter_func=psscan.PsScan.create_offset_filter(
self.context,
kernel.layer_name,
@@ -25,7 +25,7 @@ class DriverScan(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
name="poolscanner", plugin=poolscanner.PoolScanner, version=(2, 0, 0)
),
]
@@ -48,15 +48,11 @@ class DriverScan(interfaces.plugins.PluginInterface):
kernel = context.modules[kernel_module_name]
symbol_table_name = kernel.symbol_table_name
layer_name = kernel.layer_name
constraints = poolscanner.PoolScanner.builtin_constraints(
symbol_table_name, [b"Dri\xf6", b"Driv"]
kernel.symbol_table_name, [b"Dri\xf6", b"Driv"]
)
module = context.module(symbol_table_name, layer_name, 0)
driver_start_offset = module.get_type("_DRIVER_OBJECT").relative_child_offset(
driver_start_offset = kernel.get_type("_DRIVER_OBJECT").relative_child_offset(
"DriverStart"
)
@@ -65,7 +61,7 @@ class DriverScan(interfaces.plugins.PluginInterface):
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, symbol_table_name, constraints
context, kernel_module_name, constraints
):
_constraint, mem_object, _header = result
@@ -14,7 +14,7 @@ class FileScan(interfaces.plugins.PluginInterface):
"""Scans for file objects present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
@@ -25,7 +25,7 @@ class FileScan(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
name="poolscanner", plugin=poolscanner.PoolScanner, version=(2, 0, 0)
),
]
@@ -33,36 +33,32 @@ class FileScan(interfaces.plugins.PluginInterface):
def scan_files(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Scans for file objects 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: The name of the module for the kernel
Returns:
A list of File objects as found from the `layer_name` layer based on File pool signatures
"""
kernel = context.modules[kernel_module_name]
constraints = poolscanner.PoolScanner.builtin_constraints(
symbol_table, [b"Fil\xe5", b"File"]
kernel.symbol_table_name, [b"Fil\xe5", b"File"]
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, symbol_table, constraints
context, kernel_module_name, constraints
):
_constraint, mem_object, _header = result
yield mem_object
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
for fileobj in self.scan_files(
self.context, kernel.layer_name, kernel.symbol_table_name
):
for fileobj in self.scan_files(self.context, self.config["kernel"]):
try:
file_name = fileobj.FileName.String
except exceptions.InvalidAddressException:
@@ -39,7 +39,7 @@ class Handles(interfaces.plugins.PluginInterface):
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="psscan", component=psscan.PsScan, version=(1, 1, 0)
name="psscan", component=psscan.PsScan, version=(2, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -376,8 +376,7 @@ class Handles(interfaces.plugins.PluginInterface):
if self.config["offset"]:
procs = psscan.PsScan.scan_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
filter_func=psscan.PsScan.create_offset_filter(
self.context,
kernel.layer_name,
@@ -52,7 +52,7 @@ class IndirectSystemCalls(direct_system_calls.DirectSystemCalls):
requirements.PluginRequirement(
name="direct_system_calls",
plugin=direct_system_calls.DirectSystemCalls,
version=(1, 0, 0),
version=(2, 0, 0),
),
]
+35 -35
View File
@@ -17,7 +17,7 @@ class Info(plugins.PluginInterface):
"""Show OS & kernel details of the memory sample being analyzed."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -56,6 +56,9 @@ class Info(plugins.PluginInterface):
# FileLayer won't have dependencies
pass
# FIXME - this needs to be deprecated. This is exactly the same
# as getting it from context.modules
# Deprecation warning will go once the API is overhauled
@classmethod
def get_kernel_module(
cls,
@@ -80,13 +83,12 @@ class Info(plugins.PluginInterface):
cls,
context: interfaces.context.ContextInterface,
config_path: str,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> interfaces.objects.ObjectInterface:
"""Returns the KDDEBUGGER_DATA64 structure for a kernel"""
ntkrnlmp = cls.get_kernel_module(context, layer_name, symbol_table)
ntkrnlmp = context.modules[kernel_module_name]
native_types = context.symbol_space[symbol_table].natives
native_types = context.symbol_space[ntkrnlmp.symbol_table_name].natives
kdbg_offset = ntkrnlmp.get_symbol("KdDebuggerDataBlock").address
@@ -102,7 +104,7 @@ class Info(plugins.PluginInterface):
kdbg_obj = context.object(
kdbg_table_name + constants.BANG + "_KDDEBUGGER_DATA64",
offset=ntkrnlmp.offset + kdbg_offset,
layer_name=layer_name,
layer_name=ntkrnlmp.layer_name,
)
return kdbg_obj
@@ -111,16 +113,15 @@ class Info(plugins.PluginInterface):
def get_kuser_structure(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> interfaces.objects.ObjectInterface:
"""Returns the _KUSER_SHARED_DATA structure for a kernel"""
virtual_layer = context.layers[layer_name]
ntkrnlmp = context.modules[kernel_module_name]
virtual_layer = context.layers[ntkrnlmp.layer_name]
if not isinstance(virtual_layer, layers.intel.Intel):
raise TypeError("Virtual Layer is not an intel layer")
ntkrnlmp = cls.get_kernel_module(context, layer_name, symbol_table)
# this is a hard-coded address in the Windows OS
if virtual_layer.bits_per_register == 32:
kuser_addr = 0xFFDF0000
@@ -129,7 +130,6 @@ class Info(plugins.PluginInterface):
kuser = ntkrnlmp.object(
object_type="_KUSER_SHARED_DATA",
layer_name=layer_name,
offset=kuser_addr,
absolute=True,
)
@@ -140,17 +140,15 @@ class Info(plugins.PluginInterface):
def get_version_structure(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> interfaces.objects.ObjectInterface:
"""Returns the KdVersionBlock information from a kernel"""
ntkrnlmp = cls.get_kernel_module(context, layer_name, symbol_table)
ntkrnlmp = context.modules[kernel_module_name]
vers_offset = ntkrnlmp.get_symbol("KdVersionBlock").address
vers = ntkrnlmp.object(
object_type="_DBGKD_GET_VERSION64",
layer_name=layer_name,
offset=vers_offset,
)
@@ -193,35 +191,38 @@ class Info(plugins.PluginInterface):
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
layer_name = kernel.layer_name
symbol_table = kernel.symbol_table_name
layer = self.context.layers[layer_name]
table = self.context.symbol_space[symbol_table]
kernel_layer = self.context.layers[kernel.layer_name]
symbol_table = self.context.symbol_space[kernel.symbol_table_name]
kdbg = self.get_kdbg_structure(
self.context, self.config_path, layer_name, symbol_table
self.context,
self.config_path,
self.config["kernel"],
)
yield (0, ("Kernel Base", hex(layer.config["kernel_virtual_offset"])))
yield (0, ("DTB", hex(layer.config["page_map_offset"])))
yield (0, ("Symbols", table.config["isf_url"]))
yield (0, ("Kernel Base", hex(kernel_layer.config["kernel_virtual_offset"])))
yield (0, ("DTB", hex(kernel_layer.config["page_map_offset"])))
yield (0, ("Symbols", symbol_table.config["isf_url"]))
yield (
0,
(
"Is64Bit",
str(
symbols.symbol_table_is_64bit(
context=self.context, symbol_table_name=symbol_table
context=self.context, symbol_table_name=kernel.symbol_table_name
)
),
),
)
yield (
0,
("IsPAE", str(self.context.layers[layer_name].metadata.get("pae", False))),
(
"IsPAE",
str(self.context.layers[kernel.layer_name].metadata.get("pae", False)),
),
)
for i, layer in self.get_depends(self.context, layer_name):
for i, layer in self.get_depends(self.context, kernel.layer_name):
yield (0, (layer.name, f"{i} {layer.__class__.__name__}"))
if kdbg.Header.OwnerTag == 0x4742444B:
@@ -229,23 +230,22 @@ class Info(plugins.PluginInterface):
yield (0, ("NTBuildLab", kdbg.get_build_lab()))
yield (0, ("CSDVersion", str(kdbg.get_csdversion())))
vers = self.get_version_structure(self.context, layer_name, symbol_table)
vers = self.get_version_structure(self.context, self.config["kernel"])
yield (0, ("KdVersionBlock", hex(vers.vol.offset)))
yield (0, ("Major/Minor", f"{vers.MajorVersion}.{vers.MinorVersion}"))
yield (0, ("MachineType", str(vers.MachineType)))
ntkrnlmp = self.get_kernel_module(self.context, layer_name, symbol_table)
cpu_count_offset = kernel.get_symbol("KeNumberProcessors").address
cpu_count_offset = ntkrnlmp.get_symbol("KeNumberProcessors").address
cpu_count = ntkrnlmp.object(
object_type="unsigned int", layer_name=layer_name, offset=cpu_count_offset
cpu_count = kernel.object(
object_type="unsigned int",
offset=cpu_count_offset,
)
yield (0, ("KeNumberProcessors", str(cpu_count)))
kuser = self.get_kuser_structure(self.context, layer_name, symbol_table)
kuser = self.get_kuser_structure(self.context, self.config["kernel"])
yield (0, ("SystemTime", str(kuser.SystemTime.get_time())))
yield (
@@ -266,7 +266,7 @@ class Info(plugins.PluginInterface):
# yield (0, ("SafeBootMode", "True" if kuser.SafeBootMode else "False"))
nt_header = self.get_ntheader_structure(
self.context, self.config_path, layer_name
self.context, self.config_path, kernel.layer_name
)
yield (
@@ -32,7 +32,7 @@ class ModScan(modules.Modules):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="poolscanner", component=poolscanner.PoolScanner, version=(1, 0, 0)
name="poolscanner", component=poolscanner.PoolScanner, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(3, 0, 0)
@@ -81,7 +81,7 @@ class ModScan(modules.Modules):
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, kernel.layer_name, kernel.symbol_table_name, constraints
context, kernel_module_name, constraints
):
_constraint, mem_object, _header = result
yield mem_object
@@ -14,6 +14,7 @@ class MutantScan(interfaces.plugins.PluginInterface):
"""Scans for mutexes present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
@@ -24,7 +25,7 @@ class MutantScan(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
name="poolscanner", plugin=poolscanner.PoolScanner, version=(2, 0, 0)
),
]
@@ -32,36 +33,32 @@ class MutantScan(interfaces.plugins.PluginInterface):
def scan_mutants(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Scans for mutants 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: The name of the module for the kernel
Returns:
A list of Mutant objects found by scanning memory for the Mutant pool signatures
"""
kernel = context.modules[kernel_module_name]
constraints = poolscanner.PoolScanner.builtin_constraints(
symbol_table, [b"Mut\xe1", b"Muta"]
kernel.symbol_table_name, [b"Mut\xe1", b"Muta"]
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, symbol_table, constraints
context, kernel_module_name, constraints
):
_constraint, mem_object, _header = result
yield mem_object
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
for mutant in self.scan_mutants(
self.context, kernel.layer_name, kernel.symbol_table_name
):
for mutant in self.scan_mutants(self.context, self.config["kernel"]):
try:
name = mutant.get_name()
except (ValueError, exceptions.InvalidAddressException):
@@ -23,7 +23,7 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Scans for network objects present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
@@ -34,10 +34,10 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="poolscanner", component=poolscanner.PoolScanner, version=(1, 0, 0)
name="poolscanner", component=poolscanner.PoolScanner, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="info", component=info.Info, version=(1, 0, 0)
name="info", component=info.Info, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="verinfo", component=verinfo.VerInfo, version=(1, 0, 0)
@@ -117,15 +117,13 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def determine_tcpip_version(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
nt_symbol_table: str,
kernel_module_name: str,
) -> Tuple[str, Type]:
"""Tries to determine which symbol filename to use for the image's tcpip driver. The logic is partially taken from the info plugin.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
nt_symbol_table: The name of the table containing the kernel symbols
kernel_module_name: Name of the module for the kernel
Returns:
The filename of the symbol table to use.
@@ -137,12 +135,14 @@ 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.
kernel = context.modules[kernel_module_name]
is_64bit = symbols.symbol_table_is_64bit(
context=context, symbol_table_name=nt_symbol_table
context=context, symbol_table_name=kernel.symbol_table_name
)
is_18363_or_later = versions.is_win10_18363_or_later(
context=context, symbol_table=nt_symbol_table
context=context, symbol_table=kernel.symbol_table_name
)
if is_64bit:
@@ -150,9 +150,9 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
else:
arch = "x86"
vers = info.Info.get_version_structure(context, layer_name, nt_symbol_table)
vers = info.Info.get_version_structure(context, kernel_module_name)
kuser = info.Info.get_kuser_structure(context, layer_name, nt_symbol_table)
kuser = info.Info.get_kuser_structure(context, kernel_module_name)
try:
vers_minor_version = int(vers.MinorVersion)
@@ -259,7 +259,7 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"Requiring further version inspection due to OS version by checking tcpip.sys's FileVersion header"
)
# the following is IntelLayer specific and might need to be adapted to other architectures.
physical_layer_name = context.layers[layer_name].config.get(
physical_layer_name = context.layers[kernel.layer_name].config.get(
"memory_layer", None
)
if physical_layer_name:
@@ -322,27 +322,26 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def create_netscan_symbol_table(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
nt_symbol_table: str,
kernel_module_name: str,
config_path: str,
) -> str:
"""Creates a symbol table for TCP Listeners and TCP/UDP Endpoints.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
nt_symbol_table: The name of the table containing the kernel symbols
kernel_module_name: Name of the module for the kernel
config_path: The config path where to find symbol files
Returns:
The name of the constructed symbol table
"""
table_mapping = {"nt_symbols": nt_symbol_table}
kernel = context.modules[kernel_module_name]
table_mapping = {"nt_symbols": kernel.symbol_table_name}
symbol_filename, class_types = cls.determine_tcpip_version(
context,
layer_name,
nt_symbol_table,
kernel_module_name,
)
return intermed.IntermediateSymbolTable.create(
@@ -358,16 +357,14 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def scan(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
nt_symbol_table: str,
kernel_module_name: str,
netscan_symbol_table: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Scans for network objects 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
nt_symbol_table: The name of the table containing the kernel symbols
kernel_module_name: The name of the module for the kernel
netscan_symbol_table: The name of the table containing the network object symbols (_TCP_LISTENER etc.)
Returns:
@@ -377,7 +374,7 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
constraints = cls.create_netscan_constraints(context, netscan_symbol_table)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, nt_symbol_table, constraints
context, kernel_module_name, constraints
):
_constraint, mem_object, _header = result
yield mem_object
@@ -385,16 +382,13 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def _generator(self, show_corrupt_results: Optional[bool] = None):
"""Generates the network objects for use in rendering."""
kernel = self.context.modules[self.config["kernel"]]
netscan_symbol_table = self.create_netscan_symbol_table(
self.context, kernel.layer_name, kernel.symbol_table_name, self.config_path
self.context, self.config["kernel"], self.config_path
)
for netw_obj in self.scan(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
netscan_symbol_table,
):
vollog.debug(
@@ -34,7 +34,7 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="netscan", component=netscan.NetScan, version=(1, 0, 0)
name="netscan", component=netscan.NetScan, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(3, 0, 0)
@@ -43,7 +43,7 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
name="pdbutil", component=pdbutil.PDBUtility, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="info", component=info.Info, version=(1, 0, 0)
name="info", component=info.Info, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="verinfo", component=verinfo.VerInfo, version=(1, 0, 0)
@@ -629,7 +629,7 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
kernel = self.context.modules[self.config["kernel"]]
netscan_symbol_table = netscan.NetScan.create_netscan_symbol_table(
self.context, kernel.layer_name, kernel.symbol_table_name, self.config_path
self.context, self.config["kernel"], self.config_path
)
tcpip_module = self.get_tcpip_module(self.context, self.config["kernel"])
@@ -129,7 +129,7 @@ class PoolScanner(plugins.PluginInterface):
"""A generic pool scanner plugin."""
_required_framework_version = (2, 0, 0)
_version = (1, 1, 1)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -151,7 +151,7 @@ class PoolScanner(plugins.PluginInterface):
constraints = self.builtin_constraints(symbol_table)
for constraint, mem_object, header in self.generate_pool_scan(
self.context, kernel.layer_name, symbol_table, constraints
self.context, self.config["kernel"], constraints
):
# generate some type-specific info for sanity checking
if constraint.object_type == "Process":
@@ -365,8 +365,7 @@ class PoolScanner(plugins.PluginInterface):
def generate_pool_scan_extended(
cls,
context: interfaces.context.ContextInterface,
kernel_layer_name: str,
kernel_symbol_table_name: str,
kernel_module_name: str,
object_symbol_table_name: str,
constraints: List[PoolConstraint],
) -> Generator[
@@ -384,41 +383,42 @@ class PoolScanner(plugins.PluginInterface):
Args:
context: The context to retrieve required elements (layers, symbol tables) from
kernel_layer_name: The name of the base kernel layer
kernel_symbol_table_name: The name of the table containing the kernel symbols
kernel_module_name: The name of the module for the kernel
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
"""
kernel = context.modules[kernel_module_name]
# get the object type map
type_map = handles.Handles.get_type_map(
context=context,
layer_name=kernel_layer_name,
symbol_table=kernel_symbol_table_name,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
)
cookie = handles.Handles.find_cookie(
context=context,
layer_name=kernel_layer_name,
symbol_table=kernel_symbol_table_name,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
)
is_windows_10 = versions.is_windows_10(context, kernel_symbol_table_name)
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
context, kernel.symbol_table_name
)
# start off with the primary virtual layer
scan_layer = kernel_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=context, symbol_table_name=kernel_symbol_table_name
context=context, symbol_table_name=kernel.symbol_table_name
):
alignment = 0x10
else:
@@ -433,12 +433,11 @@ class PoolScanner(plugins.PluginInterface):
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=kernel_layer_name,
kernel_symbol_table=kernel_symbol_table_name,
native_layer_name=kernel.layer_name,
kernel_symbol_table=kernel.symbol_table_name,
)
for mem_object in mem_objects:
@@ -472,8 +471,7 @@ class PoolScanner(plugins.PluginInterface):
def generate_pool_scan(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
constraints: List[PoolConstraint],
) -> Generator[
Tuple[
@@ -489,17 +487,18 @@ class PoolScanner(plugins.PluginInterface):
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
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
"""
kernel = context.modules[kernel_module_name]
# 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
context, kernel_module_name, kernel.symbol_table_name, constraints
)
@classmethod
+24 -34
View File
@@ -23,7 +23,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Scans for processes present in a particular windows memory image."""
_required_framework_version = (2, 3, 1)
_version = (1, 1, 1)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
@@ -37,7 +37,10 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="info", component=info.Info, version=(1, 0, 0)
name="info", component=info.Info, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="poolscanner", component=poolscanner.PoolScanner, version=(2, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -141,8 +144,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def scan_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,
@@ -151,19 +153,20 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
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 processes found by scanning the `layer_name` layer for process pool signatures
"""
kernel = context.modules[kernel_module_name]
constraints = poolscanner.PoolScanner.builtin_constraints(
symbol_table, [b"Pro\xe3", b"Proc"]
kernel.symbol_table_name, [b"Pro\xe3", b"Proc"]
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, symbol_table, constraints
context, kernel_module_name, constraints
):
_constraint, mem_object, _header = result
if not filter_func(mem_object):
@@ -173,16 +176,14 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def virtual_process_from_physical(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
proc: interfaces.objects.ObjectInterface,
) -> Optional[interfaces.objects.ObjectInterface]:
"""Returns a virtual process from a physical addressed one
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 inside the kernel
proc: the process object with physical address
Returns:
@@ -190,16 +191,10 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""
version = cls.get_osversion(context, layer_name, symbol_table)
ntkrnlmp = context.modules[kernel_module_name]
version = cls.get_osversion(context, kernel_module_name)
# If it's WinXP->8.1 we have now a physical process address.
# We'll use the first thread to bounce back to the virtual process
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
tleoffset = ntkrnlmp.get_type("_ETHREAD").relative_child_offset(
"ThreadListEntry"
)
@@ -208,7 +203,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# If (and only if) we're dealing with 64-bit Windows 7 SP1
# then add the other commonly seen member offset to the list
bits = context.layers[layer_name].bits_per_register
bits = context.layers[ntkrnlmp.layer_name].bits_per_register
if version == (6, 1, 7601) and bits == 64:
offsets.append(tleoffset + 8)
@@ -225,7 +220,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# Sanity check the bounce.
# This compares the original offset with the new one (translated from virtual layer)
(_, _, ph_offset, _, _) = list(
context.layers[layer_name].mapping(
context.layers[ntkrnlmp.layer_name].mapping(
offset=virtual_process.vol.offset, length=0
)
)[0]
@@ -237,23 +232,20 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def get_osversion(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> Tuple[int, int, int]:
"""Returns the complete OS version (MAJ,MIN,BUILD)
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 tuple with (MAJ,MIN,BUILD)
"""
kuser = info.Info.get_kuser_structure(context, layer_name, symbol_table)
kuser = info.Info.get_kuser_structure(context, kernel_module_name)
nt_major_version = int(kuser.NtMajorVersion)
nt_minor_version = int(kuser.NtMinorVersion)
vers = info.Info.get_version_structure(context, layer_name, symbol_table)
vers = info.Info.get_version_structure(context, kernel_module_name)
build = vers.MinorVersion
return (nt_major_version, nt_minor_version, build)
@@ -268,8 +260,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
for proc in self.scan_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
filter_func=pslist.PsList.create_pid_filter(self.config.get("pid", None)),
):
file_output = "Disabled"
@@ -281,8 +272,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
try:
vproc = self.virtual_process_from_physical(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
proc,
)
except exceptions.PagedInvalidAddressException:
@@ -11,7 +11,6 @@ from volatility3.framework.renderers import TreeGrid, format_hints
from volatility3.framework.symbols.windows import extensions
from volatility3.plugins.windows import (
handles,
info,
pslist,
psscan,
thrdscan,
@@ -49,14 +48,11 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="info", component=info.Info, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="psscan", component=psscan.PsScan, version=(1, 0, 0)
name="psscan", component=psscan.PsScan, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="thrdscan", component=thrdscan.ThrdScan, version=(1, 0, 0)
@@ -114,10 +110,10 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
return self._proc_list_to_dict(tasks)
def _check_psscan(
self, layer_name: str, symbol_table: str
self,
) -> Dict[int, extensions.EPROCESS]:
res = psscan.PsScan.scan_processes(
context=self.context, layer_name=layer_name, symbol_table=symbol_table
context=self.context, kernel_module_name=self.config["kernel"]
)
return self._proc_list_to_dict(res)
@@ -144,20 +140,24 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
return self._proc_list_to_dict(ret)
def _check_csrss_handles(
self, tasks: Iterable[extensions.EPROCESS], layer_name: str, symbol_table: str
self, tasks: Iterable[extensions.EPROCESS]
) -> Dict[int, extensions.EPROCESS]:
ret: List[extensions.EPROCESS] = []
kernel = self.context.modules[self.config["kernel"]]
handles_plugin = handles.Handles(
context=self.context, config_path=self.config_path
)
type_map = handles_plugin.get_type_map(self.context, layer_name, symbol_table)
type_map = handles_plugin.get_type_map(
self.context, kernel.layer_name, kernel.symbol_table_name
)
cookie = handles_plugin.find_cookie(
context=self.context,
layer_name=layer_name,
symbol_table=symbol_table,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
)
for p in tasks:
@@ -179,8 +179,6 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
return self._proc_list_to_dict(ret)
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
kdbg_list_processes = list(
pslist.PsList.list_processes(
context=self.context, kernel_module_name=self.config["kernel"]
@@ -191,13 +189,9 @@ 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(
kernel.layer_name, kernel.symbol_table_name
)
processes["psscan"] = self._check_psscan()
processes["thrdscan"] = self._check_thrdscan()
processes["csrss"] = self._check_csrss_handles(
kdbg_list_processes, kernel.layer_name, kernel.symbol_table_name
)
processes["csrss"] = self._check_csrss_handles(kdbg_list_processes)
# Unique set of all offsets from all sources
offsets = set(chain(*(mapping.keys() for mapping in processes.values())))
@@ -26,10 +26,10 @@ class HiveScan(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
name="poolscanner", plugin=poolscanner.PoolScanner, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="bigpools", plugin=bigpools.BigPools, version=(1, 0, 0)
name="bigpools", plugin=bigpools.BigPools, version=(2, 0, 0)
),
]
@@ -62,8 +62,7 @@ class HiveScan(interfaces.plugins.PluginInterface):
for pool in bigpools.BigPools.list_big_pools(
context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=kernel_name,
tags=["CM10"],
):
cmhive = ntkrnlmp.object(
@@ -77,7 +76,7 @@ class HiveScan(interfaces.plugins.PluginInterface):
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, kernel.layer_name, kernel.symbol_table_name, constraints
context, kernel_name, constraints
):
_constraint, mem_object, _header = result
yield mem_object
@@ -20,7 +20,7 @@ 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 services as services_types
from volatility3.plugins.windows import poolscanner, pslist
from volatility3.plugins.windows import pslist
from volatility3.plugins.windows.registry import hivelist
vollog = logging.getLogger(__name__)
@@ -53,9 +53,6 @@ class SvcScan(interfaces.plugins.PluginInterface):
requirements.PluginRequirement(
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=(2, 0, 0)
),
@@ -17,6 +17,8 @@ class SymlinkScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfa
_required_framework_version = (2, 0, 0)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
return [
@@ -25,14 +27,16 @@ class SymlinkScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfa
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="poolscanner", component=poolscanner.PoolScanner, version=(2, 0, 0)
),
]
@classmethod
def scan_symlinks(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Scans for links using the poolscanner module and constraints.
@@ -45,22 +49,20 @@ class SymlinkScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfa
A list of symlink objects found by scanning memory for the Symlink pool signatures
"""
kernel = context.modules[kernel_module_name]
constraints = poolscanner.PoolScanner.builtin_constraints(
symbol_table, [b"Sym\xe2", b"Symb"]
kernel.symbol_table_name, [b"Sym\xe2", b"Symb"]
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, symbol_table, constraints
context, kernel_module_name, constraints
):
_constraint, mem_object, _header = result
yield mem_object
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
for link in self.scan_symlinks(
self.context, kernel.layer_name, kernel.symbol_table_name
):
for link in self.scan_symlinks(self.context, self.config["kernel"]):
try:
from_name = link.get_link_name()
except (ValueError, exceptions.InvalidAddressException):
@@ -34,7 +34,7 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
name="poolscanner", plugin=poolscanner.PoolScanner, version=(2, 0, 0)
),
]
@@ -54,16 +54,14 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
A list of _ETHREAD objects found by scanning memory for the "Thre" / "Thr\\xE5" pool signatures
"""
module = context.modules[module_name]
layer_name = module.layer_name
symbol_table = module.symbol_table_name
kernel = context.modules[module_name]
constraints = poolscanner.PoolScanner.builtin_constraints(
symbol_table, [b"Thr\xe5", b"Thre"]
kernel.symbol_table_name, [b"Thr\xe5", b"Thre"]
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, symbol_table, constraints
context, module_name, constraints
):
_constraint, mem_object, _header = result
yield mem_object
@@ -46,6 +46,12 @@ class WindowStations(interfaces.plugins.PluginInterface):
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="poolscanner", component=poolscanner.PoolScanner, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(3, 0, 0)
),
]
@staticmethod
@@ -152,8 +158,7 @@ class WindowStations(interfaces.plugins.PluginInterface):
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,
kernel_module_name=kernel_module_name,
object_symbol_table_name=gui_table_name,
constraints=constraints,
):