mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-10 03:37:39 +02:00
Merge pull request #1766 from volatilityfoundation/thrdscan/filtering_and_tracebacks
Windows ThrdScan: Thread filtering, type-hints and tracebacks
This commit is contained in:
@@ -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 = (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user