Skip to content

Commit 1b31357

Browse files
authored
Merge pull request #1766 from volatilityfoundation/thrdscan/filtering_and_tracebacks
Windows ThrdScan: Thread filtering, type-hints and tracebacks
2 parents e00f096 + b82458e commit 1b31357

File tree

4 files changed

+71
-61
lines changed

4 files changed

+71
-61
lines changed

volatility3/framework/constants/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# We use the SemVer 2.0.0 versioning scheme
22
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
33
VERSION_MINOR = 26 # Number of changes that only add to the interface
4-
VERSION_PATCH = 1 # Number of changes that do not change the interface
4+
VERSION_PATCH = 2 # Number of changes that do not change the interface
55
VERSION_SUFFIX = ""
66

77
PACKAGE_VERSION = (

volatility3/framework/constants/windows/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,5 @@
2828

2929
# CR3 register within structures describing initial processor state to be started
3030
PROCESSOR_START_BLOCK_CR3_OFFSET = 0xA0 # PROCESSOR_START_BLOCK->ProcessorState->SpecialRegisters->Cr3, ULONG64 8 bytes
31+
32+
MAX_PID = 0xFFFFFFFC

volatility3/framework/plugins/windows/thrdscan.py

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
##
22
## plugin for testing addition of threads scan support to poolscanner.py
33
##
4-
import logging
54
import datetime
6-
from typing import Callable, Iterable, Tuple, Optional, Dict
5+
import logging
6+
from typing import Callable, Dict, NamedTuple, Optional, Union, Tuple, Iterator
77

8-
from volatility3.framework import renderers, interfaces, exceptions
8+
from volatility3.framework import exceptions, interfaces, objects, renderers
99
from volatility3.framework.configuration import requirements
10+
from volatility3.framework.constants import windows as windows_constants
1011
from volatility3.framework.renderers import format_hints
11-
from volatility3.plugins.windows import poolscanner, pe_symbols
12+
from volatility3.framework.symbols.windows import extensions as win_extensions
1213
from volatility3.plugins import timeliner
14+
from volatility3.plugins.windows import pe_symbols, poolscanner
1315

1416
vollog = logging.getLogger(__name__)
1517

@@ -19,7 +21,18 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
1921

2022
# version 2.6.0 adds support for scanning for 'Ethread' structures by pool tags
2123
_required_framework_version = (2, 6, 0)
22-
_version = (2, 0, 0)
24+
_version = (2, 1, 0)
25+
26+
class ThreadInfo(NamedTuple):
27+
offset: int
28+
pid: objects.Pointer
29+
tid: objects.Pointer
30+
start_addr: objects.Pointer
31+
start_path: Optional[str]
32+
win32_start_addr: objects.Pointer
33+
win32_start_path: Optional[str]
34+
create_time: Union[datetime.datetime, interfaces.renderers.BaseAbsentValue]
35+
exit_time: Union[datetime.datetime, interfaces.renderers.BaseAbsentValue]
2336

2437
def __init__(self, *args, **kwargs):
2538
super().__init__(*args, **kwargs)
@@ -51,7 +64,7 @@ def scan_threads(
5164
cls,
5265
context: interfaces.context.ContextInterface,
5366
module_name: str,
54-
) -> Iterable[interfaces.objects.ObjectInterface]:
67+
) -> Iterator[win_extensions.ETHREAD]:
5568
"""Scans for threads using the poolscanner module and constraints.
5669
5770
Args:
@@ -77,19 +90,9 @@ def scan_threads(
7790
@classmethod
7891
def gather_thread_info(
7992
cls,
80-
ethread: interfaces.objects.ObjectInterface,
81-
vads_cache: Dict[int, pe_symbols.ranges_type] = None,
82-
) -> Tuple[
83-
int,
84-
int,
85-
int,
86-
int,
87-
Optional[str],
88-
int,
89-
Optional[str],
90-
Optional[datetime.datetime],
91-
Optional[datetime.datetime],
92-
]:
93+
ethread: win_extensions.ETHREAD,
94+
vads_cache: Optional[Dict[int, pe_symbols.ranges_type]] = None,
95+
) -> Optional[ThreadInfo]:
9396
try:
9497
thread_offset = ethread.vol.offset
9598
owner_proc_pid = ethread.Cid.UniqueProcess
@@ -110,44 +113,52 @@ def gather_thread_info(
110113
vollog.debug(f"Thread invalid address {ethread.vol.offset:#x}")
111114
return None
112115

113-
# don't look for VADs in kernel threads, just let them get reported with empty paths
116+
# Filter junk PIDs
114117
if (
115-
owner_proc_pid != 4
116-
and owner_proc.InheritedFromUniqueProcessId != 4
118+
ethread.Cid.UniqueProcess > windows_constants.MAX_PID
119+
or ethread.Cid.UniqueProcess == 0
120+
or ethread.Cid.UniqueProcess % 4 != 0
121+
):
122+
return None
123+
124+
# Get VAD mappings for valid non-system (PID 4) processes
125+
if (
126+
owner_proc
127+
and owner_proc.is_valid()
128+
and owner_proc.UniqueProcessId != 4
117129
and vads_cache is not None
118130
):
119131
vads = pe_symbols.PESymbols.get_vads_for_process_cache(
120132
vads_cache, owner_proc
121133
)
122-
if not vads or len(vads) < 5:
123-
vollog.debug(
124-
f"Not enough vads for process at {owner_proc.vol.offset:#x}. Skipping thread at {ethread.vol.offset:#x}"
125-
)
126-
return None
127134

128-
start_path = pe_symbols.PESymbols.filepath_for_address(
129-
vads, thread_start_addr
135+
start_path = (
136+
pe_symbols.PESymbols.filepath_for_address(vads, thread_start_addr)
137+
if vads
138+
else None
130139
)
131-
win32start_path = pe_symbols.PESymbols.filepath_for_address(
132-
vads, thread_win32start_addr
140+
win32start_path = (
141+
pe_symbols.PESymbols.filepath_for_address(vads, thread_win32start_addr)
142+
if vads
143+
else None
133144
)
134145
else:
135146
start_path = None
136147
win32start_path = None
137148

138-
return (
139-
format_hints.Hex(thread_offset),
149+
return cls.ThreadInfo(
150+
thread_offset,
140151
owner_proc_pid,
141152
thread_tid,
142-
format_hints.Hex(thread_start_addr),
153+
thread_start_addr,
143154
start_path,
144-
format_hints.Hex(thread_win32start_addr),
155+
thread_win32start_addr,
145156
win32start_path,
146157
thread_create_time,
147158
thread_exit_time,
148159
)
149160

150-
def _generator(self, filter_func: Callable):
161+
def _generator(self, filter_func: Callable) -> Iterator[Tuple[int, Tuple]]:
151162
kernel_name = self.config["kernel"]
152163

153164
vads_cache: Dict[int, pe_symbols.ranges_type] = {}
@@ -156,27 +167,16 @@ def _generator(self, filter_func: Callable):
156167
info = self.gather_thread_info(ethread, vads_cache)
157168

158169
if info:
159-
(
160-
offset,
161-
pid,
162-
tid,
163-
start_addr,
164-
start_path,
165-
win32start_addr,
166-
win32start_path,
167-
create_time,
168-
exit_time,
169-
) = info
170170
yield 0, (
171-
offset,
172-
pid,
173-
tid,
174-
start_addr,
175-
start_path or renderers.NotAvailableValue(),
176-
win32start_addr,
177-
win32start_path or renderers.NotAvailableValue(),
178-
create_time,
179-
exit_time,
171+
format_hints.Hex(info.offset),
172+
info.pid,
173+
info.tid,
174+
format_hints.Hex(info.start_addr),
175+
info.start_path or renderers.NotAvailableValue(),
176+
format_hints.Hex(info.win32_start_addr),
177+
info.win32_start_path or renderers.NotAvailableValue(),
178+
info.create_time,
179+
info.exit_time,
180180
)
181181

182182
def generate_timeline(self):

volatility3/framework/symbols/windows/extensions/__init__.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -568,16 +568,20 @@ def is_valid(self) -> bool:
568568
# passed all validations
569569
return True
570570

571-
def get_create_time(self):
571+
def get_create_time(
572+
self,
573+
) -> Union[datetime.datetime, interfaces.renderers.BaseAbsentValue]:
572574
# For Windows XPs
573575
if self.has_member("ThreadsProcess"):
574576
return conversion.wintime_to_datetime(self.CreateTime.QuadPart >> 3)
575577
return conversion.wintime_to_datetime(self.CreateTime.QuadPart)
576578

577-
def get_exit_time(self):
579+
def get_exit_time(
580+
self,
581+
) -> Union[datetime.datetime, interfaces.renderers.BaseAbsentValue]:
578582
return conversion.wintime_to_datetime(self.ExitTime.QuadPart)
579583

580-
def owning_process(self) -> interfaces.objects.ObjectInterface:
584+
def owning_process(self) -> "EPROCESS":
581585
"""Return the EPROCESS that owns this thread."""
582586

583587
# For Windows XPs
@@ -705,7 +709,11 @@ def is_valid(self) -> bool:
705709
return False
706710

707711
# NT pids are divisible by 4
708-
if self.UniqueProcessId % 4 != 0:
712+
if (
713+
self.UniqueProcessId % 4 != 0
714+
or self.UniqueProcessId == 0
715+
or self.UniqueProcessId > constants.windows.MAX_PID
716+
):
709717
return False
710718

711719
# check for all 0s besides the PCID entries

0 commit comments

Comments
 (0)