From 3139cae998313ee8a3a4553609f4d950b22acdc9 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Fri, 7 Mar 2025 14:33:02 -0600 Subject: [PATCH 1/4] Windows: Use device offsets instead of driver offsets This updates the `devicetree` plugin to return the virtual offsets of the device objects themselves instead of that of their parent driver. --- volatility3/framework/plugins/windows/devicetree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/windows/devicetree.py b/volatility3/framework/plugins/windows/devicetree.py index 012a8750d8..5cade0bbf3 100644 --- a/volatility3/framework/plugins/windows/devicetree.py +++ b/volatility3/framework/plugins/windows/devicetree.py @@ -138,7 +138,7 @@ def _generator(self) -> Iterator[Tuple]: yield ( 1, ( - format_hints.Hex(driver.vol.offset), + format_hints.Hex(device.vol.offset), "DEV", driver_name, device_name, @@ -170,7 +170,7 @@ def _generator(self) -> Iterator[Tuple]: yield ( level, ( - format_hints.Hex(driver.vol.offset), + format_hints.Hex(attached_device.vol.offset), "ATT", driver_name, device_name, From 2773b79a9f27ace50a196d1989d376184dc82424 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Fri, 7 Mar 2025 16:44:48 -0600 Subject: [PATCH 2/4] Windows: Fix devicetree traversal Previously, the attached devices were being traversed once depth-wise, but the horizontal levels of the device tree were not being properly traversed, leading to tons of missing device entries. This fixes the issue by implementing a recursive staticmethod for properly descending + enumerating devices on each level of the tree. --- .../framework/plugins/windows/devicetree.py | 117 +++++++++--------- 1 file changed, 60 insertions(+), 57 deletions(-) diff --git a/volatility3/framework/plugins/windows/devicetree.py b/volatility3/framework/plugins/windows/devicetree.py index 5cade0bbf3..5bb74a7c8a 100644 --- a/volatility3/framework/plugins/windows/devicetree.py +++ b/volatility3/framework/plugins/windows/devicetree.py @@ -3,12 +3,12 @@ # import logging +from typing import Iterator, List, Set, Tuple -from typing import Iterator, List, Tuple - -from volatility3.framework import constants, renderers, exceptions, interfaces +from volatility3.framework import constants, exceptions, interfaces, renderers from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints +from volatility3.framework.symbols.windows.extensions import DEVICE_OBJECT from volatility3.plugins.windows import driverscan DEVICE_CODES = { @@ -102,7 +102,7 @@ def _generator(self) -> Iterator[Tuple]: ): try: try: - driver_name = driver.get_driver_name() + driver_name = driver.DriverName.get_string() except (ValueError, exceptions.InvalidAddressException): vollog.log( constants.LOGLEVEL_VVVV, @@ -124,59 +124,20 @@ def _generator(self) -> Iterator[Tuple]: # Scan to get the device information of driver. for device in driver.get_devices(): - try: - device_name = device.get_device_name() - except (ValueError, exceptions.InvalidAddressException): - vollog.log( - constants.LOGLEVEL_VVVV, - f"Failed to get Device name : {device.vol.offset:x}", - ) - device_name = renderers.UnparsableValue() - - device_type = DEVICE_CODES.get(device.DeviceType, "UNKNOWN") - - yield ( - 1, - ( - format_hints.Hex(device.vol.offset), - "DEV", - driver_name, - device_name, - renderers.NotApplicableValue(), - device_type, - ), - ) - - # Scan to get the attached devices information of device. - for level, attached_device in enumerate( - device.get_attached_devices(), start=2 - ): - try: - device_name = attached_device.get_device_name() - except (ValueError, exceptions.InvalidAddressException): - vollog.log( - constants.LOGLEVEL_VVVV, - f"Failed to get Attached Device Name: {attached_device.vol.offset:x}", - ) - device_name = renderers.UnparsableValue() - - attached_device_driver_name = ( - attached_device.DriverObject.DriverName.get_string() - ) - attached_device_type = DEVICE_CODES.get( - attached_device.DeviceType, "UNKNOWN" - ) - - yield ( - level, - ( - format_hints.Hex(attached_device.vol.offset), - "ATT", - driver_name, - device_name, - attached_device_driver_name, - attached_device_type, - ), + for level, ( + offset, + drv_name, + dev_name, + att_drv_name, + dev_typ, + ) in self._traverse_device_stack(device, driver_name, 1): + yield level, ( + offset, + "DEV" if level == 1 else "ATT", + drv_name, + dev_name, + att_drv_name, + dev_typ, ) except exceptions.InvalidAddressException: @@ -186,6 +147,48 @@ def _generator(self) -> Iterator[Tuple]: ) continue + @staticmethod + def _traverse_device_stack( + device: DEVICE_OBJECT, driver_name: str, level: int, seen: Set[int] = set() + ) -> Iterator[Tuple]: + while device and device.vol.offset not in seen: + seen.add(device.vol.offset) + try: + device_name = device.get_device_name() + except (ValueError, exceptions.InvalidAddressException): + vollog.log( + constants.LOGLEVEL_VVVV, + f"Failed to get Device name : {device.vol.offset:x}", + ) + device_name = renderers.UnparsableValue() + + device_type = DEVICE_CODES.get(device.DeviceType, "UNKNOWN") + + att_drv_name = device.DriverObject.DriverName.get_string() + + yield ( + level, + ( + format_hints.Hex(device.vol.offset), + driver_name, + device_name, + att_drv_name, + device_type, + ), + ) + try: + attached = device.AttachedDevice.dereference() + yield from DeviceTree._traverse_device_stack( + attached, driver_name, level + 1, seen + ) + except exceptions.InvalidAddressException: + pass + + try: + device = device.NextDevice.dereference() + except exceptions.InvalidAddressException: + pass + def run(self) -> renderers.TreeGrid: return renderers.TreeGrid( [ From f66acd6720502911f28b3583e49526ecb505844a Mon Sep 17 00:00:00 2001 From: David McDonald Date: Fri, 7 Mar 2025 21:29:18 -0600 Subject: [PATCH 3/4] Windows: Add log statements on InvalidAddressExceptions --- volatility3/framework/plugins/windows/devicetree.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/windows/devicetree.py b/volatility3/framework/plugins/windows/devicetree.py index 5bb74a7c8a..2929815756 100644 --- a/volatility3/framework/plugins/windows/devicetree.py +++ b/volatility3/framework/plugins/windows/devicetree.py @@ -182,12 +182,18 @@ def _traverse_device_stack( attached, driver_name, level + 1, seen ) except exceptions.InvalidAddressException: - pass + vollog.debug( + f"Failed to dereference attached device for device at {device.vol.offset:#x}, " + "devnode may not have drivers associated with it" + ) try: device = device.NextDevice.dereference() except exceptions.InvalidAddressException: - pass + vollog.debug( + f"Failed to dereference next driver in linked list at {int(device.NextDevice)}, " + "may have reached end of list" + ) def run(self) -> renderers.TreeGrid: return renderers.TreeGrid( From 73e422fc97a95ced87962625206b30f9b1adccc8 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Fri, 7 Mar 2025 22:41:00 -0600 Subject: [PATCH 4/4] Windows: Cleanup devicetree + extension class Moves a lot of the code that was parsing information about device objects out of the `DeviceTree` plugin logic and into the `DEVICE_OBJECT` extension class. Also moved the dict mapping integer values to device types into the extension module. Fixes a bug within the extension class' `get_attached_devices` method - now, immediate children of the device in the tree are yielded, instead of the faulty deep traversal that was occuring before. Because of this bugfix in an extension class as well as the addition of new methods in the extension class, this also bumps the framework minor version number. --- volatility3/framework/constants/_version.py | 2 +- .../framework/plugins/windows/devicetree.py | 195 +++++------------- .../symbols/windows/extensions/__init__.py | 113 +++++++++- 3 files changed, 162 insertions(+), 148 deletions(-) diff --git a/volatility3/framework/constants/_version.py b/volatility3/framework/constants/_version.py index aa8e8936fe..1ea59c0689 100644 --- a/volatility3/framework/constants/_version.py +++ b/volatility3/framework/constants/_version.py @@ -1,6 +1,6 @@ # We use the SemVer 2.0.0 versioning scheme VERSION_MAJOR = 2 # Number of releases of the library with a breaking change -VERSION_MINOR = 23 # Number of changes that only add to the interface +VERSION_MINOR = 24 # Number of changes that only add to the interface VERSION_PATCH = 0 # Number of changes that do not change the interface VERSION_SUFFIX = "" diff --git a/volatility3/framework/plugins/windows/devicetree.py b/volatility3/framework/plugins/windows/devicetree.py index 2929815756..648a402441 100644 --- a/volatility3/framework/plugins/windows/devicetree.py +++ b/volatility3/framework/plugins/windows/devicetree.py @@ -8,70 +8,9 @@ from volatility3.framework import constants, exceptions, interfaces, renderers from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints -from volatility3.framework.symbols.windows.extensions import DEVICE_OBJECT +from volatility3.framework.symbols.windows import extensions from volatility3.plugins.windows import driverscan -DEVICE_CODES = { - 0x00000027: "FILE_DEVICE_8042_PORT", - 0x00000032: "FILE_DEVICE_ACPI", - 0x00000029: "FILE_DEVICE_BATTERY", - 0x00000001: "FILE_DEVICE_BEEP", - 0x0000002A: "FILE_DEVICE_BUS_EXTENDER", - 0x00000002: "FILE_DEVICE_CD_ROM", - 0x00000003: "FILE_DEVICE_CD_ROM_FILE_SYSTEM", - 0x00000030: "FILE_DEVICE_CHANGER", - 0x00000004: "FILE_DEVICE_CONTROLLER", - 0x00000005: "FILE_DEVICE_DATALINK", - 0x00000006: "FILE_DEVICE_DFS", - 0x00000035: "FILE_DEVICE_DFS_FILE_SYSTEM", - 0x00000036: "FILE_DEVICE_DFS_VOLUME", - 0x00000007: "FILE_DEVICE_DISK", - 0x00000008: "FILE_DEVICE_DISK_FILE_SYSTEM", - 0x00000033: "FILE_DEVICE_DVD", - 0x00000009: "FILE_DEVICE_FILE_SYSTEM", - 0x0000003A: "FILE_DEVICE_FIPS", - 0x00000034: "FILE_DEVICE_FULLSCREEN_VIDEO", - 0x0000000A: "FILE_DEVICE_INPORT_PORT", - 0x0000000B: "FILE_DEVICE_KEYBOARD", - 0x0000002F: "FILE_DEVICE_KS", - 0x00000039: "FILE_DEVICE_KSEC", - 0x0000000C: "FILE_DEVICE_MAILSLOT", - 0x0000002D: "FILE_DEVICE_MASS_STORAGE", - 0x0000000D: "FILE_DEVICE_MIDI_IN", - 0x0000000E: "FILE_DEVICE_MIDI_OUT", - 0x0000002B: "FILE_DEVICE_MODEM", - 0x0000000F: "FILE_DEVICE_MOUSE", - 0x00000010: "FILE_DEVICE_MULTI_UNC_PROVIDER", - 0x00000011: "FILE_DEVICE_NAMED_PIPE", - 0x00000012: "FILE_DEVICE_NETWORK", - 0x00000013: "FILE_DEVICE_NETWORK_BROWSER", - 0x00000014: "FILE_DEVICE_NETWORK_FILE_SYSTEM", - 0x00000028: "FILE_DEVICE_NETWORK_REDIRECTOR", - 0x00000015: "FILE_DEVICE_NULL", - 0x00000016: "FILE_DEVICE_PARALLEL_PORT", - 0x00000017: "FILE_DEVICE_PHYSICAL_NETCARD", - 0x00000018: "FILE_DEVICE_PRINTER", - 0x00000019: "FILE_DEVICE_SCANNER", - 0x0000001C: "FILE_DEVICE_SCREEN", - 0x00000037: "FILE_DEVICE_SERENUM", - 0x0000001A: "FILE_DEVICE_SERIAL_MOUSE_PORT", - 0x0000001B: "FILE_DEVICE_SERIAL_PORT", - 0x00000031: "FILE_DEVICE_SMARTCARD", - 0x0000002E: "FILE_DEVICE_SMB", - 0x0000001D: "FILE_DEVICE_SOUND", - 0x0000001E: "FILE_DEVICE_STREAMS", - 0x0000001F: "FILE_DEVICE_TAPE", - 0x00000020: "FILE_DEVICE_TAPE_FILE_SYSTEM", - 0x00000038: "FILE_DEVICE_TERMSRV", - 0x00000021: "FILE_DEVICE_TRANSPORT", - 0x00000022: "FILE_DEVICE_UNKNOWN", - 0x0000002C: "FILE_DEVICE_VDM", - 0x00000023: "FILE_DEVICE_VIDEO", - 0x00000024: "FILE_DEVICE_VIRTUAL_DISK", - 0x00000025: "FILE_DEVICE_WAVE_IN", - 0x00000026: "FILE_DEVICE_WAVE_OUT", -} - vollog = logging.getLogger(__name__) @@ -101,97 +40,77 @@ def _generator(self) -> Iterator[Tuple]: self.config["kernel"], ): try: - try: - driver_name = driver.DriverName.get_string() - except (ValueError, exceptions.InvalidAddressException): - vollog.log( - constants.LOGLEVEL_VVVV, - f"Failed to get Driver name : {driver.vol.offset:x}", - ) - driver_name = renderers.UnparsableValue() - - yield ( - 0, - ( - format_hints.Hex(driver.vol.offset), - "DRV", - driver_name, - renderers.NotApplicableValue(), - renderers.NotApplicableValue(), - renderers.NotApplicableValue(), - ), - ) - - # Scan to get the device information of driver. - for device in driver.get_devices(): - for level, ( - offset, - drv_name, - dev_name, - att_drv_name, - dev_typ, - ) in self._traverse_device_stack(device, driver_name, 1): - yield level, ( - offset, - "DEV" if level == 1 else "ATT", - drv_name, - dev_name, - att_drv_name, - dev_typ, - ) - - except exceptions.InvalidAddressException: - vollog.log( - constants.LOGLEVEL_VVVV, - f"Invalid address identified in drivers and devices: {driver.vol.offset:x}", - ) - continue - - @staticmethod - def _traverse_device_stack( - device: DEVICE_OBJECT, driver_name: str, level: int, seen: Set[int] = set() - ) -> Iterator[Tuple]: - while device and device.vol.offset not in seen: - seen.add(device.vol.offset) - try: - device_name = device.get_device_name() + driver_name = driver.DriverName.get_string() except (ValueError, exceptions.InvalidAddressException): vollog.log( constants.LOGLEVEL_VVVV, - f"Failed to get Device name : {device.vol.offset:x}", + f"Failed to get Driver name : {driver.vol.offset:x}", ) - device_name = renderers.UnparsableValue() - - device_type = DEVICE_CODES.get(device.DeviceType, "UNKNOWN") - - att_drv_name = device.DriverObject.DriverName.get_string() + driver_name = renderers.UnparsableValue() yield ( - level, + 0, ( - format_hints.Hex(device.vol.offset), + format_hints.Hex(driver.vol.offset), + "DRV", driver_name, - device_name, - att_drv_name, - device_type, + renderers.NotApplicableValue(), + renderers.NotApplicableValue(), + renderers.NotApplicableValue(), ), ) - try: - attached = device.AttachedDevice.dereference() - yield from DeviceTree._traverse_device_stack( - attached, driver_name, level + 1, seen - ) - except exceptions.InvalidAddressException: - vollog.debug( - f"Failed to dereference attached device for device at {device.vol.offset:#x}, " - "devnode may not have drivers associated with it" - ) + + # Scan to get the device information of driver. + for device in driver.get_devices(): + for level, device_entry in self._traverse_device_tree(device, 1): + try: + device_name = device.get_device_name() + except (ValueError, exceptions.InvalidAddressException): + device_name = renderers.UnparsableValue() + + try: + attached_driver_name = device.get_attached_driver_name() + except exceptions.InvalidAddressException: + attached_driver_name = renderers.UnparsableValue() + + try: + device_type = device.get_device_type() + except exceptions.InvalidAddressException: + device_type = renderers.UnparsableValue() + + yield level, ( + format_hints.Hex(device_entry.vol.offset), + "DEV" if level == 1 else "ATT", + driver_name, + device_name, + attached_driver_name, + device_type, + ) + + @classmethod + def _traverse_device_tree( + cls, device: extensions.DEVICE_OBJECT, level: int, seen: Set[int] = set() + ) -> Iterator[Tuple[int, extensions.DEVICE_OBJECT]]: + vollog.debug(f"Traversing device tree for device at {device.vol.offset:#x}") + while device and device.vol.offset not in seen: + seen.add(device.vol.offset) + + # Yield the first device and its level + yield ( + level, + device, + ) + + for attached in device.get_attached_devices(): + # Go depth-first through all of this device's child devices + yield from cls._traverse_device_tree(attached, level + 1, seen) try: + # Then move sideways to the next device in the current linked list device = device.NextDevice.dereference() except exceptions.InvalidAddressException: vollog.debug( - f"Failed to dereference next driver in linked list at {int(device.NextDevice)}, " + "Failed to dereference next driver in linked list, " "may have reached end of list" ) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 933178c914..9b2962ac06 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -22,9 +22,8 @@ from volatility3.framework.layers import intel from volatility3.framework.objects import utility from volatility3.framework.renderers import conversion -from volatility3.framework.symbols import generic +from volatility3.framework.symbols import generic, windows from volatility3.framework.symbols.windows.extensions import pool -from volatility3.framework.symbols import windows vollog = logging.getLogger(__name__) @@ -406,33 +405,129 @@ def dereference(self) -> interfaces.objects.ObjectInterface: ) +DEVICE_CODES = { + 0x00000027: "FILE_DEVICE_8042_PORT", + 0x00000032: "FILE_DEVICE_ACPI", + 0x00000029: "FILE_DEVICE_BATTERY", + 0x00000001: "FILE_DEVICE_BEEP", + 0x0000002A: "FILE_DEVICE_BUS_EXTENDER", + 0x00000002: "FILE_DEVICE_CD_ROM", + 0x00000003: "FILE_DEVICE_CD_ROM_FILE_SYSTEM", + 0x00000030: "FILE_DEVICE_CHANGER", + 0x00000004: "FILE_DEVICE_CONTROLLER", + 0x00000005: "FILE_DEVICE_DATALINK", + 0x00000006: "FILE_DEVICE_DFS", + 0x00000035: "FILE_DEVICE_DFS_FILE_SYSTEM", + 0x00000036: "FILE_DEVICE_DFS_VOLUME", + 0x00000007: "FILE_DEVICE_DISK", + 0x00000008: "FILE_DEVICE_DISK_FILE_SYSTEM", + 0x00000033: "FILE_DEVICE_DVD", + 0x00000009: "FILE_DEVICE_FILE_SYSTEM", + 0x0000003A: "FILE_DEVICE_FIPS", + 0x00000034: "FILE_DEVICE_FULLSCREEN_VIDEO", + 0x0000000A: "FILE_DEVICE_INPORT_PORT", + 0x0000000B: "FILE_DEVICE_KEYBOARD", + 0x0000002F: "FILE_DEVICE_KS", + 0x00000039: "FILE_DEVICE_KSEC", + 0x0000000C: "FILE_DEVICE_MAILSLOT", + 0x0000002D: "FILE_DEVICE_MASS_STORAGE", + 0x0000000D: "FILE_DEVICE_MIDI_IN", + 0x0000000E: "FILE_DEVICE_MIDI_OUT", + 0x0000002B: "FILE_DEVICE_MODEM", + 0x0000000F: "FILE_DEVICE_MOUSE", + 0x00000010: "FILE_DEVICE_MULTI_UNC_PROVIDER", + 0x00000011: "FILE_DEVICE_NAMED_PIPE", + 0x00000012: "FILE_DEVICE_NETWORK", + 0x00000013: "FILE_DEVICE_NETWORK_BROWSER", + 0x00000014: "FILE_DEVICE_NETWORK_FILE_SYSTEM", + 0x00000028: "FILE_DEVICE_NETWORK_REDIRECTOR", + 0x00000015: "FILE_DEVICE_NULL", + 0x00000016: "FILE_DEVICE_PARALLEL_PORT", + 0x00000017: "FILE_DEVICE_PHYSICAL_NETCARD", + 0x00000018: "FILE_DEVICE_PRINTER", + 0x00000019: "FILE_DEVICE_SCANNER", + 0x0000001C: "FILE_DEVICE_SCREEN", + 0x00000037: "FILE_DEVICE_SERENUM", + 0x0000001A: "FILE_DEVICE_SERIAL_MOUSE_PORT", + 0x0000001B: "FILE_DEVICE_SERIAL_PORT", + 0x00000031: "FILE_DEVICE_SMARTCARD", + 0x0000002E: "FILE_DEVICE_SMB", + 0x0000001D: "FILE_DEVICE_SOUND", + 0x0000001E: "FILE_DEVICE_STREAMS", + 0x0000001F: "FILE_DEVICE_TAPE", + 0x00000020: "FILE_DEVICE_TAPE_FILE_SYSTEM", + 0x00000038: "FILE_DEVICE_TERMSRV", + 0x00000021: "FILE_DEVICE_TRANSPORT", + 0x00000022: "FILE_DEVICE_UNKNOWN", + 0x0000002C: "FILE_DEVICE_VDM", + 0x00000023: "FILE_DEVICE_VIDEO", + 0x00000024: "FILE_DEVICE_VIRTUAL_DISK", + 0x00000025: "FILE_DEVICE_WAVE_IN", + 0x00000026: "FILE_DEVICE_WAVE_OUT", +} + + class DEVICE_OBJECT(objects.StructType, pool.ExecutiveObject): """A class for kernel device objects.""" def get_device_name(self) -> str: - """Get device's name from the object header.""" + """Get device's name from the object header. + + Raises: + ValueError if the device has no _OBJECT_HEADER_NAME_INFO member + InvalidAddressException if the name cannot be dereferenced due to smear/swapped pages + + """ header = self.get_object_header() return header.NameInfo.Name.String # type: ignore - def get_attached_devices(self) -> Generator[ObjectInterface, None, None]: - """Enumerate the attached device's objects""" + def get_attached_driver_name(self) -> str: + """Gets the name of the driver that is attached to this device. + + Raises: + InvalidAddressException if the name cannot be dereferenced due to smear/swapped pages + """ + return self.DriverObject.DriverName.get_string() + + def get_device_type(self) -> str: + """Gets the device type as a readable string. + + Raises: + InvalidAddressException if the DeviceType member cannot be read. + """ + return DEVICE_CODES.get(self.DeviceType, "UNKNOWN") + + def get_attached_devices(self) -> Iterator["DEVICE_OBJECT"]: + """Enumerate the attached device's objects. + + This only yields devices that are direct children of this devnode. In order to visit + nodes farther down the tree, this method must be called on each of those children + (and their children, and so on) to fully enumerate the device subtree. Child enumeration + will stop if a device is seen twice or at the first InvalidAddressException, but the + InvalidAddressException is not raised to the calling function. + """ seen = set() try: device = self.AttachedDevice.dereference() except exceptions.InvalidAddressException: + vollog.debug( + f"No attached device dereferenced for DEVICE_OBJECT at {self.vol.offset:#x}" + ) return - while device: - if device.vol.offset in seen: - break + while device and device.vol.offset not in seen: seen.add(device.vol.offset) yield device try: - device = device.AttachedDevice.dereference() + device = device.NextDevice.dereference() except exceptions.InvalidAddressException: + vollog.debug( + f"Failed to dereference next device " + f"for device at {device.vol.offset:#x}, may have reached list end" + ) return