From e71b72b3a76971aa4b07faef865cf954006a0929 Mon Sep 17 00:00:00 2001 From: Michael Ligh Date: Thu, 5 Sep 2019 19:13:17 -0500 Subject: [PATCH] add the windows callbacks plugin --- .../framework/plugins/windows/callbacks.py | 240 ++++++++++++++++++ .../symbols/windows/callbacks-x64.json | 150 +++++++++++ .../symbols/windows/callbacks-x86.json | 150 +++++++++++ 3 files changed, 540 insertions(+) create mode 100644 volatility/framework/plugins/windows/callbacks.py create mode 100644 volatility/framework/symbols/windows/callbacks-x64.json create mode 100644 volatility/framework/symbols/windows/callbacks-x86.json diff --git a/volatility/framework/plugins/windows/callbacks.py b/volatility/framework/plugins/windows/callbacks.py new file mode 100644 index 000000000..0f1c37c6d --- /dev/null +++ b/volatility/framework/plugins/windows/callbacks.py @@ -0,0 +1,240 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl_v1.0 +# + +from typing import List, Iterable, Tuple +import logging +import volatility.framework.interfaces.plugins as interfaces_plugins +from volatility.framework import constants, exceptions, renderers, interfaces, symbols +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.plugins.windows import ssdt +from volatility.plugins.windows import svcscan +from volatility.framework.symbols import intermed +vollog = logging.getLogger(__name__) + + +class Callbacks(interfaces_plugins.PluginInterface): + """Lists kernel callbacks and notification routines""" + + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement( + name = 'primary', description = 'Memory layer for the kernel', architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols") + ] + + @staticmethod + def create_callback_table(context: interfaces.context.ContextInterface, symbol_table: str, config_path: str) -> str: + + native_types = context.symbol_space[symbol_table].natives + is_64bit = symbols.symbol_table_is_64bit(context, symbol_table) + table_mapping = {"nt_symbols": symbol_table} + + if is_64bit: + symbol_filename = "callbacks-x64" + else: + symbol_filename = "callbacks-x86" + + return intermed.IntermediateSymbolTable.create( + context, + config_path, + "windows", + symbol_filename, + native_types = native_types, + table_mapping = table_mapping) + + @classmethod + def list_notify_routines(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str, + config_path: str, callback_table_name: str) -> Iterable[Tuple[str, int, str]]: + """Lists all kernel notification routines""" + + kvo = context.layers[layer_name].config['kernel_virtual_offset'] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + + is_vista_or_later = svcscan.SvcScan.is_vista_or_later(context = context, symbol_table = symbol_table) + full_type_name = callback_table_name + constants.BANG + "_GENERIC_CALLBACK" + + symbol_names = [("PspLoadImageNotifyRoutine", False), ("PspCreateThreadNotifyRoutine", True), + ("PspCreateProcessNotifyRoutine", True)] + + for symbol_name, extended_list in symbol_names: + + try: + symbol_offset = ntkrnlmp.get_symbol(symbol_name).address + except exceptions.SymbolError: + vollog.debug("Cannot find {}".format(symbol_name)) + continue + + if is_vista_or_later and extended_list: + count = 64 + else: + count = 8 + + fast_refs = ntkrnlmp.object(object_type="array", + offset=symbol_offset, + subtype=ntkrnlmp.get_type("_EX_FAST_REF"), + count=count) + + for fast_ref in fast_refs: + try: + callback = fast_ref.dereference().cast(full_type_name) + except exceptions.InvalidAddressException: + continue + + if callback.Callback != 0: + yield symbol_name, callback.Callback, None + + @classmethod + def list_registry_callbacks(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str, + config_path: str, callback_table_name: str) -> Iterable[Tuple[str, int, str]]: + """Lists all registry callbacks""" + + kvo = context.layers[layer_name].config['kernel_virtual_offset'] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + full_type_name = callback_table_name + constants.BANG + "_EX_CALLBACK_ROUTINE_BLOCK" + + try: + symbol_offset = ntkrnlmp.get_symbol("CmpCallBackVector").address + symbol_count_offset = ntkrnlmp.get_symbol("CmpCallBackCount").address + except exceptions.SymbolError: + vollog.debug("Cannot find CmpCallBackVector or CmpCallBackCount") + return + + callback_count = ntkrnlmp.object(object_type="unsigned int", + offset=symbol_count_offset) + + if callback_count == 0: + return + + fast_refs = ntkrnlmp.object(object_type="array", + offset=symbol_offset, + subtype=ntkrnlmp.get_type("_EX_FAST_REF"), + count=callback_count) + + for fast_ref in fast_refs: + try: + callback = fast_ref.dereference().cast(full_type_name) + except exceptions.InvalidAddressException: + continue + + if callback.Function != 0: + yield "CmRegisterCallback", callback.Function, None + + @classmethod + def list_bugcheck_reason_callbacks(cls, context: interfaces.context.ContextInterface, layer_name: str, + symbol_table: str, config_path: str, + callback_table_name: str) -> Iterable[Tuple[str, int, str]]: + """Lists all kernel bugcheck reason callbacks""" + + kvo = context.layers[layer_name].config['kernel_virtual_offset'] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + + try: + list_offset = ntkrnlmp.get_symbol("KeBugCheckReasonCallbackListHead").address + except exceptions.SymbolError: + vollog.debug("Cannot find KeBugCheckReasonCallbackListHead") + return + + full_type_name = callback_table_name + constants.BANG + "_KBUGCHECK_REASON_CALLBACK_RECORD" + callback_record = context.object( + object_type = full_type_name, offset = kvo + list_offset, layer_name = layer_name) + + for callback in callback_record.Entry: + + if not context.layers[layer_name].is_valid(callback.CallbackRoutine): + continue + + try: + component = context.object( + symbol_table + constants.BANG + "string", + layer_name = layer_name, + offset = callback.Component, + max_length = 64, + errors = "replace") + except exceptions.InvalidAddressException: + component = renderers.UnreadableValue() + + yield "KeBugCheckReasonCallbackListHead", callback.CallbackRoutine, component + + @classmethod + def list_bugcheck_callbacks(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str, + config_path: str, callback_table_name: str) -> Iterable[Tuple[str, int, str]]: + """Lists all kernel bugcheck callbacks""" + + kvo = context.layers[layer_name].config['kernel_virtual_offset'] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + + try: + list_offset = ntkrnlmp.get_symbol("KeBugCheckCallbackListHead").address + except exceptions.SymbolError: + vollog.debug("Cannot find KeBugCheckCallbackListHead") + return + + full_type_name = callback_table_name + constants.BANG + "_KBUGCHECK_CALLBACK_RECORD" + callback_record = context.object(full_type_name, offset = kvo + list_offset, layer_name = layer_name) + + for callback in callback_record.Entry: + + if not context.layers[layer_name].is_valid(callback.CallbackRoutine): + continue + + try: + component = context.object( + symbol_table + constants.BANG + "string", + layer_name = layer_name, + offset = callback.Component, + max_length = 64, + errors = "replace") + except exceptions.InvalidAddressException: + component = renderers.UnreadableValue() + + yield "KeBugCheckCallbackListHead", callback.CallbackRoutine, component + + def _generator(self): + + callback_table_name = self.create_callback_table(self.context, self.config["nt_symbols"], self.config_path) + + collection = ssdt.SSDT.build_module_collection(self.context, self.config['primary'], self.config['nt_symbols']) + + callback_methods = (self.list_notify_routines, self.list_bugcheck_callbacks, + self.list_bugcheck_reason_callbacks, self.list_registry_callbacks) + + for callback_method in callback_methods: + for callback_type, callback_address, callback_detail in callback_method( + self.context, self.config['primary'], self.config['nt_symbols'], self.config_path, + callback_table_name): + + if callback_detail is None: + detail = renderers.NotApplicableValue() + else: + detail = callback_detail + + module_symbols = list(collection.get_module_symbols_by_absolute_location(callback_address)) + + if module_symbols: + for module_name, symbol_generator in module_symbols: + symbols_found = False + + # we might have multiple symbols pointing to the same location + for symbol in symbol_generator: + symbols_found = True + yield (0, (callback_type, format_hints.Hex(callback_address), module_name, + symbol.split(constants.BANG)[1], detail)) + + # no symbols, but we at least can report the module name + if not symbols_found: + yield (0, (callback_type, format_hints.Hex(callback_address), module_name, + renderers.NotAvailableValue(), detail)) + else: + # no module was found at the absolute location + yield (0, (callback_type, format_hints.Hex(callback_address), renderers.NotAvailableValue(), + renderers.NotAvailableValue(), detail)) + + def run(self): + + return renderers.TreeGrid([("Type", str), ("Callback", format_hints.Hex), ("Module", str), ("Symbol", str), + ("Detail", str)], self._generator()) diff --git a/volatility/framework/symbols/windows/callbacks-x64.json b/volatility/framework/symbols/windows/callbacks-x64.json new file mode 100644 index 000000000..dbb6086df --- /dev/null +++ b/volatility/framework/symbols/windows/callbacks-x64.json @@ -0,0 +1,150 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_GENERIC_CALLBACK": { + "fields": { + "Callback": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 8 + } + }, + "kind": "struct", + "size": 16 + }, + "_KBUGCHECK_CALLBACK_RECORD": { + "fields": { + "Entry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CallbackRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 16 + }, + "Component": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 40 + } + }, + "kind": "struct", + "size": 64 + }, + "_KBUGCHECK_REASON_CALLBACK_RECORD": { + "fields": { + "Entry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CallbackRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 16 + }, + "Component": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 40 + } + }, + "kind": "struct", + "size": 64 + }, + "_EX_CALLBACK_ROUTINE_BLOCK": { + "fields": { + "Function": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 8 + } + }, + "kind": "struct", + "size": 64 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "mhl by hand", + "datetime": "2019-08-27T18:17:16.417006" + }, + "format": "4.0.0" + } +} \ No newline at end of file diff --git a/volatility/framework/symbols/windows/callbacks-x86.json b/volatility/framework/symbols/windows/callbacks-x86.json new file mode 100644 index 000000000..cf0cb8b65 --- /dev/null +++ b/volatility/framework/symbols/windows/callbacks-x86.json @@ -0,0 +1,150 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_GENERIC_CALLBACK": { + "fields": { + "Callback": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 4 + } + }, + "kind": "struct", + "size": 8 + }, + "_KBUGCHECK_CALLBACK_RECORD": { + "fields": { + "Entry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CallbackRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 8 + }, + "Component": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 20 + } + }, + "kind": "struct", + "size": 32 + }, + "_KBUGCHECK_REASON_CALLBACK_RECORD": { + "fields": { + "Entry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CallbackRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 8 + }, + "Component": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 28 + }, + "_EX_CALLBACK_ROUTINE_BLOCK": { + "fields": { + "Function": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 4 + } + }, + "kind": "struct", + "size": 28 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "mhl by hand", + "datetime": "2019-08-27T18:17:16.417006" + }, + "format": "4.0.0" + } +} \ No newline at end of file