19 package org.sleuthkit.autopsy.modules.encryptiondetection;
21 import com.healthmarketscience.jackcess.crypt.CryptCodecProvider;
22 import com.healthmarketscience.jackcess.Database;
23 import com.healthmarketscience.jackcess.DatabaseBuilder;
24 import com.healthmarketscience.jackcess.crypt.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.TikaCoreProperties;
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;
59 import org.xml.sax.ContentHandler;
60 import org.xml.sax.SAXException;
65 final class EncryptionDetectionFileIngestModule
extends FileIngestModuleAdapter {
67 private static final int FILE_SIZE_MODULUS = 512;
69 private static final String DATABASE_FILE_EXTENSION =
"db";
70 private static final int MINIMUM_DATABASE_FILE_SIZE = 65536;
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";
79 private static final String[] FILE_IGNORE_LIST = {
"hiberfile.sys",
"pagefile.sys"};
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;
88 private final double minimumEntropy;
89 private final int minimumFileSize;
90 private final boolean fileSizeMultipleEnforced;
91 private final boolean slackFilesAllowed;
100 EncryptionDetectionFileIngestModule(EncryptionDetectionIngestJobSettings settings) {
101 minimumEntropy = settings.getMinimumEntropy();
102 minimumFileSize = settings.getMinimumFileSize();
103 fileSizeMultipleEnforced = settings.isFileSizeMultipleEnforced();
104 slackFilesAllowed = settings.isSlackFilesAllowed();
108 public void startUp(IngestJobContext context)
throws IngestModule.IngestModuleException {
111 this.context = context;
112 blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
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);
123 "EncryptionDetectionFileIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f)."
126 public IngestModule.ProcessResult process(AbstractFile file) {
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)) {
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)) {
149 return IngestModule.ProcessResult.OK;
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);
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;
174 return IngestModule.ProcessResult.OK;
183 private void validateSettings() throws IngestModule.IngestModuleException {
184 EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy);
185 EncryptionDetectionTools.validateMinFileSizeValue(minimumFileSize);
200 private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.Type artifactType, Score score, String comment) {
202 if (context.fileIngestIsCancelled()) {
203 return IngestModule.ProcessResult.OK;
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();
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);
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);
230 services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(),
231 artifactType.getDisplayName() +
" Match: " + file.getName(),
232 detailsSb.toString(),
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);
239 return IngestModule.ProcessResult.ERROR;
262 private boolean isFilePasswordProtected(AbstractFile file)
throws ReadContentInputStreamException, IOException, SAXException, TikaException, UnsupportedCodecException {
264 boolean passwordProtected =
false;
266 switch (file.getMIMEType()) {
267 case MIME_TYPE_OOXML_PROTECTED:
272 passwordProtected =
true;
275 case MIME_TYPE_MSWORD:
276 case MIME_TYPE_MSEXCEL:
277 case MIME_TYPE_MSPOWERPOINT:
278 case MIME_TYPE_PDF: {
283 InputStream in = null;
284 BufferedInputStream bin = null;
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) {
298 passwordProtected =
true;
310 case MIME_TYPE_MSACCESS: {
318 InputStream in = null;
319 BufferedInputStream bin = null;
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;
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()));
337 }
catch (Exception ex) {
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;
348 if (accessDatabase.getDatabasePassword() != null) {
349 passwordProtected =
true;
351 }
catch (InvalidCredentialsException ex) {
355 passwordProtected =
true;
367 return passwordProtected;
385 private boolean isFileEncryptionSuspected(AbstractFile file)
throws ReadContentInputStreamException, IOException {
391 boolean possiblyEncrypted =
false;
396 boolean fileSizeQualified =
false;
397 String fileExtension = file.getNameExtension();
398 long contentSize = file.getSize();
400 if (fileExtension.equalsIgnoreCase(DATABASE_FILE_EXTENSION)) {
401 if (contentSize >= MINIMUM_DATABASE_FILE_SIZE) {
402 fileSizeQualified =
true;
404 }
else if (contentSize >= minimumFileSize) {
405 if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) {
406 fileSizeQualified =
true;
410 if (fileSizeQualified) {
414 calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file, context);
415 if (calculatedEntropy >= minimumEntropy) {
416 possiblyEncrypted =
true;
420 return possiblyEncrypted;