Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SQLite parser for iOS Accounts (Accounts3.sqlite) file #4926

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
13 changes: 13 additions & 0 deletions plaso/data/formatters/ios.yaml
Original file line number Diff line number Diff line change
@@ -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}'
Expand Down
6 changes: 6 additions & 0 deletions plaso/data/timeliner.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
1 change: 1 addition & 0 deletions plaso/parsers/sqlite_plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
120 changes: 120 additions & 0 deletions plaso/parsers/sqlite_plugins/ios_accounts.py
Original file line number Diff line number Diff line change
@@ -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)
Binary file added test_data/Accounts3.sqlite
Binary file not shown.
42 changes: 42 additions & 0 deletions tests/parsers/sqlite_plugins/ios_accounts.py
Original file line number Diff line number Diff line change
@@ -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': '[email protected]'
}

event_data = storage_writer.GetAttributeContainerByIndex(
'event_data', 3)
self.CheckEventData(event_data, expected_event_values)


if __name__ == '__main__':
unittest.main()
Loading