diff --git a/scripts/artifacts/FacebookMessenger.py b/scripts/artifacts/FacebookMessenger.py index 1f792be0..98f25903 100755 --- a/scripts/artifacts/FacebookMessenger.py +++ b/scripts/artifacts/FacebookMessenger.py @@ -1,3 +1,6 @@ +#2023-02-03: Added support for new msys_database format - Kevin Pagano + +import os import sqlite3 import textwrap @@ -8,7 +11,6 @@ def get_FacebookMessenger(files_found, report_folder, seeker, wrap_text): slash = '\\' if is_platform_windows() else '/' - #logfunc(str(files_found)) for file_found in files_found: file_found = str(file_found) @@ -32,7 +34,7 @@ def get_FacebookMessenger(files_found, report_folder, seeker, wrap_text): else: typeof ='' - if file_found.endswith('threads_db2-uid') or (file_found.startswith('ssus.') and file_found.endswith('threads_db2')): + if file_found.endswith('threads_db2-uid'): source_file = file_found.replace(seeker.directory, '') userid = '' data_list = [] @@ -52,163 +54,325 @@ def get_FacebookMessenger(files_found, report_folder, seeker, wrap_text): tsvname = f'Facebook{typeof}- User ID{usernum}' tsv(report_folder, data_headers, data_list, tsvname, source_file) + + continue + + if file_found.endswith(('-shm','-wal')): + continue + + if file_found.find('msys_database_') >= 0: + source_file = file_found.replace(seeker.directory, '') + db = open_sqlite_db_readonly(file_found) + cursor = db.cursor() + cursor.execute(''' + select + datetime(messages.timestamp_ms/1000,'unixepoch') as "Message Time", + contacts.name as "Sender", + messages.sender_id as "Sender Account ID", + messages.thread_key as "Thread Key", + messages.text as "Message", + attachments.title_text as "Snippet", + attachments.subtitle_text as "Call/Location Information", + attachments.filename as "Attachment File Name", + attachments.playable_url_mime_type as "Attachment Type", + attachments.playable_url as "Attachment URL", + attachment_ctas.native_url as "Location Lat/Long", + reactions.reaction as "Reaction", + datetime(reactions.reaction_creation_timestamp_ms/1000,'unixepoch') as "Reaction Time", + case + when messages.is_admin_message = 1 then "Yes" + when messages.is_admin_message = 0 then "No" + else messages.is_admin_message + end as "Is Admin Message", + messages.message_id as "Message ID" + from messages + join contacts on contacts.id = messages.sender_id + left join attachments on attachments.message_id = messages.message_id + left join attachment_ctas on messages.message_id = attachment_ctas.message_id + left join reactions on reactions.message_id = messages.message_id + order by "Message Time" ASC + ''') + + all_rows = cursor.fetchall() + usageentries = len(all_rows) + if usageentries > 0: + report = ArtifactHtmlReport('Facebook - Chats - msys_database') + report.start_artifact_report(report_folder, f'Facebook{typeof}- Chats{usernum} - msys_database') + report.add_script() + data_headers = ('Message Timestamp','Sender','Sender ID','Thread Key','Message','Snippet','Call/Location Information','Attachment Name','Attachment Type','Attachment URL','Location Lat/Long','Reaction','Reaction Timestamp','Is Admin Message','Message ID') # Don't remove the comma, that is required to make this a tuple as there is only 1 element + data_list = [] + for row in all_rows: + data_list.append((row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],row[8],row[9],row[10],row[11],row[12],row[13],row[14])) + + report.write_artifact_data_table(data_headers, data_list, file_found) + report.end_artifact_report() + + tsvname = f'Facebook{typeof}- Chats{usernum} - msys_database' + tsv(report_folder, data_headers, data_list, tsvname, source_file) + + else: + logfunc(f'No Facebook{typeof}- Chats{usernum} - msys_database data available') - if not file_found.endswith('threads_db2'): - continue # Skip all other files - - source_file = file_found.replace(seeker.directory, '') - db = open_sqlite_db_readonly(file_found) - cursor = db.cursor() - try: cursor.execute(''' select - case messages.timestamp_ms - when 0 then '' - else datetime(messages.timestamp_ms/1000,'unixepoch') - End as Datestamp, - (select json_extract (messages.sender, '$.name')) as "Sender", - substr((select json_extract (messages.sender, '$.user_key')),10) as "Sender ID", - messages.thread_key, - messages.text, - messages.snippet, - (select json_extract (messages.attachments, '$[0].filename')) as AttachmentName, - (select json_extract (messages.shares, '$[0].name')) as ShareName, - (select json_extract (messages.shares, '$[0].description')) as ShareDesc, - (select json_extract (messages.shares, '$[0].href')) as ShareLink - from messages, threads - where messages.thread_key=threads.thread_key and generic_admin_message_extensible_data IS NULL and msg_type != -1 - order by messages.thread_key, datestamp; + datetime(call_log.call_timestamp_ms/1000,'unixepoch') as "Call Timestamp", + strftime('%H:%M:%S',call_log.call_duration, 'unixepoch')as "Call Duration", + contacts.name as "Party Name", + case call_log.call_direction + when 1 then "Outgoing" + when 2 then "Incoming" + end as "Call Direction", + case call_log.call_media_type + when 2 then "Yes" + else "" + end as "Video Call", + case has_been_seen + when 0 then 'No' + when 1 then 'Yes' + end as "Call Answered", + call_log.thread_key + from call_log + left join contacts on contacts.id = call_log.thread_key ''') - snippet = 1 - except: + + all_rows = cursor.fetchall() + usageentries = len(all_rows) + if usageentries > 0: + report = ArtifactHtmlReport('Facebook - Calls - msys_database') + report.start_artifact_report(report_folder, f'Facebook{typeof}- Calls{usernum} - msys_database') + report.add_script() + data_headers = ('Call Timestamp','Call Duration','Party Name','Call Direction','Video Call','Call Answered','Thread Key') # Don't remove the comma, that is required to make this a tuple as there is only 1 element + data_list = [] + for row in all_rows: + data_list.append((row[0],row[1],row[2],row[3],row[4],row[5],row[6])) + + report.write_artifact_data_table(data_headers, data_list, file_found) + report.end_artifact_report() + + tsvname = f'Facebook{typeof}- Calls{usernum} - msys_database' + tsv(report_folder, data_headers, data_list, tsvname, source_file) + + else: + logfunc(f'No Facebook{typeof}- Calls{usernum} - msys_database data available') + cursor.execute(''' select - case messages.timestamp_ms - when 0 then '' - else datetime(messages.timestamp_ms/1000,'unixepoch') - End as Datestamp, - (select json_extract (messages.sender, '$.name')) as "Sender", - substr((select json_extract (messages.sender, '$.user_key')),10) as "Sender ID", - messages.thread_key, - messages.text, - (select json_extract (messages.attachments, '$[0].filename')) as AttachmentName, - (select json_extract (messages.shares, '$[0].name')) as ShareName, - (select json_extract (messages.shares, '$[0].description')) as ShareDesc, - (select json_extract (messages.shares, '$[0].href')) as ShareLink - from messages, threads - where messages.thread_key=threads.thread_key and generic_admin_message_extensible_data IS NULL and msg_type != -1 - order by messages.thread_key, datestamp; + id, + name, + normalized_name_for_search, + username, + profile_picture_large_url, + email_address, + phone_number, + case is_messenger_user + when 0 then "" + when 1 then "Yes" + end as "Messenger User", + case friendship_status + when 0 then "N/A (Self)" + when 1 then "Friends" + when 2 then "Friend Request Received" + when 3 then "Friend Request Sent" + when 4 then "Not Friends" + end as "Friendship Status", + substr(datetime(birthday_timestamp,'unixepoch'),6,5) as "Birthdate (MM-DD)" + from contacts ''') - - all_rows = cursor.fetchall() - usageentries = len(all_rows) - if usageentries > 0: - report = ArtifactHtmlReport('Facebook - Chats') - report.start_artifact_report(report_folder, f'Facebook{typeof}- Chats{usernum}') - report.add_script() - data_list = [] - - if snippet == 1: - data_headers = ('Timestamp','Sender Name','Sender ID','Thread Key','Message','Snippet','Attachment Name','Share Name','Share Description','Share Link') + + all_rows = cursor.fetchall() + usageentries = len(all_rows) + if usageentries > 0: + report = ArtifactHtmlReport('Facebook - Contacts - msys_database') + report.start_artifact_report(report_folder, f'Facebook{typeof}- Contacts{usernum} - msys_database') + report.add_script() + data_headers = ('Facebook ID','Name','Normalized Name','User Name','Profile Pic URL','Email Address','Phone Number','Is Messenger User','Friendship Status','Birthday Timestamp') # Don't remove the comma, that is required to make this a tuple as there is only 1 element + data_list = [] for row in all_rows: data_list.append((row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],row[8],row[9])) + + report.write_artifact_data_table(data_headers, data_list, file_found) + report.end_artifact_report() + + tsvname = f'Facebook{typeof}- Contacts{usernum} - msys_database' + tsv(report_folder, data_headers, data_list, tsvname, source_file) + else: - data_headers = ('Timestamp','Sender Name','Sender ID','Thread Key','Message','Attachment Name','Share Name','Share Description','Share Link') - for row in all_rows: - data_list.append((row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],row[8])) - - report.write_artifact_data_table(data_headers, data_list, file_found) - report.end_artifact_report() + logfunc(f'No Facebook{typeof}- Contacts{usernum} - msys_database data available') - tsvname = f'Facebook Messenger{typeof}- Chats{usernum}' - tsv(report_folder, data_headers, data_list, tsvname, source_file) + db.close() - tlactivity = f'Facebook Messenger{typeof}- Chats{usernum}' - timeline(report_folder, tlactivity, data_list, data_headers) - else: - logfunc(f'No Facebook{typeof}- Chats data available{usernum}') - - cursor.execute(''' - select - datetime((messages.timestamp_ms/1000)-(select json_extract (messages.generic_admin_message_extensible_data, '$.call_duration')),'unixepoch') as "Timestamp", - (select json_extract (messages.generic_admin_message_extensible_data, '$.caller_id')) as "Caller ID", - (select json_extract (messages.sender, '$.name')) as "Receiver", - substr((select json_extract (messages.sender, '$.user_key')),10) as "Receiver ID", - strftime('%H:%M:%S',(select json_extract (messages.generic_admin_message_extensible_data, '$.call_duration')), 'unixepoch')as "Call Duration", - case (select json_extract (messages.generic_admin_message_extensible_data, '$.video')) - when false then '' - else 'Yes' - End as "Video Call" - from messages, threads - where messages.thread_key=threads.thread_key and generic_admin_message_extensible_data NOT NULL - order by messages.thread_key, "Date/Time End"; - ''') - - all_rows = cursor.fetchall() - usageentries = len(all_rows) - if usageentries > 0: - report = ArtifactHtmlReport('Facebook - Calls') - report.start_artifact_report(report_folder, f'Facebook{typeof}- Calls{usernum}') - report.add_script() - data_headers = ('Timestamp','Caller ID','Receiver Name','Receiver ID','Call Duration','Video Call') # Don't remove the comma, that is required to make this a tuple as there is only 1 element - data_list = [] - for row in all_rows: - data_list.append((row[0],row[1],row[2],row[3],row[4],row[5])) + continue - report.write_artifact_data_table(data_headers, data_list, file_found) - report.end_artifact_report() + if (file_found.startswith('ssus.') and file_found.endswith('threads_db2')) or file_found.endswith('threads_db2'): + source_file = file_found.replace(seeker.directory, '') + db = open_sqlite_db_readonly(file_found) + cursor = db.cursor() + try: + cursor.execute(''' + select + case messages.timestamp_ms + when 0 then '' + else datetime(messages.timestamp_ms/1000,'unixepoch') + End as Datestamp, + (select json_extract (messages.sender, '$.name')) as "Sender", + substr((select json_extract (messages.sender, '$.user_key')),10) as "Sender ID", + messages.thread_key, + messages.text, + messages.snippet, + (select json_extract (messages.attachments, '$[0].filename')) as AttachmentName, + (select json_extract (messages.shares, '$[0].name')) as ShareName, + (select json_extract (messages.shares, '$[0].description')) as ShareDesc, + (select json_extract (messages.shares, '$[0].href')) as ShareLink, + message_reactions.reaction as "Message Reaction", + datetime(message_reactions.reaction_timestamp/1000,'unixepoch') as "Message Reaction Timestamp", + messages.msg_id + from messages, threads + left join message_reactions on message_reactions.msg_id = messages.msg_id + where messages.thread_key=threads.thread_key and generic_admin_message_extensible_data IS NULL and msg_type != -1 + order by messages.thread_key, datestamp; + ''') + snippet = 1 + except: + cursor.execute(''' + select + case messages.timestamp_ms + when 0 then '' + else datetime(messages.timestamp_ms/1000,'unixepoch') + End as Datestamp, + (select json_extract (messages.sender, '$.name')) as "Sender", + substr((select json_extract (messages.sender, '$.user_key')),10) as "Sender ID", + messages.thread_key, + messages.text, + (select json_extract (messages.attachments, '$[0].filename')) as AttachmentName, + (select json_extract (messages.shares, '$[0].name')) as ShareName, + (select json_extract (messages.shares, '$[0].description')) as ShareDesc, + (select json_extract (messages.shares, '$[0].href')) as ShareLink, + message_reactions.reaction as "Message Reaction", + datetime(message_reactions.reaction_timestamp/1000,'unixepoch') as "Message Reaction Timestamp", + messages.msg_id + from messages, threads + left join message_reactions on message_reactions.msg_id = messages.msg_id + where messages.thread_key=threads.thread_key and generic_admin_message_extensible_data IS NULL and msg_type != -1 + order by messages.thread_key, datestamp; + ''') + + all_rows = cursor.fetchall() + usageentries = len(all_rows) + if usageentries > 0: + report = ArtifactHtmlReport('Facebook - Chats - threads_db2') + report.start_artifact_report(report_folder, f'Facebook{typeof}- Chats{usernum} - threads_db2') + report.add_script() + data_list = [] + + if snippet == 1: + data_headers = ('Timestamp','Sender Name','Sender ID','Thread Key','Message','Snippet','Attachment Name','Share Name','Share Description','Share Link','Message Reaction','Message Reaction Timestamp','Message ID') + for row in all_rows: + data_list.append((row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],row[8],row[9],row[10],row[11],row[12])) + else: + data_headers = ('Timestamp','Sender Name','Sender ID','Thread Key','Message','Attachment Name','Share Name','Share Description','Share Link','Message Reaction','Message Reaction Timestamp','Message ID') + for row in all_rows: + data_list.append((row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],row[8],row[9],row[10],row[11])) + + report.write_artifact_data_table(data_headers, data_list, file_found) + report.end_artifact_report() + + tsvname = f'Facebook Messenger{typeof}- Chats{usernum} - threads_db2' + tsv(report_folder, data_headers, data_list, tsvname, source_file) + + tlactivity = f'Facebook Messenger{typeof}- Chats{usernum} - threads_db2' + timeline(report_folder, tlactivity, data_list, data_headers) + else: + logfunc(f'No Facebook{typeof}- Chats{usernum} - threads_db2 data available') - tsvname = f'Facebook{typeof}- Calls{usernum}' - tsv(report_folder, data_headers, data_list, tsvname, source_file) + cursor.execute(''' + select + datetime((messages.timestamp_ms/1000)-(select json_extract (messages.generic_admin_message_extensible_data, '$.call_duration')),'unixepoch') as "Call Timestamp", + strftime('%H:%M:%S',(select json_extract (messages.generic_admin_message_extensible_data, '$.call_duration')), 'unixepoch')as "Call Duration", + (select json_extract (messages.generic_admin_message_extensible_data, '$.caller_id')) as "Caller ID", + (select json_extract (messages.sender, '$.name')) as "Receiver", + substr((select json_extract (messages.sender, '$.user_key')),10) as "Receiver ID", + case (select json_extract (messages.generic_admin_message_extensible_data, '$.video')) + when false then '' + else 'Yes' + End as "Video Call", + messages.thread_key + from messages, threads + where messages.thread_key=threads.thread_key and generic_admin_message_extensible_data NOT NULL + order by messages.thread_key, "Date/Time End"; + ''') + + all_rows = cursor.fetchall() + usageentries = len(all_rows) + if usageentries > 0: + report = ArtifactHtmlReport('Facebook - Calls - threads_db2') + report.start_artifact_report(report_folder, f'Facebook{typeof}- Calls{usernum} - threads_db2') + report.add_script() + data_headers = ('Timestamp','Call Duration','Caller ID','Receiver Name','Receiver ID','Video Call','Thread Key') # Don't remove the comma, that is required to make this a tuple as there is only 1 element + data_list = [] + for row in all_rows: + data_list.append((row[0],row[1],row[2],row[3],row[4],row[5],row[6])) + + report.write_artifact_data_table(data_headers, data_list, file_found) + report.end_artifact_report() + + tsvname = f'Facebook{typeof}- Calls{usernum} - threads_db2' + tsv(report_folder, data_headers, data_list, tsvname, source_file) + + tlactivity = f'Facebook{typeof}- Calls{usernum} - threads_db2' + timeline(report_folder, tlactivity, data_list, data_headers) + else: + logfunc(f'No Facebook{typeof}- Calls{usernum} - threads_db2 data available') - tlactivity = f'Facebook{typeof}- Calls{usernum}' - timeline(report_folder, tlactivity, data_list, data_headers) - else: - logfunc(f'No Facebook{typeof}- Calls data available{usernum}') - - cursor.execute(''' - select - substr(user_key,10), - first_name, - last_name, - username, - (select json_extract (profile_pic_square, '$[0].url')) as profile_pic_square, - case is_messenger_user - when 0 then '' - else 'Yes' - end is_messenger_user, - case is_friend - when 0 then 'No' - else 'Yes' - end is_friend - from thread_users - ''') + cursor.execute(''' + select + substr(user_key,10), + first_name, + last_name, + username, + (select json_extract (profile_pic_square, '$[0].url')) as profile_pic_square, + case is_messenger_user + when 0 then '' + else 'Yes' + end is_messenger_user, + case is_friend + when 0 then 'No' + when 1 then 'Yes' + end is_friend, + friendship_status, + contact_relationship_status + from thread_users + ''') - all_rows = cursor.fetchall() - usageentries = len(all_rows) - if usageentries > 0: - report = ArtifactHtmlReport('Facebook - Contacts') - report.start_artifact_report(report_folder, f'Facebook{typeof}- Contacts{usernum}') - report.add_script() - data_headers = ('User ID','First Name','Last Name','Username','Profile Pic URL','Is App User','Is Friend') # Don't remove the comma, that is required to make this a tuple as there is only 1 element - data_list = [] - for row in all_rows: - data_list.append((row[0],row[1],row[2],row[3],row[4],row[5],row[6])) + all_rows = cursor.fetchall() + usageentries = len(all_rows) + if usageentries > 0: + report = ArtifactHtmlReport('Facebook - Contacts - threads_db2') + report.start_artifact_report(report_folder, f'Facebook{typeof}- Contacts{usernum} - threads_db2') + report.add_script() + data_headers = ('User ID','First Name','Last Name','Username','Profile Pic URL','Is Messenger User','Is Friend','Friendship Status','Contact Relationship Status') # Don't remove the comma, that is required to make this a tuple as there is only 1 element + data_list = [] + for row in all_rows: + data_list.append((row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],row[8])) - report.write_artifact_data_table(data_headers, data_list, file_found) - report.end_artifact_report() + report.write_artifact_data_table(data_headers, data_list, file_found) + report.end_artifact_report() + + tsvname = f'Facebook{typeof}- Contacts{usernum} - threads_db2' + tsv(report_folder, data_headers, data_list, tsvname, source_file) + + else: + logfunc(f'No Facebook{typeof}- Contacts{usernum} - threads_db2 data available') - tsvname = f'Facebook{typeof}- Contacts{usernum}' - tsv(report_folder, data_headers, data_list, tsvname, source_file) + db.close() + continue + else: - logfunc(f'No Facebook{typeof}- Contacts data available{usernum}') - - db.close() - + continue # Skip all other files + __artifacts__ = { "FacebookMessenger": ( "Facebook Messenger", - ('*/*threads_db2*'), + ('*/*threads_db2*','*/msys_database*'), get_FacebookMessenger) } diff --git a/scripts/report.py b/scripts/report.py index a5d3473a..21949515 100755 --- a/scripts/report.py +++ b/scripts/report.py @@ -80,7 +80,11 @@ def get_icon_name(category, artifact): elif category == 'EMULATED STORAGE METADATA': icon = 'database' elif category == 'ENCRYPTING MEDIA APPS': icon = 'lock' elif category == 'ETC HOSTS': icon = 'globe' - elif category == 'FACEBOOK MESSENGER': icon = 'facebook' + elif category == 'FACEBOOK MESSENGER': + if artifact.find('CALLS') >= 0: icon = 'phone-call' + elif artifact.find('CHAT') >= 0: icon = 'message-circle' + elif artifact.find('CONTACTS') >= 0: icon = 'users' + else: icon = 'facebook' elif category == 'FILES BY GOOGLE': icon = 'file' elif category == 'FIREBASE CLOUD MESSAGING': icon = 'database' elif category == 'FIREFOX': diff --git a/scripts/version_info.py b/scripts/version_info.py index 5657e081..2c2b3de1 100755 --- a/scripts/version_info.py +++ b/scripts/version_info.py @@ -1,4 +1,4 @@ -aleapp_version = '3.1.5' +aleapp_version = '3.1.6' # Contributors List # Format = [ Name, Blog-url, Twitter-handle, Github-url]