mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-08-29 03:09:48 +02:00
218 lines
9.2 KiB
Python
218 lines
9.2 KiB
Python
if __name__ == "__main__":
|
|
import os
|
|
import sys
|
|
|
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
|
|
|
|
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])
|
|
# Don't reduce the tuple until after all the sorting's complete
|
|
for test in tests:
|
|
hits[test.layer_type] = [x for _, x in hits.get(test.layer_type, [])]
|
|
return hits
|
|
|
|
|
|
class DtbTest(validity.ValidityRoutines):
|
|
super_bit = 2
|
|
|
|
def __init__(self, layer_type = None, ptr_size = None, ptr_struct = None, ptr_reference = None, mask = 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)
|
|
self.mask = self._check_type(mask, 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 & self.mask == page_offset) & (ptr & 0xFF1 == 0x61):
|
|
dtb = (ptr & self.mask)
|
|
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)
|
|
# We sometimes find bogus DTBs at 0x16000 with a very low sup_count and 0 usr_count
|
|
if usr_count or sup_count > 5:
|
|
return (usr_count, -sup_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,
|
|
mask = 0xFFFFF000)
|
|
|
|
|
|
class DtbTest64bit(DtbTest):
|
|
def __init__(self):
|
|
DtbTest.__init__(self,
|
|
layer_type = layers.intel.Intel32e,
|
|
ptr_size = 8,
|
|
ptr_struct = "Q",
|
|
ptr_reference = 0x1ED,
|
|
mask = 0x3FFFFFFFFFF000)
|
|
|
|
|
|
class DtbTestPae(DtbTest):
|
|
def __init__(self):
|
|
DtbTest.__init__(self,
|
|
layer_type = layers.intel.IntelPAE,
|
|
ptr_size = 8,
|
|
ptr_struct = "Q",
|
|
ptr_reference = 0x3,
|
|
mask = 0x3FFFFFFFFFF000)
|
|
|
|
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 & self.mask == dtb + 0x4000) and (val & 0xFFF == 0x001):
|
|
return val, dtb
|
|
|
|
|
|
class SelfReferentialTest(object):
|
|
def __init__(self):
|
|
self.ptr_struct = "Q"
|
|
self.ptr_size = 8
|
|
self.layer_type = layers.intel.Intel32e
|
|
self.mask = 0x3FFFFFFFFFF000
|
|
|
|
def run(self, page_offset, ctx, layer_name):
|
|
data = ctx.memory.read(layer_name, page_offset, PAGE_SIZE)
|
|
response = None
|
|
for i in range(0, PAGE_SIZE, self.ptr_size):
|
|
value = struct.unpack("<" + self.ptr_struct, data[i:i + self.ptr_size])[0] & self.mask
|
|
if value == page_offset and value != 0:
|
|
response = (i // self.ptr_size, page_offset)
|
|
print(hex(response[1]), hex(response[0]))
|
|
return response
|
|
|
|
|
|
class PageMapOffsetHelper(interfaces.configuration.HierachicalVisitor):
|
|
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 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, 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_map_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_map_offset"] = hits[test.layer_type][0]
|
|
else:
|
|
# Delete the node rather than fixing the constraints,
|
|
# since the requirements haven't changed, but some of the candidates are no longer valid
|
|
# If the constraints were global across the tree, then tagging the constraints may be more useful
|
|
del node.candidates[test.layer_type]
|
|
return True
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import argparse
|
|
|
|
from volatility.framework.symbols import native
|
|
from volatility.framework import contexts
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("filenames", metavar = "FILE", nargs = "+", action = "store", help = "FILE to read for testing")
|
|
parser.add_argument("--32bit", action = "store_false", dest = "bit32", help = "Disable 32-bit run")
|
|
parser.add_argument("--64bit", action = "store_false", dest = "bit64", help = "Disable 64-bit run")
|
|
parser.add_argument("--pae", action = "store_false", help = "Disable pae run")
|
|
parser.add_argument("--generic", action = "store_true", help = "Enable generic scan")
|
|
|
|
args = parser.parse_args()
|
|
|
|
nativelst = native.x86NativeTable
|
|
ctx = contexts.Context(nativelst)
|
|
for filename in args.filenames:
|
|
data = layers.physical.FileLayer(ctx,
|
|
'config' + str(args.filenames.index(filename)),
|
|
'data' + str(args.filenames.index(filename)),
|
|
filename = filename)
|
|
ctx.memory.add_layer(data)
|
|
|
|
tests = []
|
|
if args.bit32:
|
|
tests.append(DtbTest32bit())
|
|
if args.bit64:
|
|
tests.append(DtbTest64bit())
|
|
if args.pae:
|
|
tests.append(DtbTestPae())
|
|
if args.generic:
|
|
tests.append(SelfReferentialTest())
|
|
|
|
if tests:
|
|
for i in range(len(args.filenames)):
|
|
print("[*] Scanning " + args.filenames[i] + "...")
|
|
hits = scan(ctx, "data" + str(i), tests)
|
|
for key in tests:
|
|
arch_hits = hits.get(key.layer_type, [])
|
|
if arch_hits:
|
|
print(" ", key.layer_type.__name__ + ": " + repr([hex(x) for x in sorted(arch_hits)]))
|
|
guesses = []
|
|
for key in hits:
|
|
guesses.append((len(hits[key]), key.__name__, hits[key]))
|
|
num, arch, dtbs = max(guesses)
|
|
if num:
|
|
print("[!] OS Guess:", arch, "with DTB", hex(dtbs[0]))
|
|
else:
|
|
print("[X] No DTBs found")
|
|
print()
|
|
else:
|
|
print("[X] No tests selected")
|