diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index f51c94aaca..3aef1e5a2a 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -386,8 +386,15 @@ public class DataResultFilterNode extends FilterNode { NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewFileInDir.text"), c)); } // action to go to the source file of the artifact - actionsList.add(new ViewContextAction( + // action to go to the source file of the artifact + Content fileContent = ban.getLookup().lookup(AbstractFile.class); + if (fileContent == null) { + Content content = ban.getLookup().lookup(Content.class); + actionsList.add(new ViewContextAction("View Source Content in Directory", content)); + } else { + actionsList.add(new ViewContextAction( NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewSrcFileInDir.text"), ban)); + } } Content c = ban.getLookup().lookup(File.class); Node n = null; diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java new file mode 100644 index 0000000000..a269b2bdd2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java @@ -0,0 +1,179 @@ +/* + * Autopsy Forensic Browser + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.modules.encryptiondetection; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.Blackboard; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; +import org.sleuthkit.autopsy.ingest.IngestModule; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModule; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestMessage; +import org.sleuthkit.autopsy.ingest.IngestServices; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.ReadContentInputStream; +import org.sleuthkit.datamodel.Volume; +import org.sleuthkit.datamodel.VolumeSystem; + +/** + * Data source module to detect encryption. + */ +final class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModule { + + private final IngestServices services = IngestServices.getInstance(); + private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName()); + private Blackboard blackboard; + private double calculatedEntropy; + private final double minimumEntropy; + + /** + * Create an EncryptionDetectionDataSourceIngestModule object that will + * detect volumes that are encrypted and create blackboard artifacts as + * appropriate. The supplied EncryptionDetectionIngestJobSettings object is + * used to configure the module. + */ + EncryptionDetectionDataSourceIngestModule(EncryptionDetectionIngestJobSettings settings) { + minimumEntropy = settings.getMinimumEntropy(); + } + + @Override + public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException { + try { + validateSettings(); + blackboard = Case.getOpenCase().getServices().getBlackboard(); + } catch (NoCurrentCaseException ex) { + throw new IngestModule.IngestModuleException("Exception while getting open case.", ex); + } + } + + @Override + public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) { + + try { + if (dataSource instanceof Image) { + List volumeSystems = ((Image) dataSource).getVolumeSystems(); + for (VolumeSystem volumeSystem : volumeSystems) { + for (Volume volume : volumeSystem.getVolumes()) { + if (isVolumeEncrypted(volume)) { + return flagVolume(volume); + } + } + } + } + } catch (ReadContentInputStream.ReadContentInputStreamException ex) { + logger.log(Level.WARNING, String.format("Unable to read data source '%s'", dataSource.getName()), ex); + return IngestModule.ProcessResult.ERROR; + } catch (IOException | TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Unable to process data source '%s'", dataSource.getName()), ex); + return IngestModule.ProcessResult.ERROR; + } + + return IngestModule.ProcessResult.OK; + } + + /** + * Validate the relevant settings for the + * EncryptionDetectionDataSourceIngestModule + * + * @throws IngestModule.IngestModuleException If the input is empty, + * invalid, or out of range. + * + */ + private void validateSettings() throws IngestModule.IngestModuleException { + EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy); + } + + /** + * Create a blackboard artifact. + * + * @param The volume to be processed. + * + * @return 'OK' if the volume was processed successfully, or 'ERROR' if + * there was a problem. + */ + private IngestModule.ProcessResult flagVolume(Volume volume) { + try { + BlackboardArtifact artifact = volume.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED); + + try { + /* + * Index the artifact for keyword search. + */ + blackboard.indexArtifact(artifact); + } catch (Blackboard.BlackboardException ex) { + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS + } + + /* + * Send an event to update the view with the new result. + */ + services.fireModuleDataEvent(new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, Collections.singletonList(artifact))); + + /* + * Make an ingest inbox message. + */ + StringBuilder detailsSb = new StringBuilder(""); + detailsSb.append("File: ").append(volume.getParent().getUniquePath()).append(volume.getName()).append("
\n"); + detailsSb.append("Entropy: ").append(calculatedEntropy); + + services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(), + "Encryption Detected Match: " + volume.getName(), + detailsSb.toString(), + volume.getName(), + artifact)); + + return IngestModule.ProcessResult.OK; + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Failed to create blackboard artifact for '%s'.", volume.getName()), ex); //NON-NLS + return IngestModule.ProcessResult.ERROR; + } + } + + /** + * This method checks if the Volume input is encrypted. Initial + * qualifications require that the Volume not have a file system. + * + * @param volume Volume to be checked. + * + * @return True if the Volume is encrypted. + */ + private boolean isVolumeEncrypted(Volume volume) throws ReadContentInputStream.ReadContentInputStreamException, IOException, TskCoreException { + /* + * Criteria for the checks in this method are partially based on + * http://www.forensicswiki.org/wiki/TrueCrypt#Detection + */ + if (volume.getFileSystems().isEmpty()) { + calculatedEntropy = EncryptionDetectionTools.calculateEntropy(volume); + if (calculatedEntropy >= minimumEntropy) { + return true; + } + } + return false; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java old mode 100755 new mode 100644 index cb92b59718..d075dec1e8 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java @@ -18,18 +18,18 @@ */ package org.sleuthkit.autopsy.modules.encryptiondetection; -import java.io.BufferedInputStream; import java.io.IOException; -import java.io.InputStream; import java.util.Collections; import java.util.logging.Level; +import org.sleuthkit.datamodel.ReadContentInputStream; +import java.io.BufferedInputStream; +import java.io.InputStream; import org.apache.tika.exception.EncryptedDocumentException; import org.apache.tika.exception.TikaException; import org.apache.tika.metadata.Metadata; import org.apache.tika.parser.AutoDetectParser; import org.apache.tika.parser.ParseContext; import org.apache.tika.sax.BodyContentHandler; -import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.Blackboard; @@ -43,31 +43,20 @@ import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; + + /** * File ingest module to detect encryption and password protection. */ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter { - static final double DEFAULT_CONFIG_MINIMUM_ENTROPY = 7.5; - static final int DEFAULT_CONFIG_MINIMUM_FILE_SIZE = 5242880; // 5MB; - static final boolean DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED = true; - static final boolean DEFAULT_CONFIG_SLACK_FILES_ALLOWED = true; - - static final double MINIMUM_ENTROPY_INPUT_RANGE_MIN = 6.0; - static final double MINIMUM_ENTROPY_INPUT_RANGE_MAX = 8.0; - static final int MINIMUM_FILE_SIZE_INPUT_RANGE_MIN = 1; - private static final int FILE_SIZE_MODULUS = 512; - private static final double ONE_OVER_LOG2 = 1.4426950408889634073599246810019; // (1 / log(2)) - private static final int BYTE_OCCURENCES_BUFFER_SIZE = 256; - private final IngestServices services = IngestServices.getInstance(); private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName()); private FileTypeDetector fileTypeDetector; @@ -154,18 +143,9 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter * @throws IngestModule.IngestModuleException If the input is empty, * invalid, or out of range. */ - @NbBundle.Messages({ - "EncryptionDetectionFileIngestModule.errorMessage.minimumEntropyInput=Minimum entropy input must be a number between 6.0 and 8.0.", - "EncryptionDetectionFileIngestModule.errorMessage.minimumFileSizeInput=Minimum file size input must be an integer (in megabytes) of 1 or greater." - }) private void validateSettings() throws IngestModule.IngestModuleException { - if (minimumEntropy < MINIMUM_ENTROPY_INPUT_RANGE_MIN || minimumEntropy > MINIMUM_ENTROPY_INPUT_RANGE_MAX) { - throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionFileIngestModule_errorMessage_minimumEntropyInput()); - } - - if (minimumFileSize < MINIMUM_FILE_SIZE_INPUT_RANGE_MIN) { - throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionFileIngestModule_errorMessage_minimumFileSizeInput()); - } + EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy); + EncryptionDetectionTools.validateMinFileSizeValue(minimumFileSize); } /** @@ -315,72 +295,12 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter /* * Qualify the entropy. */ - calculatedEntropy = calculateEntropy(file); + calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file); if (calculatedEntropy >= minimumEntropy) { possiblyEncrypted = true; } } } - return possiblyEncrypted; } - - /** - * Calculate the entropy of the file. The result is used to qualify the file - * as possibly encrypted. - * - * @param file The file to be calculated against. - * - * @return The entropy of the file. - * - * @throws ReadContentInputStreamException If there is a failure reading - * from the InputStream. - * @throws IOException If there is a failure closing or - * reading from the InputStream. - */ - private double calculateEntropy(AbstractFile file) throws ReadContentInputStreamException, IOException { - /* - * Logic in this method is based on - * https://github.com/willjasen/entropy/blob/master/entropy.java - */ - - InputStream in = null; - BufferedInputStream bin = null; - - try { - in = new ReadContentInputStream(file); - bin = new BufferedInputStream(in); - - /* - * Determine the number of times each byte value appears. - */ - int[] byteOccurences = new int[BYTE_OCCURENCES_BUFFER_SIZE]; - int readByte; - while ((readByte = bin.read()) != -1) { - byteOccurences[readByte]++; - } - - /* - * Calculate the entropy based on the byte occurence counts. - */ - long dataLength = file.getSize() - 1; - double entropyAccumulator = 0; - for (int i = 0; i < BYTE_OCCURENCES_BUFFER_SIZE; i++) { - if (byteOccurences[i] > 0) { - double byteProbability = (double) byteOccurences[i] / (double) dataLength; - entropyAccumulator += (byteProbability * Math.log(byteProbability) * ONE_OVER_LOG2); - } - } - - return -entropyAccumulator; - - } finally { - if (in != null) { - in.close(); - } - if (bin != null) { - bin.close(); - } - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettings.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettings.java index 2aa6ad860d..5acb97a518 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettings.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettings.java @@ -26,7 +26,10 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; final class EncryptionDetectionIngestJobSettings implements IngestModuleIngestJobSettings { private static final long serialVersionUID = 1L; - + private static final double DEFAULT_CONFIG_MINIMUM_ENTROPY = 7.5; + private static final int DEFAULT_CONFIG_MINIMUM_FILE_SIZE = 5242880; // 5MB; + private static final boolean DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED = true; + private static final boolean DEFAULT_CONFIG_SLACK_FILES_ALLOWED = true; private double minimumEntropy; private int minimumFileSize; private boolean fileSizeMultipleEnforced; @@ -36,10 +39,10 @@ final class EncryptionDetectionIngestJobSettings implements IngestModuleIngestJo * Instantiate the ingest job settings with default values. */ EncryptionDetectionIngestJobSettings() { - this.minimumEntropy = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_MINIMUM_ENTROPY; - this.minimumFileSize = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_MINIMUM_FILE_SIZE; - this.fileSizeMultipleEnforced = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED; - this.slackFilesAllowed = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_SLACK_FILES_ALLOWED; + this.minimumEntropy = DEFAULT_CONFIG_MINIMUM_ENTROPY; + this.minimumFileSize = DEFAULT_CONFIG_MINIMUM_FILE_SIZE; + this.fileSizeMultipleEnforced = DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED; + this.slackFilesAllowed = DEFAULT_CONFIG_SLACK_FILES_ALLOWED; } /** diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java index 7798d5be51..7a2d486841 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java @@ -107,11 +107,14 @@ public class EncryptionDetectionModuleFactory implements IngestModuleFactory { @Override public boolean isDataSourceIngestModuleFactory() { - return false; + return true; } @Override public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings settings) { - throw new UnsupportedOperationException(); + if (!(settings instanceof EncryptionDetectionIngestJobSettings)) { + throw new IllegalArgumentException("Expected settings argument to be an instance of EncryptionDetectionIngestJobSettings."); + } + return new EncryptionDetectionDataSourceIngestModule((EncryptionDetectionIngestJobSettings) settings); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java new file mode 100644 index 0000000000..99dbc0aaeb --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java @@ -0,0 +1,131 @@ +/* + * Autopsy Forensic Browser + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.modules.encryptiondetection; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.ingest.IngestModule; +import org.sleuthkit.datamodel.ReadContentInputStream; +import org.sleuthkit.datamodel.Content; + +/** + * Class containing common methods concerning the Encryption Detection module. + */ +final class EncryptionDetectionTools { + + private static final double ONE_OVER_LOG2 = 1.4426950408889634073599246810019; // (1 / log(2)) + private static final int BYTE_OCCURENCES_BUFFER_SIZE = 256; + static final double MINIMUM_ENTROPY_INPUT_RANGE_MIN = 6.0; + static final double MINIMUM_ENTROPY_INPUT_RANGE_MAX = 8.0; + static final int MINIMUM_FILE_SIZE_INPUT_RANGE_MIN = 1; + + @NbBundle.Messages({ + "EncryptionDetectionTools.errorMessage.minimumEntropyInput=Minimum entropy input must be a number between 6.0 and 8.0." + }) + /** + * Check if the minimum entropy setting is in the accepted range for this + * module. + */ + static void validateMinEntropyValue(double minimumEntropy) throws IngestModule.IngestModuleException { + if (minimumEntropy < MINIMUM_ENTROPY_INPUT_RANGE_MIN || minimumEntropy > MINIMUM_ENTROPY_INPUT_RANGE_MAX) { + throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionTools_errorMessage_minimumEntropyInput()); + } + } + + @NbBundle.Messages({ + "EncryptionDetectionTools.errorMessage.minimumFileSizeInput=Minimum file size input must be an integer (in megabytes) of 1 or greater." + }) + /** + * Check if the minimum file size setting is in the accepted range for this + * module. + */ + static void validateMinFileSizeValue(int minimumFileSize) throws IngestModule.IngestModuleException { + if (minimumFileSize < MINIMUM_FILE_SIZE_INPUT_RANGE_MIN) { + throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionTools_errorMessage_minimumFileSizeInput()); + } + } + + + /** + * Calculate the entropy of the content. The result is used to qualify the + * content as possibly encrypted. + * + * @param content The content to be calculated against. + * + * @return The entropy of the content. + * + * @throws ReadContentInputStreamException If there is a failure reading + * from the InputStream. + * @throws IOException If there is a failure closing or + * reading from the InputStream. + */ + static double calculateEntropy(Content content) throws ReadContentInputStream.ReadContentInputStreamException, IOException { + /* + * Logic in this method is based on + * https://github.com/willjasen/entropy/blob/master/entropy.java + */ + + InputStream in = null; + BufferedInputStream bin = null; + + try { + in = new ReadContentInputStream(content); + bin = new BufferedInputStream(in); + + /* + * Determine the number of times each byte value appears. + */ + int[] byteOccurences = new int[BYTE_OCCURENCES_BUFFER_SIZE]; + int readByte; + while ((readByte = bin.read()) != -1) { + byteOccurences[readByte]++; + } + + /* + * Calculate the entropy based on the byte occurence counts. + */ + long dataLength = content.getSize() - 1; + double entropyAccumulator = 0; + for (int i = 0; i < BYTE_OCCURENCES_BUFFER_SIZE; i++) { + if (byteOccurences[i] > 0) { + double byteProbability = (double) byteOccurences[i] / (double) dataLength; + entropyAccumulator += (byteProbability * Math.log(byteProbability) * ONE_OVER_LOG2); + } + } + + return -entropyAccumulator; + + } finally { + if (in != null) { + in.close(); + } + if (bin != null) { + bin.close(); + } + } + } + + /** + * Private constructor for Encryption Detection Tools class. + */ + private EncryptionDetectionTools() { + } +}