mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
Merge pull request #5247 from sleuthkit/release-4.13.0
Merge release-4.13.0 branch into develop branch
This commit is contained in:
commit
c36620e876
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/commandlineingest/Bundle.properties" key="CommandLineIngestSettingsPanel.jLabelDescription.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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, "{key}")"/>
|
||||
</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"/>
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
if (status != null) {
|
||||
errorList.add(status);
|
||||
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
|
||||
return;
|
||||
// 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(userFile.getName()));
|
||||
}
|
||||
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingToReport(USERS_TXT));
|
||||
|
||||
// Get all VHD files in the dest directory
|
||||
List<String> imagePaths = new ArrayList<>();
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
@ -106,7 +113,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
|
||||
public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
|
||||
try {
|
||||
validateSettings();
|
||||
this.context = context;
|
||||
this.context = context;
|
||||
blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
|
||||
|
||||
fileTypeDetector = new 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
|
||||
@ -130,12 +138,12 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
|
||||
* verify the file hasn't been deleted.
|
||||
*/
|
||||
if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)
|
||||
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)
|
||||
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR)
|
||||
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR)
|
||||
&& (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed)
|
||||
&& !file.getKnown().equals(TskData.FileKnown.KNOWN)
|
||||
&& !file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC)) {
|
||||
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)
|
||||
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR)
|
||||
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR)
|
||||
&& (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed)
|
||||
&& !file.getKnown().equals(TskData.FileKnown.KNOWN)
|
||||
&& !file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC)) {
|
||||
/*
|
||||
* Is the file in FILE_IGNORE_LIST?
|
||||
*/
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
|
@ -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 {
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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="<HashDb>"/>
|
||||
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="<String>"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="configureHashDatabasesButton">
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
256
InternalPythonModules/android/fbmessenger.py
Normal file
256
InternalPythonModules/android/fbmessenger.py
Normal 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)
|
||||
|
||||
|
93
InternalPythonModules/android/installedapps.py
Normal file
93
InternalPythonModules/android/installedapps.py
Normal 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()
|
||||
|
@ -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))
|
||||
|
||||
|
240
InternalPythonModules/android/operabrowser.py
Normal file
240
InternalPythonModules/android/operabrowser.py
Normal 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)
|
||||
|
108
InternalPythonModules/android/oruxmaps.py
Normal file
108
InternalPythonModules/android/oruxmaps.py
Normal 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()
|
266
InternalPythonModules/android/sbrowser.py
Normal file
266
InternalPythonModules/android/sbrowser.py
Normal 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)
|
||||
|
123
InternalPythonModules/android/shareit.py
Normal file
123
InternalPythonModules/android/shareit.py
Normal 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()
|
||||
|
||||
|
||||
|
505
InternalPythonModules/android/skype.py
Normal file
505
InternalPythonModules/android/skype.py
Normal 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
|
||||
"""
|
||||
|
||||
|
@ -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())
|
||||
|
376
InternalPythonModules/android/viber.py
Normal file
376
InternalPythonModules/android/viber.py
Normal 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"))
|
@ -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
|
||||
|
||||
|
136
InternalPythonModules/android/xender.py
Normal file
136
InternalPythonModules/android/xender.py
Normal 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()
|
||||
|
||||
|
||||
|
124
InternalPythonModules/android/zapya.py
Normal file
124
InternalPythonModules/android/zapya.py
Normal 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()
|
||||
|
||||
|
||||
|
@ -1141,7 +1141,9 @@ class ExtractRegistry extends Extract {
|
||||
line = bufferedReader.readLine();
|
||||
while (line != null && !line.isEmpty()) {
|
||||
entry = getSAMKeyValue(line);
|
||||
userInfo.put(entry.getKey(), entry.getValue());
|
||||
if (entry != null) {
|
||||
userInfo.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
line = bufferedReader.readLine();
|
||||
}
|
||||
users.add(userInfo);
|
||||
@ -1208,14 +1210,18 @@ class ExtractRegistry extends Extract {
|
||||
*/
|
||||
private BlackboardArtifact.Type getShellBagArtifact() throws TskCoreException {
|
||||
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);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
shellBagArtifactType = tskCase.getArtifactType(SHELLBAG_ARTIFACT_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
return shellBagArtifactType;
|
||||
|
Loading…
x
Reference in New Issue
Block a user