Merge pull request #5247 from sleuthkit/release-4.13.0

Merge release-4.13.0 branch into develop branch
This commit is contained in:
Richard Cordovano 2019-09-24 11:20:39 -04:00 committed by GitHub
commit c36620e876
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 2449 additions and 132 deletions

View File

@ -2,12 +2,11 @@ OpenIDE-Module-Name=CommandLineAutopsy
OptionsCategory_Keywords_Command_Line_Ingest_Settings=Command Line Ingest Settings
OptionsCategory_Keywords_General=Options
OptionsCategory_Name_Command_Line_Ingest=Command Line Ingest
CommandLineIngestSettingsPanel.bnEditIngestSettings.toolTipText=Ingest job settings for the command line processing mode context.
CommandLineIngestSettingsPanel.bnEditIngestSettings.text=Ingest Module Settings
CommandLinePanel.jLabel1.text=Ingest is running from command line
CommandLineStartupWindow.title.text=Running in Command Line Mode
CommandLineIngestSettingsPanel.bnEditIngestSettings.text=Configure Ingest
CommandLineIngestSettingsPanel.bnEditReportSettings.actionCommand=Report Module Settings
CommandLineIngestSettingsPanel.bnEditReportSettings.toolTipText=Report generation settings for the command line processing mode context.
CommandLineIngestSettingsPanel.bnEditReportSettings.text=Report Module Settings
CommandLineIngestSettingsPanel.jLabelReportConfig.text=Configure Report Generation for Command Line Ingest:
CommandLineIngestSettingsPanel.jLabelBaselineConfig.text=Configure Baseline Configuration for Command Line Ingest:
CommandLineIngestSettingsPanel.bnEditReportSettings.text=Configure Reporting
CommandLineIngestSettingsPanel.jLabelDescription.text=<html>You can create cases, add data sources, run ingest modules, and generate reports from the command line.<br>This options panel allows you to configure the settings to use when running ingest modules and generating reports.<br>See the user documentation for details.</html>
CommandLineIngestSettingsPanel.bnEditIngestSettings.toolTipText=Ingest job settings for the command line processing mode context.

View File

@ -2,12 +2,11 @@ OpenIDE-Module-Name=CommandLineAutopsy
OptionsCategory_Keywords_Command_Line_Ingest_Settings=Command Line Ingest Settings
OptionsCategory_Keywords_General=Options
OptionsCategory_Name_Command_Line_Ingest=Command Line Ingest
CommandLineIngestSettingsPanel.bnEditIngestSettings.toolTipText=Ingest job settings for the command line processing mode context.
CommandLineIngestSettingsPanel.bnEditIngestSettings.text=Ingest Module Settings
CommandLinePanel.jLabel1.text=Ingest is running from command line
CommandLineStartupWindow.title.text=Running in Command Line Mode
CommandLineIngestSettingsPanel.bnEditIngestSettings.text=Configure Ingest
CommandLineIngestSettingsPanel.bnEditReportSettings.actionCommand=Report Module Settings
CommandLineIngestSettingsPanel.bnEditReportSettings.toolTipText=Report generation settings for the command line processing mode context.
CommandLineIngestSettingsPanel.bnEditReportSettings.text=Report Module Settings
CommandLineIngestSettingsPanel.jLabelReportConfig.text=Configure Report Generation for Command Line Ingest:
CommandLineIngestSettingsPanel.jLabelBaselineConfig.text=Configure Baseline Configuration for Command Line Ingest:
CommandLineIngestSettingsPanel.bnEditReportSettings.text=Configure Reporting
CommandLineIngestSettingsPanel.jLabelDescription.text=<html>You can create cases, add data sources, run ingest modules, and generate reports from the command line.<br>This options panel allows you to configure the settings to use when running ingest modules and generating reports.<br>See the user documentation for details.</html>
CommandLineIngestSettingsPanel.bnEditIngestSettings.toolTipText=Ingest job settings for the command line processing mode context.

View File

@ -60,11 +60,10 @@
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="bnEditIngestSettings" alignment="0" min="-2" pref="155" max="-2" attributes="0"/>
<Component id="jLabelBaselineConfig" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="jLabelReportConfig" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="jLabelDescription" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="bnEditReportSettings" alignment="0" min="-2" pref="155" max="-2" attributes="0"/>
</Group>
<EmptySpace pref="362" max="32767" attributes="0"/>
<EmptySpace pref="-34" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -72,14 +71,12 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="27" max="-2" attributes="0"/>
<Component id="jLabelBaselineConfig" min="-2" max="-2" attributes="0"/>
<Component id="jLabelDescription" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="bnEditIngestSettings" min="-2" max="-2" attributes="0"/>
<EmptySpace type="separate" max="-2" attributes="0"/>
<Component id="jLabelReportConfig" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="bnEditReportSettings" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="376" max="32767" attributes="0"/>
<EmptySpace pref="381" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -101,17 +98,10 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="bnEditIngestSettingsActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JLabel" name="jLabelBaselineConfig">
<Component class="javax.swing.JLabel" name="jLabelDescription">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commandlineingest/Bundle.properties" key="CommandLineIngestSettingsPanel.jLabelBaselineConfig.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="jLabelReportConfig">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commandlineingest/Bundle.properties" key="CommandLineIngestSettingsPanel.jLabelReportConfig.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/commandlineingest/Bundle.properties" key="CommandLineIngestSettingsPanel.jLabelDescription.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>

View File

