mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-08-17 20:35:40 +02:00
Add the windows plugin and associated extensions updates
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
# This file is Copyright 2025 Volatility Foundation and licensed under the Volatility Software License 1.0
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
import logging
|
||||
from typing import List, Iterable
|
||||
|
||||
from volatility3.framework import interfaces, renderers, exceptions
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.plugins.windows import windowstations
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Windows(interfaces.plugins.PluginInterface):
|
||||
"""Enumerates the Windows of Desktop instances"""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
# Since we're calling the plugin, make sure we have the plugin's requirements
|
||||
return [
|
||||
requirements.ModuleRequirement(
|
||||
name="kernel",
|
||||
description="Windows kernel",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="windowstations",
|
||||
component=windowstations.WindowStations,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def list_windows(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
config_path: str,
|
||||
kernel_module_name: str,
|
||||
) -> Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""
|
||||
Enumerates the desktops of each window station
|
||||
For each found, enumerates its windows within the desktop
|
||||
"""
|
||||
kernel = context.modules[kernel_module_name]
|
||||
|
||||
for (
|
||||
winsta,
|
||||
station_name,
|
||||
session_id,
|
||||
) in windowstations.WindowStations.scan_window_stations(
|
||||
context, config_path, kernel_module_name
|
||||
):
|
||||
# for each window station, walk its list of desktops
|
||||
for desktop, desktop_name in winsta.desktops(kernel.symbol_table_name):
|
||||
try:
|
||||
top_window = desktop.pDeskInfo.spwnd
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug(
|
||||
f"Desktop with name {desktop_name} in window station {station_name} has a broken window pointer."
|
||||
)
|
||||
continue
|
||||
|
||||
for window, window_name in desktop.windows(top_window):
|
||||
yield station_name, desktop_name, window, window_name
|
||||
|
||||
def _generator(self):
|
||||
kernel_name = self.config["kernel"]
|
||||
|
||||
# call the implementation for finding windows and gather attributes
|
||||
for station_name, desktop_name, window, window_name in self.list_windows(
|
||||
self.context, self.config_path, kernel_name
|
||||
):
|
||||
# We need a valid process and session id for the window to display it
|
||||
process = window.get_process()
|
||||
process_name = None
|
||||
if process:
|
||||
try:
|
||||
process_name = utility.array_to_string(process.ImageFileName)
|
||||
process_pid = process.UniqueProcessId
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug(
|
||||
f"Unable to read name and pid of the process for window {window.vol.offset:#x}"
|
||||
)
|
||||
|
||||
if process_name is None:
|
||||
vollog.warning(
|
||||
f"Invalid process reference for the process hosting window {window.vol.offset:#x}"
|
||||
)
|
||||
continue
|
||||
|
||||
sess_id = window.get_session_id()
|
||||
if sess_id is None:
|
||||
vollog.debug(
|
||||
f"Unable to read session id of the process for window {window.vol.offset:#x} in process {process_name}"
|
||||
)
|
||||
continue
|
||||
|
||||
# procedures can be empty, but if set, should be a valid pointer
|
||||
window_proc = window.get_window_procedure()
|
||||
if window_proc is None or window_proc == 0 or window_proc > 0x1000:
|
||||
window_proc = format_hints.Hex(window_proc)
|
||||
else:
|
||||
vollog.warning(
|
||||
f"Invalid window procedure for the window {window.vol.offset:#x}"
|
||||
)
|
||||
continue
|
||||
|
||||
yield 0, (
|
||||
format_hints.Hex(window.vol.offset),
|
||||
station_name,
|
||||
sess_id,
|
||||
desktop_name,
|
||||
window_name or renderers.NotAvailableValue(),
|
||||
window_proc,
|
||||
process_name,
|
||||
process_pid,
|
||||
)
|
||||
|
||||
def run(self):
|
||||
return renderers.TreeGrid(
|
||||
[
|
||||
("Offset", format_hints.Hex),
|
||||
("Station", str),
|
||||
("Session", int),
|
||||
("Desktop", str),
|
||||
("Window", str),
|
||||
("Procedure", format_hints.Hex),
|
||||
("Process", str),
|
||||
("PID", int),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
@@ -2,13 +2,17 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
|
||||
from typing import Optional, Tuple, Iterator
|
||||
import logging
|
||||
from typing import Optional, Tuple, Iterator, Generator
|
||||
|
||||
from volatility3.framework import exceptions, constants, interfaces
|
||||
from volatility3.framework import objects
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.framework.symbols.windows import extensions
|
||||
from volatility3.framework.symbols.windows.extensions import pool
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class tagWINDOWSTATION(objects.StructType, pool.ExecutiveObject):
|
||||
def is_valid(self) -> bool:
|
||||
@@ -77,7 +81,7 @@ class tagWINDOWSTATION(objects.StructType, pool.ExecutiveObject):
|
||||
class tagDESKTOP(objects.StructType, pool.ExecutiveObject):
|
||||
def is_valid(self) -> bool:
|
||||
"""
|
||||
Enforce a valid sid + owning window station
|
||||
Enforce a valid sid + name
|
||||
"""
|
||||
sid = self.get_session_id()
|
||||
|
||||
@@ -89,12 +93,18 @@ class tagDESKTOP(objects.StructType, pool.ExecutiveObject):
|
||||
return False
|
||||
|
||||
def get_window_station(self) -> Optional["tagWINDOWSTATION"]:
|
||||
"""
|
||||
Attempts to return the window station for this desktop
|
||||
"""
|
||||
try:
|
||||
return self.rpwinstaParent.dereference()
|
||||
except exceptions.InvalidAddressException:
|
||||
return None
|
||||
|
||||
def get_session_id(self) -> Optional[int]:
|
||||
"""
|
||||
Attempts to return the session ID for this desktop
|
||||
"""
|
||||
winsta = self.get_window_station()
|
||||
if winsta:
|
||||
return winsta.get_session_id()
|
||||
@@ -120,8 +130,193 @@ class tagDESKTOP(objects.StructType, pool.ExecutiveObject):
|
||||
|
||||
yield thread, process_name, process_pid
|
||||
|
||||
def _do_get_windows(
|
||||
self, window, max_windows
|
||||
) -> Generator[Tuple[interfaces.objects.ObjectInterface, str], None, None]:
|
||||
"""
|
||||
Recusively walks and yields the adjacent and child windows
|
||||
"""
|
||||
seen_windows = set()
|
||||
seen_children = set()
|
||||
|
||||
if window.vol.offset == 0:
|
||||
return
|
||||
|
||||
yield window, window.get_name()
|
||||
|
||||
seen_windows.add(window)
|
||||
|
||||
# Walk adjacent windows
|
||||
while len(seen_windows) < max_windows:
|
||||
try:
|
||||
window = window.spwndNext.dereference()
|
||||
except exceptions.InvalidAddressException:
|
||||
break
|
||||
|
||||
if window.vol.offset == 0:
|
||||
break
|
||||
|
||||
if window.vol.offset in seen_windows:
|
||||
break
|
||||
|
||||
yield window, window.get_name()
|
||||
|
||||
seen_windows.add(window)
|
||||
|
||||
# Walk children windows and recursively yield them
|
||||
for window in seen_windows:
|
||||
child = window
|
||||
|
||||
while len(seen_windows) + len(seen_children) < max_windows:
|
||||
try:
|
||||
child = child.spwndChild
|
||||
except exceptions.InvalidAddressException:
|
||||
break
|
||||
|
||||
if child.vol.offset == 0:
|
||||
break
|
||||
|
||||
if child in seen_children:
|
||||
break
|
||||
seen_children.add(child)
|
||||
|
||||
yield from self._do_get_windows(child, max_windows)
|
||||
|
||||
def windows(
|
||||
self, window, max_windows=10000
|
||||
) -> Generator[Tuple[interfaces.objects.ObjectInterface, str], None, None]:
|
||||
"""
|
||||
Enumerates all windows adjacent to and children of `window`
|
||||
|
||||
Args:
|
||||
window: The window to enumerate windows from
|
||||
|
||||
Returns:
|
||||
A generator of tuples containing the window and its name
|
||||
"""
|
||||
seen_windows = set()
|
||||
|
||||
for window, window_name in self._do_get_windows(window, max_windows):
|
||||
if window.vol.offset in seen_windows:
|
||||
continue
|
||||
|
||||
seen_windows.add(window.vol.offset)
|
||||
|
||||
yield window, window_name
|
||||
|
||||
if len(seen_windows) == max_windows:
|
||||
break
|
||||
|
||||
|
||||
class tagWND(objects.StructType, pool.ExecutiveObject):
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
"""
|
||||
Enforce a valid sid
|
||||
"""
|
||||
sid = self.get_session_id()
|
||||
|
||||
return sid is not None and 0 <= sid < 256
|
||||
|
||||
def get_name(self) -> Optional[str]:
|
||||
"""
|
||||
directName appeared in later Windows 10 versions and is pointer
|
||||
strName is a unicode string directly in the structure
|
||||
"""
|
||||
if self.has_member("directName"):
|
||||
try:
|
||||
return utility.pointer_to_string(
|
||||
self.directName, count=256, encoding="utf16"
|
||||
)
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug(
|
||||
f"directname for window at {self.vol.offset:#x} in layer {self.vol.layer_name} is invalid"
|
||||
)
|
||||
|
||||
try:
|
||||
return self.strName.get_string()
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug(
|
||||
f"strName for window at {self.vol.offset:#x} in layer {self.vol.layer_name} is invalid"
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def get_session_id(self) -> Optional[int]:
|
||||
"""
|
||||
Uses its tagDESKTOP pointer to find its session
|
||||
"""
|
||||
desktop = self.get_desktop()
|
||||
if desktop:
|
||||
return desktop.get_session_id()
|
||||
|
||||
return None
|
||||
|
||||
def get_desktop(self) -> Optional[tagDESKTOP]:
|
||||
"""
|
||||
Attempts to return the host desktop (tagDESKTOP) for this window
|
||||
"""
|
||||
try:
|
||||
return self.head.rpdesk.dereference()
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug(
|
||||
f"Reading the desktop pointer for window {self.vol.offset:#x} caused a page fault"
|
||||
)
|
||||
return None
|
||||
|
||||
def get_process(self) -> Optional["extensions.EPROCESS"]:
|
||||
"""
|
||||
Attempts to return the host process (_EPROCESS) for this window
|
||||
"""
|
||||
try:
|
||||
return self.head.pti.ppi.Process.dereference()
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug(
|
||||
f"Reading the process pointer for window {self.vol.offset:#x} caused a page fault"
|
||||
)
|
||||
return None
|
||||
|
||||
def get_window_procedure(self):
|
||||
"""
|
||||
Attempts to return the window procedure for this windows
|
||||
"""
|
||||
try:
|
||||
# >= 17134
|
||||
if hasattr(self, "subPointer"):
|
||||
return self.subPointer.lpfnWndProc
|
||||
else:
|
||||
return self.lpfnWndProc
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug(f"Invalid window procedure for window {self.vol.offset:#x}")
|
||||
return None
|
||||
|
||||
|
||||
# This is copy/paste from UNICODE_STRING in `symbols/windows/extensions/__init__.py`
|
||||
# The versioning of modules would get very ugly if we let different modules share implementations
|
||||
# across different data structures
|
||||
class LARGE_UNICODE_STRING(objects.StructType):
|
||||
"""A class for Windows unicode string structures."""
|
||||
|
||||
def get_string(self) -> interfaces.objects.ObjectInterface:
|
||||
# We explicitly do *not* catch errors here, we allow an exception to be thrown
|
||||
# (otherwise there's no way to determine anything went wrong)
|
||||
# It's up to the user of this method to catch exceptions
|
||||
|
||||
# We manually construct an object rather than casting a dereferenced pointer in case
|
||||
# the buffer length is 0 and the pointer is a NULL pointer
|
||||
return self._context.object(
|
||||
self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "string",
|
||||
layer_name=self.Buffer.vol.native_layer_name,
|
||||
offset=self.Buffer,
|
||||
max_length=self.Length,
|
||||
errors="replace",
|
||||
encoding="utf16",
|
||||
)
|
||||
|
||||
|
||||
class_types = {
|
||||
"tagWINDOWSTATION": tagWINDOWSTATION,
|
||||
"tagDESKTOP": tagDESKTOP,
|
||||
"tagWND": tagWND,
|
||||
"_LARGE_UNICODE_STRING": LARGE_UNICODE_STRING,
|
||||
}
|
||||
|
||||
@@ -18036,6 +18036,12 @@
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"char": {
|
||||
"kind": "char",
|
||||
"endian": "little",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"float": {
|
||||
"kind": "float",
|
||||
"endian": "little",
|
||||
|
||||
@@ -18036,6 +18036,12 @@
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"char": {
|
||||
"kind": "char",
|
||||
"endian": "little",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"float": {
|
||||
"kind": "float",
|
||||
"endian": "little",
|
||||
|
||||
@@ -18036,6 +18036,12 @@
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"char": {
|
||||
"kind": "char",
|
||||
"endian": "little",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"float": {
|
||||
"kind": "float",
|
||||
"endian": "little",
|
||||
|
||||
@@ -12464,8 +12464,8 @@
|
||||
"directName": {
|
||||
"type": {
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "nt_symbols!String"
|
||||
"kind": "base",
|
||||
"name": "char"
|
||||
},
|
||||
"kind": "pointer"
|
||||
},
|
||||
@@ -18079,6 +18079,12 @@
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"char": {
|
||||
"kind": "char",
|
||||
"endian": "little",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"float": {
|
||||
"kind": "float",
|
||||
"endian": "little",
|
||||
|
||||
@@ -12464,8 +12464,8 @@
|
||||
"directName": {
|
||||
"type": {
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "nt_symbols!String"
|
||||
"kind": "base",
|
||||
"name": "char"
|
||||
},
|
||||
"kind": "pointer"
|
||||
},
|
||||
@@ -18079,6 +18079,12 @@
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"char": {
|
||||
"kind": "char",
|
||||
"endian": "little",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"float": {
|
||||
"kind": "float",
|
||||
"endian": "little",
|
||||
|
||||
@@ -12464,8 +12464,8 @@
|
||||
"directName": {
|
||||
"type": {
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "nt_symbols!String"
|
||||
"kind": "base",
|
||||
"name": "char"
|
||||
},
|
||||
"kind": "pointer"
|
||||
},
|
||||
@@ -18079,6 +18079,12 @@
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"char": {
|
||||
"kind": "char",
|
||||
"endian": "little",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"float": {
|
||||
"kind": "float",
|
||||
"endian": "little",
|
||||
|
||||
@@ -12464,8 +12464,8 @@
|
||||
"directName": {
|
||||
"type": {
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "nt_symbols!String"
|
||||
"kind": "base",
|
||||
"name": "char"
|
||||
},
|
||||
"kind": "pointer"
|
||||
},
|
||||
@@ -18079,6 +18079,12 @@
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"char": {
|
||||
"kind": "char",
|
||||
"endian": "little",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"float": {
|
||||
"kind": "float",
|
||||
"endian": "little",
|
||||
|
||||
@@ -12464,8 +12464,8 @@
|
||||
"directName": {
|
||||
"type": {
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "nt_symbols!String"
|
||||
"kind": "base",
|
||||
"name": "char"
|
||||
},
|
||||
"kind": "pointer"
|
||||
},
|
||||
@@ -18079,6 +18079,12 @@
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"char": {
|
||||
"kind": "char",
|
||||
"endian": "little",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"float": {
|
||||
"kind": "float",
|
||||
"endian": "little",
|
||||
|
||||
@@ -12464,8 +12464,8 @@
|
||||
"directName": {
|
||||
"type": {
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "nt_symbols!String"
|
||||
"kind": "base",
|
||||
"name": "char"
|
||||
},
|
||||
"kind": "pointer"
|
||||
},
|
||||
@@ -18079,6 +18079,12 @@
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"char": {
|
||||
"kind": "char",
|
||||
"endian": "little",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"float": {
|
||||
"kind": "float",
|
||||
"endian": "little",
|
||||
|
||||
@@ -18619,6 +18619,12 @@
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"char": {
|
||||
"kind": "char",
|
||||
"endian": "little",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"float": {
|
||||
"kind": "float",
|
||||
"endian": "little",
|
||||
|
||||
@@ -17985,6 +17985,12 @@
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"char": {
|
||||
"kind": "char",
|
||||
"endian": "little",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"float": {
|
||||
"kind": "float",
|
||||
"endian": "little",
|
||||
|
||||
@@ -17992,6 +17992,12 @@
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"char": {
|
||||
"kind": "char",
|
||||
"endian": "little",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"float": {
|
||||
"kind": "float",
|
||||
"endian": "little",
|
||||
|
||||
Reference in New Issue
Block a user