import codecs import datetime import json import logging import os import typing import volatility.framework.interfaces.plugins as interfaces_plugins from volatility.framework import exceptions, renderers, constants from volatility.framework.configuration import requirements from volatility.framework.layers.physical import BufferDataLayer from volatility.framework.layers.registry import RegistryHive from volatility.framework.objects import utility from volatility.framework.renderers import format_hints from volatility.framework.symbols import intermed vollog = logging.getLogger(__name__) class UserAssist(interfaces_plugins.PluginInterface): """"Print userassist registry keys and information""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._userassist_size = 0 self._userassist_type_name = "_VOL_USERASSIST_TYPES_7" self._reg_table_name = None self._win7 = None # taken from http://msdn.microsoft.com/en-us/library/dd378457%28v=vs.85%29.aspx self._folder_guids = json.load(open(os.path.join(os.path.dirname(__file__), "userassist.json"), "rb")) @classmethod def get_requirements(cls): return [requirements.TranslationLayerRequirement(name = 'primary', description = 'Kernel Address Space', architectures = ["Intel32", "Intel64"]), requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"), requirements.IntRequirement(name = 'offset', description = "Hive Offset", default = None, optional = True)] def parse_userassist_data(self, reg_val): """Reads the raw data of a _CM_KEY_VALUE and returns a dict of userassist fields""" item = { "id": renderers.UnparsableValue(), "count": renderers.UnparsableValue(), "focus": renderers.UnparsableValue(), "time": renderers.UnparsableValue(), "lastupdated": renderers.UnparsableValue(), "rawdata": renderers.UnparsableValue(), } userassist_data = reg_val.decode_data() if userassist_data is None: return item item["rawdata"] = userassist_data if self._win7 is None: # if OS is still unknown at this point, return the default item which just has the rawdata return item if len(userassist_data) < self._userassist_size: return item userassist_layer_name = self.context.memory.free_layer_name("userassist_buffer") buffer = BufferDataLayer(self.context, self._config_path, userassist_layer_name, userassist_data) self.context.add_layer(buffer) userassist_obj = self.context.object( symbol = self._reg_table_name + constants.BANG + self._userassist_type_name, layer_name = userassist_layer_name, offset = 0) if self._win7: item["id"] = renderers.NotApplicableValue() item["count"] = int(userassist_obj.Count) seconds = (userassist_obj.FocusTime + 500) / 1000.0 time = datetime.timedelta(seconds = seconds) if seconds > 0 else userassist_obj.FocusTime item["focus"] = int(userassist_obj.FocusCount) item["time"] = str(time) else: item["id"] = int(userassist_obj.ID) item["count"] = int(userassist_obj.CountStartingAtFive if userassist_obj.CountStartingAtFive < 5 else userassist_obj.CountStartingAtFive - 5) item["focus"] = renderers.NotApplicableValue() item["time"] = renderers.NotApplicableValue() item["lastupdated"] = utility.wintime_to_datetime(userassist_obj.LastUpdated.QuadPart) return item def _determine_userassist_type(self) -> None: """Determine the userassist type and size depending on the OS version""" if self._win7 is True: self._userassist_type_name = "_VOL_USERASSIST_TYPES_7" elif self._win7 is False: self._userassist_type_name = "_VOL_USERASSIST_TYPES_XP" self._userassist_size = self.context.symbol_space.get_type( self._reg_table_name + constants.BANG + self._userassist_type_name).size def _win7_or_later(self) -> bool: # TODO: change this if there is a better way of determining the OS version # _KUSER_SHARED_DATA.CookiePad is in Windows 6.1 (Win7) and later return self.context.symbol_space.get_type( self.config['nt_symbols'] + constants.BANG + "_KUSER_SHARED_DATA").has_member('CookiePad') def list_userassist(self, hive: RegistryHive) -> typing.Generator: """Generate userassist data for a registry hive.""" hive_name = hive.hive.cast(self.config["nt_symbols"] + constants.BANG + "_CMHIVE").get_name() if self._win7 is None: try: self._win7 = self._win7_or_later() except exceptions.SymbolError: # self._win7 will be None and only registry value rawdata will be output pass self._determine_userassist_type() userassist_node_path = hive.get_key("software\\microsoft\\windows\\currentversion\\explorer\\userassist", return_list = True) if not userassist_node_path: vollog.warning("list_userassist did not find a valid node_path (or None)") raise StopIteration userassist_node = userassist_node_path[-1] # iterate through the GUIDs under the userassist key for guidkey in userassist_node.get_subkeys(): # each guid key should have a Count key in it for countkey in guidkey.get_subkeys(): countkey_path = countkey.get_key_path() countkey_last_write_time = utility.wintime_to_datetime(countkey.LastWriteTime.QuadPart) # output the parent Count key result = (0, (renderers.format_hints.Hex(hive.hive_offset), hive_name, countkey_path, countkey_last_write_time, "Key", renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue())) yield result # output any subkeys under Count for subkey in countkey.get_subkeys(): subkey_name = subkey.get_name() result = (1, (renderers.format_hints.Hex(hive.hive_offset), hive_name, countkey_path, countkey_last_write_time, "Subkey", subkey_name, renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(),)) yield result # output any values under Count for value in countkey.get_values(): value_name = value.get_name() try: value_name = codecs.encode(value_name, "rot_13") except UnicodeDecodeError: pass if self._win7: guid = value_name.split("\\")[0] if guid in self._folder_guids: value_name = value_name.replace(guid, self._folder_guids[guid]) userassist_data_dict = self.parse_userassist_data(value) result = (1, (renderers.format_hints.Hex(hive.hive_offset), hive_name, countkey_path, countkey_last_write_time, "Value", value_name, userassist_data_dict["id"], userassist_data_dict["count"], userassist_data_dict["focus"], userassist_data_dict["time"], userassist_data_dict["lastupdated"], format_hints.HexBytes(userassist_data_dict["rawdata"]),)) yield result def _generator(self): # get all the user hive offsets or use the one specified if self.config.get('offset', None) is None: try: import volatility.plugins.windows.hivelist as hivelist hive_offsets = [hive.vol.offset for hive in hivelist.HiveList.list_hives(context = self.context, layer_name = self.config['primary'], symbol_table = self.config['nt_symbols'], filter_string = "ntuser.dat")] except ImportError: vollog.warning("Unable to import windows.hivelist plugin, please provide a hive offset") raise ValueError("Unable to import windows.hivelist plugin, please provide a hive offset") else: hive_offsets = [self.config['offset']] self._reg_table_name = intermed.IntermediateSymbolTable.create(self.context, self._config_path, 'windows', 'registry') for hive_offset in hive_offsets: # Construct the hive reg_config_path = self.make_subconfig(hive_offset = hive_offset, base_layer = self.config['primary'], nt_symbols = self.config['nt_symbols']) hive_name = None try: hive = RegistryHive(self.context, reg_config_path, name = 'hive' + hex(hive_offset)) hive_name = hive.hive.cast(self.config["nt_symbols"] + constants.BANG + "_CMHIVE").get_name() self.context.memory.add_layer(hive) yield from self.list_userassist(hive) continue except exceptions.PagedInvalidAddressException as excp: vollog.debug("Invalid address identified in Hive: {}".format(hex(excp.invalid_address))) except KeyError: vollog.debug("Key '{}' not found in Hive at offset {}.".format( "software\\microsoft\\windows\\currentversion\\explorer\\userassist", hex(hive_offset))) # yield UnreadableValues when an exception occurs for a given hive_offset result = (0, (renderers.format_hints.Hex(hive_offset), hive_name if hive_name else renderers.UnreadableValue(), renderers.UnreadableValue(), renderers.UnreadableValue(), renderers.UnreadableValue(), renderers.UnreadableValue(), renderers.UnreadableValue(), renderers.UnreadableValue(), renderers.UnreadableValue(), renderers.UnreadableValue(), renderers.UnreadableValue(), renderers.UnreadableValue())) yield result def run(self): return renderers.TreeGrid([("Hive Offset", renderers.format_hints.Hex), ("Hive Name", str), ("Path", str), ("Last Write Time", datetime.datetime), ("Type", str), ("Name", str), ("ID", int), ("Count", int), ("Focus Count", int), ("Time Focused", str), ("Last Updated", datetime.datetime), ("Raw Data", format_hints.HexBytes)], self._generator())