mirror of
https://github.com/elisspace/autopsy.git
synced 2026-10-02 07:19:53 +00:00
Merge pull request #5355 from raman-bt/5560-fbmessenger-calllogs
5560 FB messenger calllogs
This commit is contained in:
@@ -45,6 +45,7 @@ from org.sleuthkit.datamodel import Account
|
||||
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
|
||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
|
||||
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CallMediaType
|
||||
|
||||
import json
|
||||
import traceback
|
||||
@@ -181,15 +182,242 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
|
||||
contactsDb.close()
|
||||
|
||||
|
||||
|
||||
## Adds a recipient to given list
|
||||
def addRecipientToList(self, user_key, recipientList):
|
||||
## Extracts recipeint id from 'user_key' column and adds recipient to given list,
|
||||
## if the recipeint id is not the same as sender id
|
||||
def addRecipientToList(self, user_key, senderId, recipientList):
|
||||
if user_key is not None:
|
||||
recipientId = user_key.replace('FACEBOOK:', '')
|
||||
recipientList.append(recipientId)
|
||||
recipientId = user_key.replace('FACEBOOK:', '')
|
||||
if recipientId != senderId:
|
||||
recipientList.append(recipientId)
|
||||
|
||||
|
||||
## Extracts sender id from the json in 'sender' column.
|
||||
def getSenderIdFromJson(self, senderJsonStr):
|
||||
senderId = None;
|
||||
if senderJsonStr is not None:
|
||||
sender_dict = json.loads(senderJsonStr)
|
||||
senderId = sender_dict['user_key']
|
||||
senderId = senderId.replace('FACEBOOK:', '')
|
||||
|
||||
## Analyze messages
|
||||
def analyzeMessages(self, dataSource, fileManager, context):
|
||||
return senderId
|
||||
|
||||
## determines communication direction by comparing senderId with selfAccountId
|
||||
def deduceDirectionFromSenderId(self, senderId):
|
||||
direction = CommunicationDirection.UNKNOWN
|
||||
if senderId is not None:
|
||||
if senderId == self.selfAccountId:
|
||||
direction = CommunicationDirection.OUTGOING
|
||||
else:
|
||||
direction = CommunicationDirection.INCOMING
|
||||
return direction
|
||||
|
||||
## Analyzes messages
|
||||
def analyzeMessages(self, threadsDb, threadsDBHelper):
|
||||
try:
|
||||
|
||||
## Messages are found in the messages table.
|
||||
## This query filters messages by msg_type to only get actual user created conversation messages (msg_type 0).
|
||||
## The participant ids can be found in the thread_participants table.
|
||||
## Participant names are found in thread_users table.
|
||||
## Joining these tables produces multiple rows per message, one row for each recipient.
|
||||
## The result set is processed to collect the multiple recipients for a given message.
|
||||
sqlString = """
|
||||
SELECT msg_id, text, sender, timestamp_ms, msg_type, messages.thread_key as thread_key,
|
||||
snippet, thread_participants.user_key as user_key, thread_users.name as name
|
||||
FROM messages
|
||||
JOIN thread_participants ON messages.thread_key = thread_participants.thread_key
|
||||
JOIN thread_users ON thread_participants.user_key = thread_users.user_key
|
||||
WHERE msg_type = 0
|
||||
ORDER BY msg_id
|
||||
"""
|
||||
|
||||
messagesResultSet = threadsDb.runQuery(sqlString)
|
||||
if messagesResultSet is not None:
|
||||
oldMsgId = None
|
||||
|
||||
direction = CommunicationDirection.UNKNOWN
|
||||
fromId = None
|
||||
recipientIdsList = None
|
||||
timeStamp = -1
|
||||
msgText = ""
|
||||
threadId = ""
|
||||
|
||||
while messagesResultSet.next():
|
||||
msgId = messagesResultSet.getString("msg_id")
|
||||
|
||||
# new msg begins when msgId changes
|
||||
if msgId != oldMsgId:
|
||||
# Create message artifact with collected attributes
|
||||
if oldMsgId is not None:
|
||||
messageArtifact = threadsDBHelper.addMessage(
|
||||
self._MESSAGE_TYPE,
|
||||
direction,
|
||||
fromId,
|
||||
recipientIdsList,
|
||||
timeStamp,
|
||||
MessageReadStatus.UNKNOWN,
|
||||
"", # subject
|
||||
msgText,
|
||||
threadId)
|
||||
|
||||
oldMsgId = msgId
|
||||
|
||||
# New message - collect all attributes
|
||||
recipientIdsList = []
|
||||
|
||||
## get sender id by parsing JSON in sender column
|
||||
fromId = self.getSenderIdFromJson(messagesResultSet.getString("sender"))
|
||||
direction = self.deduceDirectionFromSenderId(fromId)
|
||||
|
||||
# Get recipient and add to list
|
||||
self.addRecipientToList(messagesResultSet.getString("user_key"), fromId,
|
||||
recipientIdsList)
|
||||
|
||||
timeStamp = messagesResultSet.getLong("timestamp_ms") / 1000
|
||||
|
||||
# Get msg text
|
||||
# Sometimes there may not be an explict msg text,
|
||||
# but an app generated snippet instead
|
||||
msgText = messagesResultSet.getString("text")
|
||||
if not msgText:
|
||||
msgText = messagesResultSet.getString("snippet")
|
||||
|
||||
# TBD: get attachment
|
||||
|
||||
threadId = messagesResultSet.getString("thread_key")
|
||||
|
||||
else: # same msgId as last, just collect recipient from current row
|
||||
self.addRecipientToList(messagesResultSet.getString("user_key"), fromId,
|
||||
recipientIdsList)
|
||||
|
||||
|
||||
# at the end of the loop, add last message
|
||||
messageArtifact = threadsDBHelper.addMessage(
|
||||
self._MESSAGE_TYPE,
|
||||
direction,
|
||||
fromId,
|
||||
recipientIdsList,
|
||||
timeStamp,
|
||||
MessageReadStatus.UNKNOWN,
|
||||
"", # subject
|
||||
msgText,
|
||||
threadId)
|
||||
|
||||
except SQLException as ex:
|
||||
self._logger.log(Level.WARNING, "Error processing query result for FB Messenger messages.", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
except TskCoreException as ex:
|
||||
self._logger.log(Level.SEVERE, "Failed to add FB Messenger message artifacts.", ex)
|
||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
||||
except BlackboardException as ex:
|
||||
self._logger.log(Level.WARNING, "Failed to post artifacts.", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
|
||||
## Analyzes call logs
|
||||
def analyzeCallLogs(self, threadsDb, threadsDBHelper):
|
||||
try:
|
||||
|
||||
## Call logs are found in the messages table.
|
||||
## msg_type indicates type of call:
|
||||
## 9: one to one calls
|
||||
## 203: group call
|
||||
## 1-to-1 calls only have a call_ended record.
|
||||
## group calls have a call_started_record as well as call_ended recorded, with *different* message ids.
|
||||
## all the data we need can be found in the call_ended record.
|
||||
|
||||
sqlString = """
|
||||
SELECT msg_id, text, sender, timestamp_ms, msg_type, admin_text_thread_rtc_event,
|
||||
generic_admin_message_extensible_data,
|
||||
messages.thread_key as thread_key,
|
||||
thread_participants.user_key as user_key,
|
||||
thread_users.name as name
|
||||
FROM messages
|
||||
JOIN thread_participants ON messages.thread_key = thread_participants.thread_key
|
||||
JOIN thread_users ON thread_participants.user_key = thread_users.user_key
|
||||
WHERE msg_type = 9 OR (msg_type = 203 AND admin_text_thread_rtc_event = 'group_call_ended')
|
||||
ORDER BY msg_id
|
||||
"""
|
||||
|
||||
messagesResultSet = threadsDb.runQuery(sqlString)
|
||||
if messagesResultSet is not None:
|
||||
oldMsgId = None
|
||||
|
||||
direction = CommunicationDirection.UNKNOWN
|
||||
callerId = None
|
||||
calleeIdsList = None
|
||||
startTimeStamp = -1
|
||||
endTimeStamp = -1
|
||||
duration = 0
|
||||
mediaType = CallMediaType.AUDIO
|
||||
|
||||
while messagesResultSet.next():
|
||||
msgId = messagesResultSet.getString("msg_id")
|
||||
|
||||
# new call begins when msgId changes
|
||||
if msgId != oldMsgId:
|
||||
# Create call log artifact with collected attributes
|
||||
if oldMsgId is not None:
|
||||
messageArtifact = threadsDBHelper.addCalllog(
|
||||
direction,
|
||||
callerId,
|
||||
calleeIdsList,
|
||||
startTimeStamp,
|
||||
endTimeStamp,
|
||||
mediaType )
|
||||
|
||||
oldMsgId = msgId
|
||||
|
||||
# New message - collect all attributes
|
||||
calleeIdsList = []
|
||||
|
||||
## get caller id by parsing JSON in sender column
|
||||
callerId = self.getSenderIdFromJson(messagesResultSet.getString("sender"))
|
||||
direction = self.deduceDirectionFromSenderId(callerId)
|
||||
|
||||
# Get recipient and add to list
|
||||
self.addRecipientToList(messagesResultSet.getString("user_key"), callerId,
|
||||
calleeIdsList)
|
||||
|
||||
# the timestamp from call ended msg is used as end timestamp
|
||||
endTimeStamp = messagesResultSet.getLong("timestamp_ms") / 1000
|
||||
|
||||
# parse the generic_admin_message_extensible_data JSON to extract the duration and video fields
|
||||
adminDataJsonStr = messagesResultSet.getString("generic_admin_message_extensible_data")
|
||||
if adminDataJsonStr is not None:
|
||||
adminData_dict = json.loads(adminDataJsonStr)
|
||||
duration = adminData_dict['call_duration'] # call duration in seconds
|
||||
isVideo = adminData_dict['video']
|
||||
if isVideo:
|
||||
mediaType = CallMediaType.VIDEO
|
||||
|
||||
startTimeStamp = endTimeStamp - duration
|
||||
|
||||
else: # same msgId as last, just collect callee from current row
|
||||
self.addRecipientToList(messagesResultSet.getString("user_key"), callerId,
|
||||
calleeIdsList)
|
||||
|
||||
# at the end of the loop, add last message
|
||||
messageArtifact = threadsDBHelper.addCalllog(
|
||||
direction,
|
||||
callerId,
|
||||
calleeIdsList,
|
||||
startTimeStamp,
|
||||
endTimeStamp,
|
||||
mediaType )
|
||||
|
||||
except SQLException as ex:
|
||||
self._logger.log(Level.WARNING, "Error processing query result for FB Messenger call logs.", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
except TskCoreException as ex:
|
||||
self._logger.log(Level.SEVERE, "Failed to add FB Messenger call log artifacts.", ex)
|
||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
||||
except BlackboardException as ex:
|
||||
self._logger.log(Level.WARNING, "Failed to post FB Messenger call log artifacts.", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
|
||||
|
||||
## Analyze messages and call log threads
|
||||
def analyzeMessagesAndCallLogs(self, dataSource, fileManager, context):
|
||||
threadsDbs = AppSQLiteDB.findAppDatabases(dataSource, "threads_db2", True, self._FB_MESSENGER_PACKAGE_NAME)
|
||||
for threadsDb in threadsDbs:
|
||||
try:
|
||||
@@ -202,113 +430,12 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
|
||||
self._MODULE_NAME, threadsDb.getDBFile(),
|
||||
Account.Type.FACEBOOK)
|
||||
|
||||
## Messages are found in the messages table.
|
||||
## This query filters messages by msg_type to only get actual user created conversation messages (msg_type 0).
|
||||
## The participant ids can be found in the thread_participants table.
|
||||
## Participant names are found in thread_users table.
|
||||
## Joining these tables produces multiple rows per message, one row for each recipient.
|
||||
## The result set is processed to collect the multiple recipients for a given message.
|
||||
sqlString = """
|
||||
SELECT msg_id, text, sender, timestamp_ms, msg_type, messages.thread_key as thread_key,
|
||||
snippet, thread_participants.user_key as user_key, thread_users.name as name
|
||||
FROM messages
|
||||
JOIN thread_participants ON messages.thread_key = thread_participants.thread_key
|
||||
JOIN thread_users ON thread_participants.user_key = thread_users.user_key
|
||||
WHERE msg_type = 0
|
||||
ORDER BY msg_id
|
||||
"""
|
||||
|
||||
messagesResultSet = threadsDb.runQuery(sqlString)
|
||||
if messagesResultSet is not None:
|
||||
oldMsgId = None
|
||||
|
||||
direction = CommunicationDirection.UNKNOWN
|
||||
fromId = None
|
||||
recipientIdsList = None
|
||||
timeStamp = -1
|
||||
msgText = ""
|
||||
threadId = ""
|
||||
|
||||
while messagesResultSet.next():
|
||||
msgId = messagesResultSet.getString("msg_id")
|
||||
|
||||
# new msg begins when msgId changes
|
||||
if msgId != oldMsgId:
|
||||
# Create message artifact with collected attributes
|
||||
if oldMsgId is not None:
|
||||
messageArtifact = threadsDBHelper.addMessage(
|
||||
self._MESSAGE_TYPE,
|
||||
direction,
|
||||
fromId,
|
||||
recipientIdsList,
|
||||
timeStamp,
|
||||
MessageReadStatus.UNKNOWN,
|
||||
"", # subject
|
||||
msgText,
|
||||
threadId)
|
||||
|
||||
oldMsgId = msgId
|
||||
|
||||
# New message - collect all attributes
|
||||
recipientIdsList = []
|
||||
|
||||
## get sender id by parsing JSON in sender column
|
||||
senderJsonStr = messagesResultSet.getString("sender")
|
||||
if senderJsonStr is not None:
|
||||
sender_dict = json.loads(senderJsonStr)
|
||||
senderId = sender_dict['user_key']
|
||||
senderId = senderId.replace('FACEBOOK:', '')
|
||||
senderName = sender_dict['name']
|
||||
fromId = senderId
|
||||
if senderId == self.selfAccountId:
|
||||
direction = CommunicationDirection.OUTGOING
|
||||
else:
|
||||
direction = CommunicationDirection.INCOMING
|
||||
|
||||
|
||||
# Get recipient and add to list
|
||||
self.addRecipientToList(messagesResultSet.getString("user_key"),
|
||||
recipientIdsList)
|
||||
|
||||
timeStamp = messagesResultSet.getLong("timestamp_ms") / 1000
|
||||
|
||||
# Get msg text
|
||||
# Sometimes there may not be an explict msg text,
|
||||
# but an app generated snippet instead
|
||||
msgText = messagesResultSet.getString("text")
|
||||
if not msgText:
|
||||
msgText = messagesResultSet.getString("snippet")
|
||||
|
||||
# TBD: get attachment
|
||||
|
||||
threadId = messagesResultSet.getString("thread_key")
|
||||
|
||||
else: # same msgId as last, just collect recipient from current row
|
||||
self.addRecipientToList(messagesResultSet.getString("user_key"),
|
||||
recipientIdsList)
|
||||
|
||||
|
||||
# at the end of the loop, add last message
|
||||
messageArtifact = threadsDBHelper.addMessage(
|
||||
self._MESSAGE_TYPE,
|
||||
direction,
|
||||
fromId,
|
||||
recipientIdsList,
|
||||
timeStamp,
|
||||
MessageReadStatus.UNKNOWN,
|
||||
"", # subject
|
||||
msgText,
|
||||
threadId)
|
||||
self.analyzeMessages(threadsDb, threadsDBHelper)
|
||||
self.analyzeCallLogs(threadsDb, threadsDBHelper)
|
||||
|
||||
except SQLException as ex:
|
||||
self._logger.log(Level.WARNING, "Error processing query result for FB Messenger messages.", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
except TskCoreException as ex:
|
||||
self._logger.log(Level.SEVERE, "Failed to add FB Messenger message artifacts.", ex)
|
||||
self._logger.log(Level.SEVERE, "Failed to to create CommunicationArtifactsHelper for FB Messenger.", ex)
|
||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
||||
except BlackboardException as ex:
|
||||
self._logger.log(Level.WARNING, "Failed to post artifacts.", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
finally:
|
||||
threadsDb.close()
|
||||
|
||||
@@ -321,6 +448,6 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
|
||||
return
|
||||
|
||||
self.analyzeContacts(dataSource, fileManager, context)
|
||||
self.analyzeMessages(dataSource, fileManager, context)
|
||||
self.analyzeMessagesAndCallLogs(dataSource, fileManager, context)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user