diff --git a/plaso/data/formatters/ios.yaml b/plaso/data/formatters/ios.yaml index bd3c3b005c..99ccc19e4d 100644 --- a/plaso/data/formatters/ios.yaml +++ b/plaso/data/formatters/ios.yaml @@ -1,6 +1,19 @@ # Plaso iOS related event formatters. --- type: 'conditional' +data_type: 'ios:accounts:account' +message: +- 'Account Type: {account_type}' +- 'Username: {username}' +- 'Identifier: {identifier}' +- 'Owning Bundle Identifier: {owning_bundle_id}' +short_message: +- 'Account Type: {account_type}' +- 'Username: {username}' +short_source: 'SQLITE' +source: 'iOS accounts database' +--- +type: 'conditional' data_type: 'ios:app_privacy:access' message: - 'Accessor Identifier: {accessor_identifier}' diff --git a/plaso/data/timeliner.yaml b/plaso/data/timeliner.yaml index 1681f33d74..53c397e26f 100644 --- a/plaso/data/timeliner.yaml +++ b/plaso/data/timeliner.yaml @@ -487,6 +487,12 @@ attribute_mappings: description: 'Creation Time' place_holder_event: false --- +data_type: 'ios:accounts:account' +attribute_mappings: +- name: 'date' + description: 'Creation Time' +place_holder_event: true +--- data_type: 'ios:app_privacy:access' attribute_mappings: - name: 'recorded_time' diff --git a/plaso/parsers/sqlite_plugins/__init__.py b/plaso/parsers/sqlite_plugins/__init__.py index dfa7c0a680..509b4fae69 100644 --- a/plaso/parsers/sqlite_plugins/__init__.py +++ b/plaso/parsers/sqlite_plugins/__init__.py @@ -21,6 +21,7 @@ from plaso.parsers.sqlite_plugins import firefox_history from plaso.parsers.sqlite_plugins import gdrive from plaso.parsers.sqlite_plugins import imessage +from plaso.parsers.sqlite_plugins import ios_accounts from plaso.parsers.sqlite_plugins import ios_datausage from plaso.parsers.sqlite_plugins import ios_kik from plaso.parsers.sqlite_plugins import ios_netusage diff --git a/plaso/parsers/sqlite_plugins/ios_accounts.py b/plaso/parsers/sqlite_plugins/ios_accounts.py new file mode 100644 index 0000000000..aa887b5086 --- /dev/null +++ b/plaso/parsers/sqlite_plugins/ios_accounts.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +"""SQLite parser plugin for iOS accounts (Accounts3.db) database files.""" + +from dfdatetime import cocoa_time as dfdatetime_cocoa_time + +from plaso.containers import events +from plaso.parsers import sqlite +from plaso.parsers.sqlite_plugins import interface + + +class IOSAccounts(events.EventData): + """iOS accounts event data. + + Attributes: + date (dfdatetime.DateTimeValues): date and time the account + was created. + account_type (str): account type. + username (str): user name. + identifier (str): identifier. + owning_bundle_id (str): owning bundle identifier of the app + managing the account. + """ + + DATA_TYPE = 'ios:accounts:account' + + def __init__(self): + """Initializes event data.""" + super(IOSAccounts, self).__init__(data_type=self.DATA_TYPE) + self.date = None + self.account_type = None + self.username = None + self.identifier = None + self.owning_bundle_id = None + +class IOSAccountsPlugin(interface.SQLitePlugin): + """SQLite parser plugin for iOS accounts (Accounts3.db) database files.""" + + NAME = 'ios_accounts' + DATA_FORMAT = 'iOS accounts SQLite database (Accounts3.db) file' + + REQUIRED_STRUCTURE = { + 'ZACCOUNT': frozenset([ + 'ZACCOUNTTYPE', 'ZDATE', 'ZUSERNAME', 'ZIDENTIFIER', + 'ZOWNINGBUNDLEID']), + 'ZACCOUNTTYPE': frozenset([ + 'Z_PK', 'ZACCOUNTTYPEDESCRIPTION']) + } + + QUERIES = [(( + 'SELECT ZACCOUNT.ZDATE, ZACCOUNTTYPE.ZACCOUNTTYPEDESCRIPTION, ' + 'ZACCOUNT.ZUSERNAME, ZACCOUNT.ZIDENTIFIER, ZACCOUNT.ZOWNINGBUNDLEID ' + 'FROM ZACCOUNT LEFT JOIN ZACCOUNTTYPE ' + 'ON ZACCOUNT.ZACCOUNTTYPE = ZACCOUNTTYPE.Z_PK'), + 'ParseAccountRow')] + + SCHEMAS = { + 'ZACCOUNT': ( + 'CREATE TABLE ZACCOUNT (Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, ' + 'Z_OPT INTEGER, ZACTIVE INTEGER, ZAUTHENTICATED INTEGER, ' + 'ZSUPPORTSAUTHENTICATION INTEGER, ZVISIBLE INTEGER, ' + 'ZACCOUNTTYPE INTEGER, ZPARENTACCOUNT INTEGER, ' + 'ZDATE TIMESTAMP, ZLASTCREDENTIALRENEWALREJECTIONDATE TIMESTAMP, ' + 'ZACCOUNTDESCRIPTION TEXT, ZAUTHENTICATIONTYPE TEXT, ' + 'ZCREDENTIALTYPE TEXT, ZIDENTIFIER TEXT, ZOWNINGBUNDLEID TEXT, ' + 'ZUSERNAME TEXT, ZDATACLASSPROPERTIES BLOB)'), + 'ZACCOUNTTYPE': ( + 'CREATE TABLE ZACCOUNTTYPE (Z_PK INTEGER PRIMARY KEY, ' + 'Z_ENT INTEGER, Z_OPT INTEGER, ZENCRYPTACCOUNTPROPERTIES INTEGER, ' + 'ZOBSOLETE INTEGER, ZSUPPORTSAUTHENTICATION INTEGER, ' + 'ZSUPPORTSMULTIPLEACCOUNTS INTEGER, ZVISIBILITY INTEGER, ' + 'ZACCOUNTTYPEDESCRIPTION TEXT, ZCREDENTIALPROTECTIONPOLICY TEXT, ' + 'ZCREDENTIALTYPE TEXT, ZIDENTIFIER TEXT, ZOWNINGBUNDLEID TEXT)')} + + REQUIRES_SCHEMA_MATCH = False + + def _GetTimeRowValue(self, query_hash, row, value_name): + """Retrieves a date and time value from the row. + + Args: + query_hash (int): hash of the query, that uniquely + identifies the query that produced the row. + row (sqlite3.Row): row. + value_name (str): name of the value. + + Returns: + dfdatetime.CocoaTime: date and time value or None if not available. + """ + timestamp = self._GetRowValue(query_hash, row, value_name) + if timestamp is None: + return None + + return dfdatetime_cocoa_time.CocoaTime(timestamp=timestamp) + + # pylint: disable=unused-argument + def ParseAccountRow( + self, parser_mediator, query, row, **unused_kwargs): + """Parses an account row. + + Args: + parser_mediator (ParserMediator): mediates interactions between + parsers and other components, such as storage and dfVFS. + query (str): query that created the row. + row (sqlite3.Row): row. + """ + query_hash = hash(query) + + event_data = IOSAccounts() + event_data.date = self._GetTimeRowValue(query_hash, row, 'ZDATE') + event_data.account_type = self._GetRowValue(query_hash, + row, 'ZACCOUNTTYPEDESCRIPTION') + event_data.username = self._GetRowValue(query_hash, row, 'ZUSERNAME') + event_data.identifier = self._GetRowValue(query_hash, row, + 'ZIDENTIFIER') + event_data.owning_bundle_id = self._GetRowValue(query_hash, row, + 'ZOWNINGBUNDLEID') + + parser_mediator.ProduceEventData(event_data) + + +sqlite.SQLiteParser.RegisterPlugin(IOSAccountsPlugin) diff --git a/test_data/Accounts3.sqlite b/test_data/Accounts3.sqlite new file mode 100644 index 0000000000..719065727e Binary files /dev/null and b/test_data/Accounts3.sqlite differ diff --git a/tests/parsers/sqlite_plugins/ios_accounts.py b/tests/parsers/sqlite_plugins/ios_accounts.py new file mode 100644 index 0000000000..4577eaeff8 --- /dev/null +++ b/tests/parsers/sqlite_plugins/ios_accounts.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +"""Tests for the SQLite parser plugin for iOS accounts database files.""" + +import unittest + +from plaso.parsers.sqlite_plugins import ios_accounts + +from tests.parsers.sqlite_plugins import test_lib + + +class IOSAccountsPluginTest(test_lib.SQLitePluginTestCase): + """Tests for the SQLite parser plugin for iOS accounts database files.""" + + def testParse(self): + """Tests the ParseAccountRow method.""" + plugin = ios_accounts.IOSAccountsPlugin() + storage_writer = self._ParseDatabaseFileWithPlugin( + ['Accounts3.sqlite'], plugin) + + number_of_event_data = storage_writer.GetNumberOfAttributeContainers( + 'event_data') + self.assertEqual(number_of_event_data, 18) + + number_of_warnings = storage_writer.GetNumberOfAttributeContainers( + 'extraction_warning') + self.assertEqual(number_of_warnings, 0) + + expected_event_values = { + 'date': '2020-03-21T21:47:57.068197+00:00', + 'account_type': 'iCloud', + 'identifier': '1589F4EC-8F6C-4F37-929F-C6F121B36A59', + 'owning_bundle_id': 'com.apple.purplebuddy', + 'username': 'thisisdfir@gmail.com' + } + + event_data = storage_writer.GetAttributeContainerByIndex( + 'event_data', 3) + self.CheckEventData(event_data, expected_event_values) + + +if __name__ == '__main__': + unittest.main()