From 512c1abe801b731f9f36551db51075d4d41413e1 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 4 Nov 2024 17:45:56 +1100 Subject: [PATCH 01/22] 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 7c485d3c3..dde256754 100644 --- a/volatility3/framework/constants/linux/__init__.py +++ b/volatility3/framework/constants/linux/__init__.py @@ -347,3 +347,9 @@ class PT_FLAGS(Flag): 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 000000000..ea30520e3 --- /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 3289775b6..b0b6b7325 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 @@ class PageCache(object): 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/22] 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 52a73f45a..fde060733 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -76,8 +76,12 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface): 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 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface): return None @classmethod - def find_aslr( + def find_aslr_classic( cls, context: interfaces.context.ContextInterface, symbol_table: str, @@ -126,7 +130,17 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface): 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 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface): 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 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface): 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/22] 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 fde060733..248bbf04d 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -229,7 +229,6 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface): 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/22] 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 | 302 ++++++++++++++++------- 1 file changed, 212 insertions(+), 90 deletions(-) diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index 248bbf04d..f877d1cd0 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -122,7 +122,7 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface): return None @classmethod - def find_aslr_classic( + def find_aslr( cls, context: interfaces.context.ContextInterface, symbol_table: str, @@ -198,101 +198,14 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface): 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. - 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: + @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 - @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.""" @@ -302,3 +215,212 @@ class LinuxSymbolFinder(symbol_finder.SymbolFinder): 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 stack( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + progress_callback: constants.ProgressCallback = None, + ) -> Optional[interfaces.layers.DataLayerInterface]: + """Attempts to identify linux within this layer.""" + + # Verify the versions of the required modules + if not cls._check_versions(): + return None + + # 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 + + vmcoreinfo_elf_notes_iter = linux.VMCoreInfo.search_vmcoreinfo_elf_note( + context=context, + layer_name=layer_name, + progress_callback=progress_callback, + ) + + # 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 + + 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 + + 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) + + # 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 + + 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("No suitable linux banner could be matched") + return None + + @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 + + # 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 + + aslr_shift = int(kerneloffset_str, 16) + kaslr_shift = int(phys_base_str) + aslr_shift + + 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 + ) + + return dtb_paddr + + @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/22] 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 f877d1cd0..556a02c1f 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -397,7 +397,7 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): @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/22] 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 556a02c1f..c92fa05c4 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -347,16 +347,16 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): 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/22] 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 c92fa05c4..adecc36d0 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -366,7 +366,7 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): 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/22] 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 adecc36d0..ff6bbbfe4 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -373,8 +373,8 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): @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 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): 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 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): # 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 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): 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 ea30520e3..0f07f8589 100644 --- a/volatility3/framework/plugins/linux/vmcoreinfo.py +++ b/volatility3/framework/plugins/linux/vmcoreinfo.py @@ -38,6 +38,11 @@ class VMCoreInfo(plugins.PluginInterface): 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 b0b6b7325..b8a2056d5 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 @@ class VMCoreInfo(interfaces.configuration.VersionableInterface): 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/22] 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 ff6bbbfe4..64365bd83 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -295,12 +295,15 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): 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 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): 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/22] 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 64365bd83..81f5f5661 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -321,12 +321,20 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): 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/22] 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 b8a2056d5..cd4d16649 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -869,9 +869,9 @@ class VMCoreInfo(interfaces.configuration.VersionableInterface): 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/22] 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 81f5f5661..2b7cdf4cc 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 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface): 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 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): 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/22] 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 2b7cdf4cc..8c4ccd8e3 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -379,8 +379,8 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): 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/22] 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 8c4ccd8e3..b8ed47220 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 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface): 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 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): 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/22] 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 b8ed47220..90384b50d 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -111,7 +111,7 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface): 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/22] 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 bb3bf2091..ce73ee969 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 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): 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/22] 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 ce73ee969..c2fc3306d 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -339,8 +339,8 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): 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/22] 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 c2fc3306d..76c7f7ab3 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/22] 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 76c7f7ab3..58195744b 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -226,12 +226,12 @@ class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface): # 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 From c2f2fc6b42899c98b2e25700c77fe1152a1ee393 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Thu, 30 Jan 2025 18:25:58 +1100 Subject: [PATCH 20/22] linux: vmcoreinfo API: fix single letter variable --- volatility3/framework/symbols/linux/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 0a6448528..846928c3a 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -903,7 +903,7 @@ class VMCoreInfo(interfaces.configuration.VersionableInterface): """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): + if not all(char in string.printable.encode() for char in vmcoreinfo_data): # Abort, we are in the wrong place return None From f6a22eed05a7ba3784df47df77c0d8f8643e9f8c Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Thu, 30 Jan 2025 18:44:18 +1100 Subject: [PATCH 21/22] linux: vmcoreinfo API: significantly improve the performance when validating each character, from O(n) to O(1).. where n is 100 chars long --- volatility3/framework/symbols/linux/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 846928c3a..afdfee39c 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -903,7 +903,8 @@ class VMCoreInfo(interfaces.configuration.VersionableInterface): """Converts the input VMCoreInfo data buffer into a dictionary""" # Ensure the whole buffer is printable - if not all(char in string.printable.encode() for char in vmcoreinfo_data): + printable_bytes_set = set(string.printable.encode()) + if not all(byte in printable_bytes_set for byte in vmcoreinfo_data): # Abort, we are in the wrong place return None From 8b6c00e64f016ac5a08a51c4fefe19a78c8c270a Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Thu, 30 Jan 2025 18:48:42 +1100 Subject: [PATCH 22/22] linux: vmcoreinfo plugin: simplify hex conversion --- volatility3/framework/plugins/linux/vmcoreinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/vmcoreinfo.py b/volatility3/framework/plugins/linux/vmcoreinfo.py index 0f07f8589..658626014 100644 --- a/volatility3/framework/plugins/linux/vmcoreinfo.py +++ b/volatility3/framework/plugins/linux/vmcoreinfo.py @@ -39,7 +39,7 @@ class VMCoreInfo(plugins.PluginInterface): ): for key, value in vmcoreinfo.items(): if key.startswith("SYMBOL(") or key == "KERNELOFFSET": - value = f"0x{value:x}" + value = hex(value) else: value = str(value)