From 512c1abe801b731f9f36551db51075d4d41413e1 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 4 Nov 2024 17:45:56 +1100 Subject: [PATCH 01/19] linux: VMCoreInfo plugin: Add VMCoreInfo API and its respective plugin --- .../framework/constants/linux/__init__.py | 6 ++ .../framework/plugins/linux/vmcoreinfo.py | 49 +++++++++ .../framework/symbols/linux/__init__.py | 102 +++++++++++++++++- 3 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 volatility3/framework/plugins/linux/vmcoreinfo.py diff --git a/volatility3/framework/constants/linux/__init__.py b/volatility3/framework/constants/linux/__init__.py index 7c485d3c39..dde256754e 100644 --- a/volatility3/framework/constants/linux/__init__.py +++ b/volatility3/framework/constants/linux/__init__.py @@ -347,3 +347,9 @@ def flags(self) -> str: MODULE_MAXIMUM_CORE_SIZE = 20000000 MODULE_MAXIMUM_CORE_TEXT_SIZE = 20000000 MODULE_MINIMUM_SIZE = 4096 + +# VMCOREINFO +VMCOREINFO_MAGIC = b"VMCOREINFO\x00" +# Aligned to 4 bytes. See storenote() in kernels < 4.19 or append_kcore_note() in kernels >= 4.19 +VMCOREINFO_MAGIC_ALIGNED = VMCOREINFO_MAGIC + b"\x00" +OSRELEASE_TAG = b"OSRELEASE=" diff --git a/volatility3/framework/plugins/linux/vmcoreinfo.py b/volatility3/framework/plugins/linux/vmcoreinfo.py new file mode 100644 index 0000000000..ea30520e35 --- /dev/null +++ b/volatility3/framework/plugins/linux/vmcoreinfo.py @@ -0,0 +1,49 @@ +# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import List + +from volatility3.framework import renderers, interfaces +from volatility3.framework.configuration import requirements +from volatility3.framework.interfaces import plugins +from volatility3.framework.symbols import linux +from volatility3.framework.renderers import format_hints + + +class VMCoreInfo(plugins.PluginInterface): + """Enumerate VMCoreInfo tables""" + + _required_framework_version = (2, 11, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement( + name="primary", description="Memory layer to scan" + ), + requirements.VersionRequirement( + name="VMCoreInfo", component=linux.VMCoreInfo, version=(1, 0, 0) + ), + ] + + def _generator(self): + layer_name = self.config["primary"] + for ( + vmcoreinfo_offset, + vmcoreinfo, + ) in linux.VMCoreInfo.search_vmcoreinfo_elf_note( + context=self.context, + layer_name=layer_name, + ): + for key, value in vmcoreinfo.items(): + yield 0, (format_hints.Hex(vmcoreinfo_offset), key, value) + + def run(self): + headers = [ + ("Offset", format_hints.Hex), + ("Key", str), + ("Value", str), + ] + return renderers.TreeGrid(headers, self._generator()) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 3289775b6e..b0b6b7325f 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -2,15 +2,18 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # import math +import string import contextlib from abc import ABC, abstractmethod -from typing import Iterator, List, Tuple, Optional, Union +from typing import Iterator, List, Tuple, Optional, Union, Dict from volatility3 import framework from volatility3.framework import constants, exceptions, interfaces, objects from volatility3.framework.objects import utility from volatility3.framework.symbols import intermed from volatility3.framework.symbols.linux import extensions +from volatility3.framework.layers import scanners +from volatility3.framework.constants import linux as linux_constants class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable): @@ -832,3 +835,100 @@ def get_cached_pages(self) -> Iterator[interfaces.objects.ObjectInterface]: page = self.vmlinux.object("page", offset=page_addr, absolute=True) if page: yield page + + +class VMCoreInfo(interfaces.configuration.VersionableInterface): + _required_framework_version = (2, 11, 0) + + _version = (1, 0, 0) + + @staticmethod + def _vmcoreinfo_data_to_dict( + vmcoreinfo_data, + ) -> Optional[Dict[str, str]]: + """Converts the input VMCoreInfo data buffer into a dictionary""" + + # Ensure the whole buffer is printable + if not all(c in string.printable.encode() for c in vmcoreinfo_data): + # Abort, we are in the wrong place + return None + + vmcoreinfo_dict = dict() + for line in vmcoreinfo_data.decode().splitlines(): + if not line: + break + + key, value = line.split("=", 1) + vmcoreinfo_dict[key] = value + + return vmcoreinfo_dict + + @classmethod + def search_vmcoreinfo_elf_note( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + progress_callback: constants.ProgressCallback = None, + ) -> Iterator[Tuple[int, Dict[str, str]]]: + """Enumerates each VMCoreInfo ELF note table found in memory along with its offset. + + This approach is independent of any external ISF symbol or type, requiring only the + Elf64_Note found in 'elf.json', which is already included in the framework. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The layer within the context in which the module exists + progress_callback: A function that takes a percentage (and an optional description) that will be called periodically + + Yields: + Tuples with the VMCoreInfo ELF note offset and the VMCoreInfo table parsed in a dictionary. + """ + + elf_table_name = intermed.IntermediateSymbolTable.create( + context, "elf_symbol_table", "linux", "elf" + ) + module = context.module(elf_table_name, layer_name, 0) + layer = context.layers[layer_name] + + # Both Elf32_Note and Elf64_Note are of the same size + elf_note_size = context.symbol_space[elf_table_name].get_type("Elf64_Note").size + + for vmcoreinfo_offset in layer.scan( + scanner=scanners.BytesScanner(linux_constants.VMCOREINFO_MAGIC_ALIGNED), + context=context, + progress_callback=progress_callback, + ): + # vmcoreinfo_note kernels >= 2.6.24 fd59d231f81cb02870b9cf15f456a897f3669b4e + vmcoreinfo_elf_note_offset = vmcoreinfo_offset - elf_note_size + + # Elf32_Note and Elf64_Note are identical, so either can be used interchangeably here + elf_note = module.object( + object_type="Elf64_Note", + offset=vmcoreinfo_elf_note_offset, + absolute=True, + ) + + # Ensure that we are within an ELF note + if ( + elf_note.n_namesz != len(linux_constants.VMCOREINFO_MAGIC) + or elf_note.n_type != 0 + or elf_note.n_descsz == 0 + ): + continue + + vmcoreinfo_data_offset = vmcoreinfo_offset + len( + linux_constants.VMCOREINFO_MAGIC_ALIGNED + ) + + # Also, confirm this with the first tag, which has consistently been OSRELEASE + vmcoreinfo_data = layer.read(vmcoreinfo_data_offset, elf_note.n_descsz) + if not vmcoreinfo_data.startswith(linux_constants.OSRELEASE_TAG): + continue + + table = cls._vmcoreinfo_data_to_dict(vmcoreinfo_data) + if not table: + # Wrong VMCoreInfo note offset, keep trying + continue + + # A valid VMCoreInfo ELF note exists at 'vmcoreinfo_elf_note_offset' + yield vmcoreinfo_elf_note_offset, table From b4b78342763e743b25828bb6d684ba5e1be5f1d1 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 4 Nov 2024 17:47:57 +1100 Subject: [PATCH 02/19] linux: K/ASLR via VMCoreInfo --- volatility3/framework/automagic/linux.py | 108 ++++++++++++++++++++++- 1 file changed, 105 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index 52a73f45a8..fde0607331 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -76,8 +76,12 @@ def stack( isf_url=isf_path, ) context.symbol_space.append(table) + kaslr_shift, aslr_shift = cls.find_aslr( - context, table_name, layer_name, progress_callback=progress_callback + context, + table_name, + layer_name, + progress_callback=progress_callback, ) layer_class: Type = intel.Intel @@ -118,7 +122,7 @@ def stack( return None @classmethod - def find_aslr( + def find_aslr_classic( cls, context: interfaces.context.ContextInterface, symbol_table: str, @@ -126,7 +130,17 @@ def find_aslr( progress_callback: constants.ProgressCallback = None, ) -> Tuple[int, int]: """Determines the offset of the actual DTB in physical space and its - symbol offset.""" + symbol offset. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + symbol_table: The name of the kernel module on which to operate + layer_name: The layer within the context in which the module exists + progress_callback: A function that takes a percentage (and an optional description) that will be called periodically + + Returns: + kaslr_shirt and aslr_shift + """ init_task_symbol = symbol_table + constants.BANG + "init_task" init_task_json_address = context.symbol_space.get_symbol( init_task_symbol @@ -184,6 +198,59 @@ def find_aslr( vollog.debug("Scanners could not determine any ASLR shifts, using 0 for both") return 0, 0 + @classmethod + def find_aslr_vmcoreinfo( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + progress_callback: constants.ProgressCallback = None, + ) -> Optional[Tuple[int, int]]: + """Determines the ASLR offsets using the VMCOREINFO ELF note + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The layer within the context in which the module exists + progress_callback: A function that takes a percentage (and an optional description) that will be called periodically + + Returns: + kaslr_shirt and aslr_shift + """ + + for ( + _vmcoreinfo_offset, + vmcoreinfo, + ) in linux.VMCoreInfo.search_vmcoreinfo_elf_note( + context=context, + layer_name=layer_name, + progress_callback=progress_callback, + ): + + phys_base_str = vmcoreinfo.get("NUMBER(phys_base)") + if phys_base_str is None: + # We are in kernel (x86) < 4.10 401721ecd1dcb0a428aa5d6832ee05ffbdbffbbe where it was SYMBOL(phys_base) + # It's the symbol address instead of the value itself, which is useless for calculating the physical address. + # raise Exception("Kernel < 4.10") + continue + + kerneloffset_str = vmcoreinfo.get("KERNELOFFSET") + if kerneloffset_str is None: + # KERNELOFFSET: (x86) kernels < 3.13 b6085a865762236bb84934161273cdac6dd11c2d + continue + + aslr_shift = int(kerneloffset_str, 16) + kaslr_shift = int(phys_base_str) + aslr_shift + + vollog.debug( + "Linux ASLR shift values found in VMCOREINFO ELF note: physical 0x%x virtual 0x%x", + kaslr_shift, + aslr_shift, + ) + + return kaslr_shift, aslr_shift + + vollog.debug("The vmcoreinfo scanner could not determine any ASLR shifts") + return None + @classmethod def virtual_to_physical_address(cls, addr: int) -> int: """Converts a virtual linux address to a physical one (does not account @@ -192,6 +259,41 @@ def virtual_to_physical_address(cls, addr: int) -> int: return addr - 0xFFFFFFFF80000000 return addr - 0xC0000000 + @classmethod + def find_aslr( + cls, + context: interfaces.context.ContextInterface, + symbol_table: str, + layer_name: str, + progress_callback: constants.ProgressCallback = None, + ) -> Tuple[int, int]: + """Determines the offset of the actual DTB in physical space and its + symbol offset. + Args: + context: The context to retrieve required elements (layers, symbol tables) from + symbol_table: The name of the kernel module on which to operate + layer_name: The layer within the context in which the module exists + progress_callback: A function that takes a percentage (and an optional description) that will be called periodically + + Returns: + kaslr_shirt and aslr_shift + """ + + aslr_shifts = cls.find_aslr_vmcoreinfo( + context, layer_name, progress_callback=progress_callback + ) + if aslr_shifts: + kaslr_shift, aslr_shift = aslr_shifts + else: + # Fallback to the traditional scanner method + kaslr_shift, aslr_shift = cls.find_aslr_classic( + context, + symbol_table, + layer_name, + progress_callback=progress_callback, + ) + return kaslr_shift, aslr_shift + class LinuxSymbolFinder(symbol_finder.SymbolFinder): """Linux symbol loader based on uname signature strings.""" From 6610779e5c070e7b3225b47cbb3a0980caa1881f Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 4 Nov 2024 19:30:30 +1100 Subject: [PATCH 03/19] linux: VMCoreInfo: Remove debug comment --- volatility3/framework/automagic/linux.py | 1 - 1 file changed, 1 deletion(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index fde0607331..248bbf04d9 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -229,7 +229,6 @@ def find_aslr_vmcoreinfo( if phys_base_str is None: # We are in kernel (x86) < 4.10 401721ecd1dcb0a428aa5d6832ee05ffbdbffbbe where it was SYMBOL(phys_base) # It's the symbol address instead of the value itself, which is useless for calculating the physical address. - # raise Exception("Kernel < 4.10") continue kerneloffset_str = vmcoreinfo.get("KERNELOFFSET") From 5ab97564ac025978ab3baebe143a259111d6fbd3 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Thu, 14 Nov 2024 16:37:05 +1100 Subject: [PATCH 04/19] linux: intel VMCoreInfo: Move to its own stacker... Maximize use of VMCOREINFO data without reliance on ISF symbols: - Obtain the DTB - Utilize OSRELEASE (the same as UTS_RELEASE used in the Linux banner and init_uts_ns/new_utsname) to prefilter the list of Linux banners, reducing search time for linux_banner in memory. - Find the correct layer using the VMCOREINFO data (including 32bit PAE). --- volatility3/framework/automagic/linux.py | 276 ++++++++++++++++------- 1 file changed, 199 insertions(+), 77 deletions(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index 248bbf04d9..f877d1cd00 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -122,7 +122,7 @@ def stack( return None @classmethod - def find_aslr_classic( + def find_aslr( cls, context: interfaces.context.ContextInterface, symbol_table: str, @@ -198,107 +198,229 @@ def find_aslr_classic( vollog.debug("Scanners could not determine any ASLR shifts, using 0 for both") return 0, 0 + @staticmethod + def virtual_to_physical_address(addr: int) -> int: + """Converts a virtual linux address to a physical one (does not account + of ASLR)""" + if addr > 0xFFFFFFFF80000000: + return addr - 0xFFFFFFFF80000000 + return addr - 0xC0000000 + + +class LinuxSymbolFinder(symbol_finder.SymbolFinder): + """Linux symbol loader based on uname signature strings.""" + + banner_config_key = "kernel_banner" + operating_system = "linux" + symbol_class = "volatility3.framework.symbols.linux.LinuxKernelIntermedSymbols" + find_aslr = lambda cls, *args: LinuxIntelStacker.find_aslr(*args)[1] + exclusion_list = ["mac", "windows"] + + +class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): + stack_order = 34 + exclusion_list = ["mac", "windows"] + + @staticmethod + def _check_versions() -> bool: + """Verify the versions of the required modules""" + + # Check SQlite cache version + sqlitecache_version_required = (1, 0, 0) + if not requirements.VersionRequirement.matches_required( + sqlitecache_version_required, symbol_cache.SqliteCache.version + ): + vollog.info( + "SQLiteCache version not suitable: required %s found %s", + sqlitecache_version_required, + symbol_cache.SqliteCache.version, + ) + return False + + # Check VMCOREINFO API version + vmcoreinfo_version_required = (1, 0, 0) + if not requirements.VersionRequirement.matches_required( + vmcoreinfo_version_required, linux.VMCoreInfo._version + ): + vollog.info( + "VMCOREINFO version not suitable: required %s found %s", + vmcoreinfo_version_required, + linux.VMCoreInfo._version, + ) + return False + + return True + @classmethod - def find_aslr_vmcoreinfo( + def stack( cls, context: interfaces.context.ContextInterface, layer_name: str, progress_callback: constants.ProgressCallback = None, - ) -> Optional[Tuple[int, int]]: - """Determines the ASLR offsets using the VMCOREINFO ELF note + ) -> Optional[interfaces.layers.DataLayerInterface]: + """Attempts to identify linux within this layer.""" - Args: - context: The context to retrieve required elements (layers, symbol tables) from - layer_name: The layer within the context in which the module exists - progress_callback: A function that takes a percentage (and an optional description) that will be called periodically + # Verify the versions of the required modules + if not cls._check_versions(): + return None - Returns: - kaslr_shirt and aslr_shift - """ + # Bail out by default unless we can stack properly + layer = context.layers[layer_name] + + # Never stack on top of an intel layer + # FIXME: Find a way to improve this check + if isinstance(layer, intel.Intel): + return None + + identifiers_path = os.path.join( + constants.CACHE_PATH, constants.IDENTIFIERS_FILENAME + ) + sqlite_cache = symbol_cache.SqliteCache(identifiers_path) + linux_banners = sqlite_cache.get_identifier_dictionary(operating_system="linux") + if not linux_banners: + # If we have no banners, don't bother scanning + vollog.info( + "No Linux banners found - if this is a linux plugin, please check your " + "symbol files location" + ) + return None - for ( - _vmcoreinfo_offset, - vmcoreinfo, - ) in linux.VMCoreInfo.search_vmcoreinfo_elf_note( + vmcoreinfo_elf_notes_iter = linux.VMCoreInfo.search_vmcoreinfo_elf_note( context=context, layer_name=layer_name, progress_callback=progress_callback, - ): + ) - phys_base_str = vmcoreinfo.get("NUMBER(phys_base)") - if phys_base_str is None: - # We are in kernel (x86) < 4.10 401721ecd1dcb0a428aa5d6832ee05ffbdbffbbe where it was SYMBOL(phys_base) - # It's the symbol address instead of the value itself, which is useless for calculating the physical address. + # Iterate through each VMCOREINFO ELF note found, using the first one that is valid. + for _vmcoreinfo_offset, vmcoreinfo in vmcoreinfo_elf_notes_iter: + shifts = cls._vmcoreinfo_find_aslr(vmcoreinfo) + if not shifts: + # Let's try the next vmcoreinfo, in case this one isn't correct. continue - kerneloffset_str = vmcoreinfo.get("KERNELOFFSET") - if kerneloffset_str is None: - # KERNELOFFSET: (x86) kernels < 3.13 b6085a865762236bb84934161273cdac6dd11c2d + kaslr_shift, aslr_shift = shifts + + dtb = cls._vmcoreinfo_get_dtb(vmcoreinfo, aslr_shift, kaslr_shift) + + is_32bit, is_pae = cls._vmcoreinfo_is_32bit(vmcoreinfo) + if is_32bit: + layer_class = intel.IntelPAE if is_pae else intel.Intel + else: + layer_class = intel.Intel32e + + uts_release = vmcoreinfo["OSRELEASE"] + + # See how linux_banner constant is built in the linux kernel + linux_version_prefix = f"Linux version {uts_release} (".encode() + valid_banners = [ + x for x in linux_banners if x and x.startswith(linux_version_prefix) + ] + if not valid_banners: + # There's no banner matching this VMCOREINFO, keep trying with the next one continue - aslr_shift = int(kerneloffset_str, 16) - kaslr_shift = int(phys_base_str) + aslr_shift + join = interfaces.configuration.path_join + mss = scanners.MultiStringScanner(valid_banners) + for _, banner in layer.scan( + context=context, scanner=mss, progress_callback=progress_callback + ): + isf_path = linux_banners.get(banner, None) + if not isf_path: + vollog.warning( + "Identified banner %r, but no matching ISF is available.", + banner, + ) + continue + + vollog.debug("Identified banner: %r", banner) + table_name = context.symbol_space.free_table_name("LintelStacker") + table = linux.LinuxKernelIntermedSymbols( + context, + f"temporary.{table_name}", + name=table_name, + isf_url=isf_path, + ) + context.symbol_space.append(table) - vollog.debug( - "Linux ASLR shift values found in VMCOREINFO ELF note: physical 0x%x virtual 0x%x", - kaslr_shift, - aslr_shift, - ) + # Build the new layer + new_layer_name = context.layers.free_layer_name("IntelLayer") + config_path = join("IntelHelper", new_layer_name) + kernel_banner = LinuxSymbolFinder.banner_config_key + banner_str = banner.decode(encoding="latin-1") + context.config[join(config_path, "memory_layer")] = layer_name + context.config[join(config_path, "page_map_offset")] = dtb + context.config[join(config_path, kernel_banner)] = banner_str + layer = layer_class( + context, + config_path=config_path, + name=new_layer_name, + metadata={"os": "Linux"}, + ) + layer.config["kernel_virtual_offset"] = aslr_shift - return kaslr_shift, aslr_shift + if layer and dtb: + vollog.debug( + "Values found in VMCOREINFO: KASLR=0x%x, ASLR=0x%x, DTB=0x%x", + kaslr_shift, + aslr_shift, + dtb, + ) + + return layer - vollog.debug("The vmcoreinfo scanner could not determine any ASLR shifts") + vollog.debug("No suitable linux banner could be matched") return None - @classmethod - def virtual_to_physical_address(cls, addr: int) -> int: - """Converts a virtual linux address to a physical one (does not account - of ASLR)""" - if addr > 0xFFFFFFFF80000000: - return addr - 0xFFFFFFFF80000000 - return addr - 0xC0000000 + @staticmethod + def _vmcoreinfo_find_aslr(vmcoreinfo) -> Tuple[int, int]: + phys_base_str = vmcoreinfo.get("NUMBER(phys_base)") + if phys_base_str is None: + # In kernel < 4.10, there may be a SYMBOL(phys_base), but as noted in the + # c401721ecd1dcb0a428aa5d6832ee05ffbdbffbbe commit comment, this value + # isn't useful for calculating the physical address. + # There's nothing we can do here, so let's try with the next VMCOREINFO or + # the next Stacker. + return None - @classmethod - def find_aslr( - cls, - context: interfaces.context.ContextInterface, - symbol_table: str, - layer_name: str, - progress_callback: constants.ProgressCallback = None, - ) -> Tuple[int, int]: - """Determines the offset of the actual DTB in physical space and its - symbol offset. - Args: - context: The context to retrieve required elements (layers, symbol tables) from - symbol_table: The name of the kernel module on which to operate - layer_name: The layer within the context in which the module exists - progress_callback: A function that takes a percentage (and an optional description) that will be called periodically + # kernels 3.14 (b6085a865762236bb84934161273cdac6dd11c2d) KERNELOFFSET was added + kerneloffset_str = vmcoreinfo.get("KERNELOFFSET") + if kerneloffset_str is None: + # kernels < 3.14 if KERNELOFFSET is missing, KASLR might not be implemented. + # Oddly, NUMBER(phys_base) is present without it. To be safe, proceed only + # if both are present. + return None - Returns: - kaslr_shirt and aslr_shift - """ + aslr_shift = int(kerneloffset_str, 16) + kaslr_shift = int(phys_base_str) + aslr_shift - aslr_shifts = cls.find_aslr_vmcoreinfo( - context, layer_name, progress_callback=progress_callback - ) - if aslr_shifts: - kaslr_shift, aslr_shift = aslr_shifts - else: - # Fallback to the traditional scanner method - kaslr_shift, aslr_shift = cls.find_aslr_classic( - context, - symbol_table, - layer_name, - progress_callback=progress_callback, - ) return kaslr_shift, aslr_shift + @staticmethod + def _vmcoreinfo_get_dtb(vmcoreinfo, aslr_shift, kaslr_shift) -> int: + """Returns the page global directory address physical (a.k.a DTB or PGD)""" + # In x86-64, since kernels 2.5.22 swapper_pg_dir is a macro to the respective pgd. + # First, in e3ebadd95cb621e2c7436f3d3646447ac9d5c16d to init_level4_pgt, and later + # in kernels 4.13 in 65ade2f872b474fa8a04c2d397783350326634e6) to init_top_pgt. + # In x86-32, the pgd is swapper_pg_dir. So, in any case, for VMCOREINFO + # SYMBOL(swapper_pg_dir) will always have the right value. + dtb_vaddr = int(vmcoreinfo["SYMBOL(swapper_pg_dir)"], 16) + dtb_paddr = ( + LinuxIntelStacker.virtual_to_physical_address(dtb_vaddr) + - aslr_shift + + kaslr_shift + ) -class LinuxSymbolFinder(symbol_finder.SymbolFinder): - """Linux symbol loader based on uname signature strings.""" + return dtb_paddr - banner_config_key = "kernel_banner" - operating_system = "linux" - symbol_class = "volatility3.framework.symbols.linux.LinuxKernelIntermedSymbols" - find_aslr = lambda cls, *args: LinuxIntelStacker.find_aslr(*args)[1] - exclusion_list = ["mac", "windows"] + @staticmethod + def _vmcoreinfo_is_32bit(vmcoreinfo) -> Tuple[bool, bool]: + """Returns a tuple of booleans with is_32bit and is_pae values""" + is_pae = vmcoreinfo.get("CONFIG_X86_PAE", "n") == "y" + if is_pae: + is_32bit = True + else: + # Check the swapper_pg_dir virtual address size + dtb_vaddr = int(vmcoreinfo["SYMBOL(swapper_pg_dir)"], 16) + is_32bit = dtb_vaddr <= 2**32 + + return is_32bit, is_pae From 00b528aafe4205946e2b1ce11b4a203eaf467ec2 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Thu, 14 Nov 2024 18:57:26 +1100 Subject: [PATCH 05/19] linux: intel VMCoreInfo: Fix docstring typo --- volatility3/framework/automagic/linux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index f877d1cd00..556a02c1f2 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -397,7 +397,7 @@ def _vmcoreinfo_find_aslr(vmcoreinfo) -> Tuple[int, int]: @staticmethod def _vmcoreinfo_get_dtb(vmcoreinfo, aslr_shift, kaslr_shift) -> int: - """Returns the page global directory address physical (a.k.a DTB or PGD)""" + """Returns the page global directory physical address (a.k.a DTB or PGD)""" # In x86-64, since kernels 2.5.22 swapper_pg_dir is a macro to the respective pgd. # First, in e3ebadd95cb621e2c7436f3d3646447ac9d5c16d to init_level4_pgt, and later # in kernels 4.13 in 65ade2f872b474fa8a04c2d397783350326634e6) to init_top_pgt. From 41a93473e987bb43bca8e8095dddd890a1a1b351 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Thu, 14 Nov 2024 19:35:47 +1100 Subject: [PATCH 06/19] linux: intel VMCoreInfo: Improve layer config code --- volatility3/framework/automagic/linux.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index 556a02c1f2..c92fa05c43 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -347,16 +347,16 @@ def stack( config_path = join("IntelHelper", new_layer_name) kernel_banner = LinuxSymbolFinder.banner_config_key banner_str = banner.decode(encoding="latin-1") + context.config[join(config_path, kernel_banner)] = banner_str context.config[join(config_path, "memory_layer")] = layer_name context.config[join(config_path, "page_map_offset")] = dtb - context.config[join(config_path, kernel_banner)] = banner_str + context.config[join(config_path, "kernel_virtual_offset")] = aslr_shift layer = layer_class( context, config_path=config_path, name=new_layer_name, metadata={"os": "Linux"}, ) - layer.config["kernel_virtual_offset"] = aslr_shift if layer and dtb: vollog.debug( From 4c0ddca7962ade96747e16b7c2db3bd126e1af6a Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 18 Nov 2024 15:20:38 +1100 Subject: [PATCH 07/19] linux: intel VMCoreInfo: BugFix. Return layer if both values are present --- volatility3/framework/automagic/linux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index c92fa05c43..adecc36d0a 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -366,7 +366,7 @@ def stack( dtb, ) - return layer + return layer vollog.debug("No suitable linux banner could be matched") return None From 4a88a7450b8fb33b29c7bdf1ee2a9703c67b5446 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 18 Nov 2024 16:47:45 +1100 Subject: [PATCH 08/19] linux: VMCoreInfo API: Integrate support for value parsing within the VMCoreInfo API --- volatility3/framework/automagic/linux.py | 20 +++++++++++-------- .../framework/plugins/linux/vmcoreinfo.py | 5 +++++ .../framework/symbols/linux/__init__.py | 17 ++++++++++++++-- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index adecc36d0a..ff6bbbfe4b 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -373,8 +373,8 @@ def stack( @staticmethod def _vmcoreinfo_find_aslr(vmcoreinfo) -> Tuple[int, int]: - phys_base_str = vmcoreinfo.get("NUMBER(phys_base)") - if phys_base_str is None: + phys_base = vmcoreinfo.get("NUMBER(phys_base)") + if phys_base is None: # In kernel < 4.10, there may be a SYMBOL(phys_base), but as noted in the # c401721ecd1dcb0a428aa5d6832ee05ffbdbffbbe commit comment, this value # isn't useful for calculating the physical address. @@ -383,15 +383,15 @@ def _vmcoreinfo_find_aslr(vmcoreinfo) -> Tuple[int, int]: return None # kernels 3.14 (b6085a865762236bb84934161273cdac6dd11c2d) KERNELOFFSET was added - kerneloffset_str = vmcoreinfo.get("KERNELOFFSET") - if kerneloffset_str is None: + kerneloffset = vmcoreinfo.get("KERNELOFFSET") + if kerneloffset is None: # kernels < 3.14 if KERNELOFFSET is missing, KASLR might not be implemented. # Oddly, NUMBER(phys_base) is present without it. To be safe, proceed only # if both are present. return None - aslr_shift = int(kerneloffset_str, 16) - kaslr_shift = int(phys_base_str) + aslr_shift + aslr_shift = kerneloffset + kaslr_shift = phys_base + aslr_shift return kaslr_shift, aslr_shift @@ -403,7 +403,11 @@ def _vmcoreinfo_get_dtb(vmcoreinfo, aslr_shift, kaslr_shift) -> int: # in kernels 4.13 in 65ade2f872b474fa8a04c2d397783350326634e6) to init_top_pgt. # In x86-32, the pgd is swapper_pg_dir. So, in any case, for VMCOREINFO # SYMBOL(swapper_pg_dir) will always have the right value. - dtb_vaddr = int(vmcoreinfo["SYMBOL(swapper_pg_dir)"], 16) + dtb_vaddr = vmcoreinfo.get("SYMBOL(swapper_pg_dir)") + if dtb_vaddr is None: + # Abort, it should be present + return None + dtb_paddr = ( LinuxIntelStacker.virtual_to_physical_address(dtb_vaddr) - aslr_shift @@ -420,7 +424,7 @@ def _vmcoreinfo_is_32bit(vmcoreinfo) -> Tuple[bool, bool]: is_32bit = True else: # Check the swapper_pg_dir virtual address size - dtb_vaddr = int(vmcoreinfo["SYMBOL(swapper_pg_dir)"], 16) + dtb_vaddr = vmcoreinfo["SYMBOL(swapper_pg_dir)"] is_32bit = dtb_vaddr <= 2**32 return is_32bit, is_pae diff --git a/volatility3/framework/plugins/linux/vmcoreinfo.py b/volatility3/framework/plugins/linux/vmcoreinfo.py index ea30520e35..0f07f85896 100644 --- a/volatility3/framework/plugins/linux/vmcoreinfo.py +++ b/volatility3/framework/plugins/linux/vmcoreinfo.py @@ -38,6 +38,11 @@ def _generator(self): layer_name=layer_name, ): for key, value in vmcoreinfo.items(): + if key.startswith("SYMBOL(") or key == "KERNELOFFSET": + value = f"0x{value:x}" + else: + value = str(value) + yield 0, (format_hints.Hex(vmcoreinfo_offset), key, value) def run(self): diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index b0b6b7325f..b8a2056d5b 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -842,8 +842,9 @@ class VMCoreInfo(interfaces.configuration.VersionableInterface): _version = (1, 0, 0) - @staticmethod + @classmethod def _vmcoreinfo_data_to_dict( + cls, vmcoreinfo_data, ) -> Optional[Dict[str, str]]: """Converts the input VMCoreInfo data buffer into a dictionary""" @@ -859,10 +860,22 @@ def _vmcoreinfo_data_to_dict( break key, value = line.split("=", 1) - vmcoreinfo_dict[key] = value + vmcoreinfo_dict[key] = cls._parse_value(key, value) return vmcoreinfo_dict + @classmethod + def _parse_value(cls, key, value): + if key.startswith("SYMBOL(") or key == "KERNELOFFSET": + return int(value, 16) + elif key.startswith(("NUMBER(", "LENGTH(", "SIZE(", "OFFSET(")): + return int(value) + elif key == "PAGESIZE": + return int(value) + + # Default, as string + return value + @classmethod def search_vmcoreinfo_elf_note( cls, From f2a2f8aa679e2ec314049bdb721ad13376209514 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 18 Nov 2024 17:00:00 +1100 Subject: [PATCH 09/19] linux: intel VMCoreInfo: immediately abort processing the current VMCOREINFO if DTB is not found --- volatility3/framework/automagic/linux.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index ff6bbbfe4b..64365bd836 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -295,12 +295,15 @@ def stack( for _vmcoreinfo_offset, vmcoreinfo in vmcoreinfo_elf_notes_iter: shifts = cls._vmcoreinfo_find_aslr(vmcoreinfo) if not shifts: - # Let's try the next vmcoreinfo, in case this one isn't correct. + # Let's try the next VMCOREINFO, in case this one isn't correct. continue kaslr_shift, aslr_shift = shifts dtb = cls._vmcoreinfo_get_dtb(vmcoreinfo, aslr_shift, kaslr_shift) + if dtb is None: + # Discard this VMCOREINFO immediately + continue is_32bit, is_pae = cls._vmcoreinfo_is_32bit(vmcoreinfo) if is_32bit: @@ -358,7 +361,7 @@ def stack( metadata={"os": "Linux"}, ) - if layer and dtb: + if layer: vollog.debug( "Values found in VMCOREINFO: KASLR=0x%x, ASLR=0x%x, DTB=0x%x", kaslr_shift, From d80ef553255acf29ee16d884e864ec7c546a2be0 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 18 Nov 2024 17:49:30 +1100 Subject: [PATCH 10/19] linux: intel VMCoreInfo: Select the fastest scanner for each scenario --- volatility3/framework/automagic/linux.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index 64365bd836..81f5f56618 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -321,12 +321,20 @@ def stack( if not valid_banners: # There's no banner matching this VMCOREINFO, keep trying with the next one continue + elif len(valid_banners) == 1: + # Usually, we narrow down the Linux banner list to a single element. + # Using BytesScanner here is slightly faster than MultiStringScanner. + scanner = scanners.BytesScanner(valid_banners[0]) + else: + scanner = scanners.MultiStringScanner(valid_banners) join = interfaces.configuration.path_join - mss = scanners.MultiStringScanner(valid_banners) - for _, banner in layer.scan( - context=context, scanner=mss, progress_callback=progress_callback + for match in layer.scan( + context=context, scanner=scanner, progress_callback=progress_callback ): + # Unfortunately, the scanners do not maintain a consistent interface + banner = match[1] if isinstance(match, Tuple) else valid_banners[0] + isf_path = linux_banners.get(banner, None) if not isf_path: vollog.warning( From 13729ac25f699d72c280b999ddbe0e9d02fcede6 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 18 Nov 2024 18:18:14 +1100 Subject: [PATCH 11/19] linux: VMCoreInfo API: AARCH64 uses NUMBER() with hex values. Fortunately, includes the 0x prefix, allowing the parser to still handle these cases correctly --- volatility3/framework/symbols/linux/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index b8a2056d5b..cd4d16649a 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -869,9 +869,9 @@ def _parse_value(cls, key, value): if key.startswith("SYMBOL(") or key == "KERNELOFFSET": return int(value, 16) elif key.startswith(("NUMBER(", "LENGTH(", "SIZE(", "OFFSET(")): - return int(value) + return int(value, 0) elif key == "PAGESIZE": - return int(value) + return int(value, 0) # Default, as string return value From 96f5fdae2f48b6a87f735daa3fc56e7aa2902c61 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 19 Nov 2024 11:25:52 +1100 Subject: [PATCH 12/19] linux: intel VMCoreInfo: Add the vmcoreinfo to the layer metadata --- volatility3/framework/automagic/linux.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index 81f5f56618..2b7cdf4cc2 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -2,8 +2,9 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # -import logging import os +import logging +import collections from typing import Optional, Tuple, Type from volatility3.framework import constants, interfaces @@ -107,11 +108,13 @@ def stack( join(config_path, LinuxSymbolFinder.banner_config_key) ] = str(banner, "latin-1") + # Set an empty vmcoreinfo entry to prevent using the wrong one in the layer stack. + layer_metadata = dict(os="Linux", vmcoreinfo={}) layer = layer_class( context, config_path=config_path, name=new_layer_name, - metadata={"os": "Linux"}, + metadata=layer_metadata, ) layer.config["kernel_virtual_offset"] = aslr_shift @@ -376,6 +379,10 @@ def stack( aslr_shift, dtb, ) + # Add the vmcoreinfo dict to the layer metadata + layer._direct_metadata = collections.ChainMap( + {"vmcoreinfo": vmcoreinfo}, layer._direct_metadata + ) return layer From 9f6d1949800981e7d5cc6416b661880fe8041a38 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 19 Nov 2024 12:47:56 +1100 Subject: [PATCH 13/19] linux: intel VMCoreInfo: The _direct_metadata class attribute needs to be modified; otherwise, it will only exist within the instance namespace. --- volatility3/framework/automagic/linux.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index 2b7cdf4cc2..8c4ccd8e31 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -379,8 +379,8 @@ def stack( aslr_shift, dtb, ) - # Add the vmcoreinfo dict to the layer metadata - layer._direct_metadata = collections.ChainMap( + # Add the vmcoreinfo dict to the layer class metadata + layer.__class__._direct_metadata = collections.ChainMap( {"vmcoreinfo": vmcoreinfo}, layer._direct_metadata ) From 41d2e924d147aed9adfa2181aa6191e26fa90568 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Wed, 20 Nov 2024 11:47:37 +1100 Subject: [PATCH 14/19] linux: intel VMCoreInfo: Revert changes associated with adding vmcoreinfo dict to the layer metadata --- volatility3/framework/automagic/linux.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index 8c4ccd8e31..b8ed472209 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -4,7 +4,6 @@ import os import logging -import collections from typing import Optional, Tuple, Type from volatility3.framework import constants, interfaces @@ -108,13 +107,11 @@ def stack( join(config_path, LinuxSymbolFinder.banner_config_key) ] = str(banner, "latin-1") - # Set an empty vmcoreinfo entry to prevent using the wrong one in the layer stack. - layer_metadata = dict(os="Linux", vmcoreinfo={}) layer = layer_class( context, config_path=config_path, name=new_layer_name, - metadata=layer_metadata, + metadata={os: "Linux"}, ) layer.config["kernel_virtual_offset"] = aslr_shift @@ -379,10 +376,6 @@ def stack( aslr_shift, dtb, ) - # Add the vmcoreinfo dict to the layer class metadata - layer.__class__._direct_metadata = collections.ChainMap( - {"vmcoreinfo": vmcoreinfo}, layer._direct_metadata - ) return layer From bbd2f9bc3770d798c7e225586f5f78e22400970d Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Fri, 22 Nov 2024 22:40:53 +1100 Subject: [PATCH 15/19] linux: intel VMCoreInfo: Fix bug introduced in last commit reverting the layer metadata dict --- volatility3/framework/automagic/linux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index b8ed472209..90384b50db 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -111,7 +111,7 @@ def stack( context, config_path=config_path, name=new_layer_name, - metadata={os: "Linux"}, + metadata={"os": "Linux"}, ) layer.config["kernel_virtual_offset"] = aslr_shift From d14c777b4f2ba75ff06e1a39d4a23fa52473a5fa Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Thu, 19 Dec 2024 14:40:51 +1100 Subject: [PATCH 16/19] linux: vmcoreinfo layer: Update to use the new cache-manager --- volatility3/framework/automagic/linux.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index bb3bf20913..ce73ee9693 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -224,19 +224,6 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): @staticmethod def _check_versions() -> bool: """Verify the versions of the required modules""" - - # Check SQlite cache version - sqlitecache_version_required = (1, 0, 0) - if not requirements.VersionRequirement.matches_required( - sqlitecache_version_required, symbol_cache.SqliteCache.version - ): - vollog.info( - "SQLiteCache version not suitable: required %s found %s", - sqlitecache_version_required, - symbol_cache.SqliteCache.version, - ) - return False - # Check VMCOREINFO API version vmcoreinfo_version_required = (1, 0, 0) if not requirements.VersionRequirement.matches_required( @@ -272,11 +259,9 @@ def stack( if isinstance(layer, intel.Intel): return None - identifiers_path = os.path.join( - constants.CACHE_PATH, constants.IDENTIFIERS_FILENAME + linux_banners = symbol_cache.load_cache_manager().get_identifier_dictionary( + operating_system="linux" ) - sqlite_cache = symbol_cache.SqliteCache(identifiers_path) - linux_banners = sqlite_cache.get_identifier_dictionary(operating_system="linux") if not linux_banners: # If we have no banners, don't bother scanning vollog.info( From d56f3a5537de12d4b188b2d2eeace213396feea5 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Thu, 19 Dec 2024 14:49:23 +1100 Subject: [PATCH 17/19] linux: vmcoreinfo layer: Add review suggestions --- volatility3/framework/automagic/linux.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index ce73ee9693..c2fc3306de 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -339,8 +339,8 @@ def stack( context.symbol_space.append(table) # Build the new layer - new_layer_name = context.layers.free_layer_name("IntelLayer") - config_path = join("IntelHelper", new_layer_name) + new_layer_name = context.layers.free_layer_name("primary") + config_path = join("vmcoreinfo", new_layer_name) kernel_banner = LinuxSymbolFinder.banner_config_key banner_str = banner.decode(encoding="latin-1") context.config[join(config_path, kernel_banner)] = banner_str From 7858af3d6d69e3054c1b2c3690e115257efc82b0 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Thu, 19 Dec 2024 14:51:33 +1100 Subject: [PATCH 18/19] linux: vmcoreinfo layer: remove unused import --- volatility3/framework/automagic/linux.py | 1 - 1 file changed, 1 deletion(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index c2fc3306de..76c7f7ab38 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -2,7 +2,6 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # -import os import logging from typing import Optional, Tuple From 7ed8b99e9845b2f982da4ef186bed137ba77af8f Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Thu, 19 Dec 2024 15:50:17 +1100 Subject: [PATCH 19/19] linux: vmcoreinfo layer: Use the version class accessor instead of the internal class member --- volatility3/framework/automagic/linux.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index 76c7f7ab38..58195744bd 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -226,12 +226,12 @@ def _check_versions() -> bool: # Check VMCOREINFO API version vmcoreinfo_version_required = (1, 0, 0) if not requirements.VersionRequirement.matches_required( - vmcoreinfo_version_required, linux.VMCoreInfo._version + vmcoreinfo_version_required, linux.VMCoreInfo.version ): vollog.info( "VMCOREINFO version not suitable: required %s found %s", vmcoreinfo_version_required, - linux.VMCoreInfo._version, + linux.VMCoreInfo.version, ) return False