From c6a3c2d6b1df407415feb0b7d381d9779baf67f8 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 21 Feb 2016 00:42:04 +0000 Subject: [PATCH] Add in the automagic and allow mid-deptree resolution (without trying to re-fulfill already populated requirements). --- volatility/cli/__init__.py | 7 +- volatility/framework/automagic/fileformat.py | 0 volatility/framework/automagic/windows.py | 121 ++++++++++++++++++ .../framework/configuration/depresolver.py | 24 ++-- 4 files changed, 137 insertions(+), 15 deletions(-) create mode 100644 volatility/framework/automagic/fileformat.py create mode 100644 volatility/framework/automagic/windows.py diff --git a/volatility/cli/__init__.py b/volatility/cli/__init__.py index 3197f2626..b224f5485 100644 --- a/volatility/cli/__init__.py +++ b/volatility/cli/__init__.py @@ -55,10 +55,9 @@ class CommandLine(object): windows = True if windows: # Traverse the dependency tree and tag the config with the appropriate page_map_offset values where not already applied - wdf = windows_automagic.WindowsDtbFinder(volatility.framework.class_subclasses(windows_automagic.DtbTest)) - dldr.configurable_visitor(deptree, context = ctx, path = config_path, - visitor = windows_automagic.PageMapOffsetHelper()) - print(repr(deptree)) + deptree.traverse(windows_automagic.PageMapOffsetHelper(context = ctx), + config_path = config_path, + short_circuit = False) # Walk down the tree attempting to fulfil each requirement (recursive) and backtrack when necessary # Translate the parsed args to a context configuration diff --git a/volatility/framework/automagic/fileformat.py b/volatility/framework/automagic/fileformat.py new file mode 100644 index 000000000..e69de29bb diff --git a/volatility/framework/automagic/windows.py b/volatility/framework/automagic/windows.py new file mode 100644 index 000000000..9b35cb474 --- /dev/null +++ b/volatility/framework/automagic/windows.py @@ -0,0 +1,121 @@ +import struct + +from volatility.framework import interfaces, layers, validity, configuration +from volatility.framework.configuration import depresolver + +PAGE_SIZE = 0x1000 + + +def scan(ctx, layer_name, tests): + """Scans through layer_name at context and returns the best-guess layer type and a single best-guess DTB + + It should be noted that this is automagical and therefore not the guaranteed correct response + The UI should always provide the user an opportunity to specify the appropriate types and DTB values themselves + """ + hits = {} + for offset in range(ctx.memory[layer_name].minimum_address, + ctx.memory[layer_name].maximum_address - PAGE_SIZE, + PAGE_SIZE): + for test in tests: + val = test.run(offset, ctx, layer_name) + if val: + hits[test.layer_type] = sorted(hits.get(test.layer_type, []) + [val]) + return hits + + +class DtbTest(validity.ValidityRoutines): + super_bit = 2 + + def __init__(self, layer_type = None, ptr_size = None, ptr_struct = None, ptr_reference = None): + self.layer_type = self._check_class(layer_type, interfaces.layers.TranslationLayerInterface) + self.ptr_size = self._check_type(ptr_size, int) + self.ptr_struct = self._check_type(ptr_struct, str) + self.ptr_reference = self._check_type(ptr_reference, int) + + def unpack(self, value): + return struct.unpack("<" + self.ptr_struct, value)[0] + + def run(self, page_offset, ctx, layer_name): + value = ctx.memory.read(layer_name, page_offset + (self.ptr_reference * self.ptr_size), + 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 & 0xFFFFFFFFFFFFF000 == page_offset) & (ptr & 0xFF1 == 0x61): + dtb = (ptr & 0xFFFFFFFFFFFFF000) + return self.second_pass(dtb, ctx, layer_name) + + def second_pass(self, dtb, ctx, layer_name): + data = ctx.memory.read(layer_name, dtb, PAGE_SIZE) + usr_count, sup_count = 0, 0 + for i in range(0, PAGE_SIZE, self.ptr_size): + val = self.unpack(data[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) + if usr_count: + return usr_count, dtb + + +class DtbTest32bit(DtbTest): + def __init__(self): + DtbTest.__init__(self, + layer_type = layers.intel.Intel, + ptr_size = 4, + ptr_struct = "I", + ptr_reference = 0x300) + + +class DtbTest64bit(DtbTest): + def __init__(self): + DtbTest.__init__(self, + layer_type = layers.intel.Intel32e, + ptr_size = 8, + ptr_struct = "Q", + ptr_reference = 0x1ED) + + +class DtbTestPae(DtbTest): + def __init__(self): + DtbTest.__init__(self, + layer_type = layers.intel.IntelPAE, + ptr_size = 8, + ptr_struct = "Q", + ptr_reference = 0x3) + + def second_pass(self, dtb, ctx, layer_name): + dtb -= 0x4000 + data = ctx.memory.read(layer_name, dtb, PAGE_SIZE) + val = self.unpack(data[3 * self.ptr_size: 4 * self.ptr_size]) + if (val & 0xFFFFFFFFFFFFF000 == dtb + 0x4000) and (val & 0xFFF == 0x001): + return val, dtb + + +class PageMapOffsetHelper(interfaces.configuration.ReqTreeVisitorInterface): + def __init__(self, context): + self.ctx = self._check_type(context, interfaces.context.ContextInterface) + self.tests = dict([(test.layer_type, test) for test in [DtbTest32bit(), DtbTest64bit(), DtbTestPae()]]) + + def __call__(self, node, config_path): + if isinstance(node, depresolver.RequirementTreeChoice): + useful = [] + for candidate in node.candidates: + if candidate in self.tests: + useful.append(self.tests[candidate]) + if useful: + depresolver.DependencyResolver().validate_dependencies(node.candidates[useful[0].layer_type], self.ctx, + config_path) + prefix = config_path + configuration.CONFIG_SEPARATOR + memory_layer = self.ctx.config.get(prefix + "memory_layer", None) + page_table_offset = self.ctx.config.get(prefix + "page_table_offset", None) + if page_table_offset is None and memory_layer is not None: + hits = scan(self.ctx, memory_layer, useful) + for test in useful: + if hits.get(test.layer_type, []): + self.ctx.config[prefix + "page_table_offset"] = hits[test.layer_type][0][1] + print("CONFIG", dict(self.ctx.config)) + return True diff --git a/volatility/framework/configuration/depresolver.py b/volatility/framework/configuration/depresolver.py index 43f7faa75..ea66a8584 100644 --- a/volatility/framework/configuration/depresolver.py +++ b/volatility/framework/configuration/depresolver.py @@ -59,7 +59,7 @@ class DependencyResolver(validity.ValidityRoutines): if path is None: path = "" - self._check_type(deptree, RequirementTreeList) + self._check_type(deptree, interfaces.configuration.RequirementTreeNode) visitor = ValidatorVisitor(context) return deptree.traverse(visitor, path, short_circuit = True) @@ -114,16 +114,18 @@ class ValidatorVisitor(interfaces.configuration.ReqTreeVisitorInterface): return True if isinstance(node, RequirementTreeChoice) and not node.requirement.optional: - for provider in node.candidates: - try: - provider.fulfill(self.ctx, node.requirement, config_path) - break - except Exception as e: - pass - else: - logging.debug( - "Unable to fulfill requirement " + repr(node.requirement) + " - no fulfillable candidates") - return False + if self.ctx.config.get(config_path, None) is None: + # Only try to provide when we're not already sorted + for provider in node.candidates: + try: + provider.fulfill(self.ctx, node.requirement, config_path) + break + except Exception as e: + pass + else: + logging.debug( + "Unable to fulfill requirement " + repr(node.requirement) + " - no fulfillable candidates") + return False try: value = self.ctx.config[config_path]