mirror of
https://github.com/elisspace/autopsy.git
synced 2026-10-03 07:49:52 +00:00
Added password detection for Office documents.
This commit is contained in:
Regular → Executable
+141
-54
@@ -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 {
|
||||
/*
|
||||
|
||||
Regular → Executable
+3
-2
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user