Autopsy 4.22.1
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-2021 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 */
19package org.sleuthkit.autopsy.modules.encryptiondetection;
20
21import com.healthmarketscience.jackcess.crypt.CryptCodecProvider;
22import com.healthmarketscience.jackcess.Database;
23import com.healthmarketscience.jackcess.DatabaseBuilder;
24import com.healthmarketscience.jackcess.crypt.InvalidCredentialsException;
25import com.healthmarketscience.jackcess.impl.CodecProvider;
26import com.healthmarketscience.jackcess.impl.UnsupportedCodecException;
27import com.healthmarketscience.jackcess.util.MemFileChannel;
28import java.io.BufferedInputStream;
29import java.io.IOException;
30import java.io.InputStream;
31import java.util.Arrays;
32import java.util.logging.Level;
33import org.apache.tika.exception.EncryptedDocumentException;
34import org.apache.tika.exception.TikaException;
35import org.apache.tika.metadata.TikaCoreProperties;
36import org.apache.tika.metadata.Metadata;
37import org.apache.tika.parser.AutoDetectParser;
38import org.apache.tika.parser.ParseContext;
39import org.apache.tika.sax.BodyContentHandler;
40import org.openide.util.NbBundle.Messages;
41import org.sleuthkit.autopsy.casemodule.Case;
42import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
43import org.sleuthkit.autopsy.coreutils.Logger;
44import org.sleuthkit.autopsy.ingest.FileIngestModuleAdapter;
45import org.sleuthkit.autopsy.ingest.IngestJobContext;
46import org.sleuthkit.autopsy.ingest.IngestMessage;
47import org.sleuthkit.autopsy.ingest.IngestModule;
48import org.sleuthkit.autopsy.ingest.IngestServices;
49import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
50import org.sleuthkit.datamodel.AbstractFile;
51import org.sleuthkit.datamodel.Blackboard;
52import org.sleuthkit.datamodel.BlackboardArtifact;
53import org.sleuthkit.datamodel.BlackboardAttribute;
54import org.sleuthkit.datamodel.ReadContentInputStream;
55import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
56import org.sleuthkit.datamodel.Score;
57import org.sleuthkit.datamodel.TskCoreException;
58import org.sleuthkit.datamodel.TskData;
59import org.xml.sax.ContentHandler;
60import org.xml.sax.SAXException;
61
65final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter {
66
67 private static final int FILE_SIZE_MODULUS = 512;
68
69 private static final String DATABASE_FILE_EXTENSION = "db";
70 private static final int MINIMUM_DATABASE_FILE_SIZE = 65536; //64 KB
71
72 private static final String MIME_TYPE_OOXML_PROTECTED = "application/x-ooxml-protected";
73 private static final String MIME_TYPE_MSWORD = "application/msword";
74 private static final String MIME_TYPE_MSEXCEL = "application/vnd.ms-excel";
75 private static final String MIME_TYPE_MSPOWERPOINT = "application/vnd.ms-powerpoint";
76 private static final String MIME_TYPE_MSACCESS = "application/x-msaccess";
77 private static final String MIME_TYPE_PDF = "application/pdf";
78
79 private static final String[] FILE_IGNORE_LIST = {"hiberfile.sys", "pagefile.sys"};
80
81 private final IngestServices services = IngestServices.getInstance();
82 private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName());
83 private FileTypeDetector fileTypeDetector;
84 private Blackboard blackboard;
85 private IngestJobContext context;
86 private double calculatedEntropy;
87
88 private final double minimumEntropy;
89 private final int minimumFileSize;
90 private final boolean fileSizeMultipleEnforced;
91 private final boolean slackFilesAllowed;
92
100 EncryptionDetectionFileIngestModule(EncryptionDetectionIngestJobSettings settings) {
101 minimumEntropy = settings.getMinimumEntropy();
102 minimumFileSize = settings.getMinimumFileSize();
103 fileSizeMultipleEnforced = settings.isFileSizeMultipleEnforced();
104 slackFilesAllowed = settings.isSlackFilesAllowed();
105 }
106
107 @Override
108 public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
109 try {
110 validateSettings();
111 this.context = context;
112 blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
113
114 fileTypeDetector = new FileTypeDetector();
115 } catch (FileTypeDetector.FileTypeDetectorInitException ex) {
116 throw new IngestModule.IngestModuleException("Failed to create file type detector", ex);
117 } catch (NoCurrentCaseException ex) {
118 throw new IngestModule.IngestModuleException("Exception while getting open case.", ex);
119 }
120 }
121
122 @Messages({
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 EncryptionDetectionModuleFactory.PASSWORD_PROTECT_MESSAGE);
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(), context.getJobId());
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(TikaCoreProperties.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-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.