From 31726ad8823bc90f8ac3a47dbfd4e53d4631e8fe Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Mon, 23 Sep 2019 18:06:31 -0400 Subject: [PATCH] Added comments and support for group calls in line --- InternalPythonModules/android/line.py | 113 ++++++++++++++++++---- InternalPythonModules/android/skype.py | 2 +- InternalPythonModules/android/whatsapp.py | 61 ++++++++++++ 3 files changed, 158 insertions(+), 18 deletions(-) diff --git a/InternalPythonModules/android/line.py b/InternalPythonModules/android/line.py index c87ef3477c..1cdf0162b3 100644 --- a/InternalPythonModules/android/line.py +++ b/InternalPythonModules/android/line.py @@ -54,7 +54,48 @@ import general class LineAnalyzer(general.AndroidComponentAnalyzer): """ - Parses the Line App databases for TSK contacts & message artifacts. + Parses the Line App databases for contacts, + message and call log artifacts. + + About Line parser for v9.15.1: + + - Line Database Design Details: + Line has unique ids associated with their users and with their groups. These ids + are referred to as mid in the database. + + Databases: + - naver_line: contains contact and msg artifacts + - call_history: contains call artifacts + + Tables: + - naver_line/groups: This table contains group ids paired with metadata + about the group (such as creator, group name, etc). + + - naver_line/membership This table maps user mids to group ids. Each record + contains 1 group id and 1 user mid. + + - naver_line/chat_history This table contains all chat history for private + (1 to 1) and group conversations. It maps a user mid + or group id to the message details. The user mid and + group id are stored into the same column "chat_id". + If the message direction is incoming, the sender mid + is stored in the from_mid column. + + - naver_line/contacts This table contains all Line contacts known to the + device. + + - call_history/call_history This table contains all call history for private + and group calls. It maps a user mid or a group id + to the call details. The user mid and group id are + stored in the "caller_mid" column. + + - Implementation Details: + 1) Both group calls and single calls are extracted in one query. The general approach + is to build one result table with both contact mids and group ids. + This result is consistently labeled contact_list_with_groups queries below. + This table is then joined once onto the messages table to produce all communication + data. + 2) Both group chats and single chats are extracted in one query. """ def __init__(self): @@ -195,22 +236,38 @@ class LineCallLogsParser(TskCallLogsParser): def __init__(self, calllog_db): super(LineCallLogsParser, self).__init__(calllog_db.runQuery( """ - SELECT substr(CallH.call_type, -1) AS direction, - CallH.start_time AS start_time, - CallH.end_time AS end_time, - ConT.server_name AS name, - CallH.voip_type AS call_type, - ConT.m_id - FROM call_history AS CallH - JOIN naver.contacts AS ConT - ON CallH.caller_mid = ConT.m_id + SELECT Substr(CH.call_type, -1) AS direction, + CH.start_time AS start_time, + CH.end_time AS end_time, + contacts_list_with_groups.members AS group_members, + contacts_list_with_groups.member_names AS names, + CH.caller_mid, + CH.voip_type AS call_type, + CH.voip_gc_media_type AS group_call_type + FROM (SELECT id, + Group_concat(M.m_id) AS members, + Group_concat(Replace(C.server_name, ",", "")) AS member_names + FROM membership AS M + JOIN naver.contacts AS C + ON M.m_id = C.m_id + GROUP BY id + UNION + SELECT m_id, + NULL, + server_name + FROM naver.contacts) AS contacts_list_with_groups + JOIN call_history AS CH + ON CH.caller_mid = contacts_list_with_groups.id """ - ) + ) ) self._OUTGOING_CALL_TYPE = "O" self._INCOMING_CALL_TYPE = "I" self._VIDEO_CALL_TYPE = "V" self._AUDIO_CALL_TYPE = "A" + self._GROUP_CALL_TYPE = "G" + self._GROUP_VIDEO_CALL_TYPE = "VIDEO" + self._GROUP_AUDIO_CALL_TYPE = "AUDIO" def get_call_direction(self): direction = self.result_set.getString("direction") @@ -232,21 +289,40 @@ class LineCallLogsParser(TskCallLogsParser): def get_phone_number_to(self): if self.get_call_direction() == self.OUTGOING_CALL: - return Account.Address(self.result_set.getString("m_id"), - self.result_set.getString("name")) + group_members = self.result_set.getString("group_members") + if group_members is not None: + group_members = group_members.split(",") + group_names = self.result_set.getString("names").split(",") + + recipients = [] + + for member_id, member_name in zip(group_members, group_names): + recipients.append(Account.Address(member_id, member_name)) + + return recipients + + return Account.Address(self.result_set.getString("caller_mid"), + self.result_set.getString("names")) return super(LineCallLogsParser, self).get_phone_number_to() def get_phone_number_from(self): if self.get_call_direction() == self.INCOMING_CALL: - return Account.Address(self.result_set.getString("m_id"), - self.result_set.getString("name")) + return Account.Address(self.result_set.getString("caller_mid"), + self.result_set.getString("names")) return super(LineCallLogsParser, self).get_phone_number_from() def get_call_type(self): - if self.result_set.getString("call_type") == self._VIDEO_CALL_TYPE: + call_type = self.result_set.getString("call_type") + if call_type == self._VIDEO_CALL_TYPE: return self.VIDEO_CALL - if self.result_set.getString("call_type") == self._AUDIO_CALL_TYPE: + if call_type == self._AUDIO_CALL_TYPE: return self.AUDIO_CALL + if call_type == self._GROUP_CALL_TYPE: + g_type = self.result_set.getString("group_call_type") + if g_type == self._GROUP_VIDEO_CALL_TYPE: + return self.VIDEO_CALL + if g_type == self._GROUP_AUDIO_CALL_TYPE: + return self.AUDIO_CALL return super(LineCallLogsParser, self).get_call_type() class LineContactsParser(TskContactsParser): @@ -279,6 +355,9 @@ class LineMessagesParser(TskMessagesParser): """ def __init__(self, message_db): + """ + + """ super(LineMessagesParser, self).__init__(message_db.runQuery( """ SELECT contact_list_with_groups.name, diff --git a/InternalPythonModules/android/skype.py b/InternalPythonModules/android/skype.py index f9bd6f5466..6d0df56259 100644 --- a/InternalPythonModules/android/skype.py +++ b/InternalPythonModules/android/skype.py @@ -61,7 +61,7 @@ class SkypeAnalyzer(general.AndroidComponentAnalyzer): About version 8.15.0.428 (9/17/2019) Skype database: - There are 4 tables this parser uses: 1) person - this table appears to hold all contacts known to the user. - 2) user - this table holds information pertaining to the user. + 2) user - this table holds information about the user. 3) particiapnt - Yes, that is not a typo. This table maps group chat ids to skype ids (1 to many). 4) chatItem - This table contains all messages. It maps the group id or diff --git a/InternalPythonModules/android/whatsapp.py b/InternalPythonModules/android/whatsapp.py index 328f371c76..8fdb09f13b 100644 --- a/InternalPythonModules/android/whatsapp.py +++ b/InternalPythonModules/android/whatsapp.py @@ -56,6 +56,67 @@ class WhatsAppAnalyzer(general.AndroidComponentAnalyzer): """ Parses the WhatsApp databases for TSK contact, message and calllog artifacts. + + About WhatsApp parser for v2.19.244: + - Database Design Details: + There are 2 databases and 6 tables this parser uses. + + 1) Prerequisties: + Each user is assigned a whatsapp id, refered to as jid in the + database. A jid is of the form: + + ####...####@whatsapp.net + + where # is a placeholder for an arbitrary length of digits 1-9. + + 2) Databases: + - databases/msgstore.db: contains msg and call log info + - databases/wa.db: contains contact info + + 3) Tables: + - wa/wa_contacts: Each record maps a jid to a users personal + details, such as name and phone number. + + - msgstore/call_log: Each call made on the device is a single row + in the call_log table. Each record holds + information such as duration, direction, and + type (Video or Audio). + + - msgstore/call_log_participant_v2: Each row of this table maps a jid to + a call_log record. Multiple rows that + share a call_log id indicate a group call. + + - msgstore/messages: Each message is represented as a single row. + A row maps a jid or a gjid (group jid) to some + message details. Both the jid and gjid are + stored in 1 column, called key_remote_jid. + gjid's are of the form: + + #####...###-#####...####@g.us + + where # is a place holder for a digit 1-9. The + '-' is a fixed character surrounded by digits + of arbiturary length n and m. + + If the message is not from a group, the jid the + message is to/from is stored in key_remote_jid + column. If it is a group, the key_remote_jid + column contains the gjid and the 'from' jid is + stored in a secondary column called + remote_resource. + + - msgstore/group_participants: Each row of this table maps a jid to a gjid. + + - msgstore/jid: This table stores raw jid string. Some tables + only store the jid_row. A join must be + performed to get the jid value out. + - Implementation details: + 1) Group calls and single calls are extracted in two different queries. + 2) Group messages and single messages are extracted in 1 query. + - The general approach was to build one complete contacts table containing + both jid and gjid. A join can be performed once on all of the messages. + All jids that are part of a gjid were concatenated into a comma seperated + list of jids. """ def __init__(self):