Merge pull request #1620 from volatilityfoundation/issue_1474_orphan_threads

Enforce kernel boundaries correctly. Fix bugs. Closes #1474
This commit is contained in:
ikelos
2025-02-14 20:38:48 +00:00
committed by GitHub
2 changed files with 54 additions and 17 deletions
@@ -4,7 +4,7 @@
import logging
from typing import Generator, Iterable, List, Optional
from volatility3.framework import constants, exceptions, interfaces, renderers
from volatility3.framework import symbols, constants, exceptions, interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import intermed
@@ -18,7 +18,7 @@ class Modules(interfaces.plugins.PluginInterface):
"""Lists the loaded kernel modules."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 1)
_version = (2, 1, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -127,6 +127,32 @@ class Modules(interfaces.plugins.PluginInterface):
file_output,
)
@classmethod
def get_kernel_space_start(cls, context, module_name: str) -> int:
"""
Returns the starting address of the kernel address space
This method allows plugins that analyze kernel data structures to quickly detect
smeared or otherwise invalid data as many pointers must point into the kernel or
access during runtime would crash the system
"""
module = context.modules[module_name]
if symbols.symbol_table_is_64bit(context, module.symbol_table_name):
object_type = "unsigned long long"
else:
object_type = "unsigned long"
range_start_offset = module.get_symbol("MmSystemRangeStart").address
kernel_space_start = module.object(
object_type=object_type, offset=range_start_offset
)
layer = context.layers[module.layer_name]
return kernel_space_start & layer.address_mask
@classmethod
def get_session_layers(
cls,
@@ -5,9 +5,9 @@
import logging
from typing import List, Generator
from volatility3.framework import interfaces, symbols
from volatility3.framework import interfaces, exceptions
from volatility3.framework.configuration import requirements
from volatility3.plugins.windows import thrdscan, ssdt
from volatility3.plugins.windows import thrdscan, ssdt, modules
vollog = logging.getLogger(__name__)
@@ -37,6 +37,9 @@ class Threads(thrdscan.ThrdScan):
requirements.PluginRequirement(
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
),
requirements.PluginRequirement(
name="modules", plugin=modules.Modules, version=(2, 1, 0)
),
]
@classmethod
@@ -56,24 +59,27 @@ class Threads(thrdscan.ThrdScan):
"""
module = context.modules[module_name]
layer_name = module.layer_name
symbol_table = module.symbol_table_name
symbol_table_name = module.symbol_table_name
collection = ssdt.SSDT.build_module_collection(
context, layer_name, symbol_table
context, layer_name, symbol_table_name
)
# FIXME - use a proper constant once established
# used to filter out smeared pointers
if symbols.symbol_table_is_64bit(context, symbol_table):
kernel_start = 0xFFFFF80000000000
else:
kernel_start = 0x80000000
kernel_space_start = modules.Modules.get_kernel_space_start(
context, module_name
)
for thread in thrdscan.ThrdScan.scan_threads(context, module_name):
# we don't want smeared or terminated threads
# We don't want smeared or terminated threads
# So we access the owning process (which could also be terminated or smeared)
# Plus check the start address holding page
try:
proc = thread.owning_process()
except AttributeError:
pid = proc.UniqueProcessId
ppid = proc.InheritedFromUniqueProcessId
thread_start = thread.StartAddress
except (AttributeError, exceptions.InvalidAddressException):
continue
# we only care about kernel threads, 4 = System
@@ -81,14 +87,19 @@ class Threads(thrdscan.ThrdScan):
# such as bit fields and flags are not stable in Win10+
# so we check if the thread is from the kernel itself or one its child
# kernel processes (MemCompression, Regsitry, ...)
if proc.UniqueProcessId != 4 and proc.InheritedFromUniqueProcessId != 4:
if pid != 4 and ppid != 4:
continue
if thread.StartAddress < kernel_start:
# if the thread has an exit time or terminated (4) state, then skip it
if thread.ExitTime.QuadPart > 0 or thread.Tcb.State == 4:
continue
# threads pointing into userland, which is from smeared or terminated threads
if thread_start < kernel_space_start:
continue
module_symbols = list(
collection.get_module_symbols_by_absolute_location(thread.StartAddress)
collection.get_module_symbols_by_absolute_location(thread_start)
)
# alert on threads that do not map to a module