Skip to content

Commit 51f0a30

Browse files
authored
Merge pull request #1786 from volatilityfoundation/dgmcdona/handles_interface
Windows Handles: Refactor plugin
2 parents e22e82d + bceae67 commit 51f0a30

File tree

5 files changed

+85
-61
lines changed

5 files changed

+85
-61
lines changed

volatility3/framework/plugins/windows/callbacks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
4848
name="driverirp", component=driverirp.DriverIrp, version=(1, 0, 0)
4949
),
5050
requirements.VersionRequirement(
51-
name="handles", component=handles.Handles, version=(3, 0, 0)
51+
name="handles", component=handles.Handles, version=(4, 0, 0)
5252
),
5353
]
5454

volatility3/framework/plugins/windows/dumpfiles.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,12 @@
55
import logging
66
import ntpath
77
import re
8-
from typing import List, Tuple, Type, Optional, Generator
9-
10-
from volatility3.framework import (
11-
interfaces,
12-
exceptions,
13-
constants,
14-
renderers,
15-
)
8+
from typing import Generator, List, Optional, Tuple, Type
9+
10+
from volatility3.framework import constants, exceptions, interfaces, renderers
1611
from volatility3.framework.configuration import requirements
1712
from volatility3.framework.renderers import format_hints
18-
from volatility3.plugins.windows import handles
19-
from volatility3.plugins.windows import pslist
13+
from volatility3.plugins.windows import handles, pslist
2014

2115
vollog = logging.getLogger(__name__)
2216

@@ -76,7 +70,7 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
7670
name="pslist", component=pslist.PsList, version=(3, 0, 0)
7771
),
7872
requirements.VersionRequirement(
79-
name="handles", component=handles.Handles, version=(3, 0, 0)
73+
name="handles", component=handles.Handles, version=(4, 0, 0)
8074
),
8175
]
8276

@@ -231,14 +225,11 @@ def _generator(self, procs: List, offsets: List):
231225
# private variables, so we need an instance (for now, anyway). We _could_ call Handles._generator()
232226
# to do some of the other work that is duplicated here, but then we'd need to parse the TreeGrid
233227
# results instead of just dealing with them as direct objects here.
234-
handles_plugin = handles.Handles(
235-
context=self.context, config_path=self._config_path
236-
)
237-
type_map = handles_plugin.get_type_map(
228+
type_map = handles.Handles.get_type_map(
238229
context=self.context,
239230
kernel_module_name=self.config["kernel"],
240231
)
241-
cookie = handles_plugin.find_cookie(
232+
cookie = handles.Handles.find_cookie(
242233
context=self.context,
243234
kernel_module_name=self.config["kernel"],
244235
)
@@ -255,7 +246,11 @@ def _generator(self, procs: List, offsets: List):
255246
)
256247
continue
257248

258-
for entry in handles_plugin.handles(object_table):
249+
for entry in handles.Handles.handles(
250+
context=self.context,
251+
kernel_module_name=self.config["kernel"],
252+
handle_table=object_table,
253+
):
259254
try:
260255
obj_type = entry.get_object_type(type_map, cookie)
261256
if obj_type == "File":

volatility3/framework/plugins/windows/handles.py

Lines changed: 62 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44

55
import logging
6-
from typing import Dict, List, Optional
6+
from typing import Dict, Iterator, List, Optional
77

88
from volatility3.framework import constants, exceptions, interfaces, renderers, symbols
99
from volatility3.framework.configuration import requirements
@@ -18,13 +18,9 @@ class Handles(interfaces.plugins.PluginInterface):
1818
"""Lists process open handles."""
1919

2020
_required_framework_version = (2, 0, 0)
21-
_version = (3, 0, 0)
21+
_version = (4, 0, 0)
2222

23-
def __init__(self, *args, **kwargs):
24-
super().__init__(*args, **kwargs)
25-
self._type_map = None
26-
self._cookie = None
27-
self._level_mask = 7
23+
LEVEL_MASK = 7
2824

2925
@classmethod
3026
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -54,18 +50,27 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
5450
),
5551
]
5652

57-
def _get_item(self, handle_table_entry, handle_value):
58-
"""Given a handle table entry (_HANDLE_TABLE_ENTRY) structure from a
53+
@classmethod
54+
def _get_item(
55+
cls,
56+
context: interfaces.context.ContextInterface,
57+
kernel_module_name: str,
58+
handle_table_entry: interfaces.objects.ObjectInterface,
59+
handle_value: int,
60+
) -> Optional[interfaces.objects.ObjectInterface]:
61+
"""
62+
Given a handle table entry (_HANDLE_TABLE_ENTRY) structure from a
5963
process' handle table, determine where the corresponding object's
60-
_OBJECT_HEADER can be found."""
64+
_OBJECT_HEADER can be found, and construct and return the _OBJECT_HEADER
65+
"""
6166

