11##
22## plugin for testing addition of threads scan support to poolscanner.py
33##
4- import logging
54import 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
99from volatility3 .framework .configuration import requirements
10+ from volatility3 .framework .constants import windows as windows_constants
1011from 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
1213from volatility3 .plugins import timeliner
14+ from volatility3 .plugins .windows import pe_symbols , poolscanner
1315
1416vollog = 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 ):
0 commit comments