19 package org.sleuthkit.autopsy.modules.encryptiondetection;
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;
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;
55 import org.
sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
58 import org.xml.sax.ContentHandler;
59 import org.xml.sax.SAXException;
64 final class EncryptionDetectionFileIngestModule
extends FileIngestModuleAdapter {
66 private static final int FILE_SIZE_MODULUS = 512;
68 private static final String DATABASE_FILE_EXTENSION =
"db";
69 private static final int MINIMUM_DATABASE_FILE_SIZE = 65536;
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";
78 private static final String[] FILE_IGNORE_LIST = {
"hiberfile.sys",
"pagefile.sys"};
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;
86 private final double minimumEntropy;
87 private final int minimumFileSize;
88 private final boolean fileSizeMultipleEnforced;
89 private final boolean slackFilesAllowed;
98 EncryptionDetectionFileIngestModule(EncryptionDetectionIngestJobSettings settings) {
99 minimumEntropy = settings.getMinimumEntropy();
100 minimumFileSize = settings.getMinimumFileSize();
101 fileSizeMultipleEnforced = settings.isFileSizeMultipleEnforced();
102 slackFilesAllowed = settings.isSlackFilesAllowed();
106 public void startUp(IngestJobContext context)
throws IngestModule.IngestModuleException {
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);
119 "EncryptionDetectionFileIngestModule.artifactComment.password=Password protection detected.",
120 "EncryptionDetectionFileIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f)."
123 public IngestModule.ProcessResult process(AbstractFile file) {
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)) {
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)) {
146 return IngestModule.ProcessResult.OK;
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());
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;
170 return IngestModule.ProcessResult.OK;
179 private void validateSettings() throws IngestModule.IngestModuleException {
180 EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy);
181 EncryptionDetectionTools.validateMinFileSizeValue(minimumFileSize);
194 private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType, String comment) {
196 BlackboardArtifact artifact = file.newArtifact(artifactType);
197 artifact.addAttribute(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT,
198 EncryptionDetectionModuleFactory.getModuleName(), comment));
204 blackboard.indexArtifact(artifact);
205 }
catch (Blackboard.BlackboardException ex) {
206 logger.log(Level.SEVERE,
"Unable to index blackboard artifact " + artifact.getArtifactID(), ex);
212 services.fireModuleDataEvent(
new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), artifactType, Collections.singletonList(artifact)));
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);
223 services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(),
224 artifactType.getDisplayName() +
" Match: " + file.getName(),
225 detailsSb.toString(),
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);
232 return IngestModule.ProcessResult.ERROR;
255 private boolean isFilePasswordProtected(AbstractFile file)
throws ReadContentInputStreamException, IOException, SAXException, TikaException, UnsupportedCodecException {
257 boolean passwordProtected =
false;
259 switch (file.getMIMEType()) {
260 case MIME_TYPE_OOXML_PROTECTED:
265 passwordProtected =
true;
268 case MIME_TYPE_MSWORD:
269 case MIME_TYPE_MSEXCEL:
270 case MIME_TYPE_MSPOWERPOINT:
271 case MIME_TYPE_PDF: {
276 InputStream in = null;
277 BufferedInputStream bin = null;
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) {
291 passwordProtected =
true;
303 case MIME_TYPE_MSACCESS: {
311 InputStream in = null;
312 BufferedInputStream bin = null;
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();
328 if (accessDatabase.getDatabasePassword() != null) {
329 passwordProtected =
true;
331 }
catch (InvalidCredentialsException ex) {
335 passwordProtected =
true;
347 return passwordProtected;
365 private boolean isFileEncryptionSuspected(AbstractFile file)
throws ReadContentInputStreamException, IOException {
371 boolean possiblyEncrypted =
false;
376 boolean fileSizeQualified =
false;
377 String fileExtension = file.getNameExtension();
378 long contentSize = file.getSize();
380 if (fileExtension.equalsIgnoreCase(DATABASE_FILE_EXTENSION)) {
381 if (contentSize >= MINIMUM_DATABASE_FILE_SIZE) {
382 fileSizeQualified =
true;
384 }
else if (contentSize >= minimumFileSize) {
385 if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) {
386 fileSizeQualified =
true;
390 if (fileSizeQualified) {
394 calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file);
395 if (calculatedEntropy >= minimumEntropy) {
396 possiblyEncrypted =
true;
400 return possiblyEncrypted;