Added password detection for Office documents.

This commit is contained in:
U-BASIS\dgrove
2018-04-19 23:55:56 -04:00
parent fa621583e9
commit 3d67c8d2cf
2 changed files with 144 additions and 56 deletions
@@ -23,6 +23,12 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.logging.Level;
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;
@@ -41,9 +47,11 @@ 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.
* File ingest module to detect encryption and password protection.
*/
final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter {
@@ -73,9 +81,10 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
/**
* Create a EncryptionDetectionFileIngestModule object that will detect
* files that are encrypted and create blackboard artifacts as appropriate.
* The supplied EncryptionDetectionIngestJobSettings object is used to
* configure the module.
* files that are either encrypted or password protected and create
* blackboard artifacts as appropriate. The supplied
* EncryptionDetectionIngestJobSettings object is used to configure the
* module.
*/
EncryptionDetectionFileIngestModule(EncryptionDetectionIngestJobSettings settings) {
minimumEntropy = settings.getMinimumEntropy();
@@ -101,13 +110,37 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
public IngestModule.ProcessResult process(AbstractFile file) {
try {
if (isFileEncrypted(file)) {
return flagFile(file);
/*
* Qualify the file type.
*/
if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR)
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR)
&& (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed)) {
/*
* Qualify the file against hash databases.
*/
if (!file.getKnown().equals(TskData.FileKnown.KNOWN)) {
/*
* Qualify the MIME type.
*/
String mimeType = fileTypeDetector.getMIMEType(file);
if (mimeType.equals("application/octet-stream")) {
if (isFileEncryptionSuspected(file)) {
return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED);
}
} else {
if (isFilePasswordProtected(file)) {
return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED);
}
}
}
}
} catch (ReadContentInputStreamException ex) {
} catch (ReadContentInputStreamException | SAXException | TikaException ex) {
logger.log(Level.WARNING, String.format("Unable to read file '%s'", file.getParentPath() + file.getName()), ex);
return IngestModule.ProcessResult.ERROR;
} catch (IOException | TskCoreException ex) {
} catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Unable to process file '%s'", file.getParentPath() + file.getName()), ex);
return IngestModule.ProcessResult.ERROR;
}
@@ -138,14 +171,15 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
/**
* Create a blackboard artifact.
*
* @param The file to be processed.
* @param file The file to be processed.
* @param artifactType The type of artifact to create.
*
* @return 'OK' if the file was processed successfully, or 'ERROR' if there
* was a problem.
*/
private IngestModule.ProcessResult flagFile(AbstractFile file) {
private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType) {
try {
BlackboardArtifact artifact = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED);
BlackboardArtifact artifact = file.newArtifact(artifactType);
try {
/*
@@ -159,17 +193,19 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
/*
* 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)));
services.fireModuleDataEvent(new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), artifactType, Collections.singletonList(artifact)));
/*
* Make an ingest inbox message.
*/
StringBuilder detailsSb = new StringBuilder();
detailsSb.append("File: ").append(file.getParentPath()).append(file.getName()).append("<br/>\n");
detailsSb.append("Entropy: ").append(calculatedEntropy);
detailsSb.append("File: ").append(file.getParentPath()).append(file.getName());
if (artifactType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED)) {
detailsSb.append("<br/>\n").append("Entropy: ").append(calculatedEntropy);
}
services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(),
"Encryption Detected Match: " + file.getName(),
artifactType.getDisplayName() + " Match: " + file.getName(),
detailsSb.toString(),
file.getName(),
artifact));
@@ -182,16 +218,86 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
}
/**
* This method checks if the AbstractFile input is encrypted. Initial
* qualifications require that it be an actual file that is not known, meets
* file size requirements, and has a MIME type of
* 'application/octet-stream'.
* This method checks if the AbstractFile input is password protected.
*
* @param file AbstractFile to be checked.
*
* @return True if the AbstractFile is encrypted.
* @return True if the file is password protected.
*
* @throws ReadContentInputStreamException If there is a failure reading
* from the InputStream.
* @throws IOException If there is a failure closing or
* reading from the InputStream.
* @throws SAXException If there was an issue parsing the
* file with Tika.
* @throws TikaException If there was an issue parsing the
* file with Tika.
*/
private boolean isFileEncrypted(AbstractFile file) throws ReadContentInputStreamException, IOException, TskCoreException {
private boolean isFilePasswordProtected(AbstractFile file) throws ReadContentInputStreamException, IOException, SAXException, TikaException {
boolean passwordProtected = false;
switch (file.getMIMEType()) {
case "application/x-ooxml-protected":
/*
* Office Open XML files that are password protected can be
* determined so simply by checking the MIME type.
*/
passwordProtected = true;
break;
case "application/msword":
case "application/vnd.ms-excel":
case "application/vnd.ms-powerpoint":
/*
* A file of one of these types will be determined to be
* password protected or not by attempting to parse it via Tika.
*/
InputStream in = null;
BufferedInputStream bin = null;
try {
in = new ReadContentInputStream(file);
bin = new BufferedInputStream(in);
ContentHandler handler = new BodyContentHandler(-1);
Metadata metadata = new Metadata();
metadata.add(Metadata.RESOURCE_NAME_KEY, file.getName());
AutoDetectParser parser = new AutoDetectParser();
parser.parse(bin, handler, metadata, new ParseContext());
} catch (EncryptedDocumentException ex) {
/*
* Office OLE2 file is determined to be password protected.
*/
passwordProtected = true;
} finally {
if (in != null) {
in.close();
}
if (bin != null) {
bin.close();
}
}
}
return passwordProtected;
}
/**
* This method checks if the AbstractFile input is encrypted. It must meet
* file size requirements before its entropy is calculated. If the entropy
* result meets the minimum entropy value set, the file will be considered
* to be possibly encrypted.
*
* @param file AbstractFile to be checked.
*
* @return True if encryption is suspected.
*
* @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 boolean isFileEncryptionSuspected(AbstractFile file) throws ReadContentInputStreamException, IOException {
/*
* Criteria for the checks in this method are partially based on
* http://www.forensicswiki.org/wiki/TrueCrypt#Detection
@@ -200,55 +306,36 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
boolean possiblyEncrypted = false;
/*
* Qualify the file type.
* Qualify the size.
*/
if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR)
&& !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR)
&& (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed)) {
/*
* Qualify the file against hash databases.
*/
if (!file.getKnown().equals(TskData.FileKnown.KNOWN)) {
long contentSize = file.getSize();
if (contentSize >= minimumFileSize) {
if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) {
/*
* Qualify the size.
* Qualify the entropy.
*/
long contentSize = file.getSize();
if (contentSize >= minimumFileSize) {
if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) {
/*
* Qualify the MIME type.
*/
String mimeType = fileTypeDetector.getMIMEType(file);
if (mimeType.equals("application/octet-stream")) {
possiblyEncrypted = true;
}
}
calculatedEntropy = calculateEntropy(file);
if (calculatedEntropy >= minimumEntropy) {
possiblyEncrypted = true;
}
}
}
if (possiblyEncrypted) {
calculatedEntropy = calculateEntropy(file);
if (calculatedEntropy >= minimumEntropy) {
return true;
}
}
return false;
return possiblyEncrypted;
}
/**
* Calculate the entropy of the file. The result is used to qualify the file
* as an encrypted file.
* as possibly encrypted.
*
* @param file The file to be calculated against.
*
* @return The entropy of the file.
*
* @throws IOException If there is a failure closing or reading from the
* InputStream.
* @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 {
/*
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2017 Basis Technology Corp.
* Copyright 2017-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,7 +30,8 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel;
/**
* A factory that creates file ingest modules that detect encryption.
* A factory that creates file ingest modules that detect encryption and
* password protection.
*/
@ServiceProvider(service = IngestModuleFactory.class)
@Messages({