Source code for winevtrc.extractor
"""Windows Event Log message resource extractor."""
import logging
from dfimagetools import environment_variables
from dfimagetools import windows_registry
from dfvfs.helpers import volume_scanner as dfvfs_volume_scanner
from dfvfs.resolver import resolver as dfvfs_resolver
from dfwinreg import registry as dfwinreg_registry
from winevtrc import decorators
from winevtrc import eventlog_providers
from winevtrc import resource_file
[docs]
class EventMessageStringExtractor(dfvfs_volume_scanner.WindowsVolumeScanner):
"""Windows Event Log message string extractor.
Attributes:
ascii_codepage (str): ASCII string codepage.
missing_message_filenames (list[str]): names of message files that were
not found or without a resource section.
missing_resources_message_filenames (list[str]): names of message files,
where a message table resource is missing.
preferred_language_identifier (int): preferred language identifier (LCID).
"""
# Environment variables used in Windows paths.
_PATH_ENVIRONMENT_VARIABLES = (
"%COMMONPROGRAMFILES%",
"%COMMONPROGRAMFILES(X86)%",
"%COMMONPROGRAMW6432%",
"%PROGRAMDATA%",
"%PROGRAMFILES%",
"%PROGRAMFILES(X86)%",
"%PROGRAMW6432%",
"%PUBLIC%",
)
[docs]
def __init__(self, debug=False, mediator=None):
"""Initializes a Windows Event Log message string extractor.
Args:
debug (Optional[bool]): True if debug information should be printed.
mediator (dfvfs.VolumeScannerMediator): a volume scanner mediator or None.
"""
super().__init__(mediator=mediator)
self._debug = debug
self._registry = None
self._processed_message_filenames = []
self._processed_template_filenames = []
self._windows_version = None
self.ascii_codepage = "cp1252"
self.missing_message_filenames = []
self.missing_resources_message_filenames = []
self.preferred_language_identifier = 0x0409
@property
def windows_version(self):
"""The Windows version (getter)."""
if self._windows_version is None:
self._windows_version = self._GetWindowsVersion()
return self._windows_version
def _GetMessageResourceFilePath(
self, event_log_provider, normalized_file_path, message_filename
):
"""Retrieves the path of an Event Log message resource file.
Args:
event_log_provider (EventLogProvider): Event Log provider.
normalized_file_path (str): normalized path of the resource file.
message_filename (str): message filename.
Returns:
dfvfs.PathSpec: path specification of the resource file or None if not
available.
"""
path_spec = self._path_resolver.ResolvePath(normalized_file_path)
if path_spec:
file_entry = self._file_system.GetFileEntryByPathSpec(path_spec)
# If normalized_file_path points to a directory try appending the Event
# Log provider log source as the file name.
if file_entry.IsDirectory():
logging.info(f"Message file: {message_filename:s} refers to directory")
for log_source in event_log_provider.log_sources:
resource_file_path = "\\".join([normalized_file_path, log_source])
path_spec = self._path_resolver.ResolvePath(resource_file_path)
if path_spec:
break
return path_spec
def _GetMUIWindowsResourceFile(self, windows_path, windows_resource_file):
"""Retrieves a MUI resource file.
Args:
windows_path (str): Windows path of the language neutral resource file.
windows_resource_file (WindowsResourceFile): language neutral resource
file.
Returns:
WindowsResourceFile: MUI resource file or None if not available.
"""
mui_language = windows_resource_file.GetMUILanguage()
if not mui_language:
return None
path, _, name = windows_path.rpartition("\\")
mui_windows_path = "\\".join([path, mui_language, f"{name:s}.mui"])
mui_resource_file = self._OpenWindowsResourceFile(mui_windows_path)
if not mui_resource_file:
mui_windows_path = "\\".join([path, f"{name:s}.mui"])
mui_resource_file = self._OpenWindowsResourceFile(mui_windows_path)
if mui_resource_file:
logging.info(
(
f"Resource file: {windows_path:s} references MUI resource file: "
f"{mui_windows_path:s}"
)
)
return mui_resource_file
def _GetNormalizedPath(self, path):
"""Retrieves a normalized variant of a path.
Args:
path (str): path of a message file.
Returns:
str: normalized path of a message file.
"""
path_segments = path.split("\\")
if path_segments:
# Check if the first path segment is a drive letter or "%SystemDrive%".
first_path_segment = path_segments[0].lower()
if (
len(first_path_segment) == 2 and first_path_segment[1:] == ":"
) or first_path_segment == "%systemdrive%":
path_segments[0] = ""
# Check if the second path segment is "Windows".
if (
len(path_segments) >= 2
and path_segments[0] == ""
and path_segments[1].lower() == "windows"
):
path_segments.pop(0)
path_segments[0] = "%SystemRoot%"
return "\\".join(path_segments) or "\\"
def _GetSystemRoot(self):
"""Determines the value of %SystemRoot%.
Returns:
str: value of SystemRoot or None if the value cannot be determined.
"""
current_version_key = self._registry.GetKeyByPath(
"HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion"
)
system_root = None
if current_version_key:
system_root_value = current_version_key.GetValueByName("SystemRoot")
if system_root_value:
system_root = system_root_value.GetDataAsObject()
if not system_root:
system_root = self._windows_directory
return system_root
def _GetWindowsVersion(self):
"""Determines the Windows version from kernel executable file.
Returns:
str: Windows version or None otherwise.
"""
system_root = self._GetSystemRoot()
# Windows NT variants.
kernel_executable_path = "\\".join([system_root, "System32", "ntoskrnl.exe"])
windows_resource_file = self._OpenWindowsResourceFile(kernel_executable_path)
if not windows_resource_file:
# Windows 9x variants.
kernel_executable_path = "\\".join(
[system_root, "System32", "\\kernel32.dll"]
)
windows_resource_file = self._OpenWindowsResourceFile(
kernel_executable_path
)
if windows_resource_file:
return windows_resource_file.file_version
return None
def _OpenWindowsResourceFile(self, windows_path):
"""Opens the message resource file specified by the Windows path.
Args:
windows_path (str): Windows path of the Windows resource file.
Returns:
WindowsResourceFile: message resource file or None.
"""
path_spec = self._path_resolver.ResolvePath(windows_path)
if path_spec:
return self._OpenWindowsResourceFileByPathSpec(path_spec)
return None
def _OpenWindowsResourceFileByPathSpec(self, path_spec):
"""Opens the Windows resource file specified by the path specification.
Args:
path_spec (dfvfs.PathSpec): path specification.
Returns:
WindowsResourceFile: Windows resource file or None.
"""
windows_path = self._path_resolver.GetWindowsPath(path_spec)
if windows_path is None:
logging.warning("Unable to retrieve Windows path.")
try:
file_object = dfvfs_resolver.Resolver.OpenFileObject(path_spec)
except OSError as exception:
logging.warning(
f"Unable to open: {path_spec.comparable:s} with error: {exception!s}"
)
file_object = None
if file_object:
message_file = resource_file.WindowsResourceFile(
windows_path,
ascii_codepage=self.ascii_codepage,
preferred_language_identifier=self.preferred_language_identifier,
)
message_file.OpenFileObject(file_object)
return message_file
return None
[docs]
def CollectEventLogProviders(self):
"""Retrieves the Event Log providers.
Yields:
EventLogProvider: Event Log provider.
"""
# TODO: have CLI argument control this mode.
# all_control_sets = False
collector = eventlog_providers.EventLogProvidersCollector()
for event_log_provider in collector.Collect(self._registry):
event_log_provider.category_message_files = [
self.GetNormalizedResourceFilePath(path)
for path in event_log_provider.category_message_files
]
event_log_provider.event_message_files = [
self.GetNormalizedResourceFilePath(path)
for path in event_log_provider.event_message_files
]
event_log_provider.parameter_message_files = [
self.GetNormalizedResourceFilePath(path)
for path in event_log_provider.parameter_message_files
]
yield event_log_provider
[docs]
def CollectSystemEnvironmentVariables(self):
"""Collects the system environment variables."""
collector = environment_variables.WindowsEnvironmentVariablesCollector()
for environment_variable in collector.Collect(self._registry):
if environment_variable.name.upper() in self._PATH_ENVIRONMENT_VARIABLES:
normalized_path = self._GetNormalizedPath(environment_variable.value)
self._path_resolver.SetEnvironmentVariable(
environment_variable.name[1:-1], normalized_path
)
[docs]
def GetMessageResourceFile(self, event_log_provider, message_filename):
"""Retrieves an Event Log message resource file.
Args:
event_log_provider (EventLogProvider): Event Log provider.
message_filename (str): message filename.
Returns:
WindowsResourceFile: message resource file or None if not available or
already processed.
"""
normalized_message_file_path = self.GetNormalizedResourceFilePath(
message_filename
)
# Skip message file if it was already processed.
lookup_path = normalized_message_file_path.lower()
if lookup_path in self._processed_message_filenames:
return None
path_spec = self._GetMessageResourceFilePath(
event_log_provider, normalized_message_file_path, message_filename
)
if not path_spec:
return None
windows_resource_file = self._OpenWindowsResourceFileByPathSpec(path_spec)
if not windows_resource_file:
logging.warning(f"Missing message file: {message_filename:s}")
if message_filename not in self.missing_message_filenames:
self.missing_message_filenames.append(message_filename)
return None
if not windows_resource_file.HasMessageTableResource():
# Windows Vista and later use a MUI resource file to redirect to
# a language specific message resource file.
mui_resource_file = self._GetMUIWindowsResourceFile(
normalized_message_file_path, windows_resource_file
)
if mui_resource_file:
windows_resource_file.Close()
windows_resource_file = mui_resource_file
if not windows_resource_file.HasMessageTableResource():
logging.warning(
(
f"Message table resource missing from resource file: "
f"{message_filename:s}"
)
)
if message_filename not in self.missing_resources_message_filenames:
self.missing_resources_message_filenames.append(message_filename)
windows_resource_file.Close()
return None
windows_resource_file.windows_path = normalized_message_file_path
lookup_path = normalized_message_file_path.lower()
self._processed_message_filenames.append(lookup_path)
return windows_resource_file
@decorators.deprecated
def GetNormalizedMessageFilePath(self, path):
"""Retrieves a normalized variant of a message file path.
Args:
path (str): path of a message file.
Returns:
str: normalized path of a message file.
"""
return self.GetNormalizedResourceFilePath(path)
[docs]
def GetNormalizedResourceFilePath(self, path):
"""Retrieves a normalized variant of an Event Log resource file path.
Args:
path (str): path of an Event Log resource file.
Returns:
str: normalized path of an Event Log resource file.
"""
path_segments = path.split("\\")
filename = path_segments.pop()
if path_segments:
# Check if the first path segment is a drive letter or "%SystemDrive%".
first_path_segment = path_segments[0].lower()
if (
len(first_path_segment) == 2 and first_path_segment[1:] == ":"
) or first_path_segment == "%systemdrive%":
path_segments[0] = ""
path_segments_lower = [path_segment.lower() for path_segment in path_segments]
if not path_segments_lower:
# If the path is a filename assume the file is stored in:
# "%SystemRoot%\System32".
path_segments = ["%SystemRoot%", "System32"]
elif path_segments_lower[0] == "%programfiles%":
path_segments = ["%ProgramFiles%"] + path_segments[1:]
elif path_segments_lower[0] == "%programfiles(x86)%":
path_segments = ["%ProgramFiles(x86)%"] + path_segments[1:]
elif path_segments_lower[0] in ("system32", "$(runtime.system32)"):
# Note that the path can be relative so if it starts with "System32"
# assume this represents "%SystemRoot%\System32".
path_segments = ["%SystemRoot%", "System32"] + path_segments[1:]
elif path_segments_lower[0] in (
"%systemroot%",
"%windir%",
"$(runtime.windows)",
):
path_segments = ["%SystemRoot%"] + path_segments[1:]
# Check if path starts with "\Program Files\".
elif not path_segments_lower[0] and path_segments_lower[1] == "program files":
path_segments = ["%ProgramFiles%"] + path_segments[2:]
# Check if path starts with "\Program Files (x86)\".
elif (
not path_segments_lower[0]
and path_segments_lower[1] == "program files (x86)"
):
path_segments = ["%ProgramFiles(x86)%"] + path_segments[2:]
# Check if path starts with "\SystemRoot\", "\Windows\" or "\WinNT\" for
# example: "\SystemRoot\system32\drivers\SerCx.sys"
elif not path_segments_lower[0] and path_segments_lower[1] in (
"systemroot",
"windows",
"winnt",
):
path_segments = ["%SystemRoot%"] + path_segments[2:]
path_segments.append(filename)
return "\\".join(path_segments) or "\\"
[docs]
def GetTemplateResourceFile(self, template_filename):
"""Retrieves an Event Log WEVT_TEMPLATE resource file.
Args:
template_filename (str): template filename.
Returns:
WindowsResourceFile: template resource file or None if not available.
"""
normalized_template_file_path = self.GetNormalizedResourceFilePath(
template_filename
)
# Skip template file if it was already processed.
lookup_path = normalized_template_file_path.lower()
if lookup_path in self._processed_template_filenames:
return None
path_spec = self._path_resolver.ResolvePath(normalized_template_file_path)
if not path_spec:
return None
windows_resource_file = self._OpenWindowsResourceFileByPathSpec(path_spec)
if not windows_resource_file:
return None
if not windows_resource_file.HasWEVTTemplateResource():
# Windows 10 and later use .mun files in \\Windows\\SystemResources to
# store the template resource.
alternate_template_filename = template_filename.rsplit("\\", maxsplit=1)[-1]
alternate_template_file_path = "\\".join(
["Windows", "SystemResources", f"{alternate_template_filename:s}.mun"]
)
path_spec = self._path_resolver.ResolvePath(alternate_template_file_path)
if path_spec:
logging.warning(
f"Using alternate template file: {alternate_template_file_path:s}"
)
alternate_resource_file = self._OpenWindowsResourceFileByPathSpec(
path_spec
)
windows_resource_file.Close()
windows_resource_file = alternate_resource_file
if not windows_resource_file.HasWEVTTemplateResource():
windows_resource_file.Close()
return None
windows_resource_file.windows_path = normalized_template_file_path
lookup_path = normalized_template_file_path.lower()
self._processed_template_filenames.append(lookup_path)
return windows_resource_file
[docs]
def ScanForWindowsVolume(self, source_path, options=None):
"""Scans for a Windows volume.
Args:
source_path (str): source path.
options (Optional[VolumeScannerOptions]): volume scanner options. If None
the default volume scanner options are used, which are defined in the
VolumeScannerOptions class.
Returns:
bool: True if a Windows volume was found.
Raises:
ScannerError: if the source path does not exists, or if the source path
is not a file or directory, or if the format of or within
the source file is not supported.
"""
result = super().ScanForWindowsVolume(source_path, options=options)
if not result:
return False
registry_file_reader = (
windows_registry.StorageMediaImageWindowsRegistryFileReader(
self._file_system, self._path_resolver
)
)
self._registry = dfwinreg_registry.WinRegistry(
registry_file_reader=registry_file_reader
)
return True