Source code for plaso.output.winevt_rc

# -*- coding: utf-8 -*-
"""Windows EventLog resources database reader."""

import collections
import os
import sqlite3

from plaso.engine import path_helper
from plaso.helpers.windows import languages
from plaso.helpers.windows import resource_files
from plaso.output import logger


[docs] class Sqlite3DatabaseFile(object): """Class that defines a sqlite3 database file.""" _HAS_TABLE_QUERY = ( 'SELECT name FROM sqlite_master ' 'WHERE type = "table" AND name = "{0:s}"')
[docs] def __init__(self): """Initializes the database file object.""" super(Sqlite3DatabaseFile, self).__init__() self._connection = None self._cursor = None self.filename = None self.read_only = None
[docs] def Close(self): """Closes the database file. Raises: RuntimeError: if the database is not opened. """ if not self._connection: raise RuntimeError('Cannot close database not opened.') # We need to run commit or not all data is stored in the database. self._connection.commit() self._connection.close() self._connection = None self._cursor = None self.filename = None self.read_only = None
[docs] def HasTable(self, table_name): """Determines if a specific table exists. Args: table_name (str): table name. Returns: bool: True if the table exists. Raises: RuntimeError: if the database is not opened. """ if not self._connection: raise RuntimeError( 'Cannot determine if table exists database not opened.') sql_query = self._HAS_TABLE_QUERY.format(table_name) self._cursor.execute(sql_query) if self._cursor.fetchone(): return True return False
[docs] def GetValues(self, table_names, column_names, condition): """Retrieves values from a table. Args: table_names (list[str]): table names. column_names (list[str]): column names. condition (str): query condition such as "log_source == 'Application Error'". Yields: sqlite3.row: row. Raises: RuntimeError: if the database is not opened. """ if not self._connection: raise RuntimeError('Cannot retrieve values database not opened.') if condition: condition = f' WHERE {condition:s}' else: condition = '' table_names_string = ', '.join(table_names) column_names_string = ', '.join(column_names) sql_query = ( f'SELECT {column_names_string:s} FROM {table_names_string:s}' f'{condition:s}') self._cursor.execute(sql_query) # TODO: have a look at https://docs.python.org/2/library/ # sqlite3.html#sqlite3.Row. for row in self._cursor: yield { column_name: row[column_index] for column_index, column_name in enumerate(column_names)}
[docs] def Open(self, filename, read_only=False): """Opens the database file. Args: filename (str): filename of the database. read_only (Optional[bool]): True if the database should be opened in read-only mode. Since sqlite3 does not support a real read-only mode we fake it by only permitting SELECT queries. Returns: bool: True if successful. Raises: RuntimeError: if the database is already opened. """ if self._connection: raise RuntimeError('Cannot open database already opened.') self.filename = filename self.read_only = read_only try: self._connection = sqlite3.connect(filename) except sqlite3.OperationalError: return False if not self._connection: return False self._cursor = self._connection.cursor() if not self._cursor: return False return True
[docs] class WinevtResourcesSqlite3DatabaseReader(object): """Windows EventLog resources SQLite database reader."""
[docs] def __init__(self): """Initializes a Windows EventLog resources SQLite database reader.""" super(WinevtResourcesSqlite3DatabaseReader, self).__init__() self._database_file = Sqlite3DatabaseFile() self._resouce_file_helper = resource_files.WindowsResourceFileHelper self._string_format = 'wrc'
def _GetEventLogProviderKey(self, log_source): """Retrieves the EventLog provider key. Args: log_source (str): EventLog source. Returns: str: EventLog provider key or None if not available. Raises: RuntimeError: if more than one value is found in the database. """ table_names = ['event_log_providers'] column_names = ['event_log_provider_key'] condition = f'log_source == "{log_source:s}"' values_list = list(self._database_file.GetValues( table_names, column_names, condition)) number_of_values = len(values_list) if number_of_values == 0: return None if number_of_values == 1: values = values_list[0] return values['event_log_provider_key'] raise RuntimeError('More than one value found in database.') def _GetMessage(self, message_file_key, lcid, message_identifier): """Retrieves a specific message from a specific message table. Args: message_file_key (int): message file key. lcid (int): language code identifier (LCID). message_identifier (int): message identifier. Returns: str: message string or None if not available. Raises: RuntimeError: if more than one value is found in the database. """ table_name = f'message_table_{message_file_key:d}_0x{lcid:08x}' has_table = self._database_file.HasTable(table_name) if not has_table: return None column_names = ['message_string'] condition = f'message_identifier == "0x{message_identifier:08x}"' values = list(self._database_file.GetValues( [table_name], column_names, condition)) number_of_values = len(values) if number_of_values == 0: return None if number_of_values == 1: return values[0]['message_string'] raise RuntimeError('More than one value found in database.') def _GetMessageFileKeys(self, event_log_provider_key): """Retrieves the message file keys. Args: event_log_provider_key (int): EventLog provider key. Yields: int: message file key. """ table_names = ['message_file_per_event_log_provider'] column_names = ['message_file_key'] condition = f'event_log_provider_key == {event_log_provider_key:d}' generator = self._database_file.GetValues( table_names, column_names, condition) for values in generator: yield values['message_file_key']
[docs] def Close(self): """Closes the database reader object.""" self._database_file.Close()
[docs] def GetMessage(self, log_source, lcid, message_identifier): """Retrieves a specific message for a specific EventLog source. Args: log_source (str): EventLog source. lcid (int): language code identifier (LCID). message_identifier (int): message identifier. Returns: str: message string or None if not available. """ event_log_provider_key = self._GetEventLogProviderKey(log_source) if not event_log_provider_key: return None generator = self._GetMessageFileKeys(event_log_provider_key) if not generator: return None message_string = None for message_file_key in generator: message_string = self._GetMessage( message_file_key, lcid, message_identifier) if message_string: break if self._string_format == 'wrc': message_string = self._resouce_file_helper.FormatMessageStringInPEP3101( message_string) return message_string
[docs] def GetMetadataAttribute(self, attribute_name): """Retrieves the metadata attribute. Args: attribute_name (str): name of the metadata attribute. Returns: str: the metadata attribute or None. Raises: RuntimeError: if more than one value is found in the database. """ table_name = 'metadata' has_table = self._database_file.HasTable(table_name) if not has_table: return None column_names = ['value'] condition = f'name == "{attribute_name:s}"' values = list(self._database_file.GetValues( [table_name], column_names, condition)) number_of_values = len(values) if number_of_values == 0: return None if number_of_values == 1: return values[0]['value'] raise RuntimeError('More than one value found in database.')
[docs] def Open(self, filename): """Opens the database reader object. Args: filename (str): filename of the database. Returns: bool: True if successful. Raises: RuntimeError: if the version or string format of the database is not supported. """ if not self._database_file.Open(filename, read_only=True): return False version = self.GetMetadataAttribute('version') if not version or version != '20150315': raise RuntimeError(f'Unsupported version: {version:s}') string_format = self.GetMetadataAttribute('string_format') if not string_format: string_format = 'wrc' if string_format not in ('pep3101', 'wrc'): raise RuntimeError(f'Unsupported string format: {string_format:s}') self._string_format = string_format return True
[docs] class WinevtResourcesHelper(object): """Windows EventLog resources helper.""" # LCID 0x0409 is en-US. DEFAULT_LCID = 0x0409 _DEFAULT_PARAMETER_MESSAGE_FILES = ( '%SystemRoot%\\System32\\MsObjs.dll', '%SystemRoot%\\System32\\kernel32.dll') # The maximum number of cached message strings _MAXIMUM_CACHED_MESSAGE_STRINGS = 64 * 1024 _WINEVT_RC_DATABASE = 'winevt-rc.db'
[docs] def __init__(self, storage_reader, data_location, lcid): """Initializes Windows EventLog resources helper. Args: storage_reader (StorageReader): storage reader. data_location (str): data location of the winevt-rc database. lcid (int): Windows Language Code Identifier (LCID). """ language_tag = languages.WindowsLanguageHelper.GetLanguageTagForLCID( lcid or self.DEFAULT_LCID) super(WinevtResourcesHelper, self).__init__() self._data_location = data_location self._environment_variables = None self._language_tag = language_tag.lower() self._lcid = lcid or self.DEFAULT_LCID self._message_string_cache = collections.OrderedDict() self._storage_reader = storage_reader self._windows_eventlog_message_files = None self._windows_eventlog_providers = None self._winevt_database_reader = None
def _CacheMessageString( self, provider_identifier, log_source, message_identifier, event_version, message_string): """Caches a specific message string. Args: provider_identifier (str): EventLog provider identifier. log_source (str): EventLog source, such as "Application Error". message_identifier (int): message identifier. event_version (int): event version or None if not set. message_string (str): message string. """ if len(self._message_string_cache) >= self._MAXIMUM_CACHED_MESSAGE_STRINGS: self._message_string_cache.popitem(last=True) if provider_identifier: lookup_key = f'{provider_identifier:s}:0x{message_identifier:08x}' if event_version is not None: lookup_key = f'{lookup_key:s}:{event_version:d}' self._message_string_cache[lookup_key] = message_string self._message_string_cache.move_to_end(lookup_key, last=False) if log_source: lookup_key = f'{log_source:s}:0x{message_identifier:08x}' if event_version is not None: lookup_key = f'{lookup_key:s}:{event_version:d}' self._message_string_cache[lookup_key] = message_string self._message_string_cache.move_to_end(lookup_key, last=False) def _GetCachedMessageString( self, provider_identifier, log_source, message_identifier, event_version): """Retrieves a specific cached message string. Args: provider_identifier (str): EventLog provider identifier. log_source (str): EventLog source, such as "Application Error". message_identifier (int): message identifier. event_version (int): event version or None if not set. Returns: str: message string or None if not available. """ lookup_key = None message_string = None if provider_identifier: lookup_key = f'{provider_identifier:s}:0x{message_identifier:08x}' if event_version is not None: lookup_key = f'{lookup_key:s}:{event_version:d}' message_string = self._message_string_cache.get(lookup_key, None) if not message_string and log_source: lookup_key = f'{log_source:s}:0x{message_identifier:08x}' if event_version is not None: lookup_key = f'{lookup_key:s}:{event_version:d}' message_string = self._message_string_cache.get(lookup_key, None) if message_string: self._message_string_cache.move_to_end(lookup_key, last=False) return message_string def _GetEventMessageFileIdentifiers(self, message_files): """Retrieves event message file identifiers. Args: message_files (list[str]): Windows EventLog message files. Returns: list[str]: message file identifiers. """ message_file_identifiers = [] for windows_path in message_files or []: path, filename = path_helper.PathHelper.GetWindowsSystemPath( windows_path, self._environment_variables) lookup_path = '\\'.join([path, filename]).lower() message_file_identifier = self._windows_eventlog_message_files.get( lookup_path, None) if message_file_identifier: message_file_identifier = message_file_identifier.CopyToString() message_file_identifiers.append(message_file_identifier) mui_filename = f'{filename:s}.mui' lookup_path = '\\'.join([path, self._language_tag, mui_filename]).lower() message_file_identifier = self._windows_eventlog_message_files.get( lookup_path, None) if message_file_identifier: message_file_identifier = message_file_identifier.CopyToString() message_file_identifiers.append(message_file_identifier) return message_file_identifiers def _GetMappedMessageIdentifier( self, storage_reader, provider_identifier, message_identifier, event_version): """Retrieves a WEVT_TEMPLATE mapped message identifier if available. Args: storage_reader (StorageReader): storage reader. provider_identifier (str): EventLog provider identifier. message_identifier (int): message identifier. event_version (int): event version or None if not set. Returns: int: message identifier. """ # Map the event identifier to a message identifier as defined by the # WEVT_TEMPLATE event definition. if provider_identifier and storage_reader.HasAttributeContainers( 'windows_wevt_template_event'): # TODO: add message_file_identifiers to filter_expression filter_expression = ( f'provider_identifier == "{provider_identifier:s}" and ' f'identifier == {message_identifier:d}') if event_version is not None: filter_expression = ( f'{filter_expression:s} and version == {event_version:d}') for event_definition in storage_reader.GetAttributeContainers( 'windows_wevt_template_event', filter_expression=filter_expression): logger.debug(( f'Message: 0x{message_identifier:08x} of provider: ' f'{provider_identifier:s} maps to: ' f'0x{event_definition.message_identifier:08x}')) return event_definition.message_identifier return message_identifier def _GetMessageStrings( self, storage_reader, message_file_identifiers, message_identifier): """Retrieves message strings. Args: storage_reader (StorageReader): storage reader. message_file_identifiers (list[str]): message file identifiers. message_identifier (int): message identifier. Returns: list[str]: message strings. """ message_strings = [] # TODO: add message_file_identifiers to filter_expression filter_expression = ( f'language_identifier == {self._lcid:d} and ' f'message_identifier == {message_identifier:d}') for message_string in storage_reader.GetAttributeContainers( 'windows_eventlog_message_string', filter_expression=filter_expression): identifier = message_string.GetMessageFileIdentifier() identifier = identifier.CopyToString() if identifier in message_file_identifiers: message_strings.append(message_string) return message_strings def _GetWindowsEventLogProvider(self, provider_identifier, log_source): """Retrieves a Windows EventLog provider. Args: provider_identifier (str): EventLog provider identifier. log_source (str): EventLog source, such as "Application Error". Returns: tuple[WindowsEventLogProviderArtifact, str]: Windows EventLog provider or None if not available, and provider lookup key. """ provider = None if provider_identifier: lookup_key = provider_identifier.lower() provider = self._windows_eventlog_providers.get(lookup_key, None) if not provider: lookup_key = log_source.lower() provider = self._windows_eventlog_providers.get(lookup_key, None) return provider, lookup_key def _GetWinevtRcDatabaseReader(self): """Opens the Windows EventLog resource database reader. Returns: WinevtResourcesSqlite3DatabaseReader: Windows EventLog resource database reader or None. """ if not self._winevt_database_reader and self._data_location: logger.warning(( f'Falling back to {self._WINEVT_RC_DATABASE:s}. Please make sure ' f'the Windows EventLog message strings in the database correspond ' f'to those in the EventLog files.')) database_path = os.path.join( self._data_location, self._WINEVT_RC_DATABASE) if not os.path.isfile(database_path): return None self._winevt_database_reader = WinevtResourcesSqlite3DatabaseReader() if not self._winevt_database_reader.Open(database_path): self._winevt_database_reader = None return self._winevt_database_reader def _GetWinevtRcDatabaseMessageString(self, log_source, message_identifier): """Retrieves a specific Windows EventLog resource database message string. Args: log_source (str): EventLog source, such as "Application Error". message_identifier (int): message identifier. Returns: str: message string or None if not available. """ database_reader = self._GetWinevtRcDatabaseReader() if not database_reader: return None if self._lcid != self.DEFAULT_LCID: message_string = database_reader.GetMessage( log_source, self._lcid, message_identifier) if message_string: return message_string return database_reader.GetMessage( log_source, self.DEFAULT_LCID, message_identifier) def _ReadEnvironmentVariables(self, storage_reader): """Reads the environment variables. Args: storage_reader (StorageReader): storage reader. """ # TODO: get environment variables related to the source. self._environment_variables = list(storage_reader.GetAttributeContainers( 'environment_variable')) def _ReadWindowsEventLogMessageFiles(self, storage_reader): """Reads the Windows EventLog message files. Args: storage_reader (StorageReader): storage reader. """ # TODO: get windows eventlog message files related to the source. self._windows_eventlog_message_files = {} if storage_reader.HasAttributeContainers('windows_eventlog_message_file'): for message_file in storage_reader.GetAttributeContainers( 'windows_eventlog_message_file'): path, filename = path_helper.PathHelper.GetWindowsSystemPath( message_file.path, self._environment_variables) lookup_path = '\\'.join([path, filename]).lower() message_file_identifier = message_file.GetIdentifier() self._windows_eventlog_message_files[lookup_path] = ( message_file_identifier) def _ReadEventMessageString( self, storage_reader, provider_identifier, log_source, message_identifier, event_version): """Reads an event message string. Args: storage_reader (StorageReader): storage reader. provider_identifier (str): EventLog provider identifier. log_source (str): EventLog source, such as "Application Error". message_identifier (int): message identifier. event_version (int): event version or None if not set. Returns: str: message string or None if not available. """ if self._environment_variables is None: self._ReadEnvironmentVariables(storage_reader) if self._windows_eventlog_providers is None: self._ReadWindowsEventLogProviders(storage_reader) if self._windows_eventlog_message_files is None: self._ReadWindowsEventLogMessageFiles(storage_reader) provider, provider_lookup_key = self._GetWindowsEventLogProvider( provider_identifier, log_source) if not provider: return None if not storage_reader.HasAttributeContainers( 'windows_eventlog_message_string'): return None original_message_identifier = message_identifier # TODO: pass message_file_identifiers message_identifier = self._GetMappedMessageIdentifier( storage_reader, provider_identifier, message_identifier, event_version) message_file_identifiers = self._GetEventMessageFileIdentifiers( provider.event_message_files) if not message_file_identifiers: logger.warning(( f'No event message file for identifier: 0x{message_identifier:08x} ' f'(original: 0x{original_message_identifier:08x}) ' f'of provider: {provider_lookup_key:s}')) return None message_strings = self._GetMessageStrings( storage_reader, message_file_identifiers, message_identifier) if not message_strings: logger.warning(( f'No message string for identifier: 0x{message_identifier:08x} ' f'(original: 0x{original_message_identifier:08x}) ' f'of provider: {provider_lookup_key:s}')) return None return message_strings[0].string def _ReadParameterMessageString( self, storage_reader, provider_identifier, log_source, message_identifier): """Reads a parameter message string. Args: storage_reader (StorageReader): storage reader. provider_identifier (str): EventLog provider identifier. log_source (str): EventLog source, such as "Application Error". message_identifier (int): message identifier. Returns: str: parameter string or None if not available. """ if self._environment_variables is None: self._ReadEnvironmentVariables(storage_reader) if self._windows_eventlog_providers is None: self._ReadWindowsEventLogProviders(storage_reader) if self._windows_eventlog_message_files is None: self._ReadWindowsEventLogMessageFiles(storage_reader) provider, provider_lookup_key = self._GetWindowsEventLogProvider( provider_identifier, log_source) if not provider: return None if not storage_reader.HasAttributeContainers( 'windows_eventlog_message_string'): return None message_files = provider.parameter_message_files if not message_files: # If no parameter message files are defined fallback to the event # message files and default parameter message files. message_files = list(provider.event_message_files) message_files.extend(self._DEFAULT_PARAMETER_MESSAGE_FILES) message_file_identifiers = self._GetEventMessageFileIdentifiers( message_files) if not message_file_identifiers: logger.warning(( f'No parameter message file for identifier: ' f'0x{message_identifier:08x} of provider: {provider_lookup_key:s}')) return None message_strings = self._GetMessageStrings( storage_reader, message_file_identifiers, message_identifier) if not message_strings: logger.warning(( f'No parameter string for identifier: 0x{message_identifier:08x} ' f'of provider: {provider_lookup_key:s}')) return None return message_strings[0].string def _ReadWindowsEventLogProviders(self, storage_reader): """Reads the Windows EventLog providers. Args: storage_reader (StorageReader): storage reader. """ self._windows_eventlog_providers = {} if storage_reader.HasAttributeContainers('windows_eventlog_provider'): for provider in storage_reader.GetAttributeContainers( 'windows_eventlog_provider'): if provider.identifier: self._windows_eventlog_providers[provider.identifier] = provider for log_source in provider.log_sources: log_source = log_source.lower() self._windows_eventlog_providers[log_source] = provider
[docs] def GetMessageString( self, provider_identifier, log_source, message_identifier, event_version): """Retrieves a specific Windows EventLog message string. Args: provider_identifier (str): EventLog provider identifier. log_source (str): EventLog source, such as "Application Error". message_identifier (int): message identifier. event_version (int): event version or None if not set. Returns: str: message string or None if not available. """ message_string = self._GetCachedMessageString( provider_identifier, log_source, message_identifier, event_version) if not message_string: # TODO: change this logic. if self._storage_reader and self._storage_reader.HasAttributeContainers( 'windows_eventlog_provider'): message_string = self._ReadEventMessageString( self._storage_reader, provider_identifier, log_source, message_identifier, event_version) else: message_string = self._GetWinevtRcDatabaseMessageString( log_source, message_identifier) if message_string: self._CacheMessageString( provider_identifier, log_source, message_identifier, event_version, message_string) return message_string
[docs] def GetParameterString( self, provider_identifier, log_source, message_identifier): """Retrieves a specific Windows EventLog parameter string. Args: provider_identifier (str): EventLog provider identifier. log_source (str): EventLog source, such as "Application Error". message_identifier (int): parameter identifier. Returns: str: parameter string or None if not available. """ message_string = self._GetCachedMessageString( provider_identifier, log_source, message_identifier, None) if not message_string: message_string = self._ReadParameterMessageString( self._storage_reader, provider_identifier, log_source, message_identifier) if message_string: self._CacheMessageString( provider_identifier, log_source, message_identifier, None, message_string) return message_string