Skip to content

Commit d56cd83

Browse files
authored
Merge pull request #1225 from volatilityfoundation/orphan_threads_v2
Orphan threads v2
2 parents 6803bf3 + c4cc50e commit d56cd83

File tree

3 files changed

+101
-10
lines changed

3 files changed

+101
-10
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
2+
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
3+
#
4+
5+
import logging
6+
from typing import List, Generator
7+
8+
from volatility3.framework import interfaces, symbols
9+
from volatility3.framework.configuration import requirements
10+
from volatility3.plugins.windows import thrdscan, ssdt
11+
12+
vollog = logging.getLogger(__name__)
13+
14+
15+
class Threads(thrdscan.ThrdScan):
16+
"""Lists process threads"""
17+
18+
_required_framework_version = (2, 4, 0)
19+
_version = (1, 0, 0)
20+
21+
def __init__(self, *args, **kwargs):
22+
super().__init__(*args, **kwargs)
23+
self.implementation = self.list_orphan_kernel_threads
24+
25+
@classmethod
26+
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
27+
# Since we're calling the plugin, make sure we have the plugin's requirements
28+
return [
29+
requirements.ModuleRequirement(
30+
name="kernel",
31+
description="Windows kernel",
32+
architectures=["Intel32", "Intel64"],
33+
),
34+
requirements.PluginRequirement(
35+
name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 1, 0)
36+
),
37+
requirements.PluginRequirement(
38+
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
39+
),
40+
]
41+
42+
@classmethod
43+
def list_orphan_kernel_threads(
44+
cls,
45+
context: interfaces.context.ContextInterface,
46+
module_name: str,
47+
) -> Generator[interfaces.objects.ObjectInterface, None, None]:
48+
"""Yields thread objects of kernel threads that do not map to a module
49+
50+
Args:
51+
cls
52+
context: the context to operate upon
53+
module_name: name of the module to use for scanning
54+
Returns:
55+
A generator of thread objects of orphaned threads
56+
"""
57+
module = context.modules[module_name]
58+
layer_name = module.layer_name
59+
symbol_table = module.symbol_table_name
60+
61+
collection = ssdt.SSDT.build_module_collection(
62+
context, layer_name, symbol_table
63+
)
64+
65+
# FIXME - use a proper constant once established
66+
# used to filter out smeared pointers
67+
if symbols.symbol_table_is_64bit(context, symbol_table):
68+
kernel_start = 0xFFFFF80000000000
69+
else:
70+
kernel_start = 0x80000000
71+
72+
for thread in thrdscan.ThrdScan.scan_threads(context, module_name):
73+
# we don't want smeared or terminated threads
74+
try:
75+
proc = thread.owning_process()
76+
except AttributeError:
77+
continue
78+
79+
# we only care about kernel threads, 4 = System
80+
# previous methods for determining if a thread was a kernel thread
81+
# such as bit fields and flags are not stable in Win10+
82+
# so we check if the thread is from the kernel itself or one its child
83+
# kernel processes (MemCompression, Regsitry, ...)
84+
if proc.UniqueProcessId != 4 and proc.InheritedFromUniqueProcessId != 4:
85+
continue
86+
87+
if thread.StartAddress < kernel_start:
88+
continue
89+
90+
module_symbols = list(
91+
collection.get_module_symbols_by_absolute_location(thread.StartAddress)
92+
)
93+
94+
# alert on threads that do not map to a module
95+
if not module_symbols:
96+
yield thread

volatility3/framework/plugins/windows/thrdscan.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
2222
_version = (1, 1, 0)
2323

2424
def __init__(self, *args, **kwargs):
25-
self.implementation = self.scan_threads
2625
super().__init__(*args, **kwargs)
26+
self.implementation = self.scan_threads
2727

2828
@classmethod
2929
def get_requirements(cls):
@@ -48,8 +48,7 @@ def scan_threads(
4848
4949
Args:
5050
context: The context to retrieve required elements (layers, symbol tables) from
51-
layer_name: The name of the layer on which to operate
52-
symbol_table: The name of the table containing the kernel symbols
51+
module_name: Name of the module to use for scanning
5352
5453
Returns:
5554
A list of _ETHREAD objects found by scanning memory for the "Thre" / "Thr\\xE5" pool signatures

volatility3/framework/plugins/windows/threads.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ class Threads(thrdscan.ThrdScan):
1919
_version = (1, 0, 0)
2020

2121
def __init__(self, *args, **kwargs):
22-
self.implementation = self.list_process_threads
2322
super().__init__(*args, **kwargs)
23+
self.implementation = self.list_process_threads
2424

2525
@classmethod
2626
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -50,7 +50,6 @@ def list_threads(
5050
5151
Args:
5252
proc: _EPROCESS object from which to list the VADs
53-
filter_func: Function to take a virtual address descriptor value and return True if it should be filtered out
5453
5554
Returns:
5655
A list of threads based on the process and filtered based on the filter function
@@ -64,22 +63,19 @@ def list_threads(
6463
seen.add(thread.vol.offset)
6564
yield thread
6665

67-
@classmethod
68-
def filter_func(cls, config: interfaces.configuration.HierarchicalDict) -> Callable:
69-
return pslist.PsList.create_pid_filter(config.get("pid", None))
70-
7166
@classmethod
7267
def list_process_threads(
7368
cls,
7469
context: interfaces.context.ContextInterface,
7570
module_name: str,
76-
filter_func: Callable,
7771
) -> Iterable[interfaces.objects.ObjectInterface]:
7872
"""Runs through all processes and lists threads for each process"""
7973
module = context.modules[module_name]
8074
layer_name = module.layer_name
8175
symbol_table_name = module.symbol_table_name
8276

77+
filter_func = pslist.PsList.create_pid_filter(context.config.get("pid", None))
78+
8379
for proc in pslist.PsList.list_processes(
8480
context=context,
8581
layer_name=layer_name,

0 commit comments

Comments
 (0)