Merge pull request #13 from volatilityfoundation/windows-handles

add the windows handles plugin
This commit is contained in:
ikelos
2017-12-13 20:47:53 +00:00
committed by GitHub
3 changed files with 416 additions and 1 deletions
@@ -15,6 +15,11 @@ class WindowsKernelIntermedSymbols(intermed.IntermediateSymbolTable):
self.set_type_class('_LIST_ENTRY', extensions._LIST_ENTRY)
self.set_type_class('_EPROCESS', extensions._EPROCESS)
self.set_type_class('_UNICODE_STRING', extensions._UNICODE_STRING)
self.set_type_class('_EX_FAST_REF', extensions._EX_FAST_REF)
self.set_type_class('_OBJECT_HEADER', extensions._OBJECT_HEADER)
self.set_type_class('_FILE_OBJECT', extensions._FILE_OBJECT)
self.set_type_class('_DEVICE_OBJECT', extensions._DEVICE_OBJECT)
self.set_type_class('_CM_KEY_BODY', extensions._CM_KEY_BODY)
self.set_type_class('_CMHIVE', registry._CMHIVE)
self.set_type_class('_CM_KEY_NODE', registry._CM_KEY_NODE)
self.set_type_class('_CM_KEY_VALUE', registry._CM_KEY_VALUE)
@@ -2,10 +2,110 @@ import collections.abc
from volatility.framework import constants, objects
from volatility.framework.symbols import generic
from volatility.framework import exceptions
# Keep these in a basic module, to prevent import cycles when symbol providers require them
class _EX_FAST_REF(objects.Struct):
"""This is a standard Windows structure that stores a pointer to an
object but also leverages the least significant bits to encode additional
details. When dereferencing the pointer, we need to strip off the extra bits."""
def dereference(self):
if constants.BANG not in self.vol.type_name:
raise ValueError("Invalid symbol table name syntax (no {} found)".format(constants.BANG))
# the mask value is different on 32 and 64 bits
symbol_table_name = self.vol.type_name.split(constants.BANG)[0]
if self._context.symbol_space.get_type(symbol_table_name + constants.BANG + "pointer").size == 4:
max_fast_ref = 7
else:
max_fast_ref = 15
return self._context.object(symbol_table_name + constants.BANG + "pointer", layer_name = self.vol.layer_name, offset = self.Object & ~max_fast_ref)
class ExecutiveObject(object):
"""This is used as a "mixin" that provides all kernel executive
objects with a means of finding their own object header."""
def object_header(self):
if constants.BANG not in self.vol.type_name:
raise ValueError("Invalid symbol table name syntax (no {} found)".format(constants.BANG))
symbol_table_name = self.vol.type_name.split(constants.BANG)[0]
body_offset = self._context.symbol_space.get_type(symbol_table_name + constants.BANG + "_OBJECT_HEADER").relative_child_offset("Body")
return self._context.object(symbol_table_name + constants.BANG + "_OBJECT_HEADER", layer_name = self.vol.layer_name, offset = self.vol.offset - body_offset)
class _CM_KEY_BODY(objects.Struct):
"""This represents an open handle to a registry key and
is not tied to the registry hive file format on disk."""
@property
def helper_full_key_name(self):
output = []
kcb = self.KeyControlBlock
while kcb.ParentKcb:
if kcb.NameBlock.Name == None:
break
output.append(kcb.NameBlock.Name.cast("string",
encoding = "utf8",
max_length = kcb.NameBlock.NameLength,
errors = "replace"))
kcb = kcb.ParentKcb
return "\\".join(reversed(output))
class _DEVICE_OBJECT(objects.Struct, ExecutiveObject):
@property
def helper_device_name(self):
header = self.object_header()
return header.NameInfo.Name.String
class _FILE_OBJECT(objects.Struct, ExecutiveObject):
def file_name_with_device(self):
name = ""
if self._context.memory[self.vol.layer_name].is_valid(self.DeviceObject):
name = "\\Device\\{}".format(self.DeviceObject.helper_device_name)
try:
name += self.FileName.String
except exceptions.PagedInvalidAddressException:
pass
return name
class _OBJECT_HEADER(objects.Struct):
@property
def NameInfo(self):
if constants.BANG not in self.vol.type_name:
raise ValueError("Invalid symbol table name syntax (no {} found)".format(constants.BANG))
symbol_table_name = self.vol.type_name.split(constants.BANG)[0]
try:
header_offset = ord(self.NameInfoOffset)
except AttributeError:
#http://codemachine.com/article_objectheader.html (Windows 7 and later)
name_info_bit = 0x2
layer = self._context.memory[self.vol.layer_name]
kvo = layer.config.get("kernel_virtual_offset", None)
if kvo == None:
raise AttributeError("Could not find kernel_virtual_offset for layer: {}".format(self.vol.layer_name))
ntkrnlmp = self._context.module(symbol_table_name, layer_name = self.vol.layer_name, offset = kvo)
address = ntkrnlmp.get_symbol("ObpInfoMaskToOffset").address
calculated_index = ord(self.InfoMask) & (name_info_bit | (name_info_bit - 1))
header_offset = ord(self._context.object(symbol_table_name + constants.BANG + "unsigned char",
layer_name = self.vol.layer_name,
offset = kvo + address + calculated_index))
header = self._context.object(symbol_table_name + constants.BANG + "_OBJECT_HEADER_NAME_INFO",
layer_name = self.vol.layer_name,
offset = self.vol.offset - header_offset)
return header
class _ETHREAD(objects.Struct):
def owning_process(self, kernel_layer = None):
@@ -43,6 +143,9 @@ class _EPROCESS(generic.GenericIntelProcess):
def load_order_modules(self):
"""Generator for DLLs in the order that they were loaded"""
if constants.BANG not in self.vol.type_name:
raise ValueError("Invalid symbol table name syntax (no {} found)".format(constants.BANG))
proc_layer_name = self.add_process_layer(self._context)
proc_layer = self._context.memory[proc_layer_name]
+307
View File
@@ -0,0 +1,307 @@
import volatility.framework.interfaces.plugins as interfaces_plugins
import volatility.plugins.windows.pslist as pslist
from volatility.framework import exceptions, renderers
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
from volatility.framework import constants
from volatility.framework.objects import utility
import logging
vollog = logging.getLogger()
try:
import capstone
has_capstone = True
except ImportError:
has_capstone = False
class Handles(interfaces_plugins.PluginInterface):
"""Lists process open handles"""
def __init__(self, context, config_path):
super().__init__(context, config_path)
self._sar_value = None
self._type_map = None
self._cookie = None
self._level_mask = 7
@classmethod
def get_requirements(cls):
# Since we're calling the plugin, make sure we have the plugin's requirements
return pslist.PsList.get_requirements() + []
def _decode_pointer(self, value, magic):
"""Windows encodes pointers to objects and decodes them on the fly
before using them. This function mimics the decoding routine so we
can generate the proper pointer values as well."""
value = value & 0xFFFFFFFFFFFFFFF8
value = value >> magic
#if (value & (1 << 47)):
# value = value | 0xFFFF000000000000
return value
def _get_item(self, handle_table_entry, handle_value):
"""Given a handle table entry (_HANDLE_TABLE_ENTRY) structure from
a process' handle table, determine where the corresponding object's
_OBJECT_HEADER can be found."""
virtual = self.config["primary"]
try:
# before windows 7
if not self.context.memory[virtual].is_valid(handle_table_entry.Object):
return None
fast_ref = handle_table_entry.Object.cast(self.config["nt_symbols"] + constants.BANG + "_EX_FAST_REF")
object_header = fast_ref.dereference().cast(self.config["nt_symbols"] + constants.BANG + "_OBJECT_HEADER")
object_header.GrantedAccess = handle_table_entry.GrantedAccess
except AttributeError:
# starting with windows 8
if handle_table_entry.LowValue == 0:
return None
magic = self.find_sar_value()
# is this the right thing to raise here?
if magic == None:
raise AttributeError("Unable to find the SAR value for decoding handle table pointers")
offset = self._decode_pointer(handle_table_entry.LowValue, magic)
#print("LowValue: {0:#x} Magic: {1:#x} Offset: {2:#x}".format(handle_table_entry.InfoTable, magic, offset))
object_header = self.context.object(self.config["nt_symbols"] + constants.BANG + "_OBJECT_HEADER", virtual, offset = offset)
object_header.GrantedAccess = handle_table_entry.GrantedAccessBits
object_header.HandleValue = handle_value
return object_header
def find_sar_value(self):
"""Locate ObpCaptureHandleInformationEx if it exists in the
sample. Once found, parse it for the SAR value that we need
to decode pointers in the _HANDLE_TABLE_ENTRY which allows us
to find the associated _OBJECT_HEADER."""
if self._sar_value is None:
if not has_capstone:
return None
virtual_layer_name = self.config['primary']
kvo = self.context.memory[virtual_layer_name].config['kernel_virtual_offset']
ntkrnlmp = self.context.module(self.config["nt_symbols"], layer_name = virtual_layer_name, offset = kvo)
try:
func_addr = ntkrnlmp.get_symbol("ObpCaptureHandleInformationEx").address
except AttributeError:
return None
data = self.context.memory.read(virtual_layer_name, kvo + func_addr, 0x200)
if data == None:
return None
md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64)
for (address, size, mnemonic, op_str) in md.disasm_lite(data, kvo + func_addr):
#print("{} {} {} {}".format(address, size, mnemonic, op_str))
if mnemonic.startswith("sar"):
# if we don't want to parse op strings, we can disasm the
# single sar instruction again, but we use disasm_lite for speed
self._sar_value = int(op_str.split(",")[1].strip(), 16)
break
return self._sar_value
def list_objects(self):
"""List the executive object types (_OBJECT_TYPE) using the
ObTypeIndexTable or ObpObjectTypes symbol (differs per OS).
This method will be necessary for determining what type of
object we have given an object header.
Note: The object type index map was hard coded into profiles
in vol2, but we generate it dynamically now."""
if self._type_map is None:
self._type_map = {}
virtual_layer = self.config['primary']
kvo = self.context.memory[virtual_layer].config['kernel_virtual_offset']
ntkrnlmp = self.context.module(self.config["nt_symbols"], layer_name = virtual_layer, offset = kvo)
try:
table_addr = ntkrnlmp.get_symbol("ObTypeIndexTable").address
except AttributeError:
table_addr = ntkrnlmp.get_symbol("ObpObjectTypes").address
ptrs = ntkrnlmp.object(type_name = "array", offset = kvo + table_addr,
subtype = ntkrnlmp.get_type("pointer"),
count = 100)
for i, ptr in enumerate(ptrs):
# the first entry in the table is always null. break the
# loop when we encounter the first null entry after that
if i > 0 and ptr == 0:
break
objt = ptr.dereference().cast(self.config["nt_symbols"] + constants.BANG + "_OBJECT_TYPE")
try:
type_name = objt.Name.String
except exceptions.PagedInvalidAddressException:
vollog.log(constants.LOGLEVEL_VVV, "Cannot access _OBJECT_HEADER.Name at {0:#x}".format(objt.Name.vol.offset))
continue
self._type_map[i] = type_name
return self._type_map
def object_type(self, object_header, type_map):
"""Across all Windows versions, the _OBJECT_HEADER embeds details on the type of
object (i.e. process, file) but the way its embedded differs between versions.
This API abstracts away those details."""
try:
# vista and earlier have a Type member
return object_header.Type.Name.String
except AttributeError:
# windows 7 and later have a TypeIndex, but windows 10
# further encodes the index value with nt1!ObHeaderCookie
virtual = self.config["primary"]
try:
if self._cookie is None:
offset = self.context.symbol_space.get_symbol(self.config["nt_symbols"] + constants.BANG + "ObHeaderCookie").address
kvo = self.context.memory[virtual].config['kernel_virtual_offset']
self._cookie = self.context.object(self.config["nt_symbols"] + constants.BANG + "unsigned int", virtual, offset = kvo + offset)
type_index = ((object_header.vol.offset >> 8) ^ self._cookie ^ ord(object_header.TypeIndex)) & 0xFF
except AttributeError:
type_index = ord(object_header.TypeIndex)
return type_map.get(type_index)
def _make_handle_array(self, offset, level, depth = 0):
"""Parse a process' handle table and yield valid handle table
entries, going as deep into the table "levels" as necessary."""
virtual = self.config["primary"]
kvo = self.context.memory[virtual].config['kernel_virtual_offset']
ntkrnlmp = self.context.module(self.config["nt_symbols"], layer_name = virtual, offset = kvo)
if level > 0:
subtype = ntkrnlmp.get_type("pointer")
count = 0x1000 / subtype.size
else:
subtype = ntkrnlmp.get_type("_HANDLE_TABLE_ENTRY")
count = 0x1000 / subtype.size
if not self.context.memory[virtual].is_valid(offset):
raise StopIteration
table = ntkrnlmp.object(type_name = "array", offset = offset,
subtype = subtype, count = int(count))
layer_object = self.context.memory[virtual]
masked_offset = layer_object._mask(offset, 0, layer_object._maxvirtaddr)
for entry in table:
if level > 0:
for x in self._make_handle_array(entry, level - 1, depth):
yield x
depth += 1
else:
handle_multiplier = 4
handle_level_base = depth * count * handle_multiplier
handle_value = ((entry.vol.offset - masked_offset) /
(subtype.size / handle_multiplier)) + handle_level_base
item = self._get_item(entry, handle_value)
if item == None:
continue
try:
if item.TypeIndex != 0x0:
yield item
except AttributeError:
if item.Type.Name:
yield item
except exceptions.PagedInvalidAddressException:
continue
def handles(self, handle_table):
try:
TableCode = handle_table.TableCode & ~self._level_mask
table_levels = handle_table.TableCode & self._level_mask
except exceptions.PagedInvalidAddressException:
vollog.log(constants.LOGLEVEL_VVV, "Handle table parsing was aborted due to an invalid address exception")
raise StopIteration
for handle_table_entry in self._make_handle_array(TableCode, table_levels):
yield handle_table_entry
def _generator(self, procs):
type_map = self.list_objects()
for proc in procs:
try:
object_table = proc.ObjectTable
except exceptions.PagedInvalidAddressException:
vollog.log(constants.LOGLEVEL_VVV, "Cannot access _EPROCESS.ObjectType at {0:#x}".format(proc.ObjectTable.vol.offset))
continue
process_name = utility.array_to_string(proc.ImageFileName)
for entry in self.handles(object_table):
try:
obj_type = self.object_type(entry, type_map)
if obj_type == None:
continue
if obj_type == "File":
item = entry.Body.cast(self.config["nt_symbols"] + constants.BANG + "_FILE_OBJECT")
obj_name = item.file_name_with_device()
elif obj_type == "Process":
item = entry.Body.cast(self.config["nt_symbols"] + constants.BANG + "_EPROCESS")
obj_name = "{} Pid {}".format(utility.array_to_string(proc.ImageFileName),
item.UniqueProcessId)
elif obj_type == "Thread":
item = entry.Body.cast(self.config["nt_symbols"] + constants.BANG + "_ETHREAD")
obj_name = "Tid {} Pid {}".format(item.Cid.UniqueThread, item.Cid.UniqueProcess)
elif obj_type == "Key":
item = entry.Body.cast(self.config["nt_symbols"] + constants.BANG + "_CM_KEY_BODY")
obj_name = item.helper_full_key_name
else:
try:
obj_name = entry.NameInfo.Name.String
except exceptions.InvalidAddressException:
obj_name = ""
except (exceptions.InvalidAddressException):
vollog.log(constants.LOGLEVEL_VVV, "Cannot access _OBJECT_HEADER at {0:#x}".format(entry.vol.offset))
continue
yield (0, (proc.UniqueProcessId,
process_name,
format_hints.Hex(entry.HandleValue),
obj_type,
format_hints.Hex(entry.GrantedAccess),
obj_name))
def run(self):
plugin = pslist.PsList(self.context, "plugins.Handles")
return renderers.TreeGrid([("PID", int),
("Process", str),
("HandleValue", format_hints.Hex),
("Type", str),
("GrantedAccess", format_hints.Hex),
("Name", str)],
self._generator(plugin.list_processes()))