"""Most Recently Used (MRU) collector."""
from dtfabric.runtime import data_maps as dtfabric_data_maps
from winregrc import data_format
from winregrc import errors
[docs]
class MostRecentlyUsedEntry:
"""Most Recently Used (MRU) entry.
Attributes:
key_path (str): path of the Windows Registry key.
shell_item_data (bytes): Shell Item data.
shell_item_list_data (bytes): Shell Item list data.
string (str): string.
value_name (str): name of the Windows Registry value.
"""
[docs]
def __init__(
self,
key_path=None,
shell_item_data=None,
shell_item_list_data=None,
string=None,
value_name=None,
):
"""Initializes a Most Recently Used (MRU) entry.
Args:
key_path (Optional[str]): path of the Windows Registry key.
shell_item_data (Optional[bytes]): Shell Item data.
shell_item_list_data (Optional[bytes]): Shell Item list data.
string (Optional[str]): string.
value_name (Optional[str]): name of the Windows Registry value.
"""
super().__init__()
self.key_path = key_path
self.shell_item_data = shell_item_data
self.shell_item_list_data = shell_item_list_data
self.string = string
self.value_name = value_name
[docs]
class MostRecentlyUsedCollector(data_format.BinaryDataFormat):
"""Most Recently Used (MRU) collector.
Attributes:
mru_entries (list[MostRecentlyUsedEntry]): most recently used (MRU) entries.
"""
_DEFINITION_FILE = "mru.yaml"
_OPENSAVE_MRU_KEY_PATH = (
"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\"
"Explorer\\ComDlg32\\OpenSaveMRU"
).upper()
_SHELL_ITEM_MRU_KEY_PATHS = [
(
"HKEY_CURRENT_USER\\Local Settings\\Software\\Microsoft\\Windows\\"
"Shell\\BagMRU"
),
(
"HKEY_CURRENT_USER\\Local Settings\\Software\\Microsoft\\Windows\\"
"ShellNoRoam\\BagMRU"
),
"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\BagMRU",
("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\ShellNoRoam\\BagMRU"),
(
"HKEY_CURRENT_USER\\Software\\Classes\\Local Settings\\Software\\"
"Microsoft\\Windows\\Shell\\BagMRU"
),
(
"HKEY_CURRENT_USER\\Software\\Classes\\Local Settings\\Software\\"
"Microsoft\\Windows\\ShellNoRoam\\BagMRU"
),
(
"HKEY_CURRENT_USER\\Software\\Classes\\Wow6432Node\\Local Settings\\"
"Software\\Microsoft\\Windows\\Shell\\BagMRU"
),
(
"HKEY_CURRENT_USER\\Software\\Classes\\Wow6432Node\\Local Settings\\"
"Software\\Microsoft\\Windows\\ShellNoRoam\\BagMRU"
),
]
_SHELL_ITEM_MRU_KEY_PATHS = [
key_path.upper() for key_path in _SHELL_ITEM_MRU_KEY_PATHS
]
_SHELL_ITEM_LIST_MRU_KEY_PATHS = [
(
"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\"
"Explorer\\DesktopStreamMRU"
),
(
"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\"
"Explorer\\ComDlg32\\OpenSavePidlMRU"
),
(
"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\"
"Explorer\\StreamMRU"
),
(
"HKEY_CURRENT_USER\\Software\\Classes\\Software\\Microsoft\\Windows\\"
"CurrentVersion\\Explorer\\StreamMRU"
),
]
_SHELL_ITEM_LIST_MRU_KEY_PATHS = [
key_path.upper() for key_path in _SHELL_ITEM_LIST_MRU_KEY_PATHS
]
_STRING_AND_SHELL_ITEM_MRU_KEY_PATHS = [
(
"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\"
"Explorer\\RecentDocs"
)
]
_STRING_AND_SHELL_ITEM_MRU_KEY_PATHS = [
key_path.upper() for key_path in _STRING_AND_SHELL_ITEM_MRU_KEY_PATHS
]
_STRING_AND_SHELL_ITEM_LIST_MRU_KEY_PATHS = [
(
"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\"
"Explorer\\ComDlg32\\LastVisitedPidlMRU"
)
]
_STRING_AND_SHELL_ITEM_LIST_MRU_KEY_PATHS = [
key_path.upper() for key_path in _STRING_AND_SHELL_ITEM_LIST_MRU_KEY_PATHS
]
[docs]
def __init__(self, debug=False, output_writer=None):
"""Initializes a Most Recently Used (MRU) 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.mru_entries = []
def _InKeyPaths(self, key_path, key_paths):
"""Checks if a specific key path is defined in a list of key paths.
Args:
key_path (str): Windows Registry key path.
key_paths (list[str]): list of Windows Registry key paths in upper case.
Returns:
bool: True if the key path is defined in the list of key paths.
"""
key_path = key_path.upper()
for matching_key_path in key_paths:
if key_path.startswith(matching_key_path):
return True
return False
def _ProcessKey(self, registry_key):
"""Processes a Windows Registry key.
Args:
registry_key (dfwinreg.WinRegistryKey): Windows Registry key.
Returns:
bool: True if a Most Recently Used (MRU) key was found, False if not.
"""
result = False
value_names = [
(registry_value.name or "").lower()
for registry_value in registry_key.GetValues()
]
if "mrulist" in value_names:
self._ProcessKeyWithMRUListValue(registry_key)
result = True
elif "mrulistex" in value_names:
self._ProcessKeyWithMRUListExValue(registry_key)
result = True
for subkey in registry_key.GetSubkeys():
if self._ProcessKey(subkey):
result = True
return result
def _ProcessKeyWithMRUListValue(self, registry_key):
"""Processes a Windows Registry key that contains a MRUList value.
Args:
registry_key (dfwinreg.WinRegistryKey): Windows Registry key.
Raises:
ParseError: if the MRUList value could not be parsed.
"""
registry_value = registry_key.GetValueByName("MRUList")
data_type_map = self._GetDataTypeMap("mrulist_entries")
context = dtfabric_data_maps.DataTypeMapContext(
values={"data_size": len(registry_value.data)}
)
try:
mrulist_entries = self._ReadStructureFromByteStream(
registry_value.data,
0,
data_type_map,
"MRUList entries",
context=context,
)
except (ValueError, errors.ParseError) as exception:
raise errors.ParseError(
f"Unable to parse MRUList entries with error: {exception!s}"
)
mrulist = set([])
recovered_mrulist = set([])
is_recovered = False
for entry_letter in mrulist_entries:
if entry_letter == 0:
is_recovered = True
entry_letter = chr(entry_letter)
if is_recovered:
recovered_mrulist.add(entry_letter)
else:
mrulist.add(entry_letter)
for registry_value in registry_key.GetValues():
name_lower = (registry_value.name or "").lower()
if name_lower in (
"mrulist",
"nodeslot",
"nodeslots",
"viewstream",
):
continue
if self._debug:
name = registry_value.name or "(default)"
self._output_writer.WriteText(
f"Key: {registry_key.path:s}\nValue: {name:s}\n"
)
if self._InKeyPaths(registry_key.path, self._SHELL_ITEM_MRU_KEY_PATHS):
self._ProcessMRUEntryShellItem(
registry_key.path, registry_value.name, registry_value.data
)
elif self._InKeyPaths(
registry_key.path, self._SHELL_ITEM_LIST_MRU_KEY_PATHS
):
self._ProcessMRUEntryShellItemList(
registry_key.path, registry_value.name, registry_value.data
)
elif self._InKeyPaths(
registry_key.path, self._STRING_AND_SHELL_ITEM_MRU_KEY_PATHS
):
self._ProcessMRUEntryStringAndShellItem(
registry_key.path, registry_value.name, registry_value.data
)
elif self._InKeyPaths(
registry_key.path, self._STRING_AND_SHELL_ITEM_LIST_MRU_KEY_PATHS
):
self._ProcessMRUEntryStringAndShellItemList(
registry_key.path, registry_value.name, registry_value.data
)
else:
self._ProcessMRUEntryString(
registry_key.path, registry_value.name, registry_value.data
)
def _ProcessKeyWithMRUListExValue(self, registry_key):
"""Processes a Windows Registry key that contains a MRUListEx value.
Args:
registry_key (dfwinreg.WinRegistryKey): Windows Registry key.
Raises:
ParseError: if the MRUListEx value could not be parsed.
"""
# TODO: determine what trailing data is in:
# HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\
# ComDlg32\CIDSizeMRU
registry_value = registry_key.GetValueByName("MRUListEx")
data_type_map = self._GetDataTypeMap("mrulistex_entries")
context = dtfabric_data_maps.DataTypeMapContext(
values={"data_size": len(registry_value.data)}
)
try:
mrulistex_entries = self._ReadStructureFromByteStream(
registry_value.data,
0,
data_type_map,
"MRUListEx entries",
context=context,
)
except (ValueError, errors.ParseError) as exception:
raise errors.ParseError(
f"Unable to parse MRUListEx entries with error: {exception!s}"
)
mrulistex = set([])
recovered_mrulistex = set([])
is_recovered = False
for entry_number in mrulistex_entries:
if entry_number == 0:
is_recovered = True
if is_recovered:
recovered_mrulistex.add(entry_number)
else:
mrulistex.add(entry_number)
for registry_value in registry_key.GetValues():
name_lower = (registry_value.name or "").lower()
if name_lower in (
"mrulistex",
"nodeslot",
"nodeslots",
"viewstream",
):
continue
if self._debug:
name = registry_value.name or "(default)"
self._output_writer.WriteText(
f"Key: {registry_key.path:s}\nValue: {name:s}\n"
)
if self._InKeyPaths(registry_key.path, self._SHELL_ITEM_MRU_KEY_PATHS):
self._ProcessMRUEntryShellItem(
registry_key.path, registry_value.name, registry_value.data
)
elif self._InKeyPaths(
registry_key.path, self._SHELL_ITEM_LIST_MRU_KEY_PATHS
):
self._ProcessMRUEntryShellItemList(
registry_key.path, registry_value.name, registry_value.data
)
elif self._InKeyPaths(
registry_key.path, self._STRING_AND_SHELL_ITEM_MRU_KEY_PATHS
):
self._ProcessMRUEntryStringAndShellItem(
registry_key.path, registry_value.name, registry_value.data
)
elif self._InKeyPaths(
registry_key.path, self._STRING_AND_SHELL_ITEM_LIST_MRU_KEY_PATHS
):
self._ProcessMRUEntryStringAndShellItemList(
registry_key.path, registry_value.name, registry_value.data
)
else:
self._ProcessMRUEntryString(
registry_key.path, registry_value.name, registry_value.data
)
def _ProcessMRUEntryShellItem(self, key_path, value_name, value_data):
"""Processes a shell item MRUEntry.
Args:
key_path (str): path of the Windows Registry key.
value_name (str): name of the Windows Registry value.
value_data (bytes): Windows Registry value data.
"""
if self._debug:
self._output_writer.WriteDebugData("Shell item data", value_data)
mru_entry = MostRecentlyUsedEntry(
key_path=key_path, shell_item_data=value_data, value_name=value_name
)
self.mru_entries.append(mru_entry)
def _ProcessMRUEntryShellItemList(self, key_path, value_name, value_data):
"""Processes a shell item list MRUEntry.
Args:
key_path (str): path of the Windows Registry key.
value_name (str): name of the Windows Registry value.
value_data (bytes): Windows Registry value data.
"""
if self._debug:
self._output_writer.WriteDebugData("Shell item list data", value_data)
mru_entry = MostRecentlyUsedEntry(
key_path=key_path, shell_item_list_data=value_data, value_name=value_name
)
self.mru_entries.append(mru_entry)
def _ProcessMRUEntryString(self, key_path, value_name, value_data):
"""Processes a string MRUEntry.
Args:
key_path (str): path of the Windows Registry key.
value_name (str): name of the Windows Registry value.
value_data (bytes): Windows Registry value data.
"""
value_data_size = len(value_data)
data_offset = 0
for data_offset in range(0, value_data_size, 2):
if value_data[data_offset : data_offset + 2] == b"\0\0":
data_offset += 2
break
if self._debug:
self._output_writer.WriteDebugData("String data", value_data[0:data_offset])
string = value_data[0 : data_offset - 2].decode("utf-16-le")
if self._debug:
self._output_writer.WriteValue("String", string)
if self._debug and data_offset < value_data_size:
self._output_writer.WriteDebugData(
"Trailing data", value_data[data_offset:]
)
mru_entry = MostRecentlyUsedEntry(
key_path=key_path, string=string, value_name=value_name
)
self.mru_entries.append(mru_entry)
def _ProcessMRUEntryStringAndShellItem(self, key_path, value_name, value_data):
"""Processes a string and shell item MRUEntry.
Args:
key_path (str): path of the Windows Registry key.
value_name (str): name of the Windows Registry value.
value_data (bytes): Windows Registry value data.
"""
value_data_size = len(value_data)
data_offset = 0
for data_offset in range(0, value_data_size, 2):
if value_data[data_offset : data_offset + 2] == b"\0\0":
data_offset += 2
break
if self._debug:
self._output_writer.WriteDebugData("String data", value_data[0:data_offset])
string = value_data[0 : data_offset - 2].decode("utf-16-le")
if self._debug:
self._output_writer.WriteValue("String", string)
if data_offset < value_data_size:
if self._debug:
self._output_writer.WriteDebugData(
"Shell item data", value_data[data_offset:]
)
mru_entry = MostRecentlyUsedEntry(
key_path=key_path,
shell_item_data=value_data[data_offset:],
string=string,
value_name=value_name,
)
self.mru_entries.append(mru_entry)
def _ProcessMRUEntryStringAndShellItemList(self, key_path, value_name, value_data):
"""Processes a string and shell item list MRUEntry.
Args:
key_path (str): path of the Windows Registry key.
value_name (str): name of the Windows Registry value.
value_data (bytes): Windows Registry value data.
"""
value_data_size = len(value_data)
data_offset = 0
for data_offset in range(0, value_data_size, 2):
if value_data[data_offset : data_offset + 2] == b"\0\0":
data_offset += 2
break
if self._debug:
self._output_writer.WriteDebugData("String data", value_data[0:data_offset])
string = value_data[0 : data_offset - 2].decode("utf-16-le")
if self._debug:
self._output_writer.WriteValue("String", string)
if data_offset < value_data_size:
if self._debug:
self._output_writer.WriteDebugData(
"Shell item list data", value_data[data_offset:]
)
mru_entry = MostRecentlyUsedEntry(
key_path=key_path,
shell_item_list_data=value_data[data_offset:],
string=string,
value_name=value_name,
)
self.mru_entries.append(mru_entry)
[docs]
def Collect(self, registry): # pylint: disable=arguments-differ
"""Collects Most Recently Used (MRU) entries.
Args:
registry (dfwinreg.WinRegistry): Windows Registry.
Returns:
bool: True if a Most Recently Used (MRU) key was found, False if not.
"""
result = False
current_user_key = registry.GetKeyByPath("HKEY_CURRENT_USER")
if current_user_key:
if self._ProcessKey(current_user_key):
result = True
if not result:
# Fallback for if source is a single UsrClass.dat file.
current_user_classes_key = registry.GetKeyByPath(
"HKEY_CURRENT_USER\\Software\\Classes"
)
if current_user_classes_key:
if self._ProcessKey(current_user_classes_key):
result = True
return result