Autopsy  4.8.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
EncryptionDetectionFileIngestModule.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2017-2018 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.modules.encryptiondetection;
20 
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;
31 import org.sleuthkit.datamodel.ReadContentInputStream;
32 import java.io.BufferedInputStream;
33 import java.io.InputStream;
34 import org.apache.tika.exception.EncryptedDocumentException;
35 import org.apache.tika.exception.TikaException;
36 import org.apache.tika.metadata.Metadata;
37 import org.apache.tika.parser.AutoDetectParser;
38 import org.apache.tika.parser.ParseContext;
39 import org.apache.tika.sax.BodyContentHandler;
40 import org.openide.util.NbBundle.Messages;
52 import org.sleuthkit.datamodel.AbstractFile;
53 import org.sleuthkit.datamodel.BlackboardArtifact;
54 import org.sleuthkit.datamodel.BlackboardAttribute;
55 import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
56 import org.sleuthkit.datamodel.TskCoreException;
57 import org.sleuthkit.datamodel.TskData;
58 import org.xml.sax.ContentHandler;
59 import org.xml.sax.SAXException;
60 
64 final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter {
65 
66  private static final int FILE_SIZE_MODULUS = 512;
67 
68  private static final String DATABASE_FILE_EXTENSION = "db";
69  private static final int MINIMUM_DATABASE_FILE_SIZE = 65536; //64 KB
70 
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";
77 
78  private static final String[] FILE_IGNORE_LIST = {"hiberfile.sys", "pagefile.sys"};
79 
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 double calculatedEntropy;
85 
86  private final double minimumEntropy;
87  private final int minimumFileSize;
88  private final boolean fileSizeMultipleEnforced;
89  private final boolean slackFilesAllowed;
90 
98  EncryptionDetectionFileIngestModule(EncryptionDetectionIngestJobSettings settings) {
99  minimumEntropy = settings.getMinimumEntropy();
100  minimumFileSize = settings.getMinimumFileSize();
101  fileSizeMultipleEnforced = settings.isFileSizeMultipleEnforced();
102  slackFilesAllowed = settings.isSlackFilesAllowed();
103  }
104 
105  @Override
106  public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
107  try {
108  validateSettings();
109  blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard();
110  fileTypeDetector = new FileTypeDetector();
111  } catch (FileTypeDetector.FileTypeDetectorInitException ex) {
112  throw new IngestModule.IngestModuleException("Failed to create file type detector", ex);
113  } catch (NoCurrentCaseException ex) {
114  throw new IngestModule.IngestModuleException("Exception while getting open case.", ex);
115  }
116  }
117 
118  @Messages({
119  "EncryptionDetectionFileIngestModule.artifactComment.password=Password protection detected.",
120  "EncryptionDetectionFileIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f)."
121  })
122  @Override
123  public IngestModule.ProcessResult process(AbstractFile file) {
124 
125  try {
126  /*
127  * Qualify the file type, qualify it against hash databases, and
128  * verify the file hasn't been deleted.
129  */
130  if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)
131  && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)
132  && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR)
133  && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR)
134  && (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed)
135  && !file.getKnown().equals(TskData.FileKnown.KNOWN)
136  && !file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC)) {
137  /*
138  * Is the file in FILE_IGNORE_LIST?
139  */
140  String filePath = file.getParentPath();
141  if (filePath.equals("/")) {
142  String fileName = file.getName();
143  for (String listEntry : FILE_IGNORE_LIST) {
144  if (fileName.equalsIgnoreCase(listEntry)) {
145  // Skip this file.
146  return IngestModule.ProcessResult.OK;
147  }
148  }
149  }
150 
151  /*
152  * Qualify the MIME type.
153  */
154  String mimeType = fileTypeDetector.getMIMEType(file);
155  if (mimeType.equals("application/octet-stream") && isFileEncryptionSuspected(file)) {
156  return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED,
157  String.format(Bundle.EncryptionDetectionFileIngestModule_artifactComment_suspected(), calculatedEntropy));
158  } else if (isFilePasswordProtected(file)) {
159  return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, Bundle.EncryptionDetectionFileIngestModule_artifactComment_password());
160  }
161  }
162  } catch (ReadContentInputStreamException | SAXException | TikaException | UnsupportedCodecException ex) {
163  logger.log(Level.WARNING, String.format("Unable to read file '%s'", file.getParentPath() + file.getName()), ex);
164  return IngestModule.ProcessResult.ERROR;
165  } catch (IOException ex) {
166  logger.log(Level.SEVERE, String.format("Unable to process file '%s'", file.getParentPath() + file.getName()), ex);
167  return IngestModule.ProcessResult.ERROR;
168  }
169 
170  return IngestModule.ProcessResult.OK;
171  }
172 
179  private void validateSettings() throws IngestModule.IngestModuleException {
180  EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy);
181  EncryptionDetectionTools.validateMinFileSizeValue(minimumFileSize);
182  }
183 
194  private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType, String comment) {
195  try {
196  BlackboardArtifact artifact = file.newArtifact(artifactType);
197  artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT,
198  EncryptionDetectionModuleFactory.getModuleName(), comment));
199 
200  try {
201  /*
202  * Index the artifact for keyword search.
203  */
204  blackboard.indexArtifact(artifact);
205  } catch (Blackboard.BlackboardException ex) {
206  logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
207  }
208 
209  /*
210  * Send an event to update the view with the new result.
211  */
212  services.fireModuleDataEvent(new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), artifactType, Collections.singletonList(artifact)));
213 
214  /*
215  * Make an ingest inbox message.
216  */
217  StringBuilder detailsSb = new StringBuilder();
218  detailsSb.append("File: ").append(file.getParentPath()).append(file.getName());
219  if (artifactType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED)) {
220  detailsSb.append("<br/>\nEntropy: ").append(calculatedEntropy);
221  }
222 
223  services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(),
224  artifactType.getDisplayName() + " Match: " + file.getName(),
225  detailsSb.toString(),
226  file.getName(),
227  artifact));
228 
229  return IngestModule.ProcessResult.OK;
230  } catch (TskCoreException ex) {
231  logger.log(Level.SEVERE, String.format("Failed to create blackboard artifact for '%s'.", file.getParentPath() + file.getName()), ex); //NON-NLS
232  return IngestModule.ProcessResult.ERROR;
233  }
234  }
235 
255  private boolean isFilePasswordProtected(AbstractFile file) throws ReadContentInputStreamException, IOException, SAXException, TikaException, UnsupportedCodecException {
256 
257  boolean passwordProtected = false;
258 
259  switch (file.getMIMEType()) {
260  case MIME_TYPE_OOXML_PROTECTED:
261  /*
262  * Office Open XML files that are password protected can be
263  * determined so simply by checking the MIME type.
264  */
265  passwordProtected = true;
266  break;
267 
268  case MIME_TYPE_MSWORD:
269  case MIME_TYPE_MSEXCEL:
270  case MIME_TYPE_MSPOWERPOINT:
271  case MIME_TYPE_PDF: {
272  /*
273  * A file of one of these types will be determined to be
274  * password protected or not by attempting to parse it via Tika.
275  */
276  InputStream in = null;
277  BufferedInputStream bin = null;
278 
279  try {
280  in = new ReadContentInputStream(file);
281  bin = new BufferedInputStream(in);
282  ContentHandler handler = new BodyContentHandler(-1);
283  Metadata metadata = new Metadata();
284  metadata.add(Metadata.RESOURCE_NAME_KEY, file.getName());
285  AutoDetectParser parser = new AutoDetectParser();
286  parser.parse(bin, handler, metadata, new ParseContext());
287  } catch (EncryptedDocumentException ex) {
288  /*
289  * File is determined to be password protected.
290  */
291  passwordProtected = true;
292  } finally {
293  if (in != null) {
294  in.close();
295  }
296  if (bin != null) {
297  bin.close();
298  }
299  }
300  break;
301  }
302 
303  case MIME_TYPE_MSACCESS: {
304  /*
305  * Access databases are determined to be password protected
306  * using Jackcess. If the database can be opened, the password
307  * is read from it to see if it's null. If the database can not
308  * be opened due to an InvalidCredentialException being thrown,
309  * it is automatically determined to be password protected.
310  */
311  InputStream in = null;
312  BufferedInputStream bin = null;
313 
314  try {
315  in = new ReadContentInputStream(file);
316  bin = new BufferedInputStream(in);
317  MemFileChannel memFileChannel = MemFileChannel.newChannel(bin);
318  CodecProvider codecProvider = new CryptCodecProvider();
319  DatabaseBuilder databaseBuilder = new DatabaseBuilder();
320  databaseBuilder.setChannel(memFileChannel);
321  databaseBuilder.setCodecProvider(codecProvider);
322  Database accessDatabase = databaseBuilder.open();
323  /*
324  * No exception has been thrown at this point, so the file
325  * is either a JET database, or an unprotected ACE database.
326  * Read the password from the database to see if it exists.
327  */
328  if (accessDatabase.getDatabasePassword() != null) {
329  passwordProtected = true;
330  }
331  } catch (InvalidCredentialsException ex) {
332  /*
333  * The ACE database is determined to be password protected.
334  */
335  passwordProtected = true;
336  } finally {
337  if (in != null) {
338  in.close();
339  }
340  if (bin != null) {
341  bin.close();
342  }
343  }
344  }
345  }
346 
347  return passwordProtected;
348  }
349 
365  private boolean isFileEncryptionSuspected(AbstractFile file) throws ReadContentInputStreamException, IOException {
366  /*
367  * Criteria for the checks in this method are partially based on
368  * http://www.forensicswiki.org/wiki/TrueCrypt#Detection
369  */
370 
371  boolean possiblyEncrypted = false;
372 
373  /*
374  * Qualify the size.
375  */
376  boolean fileSizeQualified = false;
377  String fileExtension = file.getNameExtension();
378  long contentSize = file.getSize();
379  // Database files qualify at 64 KB minimum for SQLCipher detection.
380  if (fileExtension.equalsIgnoreCase(DATABASE_FILE_EXTENSION)) {
381  if (contentSize >= MINIMUM_DATABASE_FILE_SIZE) {
382  fileSizeQualified = true;
383  }
384  } else if (contentSize >= minimumFileSize) {
385  if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) {
386  fileSizeQualified = true;
387  }
388  }
389 
390  if (fileSizeQualified) {
391  /*
392  * Qualify the entropy.
393  */
394  calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file);
395  if (calculatedEntropy >= minimumEntropy) {
396  possiblyEncrypted = true;
397  }
398  }
399 
400  return possiblyEncrypted;
401  }
402 }

Copyright © 2012-2018 Basis Technology. Generated on: Thu Oct 4 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.