/* * Autopsy Forensic Browser * * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sleuthkit.autopsy.modules.fileextmismatch; import java.util.Collections; import java.util.HashMap; import java.util.Set; import java.util.logging.Level; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.ingest.FileIngestModule; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.ingest.IngestMessage; import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.modules.fileextmismatch.FileExtMismatchDetectorModuleSettings.CHECK_TYPE; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData.FileKnown; import org.sleuthkit.datamodel.TskException; /** * Flags mismatched filename extensions based on file signature. */ @NbBundle.Messages({ "CannotRunFileTypeDetection=Unable to run file type detection.", "FileExtMismatchIngestModule.readError.message=Could not read settings." }) public class FileExtMismatchIngestModule implements FileIngestModule { private static final Logger logger = Logger.getLogger(FileExtMismatchIngestModule.class.getName()); private final IngestServices services = IngestServices.getInstance(); private final FileExtMismatchDetectorModuleSettings settings; private HashMap<String, Set<String>> mimeTypeToExtsMap = new HashMap<>(); private long jobId; private static final HashMap<Long, IngestJobTotals> totalsForIngestJobs = new HashMap<>(); private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); private static Blackboard blackboard; private FileTypeDetector detector; private static class IngestJobTotals { private long processTime = 0; private long numFiles = 0; } /** * Update the match time total and increment num of files for this job * * @param ingestJobId * @param processTimeInc amount of time to add */ private static synchronized void addToTotals(long ingestJobId, long processTimeInc) { IngestJobTotals ingestJobTotals = totalsForIngestJobs.get(ingestJobId); if (ingestJobTotals == null) { ingestJobTotals = new IngestJobTotals(); totalsForIngestJobs.put(ingestJobId, ingestJobTotals); } ingestJobTotals.processTime += processTimeInc; ingestJobTotals.numFiles++; totalsForIngestJobs.put(ingestJobId, ingestJobTotals); } FileExtMismatchIngestModule(FileExtMismatchDetectorModuleSettings settings) { this.settings = settings; } @Override public void startUp(IngestJobContext context) throws IngestModuleException { jobId = context.getJobId(); refCounter.incrementAndGet(jobId); try { mimeTypeToExtsMap = FileExtMismatchSettings.readSettings().getMimeTypeToExtsMap(); this.detector = new FileTypeDetector(); } catch (FileExtMismatchSettings.FileExtMismatchSettingsException ex) { throw new IngestModuleException(Bundle.FileExtMismatchIngestModule_readError_message(), ex); } catch (FileTypeDetector.FileTypeDetectorInitException ex) { throw new IngestModuleException(Bundle.CannotRunFileTypeDetection(), ex); } } @Override @Messages({"FileExtMismatchIngestModule.indexError.message=Failed to index file extension mismatch artifact for keyword search."}) public ProcessResult process(AbstractFile abstractFile) { blackboard = Case.getCurrentCase().getServices().getBlackboard(); if (this.settings.skipKnownFiles() && (abstractFile.getKnown() == FileKnown.KNOWN)) { return ProcessResult.OK; } // skip non-files if ((abstractFile.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) || (abstractFile.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) || (abstractFile.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || (abstractFile.isFile() == false)) { return ProcessResult.OK; } // deleted files often have content that was not theirs and therefor causes mismatch if ((abstractFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC)) || (abstractFile.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC))) { return ProcessResult.OK; } try { long startTime = System.currentTimeMillis(); boolean mismatchDetected = compareSigTypeToExt(abstractFile); addToTotals(jobId, System.currentTimeMillis() - startTime); if (mismatchDetected) { // add artifact BlackboardArtifact bart = abstractFile.newArtifact(ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED); try { // index the artifact for keyword search blackboard.indexArtifact(bart); } catch (Blackboard.BlackboardException ex) { logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bart.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error( Bundle.FileExtMismatchIngestModule_indexError_message(), bart.getDisplayName()); } services.fireModuleDataEvent(new ModuleDataEvent(FileExtMismatchDetectorModuleFactory.getModuleName(), ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED, Collections.singletonList(bart))); } return ProcessResult.OK; } catch (TskException ex) { logger.log(Level.WARNING, "Error matching file signature", ex); //NON-NLS return ProcessResult.ERROR; } } /** * Compare file type for file and extension. * * @param abstractFile * * @return false if the two match. True if there is a mismatch. */ private boolean compareSigTypeToExt(AbstractFile abstractFile) throws TskCoreException { String currActualExt = abstractFile.getNameExtension(); // If we are skipping names with no extension if (settings.skipFilesWithNoExtension() && currActualExt.isEmpty()) { return false; } String currActualSigType = detector.getFileType(abstractFile); if (currActualSigType == null) { return false; } if (settings.getCheckType() != CHECK_TYPE.ALL) { if (settings.getCheckType() == CHECK_TYPE.NO_TEXT_FILES) { if (!currActualExt.isEmpty() && currActualSigType.equals("text/plain")) { //NON-NLS return false; } } if (settings.getCheckType() == CHECK_TYPE.ONLY_MEDIA_AND_EXE) { if (!FileExtMismatchDetectorModuleSettings.MEDIA_AND_EXE_MIME_TYPES.contains(currActualSigType)) { return false; } } } //get known allowed values from the map for this type Set<String> allowedExtSet = mimeTypeToExtsMap.get(currActualSigType); if (allowedExtSet != null) { // see if the filename ext is in the allowed list for (String e : allowedExtSet) { if (e.equals(currActualExt)) { return false; } } return true; //potential mismatch } return false; } @Override public void shutDown() { // We only need to post the summary msg from the last module per job if (refCounter.decrementAndGet(jobId) == 0) { IngestJobTotals jobTotals; synchronized (this) { jobTotals = totalsForIngestJobs.remove(jobId); } if (jobTotals != null) { StringBuilder detailsSb = new StringBuilder(); detailsSb.append("<table border='0' cellpadding='4' width='280'>"); //NON-NLS detailsSb.append("<tr><td>").append(FileExtMismatchDetectorModuleFactory.getModuleName()).append("</td></tr>"); //NON-NLS detailsSb.append("<tr><td>").append( //NON-NLS NbBundle.getMessage(this.getClass(), "FileExtMismatchIngestModule.complete.totalProcTime")) .append("</td><td>").append(jobTotals.processTime).append("</td></tr>\n"); //NON-NLS detailsSb.append("<tr><td>").append( //NON-NLS NbBundle.getMessage(this.getClass(), "FileExtMismatchIngestModule.complete.totalFiles")) .append("</td><td>").append(jobTotals.numFiles).append("</td></tr>\n"); //NON-NLS detailsSb.append("</table>"); //NON-NLS services.postMessage(IngestMessage.createMessage(IngestMessage.MessageType.INFO, FileExtMismatchDetectorModuleFactory.getModuleName(), NbBundle.getMessage(this.getClass(), "FileExtMismatchIngestModule.complete.svcMsg.text"), detailsSb.toString())); } } } }