Files
volatility3/volatility/framework/layers/intel.py
T
Mike Auty 3dc161f9b4 Rework Translation Layers to tie more closely to configurations
This change is quite signficant, and requires that TranslationLayers
get all additional parameters that they need through their requirements.

These are now automatically enumerated and populated on object
construction based on the requirements, so should not require lots of
repetitive filling out of fields.

It does come with the downside that TranslationLayers can only be
contructed with a context (and appropiate config), but TLs in particular
always require a context (to contain the base layer) and blank configs
can be constructed relatively easily (convenience functions can be added
if necessary).

This allows configuration trees to be built up, and their configs
spliced into an existing config (as if it were being loaded from a
file).

Not all ConstructableRequirements use this method, since SymbolTables
(for example) do not have access to the context or config_path in order
to get to any parameters stored in the context's config.  They therefore
are still passed their requirement values as __init__ parameters
instead.
2016-08-22 02:20:10 +01:00

205 lines
8.2 KiB
Python

"""
Created on 7 May 2013
@author: mike
"""
import math
import struct
from volatility.framework import exceptions, interfaces
from volatility.framework.configuration import requirements
class Intel(interfaces.layers.TranslationLayerInterface):
"""Translation Layer for the Intel IA32 memory mapping"""
priority = 40
provides = {"type": "memory",
"architecture": "ia32"
}
def __init__(self, context, config_path, name):
super().__init__(context, config_path, name)
self._base_layer = self._check_type(self.config["memory_layer"], str)
self._page_map_offset = self._check_type(self.config["page_map_offset"], int)
# All Intel address spaces work on 4096 byte pages
self._page_size_in_bits = 12
# These can vary depending on the type of space
self._entry_format = "<I"
self._bits_per_register = 32
self._maxphyaddr = 32
self._maxvirtaddr = self._maxphyaddr
self._index_shift = int(math.log(struct.calcsize(self._entry_format), 2))
self._structure = [('page directory', 10, False),
('page table', 10, True)]
@property
def minimum_address(self):
return 0
@property
def maximum_address(self):
return (2 ** self._maxvirtaddr) - 1
@staticmethod
def _mask(value, high_bit, low_bit):
"""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):
"""Returns whether a particular page is valid based on its entry"""
return entry & 1
def _translate(self, offset):
"""Translates a specific offset based on paging tables
Returns the offset and the pagesize
"""
# 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
entry = self._mask(self._page_map_offset, self._bits_per_register - 1, 0) | 0x1
position = min(self._maxvirtaddr, self._bits_per_register) - 1
# 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.InvalidAddressException(self.name, offset,
"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)
# Create the offset for the next entry
table_offset = base_address | (index << self._index_shift)
# Read out the new entry from memory
entry, = struct.unpack(self._entry_format, self._context.memory.read(self._base_layer, table_offset,
struct.calcsize(self._entry_format)))
# Now we're done
if not self._page_is_valid(entry):
raise exceptions.InvalidAddressException(self.name, offset,
"Page Fault at entry " + hex(entry) + " in page entry")
page = self._mask(entry, self._maxphyaddr - 1, position + 1) | self._mask(offset, position, 0)
return page, 1 << (position + 1)
def is_valid(self, offset, length = 1):
"""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[self._base_layer].is_valid(mapped_offset) for _, mapped_offset, _, _ in
self.mapping(offset, length)])
except exceptions.InvalidAddressException:
return False
def mapping(self, offset, length, ignore_errors = False):
"""Returns a sorted iterable of (offset, mapped_offset, length, layer) mappings
This allows translation layers to provide maps of contiguous regions in one layer
"""
result = []
if length == 0:
if ignore_errors and not self.is_valid(offset):
raise StopIteration
mapped_offset, _ = self._translate(offset)
yield (offset, mapped_offset, length, self._base_layer)
raise StopIteration
while length > 0:
if ignore_errors:
while not self.is_valid(offset) and length > 0:
length -= 1 << self._page_size_in_bits
offset += 1 << self._page_size_in_bits
if length <= 0:
raise StopIteration
chunk_offset, page_size = self._translate(offset)
chunk_size = min(page_size - (chunk_offset % page_size), length)
yield (offset, chunk_offset, chunk_size, self._base_layer)
length -= chunk_size
offset += chunk_size
@property
def dependencies(self):
"""Returns a list of the lower layer names that this layer is dependent upon"""
# TODO: Add in the whole buffalo
return [self._base_layer]
@classmethod
def get_requirements(cls):
return [requirements.TranslationLayerRequirement(name = 'memory_layer',
optional = False),
requirements.TranslationLayerRequirement(name = 'swap_layer',
optional = True),
requirements.IntRequirement(name = 'page_map_offset',
optional = False)]
class IntelPAE(Intel):
"""Class for handling Physical Address Extensions for Intel architectures"""
priority = 35
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# These can vary depending on the type of space
self._entry_format = "<Q"
self._bits_per_register = 32
self._maxphyaddr = 40
self._maxvirtaddr = self._maxphyaddr
self._index_shift = int(math.log(struct.calcsize(self._entry_format), 2))
self._structure = [('page directory pointer', 2, False),
('page directory', 9, True),
('page table', 9, True)]
class Intel32e(Intel):
priority = 30
provides = {"type": "memory",
"architecture": "ia64"
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# These can vary depending on the type of space
self._entry_format = "<Q"
self._bits_per_register = 64
self._maxphyaddr = 52
self._maxvirtaddr = 48
self._index_shift = int(math.log(struct.calcsize(self._entry_format), 2))
self._structure = [('page map layer 4', 9, False),
('page directory pointer', 9, True),
('page directory', 9, True),
('page table', 9, True)]
class WindowsMixin(object):
@staticmethod
def _page_is_valid(entry):
"""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 (entry & 1) or ((entry & 1 << 11) and not entry & 1 << 10)