Merge pull request #3719 from wschaeferB/3752-EncryptedPartitionsSupport

3752 encrypted partitions support
This commit is contained in:
Richard Cordovano 2018-04-27 16:53:24 -04:00 committed by GitHub
commit eb01a3e899
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 339 additions and 96 deletions

View File

@ -386,8 +386,15 @@ public class DataResultFilterNode extends FilterNode {
NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewFileInDir.text"), c)); NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewFileInDir.text"), c));
} }
// action to go to the source file of the artifact // 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)); NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewSrcFileInDir.text"), ban));
}
} }
Content c = ban.getLookup().lookup(File.class); Content c = ban.getLookup().lookup(File.class);
Node n = null; Node n = null;

View File

@ -0,0 +1,179 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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<VolumeSystem> 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("<br/>\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;
}
}

View File

@ -18,18 +18,18 @@
*/ */
package org.sleuthkit.autopsy.modules.encryptiondetection; package org.sleuthkit.autopsy.modules.encryptiondetection;
import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.Collections; import java.util.Collections;
import java.util.logging.Level; 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.EncryptedDocumentException;
import org.apache.tika.exception.TikaException; import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata; import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser; import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext; import org.apache.tika.parser.ParseContext;
import org.apache.tika.sax.BodyContentHandler; import org.apache.tika.sax.BodyContentHandler;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.Blackboard; 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.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData;
import org.xml.sax.ContentHandler; import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
/** /**
* File ingest module to detect encryption and password protection. * File ingest module to detect encryption and password protection.
*/ */
final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter { 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 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 IngestServices services = IngestServices.getInstance();
private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName()); private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName());
private FileTypeDetector fileTypeDetector; private FileTypeDetector fileTypeDetector;
@ -154,18 +143,9 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
* @throws IngestModule.IngestModuleException If the input is empty, * @throws IngestModule.IngestModuleException If the input is empty,
* invalid, or out of range. * 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 { private void validateSettings() throws IngestModule.IngestModuleException {
if (minimumEntropy < MINIMUM_ENTROPY_INPUT_RANGE_MIN || minimumEntropy > MINIMUM_ENTROPY_INPUT_RANGE_MAX) { EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy);
throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionFileIngestModule_errorMessage_minimumEntropyInput()); EncryptionDetectionTools.validateMinFileSizeValue(minimumFileSize);
}
if (minimumFileSize < MINIMUM_FILE_SIZE_INPUT_RANGE_MIN) {
throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionFileIngestModule_errorMessage_minimumFileSizeInput());
}
} }
/** /**
@ -315,72 +295,12 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
/* /*
* Qualify the entropy. * Qualify the entropy.
*/ */
calculatedEntropy = calculateEntropy(file); calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file);
if (calculatedEntropy >= minimumEntropy) { if (calculatedEntropy >= minimumEntropy) {
possiblyEncrypted = true; possiblyEncrypted = true;
} }
} }
} }
return possiblyEncrypted; 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();
}
}
}
} }

View File

@ -26,7 +26,10 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
final class EncryptionDetectionIngestJobSettings implements IngestModuleIngestJobSettings { final class EncryptionDetectionIngestJobSettings implements IngestModuleIngestJobSettings {
private static final long serialVersionUID = 1L; 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 double minimumEntropy;
private int minimumFileSize; private int minimumFileSize;
private boolean fileSizeMultipleEnforced; private boolean fileSizeMultipleEnforced;
@ -36,10 +39,10 @@ final class EncryptionDetectionIngestJobSettings implements IngestModuleIngestJo
* Instantiate the ingest job settings with default values. * Instantiate the ingest job settings with default values.
*/ */
EncryptionDetectionIngestJobSettings() { EncryptionDetectionIngestJobSettings() {
this.minimumEntropy = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_MINIMUM_ENTROPY; this.minimumEntropy = DEFAULT_CONFIG_MINIMUM_ENTROPY;
this.minimumFileSize = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_MINIMUM_FILE_SIZE; this.minimumFileSize = DEFAULT_CONFIG_MINIMUM_FILE_SIZE;
this.fileSizeMultipleEnforced = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED; this.fileSizeMultipleEnforced = DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED;
this.slackFilesAllowed = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_SLACK_FILES_ALLOWED; this.slackFilesAllowed = DEFAULT_CONFIG_SLACK_FILES_ALLOWED;
} }
/** /**

View File

@ -107,11 +107,14 @@ public class EncryptionDetectionModuleFactory implements IngestModuleFactory {
@Override @Override
public boolean isDataSourceIngestModuleFactory() { public boolean isDataSourceIngestModuleFactory() {
return false; return true;
} }
@Override @Override
public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings settings) { 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);
} }
} }

View File

@ -0,0 +1,131 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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() {
}
}