"""Windows UserAssist information collector."""
import codecs
import logging
from winregrc import data_format
from winregrc import errors
from winregrc import interface
[docs]
class UserAssistEntry:
"""UserAssist entry.
Attributes:
guid (str): GUID.
name (str): name.
value_name (str): name of the Windows Registry value.
"""
[docs]
def __init__(self, guid=None, name=None, value_name=None):
"""Initializes an UserAssist entry.
Args:
guid (Optional[str]): GUID.
name (Optional[str]): name.
value_name (Optional[str]): name of the Windows Registry value.
"""
super().__init__()
self.guid = guid
self.name = name
self.value_name = value_name
[docs]
class UserAssistDataParser(data_format.BinaryDataFormat):
"""UserAssist data parser."""
_DEFINITION_FILE = "userassist.yaml"
# pylint: disable=missing-type-doc
def _DebugPrintEntry(self, format_version, user_assist_entry):
"""Prints UserAssist entry value debug information.
Args:
format_version (int): format version.
user_assist_entry (user_assist_entry_v3|user_assist_entry_v5):
UserAssist entry.
"""
self._DebugPrintValue("Unknown1", f"0x{user_assist_entry.unknown1:08x}")
self._DebugPrintDecimalValue(
"Number of executions", user_assist_entry.number_of_executions
)
if format_version == 5:
self._DebugPrintDecimalValue(
"Application focus count", user_assist_entry.application_focus_count
)
self._DebugPrintDecimalValue(
"Application focus duration",
user_assist_entry.application_focus_duration,
)
self._DebugPrintValue("Unknown2", f"{user_assist_entry.unknown2:.2f}")
self._DebugPrintValue("Unknown3", f"{user_assist_entry.unknown3:.2f}")
self._DebugPrintValue("Unknown4", f"{user_assist_entry.unknown4:.2f}")
self._DebugPrintValue("Unknown5", f"{user_assist_entry.unknown5:.2f}")
self._DebugPrintValue("Unknown6", f"{user_assist_entry.unknown6:.2f}")
self._DebugPrintValue("Unknown7", f"{user_assist_entry.unknown7:.2f}")
self._DebugPrintValue("Unknown8", f"{user_assist_entry.unknown8:.2f}")
self._DebugPrintValue("Unknown9", f"{user_assist_entry.unknown9:.2f}")
self._DebugPrintValue("Unknown10", f"{user_assist_entry.unknown10:.2f}")
self._DebugPrintValue("Unknown11", f"{user_assist_entry.unknown11:.2f}")
self._DebugPrintValue("Unknown12", f"0x{user_assist_entry.unknown12:08x}")
self._DebugPrintFiletimeValue(
"Last execution time", user_assist_entry.last_execution_time
)
if format_version == 5:
self._DebugPrintValue("Unknown13", "0x{user_assist_entry.unknown13:08x}")
self._DebugPrintText("\n")
# pylint: disable=missing-return-type-doc
[docs]
def ParseEntry(self, format_version, entry_data):
"""Parses an UserAssist entry.
Args:
format_version (int): format version.
entry_data (bytes): entry data.
Returns:
user_assist_entry_v3|user_assist_entry_v5: UserAssist entry.
Raises:
ParseError: if the value data could not be parsed.
"""
if format_version == 3:
data_type_map = self._GetDataTypeMap("user_assist_entry_v3")
expected_entry_data_size = 16
elif format_version == 5:
data_type_map = self._GetDataTypeMap("user_assist_entry_v5")
expected_entry_data_size = 72
else:
data_type_map = None
expected_entry_data_size = 0
if expected_entry_data_size != len(entry_data):
entry_data_size = len(entry_data)
raise errors.ParseError(
(
f"Version: {format_version:d} size mismatch (calculated: "
f"{expected_entry_data_size:d}, stored: {entry_data_size:d})."
)
)
try:
user_assist_entry = self._ReadStructureFromByteStream(
entry_data, 0, data_type_map, "UserAssist entry"
)
except (ValueError, errors.ParseError) as exception:
raise errors.ParseError(
f"Unable to parse UserAssist entry value with error: {exception!s}"
)
if self._debug:
self._DebugPrintEntry(format_version, user_assist_entry)
return user_assist_entry
[docs]
class UserAssistCollector(interface.WindowsRegistryKeyCollector):
"""Windows UserAssist information collector.
Returns:
user_assist_entries (list[UserAssistEntry]): UserAssist entries.
"""
_USER_ASSIST_KEY = (
"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\"
"Explorer\\UserAssist"
)
[docs]
def __init__(self, debug=False, output_writer=None):
"""Initializes a Windows UserAssist information collector.
Args:
debug (Optional[bool]): True if debug information should be printed.
output_writer (Optional[OutputWriter]): output writer.
"""
super().__init__(debug=debug)
self._output_writer = output_writer
self._parser = UserAssistDataParser(debug=debug, output_writer=output_writer)
self.user_assist_entries = []
def _CollectUserAssistFromKey(self, guid_subkey):
"""Collects the UserAssist information from a GUID sub key.
Args:
guid_subkey (dfwinreg.WinRegistryKey): UserAssist GUID Registry key.
"""
version_value = guid_subkey.GetValueByName("Version")
if not version_value:
logging.warning(f"Missing Version value in sub key: {guid_subkey.name:s}")
return
format_version = version_value.GetDataAsObject()
if self._debug:
self._output_writer.WriteValue("GUID", guid_subkey.name)
self._output_writer.WriteIntegerValueAsDecimal(
"Format version", format_version
)
self._output_writer.WriteText("\n")
count_subkey = guid_subkey.GetSubkeyByName("Count")
for value in count_subkey.GetValues():
if self._debug:
self._output_writer.WriteValue("Original name", value.name)
try:
# Note that Python 2 codecs.decode() does not support keyword arguments
# such as encodings='rot-13'.
value_name = codecs.decode(value.name, "rot-13")
except UnicodeEncodeError:
characters = []
for character in value.name:
if ord(character) < 128:
try:
character = codecs.decode(character, "rot-13")
characters.append(character)
except UnicodeEncodeError:
characters.append(character)
else:
characters.append(character)
value_name = "".join(characters)
if self._debug:
self._output_writer.WriteValue("Converted name", value_name)
self._output_writer.WriteDebugData("Value data:", value.data)
if value_name != "UEME_CTLSESSION":
user_assist_entry = self._parser.ParseEntry(format_version, value.data)
user_assist_entry = UserAssistEntry(
guid=guid_subkey.name, name=value_name, value_name=value.name
)
self.user_assist_entries.append(user_assist_entry)
[docs]
def Collect(self, registry): # pylint: disable=arguments-differ
"""Collects the UserAssist information.
Args:
registry (dfwinreg.WinRegistry): Windows Registry.
Returns:
bool: True if the UserAssist key was found, False if not.
"""
user_assist_key = registry.GetKeyByPath(self._USER_ASSIST_KEY)
if not user_assist_key:
return False
for guid_subkey in user_assist_key.GetSubkeys():
self._CollectUserAssistFromKey(guid_subkey)
return True