diff --git a/plaso/data/formatters/android.yaml b/plaso/data/formatters/android.yaml index bd76492cdd..6fe2c47d30 100644 --- a/plaso/data/formatters/android.yaml +++ b/plaso/data/formatters/android.yaml @@ -230,6 +230,33 @@ short_source: 'Twitter Android' source: 'Twitter Android Status' --- type: 'conditional' +data_type: 'android:viber:call' +enumeration_helpers: +- input_attribute: 'type' + output_attribute: 'type' + default_value: 'UNKNOWN' + values: + 1: 'INCOMING' + 2: 'OUTGOING' +- input_attribute: 'viber_call_type' + output_attribute: 'viber_call_type' + default_value: 'UNKNOWN' + values: + 1: 'AUDIO CALL' + 4: 'VIDEO CALL' +message: +- 'Number: {number}' +- 'Call type: {type}' +- 'Viber call type: {viber_call_type}' +- 'Duration: {duration} seconds' +short_message: +- '{number}' +- '{type}' +- '{viber_call_type}' +short_source: 'Android Viber' +source: 'Android Viber Call History' +--- +type: 'conditional' data_type: 'android:webview:cookie' message: - 'Host: {host}' diff --git a/plaso/data/timeliner.yaml b/plaso/data/timeliner.yaml index 1681f33d74..0b5635d7d9 100644 --- a/plaso/data/timeliner.yaml +++ b/plaso/data/timeliner.yaml @@ -87,6 +87,14 @@ attribute_mappings: description: 'Creation Time' place_holder_event: true --- +data_type: 'android:viber:call' +attribute_mappings: +- name: 'end_time' + description: 'End Time' +- name: 'start_time' + description: 'Start Time' +place_holder_event: true +--- data_type: 'android:webview:cookie' attribute_mappings: - name: 'expiration_time' diff --git a/plaso/parsers/sqlite_plugins/__init__.py b/plaso/parsers/sqlite_plugins/__init__.py index dfa7c0a680..685efd2009 100644 --- a/plaso/parsers/sqlite_plugins/__init__.py +++ b/plaso/parsers/sqlite_plugins/__init__.py @@ -8,6 +8,7 @@ from plaso.parsers.sqlite_plugins import android_tango from plaso.parsers.sqlite_plugins import android_turbo from plaso.parsers.sqlite_plugins import android_twitter +from plaso.parsers.sqlite_plugins import android_viber_call from plaso.parsers.sqlite_plugins import android_webview from plaso.parsers.sqlite_plugins import android_webviewcache from plaso.parsers.sqlite_plugins import chrome_autofill diff --git a/plaso/parsers/sqlite_plugins/android_viber_call.py b/plaso/parsers/sqlite_plugins/android_viber_call.py new file mode 100644 index 0000000000..04d75592cf --- /dev/null +++ b/plaso/parsers/sqlite_plugins/android_viber_call.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +"""SQLite parser plugin for Android Viber call history database files.""" + +from dfdatetime import java_time as dfdatetime_java_time + +from plaso.containers import events +from plaso.lib import definitions +from plaso.parsers import sqlite +from plaso.parsers.sqlite_plugins import interface + + +class AndroidViberCallEventData(events.EventData): + """Android Viber Call event data. + + Attributes: + number (str): phone number. + type (int): type of call, such as: Incoming, or Outgoing. + viber_call_type (int): type of call in Viber,(Audio Call, or Video Call). + duration (int): number of seconds the call lasted. + start_time (dfdatetime.DateTimeValues): date and time the call was started. + end_time (dfdatetime.DateTimeValues): date and time the call was stopped. + """ + + DATA_TYPE = 'android:viber:call' + + def __init__(self): + """Initializes event data.""" + super(AndroidViberCallEventData, self).__init__(data_type=self.DATA_TYPE) + self.number = None + self.type = None + self.viber_call_type = None + self.duration = None + self.start_time = None + self.end_time = None + + +class AndroidViberCallPlugin(interface.SQLitePlugin): + """SQLite parser plugin for Android Viber call history database files. + + The Android Viber call history database file is typically stored in: + viber_data + """ + + NAME = 'android_viber_call' + DATA_FORMAT = 'Android Viber call history SQLite database (viber_data) file' + + REQUIRED_STRUCTURE = { + 'calls': frozenset(['_id', 'date', 'number', 'duration', 'type', + 'viber_call_type'])} + + QUERIES = [ + ('SELECT _id AS id, date, number, duration, type, ' + 'viber_call_type FROM calls', 'ParseViberCallsRow')] + + SCHEMAS = [{ + 'blockednumbers': ( + 'CREATE TABLE blockednumbers (_id INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'canonized_number TEXT NOT NULL, blocked_date LONG DEFAULT 0, ' + 'block_reason INTEGER DEFAULT 0, status INTEGER DEFAULT 0, ' + 'UNIQUE (canonized_number) ON CONFLICT REPLACE )'), + 'calls': ( + 'CREATE TABLE calls (_id INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'call_id LONG NOT NULL, aggregate_hash LONG NOT NULL, ' + 'number TEXT NOT NULL, canonized_number TEXT NOT NULL, ' + 'viber_call BOOLEAN DEFAULT TRUE, viber_call_type INTEGER DEFAULT 1, ' + 'date LONG NOT NULL, duration LONG NOT NULL, type INT NOT NULL, ' + 'end_reason INT DEFAULT 0, start_reason INT DEFAULT 0, ' + 'token LONG DEFAULT 0, looked BOOLEAN DEFAULT TRUE, ' + 'conference_info TEXT, group_id LONG DEFAULT 0, ' + 'flags INTEGER DEFAULT 0)'), + 'phonebookcontact': ( + 'CREATE TABLE phonebookcontact (_id INTEGER PRIMARY KEY NOT NULL, ' + 'native_id INTEGER DEFAULT 0, display_name TEXT, phonetic_name TEXT, ' + 'phone_label TEXT, low_display_name TEXT, starred BOOLEAN, ' + 'viber BOOLEAN, contact_lookup_key TEXT, contact_hash LONG, ' + 'version INTEGER DEFAULT 0, has_number BOOLEAN, has_name BOOLEAN, ' + 'native_photo_id LONG DEFAULT 0, recently_joined_date LONG DEFAULT 0, ' + 'joined_date LONG DEFAULT 0, numbers_name TEXT DEFAULT '', ' + 'deleted BOOLEAN, flags INTEGER DEFAULT 0, ' + 'UNIQUE (_id) ON CONFLICT REPLACE)'), + 'sqlite_sequence': ( + 'CREATE TABLE sqlite_sequence(name,seq)'), + 'sync_data': ( + 'CREATE TABLE sync_data (_id INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'canonized_phone_number TEXT NOT NULL, display_name TEXT DEFAULT '', ' + 'phonetic_name TEXT DEFAULT '',operation INTEGER DEFAULT 0)'), + 'vibernumbers': ( + 'CREATE TABLE vibernumbers (_id INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'canonized_number TEXT NOT NULL, photo TEXT DEFAULT '', ' + 'viber_name TEXT, clear BOOLEAN, member_id TEXT NOT NULL, ' + 'encrypted_member_id TEXT NOT NULL, viber_id TEXT, ' + 'date_of_birth TEXT)'), + 'viberpay_data': ( + 'CREATE TABLE viberpay_data (_id INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'encrypted_member_id TEXT NULL, canonized_phone_number TEXT NULL, ' + 'phone_number TEXT NULL, country_code TEXT NULL, ' + 'is_country_supported BOOLEAN NOT NULL, ' + 'default_currency_code TEXT NULL, is_viberpay_user BOOLEAN NOT NULL, ' + 'last_sync_date INTEGER NOT NULL)'), + 'walletlist': ( + 'CREATE TABLE walletlist (_id INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'country_codes INTEGER NOT NULL)'), + 'walletnumbers': ( + 'CREATE TABLE walletnumbers (_id INTEGER PRIMARY KEY AUTOINCREMENT, ' + 'canonized_number TEXT NOT NULL, wallet_wu_status INTEGER DEFAULT 0, ' + 'UNIQUE (canonized_number) ON CONFLICT REPLACE )'), + }] + + def ParseViberCallsRow(self, parser_mediator, query, row, **unused_kwargs): + """Parses a Viber Call record 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) + + duration = self._GetRowValue(query_hash, row, 'duration') + timestamp = self._GetRowValue(query_hash, row, 'date') + + event_data = AndroidViberCallEventData() + event_data.type = self._GetRowValue(query_hash, row, 'type') + event_data.duration = duration + event_data.number = self._GetRowValue(query_hash, row, 'number') + event_data.viber_call_type = ( + self._GetRowValue(query_hash, row, 'viber_call_type')) + event_data.start_time = dfdatetime_java_time.JavaTime(timestamp=timestamp) + + if duration: + # The duration is in seconds and the date value in milliseconds. + timestamp += duration * definitions.MILLISECONDS_PER_SECOND + + event_data.end_time = dfdatetime_java_time.JavaTime(timestamp=timestamp) + + parser_mediator.ProduceEventData(event_data) + + +sqlite.SQLiteParser.RegisterPlugin(AndroidViberCallPlugin) diff --git a/test_data/viber_data b/test_data/viber_data new file mode 100644 index 0000000000..24ce4a994c Binary files /dev/null and b/test_data/viber_data differ diff --git a/tests/parsers/sqlite_plugins/android_viber_call.py b/tests/parsers/sqlite_plugins/android_viber_call.py new file mode 100644 index 0000000000..72e62e8143 --- /dev/null +++ b/tests/parsers/sqlite_plugins/android_viber_call.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Tests for the Android Viber call history plugin.""" + +import unittest + +from tests.parsers.sqlite_plugins import test_lib +from plaso.parsers.sqlite_plugins import android_viber_call + + +class AndroidViberCallSQLitePluginTest(test_lib.SQLitePluginTestCase): + """Tests for the Android Viber Call History database plugin.""" + + def testProcess(self): + """Test the Process function on an Android viber_data file.""" + plugin = android_viber_call.AndroidViberCallPlugin() + storage_writer = self._ParseDatabaseFileWithPlugin(['viber_data'], plugin) + + number_of_event_data = storage_writer.GetNumberOfAttributeContainers( + 'event_data') + self.assertEqual(number_of_event_data, 4) + + number_of_warnings = storage_writer.GetNumberOfAttributeContainers( + 'extraction_warning') + self.assertEqual(number_of_warnings, 0) + + number_of_warnings = storage_writer.GetNumberOfAttributeContainers( + 'recovery_warning') + self.assertEqual(number_of_warnings, 0) + + expected_event_values = { + 'type': 2, + 'data_type': 'android:viber:call', + 'duration': 105, + 'number': '+19198887386', + 'start_time': '2022-11-25T20:43:08.267+00:00', + 'end_time': '2022-11-25T20:44:53.267+00:00', + 'viber_call_type': 4} + + event_data = storage_writer.GetAttributeContainerByIndex('event_data', 3) + self.CheckEventData(event_data, expected_event_values) + + +if __name__ == '__main__': + unittest.main()