Files
volatility3/development/nlpdtbfinder.py
T

183 lines
6.2 KiB
Python

"""nlpdtbfinder is an OS-agnostic DTB finder for Intel32e 4-layer paging.
It should be extensible to other forms of paging.
Some of the masks used are heuristics, but have been tested against recent
images from OSX, Windows, and Linux.
Adapted from dtbfinder.py.
"""
import sys
# TODO: Remove this once we install volatility
sys.path += ".."
from volatility.framework import contexts
from volatility.framework import interfaces
from volatility.framework import layers
from volatility.framework import exceptions
import struct
PAGE_SIZE = 0x1000
PHYS_MASK = 0xfffffffffff
PML4_ENTRY_SIZE = int((2 ** 64) / 512)
class PML4EScanner(interfaces.layers.ScannerInterface):
overlap = 0x4000
def __call__(self, data, data_offset):
# print("offset: 0x%x len: %d" % (data_offset, len(data)))
# go through each page in the data, look for signs of PML4
for page_offset in range(0, len(data), PAGE_SIZE):
entries = struct.unpack('<512Q', data[page_offset:page_offset+PAGE_SIZE])
valid_entries = []
invalid_count = 0
user_count = 0
supervisor_count = 0
entry_num = 0
for e in entries:
# print("PML4E: " + bin(e))
if (e & 0b10111011) == 0b00100011:
# print("It's valid!")
valid_entries.append((entry_num, e))
if (e & 0b100):
user_count = user_count + 1
else:
supervisor_count = supervisor_count + 1
elif e != 0:
invalid_count = invalid_count + 1
entry_num = entry_num + 1
# print("[%x] inv: %d val: %d" % (data_offset + page_offset, invalid_count, len(valid_entries)))
if invalid_count == 0 and len(valid_entries) > 4 and user_count != 0 and supervisor_count != 0:
yield (data_offset + page_offset, valid_entries)
def find_pt_mapping(ctx, layer_name, entries):
for entry in entries:
# valid entry?
# print("pt: " + bin(entry))
if (entry & 0b1111011) == 0b1100011:
return True
return False
def find_pd_mapping(ctx, layer_name, entries):
for entry in entries:
# valid entry?
# print("pd: " + bin(entry))
if not ((entry & 0b1111011) == 0b1100011):
continue
# bit 7 is large page
if (entry & 0b10000000):
return True
pt_offset = (entry & PHYS_MASK) >> 12
try:
pt = ctx.memory.read(baselayer_name, pt_offset, PAGE_SIZE)
except exceptions.InvalidAddressException:
# print("page fault at " + hex(pt_offset))
return False
pt_entries = struct.unpack('<512Q', pt)
if find_pt_mapping(ctx, layer_name, pt_entries):
return True
return False
def find_pdpt_mapping(ctx, layer_name, entries):
for entry in entries:
# valid entry?
# print("pdpte: " + bin(entry))
if not (entry & 1):
continue
# bit 7 is large page
if (entry & 0b10000000):
return True
pd_offset = (entry & PHYS_MASK) >> 12
try:
pd = ctx.memory.read(baselayer_name, pd_offset, PAGE_SIZE)
except exceptions.InvalidAddressException:
# print("page fault at " + hex(pd_offset))
return False
pd_entries = struct.unpack('<512Q', pd)
if find_pd_mapping(ctx, layer_name, pd_entries):
return True
return False
def find_pml4_mapping(ctx, layer_name, entries):
"""Walks through the list of PML4 (idx, entry) entries looking for the
first valid (and present) mapping"""
for (idx, entry) in entries:
# print("PML4E [%d]: %s" % (idx, bin(entry)))
pdpte_offset = (entry & PHYS_MASK) >> 12
try:
pdpte = ctx.memory.read(baselayer_name, pdpte_offset, PAGE_SIZE)
except exceptions.InvalidAddressException:
# print("page fault at " + hex(pdpte_offset))
return False
pdpte_entries = struct.unpack('<512Q', pdpte)
if find_pdpt_mapping(ctx, layer_name, pdpte_entries):
return True
return False
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("filenames", metavar = "FILE", nargs = "+", action = "store", help = "FILE to read for testing")
parser.add_argument("-l", "--lime", action = "store_true", dest = "lime", help = "All files are LIME format")
parser.add_argument("-v", "--verbose", action = "count", default = 0,
help = "Increase the verbosity of the information returned")
args = parser.parse_args()
ctx = contexts.Context()
for filename in args.filenames:
ctx.config[
interfaces.configuration.path_join('config' + str(args.filenames.index(filename)), "filename")] = filename
data = layers.physical.FileLayer(ctx,
'config' + str(args.filenames.index(filename)),
'data' + str(args.filenames.index(filename)))
ctx.memory.add_layer(data)
if args.lime:
ctx.config[interfaces.configuration.path_join('lime-config' + str(args.filenames.index(filename)),
"base_layer")] = 'data' + str(args.filenames.index(filename))
data = layers.lime.LimeLayer(ctx,
'lime-config' + str(args.filenames.index(filename)),
'lime-data' + str(args.filenames.index(filename)))
ctx.memory.add_layer(data)
layername = 'data'
if args.lime:
layername = 'lime-data'
print(str(ctx.config))
for i in range(len(args.filenames)):
print("[*] Scanning " + args.filenames[i] + "...")
baselayer_name = layername + str(i)
scan_results = ctx.memory[baselayer_name].scan(ctx, PML4EScanner())
for (dtb, entries) in scan_results:
# print("trying: " + hex(dtb))
if find_pml4_mapping(ctx, baselayer_name, entries):
print("[!] %x" % dtb)
print()