@ -99,8 +99,7 @@ public class CommandLineIngestSettingsPanel extends javax.swing.JPanel {
nodeScrollPane = new javax.swing.JScrollPane();
nodePanel = new javax.swing.JPanel();
bnEditIngestSettings = new javax.swing.JButton();
jLabelBaselineConfig = new javax.swing.JLabel();
jLabelReportConfig = new javax.swing.JLabel();
jLabelDescription = new javax.swing.JLabel();
bnEditReportSettings = new javax.swing.JButton();
setPreferredSize(new java.awt.Dimension(810, 422));
@ -120,9 +119,7 @@ public class CommandLineIngestSettingsPanel extends javax.swing.JPanel {
}
});
org.openide.awt.Mnemonics.setLocalizedText(jLabelBaselineConfig, org.openide.util.NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.jLabelBaselineConfig.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(jLabelReportConfig, org.openide.util.NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.jLabelReportConfig.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(jLabelDescription, org.openide.util.NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.jLabelDescription.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(bnEditReportSettings, org.openide.util.NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.bnEditReportSettings.text")); // NOI18N
bnEditReportSettings.setToolTipText(org.openide.util.NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.bnEditReportSettings.toolTipText")); // NOI18N
@ -141,23 +138,20 @@ public class CommandLineIngestSettingsPanel extends javax.swing.JPanel {
.addContainerGap()
.addGroup(nodePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(bnEditIngestSettings, javax.swing.GroupLayout.PREFERRED_SIZE, 155, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jLabelBaselineConfig)
.addComponent(jLabelReportConfig)
.addComponent(jLabelDescription, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(bnEditReportSettings, javax.swing.GroupLayout.PREFERRED_SIZE, 155, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap(362, Short.MAX_VALUE))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
nodePanelLayout.setVerticalGroup(
nodePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(nodePanelLayout.createSequentialGroup()
.addGap(27, 27, 27)
.addComponent(jLabelBaselineConfig)
.addComponent(jLabelDescription, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(bnEditIngestSettings)
.addGap(18, 18, 18)
.addComponent(jLabelReportConfig)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(bnEditReportSettings)
.addContainerGap(376, Short.MAX_VALUE))
.addContainerGap(381, Short.MAX_VALUE))
);
nodeScrollPane.setViewportView(nodePanel);
@ -187,8 +181,7 @@ public class CommandLineIngestSettingsPanel extends javax.swing.JPanel {
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton bnEditIngestSettings;
private javax.swing.JButton bnEditReportSettings;
private javax.swing.JLabel jLabelBaselineConfig;
private javax.swing.JLabel jLabelReportConfig;
private javax.swing.JLabel jLabelDescription;
private javax.swing.JPanel nodePanel;
private javax.swing.JScrollPane nodeScrollPane;
// End of variables declaration//GEN-END:variables

View File

@ -129,9 +129,7 @@ final public class FiltersPanel extends JPanel {
public FiltersPanel() {
initComponents();
CheckBoxIconPanel panel = createAccoutTypeCheckBoxPanel(Account.Type.DEVICE, true);
accountTypeMap.put(Account.Type.DEVICE, panel.getCheckBox());
accountTypeListPane.add(panel);
initalizeDeviceAccountType();
deviceRequiredLabel.setVisible(false);
accountTypeRequiredLabel.setVisible(false);
@ -260,6 +258,8 @@ final public class FiltersPanel extends JPanel {
accountTypeMap.clear();
accountTypeListPane.removeAll();
initalizeDeviceAccountType();
});
}
@ -270,6 +270,12 @@ final public class FiltersPanel extends JPanel {
IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener);
}
private void initalizeDeviceAccountType() {
CheckBoxIconPanel panel = createAccoutTypeCheckBoxPanel(Account.Type.DEVICE, true);
accountTypeMap.put(Account.Type.DEVICE, panel.getCheckBox());
accountTypeListPane.add(panel);
}
/**
* Populate the Account Types filter widgets
*

View File

@ -101,9 +101,12 @@
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="flagEncryptionProgramsCheckBox" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="finalizeImageWriter" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="promptBeforeExit" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="createVHDCheckBox" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="promptBeforeExit" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="102" attributes="0">
<EmptySpace min="21" pref="21" max="-2" attributes="0"/>
<Component id="finalizeImageWriter" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
@ -124,7 +127,7 @@
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="rulesScrollPane" pref="478" max="32767" attributes="0"/>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
@ -132,9 +135,9 @@
<Component id="editRuleButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="deleteRuleButton" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="30" max="-2" attributes="0"/>
@ -190,12 +193,13 @@
<Component id="jSeparator1" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Component id="flagEncryptionProgramsCheckBox" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Component id="finalizeImageWriter" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Component id="promptBeforeExit" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="createVHDCheckBox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="finalizeImageWriter" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="promptBeforeExit" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="19" max="-2" attributes="0"/>
</Group>
</Group>
</Group>
@ -472,6 +476,7 @@
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties" key="ConfigVisualPanel2.finalizeImageWriter.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="finalizeImageWriterActionPerformed"/>

View File

@ -215,6 +215,7 @@ final class ConfigVisualPanel2 extends JPanel {
org.openide.awt.Mnemonics.setLocalizedText(ruleSetFileLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.ruleSetFileLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(finalizeImageWriter, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.finalizeImageWriter.text")); // NOI18N
finalizeImageWriter.setEnabled(false);
finalizeImageWriter.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
finalizeImageWriterActionPerformed(evt);
@ -345,9 +346,11 @@ final class ConfigVisualPanel2 extends JPanel {
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(flagEncryptionProgramsCheckBox)
.addComponent(finalizeImageWriter)
.addComponent(createVHDCheckBox)
.addComponent(promptBeforeExit)
.addComponent(createVHDCheckBox))
.addGroup(layout.createSequentialGroup()
.addGap(21, 21, 21)
.addComponent(finalizeImageWriter)))
.addGap(0, 0, Short.MAX_VALUE))
.addComponent(jSeparator1)))))
);
@ -418,11 +421,12 @@ final class ConfigVisualPanel2 extends JPanel {
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(flagEncryptionProgramsCheckBox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(finalizeImageWriter)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(promptBeforeExit)
.addComponent(createVHDCheckBox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(createVHDCheckBox))))
.addComponent(finalizeImageWriter)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(promptBeforeExit)
.addGap(19, 19, 19))))
);
}// </editor-fold>//GEN-END:initComponents
@ -558,6 +562,7 @@ final class ConfigVisualPanel2 extends JPanel {
private void createVHDCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_createVHDCheckBoxActionPerformed
config.setCreateVHD(createVHDCheckBox.isSelected());
finalizeImageWriter.setEnabled(createVHDCheckBox.isSelected());
}//GEN-LAST:event_createVHDCheckBoxActionPerformed
/**
@ -661,6 +666,7 @@ final class ConfigVisualPanel2 extends JPanel {
finalizeImageWriter.setSelected(config.isFinalizeImageWriter());
promptBeforeExit.setSelected(config.isPromptBeforeExit());
createVHDCheckBox.setSelected(config.isCreateVHD());
finalizeImageWriter.setEnabled(config.isCreateVHD());
LogicalImagerRuleSet ruleSet = getRuleSetFromCurrentConfig();
flagEncryptionProgramsCheckBox.setSelected(ruleSet.find(EncryptionProgramsRule.getName()) != null);
RulesTableModel rulesTableModel = new RulesTableModel();

View File

@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.logicalimager.dsp;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
@ -53,14 +54,14 @@ import org.sleuthkit.datamodel.TskCoreException;
/**
* A runnable that - copy the logical image folder to a destination folder - add
* SearchResults.txt and users.txt files to report - add an image data source to the
* SearchResults.txt and *_users.txt files to report - add an image data source to the
* case database.
*/
final class AddLogicalImageTask implements Runnable {
private final static Logger LOGGER = Logger.getLogger(AddLogicalImageTask.class.getName());
private final static String SEARCH_RESULTS_TXT = "SearchResults.txt"; //NON-NLS
private final static String USERS_TXT = "users.txt"; //NON-NLS
private final static String USERS_TXT = "_users.txt"; //NON-NLS
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
@ -102,7 +103,7 @@ final class AddLogicalImageTask implements Runnable {
}
/**
* Add SearchResults.txt and users.txt to the case
* Add SearchResults.txt and *_users.txt to the case
* report Adds the image to the case database.
*/
@Messages({
@ -148,7 +149,7 @@ final class AddLogicalImageTask implements Runnable {
return;
}
// Add the SearchResults.txt and users.txt to the case report
// Add the SearchResults.txt and *_users.txt to the case report
String resultsFilename;
if (Paths.get(dest.toString(), SEARCH_RESULTS_TXT).toFile().exists()) {
resultsFilename = SEARCH_RESULTS_TXT;
@ -167,14 +168,24 @@ final class AddLogicalImageTask implements Runnable {
}
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingToReport(resultsFilename));
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingToReport(USERS_TXT));
status = addReport(Paths.get(dest.toString(), USERS_TXT), USERS_TXT + " " + src.getName());
// All all *_users.txt files to report
File[] userFiles = dest.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(USERS_TXT);
}
});
for (File userFile : userFiles) {
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingToReport(userFile.getName()));
status = addReport(userFile.toPath(), userFile.getName() + " " + src.getName());
if (status != null) {
errorList.add(status);
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
return;
}
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingToReport(USERS_TXT));
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingToReport(userFile.getName()));
}
// Get all VHD files in the dest directory
List<String> imagePaths = new ArrayList<>();

View File

@ -1,6 +1,7 @@
EncryptionDetectionDataSourceIngestModule.artifactComment.bitlocker=Bitlocker encryption detected.
EncryptionDetectionDataSourceIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f).
EncryptionDetectionDataSourceIngestModule.processing.message=Checking image for encryption.
EncryptionDetectionFileIngestModule.artifactComment.location=High entropy and known location/extension.
EncryptionDetectionFileIngestModule.artifactComment.password=Password protection detected.
EncryptionDetectionFileIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f).
EncryptionDetectionFileIngestModule.getDesc.text=Looks for files with the specified minimum entropy.

View File

@ -29,6 +29,8 @@ import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.BufferUnderflowException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import org.apache.tika.exception.EncryptedDocumentException;
import org.apache.tika.exception.TikaException;
@ -76,6 +78,11 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
private static final String[] FILE_IGNORE_LIST = {"hiberfile.sys", "pagefile.sys"};
/**
* This maps file locations to file extensions that are known to be encrypted
*/
private static final Map<String, String> knownEncryptedLocationExtensions = createLocationExtensionMap();
private final IngestServices services = IngestServices.getInstance();
private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName());
private FileTypeDetector fileTypeDetector;
@ -119,6 +126,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
@Messages({
"EncryptionDetectionFileIngestModule.artifactComment.password=Password protection detected.",
"EncryptionDetectionFileIngestModule.artifactComment.location=High entropy and known location/extension.",
"EncryptionDetectionFileIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f)."
})
@Override
@ -155,6 +163,9 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
*/
String mimeType = fileTypeDetector.getMIMEType(file);
if (mimeType.equals("application/octet-stream") && isFileEncryptionSuspected(file)) {
if (checkFileLocationExtension(file)) {
return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, Bundle.EncryptionDetectionFileIngestModule_artifactComment_location());
}
return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED,
String.format(Bundle.EncryptionDetectionFileIngestModule_artifactComment_suspected(), calculatedEntropy));
} else if (isFilePasswordProtected(file)) {
@ -406,4 +417,36 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
return possiblyEncrypted;
}
/**
* This method checks if the AbstractFile input is in a location that is
* known to hold encrypted files. It must meet the requirements and location
* of known encrypted file(s)
*
* @param file AbstractFile to be checked.
*
* @return True if file extension and location match known values.
*
*/
private boolean checkFileLocationExtension(AbstractFile file) {
String filePath = file.getParentPath().replace("/", "");
if ((knownEncryptedLocationExtensions.containsKey(filePath))
&& (knownEncryptedLocationExtensions.get(filePath).equals(file.getNameExtension())))
{
return true;
}
return false;
}
/*
* This method creates the map of paths and extensions that are known to
* have encrypted files
*
* @return Map of path and extension of files
*/
private static Map<String, String> createLocationExtensionMap() {
Map<String, String> locationExtensionMap = new HashMap<String, String>();
locationExtensionMap.put(".android_secure", "asec");
return locationExtensionMap;
}
}

View File

