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.BufferedInputStream;
 
   29 import java.io.IOException;
 
   30 import java.io.InputStream;
 
   31 import java.util.Arrays;
 
   32 import java.util.logging.Level;
 
   33 import org.apache.tika.exception.EncryptedDocumentException;
 
   34 import org.apache.tika.exception.TikaException;
 
   35 import org.apache.tika.metadata.Metadata;
 
   36 import org.apache.tika.parser.AutoDetectParser;
 
   37 import org.apache.tika.parser.ParseContext;
 
   38 import org.apache.tika.sax.BodyContentHandler;
 
   39 import org.openide.util.NbBundle.Messages;
 
   54 import org.
sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
 
   58 import org.xml.sax.ContentHandler;
 
   59 import org.xml.sax.SAXException;
 
   64 final class EncryptionDetectionFileIngestModule 
extends FileIngestModuleAdapter {
 
   66     private static final int FILE_SIZE_MODULUS = 512;
 
   68     private static final String DATABASE_FILE_EXTENSION = 
"db";
 
   69     private static final int MINIMUM_DATABASE_FILE_SIZE = 65536; 
 
   71     private static final String MIME_TYPE_OOXML_PROTECTED = 
"application/x-ooxml-protected";
 
   72     private static final String MIME_TYPE_MSWORD = 
"application/msword";
 
   73     private static final String MIME_TYPE_MSEXCEL = 
"application/vnd.ms-excel";
 
   74     private static final String MIME_TYPE_MSPOWERPOINT = 
"application/vnd.ms-powerpoint";
 
   75     private static final String MIME_TYPE_MSACCESS = 
"application/x-msaccess";
 
   76     private static final String MIME_TYPE_PDF = 
"application/pdf";
 
   78     private static final String[] FILE_IGNORE_LIST = {
"hiberfile.sys", 
"pagefile.sys"};
 
   80     private final IngestServices services = IngestServices.getInstance();
 
   81     private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName());
 
   82     private FileTypeDetector fileTypeDetector;
 
   83     private Blackboard blackboard;
 
   84     private IngestJobContext context;
 
   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             this.context = context;
 
  111             blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
 
  113             fileTypeDetector = 
new FileTypeDetector();
 
  114         } 
catch (FileTypeDetector.FileTypeDetectorInitException ex) {
 
  115             throw new IngestModule.IngestModuleException(
"Failed to create file type detector", ex);
 
  116         } 
