# This file was contributed to the Volatility Framework Version 3. # Copyright (C) 2018 Volatility Foundation. # # THE LICENSED WORK IS PROVIDED UNDER THE TERMS OF THE Volatility Contributors # Public License V1.0("LICENSE") AS FIRST COMPLETED BY: Volatility Foundation, # Inc. ANY USE, PUBLIC DISPLAY, PUBLIC PERFORMANCE, REPRODUCTION OR DISTRIBUTION # OF, OR PREPARATION OF SUBSEQUENT WORKS, DERIVATIVE WORKS OR DERIVED WORKS BASED # ON, THE LICENSED WORK CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS LICENSE AND ITS # TERMS, WHETHER OR NOT SUCH RECIPIENT READS THE TERMS OF THE LICENSE. "LICENSED # WORK,” “RECIPIENT" AND “DISTRIBUTOR" ARE DEFINED IN THE LICENSE. A COPY OF THE # LICENSE IS LOCATED IN THE TEXT FILE ENTITLED "LICENSE.txt" ACCOMPANYING THE # CONTENTS OF THIS FILE. IF A COPY OF THE LICENSE DOES NOT ACCOMPANY THIS FILE, A # COPY OF THE LICENSE MAY ALSO BE OBTAINED AT THE FOLLOWING WEB SITE: # https://www.volatilityfoundation.org/license/vcpl_v1.0 # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the # specific language governing rights and limitations under the License. # import collections import logging import math import struct from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, TypeVar from volatility.framework import exceptions, interfaces from volatility.framework.configuration import requirements vollog = logging.getLogger(__name__) _T = TypeVar("_T") _S = TypeVar("_S") class classproperty(object): """Class property decorator Note this will change the return type """ def __init__(self, func: Callable[[_S], _T]) -> None: self._func = func def __get__(self, _owner_self, owner_cls: _S) -> _T: return self._func(owner_cls) class Intel(interfaces.layers.TranslationLayerInterface): """Translation Layer for the Intel IA32 memory mapping""" priority = 40 _entry_format = " None: super().__init__(context = context, config_path = config_path, name = name, metadata = metadata) self._base_layer = self.config["memory_layer"] self._swap_layers = [] # type: List[str] self._page_map_offset = self.config["page_map_offset"] # These can vary depending on the type of space self._index_shift = int(math.ceil(math.log2(struct.calcsize(self._entry_format)))) @classproperty def page_size(cls) -> int: """Page size for the intel memory layers All Intel layers work on 4096 byte pages""" return 1 << cls._page_size_in_bits @classproperty def bits_per_register(cls) -> int: """Returns the bits_per_register to determine the range of an IntelTranslationLayer""" return cls._bits_per_register @classproperty def minimum_address(cls) -> int: return 0 @classproperty def maximum_address(cls) -> int: return (1 << cls._maxvirtaddr) - 1 @classproperty def structure(cls) -> List[Tuple[str, int, bool]]: return cls._structure @staticmethod def _mask(value: int, high_bit: int, low_bit: int) -> int: """Returns the bits of a value between highbit and lowbit inclusive""" high_mask = (2 ** (high_bit + 1)) - 1 low_mask = (2 ** low_bit) - 1 mask = (high_mask ^ low_mask) # print(high_bit, low_bit, bin(mask), bin(value)) return value & mask @staticmethod def _page_is_valid(entry: int) -> bool: """Returns whether a particular page is valid based on its entry""" return bool(entry & 1) def _translate(self, offset: int) -> Tuple[int, int, str]: """Translates a specific offset based on paging tables Returns the translated offset, the contiguous pagesize that the translated address lives in and the layer_name that the address lives in """ entry, position = self._translate_entry(offset) # Now we're done if not self._page_is_valid(entry): raise exceptions.PagedInvalidAddressException(self.name, offset, position + 1, entry, "Page Fault at entry {} in page entry".format(hex(entry))) page = self._mask(entry, self._maxphyaddr - 1, position + 1) | self._mask(offset, position, 0) return page, 1 << (position + 1), self._base_layer def _translate_entry(self, offset): """Translates a specific offset based on paging tables Returns the translated entry value """ # Setup the entry and how far we are through the offset # Position maintains the number of bits left to process # We or with 0x1 to ensure our page_map_offset is always valid position = min(self._maxvirtaddr, self._bits_per_register) - 1 entry = self._mask(self._page_map_offset, position, 0) | 0x1 # Run through the offset in various chunks for (name, size, large_page) in self._structure: # Check we're valid if not self._page_is_valid(entry): raise exceptions.PagedInvalidAddressException(self.name, offset, position + 1, entry, "Page Fault at entry " + hex(entry) + " in table " + name) # Check if we're a large page if large_page and (entry & (1 << 7)): # We're a large page, the rest is finished below # If we want to implement PSE-36, it would need to be done here break # Figure out how much of the offset we should be using start = position position -= size index = self._mask(offset, start, position + 1) >> (position + 1) # Grab the base address of the table we'll be getting the next entry from base_address = self._mask(entry, self._maxphyaddr - 1, size + self._index_shift) table = self._context.memory.read(self._base_layer, base_address, self.page_size) # If the table is entirely duplicates, then mark the whole table as bad if (table == table[:struct.calcsize(self._entry_format)] * (self.page_size // struct.calcsize(self._entry_format))): raise exceptions.PagedInvalidAddressException(self.name, offset, position + 1, entry, "Page Fault at entry " + hex(entry) + " in table " + name) # Read the data for the next entry entry_data = table[( index << self._index_shift):(index << self._index_shift) + struct.calcsize(self._entry_format)] # Read out the new entry from memory entry, = struct.unpack(self._entry_format, entry_data) return entry, position def is_valid(self, offset: int, length: int = 1) -> bool: """Returns whether the address offset can be translated to a valid address""" try: # TODO: Consider reimplementing this, since calls to mapping can call is_valid return all([ self._context.memory[layer].is_valid(mapped_offset) for _, mapped_offset, _, layer in self.mapping(offset, length) ]) except exceptions.InvalidAddressException: return False def mapping(self, offset: int, length: int, ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, str]]: """Returns a sorted iterable of (offset, mapped_offset, length, layer) mappings This allows translation layers to provide maps of contiguous regions in one layer """ if length == 0: try: mapped_offset, _, layer_name = self._translate(offset) if not self._context.memory[layer_name].is_valid(mapped_offset): raise exceptions.InvalidAddressException(layer_name = layer_name, invalid_address = mapped_offset) except exceptions.InvalidAddressException: if not ignore_errors: raise return yield (offset, mapped_offset, length, layer_name) return while length > 0: try: chunk_offset, page_size, layer_name = self._translate(offset) chunk_size = min(page_size - (chunk_offset % page_size), length) if not self._context.memory[layer_name].is_valid(chunk_offset, chunk_size): raise exceptions.InvalidAddressException(layer_name = layer_name, invalid_address = chunk_offset) except (exceptions.PagedInvalidAddressException, exceptions.InvalidAddressException) as excp: if not ignore_errors: raise # We can jump more if we know where the page fault failed if isinstance(excp, exceptions.PagedInvalidAddressException): mask = (1 << excp.invalid_bits) - 1 else: mask = (1 << self._page_size_in_bits) - 1 length_diff = (mask + 1 - (offset & mask)) length -= length_diff offset += length_diff else: yield (offset, chunk_offset, chunk_size, layer_name) length -= chunk_size offset += chunk_size @property def dependencies(self) -> List[str]: """Returns a list of the lower layer names that this layer is dependent upon""" return [self._base_layer] + self._swap_layers @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: return [ requirements.TranslationLayerRequirement(name = 'memory_layer', optional = False), requirements.LayerListRequirement(name = 'swap_layers', optional = True), requirements.IntRequirement(name = 'page_map_offset', optional = False), requirements.IntRequirement(name = 'kernel_virtual_offset', optional = True), requirements.StringRequirement(name = 'kernel_banner', optional = True) ] class IntelPAE(Intel): """Class for handling Physical Address Extensions for Intel architectures""" priority = 35 _entry_format = " bool: """Returns whether a particular page is valid based on its entry Windows uses additional "available" bits to store flags These flags allow windows to determine whether a page is still valid Bit 11 is the transition flag, and Bit 10 is the prototype flag For more information, see Windows Internals (6th Ed, Part 2, pages 268-269) """ return bool((entry & 1) or ((entry & 1 << 11) and not entry & 1 << 10)) def _translate_swap(self, layer: Intel, offset: int, bit_offset: int): try: return super()._translate(offset) except exceptions.PagedInvalidAddressException as excp: entry = excp.entry tbit = bool(entry & (1 << 11)) pbit = bool(entry & (1 << 10)) unknown_bit = bool(entry & (1 << 7)) n = (entry >> 1) & 0xF vbit = bool(entry & 1) if (not tbit and not pbit and not vbit and unknown_bit) and ((entry >> bit_offset) != 0): swap_offset = entry >> bit_offset << excp.invalid_bits if layer.config.get('swap_layers', False): swap_layer_name = layer.config.get( interfaces.configuration.path_join('swap_layers', 'swap_layers' + str(n)), None) if swap_layer_name: return swap_offset, 1 << excp.invalid_bits, swap_layer_name raise exceptions.SwappedInvalidAddressException( layer_name = excp.layer_name, invalid_address = excp.invalid_address, invalid_bits = excp.invalid_bits, entry = excp.entry, swap_offset = swap_offset) raise ### These must be full separate classes so that JSON configs re-create them properly class WindowsIntel(WindowsMixin, Intel): def _translate(self, offset): return self._translate_swap(self, offset, self._page_size_in_bits) class WindowsIntelPAE(WindowsMixin, IntelPAE): def _translate(self, offset): return self._translate_swap(self, offset, self._bits_per_register) class WindowsIntel32e(WindowsMixin, Intel32e): def _translate(self, offset): return self._translate_swap(self, offset, self._bits_per_register // 2)