From 2795c7cdd2ad2899508faf423656581958eadec0 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Wed, 19 Mar 2025 15:36:18 -0500 Subject: [PATCH 1/7] Windows: Fix raw Dpc offset calculation The original code was still returning this as a pointer that ended up dereferenced in later steps. However, this pointer value actually needs to be cast to an `unsigned long long` and decoded first. --- .../symbols/windows/extensions/__init__.py | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 933178c91..091d6ceb5 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -22,9 +22,8 @@ from volatility3.framework.interfaces.objects import ObjectInterface 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__) @@ -1222,17 +1221,13 @@ class KTIMER(objects.StructType): return "-" def get_raw_dpc(self): - """Returns the encoded DPC since it may not look like a pointer after encoding""" - symbol_table_name = self.get_symbol_table_name() - pointer_type = self._context.symbol_space.get_type( - symbol_table_name + constants.BANG + "pointer" - ) - - return self._context.object( - object_type=pointer_type, - layer_name=self.vol.layer_name, - offset=self.Dpc.vol.offset, - ) + """Returns the encoded DPC as an unsigned long long since the pointer is actually encoded""" + if symbols.symbol_table_is_64bit( + context=self._context, symbol_table_name=self.get_symbol_table_name() + ): + return self.Dpc.cast("unsigned long long") + else: + return self.Dpc.cast("unsigned long") def valid_type(self): return self.Header.Type in self.VALID_TYPES From 7b9fb916722a0678b666be0f02dd59b13c769951 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Thu, 20 Mar 2025 14:23:24 -0500 Subject: [PATCH 2/7] Objects: create `get_raw_value()` method for Pointer This creates a `get_raw_value()` method for the `Pointer` class that allows users to access the raw (unmasked) value of a pointer. This was required in order to decode the encoded `Dpc` pointer that is part of the `_KTIMER` Windows type. Addition of this type was favored over a cast to `unsigned long` or `unsigned long long` due to the potential for future instability of this type due to compiler changes. See https://github.com/volatilityfoundation/volatility3/issues/1041 for further discussion around the conversion of `log unsigned int` to `unsigned long` in `clang`. See https://github.com/volatilityfoundation/volatility3/pull/1177#discussion_r1650049299 for the original discussion around how to access this pointer in the `Timers` plugin. --- volatility3/framework/objects/__init__.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/volatility3/framework/objects/__init__.py b/volatility3/framework/objects/__init__.py index 39ce6f59f..9dd30db63 100644 --- a/volatility3/framework/objects/__init__.py +++ b/volatility3/framework/objects/__init__.py @@ -410,6 +410,19 @@ class Pointer(Integer): value = int.from_bytes(data, byteorder=endian, signed=signed) return value & mask + def get_raw_value(self) -> int: + formats = { + 4: "I", + 8: "Q", + } + length = self.vol.data_format.length + endian = self.vol.data_format.byteorder + raw_data = self._context.layers[self.vol.layer_name].read( + self.vol.offset, length + ) + struct_format = ("<" if endian == "little" else ">") + formats[length] + return struct.unpack(struct_format, raw_data)[0] + def dereference( self, layer_name: Optional[str] = None ) -> interfaces.objects.ObjectInterface: From a8ea3aae011827b174760ecfa05de42a49e33fca Mon Sep 17 00:00:00 2001 From: David McDonald Date: Thu, 20 Mar 2025 14:32:04 -0500 Subject: [PATCH 3/7] Extensions: Removes the `get_raw_dpc` method from `KTIMER` This removes the `get_raw_dpc` method from the `KTIMER` extension class. This method was inaccurate in that it actually returns the masked pointer value instead of the full 64-bit value encoded in that member, which is required in order to correctly decode the 'real' pointer. The invocation of `get_raw_dpc()` was replaced with `self.Dpc.get_raw_value()`, which was added in the previous commit. --- .../framework/symbols/windows/extensions/__init__.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 091d6ceb5..75608cfc6 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -1220,15 +1220,6 @@ class KTIMER(objects.StructType): return "Yes" return "-" - def get_raw_dpc(self): - """Returns the encoded DPC as an unsigned long long since the pointer is actually encoded""" - if symbols.symbol_table_is_64bit( - context=self._context, symbol_table_name=self.get_symbol_table_name() - ): - return self.Dpc.cast("unsigned long long") - else: - return self.Dpc.cast("unsigned long") - def valid_type(self): return self.Header.Type in self.VALID_TYPES @@ -1263,7 +1254,7 @@ class KTIMER(objects.StructType): ) low_byte = (wait_never) & 0xFF - entry = utility.rol(self.get_raw_dpc() ^ wait_never, low_byte) + entry = utility.rol(self.Dpc.get_raw_value() ^ wait_never, low_byte) swap_xor = self._context.layers[self.vol.native_layer_name].canonicalize( self.vol.offset ) From 144fd3139ae18a4aa785c5e4df3c323b24a67692 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Thu, 20 Mar 2025 14:38:19 -0500 Subject: [PATCH 4/7] Framework: Minor version bump Made an additive change to `Pointer` by adding the `get_raw_value()` method, so bumping the minor version here. The `get_raw_dpc()` method was removed from the `KTIMER` extension class, which is currently unversioned. --- volatility3/framework/constants/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/constants/_version.py b/volatility3/framework/constants/_version.py index 1ea59c068..f5da4c75b 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 = 24 # Number of changes that only add to the interface +VERSION_MINOR = 25 # Number of changes that only add to the interface VERSION_PATCH = 0 # Number of changes that do not change the interface VERSION_SUFFIX = "" From 1e175b5d3bf25dbc674bc2bda800c82b737cca70 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Thu, 20 Mar 2025 15:11:37 -0500 Subject: [PATCH 5/7] Objects: rework new `get_raw_value()` method Per code review recommendations, splits the `_unmarshall` classmethod into two components, one of which retrieves the raw value, and the other that returns the masked pointer. The `get_raw_value` method now calls the `_get_raw_value` classmethod using its instance information. --- volatility3/framework/objects/__init__.py | 35 ++++++++++++++--------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/volatility3/framework/objects/__init__.py b/volatility3/framework/objects/__init__.py index 9dd30db63..b863e103b 100644 --- a/volatility3/framework/objects/__init__.py +++ b/volatility3/framework/objects/__init__.py @@ -402,26 +402,35 @@ class Pointer(Integer): pointer should be recast. The "pointer" must always live within the space (even if the data provided is invalid). """ + mask = context.layers[object_info.native_layer_name].address_mask + new = ( + cls._get_raw_value( + context, data_format, object_info.layer_name, object_info.offset + ) + & mask + ) + return new + + @classmethod + def _get_raw_value( + cls, + context: interfaces.context.ContextInterface, + data_format: DataFormatInfo, + layer_name: str, + offset: int, + ) -> int: length, endian, signed = data_format if signed: raise ValueError("Pointers cannot have signed values") - mask = context.layers[object_info.native_layer_name].address_mask - data = context.layers.read(object_info.layer_name, object_info.offset, length) + data = context.layers.read(layer_name, offset, length) value = int.from_bytes(data, byteorder=endian, signed=signed) - return value & mask + return value def get_raw_value(self) -> int: - formats = { - 4: "I", - 8: "Q", - } - length = self.vol.data_format.length - endian = self.vol.data_format.byteorder - raw_data = self._context.layers[self.vol.layer_name].read( - self.vol.offset, length + raw = self._get_raw_value( + self._context, self.vol.data_format, self.vol.layer_name, self.vol.offset ) - struct_format = ("<" if endian == "little" else ">") + formats[length] - return struct.unpack(struct_format, raw_data)[0] + return raw def dereference( self, layer_name: Optional[str] = None From c4589a51d51d441838812f32ef1a97b9c328d8dc Mon Sep 17 00:00:00 2001 From: David McDonald Date: Thu, 20 Mar 2025 15:13:26 -0500 Subject: [PATCH 6/7] Timers: Adds debug log statement to catch-all exception --- volatility3/framework/plugins/windows/timers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/windows/timers.py b/volatility3/framework/plugins/windows/timers.py index 1f100bf1c..07313c004 100644 --- a/volatility3/framework/plugins/windows/timers.py +++ b/volatility3/framework/plugins/windows/timers.py @@ -131,6 +131,7 @@ class Timers(interfaces.plugins.PluginInterface): ): if not timer.valid_type(): continue + try: dpc = timer.get_dpc() if dpc == 0: @@ -138,7 +139,10 @@ class Timers(interfaces.plugins.PluginInterface): if dpc.DeferredRoutine == 0: continue deferred_routine = dpc.DeferredRoutine - except Exception: + except Exception as exc: + vollog.debug( + f"Failed to get _KTIMER.Dpc: {exc.__class__.__name__} {str(exc)}" + ) continue module_symbols = list( From d097d6abeb278de346601c6ee7570aa5383bbb73 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Thu, 20 Mar 2025 15:18:08 -0500 Subject: [PATCH 7/7] Timers: convert general Exception to InvalidAddressException --- volatility3/framework/plugins/windows/timers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/windows/timers.py b/volatility3/framework/plugins/windows/timers.py index 07313c004..f530a4c7b 100644 --- a/volatility3/framework/plugins/windows/timers.py +++ b/volatility3/framework/plugins/windows/timers.py @@ -11,6 +11,7 @@ from volatility3.framework import ( interfaces, constants, symbols, + exceptions, ) from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints @@ -139,9 +140,9 @@ class Timers(interfaces.plugins.PluginInterface): if dpc.DeferredRoutine == 0: continue deferred_routine = dpc.DeferredRoutine - except Exception as exc: + except exceptions.InvalidAddressException as exc: vollog.debug( - f"Failed to get _KTIMER.Dpc: {exc.__class__.__name__} {str(exc)}" + f"Failed to get _KTIMER.Dpc due to {exc.__class__.__name__}" ) continue