Merge pull request #1786 from volatilityfoundation/dgmcdona/handles_interface

Windows Handles: Refactor plugin
This commit is contained in:
ikelos
2025-05-01 17:49:58 +01:00
committed by GitHub
5 changed files with 84 additions and 60 deletions
@@ -48,7 +48,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
name="driverirp", component=driverirp.DriverIrp, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="handles", component=handles.Handles, version=(3, 0, 0)
name="handles", component=handles.Handles, version=(4, 0, 0)
),
]
@@ -5,18 +5,12 @@
import logging
import ntpath
import re
from typing import List, Tuple, Type, Optional, Generator
from typing import Generator, List, Optional, Tuple, Type
from volatility3.framework import (
interfaces,
exceptions,
constants,
renderers,
)
from volatility3.framework import constants, exceptions, interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.plugins.windows import handles
from volatility3.plugins.windows import pslist
from volatility3.plugins.windows import handles, pslist
vollog = logging.getLogger(__name__)
@@ -76,7 +70,7 @@ class DumpFiles(interfaces.plugins.PluginInterface):
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="handles", component=handles.Handles, version=(3, 0, 0)
name="handles", component=handles.Handles, version=(4, 0, 0)
),
]
@@ -231,14 +225,11 @@ class DumpFiles(interfaces.plugins.PluginInterface):
# private variables, so we need an instance (for now, anyway). We _could_ call Handles._generator()
# to do some of the other work that is duplicated here, but then we'd need to parse the TreeGrid
# results instead of just dealing with them as direct objects here.
handles_plugin = handles.Handles(
context=self.context, config_path=self._config_path
)
type_map = handles_plugin.get_type_map(
type_map = handles.Handles.get_type_map(
context=self.context,
kernel_module_name=self.config["kernel"],
)
cookie = handles_plugin.find_cookie(
cookie = handles.Handles.find_cookie(
context=self.context,
kernel_module_name=self.config["kernel"],
)
@@ -255,7 +246,11 @@ class DumpFiles(interfaces.plugins.PluginInterface):
)
continue
for entry in handles_plugin.handles(object_table):
for entry in handles.Handles.handles(
context=self.context,
kernel_module_name=self.config["kernel"],
handle_table=object_table,
):
try:
obj_type = entry.get_object_type(type_map, cookie)
if obj_type == "File":
@@ -3,7 +3,7 @@
#
import logging
from typing import Dict, List, Optional
from typing import Dict, Iterator, List, Optional
from volatility3.framework import constants, exceptions, interfaces, renderers, symbols
from volatility3.framework.configuration import requirements
@@ -18,13 +18,9 @@ class Handles(interfaces.plugins.PluginInterface):
"""Lists process open handles."""
_required_framework_version = (2, 0, 0)
_version = (3, 0, 0)
_version = (4, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._type_map = None
self._cookie = None
self._level_mask = 7
LEVEL_MASK = 7
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -54,18 +50,27 @@ class Handles(interfaces.plugins.PluginInterface):
),
]
def _get_item(self, handle_table_entry, handle_value):
"""Given a handle table entry (_HANDLE_TABLE_ENTRY) structure from a
@classmethod
def _get_item(
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
handle_table_entry: interfaces.objects.ObjectInterface,
handle_value: int,
) -> Optional[interfaces.objects.ObjectInterface]:
"""
Given a handle table entry (_HANDLE_TABLE_ENTRY) structure from a
process' handle table, determine where the corresponding object's
_OBJECT_HEADER can be found."""
_OBJECT_HEADER can be found, and construct and return the _OBJECT_HEADER
"""
kernel = self.context.modules[self.config["kernel"]]
kernel = context.modules[kernel_module_name]
virtual = kernel.layer_name
try:
# before windows 7
if not self.context.layers[virtual].is_valid(handle_table_entry.Object):
if not context.layers[virtual].is_valid(handle_table_entry.Object):
return None
fast_ref = handle_table_entry.Object.cast("_EX_FAST_REF")
@@ -78,7 +83,7 @@ class Handles(interfaces.plugins.PluginInterface):
except AttributeError:
# starting with windows 8
is_64bit = symbols.symbol_table_is_64bit(
context=self.context, symbol_table_name=kernel.symbol_table_name
context=context, symbol_table_name=kernel.symbol_table_name
)
if is_64bit:
@@ -104,7 +109,7 @@ class Handles(interfaces.plugins.PluginInterface):
offset = info_table & ~7
# print("LowValue: {0:#x} Magic: {1:#x} Offset: {2:#x}".format(handle_table_entry.InfoTable, magic, offset))
object_header = self.context.object(
object_header = context.object(
kernel.symbol_table_name + constants.BANG + "_OBJECT_HEADER",
virtual,
offset=offset,
@@ -205,11 +210,23 @@ class Handles(interfaces.plugins.PluginInterface):
offset=symbol_offset,
)
def _make_handle_array(self, offset, level, depth=0):
"""Parse a process' handle table and yield valid handle table entries,
going as deep into the table "levels" as necessary."""
@classmethod
def _make_handle_array(
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
offset: int,
level: int,
depth: int = 0,
) -> Iterator[interfaces.objects.ObjectInterface]:
"""
Parses a process' handle table by constructing an array of
`_HANDLE_TABLE_ENTRY` structures at the given offset, and yields valid
handle table entries, going as deep into the table "levels" as
necessary.
"""
kernel = self.context.modules[self.config["kernel"]]
kernel = context.modules[kernel_module_name]
if level > 0:
subtype = kernel.get_type("pointer")
@@ -218,7 +235,7 @@ class Handles(interfaces.plugins.PluginInterface):
subtype = kernel.get_type("_HANDLE_TABLE_ENTRY")
count = 0x1000 / subtype.size
if not self.context.layers[kernel.layer_name].is_valid(offset):
if not context.layers[kernel.layer_name].is_valid(offset):
return None
table = kernel.object(
@@ -229,7 +246,7 @@ class Handles(interfaces.plugins.PluginInterface):
absolute=True,
)
layer_object = self.context.layers[kernel.layer_name]
layer_object = context.layers[kernel.layer_name]
masked_offset = offset & layer_object.maximum_address
for i in range(len(table)):
@@ -243,11 +260,13 @@ 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[kernel.layer_name].is_valid(entry.vol.offset):
if not context.layers[kernel.layer_name].is_valid(entry.vol.offset):
continue
if level > 0:
yield from self._make_handle_array(entry, level - 1, depth)
yield from cls._make_handle_array(
context, kernel_module_name, entry, level - 1, depth
)
depth += 1
else:
handle_multiplier = 4
@@ -258,7 +277,7 @@ class Handles(interfaces.plugins.PluginInterface):
/ (subtype.size / handle_multiplier)
) + handle_level_base
item = self._get_item(entry, handle_value)
item = cls._get_item(context, kernel_module_name, entry, handle_value)
if item is None:
continue
@@ -272,10 +291,21 @@ class Handles(interfaces.plugins.PluginInterface):
except exceptions.InvalidAddressException:
continue
def handles(self, handle_table):
@classmethod
def handles(
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
handle_table: interfaces.objects.ObjectInterface,
) -> Iterator[interfaces.objects.ObjectInterface]:
"""
Takes a context, kernel module name, and handle table structure
(_HANDLE_TABLE), and yields _HANDLE_TABLE_ENTRY structures from the
handle table.
"""
try:
TableCode = handle_table.TableCode & ~self._level_mask
table_levels = handle_table.TableCode & self._level_mask
TableCode = handle_table.TableCode & ~cls.LEVEL_MASK
table_levels = handle_table.TableCode & cls.LEVEL_MASK
except exceptions.InvalidAddressException:
vollog.log(
constants.LOGLEVEL_VVV,
@@ -283,7 +313,9 @@ class Handles(interfaces.plugins.PluginInterface):
)
return None
yield from self._make_handle_array(TableCode, table_levels)
yield from cls._make_handle_array(
context, kernel_module_name, TableCode, table_levels
)
def _generator(self, procs):
type_map = self.get_type_map(
@@ -306,7 +338,9 @@ class Handles(interfaces.plugins.PluginInterface):
process_name = utility.array_to_string(proc.ImageFileName)
for entry in self.handles(object_table):
for entry in self.handles(
self.context, self.config["kernel"], object_table
):
try:
obj_type = entry.get_object_type(type_map, cookie)
if obj_type is None:
@@ -142,7 +142,7 @@ class PoolScanner(plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="handles", component=handles.Handles, version=(3, 0, 0)
name="handles", component=handles.Handles, version=(4, 0, 0)
),
requirements.VersionRequirement(
name="pool_header_scanner",
@@ -9,12 +9,7 @@ from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols.windows import extensions
from volatility3.plugins.windows import (
handles,
pslist,
psscan,
thrdscan,
)
from volatility3.plugins.windows import handles, pslist, psscan, thrdscan
vollog = logging.getLogger(__name__)
@@ -58,7 +53,7 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
name="thrdscan", component=thrdscan.ThrdScan, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="handles", component=handles.Handles, version=(3, 0, 0)
name="handles", component=handles.Handles, version=(4, 0, 0)
),
requirements.BooleanRequirement(
name="physical-offsets",
@@ -144,15 +139,11 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
) -> Dict[int, extensions.EPROCESS]:
ret: List[extensions.EPROCESS] = []
handles_plugin = handles.Handles(
context=self.context, config_path=self.config_path
)
type_map = handles_plugin.get_type_map(
type_map = handles.Handles.get_type_map(
context=self.context, kernel_module_name=self.config["kernel"]
)
cookie = handles_plugin.find_cookie(
cookie = handles.Handles.find_cookie(
context=self.context, kernel_module_name=self.config["kernel"]
)
@@ -164,7 +155,11 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
try:
ret += [
handle.Body.cast("_EPROCESS")
for handle in handles_plugin.handles(p.ObjectTable)
for handle in handles.Handles.handles(
context=self.context,
kernel_module_name=self.config["kernel"],
handle_table=p.ObjectTable,
)
if handle.get_object_type(type_map, cookie) == "Process"
]
except exceptions.InvalidAddressException: