Autopsy  4.19.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.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;
49 import org.sleuthkit.datamodel.AbstractFile;
50 import org.sleuthkit.datamodel.Blackboard;
51 import org.sleuthkit.datamodel.BlackboardArtifact;
52 import org.sleuthkit.datamodel.BlackboardAttribute;
53 import org.sleuthkit.datamodel.ReadContentInputStream;
54 import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
55 import org.sleuthkit.datamodel.Score;
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 IngestJobContext context;
85  private double calculatedEntropy;
86 
87  private final double minimumEntropy;
88  private final int minimumFileSize;
89  private final boolean fileSizeMultipleEnforced;
90  private final boolean slackFilesAllowed;
91 
99  EncryptionDetectionFileIngestModule(EncryptionDetectionIngestJobSettings settings) {
100  minimumEntropy = settings.getMinimumEntropy();
101  minimumFileSize = settings.getMinimumFileSize();
102  fileSizeMultipleEnforced = settings.isFileSizeMultipleEnforced();
103  slackFilesAllowed = settings.isSlackFilesAllowed();
104  }
105 
106  @Override
107  public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
108  try {
109  validateSettings();
110  this.context = context;
111  blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
112 
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);
118  }
119  }
120 
121  @Messages({
122  "EncryptionDetectionFileIngestModule.artifactComment.password=Password protection detected.",
123  "EncryptionDetectionFileIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f)."
124  })
125  @Override
126  public IngestModule.ProcessResult process(AbstractFile file) {
127 
128  try {
129  /*
130  * Qualify the file type, qualify it against hash databases, and
131  * verify the file hasn't been deleted.
132  */
133  if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)
134  && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)
135  && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR)
136  && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR)
137  && (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed)
138  && !file.getKnown().equals(TskData.FileKnown.KNOWN)
139  && !file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC)) {
140  /*
141  * Is the file in FILE_IGNORE_LIST?
142  */
143  String filePath = file.getParentPath();
144  if (filePath.equals("/")) {
145  String fileName = file.getName();
146  for (String listEntry : FILE_IGNORE_LIST) {
147  if (fileName.equalsIgnoreCase(listEntry)) {
148  // Skip this file.
149  return IngestModule.ProcessResult.OK;
150  }
151  }
152  }
153 
154  /*
155  * Qualify the MIME type.
156  */
157  String mimeType = fileTypeDetector.getMIMEType(file);
158  if (mimeType.equals("application/octet-stream") && isFileEncryptionSuspected(file)) {
159  return flagFile(file, BlackboardArtifact.Type.TSK_ENCRYPTION_SUSPECTED, Score.SCORE_LIKELY_NOTABLE,
160  String.format(Bundle.EncryptionDetectionFileIngestModule_artifactComment_suspected(), calculatedEntropy));
161  } else if (isFilePasswordProtected(file)) {
162  return flagFile(file, BlackboardArtifact.Type.TSK_ENCRYPTION_DETECTED, Score.SCORE_NOTABLE,
163  Bundle.EncryptionDetectionFileIngestModule_artifactComment_password());
164  }
165  }
166  } catch (ReadContentInputStreamException | SAXException | TikaException | UnsupportedCodecException ex) {
167  logger.log(Level.WARNING, String.format("Unable to read file '%s'", file.getParentPath() + file.getName()), ex);
168  return IngestModule.ProcessResult.ERROR;
169  } catch (IOException ex) {
170  logger.log(Level.SEVERE, String.format("Unable to process file '%s'", file.getParentPath() + file.getName()), ex);
171  return IngestModule.ProcessResult.ERROR;
172  }
173 
174  return IngestModule.ProcessResult.OK;
175  }
176 
183  private void validateSettings() throws IngestModule.IngestModuleException {
184  EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy);
185  EncryptionDetectionTools.validateMinFileSizeValue(minimumFileSize);
186  }
187 
200  private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.Type artifactType, Score score, String comment) {
201  try {
202  if (context.fileIngestIsCancelled()) {
203  return IngestModule.ProcessResult.OK;
204  }
205 
206  BlackboardArtifact artifact = file.newAnalysisResult(artifactType, score, null, null, comment,
207  Arrays.asList(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT,
208  EncryptionDetectionModuleFactory.getModuleName(), comment)))
209  .getAnalysisResult();
210 
211  try {
212  /*
213  * post the artifact which will index the artifact for keyword
214  * search, and fire an event to notify UI of this new artifact
215  */
216  blackboard.postArtifact(artifact, EncryptionDetectionModuleFactory.getModuleName());
217  } catch (Blackboard.BlackboardException ex) {
218  logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
219  }
220 
221  /*
222  * Make an ingest inbox message.
223  */
224  StringBuilder detailsSb = new StringBuilder();
225  detailsSb.append("File: ").append(file.getParentPath()).append(file.getName());
226  if (artifactType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED)) {
227  detailsSb.append("<br/>\nEntropy: ").append(calculatedEntropy);
228  }
229 
230  services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(),
231  artifactType.getDisplayName() + " Match: " + file.getName(),
232  detailsSb.toString(),
233  file.getName(),
234  artifact));
235 
236  return IngestModule.ProcessResult.OK;
237  } catch (TskCoreException ex) {
238  logger.log(Level.SEVERE, String.format("Failed to create blackboard artifact for '%s'.", file.getParentPath() + file.getName()), ex); //NON-NLS
239  return IngestModule.ProcessResult.ERROR;
240  }
241  }
242 
262  private boolean isFilePasswordProtected(AbstractFile file) throws ReadContentInputStreamException, IOException, SAXException, TikaException, UnsupportedCodecException {
263 
264  boolean passwordProtected = false;
265 
266  switch (file.getMIMEType()) {
267  case MIME_TYPE_OOXML_PROTECTED:
268  /*
269  * Office Open XML files that are password protected can be
270  * determined so simply by checking the MIME type.
271  */
272  passwordProtected = true;
273  break;
274 
275  case MIME_TYPE_MSWORD:
276  case MIME_TYPE_MSEXCEL:
277  case MIME_TYPE_MSPOWERPOINT:
278  case MIME_TYPE_PDF: {
279  /*
280  * A file of one of these types will be determined to be
281  * password protected or not by attempting to parse it via Tika.
282  */
283  InputStream in = null;
284  BufferedInputStream bin = null;
285 
286  try {
287  in = new ReadContentInputStream(file);
288  bin = new BufferedInputStream(in);
289  ContentHandler handler = new BodyContentHandler(-1);
290  Metadata metadata = new Metadata();
291  metadata.add(Metadata.RESOURCE_NAME_KEY, file.getName());
292  AutoDetectParser parser = new AutoDetectParser();
293  parser.parse(bin, handler, metadata, new ParseContext());
294  } catch (EncryptedDocumentException ex) {
295  /*
296  * File is determined to be password protected.
297  */
298  passwordProtected = true;
299  } finally {
300  if (in != null) {
301  in.close();
302  }
303  if (bin != null) {
304  bin.close();
305  }
306  }
307  break;
308  }
309 
310  case MIME_TYPE_MSACCESS: {
311  /*
312  * Access databases are determined to be password protected
313  * using Jackcess. If the database can be opened, the password
314  * is read from it to see if it's null. If the database can not
315  * be opened due to an InvalidCredentialException being thrown,
316  * it is automatically determined to be password protected.
317  */
318  InputStream in = null;
319  BufferedInputStream bin = null;
320 
321  try {
322  in = new ReadContentInputStream(file);
323  bin = new BufferedInputStream(in);
324  MemFileChannel memFileChannel = MemFileChannel.newChannel(bin);
325  CodecProvider codecProvider = new CryptCodecProvider();
326  DatabaseBuilder databaseBuilder = new DatabaseBuilder();
327  databaseBuilder.setChannel(memFileChannel);
328  databaseBuilder.setCodecProvider(codecProvider);
329  Database accessDatabase;
330  try {
331  accessDatabase = databaseBuilder.open();
332  } catch (InvalidCredentialsException ex) {
333  logger.log(Level.INFO, String.format(
334  "Jackcess throws invalid credentials exception for file (name: %s, id: %s). It will be assumed to be password protected.",
335  file.getName(), file.getId()));
336  return true;
337  } catch (Exception ex) { // Firewall, see JIRA-7097
338  logger.log(Level.WARNING, String.format("Unexpected exception "
339  + "trying to open msaccess database using Jackcess "
340  + "(name: %s, id: %d)", file.getName(), file.getId()), ex);
341  return passwordProtected;
342  }
343  /*
344  * No exception has been thrown at this point, so the file
345  * is either a JET database, or an unprotected ACE database.
346  * Read the password from the database to see if it exists.
347  */
348  if (accessDatabase.getDatabasePassword() != null) {
349  passwordProtected = true;
350  }
351  } catch (InvalidCredentialsException ex) {
352  /*
353  * The ACE database is determined to be password protected.
354  */
355  passwordProtected = true;
356  } finally {
357  if (in != null) {
358  in.close();
359  }
360  if (bin != null) {
361  bin.close();
362  }
363  }
364  }
365  }
366 
367  return passwordProtected;
368  }
369 
385  private boolean isFileEncryptionSuspected(AbstractFile file) throws ReadContentInputStreamException, IOException {
386  /*
387  * Criteria for the checks in this method are partially based on
388  * http://www.forensicswiki.org/wiki/TrueCrypt#Detection
389  */
390 
391  boolean possiblyEncrypted = false;
392 
393  /*
394  * Qualify the size.
395  */
396  boolean fileSizeQualified = false;
397  String fileExtension = file.getNameExtension();
398  long contentSize = file.getSize();
399  // Database files qualify at 64 KB minimum for SQLCipher detection.
400  if (fileExtension.equalsIgnoreCase(DATABASE_FILE_EXTENSION)) {
401  if (contentSize >= MINIMUM_DATABASE_FILE_SIZE) {
402  fileSizeQualified = true;
403  }
404  } else if (contentSize >= minimumFileSize) {
405  if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) {
406  fileSizeQualified = true;
407  }
408  }
409 
410  if (fileSizeQualified) {
411  /*
412  * Qualify the entropy.
413  */
414  calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file, context);
415  if (calculatedEntropy >= minimumEntropy) {
416  possiblyEncrypted = true;
417  }
418  }
419 
420  return possiblyEncrypted;
421  }
422 }

Copyright © 2012-2021 Basis Technology. Generated on: Fri Aug 6 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.