Autopsy  4.14.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.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;
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.TskCoreException;
56 import org.sleuthkit.datamodel.TskData;
57 import org.xml.sax.ContentHandler;
58 import org.xml.sax.SAXException;
59 
63 final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter {
64 
65  private static final int FILE_SIZE_MODULUS = 512;
66 
67  private static final String DATABASE_FILE_EXTENSION = "db";
68  private static final int MINIMUM_DATABASE_FILE_SIZE = 65536; //64 KB
69 
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";
76 
77  private static final String[] FILE_IGNORE_LIST = {"hiberfile.sys", "pagefile.sys"};
78 
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;
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  this.context = context;
110  blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
111 
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);
117  }
118  }
119 
120  @Messages({
121  "EncryptionDetectionFileIngestModule.artifactComment.password=Password protection detected.",
122  "EncryptionDetectionFileIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f)."
123  })
124  @Override
125  public IngestModule.ProcessResult process(AbstractFile file) {
126 
127  try {
128  /*
129  * Qualify the file type, qualify it against hash databases, and
130  * verify the file hasn't been deleted.
131  */
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)) {
139  /*
140  * Is the file in FILE_IGNORE_LIST?
141  */
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)) {
147  // Skip this file.
148  return IngestModule.ProcessResult.OK;
149  }
150  }
151  }
152 
153  /*
154  * Qualify the MIME type.
155  */
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());
162  }
163  }
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;
170  }
171 
172  return IngestModule.ProcessResult.OK;
173  }
174 
181  private void validateSettings() throws IngestModule.IngestModuleException {
182  EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy);
183  EncryptionDetectionTools.validateMinFileSizeValue(minimumFileSize);
184  }
185 
196  private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType, String comment) {
197  try {
198  if (context.fileIngestIsCancelled()) {
199  return IngestModule.ProcessResult.OK;
200  }
201 
202  BlackboardArtifact artifact = file.newArtifact(artifactType);
203  artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT,
204  EncryptionDetectionModuleFactory.getModuleName(), comment));
205 
206  try {
207  /*
208  * post the artifact which will index the artifact for keyword
209  * search, and fire an event to notify UI of this new artifact
210  */
211  blackboard.postArtifact(artifact, EncryptionDetectionModuleFactory.getModuleName());
212  } catch (Blackboard.BlackboardException ex) {
213  logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
214  }
215 
216  /*
217  * Make an ingest inbox message.
218  */
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);
223  }
224 
225  services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(),
226  artifactType.getDisplayName() + " Match: " + file.getName(),
227  detailsSb.toString(),
228  file.getName(),
229  artifact));
230 
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); //NON-NLS
234  return IngestModule.ProcessResult.ERROR;
235  }
236  }
237 
257  private boolean isFilePasswordProtected(AbstractFile file) throws ReadContentInputStreamException, IOException, SAXException, TikaException, UnsupportedCodecException {
258 
259  boolean passwordProtected = false;
260 
261  switch (file.getMIMEType()) {
262  case MIME_TYPE_OOXML_PROTECTED:
263  /*
264  * Office Open XML files that are password protected can be
265  * determined so simply by checking the MIME type.
266  */
267  passwordProtected = true;
268  break;
269 
270  case MIME_TYPE_MSWORD:
271  case MIME_TYPE_MSEXCEL:
272  case MIME_TYPE_MSPOWERPOINT:
273  case MIME_TYPE_PDF: {
274  /*
275  * A file of one of these types will be determined to be
276  * password protected or not by attempting to parse it via Tika.
277  */
278  InputStream in = null;
279  BufferedInputStream bin = null;
280 
281  try {
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) {
290  /*
291  * File is determined to be password protected.
292  */
293  passwordProtected = true;
294  } finally {
295  if (in != null) {
296  in.close();
297  }
298  if (bin != null) {
299  bin.close();
300  }
301  }
302  break;
303  }
304 
305  case MIME_TYPE_MSACCESS: {
306  /*
307  * Access databases are determined to be password protected
308  * using Jackcess. If the database can be opened, the password
309  * is read from it to see if it's null. If the database can not
310  * be opened due to an InvalidCredentialException being thrown,
311  * it is automatically determined to be password protected.
312  */
313  InputStream in = null;
314  BufferedInputStream bin = null;
315 
316  try {
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;
325  try {
326  accessDatabase = databaseBuilder.open();
327  } catch (IOException | BufferUnderflowException | IndexOutOfBoundsException ignored) {
328  return passwordProtected;
329  }
330  /*
331  * No exception has been thrown at this point, so the file
332  * is either a JET database, or an unprotected ACE database.
333  * Read the password from the database to see if it exists.
334  */
335  if (accessDatabase.getDatabasePassword() != null) {
336  passwordProtected = true;
337  }
338  } catch (InvalidCredentialsException ex) {
339  /*
340  * The ACE database is determined to be password protected.
341  */
342  passwordProtected = true;
343  } finally {
344  if (in != null) {
345  in.close();
346  }
347  if (bin != null) {
348  bin.close();
349  }
350  }
351  }
352  }
353 
354  return passwordProtected;
355  }
356 
372  private boolean isFileEncryptionSuspected(AbstractFile file) throws ReadContentInputStreamException, IOException {
373  /*
374  * Criteria for the checks in this method are partially based on
375  * http://www.forensicswiki.org/wiki/TrueCrypt#Detection
376  */
377 
378  boolean possiblyEncrypted = false;
379 
380  /*
381  * Qualify the size.
382  */
383  boolean fileSizeQualified = false;
384  String fileExtension = file.getNameExtension();
385  long contentSize = file.getSize();
386  // Database files qualify at 64 KB minimum for SQLCipher detection.
387  if (fileExtension.equalsIgnoreCase(DATABASE_FILE_EXTENSION)) {
388  if (contentSize >= MINIMUM_DATABASE_FILE_SIZE) {
389  fileSizeQualified = true;
390  }
391  } else if (contentSize >= minimumFileSize) {
392  if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) {
393  fileSizeQualified = true;
394  }
395  }
396 
397  if (fileSizeQualified) {
398  /*
399  * Qualify the entropy.
400  */
401  calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file, context);
402  if (calculatedEntropy >= minimumEntropy) {
403  possiblyEncrypted = true;
404  }
405  }
406 
407  return possiblyEncrypted;
408  }
409 }

Copyright © 2012-2020 Basis Technology. Generated on: Wed Apr 8 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.