diff --git a/plaso/cli/pinfo_tool.py b/plaso/cli/pinfo_tool.py index 6cea0c3a83..163b16d4b6 100644 --- a/plaso/cli/pinfo_tool.py +++ b/plaso/cli/pinfo_tool.py @@ -255,7 +255,8 @@ def _PrintAnalysisReportCounter( Args: analysis_reports_counter (collections.Counter): number of analysis reports per analysis plugin. - session_identifier (Optional[str]): session identifier. + session_identifier (Optional[str]): session identifier, formatted as + a UUID. """ if not analysis_reports_counter: return @@ -378,7 +379,8 @@ def _PrintEventLabelsCounter( Args: event_labels_counter (collections.Counter): number of event tags per label. - session_identifier (Optional[str]): session identifier. + session_identifier (Optional[str]): session identifier, formatted as + a UUID. """ if not event_labels_counter: return @@ -410,7 +412,8 @@ def _PrintParsersCounter(self, parsers_counter, session_identifier=None): Args: parsers_counter (collections.Counter): number of events per parser or parser plugin. - session_identifier (Optional[str]): session identifier. + session_identifier (Optional[str]): session identifier, formatted as + a UUID. """ if not parsers_counter: self._output_writer.Write('No events stored.\n\n') @@ -433,24 +436,34 @@ def _PrintParsersCounter(self, parsers_counter, session_identifier=None): table_view.Write(self._output_writer) - def _PrintPreprocessingInformation(self, storage_reader, session_number=None): + def _PrintPreprocessingInformation( + self, storage_reader, session_identifier=None): """Prints the details of the preprocessing information. Args: storage_reader (StorageReader): storage reader. - session_number (Optional[int]): session number. + session_identifier (Optional[str]): session identifier, formatted as + a UUID. """ knowledge_base_object = knowledge_base.KnowledgeBase() storage_reader.ReadPreprocessingInformation(knowledge_base_object) - # TODO: replace session_number by session_identifier. + lookup_identifier = session_identifier + if lookup_identifier: + # The knowledge base requires the session identifier to be formatted in + # hexadecimal representation. + lookup_identifier = lookup_identifier.replace('-', '') + system_configuration = knowledge_base_object.GetSystemConfigurationArtifact( - session_identifier=session_number) + session_identifier=lookup_identifier) if not system_configuration: return title = 'System configuration' + if session_identifier: + title = '{0:s}: {1:s}'.format(title, session_identifier) + table_view = views.ViewsFactory.GetTableView( self._views_format_type, title=title) @@ -477,7 +490,23 @@ def _PrintPreprocessingInformation(self, storage_reader, session_number=None): table_view.Write(self._output_writer) + title = 'Available time zones' + if session_identifier: + title = '{0:s}: {1:s}'.format(title, session_identifier) + + table_view = views.ViewsFactory.GetTableView( + self._views_format_type, + column_names=['Name', ''], title=title) + + for time_zone in system_configuration.available_time_zones: + table_view.AddRow([time_zone.name, '']) + + table_view.Write(self._output_writer) + title = 'User accounts' + if session_identifier: + title = '{0:s}: {1:s}'.format(title, session_identifier) + table_view = views.ViewsFactory.GetTableView( self._views_format_type, column_names=['Username', 'User directory'], title=title) @@ -494,7 +523,7 @@ def _PrintSessionsDetails(self, storage_reader): Args: storage_reader (BaseStore): storage. """ - for session_number, session in enumerate(storage_reader.GetSessions()): + for session in storage_reader.GetSessions(): session_identifier = uuid.UUID(hex=session.identifier) session_identifier = '{0!s}'.format(session_identifier) @@ -545,7 +574,8 @@ def _PrintSessionsDetails(self, storage_reader): table_view.Write(self._output_writer) if self._verbose: - self._PrintPreprocessingInformation(storage_reader, session_number + 1) + self._PrintPreprocessingInformation( + storage_reader, session_identifier=session_identifier) self._PrintParsersCounter( session.parsers_counter, session_identifier=session_identifier) diff --git a/plaso/containers/artifacts.py b/plaso/containers/artifacts.py index 1864e9b39a..2f37b28504 100644 --- a/plaso/containers/artifacts.py +++ b/plaso/containers/artifacts.py @@ -46,8 +46,7 @@ class HostnameArtifact(ArtifactAttributeContainer): Also see: https://en.wikipedia.org/wiki/Hostname - http://cybox.mitre.org/language/version2.1/xsddocs/objects/ - Hostname_Object.html + Cybox / Stix Hostname Object Attributes: name (str): name of the host according to the naming schema. @@ -226,6 +225,7 @@ class SystemConfigurationArtifact(ArtifactAttributeContainer): system installation e.g. Windows or Linux. Attributes: + available_time_zones (list[TimeZone]): available time zones. code_page (str): system code page. hostname (HostnameArtifact): hostname. keyboard_layout (str): keyboard layout. @@ -247,6 +247,7 @@ def __init__(self, code_page=None, time_zone=None): time_zone (Optional[str]): system time zone. """ super(SystemConfigurationArtifact, self).__init__() + self.available_time_zones = [] self.code_page = code_page self.hostname = None self.keyboard_layout = None @@ -257,12 +258,30 @@ def __init__(self, code_page=None, time_zone=None): self.user_accounts = [] +class TimeZoneArtifact(ArtifactAttributeContainer): + """Time zone artifact attribute container. + + Attributes: + name (str): name describing the time zone e.g. Greenwich Standard Time. + """ + CONTAINER_TYPE = 'time_zone' + + def __init__(self, name=None): + """Initializes a time zone artifact. + + Args: + name (Optional[str]): name describing the time zone e.g. Greenwich + Standard Time. + """ + super(TimeZoneArtifact, self).__init__() + self.name = name + + class UserAccountArtifact(ArtifactAttributeContainer): """User account artifact attribute container. Also see: - http://cybox.mitre.org/language/version2.1/xsddocs/objects/ - User_Account_Object.html + Cybox / Stix User Account Object Attributes: full_name (str): name describing the user e.g. full name. @@ -276,7 +295,7 @@ class UserAccountArtifact(ArtifactAttributeContainer): def __init__( self, full_name=None, group_identifier=None, identifier=None, path_separator='/', user_directory=None, username=None): - """Initializes an user artifact. + """Initializes a user account artifact. Args: full_name (Optional[str]): name describing the user e.g. full name. @@ -311,4 +330,4 @@ def GetUserDirectoryPathSegments(self): manager.AttributeContainersManager.RegisterAttributeContainers([ EnvironmentVariableArtifact, HostnameArtifact, SystemConfigurationArtifact, - UserAccountArtifact]) + TimeZoneArtifact, UserAccountArtifact]) diff --git a/plaso/engine/knowledge_base.py b/plaso/engine/knowledge_base.py index 91b4c9a4b6..d9f1140ff0 100644 --- a/plaso/engine/knowledge_base.py +++ b/plaso/engine/knowledge_base.py @@ -27,6 +27,7 @@ class KnowledgeBase(object): def __init__(self): """Initializes a knowledge base.""" super(KnowledgeBase, self).__init__() + self._available_time_zones = {} self._codepage = 'cp1252' self._environment_variables = {} self._hostnames = {} @@ -34,6 +35,11 @@ def __init__(self): self._user_accounts = {} self._values = {} + @property + def available_time_zones(self): + """list[TimeZone]: available time zones of the current session.""" + return self._available_time_zones.get(self.CURRENT_SESSION, {}).values() + @property def codepage(self): """str: codepage of the current session.""" @@ -63,6 +69,26 @@ def year(self): """int: year of the current session.""" return self.GetValue('year', default_value=0) + def AddAvailableTimeZone(self, time_zone, session_identifier=CURRENT_SESSION): + """Adds an available time zone. + + Args: + time_zone (TimeZoneArtifact): time zone artifact. + session_identifier (Optional[str])): session identifier, where + CURRENT_SESSION represents the active session. + + Raises: + KeyError: if the time zone already exists. + """ + if session_identifier not in self._available_time_zones: + self._available_time_zones[session_identifier] = {} + + available_time_zones = self._available_time_zones[session_identifier] + if time_zone.name in available_time_zones: + raise KeyError('Time zone: {0:s} already exists.'.format(time_zone.name)) + + available_time_zones[time_zone.name] = time_zone + def AddUserAccount(self, user_account, session_identifier=CURRENT_SESSION): """Adds an user account. @@ -187,6 +213,13 @@ def GetSystemConfigurationArtifact(self, session_identifier=CURRENT_SESSION): system_configuration.time_zone = time_zone + available_time_zones = self._available_time_zones.get( + session_identifier, {}) + # In Python 3 dict.values() returns a type dict_values, which will cause + # the JSON serializer to raise a TypeError. + system_configuration.available_time_zones = list( + available_time_zones.values()) + user_accounts = self._user_accounts.get(session_identifier, {}) # In Python 3 dict.values() returns a type dict_values, which will cause # the JSON serializer to raise a TypeError. @@ -306,8 +339,12 @@ def ReadSystemConfigurationArtifact( 'Unsupported time zone: {0:s}, defaulting to {1:s}'.format( system_configuration.time_zone, self.timezone.zone)) + self._available_time_zones[session_identifier] = { + time_zone.name: time_zone + for time_zone in system_configuration.available_time_zones} + self._user_accounts[session_identifier] = { - user_account.username: user_account + user_account.identifier: user_account for user_account in system_configuration.user_accounts} def SetCodepage(self, codepage): diff --git a/plaso/preprocessors/windows.py b/plaso/preprocessors/windows.py index 841bfa5260..bfdefb0bc8 100644 --- a/plaso/preprocessors/windows.py +++ b/plaso/preprocessors/windows.py @@ -184,6 +184,32 @@ def Collect(self, knowledge_base): pass +class WindowsAvailableTimeZonesPlugin( + interface.WindowsRegistryKeyArtifactPreprocessorPlugin): + """The Windows available time zones plugin.""" + + ARTIFACT_DEFINITION_NAME = 'WindowsAvailableTimeZones' + + def _ParseKey(self, knowledge_base, registry_key, value_name): + """Parses a Windows Registry key for a preprocessing attribute. + + Args: + knowledge_base (KnowledgeBase): to fill with preprocessing information. + registry_key (dfwinreg.WinRegistryKey): Windows Registry key. + value_name (str): name of the Windows Registry value. + + Raises: + errors.PreProcessFail: if the preprocessing fails. + """ + time_zone_artifact = artifacts.TimeZoneArtifact(name=registry_key.name) + + try: + knowledge_base.AddAvailableTimeZone(time_zone_artifact) + except KeyError: + # TODO: add and store preprocessing errors. + pass + + class WindowsCodepagePlugin( interface.WindowsRegistryValueArtifactPreprocessorPlugin): """The Windows codepage plugin.""" @@ -479,6 +505,7 @@ class WindowsWinDirEnvironmentVariablePlugin( WindowsAllUsersAppDataKnowledgeBasePlugin, WindowsAllUsersProfileEnvironmentVariablePlugin, WindowsAllUsersAppProfileKnowledgeBasePlugin, + WindowsAvailableTimeZonesPlugin, WindowsCodepagePlugin, WindowsHostnamePlugin, WindowsProgramDataEnvironmentVariablePlugin, WindowsProgramDataKnowledgeBasePlugin, diff --git a/plaso/storage/sqlite/sqlite_file.py b/plaso/storage/sqlite/sqlite_file.py index c666e6231c..8b9bbeb7cd 100644 --- a/plaso/storage/sqlite/sqlite_file.py +++ b/plaso/storage/sqlite/sqlite_file.py @@ -1085,9 +1085,10 @@ def ReadPreprocessingInformation(self, knowledge_base): generator = self._GetAttributeContainers( self._CONTAINER_TYPE_SYSTEM_CONFIGURATION) for stream_number, system_configuration in enumerate(generator): - # TODO: replace stream_number by session_identifier. + session_start = self._GetAttributeContainerByIndex( + self._CONTAINER_TYPE_SESSION_START, stream_number) knowledge_base.ReadSystemConfigurationArtifact( - system_configuration, session_identifier=stream_number) + system_configuration, session_identifier=session_start.identifier) def WritePreprocessingInformation(self, knowledge_base): """Writes preprocessing information. diff --git a/test_data/artifacts/artifacts.yaml b/test_data/artifacts/artifacts.yaml index 15ff01eac2..baf23cfa44 100644 --- a/test_data/artifacts/artifacts.yaml +++ b/test_data/artifacts/artifacts.yaml @@ -34,10 +34,10 @@ sources: - type: FILE attributes: paths: - - '/etc/enterprise-release' - - '/etc/oracle-release' - - '/etc/redhat-release' - - '/etc/system-release' + - '/etc/enterprise-release' + - '/etc/oracle-release' + - '/etc/redhat-release' + - '/etc/system-release' provides: [os_release, os_major_version, os_minor_version] labels: [Software] supported_os: [Linux] @@ -48,8 +48,8 @@ sources: - type: FILE attributes: paths: - - '/etc/issue' - - '/etc/issue.net' + - '/etc/issue' + - '/etc/issue.net' labels: [Configuration Files, System] supported_os: [Linux] urls: ['https://linux.die.net/man/5/issue'] @@ -70,8 +70,8 @@ sources: - type: FILE attributes: paths: - - '/etc/os-release' - - '/usr/lib/os-release' + - '/etc/os-release' + - '/usr/lib/os-release' provides: [os_release, os_major_version, os_minor_version] labels: [Software] supported_os: [Linux] @@ -91,8 +91,8 @@ sources: - type: FILE attributes: paths: - - '/etc/localtime' - - '/private/etc/localtime' + - '/etc/localtime' + - '/private/etc/localtime' labels: [System] supported_os: [Darwin] urls: @@ -124,21 +124,29 @@ sources: - type: FILE attributes: paths: - - '/var/db/dslocal/nodes/Default/users/*.plist' - - '/private/var/db/dslocal/nodes/Default/users/*.plist' + - '/var/db/dslocal/nodes/Default/users/*.plist' + - '/private/var/db/dslocal/nodes/Default/users/*.plist' labels: [System, Users, Authentication] supported_os: [Darwin] urls: - 'http://forensicswiki.org/wiki/Mac_OS_X' - 'http://forensicswiki.org/wiki/Mac_OS_X_10.9_-_Artifacts_Location#System_Settings_and_Informations' --- +name: WindowsAvailableTimeZones +doc: Timezones available on a Windows system. +sources: +- type: REGISTRY_KEY + attributes: {keys: ['HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Time Zones\*']} +supported_os: [Windows] +urls: ['https://github.com/libyal/winreg-kb/blob/master/documentation/Time%20zone%20keys.asciidoc'] +--- name: WindowsCodePage doc: The code page of the system. sources: - type: REGISTRY_VALUE attributes: key_value_pairs: - - {key: '%%current_control_set%%\Control\Nls\CodePage', value: 'ACP'} + - {key: '%%current_control_set%%\Control\Nls\CodePage', value: 'ACP'} provides: [code_page] supported_os: [Windows] urls: ['http://en.wikipedia.org/wiki/Windows_code_page'] @@ -149,7 +157,7 @@ sources: - type: REGISTRY_VALUE attributes: key_value_pairs: - - {key: '%%current_control_set%%\Control\ComputerName\ComputerName', value: 'ComputerName'} + - {key: '%%current_control_set%%\Control\ComputerName\ComputerName', value: 'ComputerName'} provides: [hostname] supported_os: [Windows] --- @@ -170,7 +178,7 @@ sources: - type: REGISTRY_VALUE attributes: key_value_pairs: - - {key: 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\ProfileList', value: 'AllUsersProfile'} + - {key: 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\ProfileList', value: 'AllUsersProfile'} provides: [environ_allusersprofile] supported_os: [Windows] urls: @@ -184,7 +192,7 @@ sources: - type: REGISTRY_VALUE attributes: key_value_pairs: - - {key: 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\ProfileList', value: 'ProgramData'} + - {key: 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\ProfileList', value: 'ProgramData'} provides: [environ_allusersappdata] supported_os: [Windows] urls: ['http://environmentvariables.org/ProgramData'] @@ -199,7 +207,7 @@ sources: - type: REGISTRY_VALUE attributes: key_value_pairs: - - {key: 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion', value: 'ProgramFilesDir'} + - {key: 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion', value: 'ProgramFilesDir'} provides: [environ_programfiles] supported_os: [Windows] urls: ['http://environmentvariables.org/ProgramFiles'] @@ -214,7 +222,7 @@ sources: - type: REGISTRY_VALUE attributes: key_value_pairs: - - {key: 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion', value: 'ProgramFilesDir (x86)'} + - {key: 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion', value: 'ProgramFilesDir (x86)'} provides: [environ_programfilesx86] supported_os: [Windows] urls: ['http://environmentvariables.org/ProgramFiles'] @@ -225,15 +233,15 @@ sources: - type: PATH attributes: paths: - - '\Windows' - - '\WinNT' - - '\WINNT35' - - '\WTSRV' + - '\Windows' + - '\WinNT' + - '\WINNT35' + - '\WTSRV' separator: '\' - type: REGISTRY_VALUE attributes: key_value_pairs: - - {key: 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion', value: 'SystemRoot'} + - {key: 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion', value: 'SystemRoot'} provides: [environ_systemroot] supported_os: [Windows] urls: ['http://environmentvariables.org/SystemRoot'] @@ -244,15 +252,15 @@ sources: - type: PATH attributes: paths: - - '\Windows' - - '\WinNT' - - '\WINNT35' - - '\WTSRV' + - '\Windows' + - '\WinNT' + - '\WINNT35' + - '\WTSRV' separator: '\' - type: REGISTRY_VALUE attributes: key_value_pairs: - - {key: '%%current_control_set%%\Control\Session Manager\Environment', value: 'windir'} + - {key: '%%current_control_set%%\Control\Session Manager\Environment', value: 'windir'} provides: [environ_windir] supported_os: [Windows] urls: ['http://environmentvariables.org/WinDir'] diff --git a/tests/containers/artifacts.py b/tests/containers/artifacts.py index 98ac7da134..ef0193c020 100644 --- a/tests/containers/artifacts.py +++ b/tests/containers/artifacts.py @@ -118,9 +118,9 @@ def testGetAttributeNames(self): attribute_container = artifacts.SystemConfigurationArtifact() expected_attribute_names = [ - 'code_page', 'hostname', 'keyboard_layout', 'operating_system', - 'operating_system_product', 'operating_system_version', 'time_zone', - 'user_accounts'] + 'available_time_zones', 'code_page', 'hostname', 'keyboard_layout', + 'operating_system', 'operating_system_product', + 'operating_system_version', 'time_zone', 'user_accounts'] attribute_names = sorted(attribute_container.GetAttributeNames()) self.assertEqual(attribute_names, expected_attribute_names) diff --git a/tests/preprocessors/windows.py b/tests/preprocessors/windows.py index df81515b63..f11f0c4b26 100644 --- a/tests/preprocessors/windows.py +++ b/tests/preprocessors/windows.py @@ -143,6 +143,28 @@ def testCollectWithProgramData(self): self.assertEqual(environment_variable.value, '%SystemDrive%\\ProgramData') +class WindowsAvailableTimeZonesPluginTest( + test_lib.ArtifactPreprocessorPluginTestCase): + """Tests for the Windows available time zones plugin.""" + + def testParseKey(self): + """Tests the _ParseKey function.""" + test_file_path = self._GetTestFilePath(['SOFTWARE']) + self._SkipIfPathNotExists(test_file_path) + + plugin = windows.WindowsAvailableTimeZonesPlugin() + knowledge_base_object = ( + self._RunPreprocessorPluginOnWindowsRegistryValueSoftware(plugin)) + + available_time_zones = sorted( + knowledge_base_object.available_time_zones, + key=lambda time_zone: time_zone.name) + self.assertIsNotNone(available_time_zones) + self.assertEqual(len(available_time_zones), 101) + + self.assertEqual(available_time_zones[0].name, 'AUS Central Standard Time') + + class WindowsCodepagePlugin(test_lib.ArtifactPreprocessorPluginTestCase): """Tests for the Windows codepage plugin.""" @@ -377,13 +399,13 @@ def testParseKey(self): knowledge_base_object = ( self._RunPreprocessorPluginOnWindowsRegistryValueSoftware(plugin)) - users = sorted( + user_accounts = sorted( knowledge_base_object.user_accounts, key=lambda user_account: user_account.identifier) - self.assertIsNotNone(users) - self.assertEqual(len(users), 11) + self.assertIsNotNone(user_accounts) + self.assertEqual(len(user_accounts), 11) - user_account = users[9] + user_account = user_accounts[9] expected_sid = 'S-1-5-21-2036804247-3058324640-2116585241-1114' self.assertEqual(user_account.identifier, expected_sid)