From 12713c4e6f9cc794156719773c9adc949adab10f Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 10 Jun 2026 15:59:04 +0200 Subject: [PATCH 1/4] verify windows layers using translation checks --- volatility3/framework/automagic/windows.py | 65 ++++++++++++++-------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/volatility3/framework/automagic/windows.py b/volatility3/framework/automagic/windows.py index 1627f7c1a8..9bf98e519b 100644 --- a/volatility3/framework/automagic/windows.py +++ b/volatility3/framework/automagic/windows.py @@ -29,15 +29,43 @@ import logging import struct -from typing import Generator, Iterable, List, Optional, Tuple, Type +from typing import Generator, Iterable, List, Optional, Tuple, Type, Union -from volatility3.framework import constants, interfaces, layers +from volatility3.framework import constants, exceptions, interfaces, layers from volatility3.framework.configuration import requirements from volatility3.framework.layers import intel vollog = logging.getLogger(__name__) +class Intel32LayerCheck: + KUSER_USER_SPACE_ADDR = 0x7FFE0000 + KUSER_KERNEL_SPACE_ADDR = 0xFFDF0000 + + @classmethod + def check(cls, layer: Union[intel.Intel, intel.IntelPAE]): + """Generates a single response of True or False depending on whether the space is a valid Windows AS""" + # This constraint verifies that _KUSER_SHARED_DATA is shared + # between user and kernel address spaces. + try: + uaddr = layer._translate(cls.KUSER_USER_SPACE_ADDR)[0] + kaddr = layer._translate(cls.KUSER_KERNEL_SPACE_ADDR)[0] + if kaddr != 0 and kaddr == uaddr: + return True + except ( + exceptions.PagedInvalidAddressException, + exceptions.InvalidAddressException, + ): + pass + + return False + + +class IntelLayerCheck(Intel32LayerCheck): + KUSER_USER_SPACE_ADDR = 0x7FFE0000 + KUSER_KERNEL_SPACE_ADDR = 0xFFFFF78000000000 + + class DtbSelfReferential: """A generic DTB test which looks for a self-referential pointer at *any* index within the page.""" @@ -45,12 +73,14 @@ class DtbSelfReferential: def __init__( self, layer_type: Type[layers.intel.Intel], + layer_check: Union[Intel32LayerCheck.check, IntelLayerCheck.check], ptr_struct: str, mask: int, valid_range: Iterable[int], reserved_bits: int, ) -> None: self.layer_type = layer_type + self.layer_check = layer_check self.ptr_struct = ptr_struct self.ptr_size = struct.calcsize(ptr_struct) self.mask = mask @@ -92,6 +122,7 @@ class DtbSelfRef32bit(DtbSelfReferential): def __init__(self): super().__init__( layer_type=layers.intel.WindowsIntel, + layer_check=Intel32LayerCheck.check, ptr_struct="I", mask=0xFFFFF000, valid_range=[0x300], @@ -103,6 +134,7 @@ class DtbSelfRef64bit(DtbSelfReferential): def __init__(self) -> None: super().__init__( layer_type=layers.intel.WindowsIntel32e, + layer_check=IntelLayerCheck.check, ptr_struct="Q", mask=0x3FFFFFFFFFF000, valid_range=range(0x100, 0x1FF), @@ -114,6 +146,7 @@ class DtbSelfRef64bitOldWindows(DtbSelfReferential): def __init__(self) -> None: super().__init__( layer_type=layers.intel.WindowsIntel32e, + layer_check=IntelLayerCheck.check, ptr_struct="Q", mask=0x3FFFFFFFFFF000, valid_range=[0x1ED], @@ -125,6 +158,7 @@ class DtbSelfRefPae(DtbSelfReferential): def __init__(self) -> None: super().__init__( layer_type=layers.intel.WindowsIntelPAE, + layer_check=Intel32LayerCheck.check, ptr_struct="Q", valid_range=[0x3], mask=0x3FFFFFFFFFF000, @@ -317,18 +351,6 @@ def get_max_pointer(page_table, test, ptr_size: int): ) return max_ptr - def page_table_is_dummy(page_table, ptr_size: int): - """Verify that a page table has at least 12 valid pointers""" - valid_pointers = 0 - for _ in get_valid_page_table_pointers(page_table, ptr_size): - valid_pointers += 1 - # 10 is an arbitrary constant - if valid_pointers >= 10: - # Do not consume the entire generator to enhance performance - return False - vollog.debug(f"Found {valid_pointers} valid pointers") - return True - hits = sorted(list(hits), key=sort_by_tests) vollog.debug(f"WindowsIntelStacker hits: {hits}") @@ -337,13 +359,6 @@ def page_table_is_dummy(page_table, ptr_size: int): # Turn the page tables into integers and find the largest one page_table = base_layer.read(page_map_offset, 0x1000) ptr_size = struct.calcsize(test.ptr_struct) - # Modern windows can have a dummy page table with only about 2 entries, so sanity check - if page_table_is_dummy(page_table, ptr_size): - vollog.debug( - f"DTB {page_map_offset:x} contains less than 12 valid pointers, ignoring" - ) - continue - max_pointer = get_max_pointer(page_table, test, ptr_size) if max_pointer <= base_layer.maximum_address: @@ -362,12 +377,18 @@ def page_table_is_dummy(page_table, ptr_size: int): config_path, "page_map_offset" ) ] = page_map_offset - layer = test.layer_type( + tmp_layer = test.layer_type( context, config_path=config_path, name=new_layer_name, metadata={"os": "Windows"}, ) + if not test.layer_check(tmp_layer): + vollog.debug( + f"DTB {page_map_offset:x} failed {test.layer_type.__name__} _KUSER_SHARED_DATA check, ignoring" + ) + continue + layer = tmp_layer break else: vollog.debug( From 3965376ddc0cd1da6f773b587b0961fe59f4d510 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 10 Jun 2026 17:01:17 +0200 Subject: [PATCH 2/4] rename IntelLayerCheck to Intel64LayerCheck --- volatility3/framework/automagic/windows.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/automagic/windows.py b/volatility3/framework/automagic/windows.py index 9bf98e519b..c0c669a1bc 100644 --- a/volatility3/framework/automagic/windows.py +++ b/volatility3/framework/automagic/windows.py @@ -43,7 +43,7 @@ class Intel32LayerCheck: KUSER_KERNEL_SPACE_ADDR = 0xFFDF0000 @classmethod - def check(cls, layer: Union[intel.Intel, intel.IntelPAE]): + def check(cls, layer: intel.Intel): """Generates a single response of True or False depending on whether the space is a valid Windows AS""" # This constraint verifies that _KUSER_SHARED_DATA is shared # between user and kernel address spaces. @@ -61,7 +61,7 @@ def check(cls, layer: Union[intel.Intel, intel.IntelPAE]): return False -class IntelLayerCheck(Intel32LayerCheck): +class Intel64LayerCheck(Intel32LayerCheck): KUSER_USER_SPACE_ADDR = 0x7FFE0000 KUSER_KERNEL_SPACE_ADDR = 0xFFFFF78000000000 @@ -73,7 +73,7 @@ class DtbSelfReferential: def __init__( self, layer_type: Type[layers.intel.Intel], - layer_check: Union[Intel32LayerCheck.check, IntelLayerCheck.check], + layer_check: Union[Intel32LayerCheck.check, Intel64LayerCheck.check], ptr_struct: str, mask: int, valid_range: Iterable[int], @@ -134,7 +134,7 @@ class DtbSelfRef64bit(DtbSelfReferential): def __init__(self) -> None: super().__init__( layer_type=layers.intel.WindowsIntel32e, - layer_check=IntelLayerCheck.check, + layer_check=Intel64LayerCheck.check, ptr_struct="Q", mask=0x3FFFFFFFFFF000, valid_range=range(0x100, 0x1FF), @@ -146,7 +146,7 @@ class DtbSelfRef64bitOldWindows(DtbSelfReferential): def __init__(self) -> None: super().__init__( layer_type=layers.intel.WindowsIntel32e, - layer_check=IntelLayerCheck.check, + layer_check=Intel64LayerCheck.check, ptr_struct="Q", mask=0x3FFFFFFFFFF000, valid_range=[0x1ED], From 536aae7d29e04e8c17857b87fbeef43f90b0e653 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 10 Jun 2026 17:02:23 +0200 Subject: [PATCH 3/4] add comment to pass statement --- volatility3/framework/automagic/windows.py | 1 + 1 file changed, 1 insertion(+) diff --git a/volatility3/framework/automagic/windows.py b/volatility3/framework/automagic/windows.py index c0c669a1bc..a665a8bb31 100644 --- a/volatility3/framework/automagic/windows.py +++ b/volatility3/framework/automagic/windows.py @@ -56,6 +56,7 @@ def check(cls, layer: intel.Intel): exceptions.PagedInvalidAddressException, exceptions.InvalidAddressException, ): + # Translation failed, caller will log this globally pass return False From 95e69ec92e5de889dfa10b27e8c365637fc8e681 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Thu, 11 Jun 2026 18:26:55 +0200 Subject: [PATCH 4/4] add secondary check after mass testing review --- volatility3/framework/automagic/windows.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/automagic/windows.py b/volatility3/framework/automagic/windows.py index a665a8bb31..f5973228fc 100644 --- a/volatility3/framework/automagic/windows.py +++ b/volatility3/framework/automagic/windows.py @@ -41,29 +41,44 @@ class Intel32LayerCheck: KUSER_USER_SPACE_ADDR = 0x7FFE0000 KUSER_KERNEL_SPACE_ADDR = 0xFFDF0000 + # This field offset did not change across Windows versions. + # Instead of storing a complete struct definition only for this check, + # define it locally here. + KUSER_SHARED_DATA_NTMAJOR_OFF = 0x26C + NT_MAJOR_VALIDS = [3, 4, 5, 6, 10] @classmethod def check(cls, layer: intel.Intel): """Generates a single response of True or False depending on whether the space is a valid Windows AS""" # This constraint verifies that _KUSER_SHARED_DATA is shared # between user and kernel address spaces. + kaddr = uaddr = None try: - uaddr = layer._translate(cls.KUSER_USER_SPACE_ADDR)[0] kaddr = layer._translate(cls.KUSER_KERNEL_SPACE_ADDR)[0] + uaddr = layer._translate(cls.KUSER_USER_SPACE_ADDR)[0] if kaddr != 0 and kaddr == uaddr: return True except ( exceptions.PagedInvalidAddressException, exceptions.InvalidAddressException, ): - # Translation failed, caller will log this globally + # Translation failed, most likely because of UADDR pass + # Validate by reading the _KUSER_SHARED_DATA.NtMajorVersion field + if kaddr is not None: + data = layer.read( + cls.KUSER_KERNEL_SPACE_ADDR + cls.KUSER_SHARED_DATA_NTMAJOR_OFF, + 4, + pad=True, + ) + if struct.unpack("