diff --git a/volatility3/framework/constants/_version.py b/volatility3/framework/constants/_version.py index a299f15a2..07b9e45ec 100644 --- a/volatility3/framework/constants/_version.py +++ b/volatility3/framework/constants/_version.py @@ -1,7 +1,7 @@ # We use the SemVer 2.0.0 versioning scheme VERSION_MAJOR = 2 # Number of releases of the library with a breaking change VERSION_MINOR = 26 # Number of changes that only add to the interface -VERSION_PATCH = 1 # Number of changes that do not change the interface +VERSION_PATCH = 2 # Number of changes that do not change the interface VERSION_SUFFIX = "" PACKAGE_VERSION = ( diff --git a/volatility3/framework/constants/windows/__init__.py b/volatility3/framework/constants/windows/__init__.py index 6f37acd2d..b08713cc9 100644 --- a/volatility3/framework/constants/windows/__init__.py +++ b/volatility3/framework/constants/windows/__init__.py @@ -28,3 +28,5 @@ PROCESSOR_START_BLOCK_LM_TARGET_OFFSET = ( # CR3 register within structures describing initial processor state to be started PROCESSOR_START_BLOCK_CR3_OFFSET = 0xA0 # PROCESSOR_START_BLOCK->ProcessorState->SpecialRegisters->Cr3, ULONG64 8 bytes + +MAX_PID = 0xFFFFFFFC diff --git a/volatility3/framework/plugins/windows/thrdscan.py b/volatility3/framework/plugins/windows/thrdscan.py index 0ac3d0c33..d5a1a0b07 100644 --- a/volatility3/framework/plugins/windows/thrdscan.py +++ b/volatility3/framework/plugins/windows/thrdscan.py @@ -1,15 +1,17 @@ ## ## plugin for testing addition of threads scan support to poolscanner.py ## -import logging import datetime -from typing import Callable, Iterable, Tuple, Optional, Dict +import logging +from typing import Callable, Dict, NamedTuple, Optional, Union, Tuple, Iterator -from volatility3.framework import renderers, interfaces, exceptions +from volatility3.framework import exceptions, interfaces, objects, renderers from volatility3.framework.configuration import requirements +from volatility3.framework.constants import windows as windows_constants from volatility3.framework.renderers import format_hints -from volatility3.plugins.windows import poolscanner, pe_symbols +from volatility3.framework.symbols.windows import extensions as win_extensions from volatility3.plugins import timeliner +from volatility3.plugins.windows import pe_symbols, poolscanner vollog = logging.getLogger(__name__) @@ -19,7 +21,18 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) # version 2.6.0 adds support for scanning for 'Ethread' structures by pool tags _required_framework_version = (2, 6, 0) - _version = (2, 0, 0) + _version = (2, 1, 0) + + class ThreadInfo(NamedTuple): + offset: int + pid: objects.Pointer + tid: objects.Pointer + start_addr: objects.Pointer + start_path: Optional[str] + win32_start_addr: objects.Pointer + win32_start_path: Optional[str] + create_time: Union[datetime.datetime, interfaces.renderers.BaseAbsentValue] + exit_time: Union[datetime.datetime, interfaces.renderers.BaseAbsentValue] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -51,7 +64,7 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) cls, context: interfaces.context.ContextInterface, module_name: str, - ) -> Iterable[interfaces.objects.ObjectInterface]: + ) -> Iterator[win_extensions.ETHREAD]: """Scans for threads using the poolscanner module and constraints. Args: @@ -77,19 +90,9 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) @classmethod def gather_thread_info( cls, - ethread: interfaces.objects.ObjectInterface, - vads_cache: Dict[int, pe_symbols.ranges_type] = None, - ) -> Tuple[ - int, - int, - int, - int, - Optional[str], - int, - Optional[str], - Optional[datetime.datetime], - Optional[datetime.datetime], - ]: + ethread: win_extensions.ETHREAD, + vads_cache: Optional[Dict[int, pe_symbols.ranges_type]] = None, + ) -> Optional[ThreadInfo]: try: thread_offset = ethread.vol.offset owner_proc_pid = ethread.Cid.UniqueProcess @@ -110,44 +113,52 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) vollog.debug(f"Thread invalid address {ethread.vol.offset:#x}") return None - # don't look for VADs in kernel threads, just let them get reported with empty paths + # Filter junk PIDs if ( - owner_proc_pid != 4 - and owner_proc.InheritedFromUniqueProcessId != 4 + ethread.Cid.UniqueProcess > windows_constants.MAX_PID + or ethread.Cid.UniqueProcess == 0 + or ethread.Cid.UniqueProcess % 4 != 0 + ): + return None + + # Get VAD mappings for valid non-system (PID 4) processes + if ( + owner_proc + and owner_proc.is_valid() + and owner_proc.UniqueProcessId != 4 and vads_cache is not None ): vads = pe_symbols.PESymbols.get_vads_for_process_cache( vads_cache, owner_proc ) - if not vads or len(vads) < 5: - vollog.debug( - f"Not enough vads for process at {owner_proc.vol.offset:#x}. Skipping thread at {ethread.vol.offset:#x}" - ) - return None - start_path = pe_symbols.PESymbols.filepath_for_address( - vads, thread_start_addr + start_path = ( + pe_symbols.PESymbols.filepath_for_address(vads, thread_start_addr) + if vads + else None ) - win32start_path = pe_symbols.PESymbols.filepath_for_address( - vads, thread_win32start_addr + win32start_path = ( + pe_symbols.PESymbols.filepath_for_address(vads, thread_win32start_addr) + if vads + else None ) else: start_path = None win32start_path = None - return ( - format_hints.Hex(thread_offset), + return cls.ThreadInfo( + thread_offset, owner_proc_pid, thread_tid, - format_hints.Hex(thread_start_addr), + thread_start_addr, start_path, - format_hints.Hex(thread_win32start_addr), + thread_win32start_addr, win32start_path, thread_create_time, thread_exit_time, ) - def _generator(self, filter_func: Callable): + def _generator(self, filter_func: Callable) -> Iterator[Tuple[int, Tuple]]: kernel_name = self.config["kernel"] vads_cache: Dict[int, pe_symbols.ranges_type] = {} @@ -156,27 +167,16 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) info = self.gather_thread_info(ethread, vads_cache) if info: - ( - offset, - pid, - tid, - start_addr, - start_path, - win32start_addr, - win32start_path, - create_time, - exit_time, - ) = info yield 0, ( - offset, - pid, - tid, - start_addr, - start_path or renderers.NotAvailableValue(), - win32start_addr, - win32start_path or renderers.NotAvailableValue(), - create_time, - exit_time, + format_hints.Hex(info.offset), + info.pid, + info.tid, + format_hints.Hex(info.start_addr), + info.start_path or renderers.NotAvailableValue(), + format_hints.Hex(info.win32_start_addr), + info.win32_start_path or renderers.NotAvailableValue(), + info.create_time, + info.exit_time, ) def generate_timeline(self): diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 9fe250ba5..e6850dde5 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -568,16 +568,20 @@ class ETHREAD(objects.StructType, pool.ExecutiveObject): # passed all validations return True - def get_create_time(self): + def get_create_time( + self, + ) -> Union[datetime.datetime, interfaces.renderers.BaseAbsentValue]: # For Windows XPs if self.has_member("ThreadsProcess"): return conversion.wintime_to_datetime(self.CreateTime.QuadPart >> 3) return conversion.wintime_to_datetime(self.CreateTime.QuadPart) - def get_exit_time(self): + def get_exit_time( + self, + ) -> Union[datetime.datetime, interfaces.renderers.BaseAbsentValue]: return conversion.wintime_to_datetime(self.ExitTime.QuadPart) - def owning_process(self) -> interfaces.objects.ObjectInterface: + def owning_process(self) -> "EPROCESS": """Return the EPROCESS that owns this thread.""" # For Windows XPs @@ -705,7 +709,11 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): return False # NT pids are divisible by 4 - if self.UniqueProcessId % 4 != 0: + if ( + self.UniqueProcessId % 4 != 0 + or self.UniqueProcessId == 0 + or self.UniqueProcessId > constants.windows.MAX_PID + ): return False # check for all 0s besides the PCID entries