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.nio.BufferUnderflowException;
 
   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;
 
   57 import org.xml.sax.ContentHandler;
 
   58 import org.xml.sax.SAXException;
 
   63 final class EncryptionDetectionFileIngestModule 
extends FileIngestModuleAdapter {
 
   65     private static final int FILE_SIZE_MODULUS = 512;
 
   67     private static final String DATABASE_FILE_EXTENSION = 
"db";
 
   68     private static final int MINIMUM_DATABASE_FILE_SIZE = 65536; 
 
   70     private static final String MIME_TYPE_OOXML_PROTECTED = 
"application/x-ooxml-protected";
 
   71     private static final String MIME_TYPE_MSWORD = 
"application/msword";
 
   72     private static final String MIME_TYPE_MSEXCEL = 
"application/vnd.ms-excel";
 
   73     private static final String MIME_TYPE_MSPOWERPOINT = 
"application/vnd.ms-powerpoint";
 
   74     private static final String MIME_TYPE_MSACCESS = 
"application/x-msaccess";
 
   75     private static final String MIME_TYPE_PDF = 
"application/pdf";
 
   77     private static final String[] FILE_IGNORE_LIST = {
"hiberfile.sys", 
"pagefile.sys"};
 
   79     private final IngestServices services = IngestServices.getInstance();
 
   80     private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName());
 
   81     private FileTypeDetector fileTypeDetector;
 
   82     private Blackboard blackboard;
 
   83     private IngestJobContext context;
 
   84     private double calculatedEntropy;
 
   86     private final double minimumEntropy;
 
   87     private final int minimumFileSize;
 
   88     private final boolean fileSizeMultipleEnforced;
 
   89     private final boolean slackFilesAllowed;
 
   98     EncryptionDetectionFileIngestModule(EncryptionDetectionIngestJobSettings settings) {
 
   99         minimumEntropy = settings.getMinimumEntropy();
 
  100         minimumFileSize = settings.getMinimumFileSize();
 
  101         fileSizeMultipleEnforced = settings.isFileSizeMultipleEnforced();
 
  102         slackFilesAllowed = settings.isSlackFilesAllowed();
 
  106     public void startUp(IngestJobContext context) 
throws IngestModule.IngestModuleException {
 
  109             this.context = context;
 
  110             blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
 
  112             fileTypeDetector = 
new FileTypeDetector();
 
  113         } 
catch (FileTypeDetector.FileTypeDetectorInitException ex) {
 
  114             throw new IngestModule.IngestModuleException(
"Failed to create file type detector", ex);
 
  115         } 
catch (NoCurrentCaseException ex) {
 
  116             throw new IngestModule.IngestModuleException(
"Exception while getting open case.", ex);
 
  121         "EncryptionDetectionFileIngestModule.artifactComment.password=Password protection detected.",
 
  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.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED,
 
  159                             String.format(Bundle.EncryptionDetectionFileIngestModule_artifactComment_suspected(), calculatedEntropy));
 
  160                 } 
else if (isFilePasswordProtected(file)) {
 
  161                     return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, Bundle.EncryptionDetectionFileIngestModule_artifactComment_password());
 
  164         } 
catch (ReadContentInputStreamException | SAXException | TikaException | UnsupportedCodecException ex) {
 
  165             logger.log(Level.WARNING, String.format(
"Unable to read file '%s'", file.getParentPath() + file.getName()), ex);
 
  166             return IngestModule.ProcessResult.ERROR;
 
  167         } 
catch (IOException ex) {
 
  168             logger.log(Level.SEVERE, String.format(
"Unable to process file '%s'", file.getParentPath() + file.getName()), ex);
 
  169             return IngestModule.ProcessResult.ERROR;
 
  172         return IngestModule.ProcessResult.OK;
 
  181     private void validateSettings() throws IngestModule.IngestModuleException {
 
  182         EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy);
 
  183         EncryptionDetectionTools.validateMinFileSizeValue(minimumFileSize);
 
  196     private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType, String comment) {
 
  198             if (context.fileIngestIsCancelled()) {
 
  199                 return IngestModule.ProcessResult.OK;
 
  202             BlackboardArtifact artifact = file.newArtifact(artifactType);
 
  203             artifact.addAttribute(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT,
 
  204                     EncryptionDetectionModuleFactory.getModuleName(), comment));
 
  211                 blackboard.postArtifact(artifact, EncryptionDetectionModuleFactory.getModuleName());
 
  212             } 
