From 3d67c8d2cf2f22d40f954c9b9da94222cf701034 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Thu, 19 Apr 2018 23:55:56 -0400 Subject: [PATCH] Added password detection for Office documents. --- .../EncryptionDetectionFileIngestModule.java | 195 +++++++++++++----- .../EncryptionDetectionModuleFactory.java | 5 +- 2 files changed, 144 insertions(+), 56 deletions(-) mode change 100644 => 100755 Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java mode change 100644 => 100755 Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java old mode 100644 new mode 100755 index 7b48606ea0..89cc99230e --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java @@ -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("
\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("
\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 { /* diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java old mode 100644 new mode 100755 index 27549f648f..7798d5be51 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit 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({