Files
volatility3/volatility/framework/automagic/windows.py
T

199 lines
8.3 KiB
Python

if __name__ == "__main__":
import os
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')))
import struct
from volatility.framework import interfaces, layers, validity
from volatility.framework.configuration import requirements
PAGE_SIZE = 0x1000
class DtbTest(validity.ValidityRoutines):
super_bit = 2
def __init__(self, layer_type = None, ptr_struct = None, ptr_reference = None, mask = None):
self.layer_type = self._check_class(layer_type, interfaces.layers.TranslationLayerInterface)
self.ptr_struct = self._check_type(ptr_struct, str)
self.ptr_size = struct.calcsize(ptr_struct)
self.ptr_reference = self._check_type(ptr_reference, int)
self.mask = self._check_type(mask, int)
def unpack(self, value):
return struct.unpack("<" + self.ptr_struct, value)[0]
def __call__(self, data, data_offset, page_offset):
value = data[page_offset + (self.ptr_reference * self.ptr_size):page_offset + (
(self.ptr_reference + 1) * self.ptr_size)]
ptr = self.unpack(value)
# The value *must* be present (bit 0) since it's a mapped page
# It's almost always writable (bit 1)
# It's occasionally Super, but not reliably so, haven't checked when/why not
# The top 3-bits are usually ignore (which in practice means 0
# Need to find out why the middle 3-bits are usually 6 (0110)
if ptr != 0 and (ptr & self.mask == data_offset + page_offset) & (ptr & 0xFF1 == 0x61):
dtb = (ptr & self.mask)
return self.second_pass(dtb, data, data_offset)
def second_pass(self, dtb, data, data_offset):
page = data[dtb - data_offset:dtb - data_offset + PAGE_SIZE]
usr_count, sup_count = 0, 0
for i in range(0, PAGE_SIZE, self.ptr_size):
val = self.unpack(page[i:i + self.ptr_size])
if val & 0x1:
sup_count += 0 if (val & 0x4) else 1
usr_count += 1 if (val & 0x4) else 0
# print(hex(dtb), usr_count, sup_count, usr_count + sup_count)
# We sometimes find bogus DTBs at 0x16000 with a very low sup_count and 0 usr_count
# I have a winxpsp2-x64 image with identical usr/sup counts at 0x16000 and 0x24c00 as well as the actual 0x3c3000
if usr_count or sup_count > 5:
return dtb
class DtbTest32bit(DtbTest):
def __init__(self):
super().__init__(layer_type = layers.intel.Intel,
ptr_struct = "I",
ptr_reference = 0x300,
mask = 0xFFFFF000)
class DtbTest64bit(DtbTest):
def __init__(self):
super().__init__(layer_type = layers.intel.Intel32e,
ptr_struct = "Q",
ptr_reference = 0x1ED,
mask = 0x3FFFFFFFFFF000)
class DtbTestPae(DtbTest):
def __init__(self):
super().__init__(layer_type = layers.intel.IntelPAE,
ptr_struct = "Q",
ptr_reference = 0x3,
mask = 0x3FFFFFFFFFF000)
def second_pass(self, dtb, data, data_offset):
dtb -= 0x4000
# If we're not in something that the overlap would pick up
if dtb - data_offset >= 0:
pointers = data[dtb - data_offset + (3 * self.ptr_size): dtb - data_offset + (4 * self.ptr_size)]
val = self.unpack(pointers)
if (val & self.mask == dtb + 0x4000) and (val & 0xFFF == 0x001):
return dtb
class DtbSelfReferential(DtbTest):
def __init__(self, layer_type, ptr_struct, ptr_reference, mask):
super().__init__(layer_type = layer_type,
ptr_struct = ptr_struct,
ptr_reference = ptr_reference,
mask = mask)
def __call__(self, data, data_offset, page_offset):
page = data[page_offset:page_offset + PAGE_SIZE]
if not page:
return
ref_pages = set()
for ref in range(0, PAGE_SIZE, self.ptr_size):
ptr_data = page[ref:ref + self.ptr_size]
if len(ptr_data) == self.ptr_size:
ptr, = struct.unpack(self.ptr_struct, ptr_data)
if ((ptr & self.mask) == (data_offset + page_offset)) and (data_offset + page_offset > 0):
ref_pages.add(ref)
# The DTB is extremely unlikely to refer back to itself. so the number of reference should always be exactly 1
if len(ref_pages) == 1:
return (data_offset + page_offset), ref_pages
class DtbSelfRef32bit(DtbSelfReferential):
def __init__(self):
super().__init__(layer_type = layers.intel.Intel, ptr_struct = "I", ptr_reference = 0x300,
mask = 0xFFFFF000)
class DtbSelfRef64bit(DtbSelfReferential):
def __init__(self):
super().__init__(layer_type = layers.intel.Intel32e, ptr_struct = "Q", ptr_reference = 0x1ED,
mask = 0x3FFFFFFFFFF000)
class PageMapScanner(interfaces.layers.ScannerInterface):
overlap = 0x4000
tests = [DtbTest32bit, DtbTest64bit, DtbTestPae]
def __init__(self, tests):
super().__init__()
for value in tests:
self._check_type(value, DtbTest)
self.tests = tests
def __call__(self, data, data_offset):
results = {}
for test in self.tests:
results[test] = set()
for test in self.tests:
for page_offset in range(0, len(data), PAGE_SIZE):
result = test(data, data_offset, page_offset)
if result is not None:
yield (test, result)
class WintelHelper(interfaces.automagic.AutomagicInterface, interfaces.automagic.StackerLayerInterface):
priority = 20
stack_order = 90
tests = [DtbTest32bit(), DtbTest64bit(), DtbTestPae()]
def branch_leave(self, node, config_path):
"""Ensure we're called on internal nodes as well as external"""
self(node, config_path)
return True
def __call__(self, context, config_path, requirement):
useful = []
sub_config_path = interfaces.configuration.path_join(config_path, requirement.name)
if (isinstance(requirement, requirements.TranslationLayerRequirement) and
requirement.requirements.get("class", None)):
class_req = requirement.requirements["class"]
for test in self.tests:
if (test.layer_type.__module__ + "." + test.layer_type.__name__ ==
class_req.config_value(context, sub_config_path)):
useful.append(test)
# Determine if a class has been chosen
# Once an appropriate class has been chosen, attempt to determine the page_map_offset value
if ("memory_layer" in requirement.requirements and
requirement.requirements["memory_layer"].validate(context, sub_config_path)):
physical_layer = requirement.requirements["memory_layer"].config_value(context, sub_config_path)
hits = context.memory[physical_layer].scan(context, PageMapScanner(useful))
for test, dtb in hits:
context.config[interfaces.configuration.path_join(sub_config_path, "page_map_offset")] = dtb
requirement.construct(context, config_path)
break
else:
for subreq in requirement.requirements.values():
self(context, sub_config_path, subreq)
@classmethod
def stack(cls, context, layer_name):
"""Attempts to determine and stack an intel layer on a physical layer where possible"""
hits = context.memory[layer_name].scan(context, PageMapScanner(cls.tests))
layer = None
for test, dtb in hits:
new_layer_name = context.memory.free_layer_name("IntelLayer")
config_path = interfaces.configuration.path_join("IntelHelper", new_layer_name)
context.config[interfaces.configuration.path_join(config_path, "memory_layer")] = layer_name
context.config[interfaces.configuration.path_join(config_path, "page_map_offset")] = dtb
layer = test.layer_type(context,
config_path = config_path,
name = new_layer_name)
break
return layer