diff --git a/.gitignore b/.gitignore index 340b5a525f..418d944b1e 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,6 @@ hs_err_pid*.log /RecentActivity/release/ /CentralRepository/release/ +*.img +*.vhd +*.E01 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..90b51996f9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +language: java +sudo: required +dist: trusty +os: + - linux +env: + global: + - TSK_HOME=$TRAVIS_BUILD_DIR/sleuthkit/sleuthkit +jdk: + - oraclejdk8 +before_install: + - git clone https://github.com/sleuthkit/sleuthkit.git sleuthkit/sleuthkit +install: + - sudo apt-get install testdisk + - cd sleuthkit/sleuthkit + - sh install-sleuthkit.sh +script: + - cd $TRAVIS_BUILD_DIR/ + - ant build + - cd Core/ + - xvfb-run ant test + diff --git a/Core/build.xml b/Core/build.xml index c7669a0cd7..3555ff0711 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -12,7 +12,7 @@ - + @@ -83,6 +83,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index b929956b1c..37cb78a51e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -176,9 +176,15 @@ public abstract class AbstractSqlEamDb implements EamDb { * @returns New Case class with populated database ID */ @Override - public CorrelationCase newCase(CorrelationCase eamCase) throws EamDbException { - Connection conn = connect(); + public synchronized CorrelationCase newCase(CorrelationCase eamCase) throws EamDbException { + + // check if there is already an existing CorrelationCase for this Case + CorrelationCase cRCase = getCaseByUUID(eamCase.getCaseUUID()); + if (cRCase != null) { + return cRCase; + } + Connection conn = connect(); PreparedStatement preparedStatement = null; String sql = "INSERT INTO cases(case_uid, org_id, case_name, creation_date, case_number, " diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java index 1d2a1a2a5b..98cee5bc59 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java @@ -39,7 +39,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.FileTypeViewer; @ServiceProvider(service = DataContentViewer.class, position = 3) public class FileViewer extends javax.swing.JPanel implements DataContentViewer { - private static final int CONFIDENCE_LEVEL = 7; + private static final int CONFIDENCE_LEVEL = 5; private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(FileViewer.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java index e99cae34f6..ce2721e647 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java @@ -503,7 +503,6 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont rtfbodyTextPane.setText(""); htmlbodyTextPane.setText(""); textbodyTextArea.setText(""); - drp.setNode(null); showImagesToggleButton.setEnabled(false); msgbodyTabbedPane.setEnabled(false); } @@ -546,7 +545,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont @Override public int isPreferred(Node node) { if (isSupported(node)) { - return 6; + return 7; } return 0; } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index b9e71ccdf8..4a4ee95a90 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.contentviewers; import java.awt.BorderLayout; import java.awt.Component; +import java.awt.Cursor; import java.io.File; import java.io.IOException; import java.sql.Connection; @@ -36,12 +37,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.TreeMap; -import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.JComboBox; -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; @@ -71,7 +68,6 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { private Connection connection; private int numRows; // num of rows in the selected table private int currPage = 0; // curr page of rows being displayed - private SwingWorker worker; /** * Constructs a file content viewer for SQLite database files. @@ -213,6 +209,7 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { }// //GEN-END:initComponents private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); currPage++; if (currPage * ROWS_PER_PAGE > numRows) { nextPageButton.setEnabled(false); @@ -223,9 +220,12 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { // read and display a page of rows String tableName = (String) this.tablesDropdownList.getSelectedItem(); readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); }//GEN-LAST:event_nextPageButtonActionPerformed private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed + + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); currPage--; if (currPage == 1) { prevPageButton.setEnabled(false); @@ -236,6 +236,7 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { // read and display a page of rows String tableName = (String) this.tablesDropdownList.getSelectedItem(); readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); }//GEN-LAST:event_prevPageButtonActionPerformed private void tablesDropdownListActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tablesDropdownListActionPerformed @@ -244,7 +245,9 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { if (null == tableName) { return; } + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); selectTable(tableName); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); }//GEN-LAST:event_tablesDropdownListActionPerformed @@ -269,8 +272,10 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { @Override public void setFile(AbstractFile file) { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); sqliteDbFile = file; processSQLiteFile(); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } @Override @@ -293,12 +298,6 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { logger.log(Level.SEVERE, "Failed to close DB connection to file.", ex); //NON-NLS } } - - // delete last temp file - if (null != tmpDbFile) { - tmpDbFile.delete(); - tmpDbFile = null; - } sqliteDbFile = null; } @@ -315,68 +314,55 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { "SQLiteViewer.errorMessage.failedToinitJDBCDriver=The JDBC driver for SQLite could not be loaded.", "# {0} - exception message", "SQLiteViewer.errorMessage.unexpectedError=An unexpected error occurred:\n{0).",}) private void processSQLiteFile() { - SwingUtilities.invokeLater(() -> { - tablesDropdownList.removeAllItems(); - }); - new SwingWorker, Void>() { - @Override - protected Map doInBackground() throws NoCurrentCaseException, TskCoreException, IOException, SQLException, ClassNotFoundException { - // Copy the file to temp folder - String tmpDBPathName = Case.getOpenCase().getTempDirectory() + File.separator + sqliteDbFile.getName(); - tmpDbFile = new File(tmpDBPathName); + + tablesDropdownList.removeAllItems(); + + // Copy the file to temp folder + String tmpDBPathName; + try { + tmpDBPathName = Case.getOpenCase().getTempDirectory() + File.separator + sqliteDbFile.getName(); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Current case has been closed", ex); //NON-NLS + MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_noCurrentCase()); + return; + } + + tmpDbFile = new File(tmpDBPathName); + if (! tmpDbFile.exists()) { + try { ContentUtils.writeToFile(sqliteDbFile, tmpDbFile); // Look for any meta files associated with this DB - WAL, SHM, etc. findAndCopySQLiteMetaFile(sqliteDbFile, sqliteDbFile.getName() + "-wal"); findAndCopySQLiteMetaFile(sqliteDbFile, sqliteDbFile.getName() + "-shm"); - - // Load the SQLite JDBC driver, if necessary. - Class.forName("org.sqlite.JDBC"); //NON-NLS - connection = DriverManager.getConnection("jdbc:sqlite:" + tmpDBPathName); //NON-NLS - - // Query the file for the table names and schemas. - return getTables(); + } catch (IOException | NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Failed to create temp copy of DB file '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS + MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_failedToExtractFile()); + return; } + } + + try { + // Load the SQLite JDBC driver, if necessary. + Class.forName("org.sqlite.JDBC"); //NON-NLS + connection = DriverManager.getConnection("jdbc:sqlite:" + tmpDBPathName); //NON-NLS - @Override - protected void done() { - super.done(); - try { - Map dbTablesMap = get(); - if (dbTablesMap.isEmpty()) { - tablesDropdownList.addItem(Bundle.SQLiteViewer_comboBox_noTableEntry()); - tablesDropdownList.setEnabled(false); - } else { - dbTablesMap.keySet().forEach((tableName) -> { - tablesDropdownList.addItem(tableName); - }); - } - } catch (InterruptedException ex) { - logger.log(Level.SEVERE, String.format("Interrupted while opening SQLite database file '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS - MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_interrupted()); - } catch (ExecutionException ex) { - String errorMessage; - Throwable cause = ex.getCause(); - if (cause instanceof NoCurrentCaseException) { - logger.log(Level.SEVERE, "Current case has been closed", ex); //NON-NLS - errorMessage = Bundle.SQLiteViewer_errorMessage_noCurrentCase(); - } else if (cause instanceof TskCoreException || cause instanceof IOException) { - logger.log(Level.SEVERE, String.format("Failed to create temp copy of DB file '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS - errorMessage = Bundle.SQLiteViewer_errorMessage_failedToExtractFile(); - } else if (cause instanceof SQLException) { - logger.log(Level.SEVERE, String.format("Failed to get tables from DB file '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS - errorMessage = Bundle.SQLiteViewer_errorMessage_failedToQueryDatabase(); - } else if (cause instanceof ClassNotFoundException) { - logger.log(Level.SEVERE, String.format("Failed to initialize JDBC SQLite '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS - errorMessage = Bundle.SQLiteViewer_errorMessage_failedToinitJDBCDriver(); - } else { - logger.log(Level.SEVERE, String.format("Unexpected exception while processing DB file '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS - errorMessage = Bundle.SQLiteViewer_errorMessage_unexpectedError(cause.getLocalizedMessage()); - } - MessageNotifyUtil.Message.error(errorMessage); - } + Map dbTablesMap = getTables(); + if (dbTablesMap.isEmpty()) { + tablesDropdownList.addItem(Bundle.SQLiteViewer_comboBox_noTableEntry()); + tablesDropdownList.setEnabled(false); + } else { + dbTablesMap.keySet().forEach((tableName) -> { + tablesDropdownList.addItem(tableName); + }); } - }.execute(); + } catch (ClassNotFoundException ex) { + logger.log(Level.SEVERE, String.format("Failed to initialize JDBC SQLite '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS + MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_failedToinitJDBCDriver()); + } catch (SQLException ex) { + logger.log(Level.SEVERE, String.format("Failed to get tables from DB file '%s' (objId=%d)", sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS + MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_failedToQueryDatabase()); + } } /** @@ -436,143 +422,55 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { "SQLiteViewer.selectTable.errorText=Error getting row count for table: {0}" }) private void selectTable(String tableName) { - if (worker != null && !worker.isDone()) { - worker.cancel(false); - worker = null; + + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery( + "SELECT count (*) as count FROM " + tableName)) { //NON-NLS{ + + numRows = resultSet.getInt("count"); + numEntriesField.setText(numRows + " entries"); + + currPage = 1; + currPageLabel.setText(Integer.toString(currPage)); + numPagesLabel.setText(Integer.toString((numRows / ROWS_PER_PAGE) + 1)); + + prevPageButton.setEnabled(false); + + if (numRows > 0) { + nextPageButton.setEnabled(((numRows > ROWS_PER_PAGE))); + readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE); + } else { + nextPageButton.setEnabled(false); + selectedTableView.setupTable(Collections.emptyList()); + } + + } catch (SQLException ex) { + logger.log(Level.SEVERE, String.format("Failed to load table %s from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS + MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_selectTable_errorText(tableName)); } - - worker = new SwingWorker() { - @Override - protected Integer doInBackground() throws Exception { - - Statement statement = null; - ResultSet resultSet = null; - try { - statement = connection.createStatement(); - resultSet = statement.executeQuery( - "SELECT count (*) as count FROM " + tableName); //NON-NLS - - return resultSet.getInt("count"); - } catch (SQLException ex) { - throw ex; - } finally { - if (null != resultSet) { - resultSet.close(); - } - if (null != statement) { - statement.close(); - } - } - } - - @Override - protected void done() { - super.done(); - try { - - numRows = get(); - numEntriesField.setText(numRows + " entries"); - - currPage = 1; - currPageLabel.setText(Integer.toString(currPage)); - numPagesLabel.setText(Integer.toString((numRows / ROWS_PER_PAGE) + 1)); - - prevPageButton.setEnabled(false); - - if (numRows > 0) { - nextPageButton.setEnabled(((numRows > ROWS_PER_PAGE))); - readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE); - } else { - nextPageButton.setEnabled(false); - selectedTableView.setupTable(Collections.emptyList()); - } - - } catch (InterruptedException ex) { - logger.log(Level.SEVERE, "Interrupted while getting row count from table " + tableName, ex); //NON-NLS - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - ex.getMessage(), - Bundle.SQLiteViewer_selectTable_errorText(tableName), - JOptionPane.ERROR_MESSAGE); - } catch (ExecutionException ex) { - logger.log(Level.SEVERE, "Unexpected exception while getting row count from table " + tableName, ex); //NON-NLS - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - ex.getCause().getMessage(), - Bundle.SQLiteViewer_selectTable_errorText(tableName), - JOptionPane.ERROR_MESSAGE); - } - } - }; - worker.execute(); } @NbBundle.Messages({"# {0} - tableName", "SQLiteViewer.readTable.errorText=Error getting rows for table: {0}"}) private void readTable(String tableName, int startRow, int numRowsToRead) { - if (worker != null && !worker.isDone()) { - worker.cancel(false); - worker = null; + try ( + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery( + "SELECT * FROM " + tableName + + " LIMIT " + Integer.toString(numRowsToRead) + + " OFFSET " + Integer.toString(startRow - 1))) { + + ArrayList> rows = resultSetToArrayList(resultSet); + if (Objects.nonNull(rows)) { + selectedTableView.setupTable(rows); + } else { + selectedTableView.setupTable(Collections.emptyList()); + } + } catch (SQLException ex) { + logger.log(Level.SEVERE, String.format("Failed to read table %s from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS + MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_readTable_errorText(tableName)); } - - worker = new SwingWorker>, Void>() { - @Override - protected ArrayList> doInBackground() throws Exception { - - Statement statement = null; - ResultSet resultSet = null; - try { - statement = connection.createStatement(); - resultSet = statement.executeQuery( - "SELECT * FROM " + tableName - + " LIMIT " + Integer.toString(numRowsToRead) - + " OFFSET " + Integer.toString(startRow - 1) - ); //NON-NLS - - return resultSetToArrayList(resultSet); - } catch (SQLException ex) { - throw ex; - } finally { - if (null != resultSet) { - resultSet.close(); - } - if (null != statement) { - statement.close(); - } - } - } - - @Override - protected void done() { - - if (isCancelled()) { - return; - } - - super.done(); - try { - ArrayList> rows = get(); - if (Objects.nonNull(rows)) { - selectedTableView.setupTable(rows); - } else { - selectedTableView.setupTable(Collections.emptyList()); - } - } catch (InterruptedException ex) { - logger.log(Level.SEVERE, "Interrupted while reading table " + tableName, ex); //NON-NLS - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - ex.getMessage(), - Bundle.SQLiteViewer_readTable_errorText(tableName), - JOptionPane.ERROR_MESSAGE); - } catch (ExecutionException ex) { - logger.log(Level.SEVERE, "Unexpected exception while reading table " + tableName, ex); //NON-NLS - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - ex.getCause().getMessage(), - Bundle.SQLiteViewer_readTable_errorText(tableName), - JOptionPane.ERROR_MESSAGE); - } - } - }; - - worker.execute(); } @NbBundle.Messages("SQLiteViewer.BlobNotShown.message=BLOB Data not shown") diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataContentViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataContentViewer.java index 2517bb9507..07a90d651b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataContentViewer.java @@ -85,19 +85,49 @@ public interface DataContentViewer { public boolean isSupported(Node node); /** - * Checks whether the given viewer is preferred for the Node. This is a bit - * subjective, but the idea is that Autopsy wants to display the most - * relevant tab. The more generic the viewer, the lower the return value - * should be. This will only be called on viewers that support the given - * node. - * - * @param node Node to check for preference - * - * @return an int (0-10) higher return means the viewer has higher priority - * 0 means not supported 1 to 2 means the module will display all - * file types (such as the hex viewer) 3-10 are prioritized by - * Content viewer developer. Modules that operate on very few file - * types should be towards 10. - */ + * Checks whether the given viewer is preferred for the Node. This is a bit + * subjective, but the idea is that Autopsy wants to display the most + * relevant tab. The more generic the viewer, the lower the return value + * should be. This will only be called on viewers that support the given + * node (i.e., isSupported() has already returned true). + * + * The following are some examples of the current levels in use. If the selected + * node is an artifact, the level may be determined by both the artifact and its + * associated file. + * + * Level 7 - Based on the artifact, if any, in the selected node and specific + * to an artifact type or types. Current content viewers that can return level 7 are + * the Messages tab (only supported for email and SMS) and the Indexed Text tab + * when the selected node is a Keyword Search hit. + * + * Level 6 - Based on the artifact, if any, in the selected node but not + * restricted to particular types. The current content viewer that can return level 6 + * is the Results tab. It returns this level for most artifact types, + * unless the associated file is assumed to be of greater interest (for example, + * a Hash Set Hit will not be level 6 because the file itself is of greater interest). + * + * Level 5 - Based on the file in the selected node and very specific to the file type. The current + * content viewer that will return level 5 is + * the Application tab, which supports media files (such as images) and + * certain types of databases. + * + * Level 4 - Based on the file in the selected node but fairly general. + * Currently this is the level returned by the Indexed Text tab if Keyword Search + * has been run (unless the node is a Keyword Search hit or a Credit Card account). + * This is the default tab for most files. + * + * Level 3 - Based on the artifact, if any, in the selected node where the + * artifact is thought to be of less interest than the associated file. This + * level is returned by the Results tab for artifacts like Hash Set Hits. + * + * Level 1 - Very general and should always be available. The Hex, Strings, and Metadata tabs + * are all this level + * + * Level 0 - For cases where the content viewer should never be displayed by default. + * + * @param node Node to check for preference + * + * @return an int (0-10) higher return means the viewer has higher priority + */ public int isPreferred(Node node); } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 535c88ce61..245fe1744d 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -24,7 +24,7 @@ DataContentViewerString.pageLabel2.text=Page # Product Information panel LBL_Description=
\n Product Version: {0} ({9})
Sleuth Kit Version: {7}
Netbeans RCP Build: {8}
Java: {1}; {2}
System: {3}; {4}; {5}
Userdir: {6}
Format_OperatingSystem_Value={0} version {1} running on {2} -LBL_Copyright=
Autopsy™ is a digital forensics platform based on The Sleuth Kit™ and other tools.
Copyright © 2003-2017.
+LBL_Copyright=
Autopsy™ is a digital forensics platform based on The Sleuth Kit™ and other tools.
Copyright © 2003-2018.
URL_ON_IMG=http://www.sleuthkit.org/ URL_ON_HELP=http://sleuthkit.org/autopsy/docs/user-docs/4.6.0/ FILE_FOR_LOCAL_HELP=file:/// diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java index 64ca23a4d0..48e84e6d79 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java @@ -477,7 +477,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID())) { return 3; } else { - return 5; + return 6; } } diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchFilter.java b/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchFilter.java index cf5d4f9e73..ff30916658 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchFilter.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchFilter.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,21 +19,31 @@ package org.sleuthkit.autopsy.filesearch; import java.awt.event.ActionListener; +import java.text.ParseException; +import java.util.Locale; import javax.swing.JComboBox; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; +import org.python.icu.text.NumberFormat; import org.sleuthkit.autopsy.filesearch.FileSearchFilter.FilterValidationException; /** - * - * @author pmartel + * Filter search size input. */ class SizeSearchFilter extends AbstractFileSearchFilter { + /** + * Instantiate a SizeSearchFilter object for a new SizeSearchPanel. + */ SizeSearchFilter() { this(new SizeSearchPanel()); } + /** + * Instantiate a SizeSearchFilter object for an existing SizeSearchPanel. + * + * @param component The SizeSearchPanel instance. + */ SizeSearchFilter(SizeSearchPanel component) { super(component); } @@ -53,6 +63,14 @@ class SizeSearchFilter extends AbstractFileSearchFilter { return "size " + operator + " " + size; //NON-NLS } + /** + * Get the comparison operator associated with the size comparison + * drop-down selection. + * + * @param compare The drop-down component. + * + * @return The operator. + */ private String compareComboBoxToOperator(JComboBox compare) { String compareSize = compare.getSelectedItem().toString(); @@ -75,20 +93,20 @@ class SizeSearchFilter extends AbstractFileSearchFilter { } @Override - @Messages ({ + @Messages({ "SizeSearchFilter.errorMessage.nonNegativeNumber=Input size data is a negative number.", "SizeSearchFilter.errorMessage.notANumber=Input size data is not a number." }) public boolean isValid() { String input = this.getComponent().getSizeTextField().getText(); - + try { - int inputInt = Integer.parseInt(input); + int inputInt = NumberFormat.getNumberInstance(Locale.US).parse(input).intValue(); if (inputInt < 0) { setLastError(Bundle.SizeSearchFilter_errorMessage_nonNegativeNumber()); return false; } - } catch (NumberFormatException | NullPointerException e) { + } catch (ParseException ex) { setLastError(Bundle.SizeSearchFilter_errorMessage_notANumber()); return false; } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java index 7bbee3a309..26e603e55b 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java @@ -410,11 +410,18 @@ final class IngestTasksScheduler { } /* - * Check if the file is a member of the file ingest filter that is being - * applied to the current run of ingest, checks if unallocated space - * should be processed inside call to fileIsMemberOf + * Ensures that all directories, files which are members of the ingest + * file filter, and unallocated blocks (when processUnallocated is + * enabled) all continue to be processed. AbstractFiles which do not + * meet one of these criteria will be skipped. + * + * An additional check to see if unallocated space should be processed + * is part of the FilesSet.fileIsMemberOf() method. + * + * This code may need to be updated when + * TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS comes into use by Autopsy. */ - if (!file.isDir() && task.getIngestJob().getFileIngestFilter().fileIsMemberOf(file) == null) { + if (!file.isDir() && !shouldBeCarved(task) && !fileAcceptedByFilter(task)) { return false; } @@ -462,6 +469,30 @@ final class IngestTasksScheduler { return true; } + /** + * Check whether or not a file should be carved for a data source ingest + * ingest job. + * + * @param task The file ingest task for the file. + * + * @return True or false. + */ + private static boolean shouldBeCarved(final FileIngestTask task) { + return task.getIngestJob().shouldProcessUnallocatedSpace() && task.getFile().getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS); + } + + /** + * Checks whether or not a file is accepted (passes) the file filter for a data + * source ingest job. + * + * @param task The file ingest task for the file. + * + * @return True or false. + */ + private static boolean fileAcceptedByFilter(final FileIngestTask task) { + return !(task.getIngestJob().getFileIngestFilter().fileIsMemberOf(task.getFile()) == null); + } + /** * Checks whether or not a collection of ingest tasks includes a task for a * given data source ingest job. diff --git a/Core/test/filter_test1.img b/Core/test/filter_test1.img deleted file mode 100755 index 69b6e21b6c..0000000000 Binary files a/Core/test/filter_test1.img and /dev/null differ diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java index 1a98aa7d07..54b7024790 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import static junit.framework.Assert.assertFalse; import junit.framework.TestCase; @@ -32,6 +33,7 @@ import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseDetails; import junit.framework.Test; import org.apache.commons.io.FileUtils; +import org.netbeans.junit.NbTestCase; import org.openide.util.Exceptions; import org.python.icu.impl.Assert; import org.sleuthkit.autopsy.casemodule.ImageDSProcessor; @@ -40,18 +42,23 @@ import org.sleuthkit.autopsy.casemodule.services.FileManager; import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory; +import org.sleuthkit.autopsy.modules.interestingitems.FilesSet; +import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule; +import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.MetaTypeCondition; +import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.ParentPathCondition; import org.sleuthkit.autopsy.testutils.DataSourceProcessorRunner; import org.sleuthkit.autopsy.testutils.DataSourceProcessorRunner.ProcessorCallback; import org.sleuthkit.autopsy.testutils.IngestJobRunner; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; -public class IngestFileFiltersTest extends TestCase { +public class IngestFileFiltersTest extends NbTestCase { private static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), "IngestFileFiltersTest"); private static final File CASE_DIR = new File(CASE_DIRECTORY_PATH.toString()); - private static final Path IMAGE_PATH = Paths.get("test/filter_test1.img"); + private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"filter_test1.img"); public static Test suite() { NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(IngestFileFiltersTest.class). @@ -60,6 +67,10 @@ public class IngestFileFiltersTest extends TestCase { return conf.suite(); } + public IngestFileFiltersTest(String name) { + super(name); + } + @Override public void setUp() { // Delete the test directory, if it exists @@ -82,7 +93,7 @@ public class IngestFileFiltersTest extends TestCase { } catch (CaseActionException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); - } + } assertTrue(CASE_DIR.exists()); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); try { @@ -116,28 +127,48 @@ public class IngestFileFiltersTest extends TestCase { assertFalse(CASE_DIR.exists()); } - public void testFileType() { + public void testBasicDir() { + HashMap rule = new HashMap<>(); + rule.put("Rule", new Rule("testFileType", null, new MetaTypeCondition(MetaTypeCondition.Type.FILES), new ParentPathCondition("dir1"), null, null, null)); + //Filter for dir1 and no unallocated space + FilesSet Files_Dirs_Unalloc_Ingest_Filter = new FilesSet("Filter", "Filter to find all files in dir1.", false, true, rule); + try { Case openCase = Case.getOpenCase(); - runIngestJob(openCase.getDataSources()); + runIngestJob(openCase.getDataSources(), Files_Dirs_Unalloc_Ingest_Filter); FileManager fileManager = openCase.getServices().getFileManager(); List results = fileManager.findFiles("file.jpg", "dir1"); String mimeType = results.get(0).getMIMEType(); assertEquals("image/jpeg", mimeType); + + results = fileManager.findFiles("%%"); + + for (AbstractFile file : results) { + //All files in dir1 should have MIME type, except '.' '..' and slack files + if (file.getParentPath().equalsIgnoreCase("/dir1/")) { + if (!(file.getName().equals(".") || file.getName().equals("..") || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.SLACK)) { + String errMsg = String.format("File %s (objId=%d) unexpectedly blocked by the file filter.", file.getName(), file.getId()); + assertTrue(errMsg, !(file.getMIMEType() == null || file.getMIMEType().isEmpty())); + } + } else { //All files not in dir1 shouldn't have MIME type + String errMsg = String.format("File %s (objId=%d) unexpectedly passed by the file filter.", file.getName(), file.getId()); + assertTrue(errMsg, file.getMIMEType() == null); + } + } } catch (NoCurrentCaseException | TskCoreException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } } - private void runIngestJob(List datasources) { + private void runIngestJob(List datasources, FilesSet filter) { FileTypeIdModuleFactory factory = new FileTypeIdModuleFactory(); IngestModuleIngestJobSettings settings = factory.getDefaultIngestJobSettings(); IngestModuleTemplate template = new IngestModuleTemplate(factory, settings); template.setEnabled(true); ArrayList templates = new ArrayList<>(); templates.add(template); - IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates); + IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates, filter); try { List errs = IngestJobRunner.runIngestJob(datasources, ingestJobSettings); assertEquals(0, errs.size()); @@ -146,4 +177,5 @@ public class IngestFileFiltersTest extends TestCase { Assert.fail(ex); } } -} + + } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/AddMemoryImageTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/AddMemoryImageTask.java index 57d14452b5..7490c3e7b1 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/AddMemoryImageTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/AddMemoryImageTask.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2011-2016 Basis Technology Corp. + * + * Copyright 2018 Basis Technology Corp. * Contact: carrier sleuthkit 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. @@ -23,80 +23,89 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.logging.Level; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; -import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Content; /* - * A runnable that adds a raw data source to a case database. + * A runnable that adds a memory image data source to a case database. */ final class AddMemoryImageTask implements Runnable { - private static final Logger logger = Logger.getLogger(AddMemoryImageTask.class.getName()); + private final static Logger logger = Logger.getLogger(AddMemoryImageTask.class.getName()); private final String deviceId; - private final String imageFilePath; + private final String memoryImagePath; private final String timeZone; - private final List pluginsToRun; + private final List pluginsToRun; private final DataSourceProcessorProgressMonitor progressMonitor; private final DataSourceProcessorCallback callback; - private VolatilityProcessor volatilityProcessor = null; - private boolean isCancelled = false; - + private volatile VolatilityProcessor volatilityProcessor; + private volatile boolean isCancelled; + /** - * Constructs a runnable that adds a raw data source to a case database. + * Constructs a runnable that adds a memory image to a case database. * - * @param deviceId An ASCII-printable identifier for the - * device associated with the data source - * that is intended to be unique across - * multiple cases (e.g., a UUID). - * @param imageFilePath Path to a Raw data source file. - * @param timeZone The time zone to use when processing dates - * and times for the image, obtained from - * java.util.TimeZone.getID. - * @param breakupChunks 2GB or not breakup. - * @param progressMonitor Progress monitor for reporting - * progressMonitor during processing. - * @param callback Callback to call when processing is done. + * @param deviceId An ASCII-printable identifier for the device + * associated with the data source that is intended + * to be unique across multiple cases (e.g., a UUID). + * @param memoryImagePath Path to the memory image file. + * @param pluginsToRun The Volatility plugins to run. + * @param timeZone The time zone to use when processing dates and + * times for the image, obtained from + * java.util.TimeZone.getID. + * @param progressMonitor Progress monitor for reporting progressMonitor + * during processing. + * @param callback Callback to call when processing is done. */ - AddMemoryImageTask(String deviceId, String imageFilePath, List PluginsToRun, String timeZone, long chunkSize, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + AddMemoryImageTask(String deviceId, String memoryImagePath, List pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { this.deviceId = deviceId; - this.imageFilePath = imageFilePath; - this.pluginsToRun = PluginsToRun; + this.memoryImagePath = memoryImagePath; + this.pluginsToRun = pluginsToRun; this.timeZone = timeZone; this.callback = callback; this.progressMonitor = progressMonitor; } /** - * Adds a raw data source to a case database. + * Adds a memory image data source to a case database. */ - @Override + @Messages({ + "# {0} - exception message", + "AddMemoryImageTask_errorMessage_criticalException= Critical error: {0}", + }) + @Override public void run() { + if (isCancelled) { + return; + } progressMonitor.setIndeterminate(true); progressMonitor.setProgress(0); + List dataSources = new ArrayList<>(); List errorMessages = new ArrayList<>(); boolean criticalErrorOccurred = false; - Image dataSource = addImageToCase(errorMessages); - if (dataSource == null) { + try { + Image dataSource = addImageToCase(); + dataSources.add(dataSource); + volatilityProcessor = new VolatilityProcessor(memoryImagePath, dataSource, pluginsToRun, progressMonitor); + volatilityProcessor.run(); + } catch (NoCurrentCaseException | TskCoreException | VolatilityProcessor.VolatilityProcessorException ex) { criticalErrorOccurred = true; + errorMessages.add(Bundle.AddMemoryImageTask_errorMessage_criticalException(ex.getLocalizedMessage())); + /* + * Log the exception as well as add it to the error messages, to + * ensure that the stack trace is not lost. + */ + logger.log(Level.SEVERE, String.format("Critical error processing memory image data source at %s for device %s", memoryImagePath, deviceId), ex); } - /* call Volatility to process the image */ - else { - if (isCancelled) - return; - - volatilityProcessor = new VolatilityProcessor(imageFilePath, dataSource, pluginsToRun, progressMonitor); - if (volatilityProcessor.run()) { - criticalErrorOccurred = true; - } - errorMessages.addAll(volatilityProcessor.getErrorMessages()); - } - + errorMessages.addAll(volatilityProcessor.getErrorMessages()); progressMonitor.setProgress(100); /** @@ -110,55 +119,64 @@ final class AddMemoryImageTask implements Runnable { } else { result = DataSourceProcessorCallback.DataSourceProcessorResult.NO_ERRORS; } - - callback.done(result, errorMessages, new ArrayList<>(Arrays.asList(dataSource))); + callback.done(result, errorMessages, dataSources); } /** - * Attempts to add the input image to the case. + * Attempts to add the input memory image to the case as a data source. * - * @param errorMessages If there are any error messages, the error messages - * are added to this list for eventual return to the - * caller via the callback. - * @returns Image that was added to DB or null on error + * @return The Image object representation of the memeory image file data + * source. + * + * @throws NoCurrentCaseException If there is no current case. + * @throws TskCoreException If there is an error adding the data + * source to the case database. */ - @Messages({"AddMemoryImageTask.progress.add.text=Adding memory image: ", - "AddMemoryImageTask.image.critical.error.adding=Critical error adding ", - "AddMemoryImageTask.for.device=for device ", - "AddMemoryImageTask.image.notExisting=is not existing.", - "AddMemoryImageTask.image.noncritical.error.adding=Non-critical error adding "}) - private Image addImageToCase(List errorMessages) { - progressMonitor.setProgressText(Bundle.AddMemoryImageTask_progress_add_text() + imageFilePath); - - SleuthkitCase caseDatabase = Case.getCurrentCase().getSleuthkitCase(); - caseDatabase.acquireExclusiveLock(); + @Messages({ + "# {0} - image file path", + "AddMemoryImageTask_progressMessage_addingImageFile= Adding memory image {0}", + "# {0} - image file path", + "# {1} - device id", + "AddMemoryImageTask_exceptionMessage_noImageFile= Memory image file {0} for device {1} does not exist" + }) + private Image addImageToCase() throws NoCurrentCaseException, TskCoreException { + progressMonitor.setProgressText(Bundle.AddMemoryImageTask_progressMessage_addingImageFile( memoryImagePath)); - // verify it exists - File imageFile = Paths.get(imageFilePath).toFile(); - if (!imageFile.exists()) { - errorMessages.add(Bundle.AddMemoryImageTask_image_critical_error_adding() + imageFilePath + Bundle.AddMemoryImageTask_for_device() - + deviceId + Bundle.AddMemoryImageTask_image_notExisting()); - return null; - } - + SleuthkitCase caseDatabase = Case.getOpenCase().getSleuthkitCase(); + caseDatabase.acquireSingleUserCaseWriteLock(); try { - // add it to the DB - List imageFilePaths = new ArrayList<>(); - imageFilePaths.add(imageFilePath); - Image dataSource = caseDatabase.addImageInfo(0, imageFilePaths, timeZone); //TODO: change hard coded deviceId. + /* + * Verify the memory image file exists. + */ + File imageFile = Paths.get(memoryImagePath).toFile(); + if (!imageFile.exists()) { + throw new TskCoreException(Bundle.AddMemoryImageTask_exceptionMessage_noImageFile(memoryImagePath, deviceId)); + } + + /* + * Add the data source. + * + * NOTE: The object id for device passed to + * SleuthkitCase.addImageInfo is hard-coded to zero for now. This + * will need to be changed when a Device abstraction is added to the + * SleuthKit data model. + */ + Image dataSource = caseDatabase.addImageInfo(0, new ArrayList<>(Arrays.asList(memoryImagePath)), timeZone); return dataSource; - } catch (TskCoreException ex) { - errorMessages.add(Bundle.AddMemoryImageTask_image_critical_error_adding() + imageFilePath + Bundle.AddMemoryImageTask_for_device() + deviceId + ":" + ex.getLocalizedMessage()); - return null; + } finally { - caseDatabase.releaseExclusiveLock(); - } + caseDatabase.releaseSingleUserCaseWriteLock(); + } } + /** + * Requests cancellation of this task by setting a cancelled flag. + */ void cancelTask() { isCancelled = true; if (volatilityProcessor != null) { volatilityProcessor.cancel(); } } + } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java index b2a893964a..f96a9cc801 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2011-2016 Basis Technology Corp. + * + * Copyright 2018 Basis Technology Corp. * Contact: carrier sleuthkit 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. @@ -37,11 +37,13 @@ import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumn; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PathValidator; final class MemoryDSInputPanel extends JPanel implements DocumentListener { + private static final long serialVersionUID = 1L; //default private final String PROP_LASTINPUT_PATH = "LBL_LastInputFile_PATH"; private final JFileChooser fc = new JFileChooser(); @@ -52,14 +54,14 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { private final List PluginListNames = new ArrayList<>(); private final Map pluginListStates = new HashMap<>(); // is set by listeners when users select and deselect items private final Boolean isEnabled = true; - + /** - * Creates new MemoryDSInputPanel panel for user input + * Creates new MemoryDSInputPanel panel for user input */ private MemoryDSInputPanel(String context) { - this.pluginList = new String[]{"amcache","cmdline","cmdscan","consoles","malfind","netscan","notepad","pslist","psxview","shellbags","shimcache","shutdown","userassist", "apihooks","connscan","devicetree","dlllist","envars","filescan","gahti","getservicesids","getsids","handles","hashdump","hivelist","hivescan","impscan","ldrmodules","lsadump","modules","mutantscan","privs","psscan","pstree","sockets","svcscan","shimcache","timeliner","unloadedmodules","userhandles","vadinfo","verinfo"}; + this.pluginList = new String[]{"amcache", "cmdline", "cmdscan", "consoles", "malfind", "netscan", "notepad", "pslist", "psxview", "shellbags", "shimcache", "shutdown", "userassist", "apihooks", "connscan", "devicetree", "dlllist", "envars", "filescan", "gahti", "getservicesids", "getsids", "handles", "hashdump", "hivelist", "hivescan", "impscan", "ldrmodules", "lsadump", "modules", "mutantscan", "privs", "psscan", "pstree", "sockets", "svcscan", "shimcache", "timeliner", "unloadedmodules", "userhandles", "vadinfo", "verinfo"}; Arrays.sort(this.pluginList); - + initComponents(); errorLabel.setVisible(false); @@ -91,7 +93,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { private void postInit() { pathTextField.getDocument().addDocumentListener(this); } - + private void customizePluginListTable() { PluginList.setModel(tableModel); PluginList.setTableHeader(null); @@ -135,14 +137,14 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { // set the selected timezone timeZoneComboBox.setSelectedItem(formatted); } - + private void createVolatilityVersionList() { - + volExecutableComboBox.addItem("2.6"); volExecutableComboBox.addItem("2.5"); - + } - + private void createPluginList() { PluginListNames.clear(); pluginListStates.clear(); @@ -150,19 +152,19 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { // if the config file doesn't exist, then set them all to enabled boolean allEnabled = !ModuleSettings.configExists(this.contextName); Map pluginMap = ModuleSettings.getConfigSettings(this.contextName); - + for (String plugin : pluginList) { PluginListNames.add(plugin); - if (allEnabled) + if (allEnabled) { pluginListStates.put(plugin, true); - else + } else { pluginListStates.put(plugin, pluginMap.containsKey(plugin)); + } } tableModel.fireTableDataChanged(); //this.tableModel = pluginsToRun.getModel(); } - /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -282,18 +284,18 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { }// //GEN-END:initComponents @SuppressWarnings("deprecation") private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed - String oldText = pathTextField.getText(); - // set the current directory of the FileChooser if the ImagePath Field is valid - File currentDir = new File(oldText); - if (currentDir.exists()) { - fc.setCurrentDirectory(currentDir); - } + String oldText = pathTextField.getText(); + // set the current directory of the FileChooser if the ImagePath Field is valid + File currentDir = new File(oldText); + if (currentDir.exists()) { + fc.setCurrentDirectory(currentDir); + } - int retval = fc.showOpenDialog(this); - if (retval == JFileChooser.APPROVE_OPTION) { - String path = fc.getSelectedFile().getPath(); - pathTextField.setText(path); - } + int retval = fc.showOpenDialog(this); + if (retval == JFileChooser.APPROVE_OPTION) { + String path = fc.getSelectedFile().getPath(); + pathTextField.setText(path); + } }//GEN-LAST:event_browseButtonActionPerformed private void volExecutableComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_volExecutableComboBoxActionPerformed @@ -322,7 +324,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { String getImageFilePath() { return pathTextField.getText(); } - + List getPluginsToRun() { List enabledPlugins = new ArrayList<>(); Map pluginMap = new HashMap<>(); @@ -332,7 +334,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { pluginMap.put(plugin, ""); } } - + ModuleSettings.setConfigSettings(this.contextName, pluginMap); // @@ Could return keys of set return enabledPlugins; @@ -374,11 +376,19 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { * * @param path Absolute path to the selected data source */ - @Messages({"MemoryDSInputPanel.error.text=Path to multi-user data source is on \"C:\" drive"}) + @Messages({ + "MemoryDSInputPanel_errorMsg_noOpenCase=No open case", + "MemoryDSInputPanel_errorMsg_dataSourcePathOnCdrive=Path to multi-user data source is on \"C:\" drive" + }) private void warnIfPathIsInvalid(String path) { - if (!PathValidator.isValid(path, Case.getCurrentCase().getCaseType())) { + try { + if (!PathValidator.isValid(path, Case.getOpenCase().getCaseType())) { + errorLabel.setVisible(true); + errorLabel.setText(Bundle.MemoryDSInputPanel_errorMsg_dataSourcePathOnCdrive()); + } + } catch (NoCurrentCaseException unused) { errorLabel.setVisible(true); - errorLabel.setText(Bundle.MemoryDSInputPanel_error_text()); + errorLabel.setText(Bundle.MemoryDSInputPanel_errorMsg_dataSourcePathOnCdrive()); } } @@ -470,5 +480,4 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { } } - } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java index cfe2978fe0..9791ad1f09 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,21 +28,21 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; /** - * A MEmory data source processor that implements the DataSourceProcessor service - * provider interface to allow integration with the add data source wizard. It - * also provides a run method overload to allow it to be used independently of - * the wizard. + * A memory image data source processor that implements the DataSourceProcessor + * service provider interface to allow integration with the Add Data Source + * wizard. It also provides a run method overload to allow it to be used + * independently of the wizard. */ @ServiceProvider(service = DataSourceProcessor.class) public class MemoryDSProcessor implements DataSourceProcessor { private final MemoryDSInputPanel configPanel; - private AddMemoryImageTask addImageTask = null; + private AddMemoryImageTask addImageTask; /* - * Constructs a Memory data source processor that implements the + * Constructs a memory data source processor that implements the * DataSourceProcessor service provider interface to allow integration with - * the add data source wizard. It also provides a run method overload to + * the Add Data source wizard. It also provides a run method overload to * allow it to be used independently of the wizard. */ public MemoryDSProcessor() { @@ -117,37 +117,40 @@ public class MemoryDSProcessor implements DataSourceProcessor { @Override public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { configPanel.storeSettings(); - run(UUID.randomUUID().toString(), configPanel.getImageFilePath(), configPanel.getPluginsToRun(), configPanel.getTimeZone(), 0, progressMonitor, callback); + run(UUID.randomUUID().toString(), configPanel.getImageFilePath(), configPanel.getPluginsToRun(), configPanel.getTimeZone(), progressMonitor, callback); } /** - * Adds a "memory" data source to the case database using a background task in - * a separate thread and the given settings instead of those provided by the - * selection and configuration panel. Returns as soon as the background task - * is started and uses the callback object to signal task completion and - * return results. + * Adds a memory image data source to the case database using a background + * task in a separate thread and the given settings instead of those + * provided by the selection and configuration panel. Returns as soon as the + * background task is started and uses the callback object to signal task + * completion and return results. * - * @param deviceId An ASCII-printable identifier for the device - * associated with the data source that is - * intended to be unique across multiple cases - * (e.g., a UUID). - * @param imageFilePath Path to the image file. - * @param timeZone The time zone to use when processing dates - * and times for the image, obtained from - * java.util.TimeZone.getID. - * @param chunkSize The maximum size of each chunk of the raw - * data source as it is divided up into virtual - * unallocated space files. - * @param progressMonitor Progress monitor for reporting progress - * during processing. - * @param callback Callback to call when processing is done. + * @param deviceId An ASCII-printable identifier for the device + * associated with the data source that is intended + * to be unique across multiple cases (e.g., a UUID). + * @param memoryImagePath Path to the memory image file. + * @param pluginsToRun The Volatility plugins to run. + * @param timeZone The time zone to use when processing dates and + * times for the image, obtained from + * java.util.TimeZone.getID. + * @param progressMonitor Progress monitor for reporting progress during + * processing. + * @param callback Callback to call when processing is done. */ - private void run(String deviceId, String imageFilePath, List pluginsToRun, String timeZone, long chunkSize, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - addImageTask = new AddMemoryImageTask(deviceId, imageFilePath, pluginsToRun, timeZone, 0, progressMonitor, callback); + private void run(String deviceId, String memoryImagePath, List pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + addImageTask = new AddMemoryImageTask(deviceId, memoryImagePath, pluginsToRun, timeZone, progressMonitor, callback); new Thread(addImageTask).start(); - //new Thread(new AddLocalFilesTask(deviceId, rootVirtualDirectoryName, localFilePaths, progressMonitor, callback)).start(); } + /** + * Requests cancellation of the background task that adds a data source to + * the case database, after the task is started using the run method. This + * is a "best effort" cancellation, with no guarantees that the case + * database will be unchanged. If cancellation succeeded, the list of new + * data sources returned by the background task will be empty. + */ @Override public void cancel() { if (addImageTask != null) { @@ -165,4 +168,3 @@ public class MemoryDSProcessor implements DataSourceProcessor { } } - diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java index dc2ccc296c..fd285ae1aa 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java @@ -1,15 +1,15 @@ /* - * Autopsy - * + * Autopsy + * * Copyright 2018 Basis Technology Corp. * Contact: carrier sleuthkit 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. @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.experimental.volatilityDSP; import java.io.BufferedReader; import java.io.FileReader; import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Paths; import java.util.ArrayList; @@ -30,9 +29,10 @@ import java.util.List; import java.util.Set; import java.util.logging.Level; import org.openide.modules.InstalledFileLocator; -import org.openide.util.Exceptions; import org.openide.util.Lookup; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.casemodule.services.FileManager; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; @@ -50,159 +50,204 @@ import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; - /** - * Runs Volatility and parses output + * Runs Volatility on a given memory image file and parses the output to create + * artifacts. */ class VolatilityProcessor { - private static final String VOLATILITY_DIRECTORY = "Volatility"; //NON-NLS + + private final static Logger logger = Logger.getLogger(VolatilityProcessor.class.getName()); + private static final String VOLATILITY = "Volatility"; //NON-NLS private static final String VOLATILITY_EXECUTABLE = "volatility_2.6_win64_standalone.exe"; //NON-NLS - private final String memoryImagePath; - private final List pluginsToRun; - private final Image dataSource; - private static final String SEP = System.getProperty("line.separator"); - private static final Logger logger = Logger.getLogger(VolatilityProcessor.class.getName()); - private String moduleOutputPath; - private File executableFile; private final IngestServices services = IngestServices.getInstance(); + private final List errorMsgs = new ArrayList<>(); + private final String memoryImagePath; + private final Image dataSource; + private final List pluginsToRun; private final DataSourceProcessorProgressMonitor progressMonitor; - private boolean isCancelled; + private Case currentCase; + private File executableFile; + private String moduleOutputPath; private FileManager fileManager; - private final List errorMsgs = new ArrayList<>(); + private volatile boolean isCancelled; /** - * - * @param ImagePath String path to memory image file - * @param dataSource Object for memory image that was added to case DB - * @param plugInToRuns list of Volatility plugins to run - * @param progressMonitor DSP progress monitor to report status + * Constructs a processor that runs Volatility on a given memory image file + * and parses the output to create artifacts. + * + * @param memoryImagePath Path to memory image file. + * @param dataSource The memory image data source. + * @param plugInToRuns Volatility plugins to run. + * @param progressMonitor Progress monitor for reporting progress during + * processing. */ - VolatilityProcessor(String ImagePath, Image dataSource, List plugInToRun, DataSourceProcessorProgressMonitor progressMonitor) { - this.memoryImagePath = ImagePath; + VolatilityProcessor(String memoryImagePath, Image dataSource, List plugInToRun, DataSourceProcessorProgressMonitor progressMonitor) { + this.memoryImagePath = memoryImagePath; this.pluginsToRun = plugInToRun; this.dataSource = dataSource; this.progressMonitor = progressMonitor; } - - + /** - * Run volatility and parse the outputs - * @returns true if there was a critical error + * Runs Volatility on a given memory image file and parses the output to + * create artifacts. + * + * @throws VolatilityProcessorException If there is a critical error during + * processing. */ - boolean run() { - executableFile = locateExecutable(); - if (executableFile == null) { - logger.log(Level.SEVERE, "Volatility exe not found"); - return true; + @NbBundle.Messages({ + "VolatilityProcessor_progressMessage_noCurrentCase=Failed to get current case", + "VolatilityProcessor_exceptionMessage_volatilityExeNotFound=Volatility executable not found", + "# {0} - plugin name", + "VolatilityProcessor_progressMessage_runningImageInfo=Running {0} plugin" + }) + void run() throws VolatilityProcessorException { + this.errorMsgs.clear(); + + try { + this.currentCase = Case.getOpenCase(); + } catch (NoCurrentCaseException ex) { + throw new VolatilityProcessorException(Bundle.VolatilityProcessor_progressMessage_noCurrentCase(), ex); } - - final Case currentCase = Case.getCurrentCase(); + + executableFile = locateVolatilityExecutable(); + if (executableFile == null) { + throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_volatilityExeNotFound()); + } + fileManager = currentCase.getServices().getFileManager(); - // make a unique folder for this image - moduleOutputPath = currentCase.getModulesOutputDirAbsPath() + File.separator + "Volatility" + File.separator + dataSource.getId(); File directory = new File(String.valueOf(moduleOutputPath)); - if(!directory.exists()){ + /* + * Make an output folder unique to this data source. + */ + Long dataSourceId = dataSource.getId(); + moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), VOLATILITY, dataSourceId.toString()).toString(); + File directory = new File(String.valueOf(moduleOutputPath)); + if (!directory.exists()) { directory.mkdirs(); - progressMonitor.setProgressText("Running imageinfo"); - executeAndParseVolatility("imageinfo"); + progressMonitor.setProgressText(Bundle.VolatilityProcessor_progressMessage_runningImageInfo("imageinfo")); //NON-NLS + runVolatilityPlugin("imageinfo"); //NON-NLS } progressMonitor.setIndeterminate(false); progressMonitor.setProgressMax(pluginsToRun.size()); for (int i = 0; i < pluginsToRun.size(); i++) { - if (isCancelled) + if (isCancelled) { break; + } String pluginToRun = pluginsToRun.get(i); - progressMonitor.setProgressText("Processing " + pluginToRun + " module"); - executeAndParseVolatility(pluginToRun); + progressMonitor.setProgressText(Bundle.VolatilityProcessor_progressMessage_runningImageInfo(pluginToRun)); + runVolatilityPlugin(pluginToRun); progressMonitor.setProgress(i); - } - return false; + } } - + /** - * Get list of error messages that were generated during call to run() - * @return + * Gets a list of error messages that were generated during the processing. + * + * @return The list of error messages. */ List getErrorMessages() { - return errorMsgs; + return new ArrayList<>(errorMsgs); } - private void executeAndParseVolatility(String pluginToRun) { - try { - List commandLine = new ArrayList<>(); - commandLine.add("\"" + executableFile + "\""); - File memoryImage = new File(memoryImagePath); - commandLine.add("--filename=" + memoryImage.getName()); //NON-NLS - - // run imginfo if we haven't run it yet - File imageInfoOutputFile = new File(moduleOutputPath + "\\imageinfo.txt"); - if (imageInfoOutputFile.exists()) { - String memoryProfile = parseImageInfoOutput(imageInfoOutputFile); - if (memoryProfile == null) { - String msg = "Error parsing Volatility imginfo output"; - logger.log(Level.SEVERE, msg); - errorMsgs.add(msg); - return; - } - commandLine.add("--profile=" + memoryProfile); - } - - commandLine.add(pluginToRun); //NON-NLS - - String outputFile = moduleOutputPath + "\\" + pluginToRun + ".txt"; - ProcessBuilder processBuilder = new ProcessBuilder(commandLine); - // Add environment variable to force Volatility to run with the same permissions Autopsy uses - processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS - processBuilder.redirectOutput(new File(outputFile)); - processBuilder.redirectError(new File(moduleOutputPath + "\\Volatility_Run.err")); - processBuilder.directory(new File(memoryImage.getParent())); - + /** + * Runs a given Volatility plugin and parses its output to create artifacts. + * + * @param pluginToRun The name of the Volatility plugin to run. + * + * @throws VolatilityProcessorException If there is a critical error, add + * messages to the error messages list + * for non-critical errors. + */ + @NbBundle.Messages({ + "VolatilityProcessor_exceptionMessage_failedToRunVolatilityExe=Could not run Volatility", + "# {0} - plugin name", + "VolatilityProcessor_exceptionMessage_errorRunningPlugin=Volatility error running {0} plugin", + "# {0} - plugin name", + "VolatilityProcessor_exceptionMessage_errorAddingOutput=Failed to add output for {0} to case", + "# {0} - plugin name", + "VolatilityProcessor_exceptionMessage_searchServiceNotFound=Keyword search service not found, output for {0} plugin not indexed", + "# {0} - plugin name", + "VolatilityProcessor_exceptionMessage_errorIndexingOutput=Error indexing output for {0} plugin" + }) + private void runVolatilityPlugin(String pluginToRun) throws VolatilityProcessorException { + List commandLine = new ArrayList<>(); + commandLine.add("\"" + executableFile + "\""); //NON-NLS + File memoryImage = new File(memoryImagePath); + commandLine.add("--filename=" + memoryImage.getName()); //NON-NLS + + File imageInfoOutputFile = new File(moduleOutputPath + "\\imageinfo.txt"); //NON-NLS + if (imageInfoOutputFile.exists()) { + String memoryProfile = parseImageInfoOutput(imageInfoOutputFile); + commandLine.add("--profile=" + memoryProfile); //NON-NLS + } + + commandLine.add(pluginToRun); + + String outputFile = moduleOutputPath + "\\" + pluginToRun + ".txt"; //NON-NLS + ProcessBuilder processBuilder = new ProcessBuilder(commandLine); + /* + * Add an environment variable to force Volatility to run with the same + * permissions Autopsy uses. + */ + processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS + processBuilder.redirectOutput(new File(outputFile)); + processBuilder.redirectError(new File(moduleOutputPath + "\\Volatility_Run.err")); //NON-NLS + processBuilder.directory(new File(memoryImage.getParent())); + + try { int exitVal = ExecUtil.execute(processBuilder); if (exitVal != 0) { - String msg = "Volatility non-0 exit value for module: " + pluginToRun; - logger.log(Level.SEVERE, msg); - errorMsgs.add(msg); + errorMsgs.add(Bundle.VolatilityProcessor_exceptionMessage_errorRunningPlugin(pluginToRun)); return; } - - if (isCancelled) - return; - - // add the output to the case - final Case currentCase = Case.getCurrentCase(); - Report report = currentCase.getSleuthkitCase().addReport(outputFile, "Volatility", "Volatility " + pluginToRun + " Module"); - - KeywordSearchService searchService = Lookup.getDefault().lookup(KeywordSearchService.class); - if (null == searchService) { - logger.log(Level.WARNING, "Keyword search service not found. Report will not be indexed"); - } else { - searchService.index(report); - } - - scanOutputFile(pluginToRun, new File(outputFile)); - - } catch (IOException | SecurityException | TskCoreException ex) { - logger.log(Level.SEVERE, "Unable to run Volatility", ex); //NON-NLS - //this.addErrorMessage(NbBundle.getMessage(this.getClass(), "ExtractRegistry.execRegRip.errMsg.failedAnalyzeRegFile", this.getName())); + } catch (IOException | SecurityException ex) { + throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_failedToRunVolatilityExe(), ex); } - } - - /** - * Finds and returns the path to the executable, if able. - * - * @param executableToFindName The name of the executable to find - * - * @return A File reference or null - */ - private static File locateExecutable() { - // Must be running under a Windows operating system. - if (!PlatformUtil.isWindowsOS()) { - return null; - } - - String executableToFindName = Paths.get(VOLATILITY_DIRECTORY, VOLATILITY_EXECUTABLE).toString(); + if (isCancelled) { + return; + } + + /* + * Add the plugin output file to the case as a report. + */ + try { + Report report = currentCase.getSleuthkitCase().addReport(outputFile, VOLATILITY, VOLATILITY + " " + pluginToRun + " Plugin"); //NON-NLS + try { + KeywordSearchService searchService = Lookup.getDefault().lookup(KeywordSearchService.class); + if (searchService != null) { + searchService.index(report); + } else { + errorMsgs.add(Bundle.VolatilityProcessor_exceptionMessage_searchServiceNotFound(pluginToRun)); + /* + * Log the exception as well as add it to the error + * messages, to ensure that the stack trace is not lost. + */ + logger.log(Level.WARNING, Bundle.VolatilityProcessor_exceptionMessage_errorIndexingOutput(pluginToRun)); + } + } catch (TskCoreException ex) { + throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_errorIndexingOutput(pluginToRun), ex); + } + } catch (TskCoreException ex) { + throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_errorAddingOutput(pluginToRun), ex); + } + + createArtifactsFromPluginOutput(pluginToRun, new File(outputFile)); + } + + /** + * Finds and returns the path to the Volatility executable, if able. + * + * @return A File reference or null. + */ + private static File locateVolatilityExecutable() { + if (!PlatformUtil.isWindowsOS()) { + return null; + } + + String executableToFindName = Paths.get(VOLATILITY, VOLATILITY_EXECUTABLE).toString(); File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, VolatilityProcessor.class.getPackage().getName(), false); if (null == exeFile) { return null; @@ -215,73 +260,79 @@ class VolatilityProcessor { return exeFile; } - private String parseImageInfoOutput(File imageOutputFile) throws FileNotFoundException { - // create a Buffered Reader object instance with a FileReader - try ( - BufferedReader br = new BufferedReader(new FileReader(imageOutputFile))) { - // read the first line from the text file - String fileRead = br.readLine(); - br.close(); - String[] profileLine = fileRead.split(":"); - String[] memProfile = profileLine[1].split(",|\\("); - return memProfile[0].replaceAll("\\s+",""); - } catch (IOException ex) { - Exceptions.printStackTrace(ex); - // @@@ Need to log this or rethrow it - } - - return null; + @NbBundle.Messages({ + "VolatilityProcessor_exceptionMessage_failedToParseImageInfo=Could not parse image info" + }) + private String parseImageInfoOutput(File imageOutputFile) throws VolatilityProcessorException { + try (BufferedReader br = new BufferedReader(new FileReader(imageOutputFile))) { + String fileRead = br.readLine(); + String[] profileLine = fileRead.split(":"); //NON-NLS + String[] memProfile = profileLine[1].split(",|\\("); //NON-NLS + return memProfile[0].replaceAll("\\s+", ""); //NON-NLS + } catch (IOException ex) { + throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_failedToParseImageInfo(), ex); + } } - - /** - * Lookup the set of files and add INTERESTING_ITEM artifacts for them. - * - * @param fileSet - * @param pluginName - */ - private void flagFiles(Set fileSet, String pluginName) { - - Blackboard blackboard; - try { - blackboard = Case.getCurrentCase().getServices().getBlackboard(); - } - catch (Exception ex) { - // case is closed ?? - return; - } + /** + * Adds interesting file artifacts for files found by a Volatility plugin. + * + * @param fileSet The paths of the files within the memeory image data + * source. + * @param pluginName The name of the source Volatility plugin. + */ + @NbBundle.Messages({ + "# {0} - plugin name", + "VolatilityProcessor_artifactAttribute_interestingFileSet=Volatility Plugin {0}", + "# {0} - file path", + "# {1} - file name", + "# {2} - plugin name", + "VolatilityProcessor_exceptionMessage_fileNotFound=File {0}/{1} not found for ouput of {2} plugin", + "# {0} - plugin name", + "VolatilityProcessor_exceptionMessage_errorCreatingArtifact=Error creating artifact for output of {0} plugin", + "# {0} - plugin name", + "VolatilityProcessor_errorMessage_errorFindingFiles=Error finding files parsed from output of {0} plugin", + "# {0} - plugin name", + "VolatilityProcessor_errorMessage_failedToIndexArtifact=Error indexing artifact from output of {0} plugin" + }) + private void flagFiles(Set fileSet, String pluginName) throws VolatilityProcessorException { + Blackboard blackboard = currentCase.getServices().getBlackboard(); for (String file : fileSet) { if (isCancelled) { - return; + return; } + if (file.isEmpty()) { + continue; + } + File volfile = new File(file); String fileName = volfile.getName().trim(); - // File does not have any data in it based on bad data if (fileName.length() < 1) { continue; } String filePath = volfile.getParent(); - + try { List resolvedFiles; if (filePath == null) { - resolvedFiles = fileManager.findFiles(fileName); //NON-NLS + resolvedFiles = fileManager.findFiles(fileName); } else { // File changed the slashes back to \ on us... - filePath = filePath.replaceAll("\\\\", "/"); - resolvedFiles = fileManager.findFiles(fileName, filePath); //NON-NLS + filePath = filePath.replaceAll("\\\\", "/"); //NON-NLS + resolvedFiles = fileManager.findFiles(fileName, filePath); } - + // if we didn't get anything, then try adding a wildcard for extension - if ((resolvedFiles.isEmpty()) && (fileName.contains(".") == false)) { - + if ((resolvedFiles.isEmpty()) && (fileName.contains(".") == false)) { //NON-NLS + // if there is already the same entry with ".exe" in the set, just use that one - if (fileSet.contains(file + ".exe")) + if (fileSet.contains(file + ".exe")) { //NON-NLS continue; - - fileName = fileName + ".%"; + } + + fileName += ".%"; //NON-NLS if (filePath == null) { resolvedFiles = fileManager.findFiles(fileName); //NON-NLS } else { @@ -289,425 +340,431 @@ class VolatilityProcessor { } } - + if (resolvedFiles.isEmpty()) { - logger.log(Level.SEVERE, "File not found in lookup: " + filePath + "/" + fileName); - errorMsgs.add("File not found in lookup: " + filePath + "/" + fileName); + errorMsgs.add(Bundle.VolatilityProcessor_exceptionMessage_fileNotFound(filePath, fileName, pluginName)); continue; } - - resolvedFiles.forEach((resolvedFile) -> { + + for (AbstractFile resolvedFile : resolvedFiles) { if (resolvedFile.getType() == TSK_DB_FILES_TYPE_ENUM.SLACK) { - return; // equivalent to continue in non-lambda world + continue; } try { - String MODULE_NAME = "Volatility"; BlackboardArtifact volArtifact = resolvedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT); - BlackboardAttribute att1 = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, - "Volatility Plugin " + pluginName); + BlackboardAttribute att1 = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, VOLATILITY, Bundle.VolatilityProcessor_artifactAttribute_interestingFileSet(pluginName)); volArtifact.addAttribute(att1); try { // index the artifact for keyword search blackboard.indexArtifact(volArtifact); } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, "Unable to index blackboard artifact " + volArtifact.getArtifactID(), ex); //NON-NLS + errorMsgs.add(Bundle.VolatilityProcessor_errorMessage_failedToIndexArtifact(pluginName)); + /* + * Log the exception as well as add it to the error + * messages, to ensure that the stack trace is not + * lost. + */ + logger.log(Level.SEVERE, String.format("Failed to index artifact (artifactId=%d) for for output of %s plugin", volArtifact.getArtifactID(), pluginName), ex); } // fire event to notify UI of this new artifact - services.fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT)); + services.fireModuleDataEvent(new ModuleDataEvent(VOLATILITY, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT)); } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Failed to create BlackboardArtifact.", ex); // NON-NLS - } catch (IllegalStateException ex) { - logger.log(Level.SEVERE, "Failed to create BlackboardAttribute.", ex); // NON-NLS + throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_errorCreatingArtifact(pluginName), ex); } - }); + } } catch (TskCoreException ex) { - //String msg = NbBundle.getMessage(this.getClass(), "Chrome.getHistory.errMsg.errGettingFiles"); - logger.log(Level.SEVERE, "Error in Finding Files", ex); - return; + throw new VolatilityProcessorException(Bundle.VolatilityProcessor_errorMessage_errorFindingFiles(pluginName), ex); } } } - - /** - * Scan the output of Volatility and create artifacts as needed - * - * @param pluginName Name of volatility module run - * @param PluginOutput File that contains the output to parse - */ - private void scanOutputFile(String pluginName, File PluginOutput) { - - if (pluginName.matches("dlllist")) { - Set fileSet = parseDllList(PluginOutput); - flagFiles(fileSet, pluginName); - } else if (pluginName.matches("handles")) { - Set fileSet = parseHandles(PluginOutput); - flagFiles(fileSet, pluginName); - } else if (pluginName.matches("cmdline")) { - Set fileSet = parseCmdline(PluginOutput); - flagFiles(fileSet, pluginName); - } else if (pluginName.matches("psxview")){ - Set fileSet = parsePsxview(PluginOutput); - flagFiles(fileSet, pluginName); - } else if (pluginName.matches("pslist")) { - Set fileSet = parsePslist(PluginOutput); - flagFiles(fileSet, pluginName); - } else if (pluginName.matches("psscan")) { - Set fileSet = parsePsscan(PluginOutput); - flagFiles(fileSet, pluginName); - } else if (pluginName.matches("pstree")) { - Set fileSet = parsePstree(PluginOutput); - flagFiles(fileSet, pluginName); - } else if (pluginName.matches("svcscan")) { - Set fileSet = parseSvcscan(PluginOutput); - flagFiles(fileSet, pluginName); - } else if (pluginName.matches("filescan")) { - // BC: Commented out. Too many hits to flag - //Set fileSet = ParseFilescan(PluginOutput); - //lookupFiles(fileSet, pluginName); - } else if (pluginName.matches("shimcache")) { - Set fileSet = parseShimcache(PluginOutput); - flagFiles(fileSet, pluginName); - } - } - /** - * Normalize the path we parse out of the output before - * we look it up in the case DB - * - * @param filePath Path to normalize - * @return Normalized version + /** + * Parses the output of a Volatility plugin and creates artifacts as needed. + * + * @param pluginName Name of the Volatility plugin. + * @param pluginOutputFile File that contains the output to parse. + */ + private void createArtifactsFromPluginOutput(String pluginName, File pluginOutputFile) throws VolatilityProcessorException { + Set fileSet = null; + switch (pluginName) { + case "dlllist": //NON-NLS + fileSet = parseDllListOutput(pluginOutputFile); + break; + case "handles": //NON-NLS + fileSet = parseHandlesOutput(pluginOutputFile); + break; + case "cmdline": //NON-NLS + fileSet = parseCmdlineOutput(pluginOutputFile); + break; + case "psxview": //NON-NLS + fileSet = parsePsxviewOutput(pluginOutputFile); + break; + case "pslist": //NON-NLS + fileSet = parsePslistOutput(pluginOutputFile); + break; + case "psscan": //NON-NLS + fileSet = parsePsscanOutput(pluginOutputFile); + break; + case "pstree": //NON-NLS + fileSet = parsePstreeOutput(pluginOutputFile); + break; + case "svcscan": //NON-NLS + fileSet = parseSvcscanOutput(pluginOutputFile); + break; + case "shimcache": //NON-NLS + fileSet = parseShimcacheOutput(pluginOutputFile); + break; + default: + break; + } + + if (fileSet != null && !fileSet.isEmpty()) { + flagFiles(fileSet, pluginName); + } + } + + /** + * Normalizes a file path from a Volatility plugin so it can be used to look + * up the file in the case database. + * + * @param filePath Path to normalize. + * + * @return The normalized path or the empty string if the path cannot be + * normalized or should be ignored. */ private String normalizePath(String filePath) { - if (filePath == null) - return ""; + if (filePath == null) { + return ""; //NON-NLS + } + String path = filePath.trim(); + + // change slash direction + path = path.replaceAll("\\\\", "/"); //NON-NLS + path = path.toLowerCase(); - filePath = filePath.trim(); - - // strip C: and \??\C: - if (filePath.contains(":")) { - filePath = filePath.substring(filePath.indexOf(":") + 1); + // \??\c:\windows ... + if ((path.length() > 4) && (path.startsWith("/??/"))) { //NON-NLS + path = path.substring(4); } - // change slash direction - filePath = filePath.replaceAll("\\\\", "/"); - filePath = filePath.toLowerCase(); - - filePath = filePath.replaceAll("/systemroot/", "/windows/"); + // strip C: + if (path.contains(":")) { //NON-NLS + int index = path.indexOf(":"); + if (index+1 < path.length()) + path = path.substring(index + 1); + } + + path = path.replaceAll("/systemroot/", "/windows/"); + // catches 1 type of file in cmdline - filePath = filePath.replaceAll("%systemroot%", "/windows/"); - filePath = filePath.replaceAll("/device/",""); + path = path.replaceAll("%systemroot%", "/windows/"); //NON-NLS + path = path.replaceAll("/device/", ""); //NON-NLS // helps with finding files in handles plugin // example: \Device\clfs\Device\HarddiskVolume2\Users\joe\AppData\Local\Microsoft\Windows\UsrClass.dat{e15d4b01-1598-11e8-93e6-080027b5e733}.TM - if (filePath.contains("/harddiskvolume")) { + if (path.contains("/harddiskvolume")) { //NON-NLS // 16 advances beyond harddiskvolume and the number - filePath = filePath.substring(filePath.indexOf("/harddiskvolume") + 16); + int index = path.indexOf("/harddiskvolume"); //NON-NLS + if (index+16 < path.length()) { + path = path.substring(index + 16); + } } - - // no point returning these. We won't map to them - if (filePath.startsWith("/namedpipe/")) - return ""; - return filePath; + // no point returning these. We won't map to them + if (path.startsWith("/namedpipe/")) { //NON-NLS + return ""; //NON-NLS + } + + return path; } - - private Set parseHandles(File pluginFile) { + + @NbBundle.Messages({ + "# {0} - plugin name", + "VolatilityProcessor_errorMessage_outputParsingError=Error parsing output for {0} plugin" + }) + private Set parseHandlesOutput(File pluginOutputFile) { String line; Set fileSet = new HashSet<>(); - try { - BufferedReader br = new BufferedReader(new FileReader(pluginFile)); - // Ignore the first two header lines - br.readLine(); - br.readLine(); - while ((line = br.readLine()) != null) { - // 0x89ab7878 4 0x718 0x2000003 File \Device\HarddiskVolume1\Documents and Settings\QA\Local Settings\Application - if (line.startsWith("0x") == false) - continue; - - String TAG = " File "; - String file_path = null; - if ((line.contains(TAG)) && (line.length() > 57)) { + try (BufferedReader br = new BufferedReader(new FileReader(pluginOutputFile))) { + // Ignore the first two header lines + br.readLine(); + br.readLine(); + while ((line = br.readLine()) != null) { + // 0x89ab7878 4 0x718 0x2000003 File \Device\HarddiskVolume1\Documents and Settings\QA\Local Settings\Application + if (line.startsWith("0x") == false) { //NON-NLS + continue; + } + + String TAG = " File "; //NON-NLS + String file_path; + if ((line.contains(TAG)) && (line.length() > 57)) { file_path = line.substring(57); - if (file_path.contains("\"")) { - file_path = file_path.substring(0, file_path.indexOf("\"")); + if (file_path.contains("\"")) { //NON-NLS + file_path = file_path.substring(0, file_path.indexOf('\"')); //NON-NLS } // this file has a lot of device entries that are not files - if (file_path.startsWith("\\Device\\")) { - if (file_path.contains("HardDiskVolume") == false) + if (file_path.startsWith("\\Device\\")) { //NON-NLS + if (file_path.contains("HardDiskVolume") == false) { //NON-NLS continue; + } } - + fileSet.add(normalizePath(file_path)); - } - } - br.close(); + } + } } catch (IOException ex) { - String msg = "Error parsing handles output"; - logger.log(Level.SEVERE, msg, ex); - errorMsgs.add(msg); - } + errorMsgs.add(Bundle.VolatilityProcessor_errorMessage_outputParsingError("handles")); + /* + * Log the exception as well as add it to the error messages, to + * ensure that the stack trace is not lost. + */ + logger.log(Level.SEVERE, Bundle.VolatilityProcessor_errorMessage_outputParsingError("handles"), ex); + } return fileSet; } - - private Set parseDllList(File pluginFile) { + + private Set parseDllListOutput(File outputFile) { Set fileSet = new HashSet<>(); // read the first line from the text file - try (BufferedReader br = new BufferedReader(new FileReader(pluginFile))) { + try (BufferedReader br = new BufferedReader(new FileReader(outputFile))) { String line; - while ((line = br.readLine()) != null) { - - String TAG = "Command line : "; - if (line.startsWith(TAG)) { - - if(line.length() > TAG.length()) { - String file_path; - - // Command line : "C:\Program Files\VMware\VMware Tools\vmacthlp.exe" - // grab whats inbetween the quotes - if (line.charAt(TAG.length()) == '\"') { - file_path = line.substring(TAG.length() + 1); - if (file_path.contains("\"")) { - file_path = file_path.substring(0, file_path.indexOf("\"")); - } - } - // Command line : C:\WINDOWS\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,3072,512 - // grab everything before the next space - we don't want arguments - else { - file_path = line.substring(TAG.length()); - if (file_path.contains(" ")) { - file_path = file_path.substring(0, file_path.indexOf(" ")); - } - } - fileSet.add(normalizePath(file_path)); - } - } + while ((line = br.readLine()) != null) { + // we skip the Command Line entries because that data + // is also in the 0x lines (and is more likely to have a full path there. + // 0x4a680000 0x5000 0xffff \??\C:\WINDOWS\system32\csrss.exe // 0x7c900000 0xb2000 0xffff C:\WINDOWS\system32\ntdll.dll - else if (line.startsWith("0x") && line.length() > 33) { + if (line.startsWith("0x") && line.length() > 33) { // These lines do not have arguments String file_path = line.substring(33); fileSet.add(normalizePath(file_path)); } } } catch (IOException ex) { - String msg = "Error parsing dlllist output"; - logger.log(Level.SEVERE, msg, ex); - errorMsgs.add(msg); - } - return fileSet; + errorMsgs.add(Bundle.VolatilityProcessor_errorMessage_outputParsingError("dlllist")); + /* + * Log the exception as well as add it to the error messages, to + * ensure that the stack trace is not lost. + */ + logger.log(Level.SEVERE, Bundle.VolatilityProcessor_errorMessage_outputParsingError("dlllist"), ex); + } + return fileSet; } - - private Set parseFilescan(File PluginFile) { - String line; - Set fileSet = new HashSet<>(); - try { - BufferedReader br = new BufferedReader(new FileReader(PluginFile)); - // read the first line from the text file - while ((line = br.readLine()) != null) { - try { - String file_path; - file_path = line.substring(41); - fileSet.add(normalizePath(file_path)); - } catch (StringIndexOutOfBoundsException ex) { - // TO DO Catch exception - } - } - br.close(); - } catch (IOException ex) { - String msg = "Error parsing filescan output"; - logger.log(Level.SEVERE, msg, ex); - errorMsgs.add(msg); - } - return fileSet; - } - - private Set parseCmdline(File PluginFile) { + + private Set parseCmdlineOutput(File outputFile) { Set fileSet = new HashSet<>(); // read the first line from the text file - try (BufferedReader br = new BufferedReader(new FileReader(PluginFile))) { + try (BufferedReader br = new BufferedReader(new FileReader(outputFile))) { String line; while ((line = br.readLine()) != null) { if (line.length() > 16) { - String TAG = "Command line : "; - if (line.startsWith(TAG)) { + String TAG = "Command line : "; //NON-NLS + if ((line.startsWith(TAG)) && line.length() > TAG.length() + 1) { String file_path; // Command line : "C:\Program Files\VMware\VMware Tools\vmacthlp.exe" // grab whats inbetween the quotes - if (line.charAt(TAG.length()) == '\"') { + if (line.charAt(TAG.length()) == '\"') { //NON-NLS file_path = line.substring(TAG.length() + 1); - if (file_path.contains("\"")) { - file_path = file_path.substring(0, file_path.indexOf("\"")); - } - } - // Command line : C:\WINDOWS\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,3072,512 + if (file_path.contains("\"")) { //NON-NLS + file_path = file_path.substring(0, file_path.indexOf('\"')); //NON-NLS + } + } // Command line : C:\WINDOWS\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,3072,512 // grab everything before the next space - we don't want arguments else { file_path = line.substring(TAG.length()); - if (file_path.contains(" ")) { - file_path = file_path.substring(0, file_path.indexOf(" ")); + if (file_path.contains(" ")) { //NON-NLS + file_path = file_path.substring(0, file_path.indexOf(' ')); } } fileSet.add(normalizePath(file_path)); } } } - + } catch (IOException ex) { - String msg = "Error parsing cmdline output"; - logger.log(Level.SEVERE, msg, ex); - errorMsgs.add(msg); - } - return fileSet; + errorMsgs.add(Bundle.VolatilityProcessor_errorMessage_outputParsingError("cmdline")); + /* + * Log the exception as well as add it to the error messages, to + * ensure that the stack trace is not lost. + */ + logger.log(Level.SEVERE, Bundle.VolatilityProcessor_errorMessage_outputParsingError("cmdline"), ex); + } + return fileSet; } - - private Set parseShimcache(File PluginFile) { + + private Set parseShimcacheOutput(File outputFile) { String line; Set fileSet = new HashSet<>(); - try { - BufferedReader br = new BufferedReader(new FileReader(PluginFile)); - // ignore the first 2 header lines - br.readLine(); - br.readLine(); - while ((line = br.readLine()) != null) { + try (BufferedReader br = new BufferedReader(new FileReader(outputFile))) { + // ignore the first 2 header lines + br.readLine(); + br.readLine(); + while ((line = br.readLine()) != null) { String file_path; //1970-01-01 00:00:00 UTC+0000 2017-10-25 13:07:30 UTC+0000 C:\WINDOWS\system32\msctfime.ime //2017-10-23 20:47:40 UTC+0000 2017-10-23 20:48:02 UTC+0000 \??\C:\WINDOWS\CT_dba9e71b-ad55-4132-a11b-faa946b197d6.exe if (line.length() > 62) { file_path = line.substring(62); - if (file_path.contains("\"")) { - file_path = file_path.substring(0, file_path.indexOf("\"")); - } + if (file_path.contains("\"")) { //NON-NLS + file_path = file_path.substring(0, file_path.indexOf('\"')); //NON-NLS + } fileSet.add(normalizePath(file_path)); - } - } - br.close(); - } catch (IOException ex) { - String msg = "Error parsing shimcache output"; - logger.log(Level.SEVERE, msg, ex); - errorMsgs.add(msg); - } - return fileSet; + } + } + } catch (IOException ex) { + errorMsgs.add(Bundle.VolatilityProcessor_errorMessage_outputParsingError("shimcache")); + /* + * Log the exception as well as add it to the error messages, to + * ensure that the stack trace is not lost. + */ + logger.log(Level.SEVERE, Bundle.VolatilityProcessor_errorMessage_outputParsingError("shimcache"), ex); + } + return fileSet; } - - private Set parsePsscan(File PluginFile) { + + private Set parsePsscanOutput(File outputFile) { String line; Set fileSet = new HashSet<>(); - try { - BufferedReader br = new BufferedReader(new FileReader(PluginFile)); - // ignore the first two header lines - br.readLine(); - br.readLine(); - while ((line = br.readLine()) != null) { + try (BufferedReader br = new BufferedReader(new FileReader(outputFile))) { + // ignore the first two header lines + br.readLine(); + br.readLine(); + while ((line = br.readLine()) != null) { // 0x000000000969a020 notepad.exe 3604 3300 0x16d40340 2018-01-12 14:41:16 UTC+0000 - if (line.startsWith("0x") == false) + if (line.startsWith("0x") == false) { //NON-NLS continue; + } + else if (line.length() < 37) { + continue; + } + String file_path = line.substring(19, 37); file_path = normalizePath(file_path); - + // ignore system, it's not really a path - if (file_path.equals("system")) + if (file_path.equals("system")) { //NON-NLS continue; + } fileSet.add(file_path); - } - br.close(); - } catch (IOException ex) { - String msg = "Error parsing psscan output"; - logger.log(Level.SEVERE, msg, ex); - errorMsgs.add(msg); - } - return fileSet; + } + } catch (IOException ex) { + errorMsgs.add(Bundle.VolatilityProcessor_errorMessage_outputParsingError("psscan")); + /* + * Log the exception as well as add it to the error messages, to + * ensure that the stack trace is not lost. + */ + logger.log(Level.SEVERE, Bundle.VolatilityProcessor_errorMessage_outputParsingError("psscan"), ex); + } + return fileSet; } - private Set parsePslist(File PluginFile) { + private Set parsePslistOutput(File outputFile) { String line; Set fileSet = new HashSet<>(); - try { - BufferedReader br = new BufferedReader(new FileReader(PluginFile)); - // read the first line from the text file - while ((line = br.readLine()) != null) { - if (line.startsWith("0x") == false) - continue; - + try (BufferedReader br = new BufferedReader(new FileReader(outputFile))) { + // read the first line from the text file + while ((line = br.readLine()) != null) { + if (line.startsWith("0x") == false) { //NON-NLS + continue; + } + // 0x89cfb998 csrss.exe 704 640 14 532 0 0 2017-12-07 14:05:34 UTC+0000 - String file_path; - file_path = line.substring(10, 34); - file_path = normalizePath(file_path); - - // ignore system, it's not really a path - if (file_path.equals("system")) + if (line.length() < 34) continue; + String file_path = line.substring(10, 34); + file_path = normalizePath(file_path); + + // ignore system, it's not really a path + if (file_path.equals("system")) { //NON-NLS + continue; + } fileSet.add(file_path); - } - br.close(); - } catch (IOException ex) { - String msg = "Error parsing pslist output"; - logger.log(Level.SEVERE, msg, ex); - errorMsgs.add(msg); - } - return fileSet; + } + } catch (IOException ex) { + errorMsgs.add(Bundle.VolatilityProcessor_errorMessage_outputParsingError("pslist")); + /* + * Log the exception as well as add it to the error messages, to + * ensure that the stack trace is not lost. + */ + logger.log(Level.SEVERE, Bundle.VolatilityProcessor_errorMessage_outputParsingError("pslist"), ex); + } + return fileSet; } - private Set parsePsxview(File PluginFile) { + private Set parsePsxviewOutput(File outputFile) { String line; Set fileSet = new HashSet<>(); - try { - BufferedReader br = new BufferedReader(new FileReader(PluginFile)); - // ignore the first two header lines - br.readLine(); - br.readLine(); - while ((line = br.readLine()) != null) { + try (BufferedReader br = new BufferedReader(new FileReader(outputFile))) { + // ignore the first two header lines + br.readLine(); + br.readLine(); + while ((line = br.readLine()) != null) { // 0x09adf980 svchost.exe 1368 True True False True True True True - if (line.startsWith("0x") == false) + if (line.startsWith("0x") == false) { //NON-NLS continue; + } + + if (line.length() < 34) { + continue; + } + String file_path = line.substring(11, 34); file_path = normalizePath(file_path); - + // ignore system, it's not really a path - if (file_path.equals("system")) + if (file_path.equals("system")) { //NON-NLS continue; + } fileSet.add(file_path); - } - br.close(); - } catch (IOException ex) { - String msg = "Error parsing psxview output"; - logger.log(Level.SEVERE, msg, ex); - errorMsgs.add(msg); - } - return fileSet; + } + } catch (IOException ex) { + errorMsgs.add(Bundle.VolatilityProcessor_errorMessage_outputParsingError("psxview")); + /* + * Log the exception as well as add it to the error messages, to + * ensure that the stack trace is not lost. + */ + logger.log(Level.SEVERE, Bundle.VolatilityProcessor_errorMessage_outputParsingError("psxview"), ex); + } + return fileSet; } - private Set parsePstree(File PluginFile) { + private Set parsePstreeOutput(File outputFile) { String line; Set fileSet = new HashSet<>(); - try { - BufferedReader br = new BufferedReader(new FileReader(PluginFile)); - // read the first line from the text file - while ((line = br.readLine()) != null) { - // ... 0x897e5020:services.exe 772 728 15 287 2017-12-07 14:05:35 UTC+000 - String file_path; + try (BufferedReader br = new BufferedReader(new FileReader(outputFile))) { + // read the first line from the text file + while ((line = br.readLine()) != null) { + // ... 0x897e5020:services.exe 772 728 15 287 2017-12-07 14:05:35 UTC+000 String TAG = ":"; if (line.contains(TAG)) { - file_path = line.substring(line.indexOf(":") + 1, 52); - file_path = normalizePath(file_path); - - // ignore system, it's not really a path - if (file_path.equals("system")) + int index = line.indexOf(TAG); + if (line.length() < 52 || index + 1 >= 52) { continue; + } + String file_path = line.substring(line.indexOf(':') + 1, 52); //NON-NLS + file_path = normalizePath(file_path); + + // ignore system, it's not really a path + if (file_path.equals("system")) { //NON-NLS + continue; + } fileSet.add(file_path); } - } - br.close(); - } catch (IOException ex) { - String msg = "Error parsing pstree output"; - logger.log(Level.SEVERE, msg, ex); - errorMsgs.add(msg); - } - return fileSet; + } + } catch (IOException ex) { + errorMsgs.add(Bundle.VolatilityProcessor_errorMessage_outputParsingError("pstree")); + /* + * Log the exception as well as add it to the error messages, to + * ensure that the stack trace is not lost. + */ + logger.log(Level.SEVERE, Bundle.VolatilityProcessor_errorMessage_outputParsingError("pstree"), ex); + } + return fileSet; } - private Set parseSvcscan(File PluginFile) { + private Set parseSvcscanOutput(File PluginFile) { String line; Set fileSet = new HashSet<>(); try { @@ -716,11 +773,11 @@ class VolatilityProcessor { while ((line = br.readLine()) != null) { String file_path; String TAG = "Binary Path: "; - if (line.startsWith(TAG)) { + if (line.startsWith(TAG) && line.length() > TAG.length()+1) { if (line.charAt(TAG.length()) == '\"') { file_path = line.substring(TAG.length()+1); if (file_path.contains("\"")) { - file_path = file_path.substring(0, file_path.indexOf("\"")); + file_path = file_path.substring(0, file_path.indexOf('\"')); } } // Binary Path: - @@ -731,7 +788,7 @@ class VolatilityProcessor { else { file_path = line.substring(TAG.length()); if (file_path.contains(" ")) { - file_path = file_path.substring(0, file_path.indexOf(" ")); + file_path = file_path.substring(0, file_path.indexOf(' ')); } // We can't do anything with driver entries if (file_path.startsWith("\\Driver\\")) { @@ -752,8 +809,28 @@ class VolatilityProcessor { } return fileSet; } - + + /** + * Requests cancellation of processing. + */ void cancel() { isCancelled = true; } + + /** + * Exception type thrown when the processor experiences an error condition. + */ + final class VolatilityProcessorException extends Exception { + + private static final long serialVersionUID = 1L; + + private VolatilityProcessorException(String message) { + super(message); + } + + private VolatilityProcessorException(String message, Throwable cause) { + super(message, cause); + } + } + } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java index 88163b357c..62a1837e4d 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java @@ -376,12 +376,12 @@ public class ExtractedContentViewer implements DataContentViewer { if (artifact == null) { return 4; } else if (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { - return 6; + return 7; } else if (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) { try { BlackboardAttribute attribute = artifact.getAttribute(TSK_ACCOUNT_TYPE); if (attribute != null && Account.Type.CREDIT_CARD.getTypeName().equals(attribute.getValueString())) { - return 6; + return 7; } else { return 4; } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java index ad0fc0a678..1ddd06680e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java @@ -645,18 +645,18 @@ final class RegexQuery implements KeywordSearchQuery { */ static private void addAttributeIfNotAlreadyCaptured(Map attributeMap, ATTRIBUTE_TYPE attrType, String groupName, Matcher matcher) { BlackboardAttribute.Type type = new BlackboardAttribute.Type(attrType); - attributeMap.computeIfAbsent(type, t -> { + + if( ! attributeMap.containsKey(type)) { String value = matcher.group(groupName); if (attrType.equals(ATTRIBUTE_TYPE.TSK_CARD_NUMBER)) { attributeMap.put(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_KEYWORD), new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, value)); value = CharMatcher.anyOf(" -").removeFrom(value); } + if (StringUtils.isNotBlank(value)) { - return new BlackboardAttribute(attrType, MODULE_NAME, value); - } else { - return null; + attributeMap.put(type, new BlackboardAttribute(attrType, MODULE_NAME, value)); } - }); + } } } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java index 51f34cfa5c..0b1ec7db35 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java @@ -175,24 +175,24 @@ class ExtractRegistry extends Extract { logger.log(Level.SEVERE, null, ex); } - int j = 0; for (AbstractFile regFile : allRegistryFiles) { String regFileName = regFile.getName(); + long regFileId = regFile.getId(); String regFileNameLocal = RAImageIngestModule.getRATempPath(currentCase, "reg") + File.separator + regFileName; - String outputPathBase = RAImageIngestModule.getRAOutputPath(currentCase, "reg") + File.separator + regFileName + "-regripper-" + Integer.toString(j++); //NON-NLS + String outputPathBase = RAImageIngestModule.getRAOutputPath(currentCase, "reg") + File.separator + regFileName + "-regripper-" + Long.toString(regFileId); //NON-NLS File regFileNameLocalFile = new File(regFileNameLocal); try { ContentUtils.writeToFile(regFile, regFileNameLocalFile, context::dataSourceIngestIsCancelled); } catch (ReadContentInputStreamException ex) { logger.log(Level.WARNING, String.format("Error reading registry file '%s' (id=%d).", - regFile.getName(), regFile.getId()), ex); //NON-NLS + regFile.getName(), regFileId), ex); //NON-NLS this.addErrorMessage( NbBundle.getMessage(this.getClass(), "ExtractRegistry.analyzeRegFiles.errMsg.errWritingTemp", this.getName(), regFileName)); continue; } catch (IOException ex) { logger.log(Level.SEVERE, String.format("Error writing temp registry file '%s' for registry file '%s' (id=%d).", - regFileNameLocal, regFile.getName(), regFile.getId()), ex); //NON-NLS + regFileNameLocal, regFile.getName(), regFileId), ex); //NON-NLS this.addErrorMessage( NbBundle.getMessage(this.getClass(), "ExtractRegistry.analyzeRegFiles.errMsg.errWritingTemp", this.getName(), regFileName)); @@ -205,7 +205,7 @@ class ExtractRegistry extends Extract { try { if (logFile != null) { - logFile.write(Integer.toString(j - 1) + "\t" + regFile.getUniquePath() + "\n"); + logFile.write(Long.toString(regFileId) + "\t" + regFile.getUniquePath() + "\n"); } } catch (TskCoreException | IOException ex) { logger.log(Level.SEVERE, null, ex);