mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 07:56:16 +00:00
Merged release-4.13.0 and resolved conflicts
This commit is contained in:
commit
ddb9364d5d
@ -64,6 +64,8 @@ final class AddLogicalImageTask implements Runnable {
|
||||
private final static String MODULE_NAME = "Logical Imager"; //NON-NLS
|
||||
private final static String ROOT_STR = "root"; // NON-NLS
|
||||
private final static String VHD_EXTENSION = ".vhd"; // NON-NLS
|
||||
private final static int REPORT_PROGRESS_INTERVAL = 100;
|
||||
private final static int POST_ARTIFACT_INTERVAL = 1000;
|
||||
private final String deviceId;
|
||||
private final String timeZone;
|
||||
private final File src;
|
||||
@ -145,7 +147,7 @@ final class AddLogicalImageTask implements Runnable {
|
||||
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Add the SearchResults.txt and users.txt to the case report
|
||||
String resultsFilename;
|
||||
if (Paths.get(dest.toString(), SEARCH_RESULTS_TXT).toFile().exists()) {
|
||||
@ -200,7 +202,7 @@ final class AddLogicalImageTask implements Runnable {
|
||||
|
||||
List<Content> newDataSources = new ArrayList<>();
|
||||
Map<String, List<Long>> interestingFileMap = new HashMap<>();
|
||||
|
||||
|
||||
if (imagePaths.isEmpty()) {
|
||||
createVHD = false;
|
||||
// No VHD in src directory, try ingest the root directory as local files
|
||||
@ -241,7 +243,7 @@ final class AddLogicalImageTask implements Runnable {
|
||||
if (addMultipleImagesTask.getResult() == DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to add VHD datasource"); // NON-NLS
|
||||
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, addMultipleImagesTask.getErrorMessages(), emptyDataSources);
|
||||
return;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
interestingFileMap = getInterestingFileMapForVHD(Paths.get(dest.toString(), resultsFilename));
|
||||
@ -250,7 +252,7 @@ final class AddLogicalImageTask implements Runnable {
|
||||
LOGGER.log(Level.SEVERE, "Failed to add interesting files", ex); // NON-NLS
|
||||
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.NONCRITICAL_ERRORS, errorList, emptyDataSources);
|
||||
}
|
||||
|
||||
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
String msg = Bundle.AddLogicalImageTask_noCurrentCase();
|
||||
errorList.add(msg);
|
||||
@ -261,7 +263,7 @@ final class AddLogicalImageTask implements Runnable {
|
||||
|
||||
if (cancelled) {
|
||||
if (!createVHD) {
|
||||
// TODO: When 5453 is fixed, we should be able to delete it when adding VHD.
|
||||
// TODO: When 5453 is fixed, we should be able to delete it when adding VHD.
|
||||
deleteDestinationDirectory();
|
||||
}
|
||||
errorList.add(Bundle.AddLogicalImageTask_addImageCancelled());
|
||||
@ -345,7 +347,7 @@ final class AddLogicalImageTask implements Runnable {
|
||||
private void addInterestingFiles(Map<String, List<Long>> interestingFileMap) throws IOException, TskCoreException {
|
||||
int lineNumber = 0;
|
||||
List<BlackboardArtifact> artifacts = new ArrayList<>();
|
||||
|
||||
|
||||
Iterator<Map.Entry<String, List<Long>>> iterator = interestingFileMap.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
|
||||
@ -365,21 +367,23 @@ final class AddLogicalImageTask implements Runnable {
|
||||
|
||||
List<Long> fileIds = entry.getValue();
|
||||
for (Long fileId: fileIds) {
|
||||
if (lineNumber % 100 == 0) {
|
||||
if (cancelled) {
|
||||
postArtifacts(artifacts);
|
||||
return;
|
||||
}
|
||||
if (lineNumber % REPORT_PROGRESS_INTERVAL == 0) {
|
||||
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingInterestingFile(lineNumber, totalFiles));
|
||||
}
|
||||
if (lineNumber % POST_ARTIFACT_INTERVAL == 0) {
|
||||
postArtifacts(artifacts);
|
||||
artifacts.clear();
|
||||
}
|
||||
addInterestingFileToArtifacts(fileId, ruleSetName, ruleName, artifacts);
|
||||
lineNumber++;
|
||||
}
|
||||
iterator.remove();
|
||||
}
|
||||
|
||||
try {
|
||||
// index the artifact for keyword search
|
||||
blackboard.postArtifacts(artifacts, MODULE_NAME);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Unable to post artifacts to blackboard", ex); //NON-NLS
|
||||
}
|
||||
postArtifacts(artifacts);
|
||||
}
|
||||
|
||||
private void addInterestingFileToArtifacts(long fileId, String ruleSetName, String ruleName, List<BlackboardArtifact> artifacts) throws TskCoreException {
|
||||
@ -400,7 +404,7 @@ final class AddLogicalImageTask implements Runnable {
|
||||
Map<Long, List<String>> objIdToimagePathsMap = currentCase.getSleuthkitCase().getImagePaths();
|
||||
imagePathToObjIdMap = imagePathsToDataSourceObjId(objIdToimagePathsMap);
|
||||
Map<String, List<Long>> interestingFileMap = new HashMap<>();
|
||||
|
||||
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(
|
||||
new FileInputStream(resultsPath.toFile()), "UTF8"))) { // NON-NLS
|
||||
String line;
|
||||
@ -426,7 +430,7 @@ final class AddLogicalImageTask implements Runnable {
|
||||
String filename = fields[7];
|
||||
String parentPath = fields[8];
|
||||
|
||||
if (lineNumber % 100 == 0) {
|
||||
if (lineNumber % REPORT_PROGRESS_INTERVAL == 0) {
|
||||
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_searchingInterestingFile(lineNumber, totalFiles));
|
||||
}
|
||||
|
||||
@ -443,11 +447,20 @@ final class AddLogicalImageTask implements Runnable {
|
||||
interestingFileMap.put(key, fileIds);
|
||||
}
|
||||
lineNumber++;
|
||||
} // end reading file }
|
||||
} // end reading file
|
||||
}
|
||||
return interestingFileMap;
|
||||
}
|
||||
|
||||
private void postArtifacts(List<BlackboardArtifact> artifacts) {
|
||||
try {
|
||||
// index the artifact for keyword search
|
||||
blackboard.postArtifacts(artifacts, MODULE_NAME);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Unable to post artifacts to blackboard", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"# {0} - file number", "# {1} - total files", "AddLogicalImageTask.addingExtractedFile=Adding extracted files ({0}/{1})"
|
||||
})
|
||||
@ -455,7 +468,7 @@ final class AddLogicalImageTask implements Runnable {
|
||||
SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase();
|
||||
SleuthkitCase.CaseDbTransaction trans = null;
|
||||
Map<String, List<Long>> interestingFileMap = new HashMap<>();
|
||||
|
||||
|
||||
try {
|
||||
trans = skCase.beginTransaction();
|
||||
LocalFilesDataSource localFilesDataSource = skCase.addLocalFilesDataSource(deviceId, this.src.getName(), timeZone, trans);
|
||||
@ -492,7 +505,7 @@ final class AddLogicalImageTask implements Runnable {
|
||||
String ctime = fields[13];
|
||||
parentPath = ROOT_STR + "/" + vhdFilename + "/" + parentPath;
|
||||
|
||||
if (lineNumber % 100 == 0) {
|
||||
if (lineNumber % REPORT_PROGRESS_INTERVAL == 0) {
|
||||
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingExtractedFile(lineNumber, totalFiles));
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,7 @@ from java.util.logging import Level
|
||||
from java.util import ArrayList
|
||||
from org.apache.commons.codec.binary import Base64
|
||||
from org.sleuthkit.autopsy.casemodule import Case
|
||||
from org.sleuthkit.autopsy.casemodule import NoCurrentCaseException
|
||||
from org.sleuthkit.autopsy.coreutils import Logger
|
||||
from org.sleuthkit.autopsy.coreutils import MessageNotifyUtil
|
||||
from org.sleuthkit.autopsy.coreutils import AppSQLiteDB
|
||||
@ -56,10 +57,14 @@ and adds artifacts to the case.
|
||||
class IMOAnalyzer(general.AndroidComponentAnalyzer):
|
||||
def __init__(self):
|
||||
self._logger = Logger.getLogger(self.__class__.__name__)
|
||||
self._PACKAGE_NAME = "com.imo.android.imous"
|
||||
self._PARSER_NAME = "IMO Parser"
|
||||
self._MESSAGE_TYPE = "IMO Message"
|
||||
self._VERSION = "9.8.0"
|
||||
|
||||
def analyze(self, dataSource, fileManager, context):
|
||||
selfAccountAddress = None
|
||||
accountDbs = AppSQLiteDB.findAppDatabases(dataSource, "accountdb.db", True, "com.imo.android.imous")
|
||||
accountDbs = AppSQLiteDB.findAppDatabases(dataSource, "accountdb.db", True, self._PACKAGE_NAME)
|
||||
for accountDb in accountDbs:
|
||||
try:
|
||||
accountResultSet = accountDb.runQuery("SELECT uid, name FROM account")
|
||||
@ -71,16 +76,26 @@ class IMOAnalyzer(general.AndroidComponentAnalyzer):
|
||||
selfAccountAddress = Account.Address(accountResultSet.getString("uid"), accountResultSet.getString("name"))
|
||||
|
||||
except SQLException as ex:
|
||||
self._logger.log(Level.SEVERE, "Error processing query result for account", ex)
|
||||
self._logger.log(Level.WARNING, "Error processing query result for account", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
finally:
|
||||
accountDb.close()
|
||||
|
||||
friendsDbs = AppSQLiteDB.findAppDatabases(dataSource, "imofriends.db", True, "com.imo.android.imous")
|
||||
friendsDbs = AppSQLiteDB.findAppDatabases(dataSource, "imofriends.db", True, self._PACKAGE_NAME)
|
||||
for friendsDb in friendsDbs:
|
||||
try:
|
||||
friendsDBHelper = CommunicationArtifactsHelper(Case.getCurrentCase().getSleuthkitCase(),
|
||||
"IMO Parser", friendsDb.getDBFile(),
|
||||
current_case = Case.getCurrentCaseThrows()
|
||||
if selfAccountAddress is not None:
|
||||
friendsDBHelper = CommunicationArtifactsHelper(current_case.getSleuthkitCase(),
|
||||
self._PARSER_NAME,
|
||||
friendsDb.getDBFile(),
|
||||
Account.Type.IMO, Account.Type.IMO, selfAccountAddress )
|
||||
else:
|
||||
friendsDBHelper = CommunicationArtifactsHelper(current_case.getSleuthkitCase(),
|
||||
self._PARSER_NAME,
|
||||
friendsDb.getDBFile(),
|
||||
Account.Type.IMO
|
||||
)
|
||||
contactsResultSet = friendsDb.runQuery("SELECT buid, name FROM friends")
|
||||
if contactsResultSet is not None:
|
||||
while contactsResultSet.next():
|
||||
@ -121,7 +136,7 @@ class IMOAnalyzer(general.AndroidComponentAnalyzer):
|
||||
|
||||
|
||||
messageArtifact = friendsDBHelper.addMessage(
|
||||
"IMO Message",
|
||||
self._MESSAGE_TYPE,
|
||||
direction,
|
||||
fromAddress,
|
||||
toAddress,
|
||||
@ -137,8 +152,16 @@ class IMOAnalyzer(general.AndroidComponentAnalyzer):
|
||||
|
||||
except SQLException as ex:
|
||||
self._logger.log(Level.WARNING, "Error processing query result for IMO friends", ex)
|
||||
except (TskCoreException, BlackboardException) as ex:
|
||||
self._logger.log(Level.WARNING, "Failed to message artifacts.", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
except TskCoreException as ex:
|
||||
self._logger.log(Level.SEVERE, "Failed to add IMO 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:
|
||||
friendsDb.close()
|
||||
|
||||
|
@ -48,6 +48,7 @@ import textmessage
|
||||
import wwfmessage
|
||||
import imo
|
||||
import line
|
||||
import textnow
|
||||
|
||||
class AndroidModuleFactory(IngestModuleFactoryAdapter):
|
||||
|
||||
@ -92,7 +93,7 @@ class AndroidIngestModule(DataSourceIngestModule):
|
||||
analyzers = [contact.ContactAnalyzer(), calllog.CallLogAnalyzer(), textmessage.TextMessageAnalyzer(),
|
||||
tangomessage.TangoMessageAnalyzer(), wwfmessage.WWFMessageAnalyzer(),
|
||||
googlemaplocation.GoogleMapLocationAnalyzer(), browserlocation.BrowserLocationAnalyzer(),
|
||||
cachelocation.CacheLocationAnalyzer(), imo.IMOAnalyzer(), line.LineAnalyzer()]
|
||||
cachelocation.CacheLocationAnalyzer(), imo.IMOAnalyzer(), line.LineAnalyzer(), textnow.TextNowAnalyzer()]
|
||||
self.log(Level.INFO, "running " + str(len(analyzers)) + " analyzers")
|
||||
progressBar.switchToDeterminate(len(analyzers))
|
||||
|
||||
|
392
InternalPythonModules/android/textnow.py
Normal file
392
InternalPythonModules/android/textnow.py
Normal file
@ -0,0 +1,392 @@
|
||||
"""
|
||||
Autopsy Forensic Browser
|
||||
|
||||
Copyright 2019 Basis Technology Corp.
|
||||
Contact: carrier <at> sleuthkit <dot> org
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
from java.io import File
|
||||
from java.lang import Class
|
||||
from java.lang import ClassNotFoundException
|
||||
from java.lang import Long
|
||||
from java.lang import String
|
||||
from java.sql import ResultSet
|
||||
from java.sql import SQLException
|
||||
from java.sql import Statement
|
||||
from java.util.logging import Level
|
||||
from java.util import ArrayList
|
||||
from org.apache.commons.codec.binary import Base64
|
||||
from org.sleuthkit.autopsy.casemodule import Case
|
||||
from org.sleuthkit.autopsy.coreutils import Logger
|
||||
from org.sleuthkit.autopsy.coreutils import MessageNotifyUtil
|
||||
from org.sleuthkit.autopsy.coreutils import AppSQLiteDB
|
||||
|
||||
from org.sleuthkit.autopsy.datamodel import ContentUtils
|
||||
from org.sleuthkit.autopsy.ingest import IngestJobContext
|
||||
from org.sleuthkit.datamodel import AbstractFile
|
||||
from org.sleuthkit.datamodel import BlackboardArtifact
|
||||
from org.sleuthkit.datamodel import BlackboardAttribute
|
||||
from org.sleuthkit.datamodel import Content
|
||||
from org.sleuthkit.datamodel import TskCoreException
|
||||
from org.sleuthkit.datamodel.Blackboard import BlackboardException
|
||||
from org.sleuthkit.autopsy.casemodule import NoCurrentCaseException
|
||||
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 TskMessagesParser import TskMessagesParser
|
||||
from TskContactsParser import TskContactsParser
|
||||
from TskCallLogsParser import TskCallLogsParser
|
||||
|
||||
import traceback
|
||||
import general
|
||||
|
||||
class TextNowAnalyzer(general.AndroidComponentAnalyzer):
|
||||
"""
|
||||
Parses the TextNow App databases for TSK contacts, message
|
||||
and calllog artifacts.
|
||||
|
||||
The TextNow database in v6.41.0.2 is structured as follows:
|
||||
- A messages table, which stores messages from/to a number
|
||||
- A contacts table, which stores phone numbers
|
||||
- A groups table, which stores each group the device owner is a part of
|
||||
- A group_members table, which stores who is in each group
|
||||
|
||||
The messages table contains both call logs and messages, with a type
|
||||
column differentiating the two.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._logger = Logger.getLogger(self.__class__.__name__)
|
||||
self._TEXTNOW_PACKAGE_NAME = "com.enflick.android.TextNow"
|
||||
self._PARSER_NAME = "TextNow Parser"
|
||||
self._VERSION = "6.41.0.2"
|
||||
|
||||
def analyze(self, dataSource, fileManager, context):
|
||||
"""
|
||||
Extract, Transform and Load all messages, contacts and
|
||||
calllogs from the TextNow databases.
|
||||
"""
|
||||
|
||||
textnow_dbs = AppSQLiteDB.findAppDatabases(dataSource,
|
||||
"textnow_data.db", True, self._TEXTNOW_PACKAGE_NAME)
|
||||
|
||||
try:
|
||||
for textnow_db in textnow_dbs:
|
||||
current_case = Case.getCurrentCaseThrows()
|
||||
helper = CommunicationArtifactsHelper(
|
||||
current_case.getSleuthkitCase(), self._PARSER_NAME,
|
||||
textnow_db.getDBFile(), Account.Type.TEXTNOW
|
||||
)
|
||||
self.parse_contacts(textnow_db, helper)
|
||||
self.parse_calllogs(textnow_db, helper)
|
||||
self.parse_messages(textnow_db, helper)
|
||||
except NoCurrentCaseException as ex:
|
||||
self._logger.log(Level.WARNING, "No case currently open.", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
|
||||
for textnow_db in textnow_dbs:
|
||||
textnow_db.close()
|
||||
|
||||
def parse_contacts(self, textnow_db, helper):
|
||||
#Query for contacts and iterate row by row adding
|
||||
#each contact artifact
|
||||
try:
|
||||
contacts_parser = TextNowContactsParser(textnow_db)
|
||||
while contacts_parser.next():
|
||||
helper.addContact(
|
||||
contacts_parser.get_account_name(),
|
||||
contacts_parser.get_contact_name(),
|
||||
contacts_parser.get_phone(),
|
||||
contacts_parser.get_home_phone(),
|
||||
contacts_parser.get_mobile_phone(),
|
||||
contacts_parser.get_email()
|
||||
)
|
||||
contacts_parser.close()
|
||||
except SQLException as ex:
|
||||
#Error parsing TextNow db
|
||||
self._logger.log(Level.WARNING, "Error parsing TextNow databases for contacts", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
except TskCoreException as ex:
|
||||
#Error adding artifacts to the case database.. case database is not complete.
|
||||
self._logger.log(Level.SEVERE,
|
||||
"Error adding TextNow contacts artifacts to the case database", ex)
|
||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
||||
except BlackboardException as ex:
|
||||
#Error posting notification to blackboard...
|
||||
self._logger.log(Level.WARNING,
|
||||
"Error posting TextNow contacts artifact to the blackboard", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
|
||||
def parse_calllogs(self, textnow_db, helper):
|
||||
#Query for call logs and iterate row by row adding
|
||||
#each call log artifact
|
||||
try:
|
||||
calllog_parser = TextNowCallLogsParser(textnow_db)
|
||||
while calllog_parser.next():
|
||||
helper.addCalllog(
|
||||
calllog_parser.get_call_direction(),
|
||||
calllog_parser.get_phone_number_from(),
|
||||
calllog_parser.get_phone_number_to(),
|
||||
calllog_parser.get_call_start_date_time(),
|
||||
calllog_parser.get_call_end_date_time(),
|
||||
calllog_parser.get_call_type()
|
||||
)
|
||||
calllog_parser.close()
|
||||
except SQLException as ex:
|
||||
self._logger.log(Level.WARNING, "Error parsing TextNow databases for calllogs", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
except TskCoreException as ex:
|
||||
#Error adding artifacts to the case database.. case database is not complete.
|
||||
self._logger.log(Level.SEVERE,
|
||||
"Error adding TextNow call log artifacts to the case database", ex)
|
||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
||||
except BlackboardException as ex:
|
||||
#Error posting notification to blackboard...
|
||||
self._logger.log(Level.WARNING,
|
||||
"Error posting TextNow call log artifact to the blackboard", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
|
||||
def parse_messages(self, textnow_db, helper):
|
||||
#Query for messages and iterate row by row adding
|
||||
#each message artifact
|
||||
try:
|
||||
messages_parser = TextNowMessagesParser(textnow_db)
|
||||
while messages_parser.next():
|
||||
helper.addMessage(
|
||||
messages_parser.get_message_type(),
|
||||
messages_parser.get_message_direction(),
|
||||
messages_parser.get_phone_number_from(),
|
||||
messages_parser.get_phone_number_to(),
|
||||
messages_parser.get_message_date_time(),
|
||||
messages_parser.get_message_read_status(),
|
||||
messages_parser.get_message_subject(),
|
||||
messages_parser.get_message_text(),
|
||||
messages_parser.get_thread_id()
|
||||
)
|
||||
messages_parser.close()
|
||||
except SQLException as ex:
|
||||
#Error parsing TextNow db
|
||||
self._logger.log(Level.WARNING, "Error parsing TextNow databases for messages.", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
except TskCoreException as ex:
|
||||
#Error adding artifacts to the case database.. case database is not complete.
|
||||
self._logger.log(Level.SEVERE,
|
||||
"Error adding TextNow messages artifacts to the case database", ex)
|
||||
self._logger.log(Level.SEVERE, traceback.format_exc())
|
||||
except BlackboardException as ex:
|
||||
#Error posting notification to blackboard...
|
||||
self._logger.log(Level.WARNING,
|
||||
"Error posting TextNow messages artifact to the blackboard", ex)
|
||||
self._logger.log(Level.WARNING, traceback.format_exc())
|
||||
|
||||
class TextNowCallLogsParser(TskCallLogsParser):
|
||||
"""
|
||||
Extracts TSK_CALLLOG information from the TextNow database.
|
||||
TSK_CALLLOG fields that are not in the TextNow database are given
|
||||
a default value inherited from the super class.
|
||||
"""
|
||||
|
||||
def __init__(self, calllog_db):
|
||||
"""
|
||||
message_type of 100 or 102 are for calls (audio, video)
|
||||
"""
|
||||
super(TextNowCallLogsParser, self).__init__(calllog_db.runQuery(
|
||||
"""
|
||||
SELECT contact_value AS num,
|
||||
message_direction AS direction,
|
||||
message_text AS duration,
|
||||
date AS datetime
|
||||
FROM messages AS M
|
||||
WHERE message_type IN ( 100, 102 )
|
||||
"""
|
||||
)
|
||||
)
|
||||
self._INCOMING_CALL_TYPE = 1
|
||||
self._OUTGOING_CALL_TYPE = 2
|
||||
|
||||
def get_phone_number_from(self):
|
||||
if self.get_call_direction() == self.OUTGOING_CALL:
|
||||
return super(TextNowCallLogsParser, self).get_phone_number_from()
|
||||
return Account.Address(self.result_set.getString("num"),
|
||||
self.result_set.getString("num"))
|
||||
|
||||
def get_phone_number_to(self):
|
||||
if self.get_call_direction() == self.INCOMING_CALL:
|
||||
return super(TextNowCallLogsParser, self).get_phone_number_to()
|
||||
return Account.Address(self.result_set.getString("num"),
|
||||
self.result_set.getString("num"))
|
||||
|
||||
def get_call_direction(self):
|
||||
if self.result_set.getInt("direction") == self._INCOMING_CALL_TYPE:
|
||||
return self.INCOMING_CALL
|
||||
return self.OUTGOING_CALL
|
||||
|
||||
def get_call_start_date_time(self):
|
||||
return self.result_set.getLong("datetime") / 1000
|
||||
|
||||
def get_call_end_date_time(self):
|
||||
start = self.get_call_start_date_time()
|
||||
duration = self.result_set.getString("duration")
|
||||
try:
|
||||
return start + long(duration)
|
||||
except ValueError as ve:
|
||||
return super(TextNowCallLogsParser, self).get_call_end_date_time()
|
||||
|
||||
class TextNowContactsParser(TskContactsParser):
|
||||
"""
|
||||
Extracts TSK_CONTACT information from the TextNow database.
|
||||
TSK_CONTACT fields that are not in the TextNow database are given
|
||||
a default value inherited from the super class.
|
||||
"""
|
||||
|
||||
def __init__(self, contact_db):
|
||||
super(TextNowContactsParser, self).__init__(contact_db.runQuery(
|
||||
"""
|
||||
SELECT C.contact_value AS number,
|
||||
CASE
|
||||
WHEN contact_name IS NULL THEN contact_value
|
||||
WHEN contact_name == "" THEN contact_value
|
||||
ELSE contact_name
|
||||
END name
|
||||
FROM contacts AS C
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
def get_account_name(self):
|
||||
return self.result_set.getString("number")
|
||||
|
||||
def get_contact_name(self):
|
||||
return self.result_set.getString("name")
|
||||
|
||||
def get_phone(self):
|
||||
return self.result_set.getString("number")
|
||||
|
||||
class TextNowMessagesParser(TskMessagesParser):
|
||||
"""
|
||||
Extract TSK_MESSAGE information from the TextNow database.
|
||||
TSK_CONTACT fields that are not in the TextNow database are given
|
||||
a default value inherited from the super class.
|
||||
"""
|
||||
|
||||
def __init__(self, message_db):
|
||||
"""
|
||||
The query below does the following:
|
||||
- The group_info inner query creates a comma seperated list of group recipients
|
||||
for each group. This result is then joined on the groups table to get the thread id.
|
||||
- The contacts table is unioned with this result so we have a complete map
|
||||
of "from" phone_numbers -> recipients (group or single). This is the
|
||||
'to_from_map' inner query.
|
||||
- Finally, the to_from_map results are joined with the messages table to get all
|
||||
of the communication details.
|
||||
"""
|
||||
super(TextNowMessagesParser, self).__init__(message_db.runQuery(
|
||||
"""
|
||||
|
||||
SELECT CASE
|
||||
WHEN message_direction == 2 THEN ""
|
||||
WHEN to_addresses IS NULL THEN M.contact_value
|
||||
ELSE contact_name
|
||||
end from_address,
|
||||
CASE
|
||||
WHEN message_direction == 1 THEN ""
|
||||
WHEN to_addresses IS NULL THEN M.contact_value
|
||||
ELSE to_addresses
|
||||
end to_address,
|
||||
message_direction,
|
||||
message_text,
|
||||
M.READ,
|
||||
M.date,
|
||||
M.attach,
|
||||
thread_id
|
||||
FROM (SELECT group_info.contact_value,
|
||||
group_info.to_addresses,
|
||||
G.contact_value AS thread_id
|
||||
FROM (SELECT GM.contact_value,
|
||||
Group_concat(GM.member_contact_value) AS to_addresses
|
||||
FROM group_members AS GM
|
||||
GROUP BY GM.contact_value) AS group_info
|
||||
JOIN groups AS G
|
||||
ON G.contact_value = group_info.contact_value
|
||||
UNION
|
||||
SELECT c.contact_value,
|
||||
NULL,
|
||||
"-1"
|
||||
FROM contacts AS c) AS to_from_map
|
||||
JOIN messages AS M
|
||||
ON M.contact_value = to_from_map.contact_value
|
||||
WHERE message_type NOT IN ( 102, 100 )
|
||||
"""
|
||||
)
|
||||
)
|
||||
self._TEXTNOW_MESSAGE_TYPE = "TextNow Message"
|
||||
self._INCOMING_MESSAGE_TYPE = 1
|
||||
self._OUTGOING_MESSAGE_TYPE = 2
|
||||
self._UNKNOWN_THREAD_ID = "-1"
|
||||
|
||||
def get_message_type(self):
|
||||
return self._TEXTNOW_MESSAGE_TYPE
|
||||
|
||||
def get_phone_number_from(self):
|
||||
if self.result_set.getString("from_address") == "":
|
||||
return super(TextNowMessagesParser, self).get_phone_number_from()
|
||||
return Account.Address(self.result_set.getString("from_address"),
|
||||
self.result_set.getString("from_address"))
|
||||
|
||||
def get_message_direction(self):
|
||||
direction = self.result_set.getInt("message_direction")
|
||||
if direction == self._INCOMING_MESSAGE_TYPE:
|
||||
return self.INCOMING
|
||||
return self.OUTGOING
|
||||
|
||||
def get_phone_number_to(self):
|
||||
if self.result_set.getString("to_address") == "":
|
||||
return super(TextNowMessagesParser, self).get_phone_number_to()
|
||||
recipients = self.result_set.getString("to_address").split(",")
|
||||
|
||||
recipient_accounts = []
|
||||
for recipient in recipients:
|
||||
recipient_accounts.append(Account.Address(recipient, recipient))
|
||||
|
||||
return recipient_accounts
|
||||
|
||||
def get_message_date_time(self):
|
||||
#convert ms to s
|
||||
return self.result_set.getLong("date") / 1000;
|
||||
|
||||
def get_message_read_status(self):
|
||||
read = self.result_set.getBoolean("read")
|
||||
if self.get_message_direction() == self.INCOMING:
|
||||
if read == True:
|
||||
return self.READ
|
||||
return self.UNREAD
|
||||
|
||||
#read status for outgoing messages cannot be determined, give default
|
||||
return super(TextNowMessagesParser, self).get_message_read_status()
|
||||
|
||||
def get_message_text(self):
|
||||
text = self.result_set.getString("message_text")
|
||||
attachment = self.result_set.getString("attach")
|
||||
if attachment != "":
|
||||
text = general.appendAttachmentList(text, [attachment])
|
||||
return text
|
||||
|
||||
def get_thread_id(self):
|
||||
thread_id = self.result_set.getString("thread_id")
|
||||
if thread_id == self._UNKNOWN_THREAD_ID:
|
||||
return super(TextNowMessagesParser, self).get_thread_id()
|
||||
return thread_id
|
Loading…
x
Reference in New Issue
Block a user