@ -693,9 +693,7 @@ public class HashDbManager implements PropertyChangeListener {
return filePath;
}
public static abstract class HashDb implements Serializable {
private static final long serialVersionUID = 1L;
public static abstract class HashDb {
/**
* Indicates how files with hashes stored in a particular hash database

View File

@ -73,7 +73,7 @@ class PortableCaseInterestingItemsListPanel extends javax.swing.JPanel {
customizeComponents();
// update tag selection
jAllSetsCheckBox.setSelected(settings.areAllTagsSelected());
jAllSetsCheckBox.setSelected(settings.areAllSetsSelected());
setNamesListBox.setEnabled(!jAllSetsCheckBox.isSelected());
selectButton.setEnabled(!jAllSetsCheckBox.isSelected());
deselectButton.setEnabled(!jAllSetsCheckBox.isSelected());

View File

@ -282,6 +282,7 @@ public class ReportGenerator {
/**
* Run the TableReportModules using a SwingWorker.
*
* @param tableReport
* @param tableReportSettings settings for the table report
*/
private void generateTableReport(TableReportModule tableReport, TableReportSettings tableReportSettings) throws IOException {
@ -301,6 +302,7 @@ public class ReportGenerator {
/**
* Run the FileReportModules using a SwingWorker.
*
* @param fileReportModule
* @param fileReportSettings settings for the file report
*/
private void generateFileListReport(FileReportModule fileReportModule, FileReportSettings fileReportSettings) throws IOException {

View File

@ -80,8 +80,10 @@ class ReportWizardFileOptionsVisualPanel extends javax.swing.JPanel {
optionsList.setCellRenderer(new OptionsListRenderer());
optionsList.setVisibleRowCount(-1);
selectAllButton.setEnabled(true);
deselectAllButton.setEnabled(false);
selectAllButton.setEnabled(notAllSelected());
deselectAllButton.setEnabled(anySelected());
wizPanel.setFinish(anySelected());
// Add the ability to enable and disable Tag checkboxes to the list
optionsList.addMouseListener(new MouseAdapter() {

View File

@ -461,7 +461,7 @@ public class HTMLReport implements TableReportModule {
* temporary workaround to avoid modifying the TableReportModule interface.
*
* @param name Name of the data type
* @param comment Comment on the data type, may be the empty string
* @param description Comment on the data type, may be the empty string
*/
@Override
public void startDataType(String name, String description) {
@ -644,7 +644,6 @@ public class HTMLReport implements TableReportModule {
* temporary workaround to avoid modifying the TableReportModule interface.
*
* @param columnHeaders column headers
* @param sourceArtifact source blackboard artifact for the table data
*/
public void startContentTagsTable(List<String> columnHeaders) {
StringBuilder htmlOutput = new StringBuilder();
@ -718,12 +717,10 @@ public class HTMLReport implements TableReportModule {
/**
* Saves a local copy of a tagged file and adds a row with a hyper link to
* the file. The content of the hyperlink is provided in linkHTMLContent.
* the file.
*
* @param row Values for each data cell in the row
* @param file The file to link to in the report.
* @param tagName the name of the tag that the content was flagged by
* @param linkHTMLContent the html that will be the body of the link
* @param contentTag The tag
*/
public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
Content content = contentTag.getContent();

View File

@ -24,6 +24,9 @@ PortableCaseReportModule.generateReport.errorCopyingFiles=Error copying tagged f
PortableCaseReportModule.generateReport.errorCopyingInterestingFiles=Error copying interesting files
PortableCaseReportModule.generateReport.errorCopyingInterestingResults=Error copying interesting results
PortableCaseReportModule.generateReport.errorCopyingTags=Error copying tags
PortableCaseReportModule.generateReport.errorCreatingImageTagTable=Error creating image tags table
PortableCaseReportModule.generateReport.errorCreatingReportFolder=Could not make report folder
PortableCaseReportModule.generateReport.errorGeneratingUCOreport=Problem while generating CASE-UCO report
# {0} - attribute type name
PortableCaseReportModule.generateReport.errorLookingUpAttrType=Error looking up attribute type {0}
PortableCaseReportModule.generateReport.errorReadingSets=Error while reading interesting items sets from case database

View File

@ -267,6 +267,7 @@ public class STIXReportModule implements GeneralReportModule {
*
* @param stix STIXPackage
* @param output
* @param progressPanel
*/
private void processIndicators(STIXPackage stix, BufferedWriter output, ReportProgressPanel progressPanel) throws TskCoreException {
if (stix.getIndicators() != null) {
@ -304,6 +305,7 @@ public class STIXReportModule implements GeneralReportModule {
*
* @param ind
* @param result
* @param progressPanel
*
* @throws TskCoreException
*/

View File

@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.report.modules.taggedhashes;
import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb;
import org.sleuthkit.autopsy.report.ReportModuleSettings;
/**
@ -28,7 +27,7 @@ class HashesReportModuleSettings implements ReportModuleSettings {
private static final long serialVersionUID = 1L;
private final boolean exportAllTags;
private final HashDb hashDb;
private final String hashDbName;
@Override
public long getVersionNumber() {
@ -40,18 +39,18 @@ class HashesReportModuleSettings implements ReportModuleSettings {
*/
HashesReportModuleSettings() {
exportAllTags = true;
hashDb = null;
hashDbName = "";
}
/**
* Configuration for the Tagged Hashes report module.
*
* @param exportAllTags Flag whether to export all tags.
* @param hashDb Selected HashDb object to export to
* @param hashDbName Name of selected hash database to export to
*/
HashesReportModuleSettings(boolean exportAllTags, HashDb hashDb) {
HashesReportModuleSettings(boolean exportAllTags, String hashDbName) {
this.exportAllTags = exportAllTags;
this.hashDb = hashDb;
this.hashDbName = hashDbName;
}
/**
@ -64,9 +63,11 @@ class HashesReportModuleSettings implements ReportModuleSettings {
}
/**
* Get name of the selected hash database.
*
* @return the hashDb
*/
public HashDb getHashDb() {
return hashDb;
public String getHashDbName() {
return hashDbName;
}
}

View File

@ -129,7 +129,7 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="hashSetsComboBoxActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;HashDb&gt;"/>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
<Component class="javax.swing.JButton" name="configureHashDatabasesButton">

View File

@ -56,7 +56,8 @@ class SaveTaggedHashesToHashDbConfigPanel extends javax.swing.JPanel {
private Map<String, Boolean> tagNameSelections = new LinkedHashMap<>();
private TagNamesListModel tagsNamesListModel = new TagNamesListModel();
private TagsNamesListCellRenderer tagsNamesRenderer = new TagsNamesListCellRenderer();
private HashDb selectedHashSet = null;
private String selectedHashSetName;
private List<HashDb> updateableHashSets = new ArrayList<>();
SaveTaggedHashesToHashDbConfigPanel() {
initComponents();
@ -74,7 +75,7 @@ class SaveTaggedHashesToHashDbConfigPanel extends javax.swing.JPanel {
}
HashesReportModuleSettings getConfiguration() {
return new HashesReportModuleSettings(jAllTagsCheckBox.isSelected(), selectedHashSet);
return new HashesReportModuleSettings(jAllTagsCheckBox.isSelected(), selectedHashSetName);
}
void setConfiguration(HashesReportModuleSettings settings) {
@ -84,11 +85,14 @@ class SaveTaggedHashesToHashDbConfigPanel extends javax.swing.JPanel {
// update tag selection
jAllTagsCheckBox.setSelected(settings.isExportAllTags());
if (settings.isExportAllTags()) {
selectAllTags(true);
}
// update hash database selection
if (settings.getHashDb() != null) {
if (settings.getHashDbName() != null) {
populateHashSetComponents();
hashSetsComboBox.setSelectedItem(settings.getHashDb());
hashSetsComboBox.setSelectedItem(settings.getHashDbName());
}
}
@ -147,13 +151,14 @@ class SaveTaggedHashesToHashDbConfigPanel extends javax.swing.JPanel {
// Clear the components because this method is called both during construction
// and when the user changes the hash set configuration.
hashSetsComboBox.removeAllItems();
selectedHashSetName = "";
// Get the updateable hash databases and add their hash set names to the
// JComboBox component.
List<HashDb> updateableHashSets = HashDbManager.getInstance().getUpdateableHashSets();
updateableHashSets = HashDbManager.getInstance().getUpdateableHashSets();
if (!updateableHashSets.isEmpty()) {
for (HashDb hashDb : updateableHashSets) {
hashSetsComboBox.addItem(hashDb);
hashSetsComboBox.addItem(hashDb.getHashSetName());
}
hashSetsComboBox.setEnabled(true);
} else {
@ -182,7 +187,12 @@ class SaveTaggedHashesToHashDbConfigPanel extends javax.swing.JPanel {
* @return A HashDb object representing the database or null.
*/
HashDb getSelectedHashDatabase() {
return selectedHashSet;
for (HashDb hashDb : updateableHashSets) {
if (hashDb.getHashSetName().equals(selectedHashSetName)) {
return hashDb;
}
}
return null;
}
// This class is a list model for the tag names JList component.
@ -341,7 +351,7 @@ class SaveTaggedHashesToHashDbConfigPanel extends javax.swing.JPanel {
}//GEN-LAST:event_selectAllButtonActionPerformed
private void hashSetsComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_hashSetsComboBoxActionPerformed
selectedHashSet = (HashDb)hashSetsComboBox.getSelectedItem();
selectedHashSetName = (String)hashSetsComboBox.getSelectedItem();
}//GEN-LAST:event_hashSetsComboBoxActionPerformed
private void deselectAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deselectAllButtonActionPerformed
@ -377,7 +387,7 @@ class SaveTaggedHashesToHashDbConfigPanel extends javax.swing.JPanel {
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton configureHashDatabasesButton;
private javax.swing.JButton deselectAllButton;
private javax.swing.JComboBox<HashDb> hashSetsComboBox;
private javax.swing.JComboBox<String> hashSetsComboBox;
private javax.swing.JCheckBox jAllTagsCheckBox;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;

View File

@ -335,7 +335,9 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
eventsTreeTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS
eventsTreeTab.disableProperty().bind(controller.viewModeProperty().isNotEqualTo(ViewMode.DETAIL));
final TabPane leftTabPane = new TabPane(filterTab, eventsTreeTab);
// There is a bug in the sort of the EventsTree, it doesn't seem to
//sort anything. For now removing from tabpane until fixed
final TabPane leftTabPane = new TabPane(filterTab);
VBox.setVgrow(leftTabPane, Priority.ALWAYS);
controller.viewModeProperty().addListener(viewMode -> {
if (controller.getViewMode() != ViewMode.DETAIL) {

View File

@ -0,0 +1,256 @@
"""
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.casemodule import NoCurrentCaseException
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.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
import json
import traceback
import general
"""
Finds the SQLite DB for Facebook messenger, parses the DB for contacts & messages,
and adds artifacts to the case.
"""
class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
def __init__(self):
self._logger = Logger.getLogger(self.__class__.__name__)
self._FB_MESSENGER_PACKAGE_NAME = "com.facebook.orca"
self._FACEBOOK_PACKAGE_NAME = "com.facebook.katana"
self._MODULE_NAME = "FB Messenger Analyzer"
self._MESSAGE_TYPE = "Facebook Messenger"
self._VERSION = "239.0.0.41" ## FB version number. Did not find independent version number in FB Messenger
self.selfAccountAddress = None
self.current_case = None
## Analyze contacts
def analyzeContacts(self, dataSource, fileManager, context):
## FB messenger and FB have same database structure for contacts.
## In our example the FB Messenger database was empty.
## But the FB database had the data.
contactsDbs = AppSQLiteDB.findAppDatabases(dataSource, "contacts_db2", True, self._FACEBOOK_PACKAGE_NAME)
for contactsDb in contactsDbs:
try:
selfAccountResultSet = contactsDb.runQuery("SELECT fbid, display_name FROM contacts WHERE added_time_ms = 0")
if selfAccountResultSet:
if not self.selfAccountAddress:
self.selfAccountAddress = Account.Address(selfAccountResultSet.getString("fbid"), selfAccountResultSet.getString("display_name"))
if self.selfAccountAddress is not None:
contactsDBHelper = CommunicationArtifactsHelper(self.current_case.getSleuthkitCase(),
self._MODULE_NAME, contactsDb.getDBFile(),
Account.Type.FACEBOOK, Account.Type.FACEBOOK, self.selfAccountAddress )
else:
contactsDBHelper = CommunicationArtifactsHelper(self.current_case.getSleuthkitCase(),
self._MODULE_NAME, contactsDb.getDBFile(),
Account.Type.FACEBOOK)
contactsResultSet = contactsDb.runQuery("SELECT fbid, display_name FROM contacts WHERE added_time_ms <> " + self.selfAccountAddress.getUniqueID() )
if contactsResultSet is not None:
while contactsResultSet.next():
contactsDBHelper.addContact( contactsResultSet.getString("fbid"), ## unique id for account
contactsResultSet.getString("display_name"), ## contact name
"", ## phone
"", ## home phone
"", ## mobile
"") ## email
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query result for account", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to add Facebook Messenger contact 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())
finally:
contactsDb.close()
## Adds a recipient to given list
def addRecipientToList(self, user_key, name, fromAddress, recipientList):
if user_key is not None:
recipientId = user_key.replace('FACEBOOK:', '')
toAddress = Account.Address(recipientId, name)
# ensure sender, if known, isn't added to recipientList.
if (fromAddress and fromAddress.getUniqueID() != toAddress.getUniqueID()) or (not fromAddress) :
# add recipient to list
recipientList.append(toAddress)
## Analyze messages
def analyzeMessages(self, dataSource, fileManager, context):
threadsDbs = AppSQLiteDB.findAppDatabases(dataSource, "threads_db2", True, self._FB_MESSENGER_PACKAGE_NAME)
for threadsDb in threadsDbs:
try:
if self.selfAccountAddress is not None:
threadsDBHelper = CommunicationArtifactsHelper(self.current_case.getSleuthkitCase(),
self._MODULE_NAME, threadsDb.getDBFile(),
Account.Type.FACEBOOK, Account.Type.FACEBOOK, self.selfAccountAddress )
else:
threadsDBHelper = CommunicationArtifactsHelper(self.current_case.getSleuthkitCase(),
self._MODULE_NAME, threadsDb.getDBFile(),
Account.Type.FACEBOOK)
## Messages are found in the messages table. 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
sqlString = "SELECT msg_id, text, sender, timestamp_ms, 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"\
" ORDER BY msg_id"
messagesResultSet = threadsDb.runQuery(sqlString)
if messagesResultSet is not None:
oldMsgId = None
direction = CommunicationDirection.UNKNOWN
fromAddress = None
recipientAddressList = 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,
fromAddress,
recipientAddressList,
timeStamp,
MessageReadStatus.UNKNOWN,
"", # subject
msgText,
threadId)
oldMsgId = msgId
# New message - collect all attributes
recipientAddressList = []
## get sender address 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']
fromAddress = Account.Address(senderId, senderName)
if senderId == self.selfAccountAddress.getUniqueID():
direction = CommunicationDirection.OUTGOING
else:
direction = CommunicationDirection.INCOMING
# Get recipient and add to list
self.addRecipientToList(messagesResultSet.getString("user_key"), messagesResultSet.getString("name"),
fromAddress, recipientAddressList)
timeStamp = messagesResultSet.getLong("timestamp_ms") / 1000
# Get msg text
# Sometimes there may not be an explict msg text,
# but a app genrated 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"), messagesResultSet.getString("name"),
fromAddress, recipientAddressList)
# at the end of the loop, add last message
messageArtifact = threadsDBHelper.addMessage(
self._MESSAGE_TYPE,
direction,
fromAddress,
recipientAddressList,
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())
finally:
threadsDb.close()
def analyze(self, dataSource, fileManager, context):
try:
self.current_case = Case.getCurrentCaseThrows()
except NoCurrentCaseException as ex:
self._logger.log(Level.WARNING, "No case currently open.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
return
self.analyzeContacts(dataSource, fileManager, context)
self.analyzeMessages(dataSource, fileManager, context)

View File

@ -0,0 +1,93 @@
"""
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.casemodule import NoCurrentCaseException
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.datamodel.blackboardutils import ArtifactsHelper
import traceback
import general
"""
Finds the SQLite DB for Android installed applications, parses the DB,
and adds artifacts to the case.
"""
class InstalledApplicationsAnalyzer(general.AndroidComponentAnalyzer):
moduleName = "Android Installed Applications Analyzer"
def __init__(self):
self._logger = Logger.getLogger(self.__class__.__name__)
self._PACKAGE_NAME = "com.android.vending"
self._MODULE_NAME = "Android Installed Applications Analyzer"
self._VERSION = "5.1.1" ## Android version
self.current_case = None
def analyze(self, dataSource, fileManager, context):
libraryDbs = AppSQLiteDB.findAppDatabases(dataSource, "library.db", True, self._PACKAGE_NAME)
for libraryDb in libraryDbs:
try:
current_case = Case.getCurrentCaseThrows()
libraryDbHelper = ArtifactsHelper(current_case.getSleuthkitCase(),
self.moduleName, libraryDb.getDBFile())
queryString = "SELECT doc_id, purchase_time FROM ownership"
ownershipResultSet = libraryDb.runQuery(queryString)
if ownershipResultSet is not None:
while ownershipResultSet.next():
purchase_time = ownershipResultSet.getLong("purchase_time") / 1000
libraryDbHelper.addInstalledProgram(ownershipResultSet.getString("doc_id"),
purchase_time)
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query result for installed applications. ", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to adding installed application 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:
libraryDb.close()

View File

@ -47,9 +47,20 @@ import tangomessage
import textmessage
import wwfmessage
import imo
import xender
import zapya
import shareit
import viber
import skype
import line
import whatsapp
import textnow
import sbrowser
import operabrowser
import oruxmaps
import installedapps
import fbmessenger
class AndroidModuleFactory(IngestModuleFactoryAdapter):
@ -94,7 +105,14 @@ class AndroidIngestModule(DataSourceIngestModule):
analyzers = [contact.ContactAnalyzer(), calllog.CallLogAnalyzer(), textmessage.TextMessageAnalyzer(),
tangomessage.TangoMessageAnalyzer(), wwfmessage.WWFMessageAnalyzer(),
googlemaplocation.GoogleMapLocationAnalyzer(), browserlocation.BrowserLocationAnalyzer(),
cachelocation.CacheLocationAnalyzer(), imo.IMOAnalyzer(), line.LineAnalyzer(), textnow.TextNowAnalyzer(), whatsapp.WhatsAppAnalyzer()]
cachelocation.CacheLocationAnalyzer(), imo.IMOAnalyzer(),
xender.XenderAnalyzer(), zapya.ZapyaAnalyzer(), shareit.ShareItAnalyzer(),
line.LineAnalyzer(), whatsapp.WhatsAppAnalyzer(),
textnow.TextNowAnalyzer(), skype.SkypeAnalyzer(), viber.ViberAnalyzer(),
fbmessenger.FBMessengerAnalyzer(),
sbrowser.SBrowserAnalyzer(), operabrowser.OperaAnalyzer(),
oruxmaps.OruxMapsAnalyzer(),
installedapps.InstalledApplicationsAnalyzer()]
self.log(Level.INFO, "running " + str(len(analyzers)) + " analyzers")
progressBar.switchToDeterminate(len(analyzers))

View File

@ -0,0 +1,240 @@
"""
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.casemodule import NoCurrentCaseException
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.datamodel.blackboardutils import WebBrowserArtifactsHelper
import traceback
import general
"""
Finds the SQLite DB for Opera browser, parses the DB for Bookmarks, Cookies, Web History
and adds artifacts to the case.
"""
class OperaAnalyzer(general.AndroidComponentAnalyzer):
moduleName = "Opera Parser"
progName = "Opera"
def __init__(self):
self._logger = Logger.getLogger(self.__class__.__name__)
self._PACKAGE_NAME = "com.opera.browser"
self._MODULE_NAME = "Opera Analyzer"
self._PROGRAM_NAME = "Opera"
self._VERSION = "53.1.2569"
self.current_case = None
def analyzeCookies(self, dataSource, fileManager, context):
cookiesDbs = AppSQLiteDB.findAppDatabases(dataSource, "Cookies", True, self._PACKAGE_NAME)
for cookiesDb in cookiesDbs:
try:
cookiesDbHelper = WebBrowserArtifactsHelper(self.current_case.getSleuthkitCase(),
self.moduleName, cookiesDb.getDBFile())
cookiesResultSet = cookiesDb.runQuery("SELECT host_key, name, value, creation_utc FROM cookies")
if cookiesResultSet is not None:
while cookiesResultSet.next():
createTime = cookiesResultSet.getLong("creation_utc") / 1000000 - 11644473600 # Webkit time
cookiesDbHelper.addWebCookie( cookiesResultSet.getString("host_key"),
createTime,
cookiesResultSet.getString("name"),
cookiesResultSet.getString("value"),
self._PROGRAM_NAME)
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query results for Opera cookies.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to add Opera cookie 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())
finally:
cookiesDb.close()
def analyzeHistory(self, dataSource, fileManager, context):
historyDbs = AppSQLiteDB.findAppDatabases(dataSource, "History", True, self._PACKAGE_NAME)
for historyDb in historyDbs:
try:
historyDbHelper = WebBrowserArtifactsHelper(self.current_case.getSleuthkitCase(),
self.moduleName, historyDb.getDBFile())
historyResultSet = historyDb.runQuery("SELECT url, title, last_visit_time FROM urls")
if historyResultSet is not None:
while historyResultSet.next():
accessTime = historyResultSet.getLong("last_visit_time") / 1000000 - 11644473600
historyDbHelper.addWebHistory( historyResultSet.getString("url"),
accessTime,
"", # referrer
historyResultSet.getString("title"),
self._PROGRAM_NAME)
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query results for Opera history.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to add Opera history 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())
finally:
historyDb.close()
def analyzeDownloads(self, dataSource, fileManager, context):
downloadsDbs = AppSQLiteDB.findAppDatabases(dataSource, "History", True, self._PACKAGE_NAME)
for downloadsDb in downloadsDbs:
try:
downloadsDbHelper = WebBrowserArtifactsHelper(self.current_case.getSleuthkitCase(),
self.moduleName, downloadsDb.getDBFile())
queryString = "SELECT target_path, start_time, url FROM downloads"\
" INNER JOIN downloads_url_chains ON downloads.id = downloads_url_chains.id"
downloadsResultSet = downloadsDb.runQuery(queryString)
if downloadsResultSet is not None:
while downloadsResultSet.next():
startTime = historyResultSet.getLong("start_time") / 1000000 - 11644473600 #Webkit time format
downloadsDbHelper.addWebDownload( downloadsResultSet.getString("target_path"),
startTime,
downloadsResultSet.getString("url"),
self._PROGRAM_NAME)
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query results for Opera downloads.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to add Opera download 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())
finally:
downloadsDb.close()
def analyzeAutofill(self, dataSource, fileManager, context):
autofillDbs = AppSQLiteDB.findAppDatabases(dataSource, "Web Data", True, self._PACKAGE_NAME)
for autofillDb in autofillDbs:
try:
autofillDbHelper = WebBrowserArtifactsHelper(self.current_case.getSleuthkitCase(),
self.moduleName, autofillDb.getDBFile())
autofillsResultSet = autofillDb.runQuery("SELECT name, value, count, date_created FROM autofill")
if autofillsResultSet is not None:
while autofillsResultSet.next():
creationTime = autofillsResultSet.getLong("date_created") / 1000000 - 11644473600 #Webkit time format
autofillDbHelper.addWebFormAutofill( autofillsResultSet.getString("name"),
autofillsResultSet.getString("value"),
creationTime,
0,
autofillsResultSet.getInt("count"))
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query results for Opera autofill.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to add Opera autofill 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())
finally:
autofillDb.close()
def analyzeWebFormAddress(self, dataSource, fileManager, context):
webFormAddressDbs = AppSQLiteDB.findAppDatabases(dataSource, "Web Data", True, self._PACKAGE_NAME)
for webFormAddressDb in webFormAddressDbs:
try:
webFormAddressDbHelper = WebBrowserArtifactsHelper(self.current_case.getSleuthkitCase(),
self.moduleName, webFormAddressDb.getDBFile())
queryString = "SELECT street_address, city, state, zipcode, country_code, date_modified, first_name, last_name, number, email FROM autofill_profiles "\
" INNER JOIN autofill_profile_names"\
" ON autofill_profiles.guid = autofill_profile_names.guid"\
" INNER JOIN autofill_profile_phones"\
" ON autofill_profiles.guid = autofill_profile_phones.guid"\
" INNER JOIN autofill_profile_emails"\
" ON autofill_profiles.guid = autofill_profile_emails.guid"
webFormAddressResultSet = webFormAddressDb.runQuery(queryString)
if webFormAddressResultSet is not None:
while webFormAddressResultSet.next():
personName = webFormAddressResultSet.getString("first_name") + " " + webFormAddressResultSet.getString("last_name")
address = '\n'.join([ webFormAddressResultSet.getString("street_address"),
webFormAddressResultSet.getString("city"),
webFormAddressResultSet.getString("state") + " " + webFormAddressResultSet.getString("zipcode"),
webFormAddressResultSet.getString("country_code") ])
creationTime = webFormAddressResultSet.getLong("date_modified") / 1000000 - 11644473600
autofillDbHelper.addWebFormAddress( personName,
webFormAddressResultSet.getString("email"),
webFormAddressResultSet.getString("number"),
address,
creationTime,
0,
0)
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query results for Opera web form addresses.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to add Opera form address 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())
finally:
webFormAddressDb.close()
def analyze(self, dataSource, fileManager, context):
## open current case
try:
self.current_case = Case.getCurrentCaseThrows()
except NoCurrentCaseException as ex:
self._logger.log(Level.WARNING, "No case currently open.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
return
self.analyzeCookies(dataSource, fileManager, context)
self.analyzeHistory(dataSource, fileManager, context)
self.analyzeDownloads(dataSource, fileManager, context)
self.analyzeAutofill(dataSource, fileManager, context)
self.analyzeWebFormAddress(dataSource, fileManager, context)

View File

@ -0,0 +1,108 @@
"""
Autopsy Forensic Browser
Copyright 2016-2018 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 Double
from java.lang import Long
from java.sql import Connection
from java.sql import DriverManager
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.sleuthkit.autopsy.casemodule import Case
from org.sleuthkit.autopsy.casemodule import NoCurrentCaseException
from org.sleuthkit.autopsy.casemodule.services import FileManager
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 Blackboard
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.datamodel.blackboardutils import ArtifactsHelper
import traceback
import general
"""
Analyzes database created by ORUX Maps.
"""
class OruxMapsAnalyzer(general.AndroidComponentAnalyzer):
def __init__(self):
self._logger = Logger.getLogger(self.__class__.__name__)
self._PACKAGE_NAME = "oruxmaps"
self._MODULE_NAME = "OruxMaps Analyzer"
self._PROGRAM_NAME = "OruxMaps"
self._VERSION = "7.5.7"
self.current_case = None
def analyze(self, dataSource, fileManager, context):
oruxMapsTrackpointsDbs = AppSQLiteDB.findAppDatabases(dataSource, "oruxmapstracks.db", True, self._PACKAGE_NAME)
for oruxMapsTrackpointsDb in oruxMapsTrackpointsDbs:
try:
current_case = Case.getCurrentCaseThrows()
oruxDbHelper = ArtifactsHelper(current_case.getSleuthkitCase(),
self._MODULE_NAME, oruxMapsTrackpointsDb.getDBFile())
poiQueryString = "SELECT poilat, poilon, poitime, poiname FROM pois"
poisResultSet = oruxMapsTrackpointsDb.runQuery(poiQueryString)
if poisResultSet is not None:
while poisResultSet.next():
oruxDbHelper.addGPSLocation(
poisResultSet.getDouble("poilat"),
poisResultSet.getDouble("poilon"),
poisResultSet.getLong("poitime") / 1000, # milliseconds since unix epoch
poisResultSet.getString("poiname"),
self._PROGRAM_NAME)
trackpointsQueryString = "SELECT trkptlat, trkptlon, trkpttime FROM trackpoints"
trackpointsResultSet = oruxMapsTrackpointsDb.runQuery(trackpointsQueryString)
if trackpointsResultSet is not None:
while trackpointsResultSet.next():
oruxDbHelper.addGPSLocation(
trackpointsResultSet.getDouble("trkptlat"),
trackpointsResultSet.getDouble("trkptlon"),
trackpointsResultSet.getLong("trkpttime") / 1000, # milliseconds since unix epoch
"",
self._PROGRAM_NAME)
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query result for Orux Map trackpoints.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to add Orux Map trackpoint 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:
oruxMapsTrackpointsDb.close()

View File

@ -0,0 +1,266 @@
"""
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.casemodule import NoCurrentCaseException
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.datamodel.blackboardutils import WebBrowserArtifactsHelper
import traceback
import general
"""
Finds the SQLite DB for S-Browser, parses the DB for Bookmarks, Cookies, Web History
and adds artifacts to the case.
"""
class SBrowserAnalyzer(general.AndroidComponentAnalyzer):
def __init__(self):
self._logger = Logger.getLogger(self.__class__.__name__)
self._PACKAGE_NAME = "com.sec.android.app.sbrowser"
self._MODULE_NAME = "SBrowser Analyzer"
self._PROGRAM_NAME = "SBrowser"
self._VERSION = "10.1.00.27"
self.current_case = None
def analyzeBookmarks(self, dataSource, fileManager, context):
sbrowserDbs = AppSQLiteDB.findAppDatabases(dataSource, "sbrowser.db", True, self._PACKAGE_NAME)
for sbrowserDb in sbrowserDbs:
try:
sbrowserDbHelper = WebBrowserArtifactsHelper(self.current_case.getSleuthkitCase(),
self._MODULE_NAME, sbrowserDb.getDBFile())
bookmarkResultSet = sbrowserDb.runQuery("SELECT url, title, created FROM bookmarks WHERE url IS NOT NULL")
if bookmarkResultSet is not None:
while bookmarkResultSet.next():
createTime = bookmarkResultSet.getLong("created") / 1000
sbrowserDbHelper.addWebBookmark( bookmarkResultSet.getString("url"),
bookmarkResultSet.getString("title"),
createTime,
self._PROGRAM_NAME)
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query results for SBrowser bookmarks.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to add SBrowser bookmark 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())
finally:
sbrowserDb.close()
def analyzeCookies(self, dataSource, fileManager, context):
cookiesDbs = AppSQLiteDB.findAppDatabases(dataSource, "Cookies", True, self._PACKAGE_NAME)
for cookiesDb in cookiesDbs:
try:
cookiesDbHelper = WebBrowserArtifactsHelper(self.current_case.getSleuthkitCase(),
self._MODULE_NAME, cookiesDb.getDBFile())
cookiesResultSet = cookiesDb.runQuery("SELECT host_key, name, value, creation_utc FROM cookies")
if cookiesResultSet is not None:
while cookiesResultSet.next():
createTime = cookiesResultSet.getLong("creation_utc") / 1000000 - 11644473600 # Webkit time
cookiesDbHelper.addWebCookie( cookiesResultSet.getString("host_key"),
createTime,
cookiesResultSet.getString("name"),
cookiesResultSet.getString("value"),
self._PROGRAM_NAME)
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query results for SBrowser cookies.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to add SBrowser cookie 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())
finally:
cookiesDb.close()
def analyzeHistory(self, dataSource, fileManager, context):
historyDbs = AppSQLiteDB.findAppDatabases(dataSource, "History", True, self._PACKAGE_NAME)
for historyDb in historyDbs:
try:
historyDbHelper = WebBrowserArtifactsHelper(self.current_case.getSleuthkitCase(),
self._MODULE_NAME, historyDb.getDBFile())
historyResultSet = historyDb.runQuery("SELECT url, title, last_visit_time FROM urls")
if historyResultSet is not None:
while historyResultSet.next():
accessTime = historyResultSet.getLong("last_visit_time") / 1000000 - 11644473600 # Webkit time
historyDbHelper.addWebHistory( historyResultSet.getString("url"),
accessTime,
"", # referrer
historyResultSet.getString("title"),
self._PROGRAM_NAME)
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query results for SBrowser history.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to add SBrowser history 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())
finally:
historyDb.close()
def analyzeDownloads(self, dataSource, fileManager, context):
downloadsDbs = AppSQLiteDB.findAppDatabases(dataSource, "History", True, self._PACKAGE_NAME)
for downloadsDb in downloadsDbs:
try:
downloadsDbHelper = WebBrowserArtifactsHelper(self.current_case.getSleuthkitCase(),
self._MODULE_NAME, downloadsDb.getDBFile())
queryString = "SELECT target_path, start_time, url FROM downloads"\
" INNER JOIN downloads_url_chains ON downloads.id = downloads_url_chains.id"
downloadsResultSet = downloadsDb.runQuery(queryString)
if downloadsResultSet is not None:
while downloadsResultSet.next():
startTime = historyResultSet.getLong("start_time") / 1000000 - 11644473600 # Webkit time
downloadsDbHelper.addWebDownload( downloadsResultSet.getString("target_path"),
startTime,
downloadsResultSet.getString("url"),
self._PROGRAM_NAME)
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query results for SBrowser downloads.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to add SBrowser download 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())
finally:
downloadsDb.close()
def analyzeAutofill(self, dataSource, fileManager, context):
autofillDbs = AppSQLiteDB.findAppDatabases(dataSource, "Web Data", True, self._PACKAGE_NAME)
for autofillDb in autofillDbs:
try:
autofillDbHelper = WebBrowserArtifactsHelper(self.current_case.getSleuthkitCase(),
self._MODULE_NAME, autofillDb.getDBFile())
autofillsResultSet = autofillDb.runQuery("SELECT name, value, count, date_created FROM autofill INNER JOIN autofill_dates ON autofill.pair_id = autofill_dates.pair_id")
if autofillsResultSet is not None:
while autofillsResultSet.next():
creationTime = autofillsResultSet.getLong("date_created") / 1000000 - 11644473600 # Webkit time
autofillDbHelper.addWebFormAutofill( autofillsResultSet.getString("name"),
autofillsResultSet.getString("value"),
creationTime,
0,
autofillsResultSet.getInt("count"))
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query results for SBrowser autofill.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to add SBrowser autofill 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())
finally:
autofillDb.close()
def analyzeWebFormAddress(self, dataSource, fileManager, context):
webFormAddressDbs = AppSQLiteDB.findAppDatabases(dataSource, "Web Data", True, self._PACKAGE_NAME)
for webFormAddressDb in webFormAddressDbs:
try:
webFormAddressDbHelper = WebBrowserArtifactsHelper(self.current_case.getSleuthkitCase(),
self._MODULE_NAME, webFormAddressDb.getDBFile())
queryString = "SELECT street_address, city, state, zipcode, country_code, date_modified, first_name, last_name, number, email FROM autofill_profiles "\
" INNER JOIN autofill_profile_names"\
" ON autofill_profiles.guid = autofill_profile_names.guid"\
" INNER JOIN autofill_profile_phones"\
" ON autofill_profiles.guid = autofill_profile_phones.guid"\
" INNER JOIN autofill_profile_emails"\
" ON autofill_profiles.guid = autofill_profile_emails.guid"
webFormAddressResultSet = webFormAddressDb.runQuery(queryString)
if webFormAddressResultSet is not None:
while webFormAddressResultSet.next():
personName = webFormAddressResultSet.getString("first_name") + " " + webFormAddressResultSet.getString("last_name")
address = '\n'.join([ webFormAddressResultSet.getString("street_address"),
webFormAddressResultSet.getString("city"),
webFormAddressResultSet.getString("state") + " " + webFormAddressResultSet.getString("zipcode"),
webFormAddressResultSet.getString("country_code") ])
creationTime = webFormAddressResultSet.getLong("date_modified") / 1000000 - 11644473600 # Webkit time
autofillDbHelper.addWebFormAddress( personName,
webFormAddressResultSet.getString("email"),
webFormAddressResultSet.getString("number"),
address,
creationTime,
0,
0)
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query results for SBrowser form addresses.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to add SBrowser form address 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())
finally:
webFormAddressDb.close()
def analyze(self, dataSource, fileManager, context):
## open current case
try:
self.current_case = Case.getCurrentCaseThrows()
except NoCurrentCaseException as ex:
self._logger.log(Level.WARNING, "No case currently open.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
return
self.analyzeBookmarks(dataSource, fileManager, context)
self.analyzeCookies(dataSource, fileManager, context)
self.analyzeHistory(dataSource, fileManager, context)
self.analyzeDownloads(dataSource, fileManager, context)
self.analyzeAutofill(dataSource, fileManager, context)
self.analyzeWebFormAddress(dataSource, fileManager, context)

View File

@ -0,0 +1,123 @@
"""
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.casemodule import NoCurrentCaseException
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.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
import traceback
import general
"""
Finds the SQLite DB for ShareIt, parses the DB for contacts & messages,
and adds artifacts to the case.
"""
class ShareItAnalyzer(general.AndroidComponentAnalyzer):
def __init__(self):
self._logger = Logger.getLogger(self.__class__.__name__)
self._PACKAGE_NAME = "com.lenovo.anyshare.gps"
self._MODULE_NAME = "ShareIt Analyzer"
self._MESSAGE_TYPE = "ShareIt Message"
self._VERSION = "5.0.28_ww"
def analyze(self, dataSource, fileManager, context):
historyDbs = AppSQLiteDB.findAppDatabases(dataSource, "history.db", True, self._PACKAGE_NAME)
for historyDb in historyDbs:
try:
current_case = Case.getCurrentCaseThrows()
historyDbHelper = CommunicationArtifactsHelper(current_case.getSleuthkitCase(),
self._MODULE_NAME, historyDb.getDBFile(),
Account.Type.SHAREIT)
queryString = "SELECT history_type, device_id, device_name, description, timestamp, import_path FROM history"
historyResultSet = historyDb.runQuery(queryString)
if historyResultSet is not None:
while historyResultSet.next():
direction = ""
fromAddress = None
toAdddress = None
if (historyResultSet.getInt("history_type") == 1):
direction = CommunicationDirection.OUTGOING
toAddress = Account.Address(historyResultSet.getString("device_id"), historyResultSet.getString("device_name") )
else:
direction = CommunicationDirection.INCOMING
fromAddress = Account.Address(historyResultSet.getString("device_id"), historyResultSet.getString("device_name") )
msgBody = "" # there is no body.
attachments = [historyResultSet.getString("import_path")]
msgBody = general.appendAttachmentList(msgBody, attachments)
timeStamp = historyResultSet.getLong("timestamp") / 1000
messageArtifact = transferDbHelper.addMessage(
self._MESSAGE_TYPE,
direction,
fromAddress,
toAddress,
timeStamp,
MessageReadStatus.UNKNOWN,
None, # subject
msgBody,
None ) # thread id
# TBD: add the file as attachment ??
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query result for ShareIt history.", ex)
self._logger.log(Level.SEVERE, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to create ShareIt 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:
historyDb.close()

View File

@ -0,0 +1,505 @@
"""
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 SkypeAnalyzer(general.AndroidComponentAnalyzer):
"""
Parses the Skype App databases for TSK contacts, message
and calllog artifacts.
About version 8.15.0.428 (9/17/2019) Skype database:
- There are 4 tables this parser uses:
1) person - this table appears to hold all contacts known to the user.
2) user - this table holds information pertaining to the user.
3) particiapnt - Yes, that is not a typo. This table maps group chat
ids to skype ids (1 to many).
4) chatItem - This table contains all messages. It maps the group id or
skype id (for 1 to 1 communication) to the message content
and metadata. Either the group id or skype id is stored in
a column named 'conversation_link'.
More info and implementation details:
- The person table does not include groups. To get
all 1 to 1 communications, we could simply join the person and chatItem tables.
This would mean we'd need to do a second pass to get all the group information
as they would be excluded in the join. Since the chatItem table stores both the
group id or skype_id in one column, an implementation decision was made to union
the person and particiapnt table together so that all rows are matched in one join
with chatItem. This result is consistently labeled contact_list_with_groups in the
following queries.
- In order to keep the formatting of the name consistent throughout each query,
a _format_user_name() function was created to encapsulate the CASE statement
that was being shared across them. Refer to the method for more details.
"""
def __init__(self):
self._logger = Logger.getLogger(self.__class__.__name__)
self._SKYPE_PACKAGE_NAME = "com.skype.raider"
self._PARSER_NAME = "Skype Parser"
self._VERSION = "8.15.0.428"
def get_user_account(self, skype_db):
account_query_result = skype_db.runQuery(
"""
SELECT entry_id,
"""+_format_user_name()+""" AS name
FROM user
"""
)
if account_query_result is not None and account_query_result.next():
return Account.Address(account_query_result.getString("entry_id"),
account_query_result.getString("name"))
return None
def analyze(self, dataSource, fileManager, context):
#Skype databases are of the form: live:XYZ.db, where
#XYZ is the skype id of the user. The following search
#does a generic substring match for 'live' in the skype
#package.
skype_dbs = AppSQLiteDB.findAppDatabases(dataSource,
"live:", False, self._SKYPE_PACKAGE_NAME)
try:
for skype_db in skype_dbs:
#Attempt to get the user account id from the database
user_account_instance = None
try:
user_account_instance = self.get_user_account(skype_db)
except SQLException as ex:
self._logger.log(Level.WARNING,
"Error querying for the user account in the Skype db.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
current_case = Case.getCurrentCaseThrows()
if user_account_instance is None:
helper = CommunicationArtifactsHelper(
current_case.getSleuthkitCase(), self._PARSER_NAME,
skype_db.getDBFile(), Account.Type.SKYPE
)
else:
helper = CommunicationArtifactsHelper(
current_case.getSleuthkitCase(), self._PARSER_NAME,
skype_db.getDBFile(), Account.Type.SKYPE,
Account.Type.SKYPE, user_account_instance
)
self.parse_contacts(skype_db, helper)
self.parse_calllogs(skype_db, helper)
self.parse_messages(skype_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 skype_db in skype_dbs:
skype_db.close()
def parse_contacts(self, skype_db, helper):
#Query for contacts and iterate row by row adding
#each contact artifact
try:
contacts_parser = SkypeContactsParser(skype_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 Skype db
self._logger.log(Level.WARNING,
"Error parsing contact database for call logs artifacts.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
#Severe error trying to add to case database.. case is not complete.
#These exceptions are thrown by the CommunicationArtifactsHelper.
self._logger.log(Level.SEVERE,
"Failed to add contact artifacts to the case database.", ex)
self._logger.log(Level.SEVERE, traceback.format_exc())
except BlackboardException as ex:
#Failed to post notification to blackboard
self._logger.log(Level.WARNING,
"Failed to post contact artifact to the blackboard", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
def parse_calllogs(self, skype_db, helper):
#Query for call logs and iterate row by row adding
#each call log artifact
try:
calllog_parser = SkypeCallLogsParser(skype_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:
#Error parsing Skype db
self._logger.log(Level.WARNING,
"Error parsing Skype database for call logs artifacts.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
#Severe error trying to add to case database.. case is not complete.
#These exceptions are thrown by the CommunicationArtifactsHelper.
self._logger.log(Level.SEVERE,
"Failed to add call log artifacts to the case database.", ex)
self._logger.log(Level.SEVERE, traceback.format_exc())
except BlackboardException as ex:
#Failed to post notification to blackboard
self._logger.log(Level.WARNING,
"Failed to post call log artifact to the blackboard", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
def parse_messages(self, skype_db, helper):
#Query for messages and iterate row by row adding
#each message artifact
try:
messages_parser = SkypeMessagesParser(skype_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 Skype db
self._logger.log(Level.WARNING,
"Error parsing Skype database for message artifacts.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
#Severe error trying to add to case database.. case is not complete.
#These exceptions are thrown by the CommunicationArtifactsHelper.
self._logger.log(Level.SEVERE,
"Failed to add message artifacts to the case database.", ex)
self._logger.log(Level.SEVERE, traceback.format_exc())
except BlackboardException as ex:
#Failed to post notification to blackboard
self._logger.log(Level.WARNING,
"Failed to post message artifact to the blackboard", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
class SkypeCallLogsParser(TskCallLogsParser):
"""
Extracts TSK_CALLLOG information from the Skype database.
TSK_CALLLOG fields that are not in the Skype database are given
a default value inherited from the super class.
"""
def __init__(self, calllog_db):
"""
Big picture:
The query below creates a contacts_list_with_groups table, which
represents the recipient info. A chatItem record holds ids for
both the recipient and sender. The first join onto chatItem fills
in the blanks for the recipients. The second join back onto person
handles the sender info. The result is a table with all of the
communication details.
Implementation details:
- message_type w/ value 3 appeared to be the call type, regardless
of if it was audio or video.
"""
super(SkypeCallLogsParser, self).__init__(calllog_db.runQuery(
"""
SELECT contacts_list_with_groups.conversation_id,
contacts_list_with_groups.participant_ids,
contacts_list_with_groups.participants,
time,
duration,
is_sender_me,
person_id as sender_id,
sender_name.name as sender_name
FROM (SELECT conversation_id,
Group_concat(person_id) AS participant_ids,
Group_concat("""+_format_user_name()+""") AS participants
FROM particiapnt AS PART
JOIN person AS P
ON PART.person_id = P.entry_id
GROUP BY conversation_id
UNION
SELECT entry_id,
NULL,
"""+_format_user_name()+""" AS participant
FROM person) AS contacts_list_with_groups
JOIN chatitem AS C
ON C.conversation_link = contacts_list_with_groups.conversation_id
JOIN (SELECT entry_id as id,
"""+_format_user_name()+""" AS name
FROM person
UNION
SELECT entry_id as id,
"""+_format_user_name()+""" AS name
FROM user) AS sender_name
ON sender_name.id = C.person_id
WHERE message_type == 3
"""
)
)
self._INCOMING_CALL_TYPE = 0
self._OUTGOING_CALL_TYPE = 1
def get_phone_number_from(self):
if self.get_call_direction() == self.INCOMING_CALL:
return Account.Address(self.result_set.getString("sender_id"),
self.result_set.getString("sender_name"))
def get_phone_number_to(self):
if self.get_call_direction() == self.OUTGOING_CALL:
group_ids = self.result_set.getString("participant_ids")
name = self.result_set.getString("participants")
if group_ids is not None:
group_ids = group_ids.split(",")
name = name.split(",")
recipients = []
for person_id, person_name in zip(group_ids, name):
recipients.append(Account.Address(person_id, person_name))
return recipients
return Account.Address(self.result_set.getString("conversation_id"), name)
return super(SkypeCallLogsParser, self).get_phone_number_to()
def get_call_direction(self):
direction = self.result_set.getInt("is_sender_me")
if direction == self._INCOMING_CALL_TYPE:
return self.INCOMING_CALL
if direction == self._OUTGOING_CALL_TYPE:
return self.OUTGOING_CALL
return super(SkypeCallLogsParser, self).get_call_direction()
def get_call_start_date_time(self):
return self.result_set.getLong("time") / 1000
def get_call_end_date_time(self):
start = self.get_call_start_date_time()
duration = self.result_set.getInt("duration") / 1000
return start + duration
class SkypeContactsParser(TskContactsParser):
"""
Extracts TSK_CONTACT information from the Skype database.
TSK_CONTACT fields that are not in the Skype database are given
a default value inherited from the super class.
"""
def __init__(self, contact_db):
super(SkypeContactsParser, self).__init__(contact_db.runQuery(
"""
SELECT entry_id,
"""+_format_user_name()+""" AS name
FROM person
"""
)
)
def get_account_name(self):
return self.result_set.getString("entry_id")
def get_contact_name(self):
return self.result_set.getString("name")
class SkypeMessagesParser(TskMessagesParser):
"""
Extract TSK_MESSAGE information from the Skype database.
TSK_CONTACT fields that are not in the Skype database are given
a default value inherited from the super class.
"""
def __init__(self, message_db):
"""
This query is very similar to the call logs query, the only difference is
it grabs more columns in the SELECT and excludes message_types which have
the call type value (3).
"""
super(SkypeMessagesParser, self).__init__(message_db.runQuery(
"""
SELECT contacts_list_with_groups.conversation_id,
contacts_list_with_groups.participant_ids,
contacts_list_with_groups.participants,
time,
content,
device_gallery_path,
is_sender_me,
person_id as sender_id,
sender_name.name AS sender_name
FROM (SELECT conversation_id,
Group_concat(person_id) AS participant_ids,
Group_concat("""+_format_user_name()+""") AS participants
FROM particiapnt AS PART
JOIN person AS P
ON PART.person_id = P.entry_id
GROUP BY conversation_id
UNION
SELECT entry_id as conversation_id,
NULL,
"""+_format_user_name()+""" AS participant
FROM person) AS contacts_list_with_groups
JOIN chatitem AS C
ON C.conversation_link = contacts_list_with_groups.conversation_id
JOIN (SELECT entry_id as id,
"""+_format_user_name()+""" AS name
FROM person
UNION
SELECT entry_id as id,
"""+_format_user_name()+""" AS name
FROM user) AS sender_name
ON sender_name.id = C.person_id
WHERE message_type != 3
"""
)
)
self._SKYPE_MESSAGE_TYPE = "Skype Message"
self._OUTGOING_MESSAGE_TYPE = 1
self._INCOMING_MESSAGE_TYPE = 0
def get_message_type(self):
return self._SKYPE_MESSAGE_TYPE
def get_phone_number_from(self):
if self.get_message_direction() == self.INCOMING:
return Account.Address(self.result_set.getString("sender_id"),
self.result_set.getString("sender_name"))
return super(SkypeMessagesParser, self).get_phone_number_from()
def get_message_direction(self):
direction = self.result_set.getInt("is_sender_me")
if direction == self._OUTGOING_MESSAGE_TYPE:
return self.OUTGOING
if direction == self._INCOMING_MESSAGE_TYPE:
return self.INCOMING
return super(SkypeMessagesParser, self).get_message_direction()
def get_phone_number_to(self):
if self.get_message_direction() == self.OUTGOING:
group_ids = self.result_set.getString("participant_ids")
names = self.result_set.getString("participants")
if group_ids is not None:
group_ids = group_ids.split(",")
names = names.split(",")
recipients = []
for participant_id, participant_name in zip(group_ids, names):
recipients.append(Account.Address(participant_id, participant_name))
return recipients
return Account.Address(self.result_set.getString("conversation_id"), names)
return super(SkypeMessagesParser, self).get_phone_number_to()
def get_message_date_time(self):
date = self.result_set.getLong("time")
return date / 1000
def get_message_text(self):
content = self.result_set.getString("content")
if content is not None:
file_path = self.result_set.getString("device_gallery_path")
#if a file name and file path are associated with a message, append it
if file_path is not None:
return general.appendAttachmentList(content, [file_path])
return content
return super(SkypeMessagesParser, self).get_message_text()
def get_thread_id(self):
group_ids = self.result_set.getString("participant_ids")
if group_ids is not None:
return self.result_set.getString("conversation_id")
return super(SkypeMessagesParser, self).get_thread_id()
def _format_user_name():
"""
This CASE SQL statement is used in many queries to
format the names of users. For a user, there is a first_name
column and a last_name column. Some of these columns can be null
and our goal is to produce the cleanest data possible. In the event
that both the first and last name columns are null, we return the skype_id
which is stored in the database as 'entry_id'. Commas are removed from the name
so that we can concatenate names into a comma seperate list for group chats.
"""
return """
CASE
WHEN Ifnull(first_name, "") == "" AND Ifnull(last_name, "") == "" THEN entry_id
WHEN first_name is NULL THEN replace(last_name, ",", "")
WHEN last_name is NULL THEN replace(first_name, ",", "")
ELSE replace(first_name, ",", "") || " " || replace(last_name, ",", "")
END
"""

View File

@ -118,7 +118,7 @@ class TangoMessageAnalyzer(general.AndroidComponentAnalyzer):
try:
# index the artifact for keyword search
blackboard = Case.getCurrentCase().getSleuthkitCase().getBlackboard()
blackboard.postArtifact(artifact, MODULE_NAME)
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())

View File

@ -0,0 +1,376 @@
"""
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 ViberAnalyzer(general.AndroidComponentAnalyzer):
"""
Parses the Viber App databases for TSK contacts, message
and calllog artifacts.
The Viber v11.5.0 database structure is as follows:
- People can take part in N conversation(s). A conversation can have M
members and messages are exchanged in a conversation.
- Viber has a conversation table, a participant table (the people/members in the above
analogy) and a messages table.
- Each row of the participants table maps a person to a conversation_id
- Each row in the messages table has a from participant id and a conversation id.
"""
def __init__(self):
self._logger = Logger.getLogger(self.__class__.__name__)
self._VIBER_PACKAGE_NAME = "com.viber.voip"
self._PARSER_NAME = "Viber Parser"
self._VERSION = "11.5.0"
def analyze(self, dataSource, fileManager, context):
"""
Extract, Transform and Load all messages, contacts and
calllogs from the Viber databases.
"""
try:
contact_and_calllog_dbs = AppSQLiteDB.findAppDatabases(dataSource,
"viber_data", True, self._VIBER_PACKAGE_NAME)
message_dbs = AppSQLiteDB.findAppDatabases(dataSource,
"viber_messages", True, self._VIBER_PACKAGE_NAME)
#Extract TSK_CONTACT and TSK_CALLLOG information
for contact_and_calllog_db in contact_and_calllog_dbs:
current_case = Case.getCurrentCaseThrows()
helper = CommunicationArtifactsHelper(
current_case.getSleuthkitCase(), self._PARSER_NAME,
contact_and_calllog_db.getDBFile(), Account.Type.VIBER)
self.parse_contacts(contact_and_calllog_db, helper)
self.parse_calllogs(contact_and_calllog_db, helper)
#Extract TSK_MESSAGE information
for message_db in message_dbs:
current_case = Case.getCurrentCaseThrows()
helper = CommunicationArtifactsHelper(
current_case.getSleuthkitCase(), self._PARSER_NAME,
message_db.getDBFile(), Account.Type.VIBER)
self.parse_messages(message_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 message_db in message_dbs:
message_db.close()
for contact_and_calllog_db in contact_and_calllog_dbs:
contact_and_calllog_db.close()
def parse_contacts(self, contacts_db, helper):
try:
contacts_parser = ViberContactsParser(contacts_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:
self._logger.log(Level.WARNING, "Error querying the viber database for contacts.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE,
"Error adding viber contacts artifact to case database.", ex)
self._logger.log(Level.SEVERE, traceback.format_exc())
except BlackboardException as ex:
self._logger.log(Level.WARNING,
"Error posting viber contacts artifact to the blackboard.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
def parse_calllogs(self, calllogs_db, helper):
try:
calllog_parser = ViberCallLogsParser(calllogs_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 querying the viber database for calllogs.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE,
"Error adding viber calllogs artifact to case database.", ex)
self._logger.log(Level.SEVERE, traceback.format_exc())
except BlackboardException as ex:
self._logger.log(Level.WARNING,
"Error posting viber calllogs artifact to the blackboard.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
def parse_messages(self, messages_db, helper):
try:
messages_parser = ViberMessagesParser(messages_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:
self._logger.log(Level.WARNING, "Error querying the viber database for messages.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE,
"Error adding viber messages artifact to case database.", ex)
self._logger.log(Level.SEVERE, traceback.format_exc())
except BlackboardException as ex:
self._logger.log(Level.WARNING,
"Error posting viber messages artifact to the blackboard.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
class ViberCallLogsParser(TskCallLogsParser):
"""
Extracts TSK_CALLLOG information from the Viber database.
TSK_CALLLOG fields that are not in the Viber database are given
a default value inherited from the super class.
"""
def __init__(self, calllog_db):
super(ViberCallLogsParser, self).__init__(calllog_db.runQuery(
"""
SELECT C.canonized_number AS number,
C.type AS direction,
C.duration AS seconds,
C.date AS start_time,
C.viber_call_type AS call_type
FROM calls AS C
"""
)
)
self._OUTGOING_CALL_TYPE = 2
self._INCOMING_CALL_TYPE = 1
self._MISSED_CALL_TYPE = 3
self._AUDIO_CALL_TYPE = 1
self._VIDEO_CALL_TYPE = 4
def get_phone_number_from(self):
if self.get_call_direction() == self.INCOMING_CALL:
return Account.Address(self.result_set.getString("number"),
self.result_set.getString("number"))
#Give default value if the call is outgoing,
#the device's # is not stored in the database.
return super(ViberCallLogsParser, self).get_phone_number_from()
def get_phone_number_to(self):
if self.get_call_direction() == self.OUTGOING_CALL:
return Account.Address(self.result_set.getString("number"),
self.result_set.getString("number"))
#Give default value if the call is incoming,
#the device's # is not stored in the database.
return super(ViberCallLogsParser, self).get_phone_number_to()
def get_call_direction(self):
direction = self.result_set.getInt("direction")
if direction == self._INCOMING_CALL_TYPE or direction == self._MISSED_CALL_TYPE:
return self.INCOMING_CALL
return self.OUTGOING_CALL
def get_call_start_date_time(self):
return self.result_set.getLong("start_time") / 1000
def get_call_end_date_time(self):
start_time = self.get_call_start_date_time()
duration = self.result_set.getLong("seconds")
return start_time + duration
def get_call_type(self):
call_type = self.result_set.getInt("call_type")
if call_type == self._AUDIO_CALL_TYPE:
return self.AUDIO_CALL
if call_type == self._VIDEO_CALL_TYPE:
return self.VIDEO_CALL
return super(ViberCallLogsParser, self).get_call_type()
class ViberContactsParser(TskContactsParser):
"""
Extracts TSK_CONTACT information from the Viber database.
TSK_CONTACT fields that are not in the Viber database are given
a default value inherited from the super class.
"""
def __init__(self, contact_db):
super(ViberContactsParser, self).__init__(contact_db.runQuery(
"""
SELECT C.display_name AS name,
D.data2 AS number
FROM phonebookcontact AS C
JOIN phonebookdata AS D
ON C._id = D.contact_id
"""
)
)
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 ViberMessagesParser(TskMessagesParser):
"""
Extract TSK_MESSAGE information from the Viber database.
TSK_CONTACT fields that are not in the Viber database are given
a default value inherited from the super class.
"""
def __init__(self, message_db):
"""
The query below does the following:
- The first two inner joins on participants and participants_info build
the 1 to many (M) mappings between the sender and the recipients for each
conversation_id. If a and b do private messaging, then 2 rows in the result
will be a -> b and b -> a.
If a, b, c, d are in a group, then 4 rows containing a -> b,c,d. b -> a,c,d. etc.
Participants_info is needed to get phone numbers.
- The result of the above step is a look up table for each message. Joining this result
onto the messages table lets us know which participant a message originated from and
everyone else that received it.
"""
super(ViberMessagesParser, self).__init__(message_db.runQuery(
"""
SELECT convo_participants.from_number AS from_number,
convo_participants.recipients AS recipients,
M.conversation_id AS thread_id,
M.body AS msg_content,
M.send_type AS direction,
M.msg_date AS msg_date,
M.unread AS read_status
FROM (SELECT *,
group_concat(TO_RESULT.number) AS recipients
FROM (SELECT P._id AS FROM_ID,
P.conversation_id,
PI.number AS FROM_NUMBER
FROM participants AS P
JOIN participants_info AS PI
ON P.participant_info_id = PI._id) AS FROM_RESULT
JOIN (SELECT P._id AS TO_ID,
P.conversation_id,
PI.number
FROM participants AS P
JOIN participants_info AS PI
ON P.participant_info_id = PI._id) AS TO_RESULT
ON FROM_RESULT.from_id != TO_RESULT.to_id
AND FROM_RESULT.conversation_id = TO_RESULT.conversation_id
GROUP BY FROM_RESULT.from_id) AS convo_participants
JOIN messages AS M
ON M.participant_id = convo_participants.from_id
AND M.conversation_id = convo_participants.conversation_id
"""
)
)
self._VIBER_MESSAGE_TYPE = "Viber Message"
self._INCOMING_MESSAGE_TYPE = 0
self._OUTGOING_MESSAGE_TYPE = 1
def get_message_type(self):
return self._VIBER_MESSAGE_TYPE
def get_phone_number_from(self):
return Account.Address(self.result_set.getString("from_number"),
self.result_set.getString("from_number"))
def get_message_direction(self):
direction = self.result_set.getInt("direction")
if direction == self._INCOMING_MESSAGE_TYPE:
return self.INCOMING
return self.OUTGOING
def get_phone_number_to(self):
recipients = []
for token in self.result_set.getString("recipients").split(","):
recipients.append(Account.Address(token, token))
return recipients
def get_message_date_time(self):
#transform from ms to seconds
return self.result_set.getLong("msg_date") / 1000
def get_message_read_status(self):
if self.get_message_direction() == self.INCOMING:
if self.result_set.getInt("read_status") == 0:
return self.READ
else:
return self.UNREAD
return super(ViberMessagesParser, self).get_message_read_status()
def get_message_text(self):
return self.result_set.getString("msg_content")
def get_thread_id(self):
return str(self.result_set.getInt("thread_id"))

View File

@ -379,8 +379,7 @@ class WhatsAppMessagesParser(TskMessagesParser):
M.timestamp AS send_timestamp,
M.received_timestamp,
M.remote_resource AS group_sender,
M.media_url As attachment,
M.media_mime_type as attachment_mimetype
M.media_url As attachment
FROM (SELECT jid,
recipients
FROM wadb.wa_contacts AS WC
@ -449,9 +448,6 @@ class WhatsAppMessagesParser(TskMessagesParser):
message = self.result_set.getString("content")
attachment = self.result_set.getString("attachment")
if attachment is not None:
mime_type = self.result_set.getString("attachment_mimetype")
if mime_type is not None:
attachment += "\nMIME type: " + mime_type
return general.appendAttachmentList(message, [attachment])
return message

View File

@ -0,0 +1,136 @@
"""
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.casemodule import NoCurrentCaseException
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.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
import traceback
import general
"""
Finds the SQLite DB for Xender, parses the DB for contacts & messages,
and adds artifacts to the case.
"""
class XenderAnalyzer(general.AndroidComponentAnalyzer):
def __init__(self):
self._logger = Logger.getLogger(self.__class__.__name__)
self._PACKAGE_NAME = "cn.xender"
self._MODULE_NAME = "Xender Analyzer"
self._MESSAGE_TYPE = "Xender Message"
self._VERSION = "4.6.5"
def analyze(self, dataSource, fileManager, context):
selfAccountAddress = None
transactionDbs = AppSQLiteDB.findAppDatabases(dataSource, "trans-history-db", True, self._PACKAGE_NAME)
for transactionDb in transactionDbs:
try:
current_case = Case.getCurrentCaseThrows()
# get the profile with connection_times 0, that's the self account.
profilesResultSet = transactionDb.runQuery("SELECT device_id, nick_name FROM profile WHERE connect_times = 0")
if profilesResultSet:
while profilesResultSet.next():
if not selfAccountAddress:
selfAccountAddress = Account.Address(profilesResultSet.getString("device_id"), profilesResultSet.getString("nick_name"))
# create artifacts helper
if selfAccountAddress is not None:
transactionDbHelper = CommunicationArtifactsHelper(current_case.getSleuthkitCase(),
self._MODULE_NAME, transactionDb.getDBFile(),
Account.Type.XENDER, Account.Type.XENDER, selfAccountAddress )
else:
transactionDbHelper = CommunicationArtifactsHelper(current_case.getSleuthkitCase(),
self._MODULE_NAME, transactionDb.getDBFile(),
Account.Type.XENDER)
queryString = "SELECT f_path, f_display_name, f_size_str, f_create_time, c_direction, c_session_id, s_name, s_device_id, r_name, r_device_id FROM new_history "
messagesResultSet = transactionDb.runQuery(queryString)
if messagesResultSet is not None:
while messagesResultSet.next():
direction = CommunicationDirection.UNKNOWN
fromAddress = None
toAdddress = None
if (messagesResultSet.getInt("c_direction") == 1):
direction = CommunicationDirection.OUTGOING
toAddress = Account.Address(messagesResultSet.getString("r_device_id"), messagesResultSet.getString("r_name"))
else:
direction = CommunicationDirection.INCOMING
fromAddress = Account.Address(messagesResultSet.getString("s_device_id"), messagesResultSet.getString("s_name"))
msgBody = "" # there is no body.
attachments = [messagesResultSet.getString("f_path")]
msgBody = general.appendAttachmentList(msgBody, attachments)
timeStamp = messagesResultSet.getLong("f_create_time") / 1000
messageArtifact = transactionDbHelper.addMessage(
self._MESSAGE_TYPE,
direction,
fromAddress,
toAddress,
timeStamp,
MessageReadStatus.UNKNOWN,
None, # subject
msgBody,
messagesResultSet.getString("c_session_id") )
# TBD: add the file as attachment ??
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query result for profiles", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to create Xender 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:
transactionDb.close()

View File

@ -0,0 +1,124 @@
"""
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.casemodule import NoCurrentCaseException
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.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
import traceback
import general
"""
Finds the SQLite DB for Zapya, parses the DB for contacts & messages,
and adds artifacts to the case.
"""
class ZapyaAnalyzer(general.AndroidComponentAnalyzer):
def __init__(self):
self._logger = Logger.getLogger(self.__class__.__name__)
self._PACKAGE_NAME = "com.dewmobile.kuaiya.play"
self._MODULE_NAME = "Zapya Analyzer"
self._MESSAGE_TYPE = "Zapya Message"
self._VERSION = "5.8.3"
def analyze(self, dataSource, fileManager, context):
transferDbs = AppSQLiteDB.findAppDatabases(dataSource, "transfer20.db", True, self._PACKAGE_NAME)
for transferDb in transferDbs:
try:
current_case = Case.getCurrentCaseThrows()
#
transferDbHelper = CommunicationArtifactsHelper(current_case.getSleuthkitCase(),
self._MODULE_NAME, transferDb.getDBFile(),
Account.Type.ZAPYA)
queryString = "SELECT device, name, direction, createtime, path, title FROM transfer"
transfersResultSet = transferDb.runQuery(queryString)
if transfersResultSet is not None:
while transfersResultSet.next():
direction = CommunicationDirection.UNKNOWN
fromAddress = None
toAddress = None
if (transfersResultSet.getInt("direction") == 1):
direction = CommunicationDirection.OUTGOING
toAddress = Account.Address(transfersResultSet.getString("device"), transfersResultSet.getString("name") )
else:
direction = CommunicationDirection.INCOMING
fromAddress = Account.Address(transfersResultSet.getString("device"), transfersResultSet.getString("name") )
msgBody = "" # there is no body.
attachments = [transfersResultSet.getString("path")]
msgBody = general.appendAttachmentList(msgBody, attachments)
timeStamp = transfersResultSet.getLong("createtime") / 1000
messageArtifact = transferDbHelper.addMessage(
self._MESSAGE_TYPE,
direction,
fromAddress,
toAddress,
timeStamp,
MessageReadStatus.UNKNOWN,
None, # subject
msgBody,
None ) # thread id
# TBD: add the file as attachment ??
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query result for transfer", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to create Zapya 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:
transferDb.close()

View File

@ -1141,7 +1141,9 @@ class ExtractRegistry extends Extract {
line = bufferedReader.readLine();
while (line != null && !line.isEmpty()) {
entry = getSAMKeyValue(line);
if (entry != null) {
userInfo.put(entry.getKey(), entry.getValue());
}
line = bufferedReader.readLine();
}
users.add(userInfo);
@ -1207,16 +1209,20 @@ class ExtractRegistry extends Extract {
* @throws TskCoreException
*/
private BlackboardArtifact.Type getShellBagArtifact() throws TskCoreException {
if (shellBagArtifactType == null) {
shellBagArtifactType = tskCase.getArtifactType(SHELLBAG_ARTIFACT_NAME);
if(shellBagArtifactType == null) {
try {
tskCase.addBlackboardArtifactType(SHELLBAG_ARTIFACT_NAME, Bundle.Shellbag_Artifact_Display_Name()); //NON-NLS
} catch (TskDataException ex) {
// Artifact already exists
logger.log(Level.INFO, String.format("%s may have already been defined for this case", SHELLBAG_ARTIFACT_NAME), ex);
logger.log(Level.INFO, String.format("%s may have already been defined for this case", SHELLBAG_ARTIFACT_NAME));
}
shellBagArtifactType = tskCase.getArtifactType(SHELLBAG_ARTIFACT_NAME);
}
}
return shellBagArtifactType;
}