Skip to content

Commit

Permalink
Merge pull request #1620 from volatilityfoundation/issue_1474_orphan_…
Browse files Browse the repository at this point in the history
…threads

Enforce kernel boundaries correctly. Fix bugs. Closes #1474
  • Loading branch information
ikelos authored Feb 14, 2025
2 parents 575e645 + 92db0e3 commit 0ea766a
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 17 deletions.
30 changes: 28 additions & 2 deletions volatility3/framework/plugins/windows/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -127,6 +127,32 @@ def _generator(self):
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,
Expand Down
41 changes: 26 additions & 15 deletions volatility3/framework/plugins/windows/orphan_kernel_threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -37,6 +37,9 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
requirements.PluginRequirement(
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
),
requirements.PluginRequirement(
name="modules", plugin=modules.Modules, version=(2, 1, 0)
),
]

@classmethod
Expand All @@ -56,39 +59,47 @@ def list_orphan_kernel_threads(
"""
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
# previous methods for determining if a thread was a kernel thread
# 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 the thread has an exit time or terminated (4) state, then skip it
if thread.ExitTime.QuadPart > 0 or thread.Tcb.State == 4:
continue

if thread.StartAddress < kernel_start:
# 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
Expand Down

0 comments on commit 0ea766a

Please sign in to comment.