From 786fd61fc9b6b2978e0de7595bdb22aca9e91843 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sat, 9 Apr 2022 20:57:34 +0100 Subject: [PATCH 01/11] Layers: Add architecture to qemu layer --- volatility3/framework/layers/qemu.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/layers/qemu.py b/volatility3/framework/layers/qemu.py index f1ba1e468..65f8eed9a 100644 --- a/volatility3/framework/layers/qemu.py +++ b/volatility3/framework/layers/qemu.py @@ -3,9 +3,9 @@ # import functools import json -from typing import Optional, Dict, Any, Tuple, List, Set +from typing import Any, Dict, List, Optional, Set, Tuple -from volatility3.framework import interfaces, exceptions, constants +from volatility3.framework import constants, exceptions, interfaces from volatility3.framework.layers import segmented from volatility3.framework.symbols import intermed @@ -39,6 +39,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): metadata: Optional[Dict[str, Any]] = None) -> None: self._qemu_table_name = intermed.IntermediateSymbolTable.create(context, config_path, 'generic', 'qemu') self._configuration = None + self._architecture = None self._compressed: Set[int] = set() self._current_segment_name = b'' super().__init__(context = context, config_path = config_path, name = name, metadata = metadata) @@ -139,6 +140,9 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): section_len = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned long', offset = index, layer_name = self._base_layer) + self._architecture = self.context.object(self._qemu_table_name + constants.BANG + 'string', + offset = index + 4, layer_name = self._base_layer, + max_length = section_len) index += 4 + section_len elif section_byte == self.QEVM_SECTION_START or section_byte == self.QEVM_SECTION_FULL: section_id = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned long', From 302bb63645af3b9b20b2a361a73c06a2c27e3513 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sat, 9 Apr 2022 21:46:47 +0100 Subject: [PATCH 02/11] Layers: Detect and compensate for QEVM pci-hole --- volatility3/framework/layers/qemu.py | 30 +++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/layers/qemu.py b/volatility3/framework/layers/qemu.py index 65f8eed9a..4e6252b2f 100644 --- a/volatility3/framework/layers/qemu.py +++ b/volatility3/framework/layers/qemu.py @@ -3,12 +3,17 @@ # import functools import json +import logging +import re +import struct from typing import Any, Dict, List, Optional, Set, Tuple from volatility3.framework import constants, exceptions, interfaces from volatility3.framework.layers import segmented from volatility3.framework.symbols import intermed +vollog = logging.getLogger(__name__) + class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): """A Qemu suspend-to-disk translation layer.""" @@ -32,6 +37,13 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): SEGMENT_FLAG_XBZRLE = 0x40 SEGMENT_FLAG_HOOK = 0x80 + pci_hole_table = {re.compile(r"^pc-i440fx-\d\.\d$"): (0xc0000000, 0x100000000), + re.compile(r"^pc-1440fx-eoan$"): (0xe0000000, 0x100000000), + re.compile(r"^pc-q35$"): (0x80000000, 0x100000000), + re.compile(r"^microvm$"): (0xc0000000, 0x100000000), + re.compile(r"^xen$"): (0xf0000000, 0x100000000) + } + def __init__(self, context: interfaces.context.ContextInterface, config_path: str, @@ -42,6 +54,8 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): self._architecture = None self._compressed: Set[int] = set() self._current_segment_name = b'' + self._pci_hole_start = 0 + self._pci_hole_end = 0 super().__init__(context = context, config_path = config_path, name = name, metadata = metadata) @classmethod @@ -77,9 +91,9 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): base_layer = self.context.layers[self._base_layer] while not done: - addr = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned long long', - offset = index, - layer_name = self._base_layer) + # Use struct.unpack here for performance improvements + addr = struct.unpack('>Q', base_layer.read(index, 8))[0] + # Flags are stored in the n least significant bits, where n equals the bit-length of pagesize flags = addr & (page_size - 1) # addr equals the highest multiple of pagesize <= offset @@ -87,6 +101,9 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): addr = addr ^ (addr & (page_size - 1)) index += 8 + if addr > self._pci_hole_start: + addr += self._pci_hole_end - self._pci_hole_start + if flags & self.SEGMENT_FLAG_MEM_SIZE: namelen = self._context.object(self._qemu_table_name + constants.BANG + 'unsigned char', offset = index, @@ -143,6 +160,13 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): self._architecture = self.context.object(self._qemu_table_name + constants.BANG + 'string', offset = index + 4, layer_name = self._base_layer, max_length = section_len) + for regex in self.pci_hole_table: + if regex.match(self._architecture): + self._pci_hole_start, self._pci_hole_end = self.pci_hole_table[regex] + vollog.log(constants.LOGLEVEL_VVVV, f"QEVM archicture detected as: {self._architecture}") + break + else: + vollog.debug(constants.LOGLEVEL_VVVV, f"QEVM unknown architecture found: {self._architecture}") index += 4 + section_len elif section_byte == self.QEVM_SECTION_START or section_byte == self.QEVM_SECTION_FULL: section_id = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned long', From dea8e1dac9090b40823a941fdcfc1a828f80de9e Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Mon, 18 Apr 2022 01:45:12 +0100 Subject: [PATCH 03/11] Layers: Add QEVM architecture fallback detection --- volatility3/framework/layers/qemu.py | 97 ++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 13 deletions(-) diff --git a/volatility3/framework/layers/qemu.py b/volatility3/framework/layers/qemu.py index 4e6252b2f..7835cad17 100644 --- a/volatility3/framework/layers/qemu.py +++ b/volatility3/framework/layers/qemu.py @@ -9,7 +9,7 @@ import struct from typing import Any, Dict, List, Optional, Set, Tuple from volatility3.framework import constants, exceptions, interfaces -from volatility3.framework.layers import segmented +from volatility3.framework.layers import scanners, segmented from volatility3.framework.symbols import intermed vollog = logging.getLogger(__name__) @@ -37,11 +37,32 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): SEGMENT_FLAG_XBZRLE = 0x40 SEGMENT_FLAG_HOOK = 0x80 - pci_hole_table = {re.compile(r"^pc-i440fx-\d\.\d$"): (0xc0000000, 0x100000000), - re.compile(r"^pc-1440fx-eoan$"): (0xe0000000, 0x100000000), - re.compile(r"^pc-q35$"): (0x80000000, 0x100000000), - re.compile(r"^microvm$"): (0xc0000000, 0x100000000), - re.compile(r"^xen$"): (0xf0000000, 0x100000000) + # See https://qemu.readthedocs.io/en/latest/devel/memory.html for more info + # + # At least the following values could occur for devices using > 3-4 GB RAM: + # +--------------------------------+--------------------------------+------------+-------------+ + # | Architecture | Reference Code | Hole Start | Hole End | + # +--------------------------------+--------------------------------+------------+-------------+ + # | PC i440FX + PIIX "New Default" | qemu/hw/i386/pc_piix.c:98 | 0xc0000000 | 0x100000000 | + # | PC i440FX + PIIX "Old Default" | qemu/hw/i386/pc_piix.c:98 | 0xe0000000 | 0x100000000 | + # | PC Q35 + ICH9 | qemu/hw/i386/pc_q35.c:141 | 0x80000000 | 0x100000000 | + # | MicroVM | qemu/hw/i386/microvm.c:291 | 0xc0000000 | 0x100000000 | + # | Xen | qemu/hw/i386/xen/xen-hvm.c:248 | 0xf0000000 | 0x100000000 | + # +--------------------------------+--------------------------------+------------+-------------+ + # + # For now, we assume that the parameter max-ram-below-4g is not set, since this parameter influences the size + # and location of the memory gap. Deviating hole sizes could eventually be detected for Linux by e.g. scanning + # for dmesg entries with a regex like rb'\[mem (0x[0-9a-f]{4,10})-0x[0-9a-f]{4,10}\] available for PCI devices' + + debian_re = r"artful|eoan" + + pci_hole_table = {re.compile(r"^pc-i440fx-([23456789]|\d\d+)\.\d$"): (0xe0000000, 0xc0000000, 0x100000000), + re.compile(r"^pc-i440fx-[01].\d$"): (0xe0000000, 0xe0000000, 0x100000000), + re.compile(r"^pc-q35-\d.\d$"): (0xe0000000, 0x80000000, 0x100000000), + re.compile(r"^microvm$"): (0xe0000000, 0xc0000000, 0x100000000), + re.compile(r"^xen$"): (0xe0000000, 0xf0000000, 0x100000000), + re.compile(r"^pc-i440fx-" + debian_re + r"$"): (0xe0000000, 0xc0000000, 0x100000000), + re.compile(r"^pc-q35-" + debian_re + r"$"): (0xe0000000, 0x80000000, 0x100000000), } def __init__(self, @@ -65,6 +86,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): raise exceptions.LayerException(name, 'No QEMU magic bytes') if header[4:] != b'\x00\x00\x00\x03': raise exceptions.LayerException(name, 'Unsupported QEMU version found') + vollog.debug("QEVM header found") def _read_configuration(self, base_layer: interfaces.layers.DataLayerInterface, name: str) -> Any: """Reads the JSON configuration from the end of the file""" @@ -160,13 +182,6 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): self._architecture = self.context.object(self._qemu_table_name + constants.BANG + 'string', offset = index + 4, layer_name = self._base_layer, max_length = section_len) - for regex in self.pci_hole_table: - if regex.match(self._architecture): - self._pci_hole_start, self._pci_hole_end = self.pci_hole_table[regex] - vollog.log(constants.LOGLEVEL_VVVV, f"QEVM archicture detected as: {self._architecture}") - break - else: - vollog.debug(constants.LOGLEVEL_VVVV, f"QEVM unknown architecture found: {self._architecture}") index += 4 + section_len elif section_byte == self.QEVM_SECTION_START or section_byte == self.QEVM_SECTION_FULL: section_id = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned long', @@ -217,6 +232,62 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): else: raise exceptions.LayerException(self._name, f'QEMU unknown section encountered: {section_byte}') + # If no architecture has been set, try to determine it using fallback mechanisms + if not self._architecture: + self._architecture = self._fallback_determine_architecture() + if self._architecture is None: + vollog.log(constants.LOGLEVEL_VV, f"QEVM architecture could not be determined") + + # Once all segments have been read, determine the PCI hole if any + for regex in self.pci_hole_table: + if regex.match(self._architecture): + self._pci_hole_minimum, self._pci_hole_start, self._pci_hole_end = self.pci_hole_table[regex] + if self.maximum_address < self._pci_hole_minimum: + # The PCI hole isn't present because we're below the minimum value + self._pci_hole_start, self._pci_hole_end = 0, 0 + vollog.log(constants.LOGLEVEL_VVVV, f"QEVM architecture detected as: {self._architecture}") + break + else: + vollog.log(constants.LOGLEVEL_VVVV, f"QEVM unknown architecture found: {self._architecture}") + + def _fallback_determine_architecture(self) -> str: + architecture_pattern = rb'pc-(i440fx|q35)-([0-9]{1,2}.[0-9]{1,2}(?:.[0-9]{1,2})?)' + base_layer = self.context.layers[self._base_layer] + + vollog.log(constants.LOGLEVEL_VVVV, "QEVM fallback architecture detection used") + + res = scanners.RegExScanner(architecture_pattern) + for offset in base_layer.scan(context = self.context, scanner = res): + line = base_layer.read(offset, 64) + regex_results = re.search(architecture_pattern, line) + architecture = "pc-" + regex_results.groups()[0].decode() + return architecture + + # If that does not work, look in configuration JSON for devices specific to a certain architecture + architecture = None + for device in self._configuration.get('devices', []): + device_name = device.get('vmsd_name', '').lower() + if 'i440fx' in device_name or 'piix' in device_name: + architecture = 'pc-i440fx-2.0' + break + elif 'ich9' in device_name: + architecture = 'pc-q35-1.0' + break + if architecture: + return architecture + + # Still haven't found architecture, switch to fallback-method + architecture_pattern = rb'Standard PC \((i440FX|Q35)' + res = scanners.RegExScanner(architecture_pattern) + for offset in base_layer.scan(context = self.context, scanner = res): + line = base_layer.read(offset, 64) + regex_results = re.search(architecture_pattern, line) + architecture = "pc-" + regex_results.groups()[0].decode().lower() + return architecture + + vollog.warning("Could not determine QEMU target architecture!") + return None + def extract_data(self, index, name, version_id): if name == 'ram': if version_id != 4: From 1fca57ffc3603ab383332076f05e54f973c21452 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Wed, 20 Apr 2022 21:43:30 +0100 Subject: [PATCH 04/11] Layers: QEVM more fixes for minimum addresses --- volatility3/framework/layers/qemu.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/volatility3/framework/layers/qemu.py b/volatility3/framework/layers/qemu.py index 7835cad17..23c29dc39 100644 --- a/volatility3/framework/layers/qemu.py +++ b/volatility3/framework/layers/qemu.py @@ -54,15 +54,15 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): # and location of the memory gap. Deviating hole sizes could eventually be detected for Linux by e.g. scanning # for dmesg entries with a regex like rb'\[mem (0x[0-9a-f]{4,10})-0x[0-9a-f]{4,10}\] available for PCI devices' - debian_re = r"artful|eoan" + distro_re = r"(artful|eoan|rhel[\d\.]+)" pci_hole_table = {re.compile(r"^pc-i440fx-([23456789]|\d\d+)\.\d$"): (0xe0000000, 0xc0000000, 0x100000000), - re.compile(r"^pc-i440fx-[01].\d$"): (0xe0000000, 0xe0000000, 0x100000000), - re.compile(r"^pc-q35-\d.\d$"): (0xe0000000, 0x80000000, 0x100000000), - re.compile(r"^microvm$"): (0xe0000000, 0xc0000000, 0x100000000), - re.compile(r"^xen$"): (0xe0000000, 0xf0000000, 0x100000000), - re.compile(r"^pc-i440fx-" + debian_re + r"$"): (0xe0000000, 0xc0000000, 0x100000000), - re.compile(r"^pc-q35-" + debian_re + r"$"): (0xe0000000, 0x80000000, 0x100000000), + re.compile(r"^pc-i440fx-[01]\.\d$"): (0xe0000000, 0xe0000000, 0x100000000), + re.compile(r"^pc-q35-\d\.\d$"): (0xb0000000, 0x80000000, 0x100000000), + re.compile(r"^microvm$"): (0xc0000000, 0xc0000000, 0x100000000), + re.compile(r"^xen$"): (0xf0000000, 0xf0000000, 0x100000000), + re.compile(r"^pc-i440fx-" + distro_re + r"$"): (0xe0000000, 0xe0000000, 0x100000000), + re.compile(r"^pc-q35-" + distro_re + r"$"): (0xb0000000, 0x80000000, 0x100000000), } def __init__(self, @@ -252,6 +252,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): def _fallback_determine_architecture(self) -> str: architecture_pattern = rb'pc-(i440fx|q35)-([0-9]{1,2}.[0-9]{1,2}(?:.[0-9]{1,2})?)' + old_suffix = "-1.0" base_layer = self.context.layers[self._base_layer] vollog.log(constants.LOGLEVEL_VVVV, "QEVM fallback architecture detection used") @@ -260,7 +261,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): for offset in base_layer.scan(context = self.context, scanner = res): line = base_layer.read(offset, 64) regex_results = re.search(architecture_pattern, line) - architecture = "pc-" + regex_results.groups()[0].decode() + architecture = "pc-" + regex_results.groups()[0].decode() + old_suffix return architecture # If that does not work, look in configuration JSON for devices specific to a certain architecture @@ -268,10 +269,10 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): for device in self._configuration.get('devices', []): device_name = device.get('vmsd_name', '').lower() if 'i440fx' in device_name or 'piix' in device_name: - architecture = 'pc-i440fx-2.0' + architecture = 'pc-i440fx' + old_suffix break elif 'ich9' in device_name: - architecture = 'pc-q35-1.0' + architecture = 'pc-q35' + old_suffix break if architecture: return architecture @@ -282,7 +283,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): for offset in base_layer.scan(context = self.context, scanner = res): line = base_layer.read(offset, 64) regex_results = re.search(architecture_pattern, line) - architecture = "pc-" + regex_results.groups()[0].decode().lower() + architecture = "pc-" + regex_results.groups()[0].decode().lower() + old_suffix return architecture vollog.warning("Could not determine QEMU target architecture!") From afec05106127a8de73c22e7cc7f87ab3dd67ef9d Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Thu, 21 Apr 2022 10:29:26 +0100 Subject: [PATCH 05/11] Layers: Make QEVM changes based on @cstation 's feedback --- volatility3/framework/layers/qemu.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/layers/qemu.py b/volatility3/framework/layers/qemu.py index 23c29dc39..16f18cca1 100644 --- a/volatility3/framework/layers/qemu.py +++ b/volatility3/framework/layers/qemu.py @@ -123,7 +123,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): addr = addr ^ (addr & (page_size - 1)) index += 8 - if addr > self._pci_hole_start: + if addr >= self._pci_hole_start: addr += self._pci_hole_end - self._pci_hole_start if flags & self.SEGMENT_FLAG_MEM_SIZE: @@ -252,7 +252,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): def _fallback_determine_architecture(self) -> str: architecture_pattern = rb'pc-(i440fx|q35)-([0-9]{1,2}.[0-9]{1,2}(?:.[0-9]{1,2})?)' - old_suffix = "-1.0" + old_suffix = "-2.0" base_layer = self.context.layers[self._base_layer] vollog.log(constants.LOGLEVEL_VVVV, "QEVM fallback architecture detection used") @@ -261,7 +261,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): for offset in base_layer.scan(context = self.context, scanner = res): line = base_layer.read(offset, 64) regex_results = re.search(architecture_pattern, line) - architecture = "pc-" + regex_results.groups()[0].decode() + old_suffix + architecture = regex_results.group().decode() return architecture # If that does not work, look in configuration JSON for devices specific to a certain architecture From fb861eb6dfc528ec8f4c2a3f71a0995bdada0cba Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Thu, 21 Apr 2022 10:33:29 +0100 Subject: [PATCH 06/11] Layers: Simplification of QEVM fallback regex --- volatility3/framework/layers/qemu.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/layers/qemu.py b/volatility3/framework/layers/qemu.py index 16f18cca1..11bec2dd3 100644 --- a/volatility3/framework/layers/qemu.py +++ b/volatility3/framework/layers/qemu.py @@ -61,7 +61,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): re.compile(r"^pc-q35-\d\.\d$"): (0xb0000000, 0x80000000, 0x100000000), re.compile(r"^microvm$"): (0xc0000000, 0xc0000000, 0x100000000), re.compile(r"^xen$"): (0xf0000000, 0xf0000000, 0x100000000), - re.compile(r"^pc-i440fx-" + distro_re + r"$"): (0xe0000000, 0xe0000000, 0x100000000), + re.compile(r"^pc-i440fx-" + distro_re + r"$"): (0xe0000000, 0xc0000000, 0x100000000), re.compile(r"^pc-q35-" + distro_re + r"$"): (0xb0000000, 0x80000000, 0x100000000), } @@ -251,7 +251,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): vollog.log(constants.LOGLEVEL_VVVV, f"QEVM unknown architecture found: {self._architecture}") def _fallback_determine_architecture(self) -> str: - architecture_pattern = rb'pc-(i440fx|q35)-([0-9]{1,2}.[0-9]{1,2}(?:.[0-9]{1,2})?)' + architecture_pattern = rb'pc-(i440fx|q35)-(\d{1,2}\.\d{1,2}|[\w\d\.]+)' old_suffix = "-2.0" base_layer = self.context.layers[self._base_layer] From 21b0cb56a5746410f8ff9c96fba9d0e0ee86730f Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Thu, 21 Apr 2022 23:50:13 +0100 Subject: [PATCH 07/11] Layers: Shift when we calculate the QEVM PCI hole --- volatility3/framework/layers/qemu.py | 39 +++++++++++++++------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/volatility3/framework/layers/qemu.py b/volatility3/framework/layers/qemu.py index 11bec2dd3..270405645 100644 --- a/volatility3/framework/layers/qemu.py +++ b/volatility3/framework/layers/qemu.py @@ -170,7 +170,28 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): index = 8 section_info = dict() current_section_id = -1 + version_id = -1 + name = None + arch_detected = False while section_byte != self.QEVM_EOF and index <= base_layer.maximum_address: + if index > 20 and not arch_detected: + # We're past where the QEVM_CONFIGURATION might be, so set the values + # If no architecture has been set, try to determine it using fallback mechanisms + if not self._architecture: + self._architecture = self._fallback_determine_architecture() + if self._architecture is None: + vollog.log(constants.LOGLEVEL_VV, f"QEVM architecture could not be determined") + + # Once all segments have been read, determine the PCI hole if any + for regex in self.pci_hole_table: + if regex.match(self._architecture): + _, self._pci_hole_start, self._pci_hole_end = self.pci_hole_table[regex] + vollog.log(constants.LOGLEVEL_VVVV, f"QEVM architecture detected as: {self._architecture}") + break + else: + vollog.log(constants.LOGLEVEL_VVVV, f"QEVM unknown architecture found: {self._architecture}") + arch_detected = True + section_byte = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned char', offset = index, layer_name = self._base_layer) @@ -232,24 +253,6 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): else: raise exceptions.LayerException(self._name, f'QEMU unknown section encountered: {section_byte}') - # If no architecture has been set, try to determine it using fallback mechanisms - if not self._architecture: - self._architecture = self._fallback_determine_architecture() - if self._architecture is None: - vollog.log(constants.LOGLEVEL_VV, f"QEVM architecture could not be determined") - - # Once all segments have been read, determine the PCI hole if any - for regex in self.pci_hole_table: - if regex.match(self._architecture): - self._pci_hole_minimum, self._pci_hole_start, self._pci_hole_end = self.pci_hole_table[regex] - if self.maximum_address < self._pci_hole_minimum: - # The PCI hole isn't present because we're below the minimum value - self._pci_hole_start, self._pci_hole_end = 0, 0 - vollog.log(constants.LOGLEVEL_VVVV, f"QEVM architecture detected as: {self._architecture}") - break - else: - vollog.log(constants.LOGLEVEL_VVVV, f"QEVM unknown architecture found: {self._architecture}") - def _fallback_determine_architecture(self) -> str: architecture_pattern = rb'pc-(i440fx|q35)-(\d{1,2}\.\d{1,2}|[\w\d\.]+)' old_suffix = "-2.0" From 4eacdd9bea6be1be5506273abba5a4cc7715e7ff Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Mon, 25 Apr 2022 00:09:07 +0100 Subject: [PATCH 08/11] Layers: use QEVM size to turn off pci hole if needed --- volatility3/framework/layers/qemu.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/layers/qemu.py b/volatility3/framework/layers/qemu.py index 270405645..c755b8a9b 100644 --- a/volatility3/framework/layers/qemu.py +++ b/volatility3/framework/layers/qemu.py @@ -77,6 +77,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): self._current_segment_name = b'' self._pci_hole_start = 0 self._pci_hole_end = 0 + self._pci_hole_minimum = 0 super().__init__(context = context, config_path = config_path, name = name, metadata = metadata) @classmethod @@ -110,6 +111,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): done = None segments = [] + size_array = {} base_layer = self.context.layers[self._base_layer] while not done: @@ -131,14 +133,21 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): offset = index, layer_name = self._base_layer) while namelen != 0: - # if base_layer.read(index + 1, namelen) == b'pc.ram': - # total_size = self._context.object(self._qemu_table_name + constants.BANG + 'unsigned long long', - # offset = index + 1 + namelen, - # layer_name = self._base_layer) + total_size = self._context.object(self._qemu_table_name + constants.BANG + 'unsigned long long', + offset = index + 1 + namelen, + layer_name = self._base_layer) + size_array[base_layer.read(index + 1, namelen)] = total_size index += 1 + namelen + 8 namelen = self._context.object(self._qemu_table_name + constants.BANG + 'unsigned char', offset = index, layer_name = self._base_layer) + if size_array.get(b'pc.ram', + max([x[0] for x in self.pci_hole_table.values()]) + 1) <= self._pci_hole_minimum: + # Turns off the pci_hole if it's not supposed to be there + vollog.debug( + f"QEVM tunrning off PCI hole due to small image size: {size_array.get(b'pc.ram'):x} < {self._pci_hole_minimum:x}") + self._pci_hole_start, self._pci_hole_end = 0, 0 + if flags & (self.SEGMENT_FLAG_COMPRESS | self.SEGMENT_FLAG_PAGE): if not (flags & self.SEGMENT_FLAG_CONTINUE): namelen = self._context.object(self._qemu_table_name + constants.BANG + 'unsigned char', @@ -185,7 +194,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): # Once all segments have been read, determine the PCI hole if any for regex in self.pci_hole_table: if regex.match(self._architecture): - _, self._pci_hole_start, self._pci_hole_end = self.pci_hole_table[regex] + self._pci_hole_minimum, self._pci_hole_start, self._pci_hole_end = self.pci_hole_table[regex] vollog.log(constants.LOGLEVEL_VVVV, f"QEVM architecture detected as: {self._architecture}") break else: From dd8fcce2ef683aa4bad211ee9273a865a59e84e1 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Wed, 27 Apr 2022 22:30:14 +0100 Subject: [PATCH 09/11] Layers: QEMU improvements suggested by @cstation --- volatility3/framework/layers/qemu.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/volatility3/framework/layers/qemu.py b/volatility3/framework/layers/qemu.py index c755b8a9b..e3d70ba6e 100644 --- a/volatility3/framework/layers/qemu.py +++ b/volatility3/framework/layers/qemu.py @@ -54,7 +54,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): # and location of the memory gap. Deviating hole sizes could eventually be detected for Linux by e.g. scanning # for dmesg entries with a regex like rb'\[mem (0x[0-9a-f]{4,10})-0x[0-9a-f]{4,10}\] available for PCI devices' - distro_re = r"(artful|eoan|rhel[\d\.]+)" + distro_re = r"(\w+[\d\.]?)" pci_hole_table = {re.compile(r"^pc-i440fx-([23456789]|\d\d+)\.\d$"): (0xe0000000, 0xc0000000, 0x100000000), re.compile(r"^pc-i440fx-[01]\.\d$"): (0xe0000000, 0xe0000000, 0x100000000), @@ -141,8 +141,8 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): namelen = self._context.object(self._qemu_table_name + constants.BANG + 'unsigned char', offset = index, layer_name = self._base_layer) - if size_array.get(b'pc.ram', - max([x[0] for x in self.pci_hole_table.values()]) + 1) <= self._pci_hole_minimum: + highest_possible_maximum = max([x[0] for x in self.pci_hole_table.values()]) + 1 + if size_array.get(b'pc.ram', highest_possible_maximum) < self._pci_hole_minimum: # Turns off the pci_hole if it's not supposed to be there vollog.debug( f"QEVM tunrning off PCI hole due to small image size: {size_array.get(b'pc.ram'):x} < {self._pci_hole_minimum:x}") @@ -264,7 +264,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): def _fallback_determine_architecture(self) -> str: architecture_pattern = rb'pc-(i440fx|q35)-(\d{1,2}\.\d{1,2}|[\w\d\.]+)' - old_suffix = "-2.0" + default_suffix = "-2.0" base_layer = self.context.layers[self._base_layer] vollog.log(constants.LOGLEVEL_VVVV, "QEVM fallback architecture detection used") @@ -281,10 +281,10 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): for device in self._configuration.get('devices', []): device_name = device.get('vmsd_name', '').lower() if 'i440fx' in device_name or 'piix' in device_name: - architecture = 'pc-i440fx' + old_suffix + architecture = 'pc-i440fx' + default_suffix break elif 'ich9' in device_name: - architecture = 'pc-q35' + old_suffix + architecture = 'pc-q35' + default_suffix break if architecture: return architecture @@ -295,7 +295,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): for offset in base_layer.scan(context = self.context, scanner = res): line = base_layer.read(offset, 64) regex_results = re.search(architecture_pattern, line) - architecture = "pc-" + regex_results.groups()[0].decode().lower() + old_suffix + architecture = "pc-" + regex_results.groups()[0].decode().lower() + default_suffix return architecture vollog.warning("Could not determine QEMU target architecture!") From 7ce95117484e13ab0ba6a4a50051b56252cca978 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Mon, 23 May 2022 01:20:46 +0100 Subject: [PATCH 10/11] Layers: QEMU recommendations from @cstation --- volatility3/framework/layers/qemu.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/layers/qemu.py b/volatility3/framework/layers/qemu.py index e3d70ba6e..985c4534a 100644 --- a/volatility3/framework/layers/qemu.py +++ b/volatility3/framework/layers/qemu.py @@ -54,7 +54,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): # and location of the memory gap. Deviating hole sizes could eventually be detected for Linux by e.g. scanning # for dmesg entries with a regex like rb'\[mem (0x[0-9a-f]{4,10})-0x[0-9a-f]{4,10}\] available for PCI devices' - distro_re = r"(\w+[\d\.]?)" + distro_re = r"(\w+[\d{1,2}\.]*)" pci_hole_table = {re.compile(r"^pc-i440fx-([23456789]|\d\d+)\.\d$"): (0xe0000000, 0xc0000000, 0x100000000), re.compile(r"^pc-i440fx-[01]\.\d$"): (0xe0000000, 0xe0000000, 0x100000000), @@ -145,7 +145,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): if size_array.get(b'pc.ram', highest_possible_maximum) < self._pci_hole_minimum: # Turns off the pci_hole if it's not supposed to be there vollog.debug( - f"QEVM tunrning off PCI hole due to small image size: {size_array.get(b'pc.ram'):x} < {self._pci_hole_minimum:x}") + f"QEVM tunrning off PCI hole due to small image size: 0x{size_array.get(b'pc.ram'):x} < 0x{self._pci_hole_minimum:x}") self._pci_hole_start, self._pci_hole_end = 0, 0 if flags & (self.SEGMENT_FLAG_COMPRESS | self.SEGMENT_FLAG_PAGE): @@ -179,8 +179,6 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): index = 8 section_info = dict() current_section_id = -1 - version_id = -1 - name = None arch_detected = False while section_byte != self.QEVM_EOF and index <= base_layer.maximum_address: if index > 20 and not arch_detected: @@ -263,7 +261,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): raise exceptions.LayerException(self._name, f'QEMU unknown section encountered: {section_byte}') def _fallback_determine_architecture(self) -> str: - architecture_pattern = rb'pc-(i440fx|q35)-(\d{1,2}\.\d{1,2}|[\w\d\.]+)' + architecture_pattern = rb'pc-(i440fx|q35)-(\d{1,2}\.\d{1,2}|\w+[\d{1,2}\.]*)' default_suffix = "-2.0" base_layer = self.context.layers[self._base_layer] @@ -287,6 +285,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): architecture = 'pc-q35' + default_suffix break if architecture: + vollog.log(constants.LOGLEVEL_VVV, f'Architecture version unknown, default used: {default_suffix}') return architecture # Still haven't found architecture, switch to fallback-method @@ -296,6 +295,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): line = base_layer.read(offset, 64) regex_results = re.search(architecture_pattern, line) architecture = "pc-" + regex_results.groups()[0].decode().lower() + default_suffix + vollog.log(constants.LOGLEVEL_VVV, f'Architecture version unknown, default used: {default_suffix}') return architecture vollog.warning("Could not determine QEMU target architecture!") From 6276b984008868161638971c8e7bf0eeddc1ea1a Mon Sep 17 00:00:00 2001 From: ikelos Date: Mon, 23 May 2022 01:48:07 +0100 Subject: [PATCH 11/11] Update volatility3/framework/layers/qemu.py Fix typo courtesy of @digitalisx Co-authored-by: Donghyun Kim --- volatility3/framework/layers/qemu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/layers/qemu.py b/volatility3/framework/layers/qemu.py index 985c4534a..907116e99 100644 --- a/volatility3/framework/layers/qemu.py +++ b/volatility3/framework/layers/qemu.py @@ -145,7 +145,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): if size_array.get(b'pc.ram', highest_possible_maximum) < self._pci_hole_minimum: # Turns off the pci_hole if it's not supposed to be there vollog.debug( - f"QEVM tunrning off PCI hole due to small image size: 0x{size_array.get(b'pc.ram'):x} < 0x{self._pci_hole_minimum:x}") + f"QEVM turning off PCI hole due to small image size: 0x{size_array.get(b'pc.ram'):x} < 0x{self._pci_hole_minimum:x}") self._pci_hole_start, self._pci_hole_end = 0, 0 if flags & (self.SEGMENT_FLAG_COMPRESS | self.SEGMENT_FLAG_PAGE):