Merge pull request #5355 from raman-bt/5560-fbmessenger-calllogs

5560  FB messenger calllogs
This commit is contained in:
Richard Cordovano
2019-11-05 10:08:17 -05:00
committed by GitHub
+239 -112
View File
@@ -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)