19 package org.sleuthkit.autopsy.modules.encryptiondetection;
 
   21 import com.healthmarketscience.jackcess.CryptCodecProvider;
 
   22 import com.healthmarketscience.jackcess.Database;
 
   23 import com.healthmarketscience.jackcess.DatabaseBuilder;
 
   24 import com.healthmarketscience.jackcess.InvalidCredentialsException;
 
   25 import com.healthmarketscience.jackcess.impl.CodecProvider;
 
   26 import com.healthmarketscience.jackcess.impl.UnsupportedCodecException;
 
   27 import com.healthmarketscience.jackcess.util.MemFileChannel;
 
   28 import java.io.IOException;
 
   29 import java.util.Collections;
 
   30 import java.util.logging.Level;
 
   32 import java.io.BufferedInputStream;
 
   33 import java.io.InputStream;
 
   34 import java.nio.BufferUnderflowException;
 
   35 import org.apache.tika.exception.EncryptedDocumentException;
 
   36 import org.apache.tika.exception.TikaException;
 
   37 import org.apache.tika.metadata.Metadata;
 
   38 import org.apache.tika.parser.AutoDetectParser;
 
   39 import org.apache.tika.parser.ParseContext;
 
   40 import org.apache.tika.sax.BodyContentHandler;
 
   41 import org.openide.util.NbBundle.Messages;
 
   56 import org.
sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
 
   59 import org.xml.sax.ContentHandler;
 
   60 import org.xml.sax.SAXException;
 
   65 final class EncryptionDetectionFileIngestModule 
extends FileIngestModuleAdapter {
 
   67     private static final int FILE_SIZE_MODULUS = 512;
 
   69     private static final String DATABASE_FILE_EXTENSION = 
"db";
 
   70     private static final int MINIMUM_DATABASE_FILE_SIZE = 65536; 
 
   72     private static final String MIME_TYPE_OOXML_PROTECTED = 
"application/x-ooxml-protected";
 
   73     private static final String MIME_TYPE_MSWORD = 
"application/msword";
 
   74     private static final String MIME_TYPE_MSEXCEL = 
"application/vnd.ms-excel";
 
   75     private static final String MIME_TYPE_MSPOWERPOINT = 
"application/vnd.ms-powerpoint";
 
   76     private static final String MIME_TYPE_MSACCESS = 
"application/x-msaccess";
 
   77     private static final String MIME_TYPE_PDF = 
"application/pdf";
 
   79     private static final String[] FILE_IGNORE_LIST = {
"hiberfile.sys", 
"pagefile.sys"};
 
   81     private final IngestServices services = IngestServices.getInstance();
 
   82     private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName());
 
   83     private FileTypeDetector fileTypeDetector;
 
   84     private Blackboard blackboard;
 
   85     private double calculatedEntropy;
 
   87     private final double minimumEntropy;
 
   88     private final int minimumFileSize;
 
   89     private final boolean fileSizeMultipleEnforced;
 
   90     private final boolean slackFilesAllowed;
 
   99     EncryptionDetectionFileIngestModule(EncryptionDetectionIngestJobSettings settings) {
 
  100         minimumEntropy = settings.getMinimumEntropy();
 
  101         minimumFileSize = settings.getMinimumFileSize();
 
  102         fileSizeMultipleEnforced = settings.isFileSizeMultipleEnforced();
 
  103         slackFilesAllowed = settings.isSlackFilesAllowed();
 
  107     public void startUp(IngestJobContext context) 
throws IngestModule.IngestModuleException {
 
  110             blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard();
 
  111             fileTypeDetector = 
new FileTypeDetector();
 
  112         } 
catch (FileTypeDetector.FileTypeDetectorInitException ex) {
 
  113             throw new IngestModule.IngestModuleException(
"Failed to create file type detector", ex);
 
  114         } 
catch (NoCurrentCaseException ex) {
 
  115             throw new IngestModule.IngestModuleException(
"Exception while getting open case.", ex);
 
  120         "EncryptionDetectionFileIngestModule.artifactComment.password=Password protection detected.",
 
  121         "EncryptionDetectionFileIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f)." 
  124     public IngestModule.ProcessResult process(AbstractFile file) {
 
  131             if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)
 
  132                     && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)
 
  133                     && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR)
 
  134                     && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR)
 
  135                     && (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed)
 
  136                     && !file.getKnown().equals(TskData.FileKnown.KNOWN)
 
  137                     && !file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC)) {
 
  141                 String filePath = file.getParentPath();
 
  142                 if (filePath.equals(
"/")) {
 
  143                     String fileName = file.getName();
 
  144                     for (String listEntry : FILE_IGNORE_LIST) {
 
  145                         if (fileName.equalsIgnoreCase(listEntry)) {
 
  147                             return IngestModule.ProcessResult.OK;
 
  155                 String mimeType = fileTypeDetector.getMIMEType(file);
 
  156                 if (mimeType.equals(
"application/octet-stream") && isFileEncryptionSuspected(file)) {
 
  157                     return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED,
 
  158                             String.format(Bundle.EncryptionDetectionFileIngestModule_artifactComment_suspected(), calculatedEntropy));
 
  159                 } 
else if (isFilePasswordProtected(file)) {
 
  160                     return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, Bundle.EncryptionDetectionFileIngestModule_artifactComment_password());
 
  163         } 
catch (ReadContentInputStreamException | SAXException | TikaException | UnsupportedCodecException ex) {
 
  164             logger.log(Level.WARNING, String.format(
"Unable to read file '%s'", file.getParentPath() + file.getName()), ex);
 
  165             return IngestModule.ProcessResult.ERROR;
 
  166         } 
catch (IOException ex) {
 
  167             logger.log(Level.SEVERE, String.format(
"Unable to process file '%s'", file.getParentPath() + file.getName()), ex);
 
  168             return IngestModule.ProcessResult.ERROR;
 
  171         return IngestModule.ProcessResult.OK;
 
  180     private void validateSettings() throws IngestModule.IngestModuleException {
 
  181         EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy);
 
  182         EncryptionDetectionTools.validateMinFileSizeValue(minimumFileSize);
 
  195     private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType, String comment) {
 
  197             BlackboardArtifact artifact = file.newArtifact(artifactType);
 
  198             artifact.addAttribute(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT,
 
  199                     EncryptionDetectionModuleFactory.getModuleName(), comment));
 
  205                 blackboard.indexArtifact(artifact);
 
  206             } 
catch (Blackboard.BlackboardException ex) {
 
  207                 logger.log(Level.SEVERE, 
"Unable to index blackboard artifact " + artifact.getArtifactID(), ex); 
 
  213             services.fireModuleDataEvent(
new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), artifactType, Collections.singletonList(artifact)));
 
  218             StringBuilder detailsSb = 
