diff --git a/volatility/framework/symbols/windows/__init__.py b/volatility/framework/symbols/windows/__init__.py index 7bc0a1fdd..dbc3fe028 100644 --- a/volatility/framework/symbols/windows/__init__.py +++ b/volatility/framework/symbols/windows/__init__.py @@ -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) diff --git a/volatility/framework/symbols/windows/extensions/__init__.py b/volatility/framework/symbols/windows/extensions/__init__.py index 35e8470cc..07ccbb70d 100644 --- a/volatility/framework/symbols/windows/extensions/__init__.py +++ b/volatility/framework/symbols/windows/extensions/__init__.py @@ -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] diff --git a/volatility/plugins/windows/handles.py b/volatility/plugins/windows/handles.py new file mode 100644 index 000000000..f8efb94f9 --- /dev/null +++ b/volatility/plugins/windows/handles.py @@ -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()))