From bad34a112aabfc4c7a0e3eb29f3d7716fa839faf Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sat, 15 Mar 2025 22:58:07 +0000 Subject: [PATCH] Add the windows plugin and associated extensions updates --- .../framework/plugins/windows/windows.py | 137 ++++++++++++ .../symbols/windows/extensions/gui.py | 199 +++++++++++++++++- .../windows/gui/gui-win10-10586-x64.json | 6 + .../windows/gui/gui-win10-15063-x64.json | 6 + .../windows/gui/gui-win10-16299-x64.json | 6 + .../windows/gui/gui-win10-17134-x64.json | 10 +- .../windows/gui/gui-win10-17735-x64.json | 10 +- .../windows/gui/gui-win10-17763-x64.json | 10 +- .../windows/gui/gui-win10-18362-x64.json | 10 +- .../windows/gui/gui-win10-19041-x64.json | 10 +- .../windows/gui/gui-win10-19577-x64.json | 10 +- .../symbols/windows/gui/gui-win7sp0-x64.json | 6 + .../symbols/windows/gui/gui-win7sp1-x64.json | 6 + .../symbols/windows/gui/gui-win8-x64.json | 6 + 14 files changed, 418 insertions(+), 14 deletions(-) create mode 100644 volatility3/framework/plugins/windows/windows.py diff --git a/volatility3/framework/plugins/windows/windows.py b/volatility3/framework/plugins/windows/windows.py new file mode 100644 index 000000000..5e3059e45 --- /dev/null +++ b/volatility3/framework/plugins/windows/windows.py @@ -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(), + ) diff --git a/volatility3/framework/symbols/windows/extensions/gui.py b/volatility3/framework/symbols/windows/extensions/gui.py index 92693dba5..559fda095 100644 --- a/volatility3/framework/symbols/windows/extensions/gui.py +++ b/volatility3/framework/symbols/windows/extensions/gui.py @@ -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, } diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-10586-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-10586-x64.json index a542d4778..0308f4c7d 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-10586-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-10586-x64.json @@ -18036,6 +18036,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-15063-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-15063-x64.json index 7c4af02e1..a69cbb7c3 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-15063-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-15063-x64.json @@ -18036,6 +18036,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-16299-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-16299-x64.json index 1ff5fdcd9..54e8aeec3 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-16299-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-16299-x64.json @@ -18036,6 +18036,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-17134-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-17134-x64.json index f74c8dd5b..48b97a1c2 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-17134-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-17134-x64.json @@ -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", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-17735-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-17735-x64.json index 88e419100..affaf8731 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-17735-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-17735-x64.json @@ -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", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-17763-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-17763-x64.json index ed183f39b..33db6c28d 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-17763-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-17763-x64.json @@ -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", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-18362-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-18362-x64.json index f568eedbe..bb1c74f7f 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-18362-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-18362-x64.json @@ -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", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-19041-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-19041-x64.json index be4341cfd..74868f1a7 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-19041-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-19041-x64.json @@ -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", diff --git a/volatility3/framework/symbols/windows/gui/gui-win10-19577-x64.json b/volatility3/framework/symbols/windows/gui/gui-win10-19577-x64.json index 68692dcf4..e718710cf 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win10-19577-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win10-19577-x64.json @@ -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", diff --git a/volatility3/framework/symbols/windows/gui/gui-win7sp0-x64.json b/volatility3/framework/symbols/windows/gui/gui-win7sp0-x64.json index ec81241b2..9b413baaa 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win7sp0-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win7sp0-x64.json @@ -18619,6 +18619,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win7sp1-x64.json b/volatility3/framework/symbols/windows/gui/gui-win7sp1-x64.json index ae844e535..b856506a7 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win7sp1-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win7sp1-x64.json @@ -17985,6 +17985,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little", diff --git a/volatility3/framework/symbols/windows/gui/gui-win8-x64.json b/volatility3/framework/symbols/windows/gui/gui-win8-x64.json index e7581413f..8662bf62b 100644 --- a/volatility3/framework/symbols/windows/gui/gui-win8-x64.json +++ b/volatility3/framework/symbols/windows/gui/gui-win8-x64.json @@ -17992,6 +17992,12 @@ "signed": false, "size": 1 }, + "char": { + "kind": "char", + "endian": "little", + "signed": false, + "size": 1 + }, "float": { "kind": "float", "endian": "little",