catch (Blackboard.BlackboardException ex) {
 
  213                 logger.log(Level.SEVERE, 
"Unable to index blackboard artifact " + artifact.getArtifactID(), ex); 
 
  219             StringBuilder detailsSb = 
new StringBuilder();
 
  220             detailsSb.append(
"File: ").append(file.getParentPath()).append(file.getName());
 
  221             if (artifactType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED)) {
 
  222                 detailsSb.append(
"<br/>\nEntropy: ").append(calculatedEntropy);
 
  225             services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(),
 
  226                     artifactType.getDisplayName() + 
" Match: " + file.getName(),
 
  227                     detailsSb.toString(),
 
  231             return IngestModule.ProcessResult.OK;
 
  232         } 
catch (TskCoreException ex) {
 
  233             logger.log(Level.SEVERE, String.format(
"Failed to create blackboard artifact for '%s'.", file.getParentPath() + file.getName()), ex); 
 
  234             return IngestModule.ProcessResult.ERROR;
 
  257     private boolean isFilePasswordProtected(AbstractFile file) 
throws ReadContentInputStreamException, IOException, SAXException, TikaException, UnsupportedCodecException {
 
  259         boolean passwordProtected = 
false;
 
  261         switch (file.getMIMEType()) {
 
  262             case MIME_TYPE_OOXML_PROTECTED:
 
  267                 passwordProtected = 
true;
 
  270             case MIME_TYPE_MSWORD:
 
  271             case MIME_TYPE_MSEXCEL:
 
  272             case MIME_TYPE_MSPOWERPOINT:
 
  273             case MIME_TYPE_PDF: {
 
  278                 InputStream in = null;
 
  279                 BufferedInputStream bin = null;
 
  282                     in = 
new ReadContentInputStream(file);
 
  283                     bin = 
new BufferedInputStream(in);
 
  284                     ContentHandler handler = 
new BodyContentHandler(-1);
 
  285                     Metadata metadata = 
new Metadata();
 
  286                     metadata.add(Metadata.RESOURCE_NAME_KEY, file.getName());
 
  287                     AutoDetectParser parser = 
new AutoDetectParser();
 
  288                     parser.parse(bin, handler, metadata, 
new ParseContext());
 
  289                 } 
catch (EncryptedDocumentException ex) {
 
  293                     passwordProtected = 
true;
 
  305             case MIME_TYPE_MSACCESS: {
 
  313                 InputStream in = null;
 
  314                 BufferedInputStream bin = null;
 
  317                     in = 
new ReadContentInputStream(file);
 
  318                     bin = 
new BufferedInputStream(in);
 
  319                     MemFileChannel memFileChannel = MemFileChannel.newChannel(bin);
 
  320                     CodecProvider codecProvider = 
new CryptCodecProvider();
 
  321                     DatabaseBuilder databaseBuilder = 
new DatabaseBuilder();
 
  322                     databaseBuilder.setChannel(memFileChannel);
 
  323                     databaseBuilder.setCodecProvider(codecProvider);
 
  324                     Database accessDatabase;
 
  326                         accessDatabase = databaseBuilder.open();
 
  327                     } 
catch (Exception ex) { 
 
  328                         logger.log(Level.WARNING, String.format(
"Unexpected exception " 
  329                                 + 
"trying to open msaccess database using Jackcess " 
  330                                 + 
"(name: %s, id: %d)", file.getName(), file.getId()), ex);
 
  331                         return passwordProtected; 
 
  338                     if (accessDatabase.getDatabasePassword() != null) {
 
  339                         passwordProtected = 
true;
 
  341                 } 
catch (InvalidCredentialsException ex) {
 
  345                     passwordProtected = 
true;
 
  357         return passwordProtected;
 
  375     private boolean isFileEncryptionSuspected(AbstractFile file) 
throws ReadContentInputStreamException, IOException {
 
  381         boolean possiblyEncrypted = 
false;
 
  386         boolean fileSizeQualified = 
false;
 
  387         String fileExtension = file.getNameExtension();
 
  388         long contentSize = file.getSize();
 
  390         if (fileExtension.equalsIgnoreCase(DATABASE_FILE_EXTENSION)) {
 
  391             if (contentSize >= MINIMUM_DATABASE_FILE_SIZE) {
 
  392                 fileSizeQualified = 
true;
 
  394         } 
else if (contentSize >= minimumFileSize) {
 
  395             if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) {
 
  396                 fileSizeQualified = 
true;
 
  400         if (fileSizeQualified) {
 
  404             calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file, context);
 
  405             if (calculatedEntropy >= minimumEntropy) {
 
  406                 possiblyEncrypted = 
true;
 
  410         return possiblyEncrypted;