62-
kernel = self.context.modules[self.config["kernel"]]
67+
kernel = context.modules[kernel_module_name]
6368

6469
virtual = kernel.layer_name
6570

6671
try:
6772
# before windows 7
68-
if not self.context.layers[virtual].is_valid(handle_table_entry.Object):
73+
if not context.layers[virtual].is_valid(handle_table_entry.Object):
6974
return None
7075
fast_ref = handle_table_entry.Object.cast("_EX_FAST_REF")
7176

@@ -78,7 +83,7 @@ def _get_item(self, handle_table_entry, handle_value):
7883
except AttributeError:
7984
# starting with windows 8
8085
is_64bit = symbols.symbol_table_is_64bit(
81-
context=self.context, symbol_table_name=kernel.symbol_table_name
86+
context=context, symbol_table_name=kernel.symbol_table_name
8287
)
8388

8489
if is_64bit:
@@ -104,7 +109,7 @@ def _get_item(self, handle_table_entry, handle_value):
104109
offset = info_table & ~7
105110

106111
# print("LowValue: {0:#x} Magic: {1:#x} Offset: {2:#x}".format(handle_table_entry.InfoTable, magic, offset))
107-
object_header = self.context.object(
112+
object_header = context.object(
108113
kernel.symbol_table_name + constants.BANG + "_OBJECT_HEADER",
109114
virtual,
110115
offset=offset,
@@ -205,11 +210,23 @@ def find_cookie(
205210
offset=symbol_offset,
206211
)
207212

208-
def _make_handle_array(self, offset, level, depth=0):
209-
"""Parse a process' handle table and yield valid handle table entries,
210-
going as deep into the table "levels" as necessary."""
213+
@classmethod
214+
def _make_handle_array(
215+
cls,
216+
context: interfaces.context.ContextInterface,
217+
kernel_module_name: str,
218+
offset: int,
219+
level: int,
220+
depth: int = 0,
221+
) -> Iterator[interfaces.objects.ObjectInterface]:
222+
"""
223+
Parses a process' handle table by constructing an array of
224+
`_HANDLE_TABLE_ENTRY` structures at the given offset, and yields valid
225+
handle table entries, going as deep into the table "levels" as
226+
necessary.
227+
"""
211228

212-
kernel = self.context.modules[self.config["kernel"]]
229+
kernel = context.modules[kernel_module_name]
213230

214231
if level > 0:
215232
subtype = kernel.get_type("pointer")
@@ -218,7 +235,7 @@ def _make_handle_array(self, offset, level, depth=0):
218235
subtype = kernel.get_type("_HANDLE_TABLE_ENTRY")
219236
count = 0x1000 / subtype.size
220237

221-
if not self.context.layers[kernel.layer_name].is_valid(offset):
238+
if not context.layers[kernel.layer_name].is_valid(offset):
222239
return None
223240

224241
table = kernel.object(
@@ -229,7 +246,7 @@ def _make_handle_array(self, offset, level, depth=0):
229246
absolute=True,
230247
)
231248

232-
layer_object = self.context.layers[kernel.layer_name]
249+
layer_object = context.layers[kernel.layer_name]
233250
masked_offset = offset & layer_object.maximum_address
234251

235252
for i in range(len(table)):
@@ -243,11 +260,13 @@ def _make_handle_array(self, offset, level, depth=0):
243260
# The code above this calls `is_valid` on the `offset`
244261
# It is sent but then does not validate `entry` before
245262
# sending it to `_get_item`
246-
if not self.context.layers[kernel.layer_name].is_valid(entry.vol.offset):
263+
if not context.layers[kernel.layer_name].is_valid(entry.vol.offset):
247264
continue
248265

249266
if level > 0:
250-
yield from self._make_handle_array(entry, level - 1, depth)
267+
yield from cls._make_handle_array(
268+
context, kernel_module_name, entry, level - 1, depth
269+
)
251270
depth += 1
252271
else:
253272
handle_multiplier = 4
@@ -258,7 +277,7 @@ def _make_handle_array(self, offset, level, depth=0):
258277
/ (subtype.size / handle_multiplier)
259278
) + handle_level_base
260279

261-
item = self._get_item(entry, handle_value)
280+
item = cls._get_item(context, kernel_module_name, entry, handle_value)
262281

263282
if item is None:
264283
continue
@@ -272,18 +291,31 @@ def _make_handle_array(self, offset, level, depth=0):
272291
except exceptions.InvalidAddressException:
273292
continue
274293

275-
def handles(self, handle_table):
294+
@classmethod
295+
def handles(
296+
cls,
297+
context: interfaces.context.ContextInterface,
298+
kernel_module_name: str,
299+
handle_table: interfaces.objects.ObjectInterface,
300+
) -> Iterator[interfaces.objects.ObjectInterface]:
301+
"""
302+
Takes a context, kernel module name, and handle table structure
303+
(_HANDLE_TABLE), and yields _HANDLE_TABLE_ENTRY structures from the
304+
handle table.
305+
"""
276306
try:
277-
TableCode = handle_table.TableCode & ~self._level_mask
278-
table_levels = handle_table.TableCode & self._level_mask
307+
TableCode = handle_table.TableCode & ~cls.LEVEL_MASK
308+
table_levels = handle_table.TableCode & cls.LEVEL_MASK
279309
except exceptions.InvalidAddressException:
280310
vollog.log(
281311
constants.LOGLEVEL_VVV,
282312
"Handle table parsing was aborted due to an invalid address exception",
283313
)
284314
return None
285315

286-
yield from self._make_handle_array(TableCode, table_levels)
316+
yield from cls._make_handle_array(
317+
context, kernel_module_name, TableCode, table_levels
318+
)
287319

288320
def _generator(self, procs):
289321
type_map = self.get_type_map(
@@ -306,7 +338,9 @@ def _generator(self, procs):
306338

307339
process_name = utility.array_to_string(proc.ImageFileName)
308340

309-
for entry in self.handles(object_table):
341+
for entry in self.handles(
342+
self.context, self.config["kernel"], object_table
343+
):
310344
try:
311345
obj_type = entry.get_object_type(type_map, cookie)
312346
if obj_type is None:

volatility3/framework/plugins/windows/poolscanner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
142142
architectures=["Intel32", "Intel64"],
143143
),
144144
requirements.VersionRequirement(
145-
name="handles", component=handles.Handles, version=(3, 0, 0)
145+
name="handles", component=handles.Handles, version=(4, 0, 0)
146146
),
147147
requirements.VersionRequirement(
148148
name="pool_header_scanner",

volatility3/framework/plugins/windows/psxview.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,7 @@
99
from volatility3.framework.interfaces import plugins
1010
from volatility3.framework.renderers import format_hints
1111
from volatility3.framework.symbols.windows import extensions
12-
from volatility3.plugins.windows import (
13-
handles,
14-
pslist,
15-
psscan,
16-
thrdscan,
17-
)
12+
from volatility3.plugins.windows import handles, pslist, psscan, thrdscan
1813

1914
vollog = logging.getLogger(__name__)
2015

@@ -58,7 +53,7 @@ def get_requirements(cls):
5853
name="thrdscan", component=thrdscan.ThrdScan, version=(2, 0, 0)
5954
),
6055
requirements.VersionRequirement(
61-
name="handles", component=handles.Handles, version=(3, 0, 0)
56+
name="handles", component=handles.Handles, version=(4, 0, 0)
6257
),
6358
requirements.BooleanRequirement(
6459
name="physical-offsets",
@@ -144,15 +139,11 @@ def _check_csrss_handles(
144139
) -> Dict[int, extensions.EPROCESS]:
145140
ret: List[extensions.EPROCESS] = []
146141

147-
handles_plugin = handles.Handles(
148-
context=self.context, config_path=self.config_path
149-
)
150-
151-
type_map = handles_plugin.get_type_map(
142+
type_map = handles.Handles.get_type_map(
152143
context=self.context, kernel_module_name=self.config["kernel"]
153144
)
154145

155-
cookie = handles_plugin.find_cookie(
146+
cookie = handles.Handles.find_cookie(
156147
context=self.context, kernel_module_name=self.config["kernel"]
157148
)
158149

@@ -164,7 +155,11 @@ def _check_csrss_handles(
164155
try:
165156
ret += [
166157
handle.Body.cast("_EPROCESS")
167-
for handle in handles_plugin.handles(p.ObjectTable)
158+
for handle in handles.Handles.handles(
159+
context=self.context,
160+
kernel_module_name=self.config["kernel"],
161+
handle_table=p.ObjectTable,
162+
)
168163
if handle.get_object_type(type_map, cookie) == "Process"
169164
]
170165
except exceptions.InvalidAddressException:

0 commit comments

Comments
 (0)