Files
volatility3/volatility/framework/plugins/windows/poolscanner.py
T
Mike Auty 0a655d76a8 Core: Fix LGTM recommendations
This is mostly unused imports and unused variables.
2020-11-04 22:41:39 +00:00

402 lines
19 KiB
Python

# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import enum
import logging
from typing import Dict, Generator, List, Optional, Tuple
from volatility.framework import constants, interfaces, renderers, exceptions, symbols
from volatility.framework.configuration import requirements
from volatility.framework.interfaces import plugins, configuration
from volatility.framework.layers import scanners
from volatility.framework.renderers import format_hints
from volatility.framework.symbols import intermed
from volatility.framework.symbols.windows import extensions, versions
from volatility.plugins.windows import handles
vollog = logging.getLogger(__name__)
# TODO: When python3.5 is no longer supported, make this enum.IntFlag
# Revisit the page_type signature of PoolConstraint once using enum.IntFlag
class PoolType(enum.IntEnum):
"""Class to maintain the different possible PoolTypes The values must be
integer powers of 2."""
PAGED = 1
NONPAGED = 2
FREE = 4
class PoolConstraint:
"""Class to maintain tag/size/index/type information about Pool header
tags."""
def __init__(self,
tag: bytes,
type_name: str,
object_type: Optional[str] = None,
page_type: Optional[int] = None,
size: Optional[Tuple[Optional[int], Optional[int]]] = None,
index: Optional[Tuple[Optional[int], Optional[int]]] = None,
alignment: Optional[int] = 1,
skip_type_test: bool = False) -> None:
self.tag = tag
self.type_name = type_name
self.object_type = object_type
self.page_type = page_type
self.size = size
self.index = index
self.alignment = alignment
self.skip_type_test = skip_type_test
class PoolHeaderScanner(interfaces.layers.ScannerInterface):
def __init__(self, module: interfaces.context.ModuleInterface, constraint_lookup: Dict[bytes, PoolConstraint],
alignment: int):
super().__init__()
self._module = module
self._constraint_lookup = constraint_lookup
self._alignment = alignment
header_type = self._module.get_type('_POOL_HEADER')
self._header_offset = header_type.relative_child_offset('PoolTag')
self._subscanner = scanners.MultiStringScanner([c for c in constraint_lookup.keys()])
def __call__(self, data: bytes, data_offset: int):
for offset, pattern in self._subscanner(data, data_offset):
header = self._module.object(object_type = "_POOL_HEADER",
offset = offset - self._header_offset,
absolute = True)
constraint = self._constraint_lookup[pattern]
try:
# Size check
if constraint.size is not None:
if constraint.size[0]:
if (self._alignment * header.BlockSize) < constraint.size[0]:
continue
if constraint.size[1]:
if (self._alignment * header.BlockSize) > constraint.size[1]:
continue
# Type check
if constraint.page_type is not None:
checks_pass = False
if (constraint.page_type & PoolType.FREE) and header.is_free_pool():
checks_pass = True
elif (constraint.page_type & PoolType.NONPAGED) and header.is_nonpaged_pool():
checks_pass = True
elif (constraint.page_type & PoolType.PAGED) and header.is_paged_pool():
checks_pass = True
if not checks_pass:
continue
if constraint.index is not None:
if constraint.index[0]:
if header.PoolIndex < constraint.index[0]:
continue
if constraint.index[1]:
if header.PoolIndex > constraint.index[1]:
continue
except exceptions.InvalidAddressException:
# The tested object's header doesn't point to valid addresses, ignore it
continue
# We found one that passed!
yield (constraint, header)
class PoolScanner(plugins.PluginInterface):
"""A generic pool scanner plugin."""
_version = (1, 0, 0)
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"),
requirements.PluginRequirement(name = 'handles', plugin = handles.Handles, version = (1, 0, 0)),
]
def _generator(self):
symbol_table = self.config["nt_symbols"]
constraints = self.builtin_constraints(symbol_table)
for constraint, mem_object, header in self.generate_pool_scan(self.context, self.config["primary"],
symbol_table, constraints):
# generate some type-specific info for sanity checking
if constraint.object_type == "Process":
name = mem_object.ImageFileName.cast("string",
max_length = mem_object.ImageFileName.vol.count,
errors = "replace")
elif constraint.object_type == "File":
try:
name = mem_object.FileName.String
except exceptions.InvalidAddressException:
vollog.log(constants.LOGLEVEL_VVV, "Skipping file at {0:#x}".format(mem_object.vol.offset))
continue
else:
name = renderers.NotApplicableValue()
yield (0, (constraint.type_name, format_hints.Hex(header.vol.offset), header.vol.layer_name, name))
@staticmethod
def builtin_constraints(symbol_table: str, tags_filter: List[bytes] = None) -> List[PoolConstraint]:
"""Get built-in PoolConstraints given a list of pool tags.
The tags_filter is a list of pool tags, and the associated
PoolConstraints are returned. If tags_filter is empty or
not supplied, then all builtin constraints are returned.
Args:
symbol_table: The name of the symbol table to prepend to the types used
tags_filter: List of tags to return or None to return all
Returns:
A list of well-known constructed PoolConstraints that match the provided tags
"""
builtins = [
# atom tables
PoolConstraint(b'AtmT',
type_name = symbol_table + constants.BANG + "_RTL_ATOM_TABLE",
size = (200, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# processes on windows before windows 8
PoolConstraint(b'Pro\xe3',
type_name = symbol_table + constants.BANG + "_EPROCESS",
object_type = "Process",
size = (600, None),
skip_type_test = True,
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# processes on windows starting with windows 8
PoolConstraint(b'Proc',
type_name = symbol_table + constants.BANG + "_EPROCESS",
object_type = "Process",
size = (600, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# files on windows before windows 8
PoolConstraint(b'Fil\xe5',
type_name = symbol_table + constants.BANG + "_FILE_OBJECT",
object_type = "File",
size = (150, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# files on windows starting with windows 8
PoolConstraint(b'File',
type_name = symbol_table + constants.BANG + "_FILE_OBJECT",
object_type = "File",
size = (150, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# mutants on windows before windows 8
PoolConstraint(b'Mut\xe1',
type_name = symbol_table + constants.BANG + "_KMUTANT",
object_type = "Mutant",
size = (64, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# mutants on windows starting with windows 8
PoolConstraint(b'Muta',
type_name = symbol_table + constants.BANG + "_KMUTANT",
object_type = "Mutant",
size = (64, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# drivers on windows before windows 8
PoolConstraint(b'Dri\xf6',
type_name = symbol_table + constants.BANG + "_DRIVER_OBJECT",
object_type = "Driver",
size = (248, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# drivers on windows starting with windows 8
PoolConstraint(b'Driv',
type_name = symbol_table + constants.BANG + "_DRIVER_OBJECT",
object_type = "Driver",
size = (248, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# kernel modules
PoolConstraint(b'MmLd',
type_name = symbol_table + constants.BANG + "_LDR_DATA_TABLE_ENTRY",
size = (76, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# symlinks on windows before windows 8
PoolConstraint(b'Sym\xe2',
type_name = symbol_table + constants.BANG + "_OBJECT_SYMBOLIC_LINK",
object_type = "SymbolicLink",
size = (72, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# symlinks on windows starting with windows 8
PoolConstraint(b'Symb',
type_name = symbol_table + constants.BANG + "_OBJECT_SYMBOLIC_LINK",
object_type = "SymbolicLink",
size = (72, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# registry hives
PoolConstraint(b'CM10',
type_name = symbol_table + constants.BANG + "_CMHIVE",
size = (800, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE,
skip_type_test = True),
]
if not tags_filter:
return builtins
return [constraint for constraint in builtins if constraint.tag in tags_filter]
@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]:
"""
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
"""
# get the object type map
type_map = handles.Handles.get_type_map(context = context, layer_name = layer_name, symbol_table = symbol_table)
cookie = handles.Handles.find_cookie(context = context, layer_name = layer_name, symbol_table = 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)
# start off with the primary virtual layer
scan_layer = 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):
alignment = 0x10
else:
alignment = 8
for constraint, header in cls.pool_scan(context, scan_layer, symbol_table, constraints, alignment = alignment):
mem_object = header.get_object(type_name = constraint.type_name,
use_top_down = is_windows_8_or_later,
executive = constraint.object_type is not None,
native_layer_name = 'primary',
kernel_symbol_table = symbol_table)
if mem_object is None:
vollog.log(constants.LOGLEVEL_VVV, "Cannot create an instance of {}".format(constraint.type_name))
continue
if constraint.object_type is not None and not constraint.skip_type_test:
try:
if mem_object.get_object_header().get_object_type(type_map, cookie) != constraint.object_type:
continue
except exceptions.InvalidAddressException:
vollog.log(constants.LOGLEVEL_VVV,
"Cannot test instance type check for {}".format(constraint.type_name))
continue
yield constraint, mem_object, header
@classmethod
def pool_scan(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
pool_constraints: List[PoolConstraint],
alignment: int = 8,
progress_callback: Optional[constants.ProgressCallback] = None) \
-> Generator[Tuple[PoolConstraint, interfaces.objects.ObjectInterface], None, None]:
"""Returns the _POOL_HEADER object (based on the symbol_table template)
after scanning through layer_name returning all headers that match any
of the constraints provided. Only one constraint can be provided per
tag.
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
pool_constraints: List of pool constraints used to limit the scan results
alignment: An optional value that all pool headers will be aligned to
progress_callback: An optional function to provide progress feedback whilst scanning
Returns:
An Iterable of pool constraints and the pool headers associated with them
"""
# Setup the pattern
constraint_lookup = {} # type: Dict[bytes, PoolConstraint]
for constraint in pool_constraints:
if constraint.tag in constraint_lookup:
raise ValueError("Constraint tag is used for more than one constraint: {}".format(repr(constraint.tag)))
constraint_lookup[constraint.tag] = constraint
pool_header_table_name = cls.get_pool_header_table(context, symbol_table)
module = context.module(pool_header_table_name, layer_name, offset = 0)
# Run the scan locating the offsets of a particular tag
layer = context.layers[layer_name]
scanner = PoolHeaderScanner(module, constraint_lookup, alignment)
yield from layer.scan(context, scanner, progress_callback)
@classmethod
def get_pool_header_table(cls, context: interfaces.context.ContextInterface, symbol_table: str) -> str:
"""Returns the appropriate symbol_table containing a _POOL_HEADER type, even if the original symbol table
doesn't contain one.
Args:
context: The context that the symbol tables does (or will) reside in
symbol_table: The expected symbol_table to contain the _POOL_HEADER type
"""
# Setup the pool header and offset differential
try:
context.symbol_space.get_type(symbol_table + constants.BANG + "_POOL_HEADER")
table_name = symbol_table
except exceptions.SymbolError:
# We have to manually load a symbol table
if symbols.symbol_table_is_64bit(context, symbol_table):
is_win_7 = versions.is_windows_7(context, symbol_table)
if is_win_7:
pool_header_json_filename = "poolheader-x64-win7"
else:
pool_header_json_filename = "poolheader-x64"
else:
pool_header_json_filename = "poolheader-x86"
# set the class_type to match the normal WindowsKernelIntermedSymbols
is_vista_or_later = versions.is_vista_or_later(context, symbol_table)
if is_vista_or_later:
class_type = extensions.pool.POOL_HEADER_VISTA
else:
class_type = extensions.pool.POOL_HEADER
table_name = intermed.IntermediateSymbolTable.create(context = context,
config_path = configuration.path_join(
context.symbol_space[symbol_table].config_path,
"poolheader"),
sub_path = "windows",
filename = pool_header_json_filename,
table_mapping = {'nt_symbols': symbol_table},
class_types = {'_POOL_HEADER': class_type})
return table_name
def run(self) -> renderers.TreeGrid:
return renderers.TreeGrid([("Tag", str), ("Offset", format_hints.Hex), ("Layer", str), ("Name", str)],
self._generator())