new StringBuilder();
 
  219             detailsSb.append(
"File: ").append(file.getParentPath()).append(file.getName());
 
  220             if (artifactType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED)) {
 
  221                 detailsSb.append(
"<br/>\nEntropy: ").append(calculatedEntropy);
 
  224             services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(),
 
  225                     artifactType.getDisplayName() + 
" Match: " + file.getName(),
 
  226                     detailsSb.toString(),
 
  230             return IngestModule.ProcessResult.OK;
 
  231         } 
catch (TskCoreException ex) {
 
  232             logger.log(Level.SEVERE, String.format(
"Failed to create blackboard artifact for '%s'.", file.getParentPath() + file.getName()), ex); 
 
  233             return IngestModule.ProcessResult.ERROR;
 
  256     private boolean isFilePasswordProtected(AbstractFile file) 
throws ReadContentInputStreamException, IOException, SAXException, TikaException, UnsupportedCodecException {
 
  258         boolean passwordProtected = 
false;
 
  260         switch (file.getMIMEType()) {
 
  261             case MIME_TYPE_OOXML_PROTECTED:
 
  266                 passwordProtected = 
true;
 
  269             case MIME_TYPE_MSWORD:
 
  270             case MIME_TYPE_MSEXCEL:
 
  271             case MIME_TYPE_MSPOWERPOINT:
 
  272             case MIME_TYPE_PDF: {
 
  277                 InputStream in = null;
 
  278                 BufferedInputStream bin = null;
 
  281                     in = 
new ReadContentInputStream(file);
 
  282                     bin = 
new BufferedInputStream(in);
 
  283                     ContentHandler handler = 
new BodyContentHandler(-1);
 
  284                     Metadata metadata = 
new Metadata();
 
  285                     metadata.add(Metadata.RESOURCE_NAME_KEY, file.getName());
 
  286                     AutoDetectParser parser = 
new AutoDetectParser();
 
  287                     parser.parse(bin, handler, metadata, 
new ParseContext());
 
  288                 } 
catch (EncryptedDocumentException ex) {
 
  292                     passwordProtected = 
true;
 
  304             case MIME_TYPE_MSACCESS: {
 
  312                 InputStream in = null;
 
  313                 BufferedInputStream bin = null;
 
  316                     in = 
new ReadContentInputStream(file);
 
  317                     bin = 
new BufferedInputStream(in);
 
  318                     MemFileChannel memFileChannel = MemFileChannel.newChannel(bin);
 
  319                     CodecProvider codecProvider = 
new CryptCodecProvider();
 
  320                     DatabaseBuilder databaseBuilder = 
new DatabaseBuilder();
 
  321                     databaseBuilder.setChannel(memFileChannel);
 
  322                     databaseBuilder.setCodecProvider(codecProvider);
 
  323                     Database accessDatabase;
 
  325                         accessDatabase = databaseBuilder.open();
 
  326                     } 
catch (IOException | BufferUnderflowException | IndexOutOfBoundsException ignored) {
 
  327                         return passwordProtected; 
 
  334                     if (accessDatabase.getDatabasePassword() != null) {
 
  335                         passwordProtected = 
true;
 
  337                 } 
catch (InvalidCredentialsException ex) {
 
  341                     passwordProtected = 
true;
 
  353         return passwordProtected;
 
  371     private boolean isFileEncryptionSuspected(AbstractFile file) 
throws ReadContentInputStreamException, IOException {
 
  377         boolean possiblyEncrypted = 
false;
 
  382         boolean fileSizeQualified = 
false;
 
  383         String fileExtension = file.getNameExtension();
 
  384         long contentSize = file.getSize();
 
  386         if (fileExtension.equalsIgnoreCase(DATABASE_FILE_EXTENSION)) {
 
  387             if (contentSize >= MINIMUM_DATABASE_FILE_SIZE) {
 
  388                 fileSizeQualified = 
true;
 
  390         } 
else if (contentSize >= minimumFileSize) {
 
  391             if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) {
 
  392                 fileSizeQualified = 
true;
 
  396         if (fileSizeQualified) {
 
  400             calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file);
 
  401             if (calculatedEntropy >= minimumEntropy) {
 
  402                 possiblyEncrypted = 
true;
 
  406         return possiblyEncrypted;