catch (NoCurrentCaseException ex) {
 
  117             throw new IngestModule.IngestModuleException(
"Exception while getting open case.", ex);
 
  122         "EncryptionDetectionFileIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f)." 
  125     public IngestModule.ProcessResult process(AbstractFile file) {
 
  132             if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)
 
  133                     && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)
 
  134                     && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR)
 
  135                     && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR)
 
  136                     && (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed)
 
  137                     && !file.getKnown().equals(TskData.FileKnown.KNOWN)
 
  138                     && !file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC)) {
 
  142                 String filePath = file.getParentPath();
 
  143                 if (filePath.equals(
"/")) {
 
  144                     String fileName = file.getName();
 
  145                     for (String listEntry : FILE_IGNORE_LIST) {
 
  146                         if (fileName.equalsIgnoreCase(listEntry)) {
 
  148                             return IngestModule.ProcessResult.OK;
 
  156                 String mimeType = fileTypeDetector.getMIMEType(file);
 
  157                 if (mimeType.equals(
"application/octet-stream") && isFileEncryptionSuspected(file)) {
 
  158                     return flagFile(file, BlackboardArtifact.Type.TSK_ENCRYPTION_SUSPECTED, Score.SCORE_LIKELY_NOTABLE,
 
  159                             String.format(Bundle.EncryptionDetectionFileIngestModule_artifactComment_suspected(), calculatedEntropy));
 
  160                 } 
else if (isFilePasswordProtected(file)) {
 
  161                     return flagFile(file, BlackboardArtifact.Type.TSK_ENCRYPTION_DETECTED, Score.SCORE_NOTABLE, 
 
  162                     EncryptionDetectionModuleFactory.PASSWORD_PROTECT_MESSAGE);
 
  165         } 
catch (ReadContentInputStreamException | SAXException | TikaException | UnsupportedCodecException ex) {
 
  166             logger.log(Level.WARNING, String.format(
"Unable to read file '%s'", file.getParentPath() + file.getName()), ex);
 
  167             return IngestModule.ProcessResult.ERROR;
 
  168         } 
catch (IOException ex) {
 
  169             logger.log(Level.SEVERE, String.format(
"Unable to process file '%s'", file.getParentPath() + file.getName()), ex);
 
  170             return IngestModule.ProcessResult.ERROR;
 
  173         return IngestModule.ProcessResult.OK;
 
  182     private void validateSettings() throws IngestModule.IngestModuleException {
 
  183         EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy);
 
  184         EncryptionDetectionTools.validateMinFileSizeValue(minimumFileSize);
 
  199     private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.Type artifactType, Score score, String comment) {
 
  201             if (context.fileIngestIsCancelled()) {
 
  202                 return IngestModule.ProcessResult.OK;
 
  205             BlackboardArtifact artifact = file.newAnalysisResult(artifactType, score, null, null, comment, 
 
  206                     Arrays.asList(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT,
 
  207                     EncryptionDetectionModuleFactory.getModuleName(), comment)))
 
  208                     .getAnalysisResult();
 
  215                 blackboard.postArtifact(artifact, EncryptionDetectionModuleFactory.getModuleName());
 
  216             } 
catch (Blackboard.BlackboardException ex) {
 
  217                 logger.log(Level.SEVERE, 
"Unable to index blackboard artifact " + artifact.getArtifactID(), ex); 
 
  223             StringBuilder detailsSb = 
new StringBuilder();
 
  224             detailsSb.append(
"File: ").append(file.getParentPath()).append(file.getName());
 
  225             if (artifactType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED)) {
 
  226                 detailsSb.append(
"<br/>\nEntropy: ").append(calculatedEntropy);
 
  229             services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(),
 
  230                     artifactType.getDisplayName() + 
" Match: " + file.getName(),
 
  231                     detailsSb.toString(),
 
  235             return IngestModule.ProcessResult.OK;
 
  236         } 
catch (TskCoreException ex) {
 
  237             logger.log(Level.SEVERE, String.format(
"Failed to create blackboard artifact for '%s'.", file.getParentPath() + file.getName()), ex); 
 
  238             return IngestModule.ProcessResult.ERROR;
 
  261     private boolean isFilePasswordProtected(AbstractFile file) 
throws ReadContentInputStreamException, IOException, SAXException, TikaException, UnsupportedCodecException {
 
  263         boolean passwordProtected = 
false;
 
  265         switch (file.getMIMEType()) {
 
  266             case MIME_TYPE_OOXML_PROTECTED:
 
  271                 passwordProtected = 
true;
 
  274             case MIME_TYPE_MSWORD:
 
  275             case MIME_TYPE_MSEXCEL:
 
  276             case MIME_TYPE_MSPOWERPOINT:
 
  277             case MIME_TYPE_PDF: {
 
  282                 InputStream in = null;
 
  283                 BufferedInputStream bin = null;
 
  286                     in = 
new ReadContentInputStream(file);
 
  287                     bin = 
new BufferedInputStream(in);
 
  288                     ContentHandler handler = 
new BodyContentHandler(-1);
 
  289                     Metadata metadata = 
new Metadata();
 
  290                     metadata.add(Metadata.RESOURCE_NAME_KEY, file.getName());
 
  291                     AutoDetectParser parser = 
new AutoDetectParser();
 
  292                     parser.parse(bin, handler, metadata, 
new ParseContext());
 
  293                 } 
catch (EncryptedDocumentException ex) {
 
  297                     passwordProtected = 
true;
 
  309             case MIME_TYPE_MSACCESS: {
 
  317                 InputStream in = null;
 
  318                 BufferedInputStream bin = null;
 
  321                     in = 
new ReadContentInputStream(file);
 
  322                     bin = 
new BufferedInputStream(in);
 
  323                     MemFileChannel memFileChannel = MemFileChannel.newChannel(bin);
 
  324                     CodecProvider codecProvider = 
new CryptCodecProvider();
 
  325                     DatabaseBuilder databaseBuilder = 
new DatabaseBuilder();
 
  326                     databaseBuilder.setChannel(memFileChannel);
 
  327                     databaseBuilder.setCodecProvider(codecProvider);
 
  328                     Database accessDatabase;
 
  330                         accessDatabase = databaseBuilder.open();
 
  331                     } 
catch (InvalidCredentialsException ex) {
 
  332                         logger.log(Level.INFO, String.format(
 
  333                                 "Jackcess throws invalid credentials exception for file (name: %s, id: %s).  It will be assumed to be password protected.",
 
  334                                 file.getName(), file.getId()));
 
  336                     } 
catch (Exception ex) { 
 
  337                         logger.log(Level.WARNING, String.format(
"Unexpected exception " 
  338                                 + 
"trying to open msaccess database using Jackcess " 
  339                                 + 
"(name: %s, id: %d)", file.getName(), file.getId()), ex);
 
  340                         return passwordProtected;
 
  347                     if (accessDatabase.getDatabasePassword() != null) {
 
  348                         passwordProtected = 
true;
 
  350                 } 
catch (InvalidCredentialsException ex) {
 
  354                     passwordProtected = 
true;
 
  366         return passwordProtected;
 
  384     private boolean isFileEncryptionSuspected(AbstractFile file) 
throws ReadContentInputStreamException, IOException {
 
  390         boolean possiblyEncrypted = 
false;
 
  395         boolean fileSizeQualified = 
false;
 
  396         String fileExtension = file.getNameExtension();
 
  397         long contentSize = file.getSize();
 
  399         if (fileExtension.equalsIgnoreCase(DATABASE_FILE_EXTENSION)) {
 
  400             if (contentSize >= MINIMUM_DATABASE_FILE_SIZE) {
 
  401                 fileSizeQualified = 
true;
 
  403         } 
else if (contentSize >= minimumFileSize) {
 
  404             if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) {
 
  405                 fileSizeQualified = 
true;
 
  409         if (fileSizeQualified) {
 
  413             calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file, context);
 
  414             if (calculatedEntropy >= minimumEntropy) {
 
  415                 possiblyEncrypted = 
true;
 
  419         return possiblyEncrypted;