mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
5907: Update legacy Python modules to use Communication Artifacts helper.
This commit is contained in:
parent
f11a6ac526
commit
da016a0d34
@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.coreutils;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
|
import java.sql.DatabaseMetaData;
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
@ -286,6 +287,16 @@ public final class AppSQLiteDB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns connection meta data.
|
||||||
|
*
|
||||||
|
* @return DatabaseMetaData
|
||||||
|
* @throws SQLException
|
||||||
|
*/
|
||||||
|
public DatabaseMetaData getConnectionMetadata() throws SQLException {
|
||||||
|
return connection.getMetaData();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches for a meta file associated with the give SQLite database. If
|
* Searches for a meta file associated with the give SQLite database. If
|
||||||
* found, it copies this file into the temp directory of the current case.
|
* found, it copies this file into the temp directory of the current case.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Autopsy Forensic Browser
|
Autopsy Forensic Browser
|
||||||
|
|
||||||
Copyright 2016-2018 Basis Technology Corp.
|
Copyright 2016-2020 Basis Technology Corp.
|
||||||
Contact: carrier <at> sleuthkit <dot> org
|
Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -18,10 +18,10 @@ limitations under the License.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from java.io import File
|
from java.io import File
|
||||||
from java.io import IOException
|
|
||||||
from java.lang import Class
|
from java.lang import Class
|
||||||
from java.lang import ClassNotFoundException
|
from java.lang import ClassNotFoundException
|
||||||
from java.lang import String
|
from java.lang import Integer
|
||||||
|
from java.lang import Long
|
||||||
from java.sql import Connection
|
from java.sql import Connection
|
||||||
from java.sql import DriverManager
|
from java.sql import DriverManager
|
||||||
from java.sql import ResultSet
|
from java.sql import ResultSet
|
||||||
@ -29,11 +29,14 @@ from java.sql import SQLException
|
|||||||
from java.sql import Statement
|
from java.sql import Statement
|
||||||
from java.util.logging import Level
|
from java.util.logging import Level
|
||||||
from java.util import ArrayList
|
from java.util import ArrayList
|
||||||
|
from java.util import UUID
|
||||||
from org.sleuthkit.autopsy.casemodule import Case
|
from org.sleuthkit.autopsy.casemodule import Case
|
||||||
|
from org.sleuthkit.autopsy.casemodule import NoCurrentCaseException
|
||||||
from org.sleuthkit.autopsy.casemodule.services import FileManager
|
from org.sleuthkit.autopsy.casemodule.services import FileManager
|
||||||
from org.sleuthkit.autopsy.coreutils import Logger
|
from org.sleuthkit.autopsy.coreutils import Logger
|
||||||
from org.sleuthkit.autopsy.coreutils import MessageNotifyUtil
|
from org.sleuthkit.autopsy.coreutils import MessageNotifyUtil
|
||||||
from org.sleuthkit.autopsy.datamodel import ContentUtils
|
from org.sleuthkit.autopsy.datamodel import ContentUtils
|
||||||
|
from org.sleuthkit.autopsy.coreutils import AppSQLiteDB
|
||||||
from org.sleuthkit.autopsy.ingest import IngestJobContext
|
from org.sleuthkit.autopsy.ingest import IngestJobContext
|
||||||
from org.sleuthkit.autopsy.ingest import IngestServices
|
from org.sleuthkit.autopsy.ingest import IngestServices
|
||||||
from org.sleuthkit.autopsy.ingest import ModuleDataEvent
|
from org.sleuthkit.autopsy.ingest import ModuleDataEvent
|
||||||
@ -41,128 +44,103 @@ from org.sleuthkit.datamodel import AbstractFile
|
|||||||
from org.sleuthkit.datamodel import Blackboard
|
from org.sleuthkit.datamodel import Blackboard
|
||||||
from org.sleuthkit.datamodel import BlackboardArtifact
|
from org.sleuthkit.datamodel import BlackboardArtifact
|
||||||
from org.sleuthkit.datamodel import BlackboardAttribute
|
from org.sleuthkit.datamodel import BlackboardAttribute
|
||||||
from org.sleuthkit.datamodel.BlackboardAttribute import ATTRIBUTE_TYPE
|
|
||||||
from org.sleuthkit.datamodel import Content
|
from org.sleuthkit.datamodel import Content
|
||||||
from org.sleuthkit.datamodel import TskCoreException
|
from org.sleuthkit.datamodel import TskCoreException
|
||||||
|
from org.sleuthkit.datamodel.Blackboard import BlackboardException
|
||||||
from org.sleuthkit.datamodel import Account
|
from org.sleuthkit.datamodel import Account
|
||||||
from org.sleuthkit.datamodel import Relationship
|
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CallMediaType
|
||||||
|
|
||||||
import traceback
|
import traceback
|
||||||
import general
|
import general
|
||||||
|
|
||||||
deviceAccountInstance = None
|
|
||||||
|
|
||||||
"""
|
|
||||||
Locates a variety of different call log databases, parses them, and populates the blackboard.
|
|
||||||
"""
|
|
||||||
class CallLogAnalyzer(general.AndroidComponentAnalyzer):
|
class CallLogAnalyzer(general.AndroidComponentAnalyzer):
|
||||||
|
"""
|
||||||
|
Locates a variety of different call log databases, parses them, and populates the blackboard.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
# the names of db files that potentially hold call logs
|
||||||
self._logger = Logger.getLogger(self.__class__.__name__)
|
_dbFileNames = ["logs.db", "contacts.db", "contacts2.db"]
|
||||||
|
|
||||||
# the names of tables that potentially hold call logs in the dbs
|
# the names of tables that potentially hold call logs in the dbs
|
||||||
_tableNames = ["calls", "logs"]
|
_tableNames = ["calls", "logs"]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._logger = Logger.getLogger(self.__class__.__name__)
|
||||||
|
self._PACKAGE_NAME = "com.sec.android.provider.logsprovider"
|
||||||
|
self._PARSER_NAME = "Android CallLog Parser"
|
||||||
|
|
||||||
class CallDirection:
|
|
||||||
|
|
||||||
def __init__(self, type, displayName):
|
|
||||||
self.type = type
|
|
||||||
self.displayName = displayName
|
|
||||||
|
|
||||||
def getDisplayName(self):
|
|
||||||
return self.displayName
|
|
||||||
|
|
||||||
INCOMING = CallDirection(1, "Incoming")
|
|
||||||
OUTGOING = CallDirection(2, "Outgoing")
|
|
||||||
MISSED = CallDirection(3, "Missed")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def fromType(t):
|
|
||||||
return {
|
|
||||||
1: CallLogAnalyzer.INCOMING,
|
|
||||||
2: CallLogAnalyzer.OUTGOING,
|
|
||||||
3: CallLogAnalyzer.MISSED
|
|
||||||
}.get(t, None)
|
|
||||||
|
|
||||||
def analyze(self, dataSource, fileManager, context):
|
def analyze(self, dataSource, fileManager, context):
|
||||||
try:
|
for _dbFileName in CallLogAnalyzer._dbFileNames:
|
||||||
absFiles = fileManager.findFiles(dataSource, "logs.db")
|
selfAccountId = None
|
||||||
absFiles.addAll(fileManager.findFiles(dataSource, "contacts.db"))
|
callLogDbs = AppSQLiteDB.findAppDatabases(dataSource, _dbFileName, True, self._PACKAGE_NAME)
|
||||||
absFiles.addAll(fileManager.findFiles(dataSource, "contacts2.db"))
|
for callLogDb in callLogDbs:
|
||||||
for abstractFile in absFiles:
|
|
||||||
try:
|
try:
|
||||||
file = File(Case.getCurrentCase().getTempDirectory(), str(abstractFile.getId()) + abstractFile.getName())
|
current_case = Case.getCurrentCaseThrows()
|
||||||
ContentUtils.writeToFile(abstractFile, file, context.dataSourceIngestIsCancelled)
|
if selfAccountId is not None:
|
||||||
self.__findCallLogsInDB(file.toString(), abstractFile, dataSource)
|
callLogDbHelper = CommunicationArtifactsHelper(current_case.getSleuthkitCase(),
|
||||||
except IOException as ex:
|
self._PARSER_NAME,
|
||||||
self._logger.log(Level.SEVERE, "Error writing temporary call log db to disk", ex)
|
callLogDb.getDBFile(),
|
||||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
Account.Type.PHONE, Account.Type.PHONE, selfAccountId )
|
||||||
except TskCoreException as ex:
|
else:
|
||||||
# Error finding call logs.
|
callLogDbHelper = CommunicationArtifactsHelper(current_case.getSleuthkitCase(),
|
||||||
pass
|
self._PARSER_NAME,
|
||||||
|
callLogDb.getDBFile(),
|
||||||
def __findCallLogsInDB(self, databasePath, abstractFile, dataSource):
|
Account.Type.PHONE )
|
||||||
if not databasePath:
|
|
||||||
return
|
for tableName in CallLogAnalyzer._tableNames:
|
||||||
|
|
||||||
|
|
||||||
bbartifacts = list()
|
|
||||||
try:
|
|
||||||
connection = DriverManager.getConnection("jdbc:sqlite:" + databasePath)
|
|
||||||
statement = connection.createStatement()
|
|
||||||
|
|
||||||
# Create a 'Device' account using the data source device id
|
|
||||||
datasourceObjId = dataSource.getDataSource().getId()
|
|
||||||
ds = Case.getCurrentCase().getSleuthkitCase().getDataSource(datasourceObjId)
|
|
||||||
deviceID = ds.getDeviceId()
|
|
||||||
deviceAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.DEVICE, deviceID, general.MODULE_NAME, abstractFile)
|
|
||||||
|
|
||||||
for tableName in CallLogAnalyzer._tableNames:
|
|
||||||
try:
|
|
||||||
resultSet = statement.executeQuery("SELECT number, date, duration, type, name FROM " + tableName + " ORDER BY date DESC;")
|
|
||||||
self._logger.log(Level.INFO, "Reading call log from table {0} in db {1}", [tableName, databasePath])
|
|
||||||
while resultSet.next():
|
|
||||||
date = resultSet.getLong("date") / 1000
|
|
||||||
direction = CallLogAnalyzer.fromType(resultSet.getInt("type"))
|
|
||||||
directionString = direction.getDisplayName() if direction is not None else ""
|
|
||||||
number = resultSet.getString("number")
|
|
||||||
duration = resultSet.getLong("duration") # duration of call is in seconds
|
|
||||||
name = resultSet.getString("name") # name of person dialed or called. None if unregistered
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
attributes = ArrayList()
|
resultSet = callLogDb.runQuery("SELECT number, date, duration, type, name FROM " + tableName + " ORDER BY date DESC;")
|
||||||
artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG) # create a call log and then add attributes from result set.
|
self._logger.log(Level.INFO, "Reading call log from table {0} in db {1}", [tableName, callLogDb.getDBFile().getName()])
|
||||||
if direction == CallLogAnalyzer.OUTGOING:
|
if resultSet is not None:
|
||||||
attributes.add(BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, general.MODULE_NAME, number))
|
while resultSet.next():
|
||||||
else: # Covers INCOMING and MISSED
|
direction = ""
|
||||||
attributes.add(BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, general.MODULE_NAME, number))
|
callerId = None
|
||||||
|
calleeId = None
|
||||||
|
|
||||||
|
timeStamp = resultSet.getLong("date") / 1000
|
||||||
|
|
||||||
|
number = resultSet.getString("number")
|
||||||
|
duration = resultSet.getLong("duration") # duration of call is in seconds
|
||||||
|
name = resultSet.getString("name") # name of person dialed or called. None if unregistered
|
||||||
|
|
||||||
attributes.add(BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_START, general.MODULE_NAME, date))
|
calltype = resultSet.getInt("type")
|
||||||
attributes.add(BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_END, general.MODULE_NAME, duration + date))
|
if calltype == 1 or calltype == 3:
|
||||||
attributes.add(BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DIRECTION, general.MODULE_NAME, directionString))
|
direction = CommunicationDirection.INCOMING
|
||||||
attributes.add(BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME, general.MODULE_NAME, name))
|
callerId = number
|
||||||
|
elif calltype == 2 or calltype == 5:
|
||||||
|
direction = CommunicationDirection.OUTGOING
|
||||||
|
calleeId = number
|
||||||
|
else:
|
||||||
|
direction = CommunicationDirection.UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
artifact.addAttributes(attributes)
|
## add a call log
|
||||||
|
if callerId is not None or calleeId is not None:
|
||||||
# Create an account
|
callLogArtifact = callLogDbHelper.addCalllog( direction,
|
||||||
calllogAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.PHONE, number, general.MODULE_NAME, abstractFile);
|
callerId,
|
||||||
|
calleeId,
|
||||||
# create relationship between accounts
|
timeStamp, ## start time
|
||||||
Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().addRelationships(deviceAccountInstance, [calllogAccountInstance], artifact, Relationship.Type.CALL_LOG, date);
|
timeStamp + duration * 1000, ## end time
|
||||||
|
CallMediaType.AUDIO)
|
||||||
bbartifacts.append(artifact)
|
|
||||||
|
|
||||||
|
except SQLException as ex:
|
||||||
|
self._logger.log(Level.WARNING, "Error processing query result for Android messages.", ex)
|
||||||
|
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||||
except TskCoreException as ex:
|
except TskCoreException as ex:
|
||||||
self._logger.log(Level.SEVERE, "Error posting call log record to the blackboard", ex)
|
self._logger.log(Level.SEVERE, "Failed to add Android call log artifacts.", ex)
|
||||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
self._logger.log(Level.SEVERE, traceback.format_exc())
|
||||||
except SQLException as ex:
|
except BlackboardException as ex:
|
||||||
# Could not read table in db.
|
self._logger.log(Level.WARNING, "Failed to post artifacts.", ex)
|
||||||
# Catch and proceed to the next table in the loop.
|
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||||
pass
|
|
||||||
except SQLException as ex:
|
|
||||||
# Could not parse call log; error connecting to db.
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
if bbartifacts:
|
|
||||||
Case.getCurrentCase().getSleuthkitCase().getBlackboard().postArtifacts(bbartifacts, general.MODULE_NAME)
|
|
||||||
|
|
||||||
|
except TskCoreException as ex:
|
||||||
|
self._logger.log(Level.SEVERE, "Failed to create CommunicationArtifactsHelper.", ex)
|
||||||
|
self._logger.log(Level.SEVERE, traceback.format_exc())
|
||||||
|
except NoCurrentCaseException as ex:
|
||||||
|
self._logger.log(Level.WARNING, "No case currently open.", ex)
|
||||||
|
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||||
|
finally:
|
||||||
|
callLogDb.close()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Autopsy Forensic Browser
|
Autopsy Forensic Browser
|
||||||
|
|
||||||
Copyright 2016-2018 Basis Technology Corp.
|
Copyright 2016-2020 Basis Technology Corp.
|
||||||
Contact: carrier <at> sleuthkit <dot> org
|
Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -20,6 +20,8 @@ limitations under the License.
|
|||||||
from java.io import File
|
from java.io import File
|
||||||
from java.lang import Class
|
from java.lang import Class
|
||||||
from java.lang import ClassNotFoundException
|
from java.lang import ClassNotFoundException
|
||||||
|
from java.lang import Integer
|
||||||
|
from java.lang import Long
|
||||||
from java.sql import Connection
|
from java.sql import Connection
|
||||||
from java.sql import DatabaseMetaData
|
from java.sql import DatabaseMetaData
|
||||||
from java.sql import DriverManager
|
from java.sql import DriverManager
|
||||||
@ -28,11 +30,14 @@ from java.sql import SQLException
|
|||||||
from java.sql import Statement
|
from java.sql import Statement
|
||||||
from java.util.logging import Level
|
from java.util.logging import Level
|
||||||
from java.util import ArrayList
|
from java.util import ArrayList
|
||||||
|
from java.util import UUID
|
||||||
from org.sleuthkit.autopsy.casemodule import Case
|
from org.sleuthkit.autopsy.casemodule import Case
|
||||||
|
from org.sleuthkit.autopsy.casemodule import NoCurrentCaseException
|
||||||
from org.sleuthkit.autopsy.casemodule.services import FileManager
|
from org.sleuthkit.autopsy.casemodule.services import FileManager
|
||||||
from org.sleuthkit.autopsy.coreutils import Logger
|
from org.sleuthkit.autopsy.coreutils import Logger
|
||||||
from org.sleuthkit.autopsy.coreutils import MessageNotifyUtil
|
from org.sleuthkit.autopsy.coreutils import MessageNotifyUtil
|
||||||
from org.sleuthkit.autopsy.datamodel import ContentUtils
|
from org.sleuthkit.autopsy.datamodel import ContentUtils
|
||||||
|
from org.sleuthkit.autopsy.coreutils import AppSQLiteDB
|
||||||
from org.sleuthkit.autopsy.ingest import IngestJobContext
|
from org.sleuthkit.autopsy.ingest import IngestJobContext
|
||||||
from org.sleuthkit.autopsy.ingest import IngestServices
|
from org.sleuthkit.autopsy.ingest import IngestServices
|
||||||
from org.sleuthkit.autopsy.ingest import ModuleDataEvent
|
from org.sleuthkit.autopsy.ingest import ModuleDataEvent
|
||||||
@ -42,32 +47,35 @@ from org.sleuthkit.datamodel import BlackboardArtifact
|
|||||||
from org.sleuthkit.datamodel import BlackboardAttribute
|
from org.sleuthkit.datamodel import BlackboardAttribute
|
||||||
from org.sleuthkit.datamodel import Content
|
from org.sleuthkit.datamodel import Content
|
||||||
from org.sleuthkit.datamodel import TskCoreException
|
from org.sleuthkit.datamodel import TskCoreException
|
||||||
|
from org.sleuthkit.datamodel.Blackboard import BlackboardException
|
||||||
from org.sleuthkit.datamodel import Account
|
from org.sleuthkit.datamodel import Account
|
||||||
from org.sleuthkit.datamodel import Relationship
|
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
||||||
|
|
||||||
import traceback
|
import traceback
|
||||||
import general
|
import general
|
||||||
|
|
||||||
"""
|
|
||||||
Locates a variety of different contacts databases, parses them, and populates the blackboard.
|
|
||||||
"""
|
|
||||||
class ContactAnalyzer(general.AndroidComponentAnalyzer):
|
class ContactAnalyzer(general.AndroidComponentAnalyzer):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Finds and parsers Android contacts database, and populates the blackboard with Contacts.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._logger = Logger.getLogger(self.__class__.__name__)
|
self._logger = Logger.getLogger(self.__class__.__name__)
|
||||||
|
self._PACKAGE_NAME = "com.android.providers.contacts"
|
||||||
|
self._PARSER_NAME = "Android Contacts Parser"
|
||||||
|
self._VERSION = "53.1.0.1" # icu_version in 'properties' table.
|
||||||
|
|
||||||
def analyze(self, dataSource, fileManager, context):
|
def analyze(self, dataSource, fileManager, context):
|
||||||
try:
|
try:
|
||||||
|
|
||||||
absFiles = fileManager.findFiles(dataSource, "contacts.db")
|
contactsDbs = AppSQLiteDB.findAppDatabases(dataSource, "contacts.db", True, self._PACKAGE_NAME)
|
||||||
absFiles.addAll(fileManager.findFiles(dataSource, "contacts2.db"))
|
contactsDbs.addAll(AppSQLiteDB.findAppDatabases(dataSource, "contacts2.db", True, self._PACKAGE_NAME))
|
||||||
if absFiles.isEmpty():
|
if contactsDbs.isEmpty():
|
||||||
return
|
return
|
||||||
for abstractFile in absFiles:
|
for contactDb in contactsDbs:
|
||||||
try:
|
try:
|
||||||
jFile = File(Case.getCurrentCase().getTempDirectory(), str(abstractFile.getId()) + abstractFile.getName())
|
self.__findContactsInDB(contactDb, dataSource)
|
||||||
ContentUtils.writeToFile(abstractFile, jFile, context.dataSourceIngestIsCancelled)
|
|
||||||
self.__findContactsInDB(str(jFile.toString()), abstractFile, dataSource)
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self._logger.log(Level.SEVERE, "Error parsing Contacts", ex)
|
self._logger.log(Level.SEVERE, "Error parsing Contacts", ex)
|
||||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
self._logger.log(Level.SEVERE, traceback.format_exc())
|
||||||
@ -76,48 +84,33 @@ class ContactAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Will create artifact from a database given by the path
|
Queries the given contact database and adds Contacts to the case.
|
||||||
The fileId will be the abstract file associated with the artifacts
|
|
||||||
"""
|
"""
|
||||||
def __findContactsInDB(self, databasePath, abstractFile, dataSource):
|
def __findContactsInDB(self, contactDb, dataSource):
|
||||||
if not databasePath:
|
if not contactDb:
|
||||||
return
|
return
|
||||||
|
|
||||||
bbartifacts = list()
|
|
||||||
try:
|
try:
|
||||||
Class.forName("org.sqlite.JDBC") # load JDBC driver
|
current_case = Case.getCurrentCaseThrows()
|
||||||
connection = DriverManager.getConnection("jdbc:sqlite:" + databasePath)
|
|
||||||
statement = connection.createStatement()
|
|
||||||
except (ClassNotFoundException) as ex:
|
|
||||||
self._logger.log(Level.SEVERE, "Error loading JDBC driver", ex)
|
|
||||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
|
||||||
return
|
|
||||||
except (SQLException) as ex:
|
|
||||||
# Error opening database.
|
|
||||||
return
|
|
||||||
|
|
||||||
|
# Create a helper to parse the DB
|
||||||
# Create a 'Device' account using the data source device id
|
contactDbHelper = CommunicationArtifactsHelper(current_case.getSleuthkitCase(),
|
||||||
datasourceObjId = dataSource.getDataSource().getId()
|
self._PARSER_NAME,
|
||||||
ds = Case.getCurrentCase().getSleuthkitCase().getDataSource(datasourceObjId)
|
contactDb.getDBFile(),
|
||||||
deviceID = ds.getDeviceId()
|
Account.Type.PHONE )
|
||||||
|
|
||||||
deviceAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance (Account.Type.DEVICE, deviceID, general.MODULE_NAME, abstractFile)
|
|
||||||
|
|
||||||
resultSet = None
|
|
||||||
try:
|
|
||||||
# get display_name, mimetype(email or phone number) and data1 (phonenumber or email address depending on mimetype)
|
# get display_name, mimetype(email or phone number) and data1 (phonenumber or email address depending on mimetype)
|
||||||
# sorted by name, so phonenumber/email would be consecutive for a person if they exist.
|
# sorted by name, so phonenumber/email would be consecutive for a person if they exist.
|
||||||
# check if contacts.name_raw_contact_id exists. Modify the query accordingly.
|
# check if contacts.name_raw_contact_id exists. Modify the query accordingly.
|
||||||
columnFound = False
|
columnFound = False
|
||||||
metadata = connection.getMetaData()
|
metadata = contactDb.getConnectionMetadata()
|
||||||
columnListResultSet = metadata.getColumns(None, None, "contacts", None)
|
columnListResultSet = metadata.getColumns(None, None, "contacts", None)
|
||||||
while columnListResultSet.next():
|
while columnListResultSet.next():
|
||||||
if columnListResultSet.getString("COLUMN_NAME") == "name_raw_contact_id":
|
if columnListResultSet.getString("COLUMN_NAME") == "name_raw_contact_id":
|
||||||
columnFound = True
|
columnFound = True
|
||||||
break
|
break
|
||||||
if columnFound:
|
if columnFound:
|
||||||
resultSet = statement.executeQuery(
|
resultSet = contactDb.runQuery(
|
||||||
"SELECT mimetype, data1, name_raw_contact.display_name AS display_name \n"
|
"SELECT mimetype, data1, name_raw_contact.display_name AS display_name \n"
|
||||||
+ "FROM raw_contacts JOIN contacts ON (raw_contacts.contact_id=contacts._id) \n"
|
+ "FROM raw_contacts JOIN contacts ON (raw_contacts.contact_id=contacts._id) \n"
|
||||||
+ "JOIN raw_contacts AS name_raw_contact ON(name_raw_contact_id=name_raw_contact._id) "
|
+ "JOIN raw_contacts AS name_raw_contact ON(name_raw_contact_id=name_raw_contact._id) "
|
||||||
@ -126,7 +119,7 @@ class ContactAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
+ "WHERE mimetype = 'vnd.android.cursor.item/phone_v2' OR mimetype = 'vnd.android.cursor.item/email_v2'\n"
|
+ "WHERE mimetype = 'vnd.android.cursor.item/phone_v2' OR mimetype = 'vnd.android.cursor.item/email_v2'\n"
|
||||||
+ "ORDER BY name_raw_contact.display_name ASC;")
|
+ "ORDER BY name_raw_contact.display_name ASC;")
|
||||||
else:
|
else:
|
||||||
resultSet = statement.executeQuery(
|
resultSet = contactDb.runQuery(
|
||||||
"SELECT mimetype, data1, raw_contacts.display_name AS display_name \n"
|
"SELECT mimetype, data1, raw_contacts.display_name AS display_name \n"
|
||||||
+ "FROM raw_contacts JOIN contacts ON (raw_contacts.contact_id=contacts._id) \n"
|
+ "FROM raw_contacts JOIN contacts ON (raw_contacts.contact_id=contacts._id) \n"
|
||||||
+ "LEFT OUTER JOIN data ON (data.raw_contact_id=raw_contacts._id) \n"
|
+ "LEFT OUTER JOIN data ON (data.raw_contact_id=raw_contacts._id) \n"
|
||||||
@ -134,51 +127,56 @@ class ContactAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
+ "WHERE mimetype = 'vnd.android.cursor.item/phone_v2' OR mimetype = 'vnd.android.cursor.item/email_v2'\n"
|
+ "WHERE mimetype = 'vnd.android.cursor.item/phone_v2' OR mimetype = 'vnd.android.cursor.item/email_v2'\n"
|
||||||
+ "ORDER BY raw_contacts.display_name ASC;")
|
+ "ORDER BY raw_contacts.display_name ASC;")
|
||||||
|
|
||||||
attributes = ArrayList()
|
contactArtifact = None
|
||||||
artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT)
|
oldName = None
|
||||||
oldName = ""
|
phoneNumber = None
|
||||||
|
emailAddr = None
|
||||||
|
name = None
|
||||||
while resultSet.next():
|
while resultSet.next():
|
||||||
name = resultSet.getString("display_name")
|
name = resultSet.getString("display_name")
|
||||||
data1 = resultSet.getString("data1") # the phone number or email
|
data1 = resultSet.getString("data1") # the phone number or email
|
||||||
mimetype = resultSet.getString("mimetype") # either phone or email
|
mimetype = resultSet.getString("mimetype") # either phone or email
|
||||||
attributes = ArrayList()
|
if oldName and (name != oldName):
|
||||||
if name != oldName:
|
if phoneNumber or emailAddr:
|
||||||
artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT)
|
contactArtifact = contactDbHelper.addContact(oldName,
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, general.MODULE_NAME, name))
|
phoneNumber, # phoneNumber,
|
||||||
|
None, # homePhoneNumber,
|
||||||
|
None, # mobilePhoneNumber,
|
||||||
|
emailAddr) # emailAddr
|
||||||
|
|
||||||
|
oldName = name
|
||||||
|
phoneNumber = None
|
||||||
|
emailAddr = None
|
||||||
|
name = None
|
||||||
|
|
||||||
if mimetype == "vnd.android.cursor.item/phone_v2":
|
if mimetype == "vnd.android.cursor.item/phone_v2":
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, general.MODULE_NAME, data1))
|
phoneNumber = data1
|
||||||
acctType = Account.Type.PHONE
|
|
||||||
else:
|
else:
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL, general.MODULE_NAME, data1))
|
emailAddr = data1
|
||||||
acctType = Account.Type.EMAIL
|
|
||||||
|
if name:
|
||||||
|
oldName = name
|
||||||
|
|
||||||
artifact.addAttributes(attributes)
|
|
||||||
|
|
||||||
# Create an account instance
|
|
||||||
contactAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance (acctType, data1, general.MODULE_NAME, abstractFile);
|
|
||||||
|
|
||||||
# create relationship between accounts
|
|
||||||
Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().addRelationships(deviceAccountInstance, [contactAccountInstance], artifact,Relationship.Type.CONTACT, 0);
|
|
||||||
|
|
||||||
oldName = name
|
|
||||||
|
|
||||||
bbartifacts.append(artifact)
|
|
||||||
|
|
||||||
|
# create contact for last row
|
||||||
|
if oldName and (phoneNumber or emailAddr):
|
||||||
|
contactArtifact = contactDbHelper.addContact(oldName,
|
||||||
|
phoneNumber, # phoneNumber,
|
||||||
|
None, # homePhoneNumber,
|
||||||
|
None, # mobilePhoneNumber,
|
||||||
|
emailAddr) # emailAddr
|
||||||
|
|
||||||
except SQLException as ex:
|
except SQLException as ex:
|
||||||
# Unable to execute contacts SQL query against database.
|
self._logger.log(Level.WARNING, "Error processing query result for Android messages.", ex)
|
||||||
pass
|
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||||
except TskCoreException as ex:
|
except TskCoreException as ex:
|
||||||
self._logger.log(Level.SEVERE, "Error posting to blackboard", ex)
|
self._logger.log(Level.SEVERE, "Failed to add Android message artifacts.", ex)
|
||||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
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())
|
||||||
|
except NoCurrentCaseException as ex:
|
||||||
|
self._logger.log(Level.WARNING, "No case currently open.", ex)
|
||||||
|
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||||
finally:
|
finally:
|
||||||
if bbartifacts:
|
contactDb.close()
|
||||||
Case.getCurrentCase().getSleuthkitCase().getBlackboard().postArtifacts(bbartifacts, general.MODULE_NAME)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if resultSet is not None:
|
|
||||||
resultSet.close()
|
|
||||||
statement.close()
|
|
||||||
connection.close()
|
|
||||||
except Exception as ex:
|
|
||||||
# Error closing database.
|
|
||||||
pass
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Autopsy Forensic Browser
|
Autopsy Forensic Browser
|
||||||
|
|
||||||
Copyright 2016-2018 Basis Technology Corp.
|
Copyright 2016-2020 Basis Technology Corp.
|
||||||
Contact: carrier <at> sleuthkit <dot> org
|
Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -31,6 +31,7 @@ from java.util.logging import Level
|
|||||||
from java.util import ArrayList
|
from java.util import ArrayList
|
||||||
from org.apache.commons.codec.binary import Base64
|
from org.apache.commons.codec.binary import Base64
|
||||||
from org.sleuthkit.autopsy.casemodule import Case
|
from org.sleuthkit.autopsy.casemodule import Case
|
||||||
|
from org.sleuthkit.autopsy.casemodule import NoCurrentCaseException
|
||||||
from org.sleuthkit.autopsy.casemodule.services import FileManager
|
from org.sleuthkit.autopsy.casemodule.services import FileManager
|
||||||
from org.sleuthkit.autopsy.coreutils import Logger
|
from org.sleuthkit.autopsy.coreutils import Logger
|
||||||
from org.sleuthkit.autopsy.coreutils import MessageNotifyUtil
|
from org.sleuthkit.autopsy.coreutils import MessageNotifyUtil
|
||||||
@ -43,6 +44,11 @@ from org.sleuthkit.datamodel import BlackboardAttribute
|
|||||||
from org.sleuthkit.datamodel import Content
|
from org.sleuthkit.datamodel import Content
|
||||||
from org.sleuthkit.datamodel import TskCoreException
|
from org.sleuthkit.datamodel import TskCoreException
|
||||||
from org.sleuthkit.datamodel import Account
|
from org.sleuthkit.datamodel import Account
|
||||||
|
from org.sleuthkit.datamodel.Blackboard import BlackboardException
|
||||||
|
from org.sleuthkit.autopsy.coreutils import AppSQLiteDB
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
|
||||||
|
|
||||||
import traceback
|
import traceback
|
||||||
import general
|
import general
|
||||||
@ -54,16 +60,19 @@ class TangoMessageAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._logger = Logger.getLogger(self.__class__.__name__)
|
self._logger = Logger.getLogger(self.__class__.__name__)
|
||||||
|
self._PACKAGE_NAME = "com.sgiggle.production"
|
||||||
|
self._PARSER_NAME = "Tango Parser"
|
||||||
|
self._MESSAGE_TYPE = "Tango Message"
|
||||||
|
self._VERSION = "7" # DB_VERSION in 'profiles' table
|
||||||
|
|
||||||
|
|
||||||
def analyze(self, dataSource, fileManager, context):
|
def analyze(self, dataSource, fileManager, context):
|
||||||
try:
|
try:
|
||||||
|
|
||||||
absFiles = fileManager.findFiles(dataSource, "tc.db")
|
tangoDbFiles = AppSQLiteDB.findAppDatabases(dataSource, "tc.db", True, self._PACKAGE_NAME)
|
||||||
for abstractFile in absFiles:
|
for tangoDbFile in tangoDbFiles:
|
||||||
try:
|
try:
|
||||||
jFile = File(Case.getCurrentCase().getTempDirectory(), str(abstractFile.getId()) + abstractFile.getName())
|
self.__findTangoMessagesInDB(tangoDbFile, dataSource)
|
||||||
ContentUtils.writeToFile(abstractFile, jFile, context.dataSourceIngestIsCancelled)
|
|
||||||
self.__findTangoMessagesInDB(jFile.toString(), abstractFile, dataSource)
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self._logger.log(Level.SEVERE, "Error parsing Tango messages", ex)
|
self._logger.log(Level.SEVERE, "Error parsing Tango messages", ex)
|
||||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
self._logger.log(Level.SEVERE, traceback.format_exc())
|
||||||
@ -71,74 +80,61 @@ class TangoMessageAnalyzer(general.AndroidComponentAnalyzer):
|
|||||||
# Error finding Tango messages.
|
# Error finding Tango messages.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __findTangoMessagesInDB(self, databasePath, abstractFile, dataSource):
|
def __findTangoMessagesInDB(self, tangoDb, dataSource):
|
||||||
if not databasePath:
|
if not tangoDb:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Class.forName("org.sqlite.JDBC") # load JDBC driver
|
current_case = Case.getCurrentCaseThrows()
|
||||||
connection = DriverManager.getConnection("jdbc:sqlite:" + databasePath)
|
|
||||||
statement = connection.createStatement()
|
|
||||||
except (ClassNotFoundException) as ex:
|
|
||||||
self._logger.log(Level.SEVERE, "Error loading JDBC driver", ex)
|
|
||||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
|
||||||
return
|
|
||||||
except (SQLException) as ex:
|
|
||||||
# Error opening database.
|
|
||||||
return
|
|
||||||
|
|
||||||
# Create a 'Device' account using the data source device id
|
# Create a helper to parse the DB
|
||||||
datasourceObjId = dataSource.getDataSource().getId()
|
tangoDbHelper = CommunicationArtifactsHelper(current_case.getSleuthkitCase(),
|
||||||
ds = Case.getCurrentCase().getSleuthkitCase().getDataSource(datasourceObjId)
|
self._PARSER_NAME,
|
||||||
deviceID = ds.getDeviceId()
|
tangoDb.getDBFile(),
|
||||||
deviceAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.DEVICE, deviceID, general.MODULE_NAME, abstractFile)
|
Account.Type.TANGO )
|
||||||
|
|
||||||
resultSet = None
|
resultSet = tangoDb.runQuery(
|
||||||
try:
|
|
||||||
resultSet = statement.executeQuery(
|
|
||||||
"SELECT conv_id, create_time, direction, payload FROM messages ORDER BY create_time DESC;")
|
"SELECT conv_id, create_time, direction, payload FROM messages ORDER BY create_time DESC;")
|
||||||
|
|
||||||
while resultSet.next():
|
while resultSet.next():
|
||||||
|
fromId = None
|
||||||
|
toId = None
|
||||||
conv_id = resultSet.getString("conv_id") # seems to wrap around the message found in payload after decoding from base-64
|
conv_id = resultSet.getString("conv_id") # seems to wrap around the message found in payload after decoding from base-64
|
||||||
create_time = Long.valueOf(resultSet.getString("create_time")) / 1000
|
create_time = Long.valueOf(resultSet.getString("create_time")) / 1000
|
||||||
|
|
||||||
if resultSet.getString("direction") == "1": # 1 incoming, 2 outgoing
|
if resultSet.getString("direction") == "1": # 1 incoming, 2 outgoing
|
||||||
direction = "Incoming"
|
direction = CommunicationDirection.INCOMING
|
||||||
else:
|
else:
|
||||||
direction = "Outgoing"
|
direction = CommunicationDirection.OUTGOING
|
||||||
|
|
||||||
payload = resultSet.getString("payload")
|
payload = resultSet.getString("payload")
|
||||||
|
msgBody = TangoMessageAnalyzer.decodeMessage(conv_id, payload)
|
||||||
attributes = ArrayList()
|
|
||||||
artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE) #create a call log and then add attributes from result set.
|
messageArtifact = tangoDbHelper.addMessage(
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, general.MODULE_NAME, create_time))
|
self._MESSAGE_TYPE,
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, general.MODULE_NAME, direction))
|
direction,
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT, general.MODULE_NAME, TangoMessageAnalyzer.decodeMessage(conv_id, payload)))
|
fromId,
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, general.MODULE_NAME, "Tango Message"))
|
toId,
|
||||||
|
create_time,
|
||||||
artifact.addAttributes(attributes)
|
MessageReadStatus.UNKNOWN,
|
||||||
try:
|
"", # subject
|
||||||
# index the artifact for keyword search
|
msgBody,
|
||||||
blackboard = Case.getCurrentCase().getSleuthkitCase().getBlackboard()
|
"")
|
||||||
blackboard.postArtifact(artifact, general.MODULE_NAME)
|
|
||||||
except Blackboard.BlackboardException as ex:
|
|
||||||
self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex)
|
|
||||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
|
||||||
MessageNotifyUtil.Notify.error("Failed to index Tango message artifact for keyword search.", artifact.getDisplayName())
|
|
||||||
|
|
||||||
except SQLException as ex:
|
except SQLException as ex:
|
||||||
# Unable to execute Tango messages SQL query against database.
|
self._logger.log(Level.WARNING, "Error processing query result for Tango messages", ex)
|
||||||
pass
|
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||||
except Exception as ex:
|
except TskCoreException as ex:
|
||||||
self._logger.log(Level.SEVERE, "Error parsing Tango messages to the blackboard", ex)
|
self._logger.log(Level.SEVERE, "Failed to add Tango message artifacts.", ex)
|
||||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
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())
|
||||||
|
except NoCurrentCaseException as ex:
|
||||||
|
self._logger.log(Level.WARNING, "No case currently open.", ex)
|
||||||
|
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||||
finally:
|
finally:
|
||||||
try:
|
tangoDb.close()
|
||||||
if resultSet is not None:
|
|
||||||
resultSet.close()
|
|
||||||
statement.close()
|
|
||||||
connection.close()
|
|
||||||
except Exception as ex:
|
|
||||||
# Error closing database.
|
|
||||||
pass
|
|
||||||
|
|
||||||
# take the message string which is wrapped by a certain string, and return the text enclosed.
|
# take the message string which is wrapped by a certain string, and return the text enclosed.
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Autopsy Forensic Browser
|
Autopsy Forensic Browser
|
||||||
|
|
||||||
Copyright 2016-2018 Basis Technology Corp.
|
Copyright 2016-2020 Basis Technology Corp.
|
||||||
Contact: carrier <at> sleuthkit <dot> org
|
Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -31,10 +31,12 @@ from java.util.logging import Level
|
|||||||
from java.util import ArrayList
|
from java.util import ArrayList
|
||||||
from java.util import UUID
|
from java.util import UUID
|
||||||
from org.sleuthkit.autopsy.casemodule import Case
|
from org.sleuthkit.autopsy.casemodule import Case
|
||||||
|
from org.sleuthkit.autopsy.casemodule import NoCurrentCaseException
|
||||||
from org.sleuthkit.autopsy.casemodule.services import FileManager
|
from org.sleuthkit.autopsy.casemodule.services import FileManager
|
||||||
from org.sleuthkit.autopsy.coreutils import Logger
|
from org.sleuthkit.autopsy.coreutils import Logger
|
||||||
from org.sleuthkit.autopsy.coreutils import MessageNotifyUtil
|
from org.sleuthkit.autopsy.coreutils import MessageNotifyUtil
|
||||||
from org.sleuthkit.autopsy.datamodel import ContentUtils
|
from org.sleuthkit.autopsy.datamodel import ContentUtils
|
||||||
|
from org.sleuthkit.autopsy.coreutils import AppSQLiteDB
|
||||||
from org.sleuthkit.autopsy.ingest import IngestJobContext
|
from org.sleuthkit.autopsy.ingest import IngestJobContext
|
||||||
from org.sleuthkit.autopsy.ingest import IngestServices
|
from org.sleuthkit.autopsy.ingest import IngestServices
|
||||||
from org.sleuthkit.autopsy.ingest import ModuleDataEvent
|
from org.sleuthkit.autopsy.ingest import ModuleDataEvent
|
||||||
@ -44,114 +46,102 @@ from org.sleuthkit.datamodel import BlackboardArtifact
|
|||||||
from org.sleuthkit.datamodel import BlackboardAttribute
|
from org.sleuthkit.datamodel import BlackboardAttribute
|
||||||
from org.sleuthkit.datamodel import Content
|
from org.sleuthkit.datamodel import Content
|
||||||
from org.sleuthkit.datamodel import TskCoreException
|
from org.sleuthkit.datamodel import TskCoreException
|
||||||
|
from org.sleuthkit.datamodel.Blackboard import BlackboardException
|
||||||
from org.sleuthkit.datamodel import Account
|
from org.sleuthkit.datamodel import Account
|
||||||
from org.sleuthkit.datamodel import Relationship
|
from org.sleuthkit.datamodel.blackboardutils.attributes import MessageAttachments
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments import FileAttachment
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments import URLAttachment
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus
|
||||||
|
from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection
|
||||||
|
|
||||||
import traceback
|
import traceback
|
||||||
import general
|
import general
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
Finds database with SMS/MMS messages and adds them to blackboard.
|
|
||||||
"""
|
|
||||||
class TextMessageAnalyzer(general.AndroidComponentAnalyzer):
|
class TextMessageAnalyzer(general.AndroidComponentAnalyzer):
|
||||||
|
"""
|
||||||
|
Finds and parsers Android SMS/MMS database, and populates the blackboard with messages.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._logger = Logger.getLogger(self.__class__.__name__)
|
self._logger = Logger.getLogger(self.__class__.__name__)
|
||||||
|
self._PACKAGE_NAME = "com.android.providers.telephony"
|
||||||
|
self._PARSER_NAME = "Android Message Parser"
|
||||||
|
self._MESSAGE_TYPE = "Android Message"
|
||||||
|
|
||||||
|
|
||||||
def analyze(self, dataSource, fileManager, context):
|
def analyze(self, dataSource, fileManager, context):
|
||||||
try:
|
selfAccountId = None
|
||||||
absFiles = fileManager.findFiles(dataSource, "mmssms.db")
|
messageDbs = AppSQLiteDB.findAppDatabases(dataSource, "mmssms.db", True, self._PACKAGE_NAME)
|
||||||
for abstractFile in absFiles:
|
for messageDb in messageDbs:
|
||||||
try:
|
|
||||||
jFile = File(Case.getCurrentCase().getTempDirectory(), str(abstractFile.getId()) + abstractFile.getName())
|
|
||||||
ContentUtils.writeToFile(abstractFile, jFile, context.dataSourceIngestIsCancelled)
|
|
||||||
self.__findTextsInDB(jFile.toString(), abstractFile, dataSource)
|
|
||||||
except Exception as ex:
|
|
||||||
self._logger.log(Level.SEVERE, "Error parsing text messages", ex)
|
|
||||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
|
||||||
except TskCoreException as ex:
|
|
||||||
# Error finding text messages.
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __findTextsInDB(self, databasePath, abstractFile, dataSource):
|
|
||||||
if not databasePath:
|
|
||||||
return
|
|
||||||
|
|
||||||
bbartifacts = list()
|
|
||||||
try:
|
|
||||||
Class.forName("org.sqlite.JDBC") # load JDBC driver
|
|
||||||
connection = DriverManager.getConnection("jdbc:sqlite:" + databasePath)
|
|
||||||
statement = connection.createStatement()
|
|
||||||
except (ClassNotFoundException) as ex:
|
|
||||||
self._logger.log(Level.SEVERE, "Error loading JDBC driver", ex)
|
|
||||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
|
||||||
return
|
|
||||||
except (SQLException) as ex:
|
|
||||||
# Error opening database.
|
|
||||||
return
|
|
||||||
|
|
||||||
# Create a 'Device' account using the data source device id
|
|
||||||
datasourceObjId = dataSource.getDataSource().getId()
|
|
||||||
ds = Case.getCurrentCase().getSleuthkitCase().getDataSource(datasourceObjId)
|
|
||||||
deviceID = ds.getDeviceId()
|
|
||||||
deviceAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.DEVICE, deviceID, general.MODULE_NAME, abstractFile)
|
|
||||||
uuid = UUID.randomUUID().toString()
|
|
||||||
|
|
||||||
resultSet = None
|
|
||||||
try:
|
|
||||||
resultSet = statement.executeQuery(
|
|
||||||
"SELECT address, date, read, type, subject, body, thread_id FROM sms;")
|
|
||||||
while resultSet.next():
|
|
||||||
address = resultSet.getString("address") # may be phone number, or other addresses
|
|
||||||
date = Long.valueOf(resultSet.getString("date")) / 1000
|
|
||||||
read = resultSet.getInt("read") # may be unread = 0, read = 1
|
|
||||||
subject = resultSet.getString("subject") # message subject
|
|
||||||
body = resultSet.getString("body") # message body
|
|
||||||
thread_id = "{0}-{1}".format(uuid, resultSet.getInt("thread_id"))
|
|
||||||
attributes = ArrayList()
|
|
||||||
artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE); #create Message artifact and then add attributes from result set.
|
|
||||||
if resultSet.getString("type") == "1":
|
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, general.MODULE_NAME, "Incoming"))
|
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, general.MODULE_NAME, address))
|
|
||||||
else:
|
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, general.MODULE_NAME, "Outgoing"))
|
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, general.MODULE_NAME, address))
|
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, general.MODULE_NAME, date))
|
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_READ_STATUS, general.MODULE_NAME, Integer(read)))
|
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT, general.MODULE_NAME, subject))
|
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT, general.MODULE_NAME, body))
|
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, general.MODULE_NAME, "SMS Message"))
|
|
||||||
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_THREAD_ID, general.MODULE_NAME, thread_id))
|
|
||||||
|
|
||||||
artifact.addAttributes(attributes)
|
|
||||||
|
|
||||||
if address is not None:
|
|
||||||
# Create an account
|
|
||||||
msgAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.PHONE, address, general.MODULE_NAME, abstractFile);
|
|
||||||
|
|
||||||
# create relationship between accounts
|
|
||||||
Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().addRelationships(deviceAccountInstance, [msgAccountInstance], artifact,Relationship.Type.MESSAGE, date);
|
|
||||||
|
|
||||||
bbartifacts.append(artifact)
|
|
||||||
|
|
||||||
except SQLException as ex:
|
|
||||||
# Unable to execute text messages SQL query against database.
|
|
||||||
pass
|
|
||||||
except Exception as ex:
|
|
||||||
self._logger.log(Level.SEVERE, "Error parsing text messages to blackboard", ex)
|
|
||||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
|
||||||
finally:
|
|
||||||
|
|
||||||
if bbartifacts:
|
|
||||||
Case.getCurrentCase().getSleuthkitCase().getBlackboard().postArtifacts(bbartifacts, general.MODULE_NAME)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
current_case = Case.getCurrentCaseThrows()
|
||||||
|
if selfAccountId is not None:
|
||||||
|
messageDbHelper = CommunicationArtifactsHelper(current_case.getSleuthkitCase(),
|
||||||
|
self._PARSER_NAME,
|
||||||
|
messageDb.getDBFile(),
|
||||||
|
Account.Type.PHONE, Account.Type.IMO, selfAccountId )
|
||||||
|
else:
|
||||||
|
messageDbHelper = CommunicationArtifactsHelper(current_case.getSleuthkitCase(),
|
||||||
|
self._PARSER_NAME,
|
||||||
|
messageDb.getDBFile(),
|
||||||
|
Account.Type.PHONE )
|
||||||
|
|
||||||
if resultSet is not None:
|
uuid = UUID.randomUUID().toString()
|
||||||
resultSet.close()
|
messagesResultSet = messageDb.runQuery("SELECT address, date, read, type, subject, body, thread_id FROM sms;")
|
||||||
statement.close()
|
if messagesResultSet is not None:
|
||||||
connection.close()
|
while messagesResultSet.next():
|
||||||
except Exception as ex:
|
direction = ""
|
||||||
# Error closing database.
|
address = None
|
||||||
pass
|
fromId = None
|
||||||
|
toId = None
|
||||||
|
|
||||||
|
address = messagesResultSet.getString("address") # may be phone number, or other addresses
|
||||||
|
timeStamp = Long.valueOf(messagesResultSet.getString("date")) / 1000
|
||||||
|
read = messagesResultSet.getInt("read") # may be unread = 0, read = 1
|
||||||
|
subject = messagesResultSet.getString("subject") # message subject
|
||||||
|
msgBody = messagesResultSet.getString("body") # message body
|
||||||
|
thread_id = "{0}-{1}".format(uuid, messagesResultSet.getInt("thread_id"))
|
||||||
|
if messagesResultSet.getString("type") == "1":
|
||||||
|
direction = CommunicationDirection.INCOMING
|
||||||
|
fromId = address
|
||||||
|
else:
|
||||||
|
direction = CommunicationDirection.OUTGOING
|
||||||
|
toId = address
|
||||||
|
|
||||||
|
message_read = messagesResultSet.getInt("read") # may be unread = 0, read = 1
|
||||||
|
if (message_read == 1):
|
||||||
|
msgReadStatus = MessageReadStatus.READ
|
||||||
|
elif (message_read == 0):
|
||||||
|
msgReadStatus = MessageReadStatus.UNREAD
|
||||||
|
else:
|
||||||
|
msgReadStatus = MessageReadStatus.UNKNOWN
|
||||||
|
|
||||||
|
## add a message
|
||||||
|
if address is not None:
|
||||||
|
messageArtifact = messageDbHelper.addMessage(
|
||||||
|
self._MESSAGE_TYPE,
|
||||||
|
direction,
|
||||||
|
fromId,
|
||||||
|
toId,
|
||||||
|
timeStamp,
|
||||||
|
msgReadStatus,
|
||||||
|
subject, # subject
|
||||||
|
msgBody,
|
||||||
|
thread_id)
|
||||||
|
|
||||||
|
|
||||||
|
except SQLException as ex:
|
||||||
|
self._logger.log(Level.WARNING, "Error processing query result for Android messages.", ex)
|
||||||
|
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||||
|
except TskCoreException as ex:
|
||||||
|
self._logger.log(Level.SEVERE, "Failed to add Android 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())
|
||||||
|
except NoCurrentCaseException as ex:
|
||||||
|
self._logger.log(Level.WARNING, "No case currently open.", ex)
|
||||||
|
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||||
|
finally:
|
||||||
|
messageDb.close()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user