Autopsy  4.6.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
HashDbIngestModule.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011 - 2013 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.hashdatabase;
20 
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.concurrent.atomic.AtomicLong;
28 import java.util.logging.Level;
29 import org.openide.util.NbBundle;
30 import org.openide.util.NbBundle.Messages;
44 import org.sleuthkit.datamodel.AbstractFile;
45 import org.sleuthkit.datamodel.BlackboardArtifact;
46 import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
47 import org.sleuthkit.datamodel.BlackboardAttribute;
48 import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
49 import org.sleuthkit.datamodel.HashHitInfo;
50 import org.sleuthkit.datamodel.HashUtility;
51 import org.sleuthkit.datamodel.SleuthkitCase;
52 import org.sleuthkit.datamodel.TskCoreException;
53 import org.sleuthkit.datamodel.TskData;
54 import org.sleuthkit.datamodel.TskException;
55 
56 @NbBundle.Messages({
57  "HashDbIngestModule.noKnownBadHashDbSetMsg=No notable hash set.",
58  "HashDbIngestModule.knownBadFileSearchWillNotExecuteWarn=Notable file search will not be executed.",
59  "HashDbIngestModule.noKnownHashDbSetMsg=No known hash set.",
60  "HashDbIngestModule.knownFileSearchWillNotExecuteWarn=Known file search will not be executed."
61 })
62 public class HashDbIngestModule implements FileIngestModule {
63 
64  private static final Logger logger = Logger.getLogger(HashDbIngestModule.class.getName());
65  private static final int MAX_COMMENT_SIZE = 500;
66  private final IngestServices services = IngestServices.getInstance();
67  private final SleuthkitCase skCase;
68  private final HashDbManager hashDbManager = HashDbManager.getInstance();
69  private final HashLookupModuleSettings settings;
70  private List<HashDb> knownBadHashSets = new ArrayList<>();
71  private List<HashDb> knownHashSets = new ArrayList<>();
72  private long jobId;
73  private static final HashMap<Long, IngestJobTotals> totalsForIngestJobs = new HashMap<>();
74  private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
76 
77  private static class IngestJobTotals {
78 
79  private AtomicLong totalKnownBadCount = new AtomicLong(0);
80  private AtomicLong totalCalctime = new AtomicLong(0);
81  private AtomicLong totalLookuptime = new AtomicLong(0);
82  }
83 
84  private static synchronized IngestJobTotals getTotalsForIngestJobs(long ingestJobId) {
85  IngestJobTotals totals = totalsForIngestJobs.get(ingestJobId);
86  if (totals == null) {
87  totals = new HashDbIngestModule.IngestJobTotals();
88  totalsForIngestJobs.put(ingestJobId, totals);
89  }
90  return totals;
91  }
92 
93  HashDbIngestModule(HashLookupModuleSettings settings) throws NoCurrentCaseException {
94  this.settings = settings;
95  skCase = Case.getOpenCase().getSleuthkitCase();
96  }
97 
98  @Override
100  jobId = context.getJobId();
101  if (!hashDbManager.verifyAllDatabasesLoadedCorrectly()) {
102  throw new IngestModuleException("Could not load all hash sets");
103  }
104  updateEnabledHashSets(hashDbManager.getKnownBadFileHashSets(), knownBadHashSets);
105  updateEnabledHashSets(hashDbManager.getKnownFileHashSets(), knownHashSets);
106 
107  if (refCounter.incrementAndGet(jobId) == 1) {
108  // initialize job totals
109  getTotalsForIngestJobs(jobId);
110 
111  // if first module for this job then post error msgs if needed
112  if (knownBadHashSets.isEmpty()) {
114  HashLookupModuleFactory.getModuleName(),
115  Bundle.HashDbIngestModule_noKnownBadHashDbSetMsg(),
116  Bundle.HashDbIngestModule_knownBadFileSearchWillNotExecuteWarn()));
117  }
118 
119  if (knownHashSets.isEmpty()) {
121  HashLookupModuleFactory.getModuleName(),
122  Bundle.HashDbIngestModule_noKnownHashDbSetMsg(),
123  Bundle.HashDbIngestModule_knownFileSearchWillNotExecuteWarn()));
124  }
125  }
126  }
127 
134  private void updateEnabledHashSets(List<HashDb> allHashSets, List<HashDb> enabledHashSets) {
135  enabledHashSets.clear();
136  for (HashDb db : allHashSets) {
137  if (settings.isHashSetEnabled(db)) {
138  try {
139  if (db.isValid()) {
140  enabledHashSets.add(db);
141  }
142  } catch (TskCoreException ex) {
143  logger.log(Level.WARNING, "Error getting index status for " + db.getDisplayName()+ " hash set", ex); //NON-NLS
144  }
145  }
146  }
147  }
148 
149  @Override
150  public ProcessResult process(AbstractFile file) {
151  try {
152  blackboard = Case.getOpenCase().getServices().getBlackboard();
153  } catch (NoCurrentCaseException ex) {
154  logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
155  return ProcessResult.ERROR;
156  }
157 
158  // Skip unallocated space files.
159  if ((file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) ||
160  file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) {
161  return ProcessResult.OK;
162  }
163 
164  /*
165  * Skip directories. One reason for this is because we won't accurately
166  * calculate hashes of NTFS directories that have content that spans the
167  * IDX_ROOT and IDX_ALLOC artifacts. So we disable that until a solution
168  * for it is developed.
169  */
170  if (file.isDir()) {
171  return ProcessResult.OK;
172  }
173 
174  // bail out if we have no hashes set
175  if ((knownHashSets.isEmpty()) && (knownBadHashSets.isEmpty()) && (!settings.shouldCalculateHashes())) {
176  return ProcessResult.OK;
177  }
178 
179  // Safely get a reference to the totalsForIngestJobs object
180  IngestJobTotals totals = getTotalsForIngestJobs(jobId);
181 
182  // calc hash value
183  String name = file.getName();
184  String md5Hash = file.getMd5Hash();
185  if (md5Hash == null || md5Hash.isEmpty()) {
186  try {
187  TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Disk Reads: Hash calculation");
188  long calcstart = System.currentTimeMillis();
189  md5Hash = HashUtility.calculateMd5Hash(file);
190  if (file.getSize() > 0) {
191  // Surprisingly, the hash calculation does not seem to be correlated that
192  // strongly with file size until the files get large.
193  // Only normalize if the file size is greater than ~1MB.
194  if (file.getSize() < 1000000) {
196  } else {
197  // In testing, this normalization gave reasonable resuls
198  EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000);
199  }
200  }
201  file.setMd5Hash(md5Hash);
202  long delta = (System.currentTimeMillis() - calcstart);
203  totals.totalCalctime.addAndGet(delta);
204 
205  } catch (IOException ex) {
206  logger.log(Level.WARNING, "Error calculating hash of file " + name, ex); //NON-NLS
208  HashLookupModuleFactory.getModuleName(),
209  NbBundle.getMessage(this.getClass(),
210  "HashDbIngestModule.fileReadErrorMsg",
211  name),
212  NbBundle.getMessage(this.getClass(),
213  "HashDbIngestModule.calcHashValueErr",
214  name)));
215  return ProcessResult.ERROR;
216  }
217  }
218 
219  // look up in notable first
220  boolean foundBad = false;
222  for (HashDb db : knownBadHashSets) {
223  try {
224  long lookupstart = System.currentTimeMillis();
225  HashHitInfo hashInfo = db.lookupMD5(file);
226  if (null != hashInfo) {
227  foundBad = true;
228  totals.totalKnownBadCount.incrementAndGet();
229 
230  file.setKnown(TskData.FileKnown.BAD);
231 
232  String hashSetName = db.getDisplayName();
233 
234  String comment = "";
235  ArrayList<String> comments = hashInfo.getComments();
236  int i = 0;
237  for (String c : comments) {
238  if (++i > 1) {
239  comment += " ";
240  }
241  comment += c;
242  if (comment.length() > MAX_COMMENT_SIZE) {
243  comment = comment.substring(0, MAX_COMMENT_SIZE) + "...";
244  break;
245  }
246  }
247 
248  postHashSetHitToBlackboard(file, md5Hash, hashSetName, comment, db.getSendIngestMessages());
249  }
250  long delta = (System.currentTimeMillis() - lookupstart);
251  totals.totalLookuptime.addAndGet(delta);
252 
253  } catch (TskException ex) {
254  logger.log(Level.WARNING, "Couldn't lookup notable hash for file " + name + " - see sleuthkit log for details", ex); //NON-NLS
256  HashLookupModuleFactory.getModuleName(),
257  NbBundle.getMessage(this.getClass(),
258  "HashDbIngestModule.hashLookupErrorMsg",
259  name),
260  NbBundle.getMessage(this.getClass(),
261  "HashDbIngestModule.lookingUpKnownBadHashValueErr",
262  name)));
263  ret = ProcessResult.ERROR;
264  }
265  }
266 
267  // If the file is not in the notable sets, search for it in the known sets.
268  // Any hit is sufficient to classify it as known, and there is no need to create
269  // a hit artifact or send a message to the application inbox.
270  if (!foundBad) {
271  for (HashDb db : knownHashSets) {
272  try {
273  long lookupstart = System.currentTimeMillis();
274  if (db.lookupMD5Quick(file)) {
275  file.setKnown(TskData.FileKnown.KNOWN);
276  break;
277  }
278  long delta = (System.currentTimeMillis() - lookupstart);
279  totals.totalLookuptime.addAndGet(delta);
280 
281  } catch (TskException ex) {
282  logger.log(Level.WARNING, "Couldn't lookup known hash for file " + name + " - see sleuthkit log for details", ex); //NON-NLS
284  HashLookupModuleFactory.getModuleName(),
285  NbBundle.getMessage(this.getClass(),
286  "HashDbIngestModule.hashLookupErrorMsg",
287  name),
288  NbBundle.getMessage(this.getClass(),
289  "HashDbIngestModule.lookingUpKnownHashValueErr",
290  name)));
291  ret = ProcessResult.ERROR;
292  }
293  }
294  }
295 
296  return ret;
297  }
298 
299  @Messages({"HashDbIngestModule.indexError.message=Failed to index hashset hit artifact for keyword search."})
300  private void postHashSetHitToBlackboard(AbstractFile abstractFile, String md5Hash, String hashSetName, String comment, boolean showInboxMessage) {
301  try {
302  String MODULE_NAME = NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.moduleName");
303 
304  BlackboardArtifact badFile = abstractFile.newArtifact(ARTIFACT_TYPE.TSK_HASHSET_HIT);
305  Collection<BlackboardAttribute> attributes = new ArrayList<>();
306  //TODO Revisit usage of deprecated constructor as per TSK-583
307  //BlackboardAttribute att2 = new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(), MODULE_NAME, "Known Bad", hashSetName);
308  attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, hashSetName));
309  attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_HASH_MD5, MODULE_NAME, md5Hash));
310  attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COMMENT, MODULE_NAME, comment));
311 
312  badFile.addAttributes(attributes);
313 
314  try {
315  // index the artifact for keyword search
316  blackboard.indexArtifact(badFile);
317  } catch (Blackboard.BlackboardException ex) {
318  logger.log(Level.SEVERE, "Unable to index blackboard artifact " + badFile.getArtifactID(), ex); //NON-NLS
320  Bundle.HashDbIngestModule_indexError_message(), badFile.getDisplayName());
321  }
322 
323  if (showInboxMessage) {
324  StringBuilder detailsSb = new StringBuilder();
325  //details
326  detailsSb.append("<table border='0' cellpadding='4' width='280'>"); //NON-NLS
327  //hit
328  detailsSb.append("<tr>"); //NON-NLS
329  detailsSb.append("<th>") //NON-NLS
330  .append(NbBundle.getMessage(this.getClass(), "HashDbIngestModule.postToBB.fileName"))
331  .append("</th>"); //NON-NLS
332  detailsSb.append("<td>") //NON-NLS
333  .append(abstractFile.getName())
334  .append("</td>"); //NON-NLS
335  detailsSb.append("</tr>"); //NON-NLS
336 
337  detailsSb.append("<tr>"); //NON-NLS
338  detailsSb.append("<th>") //NON-NLS
339  .append(NbBundle.getMessage(this.getClass(), "HashDbIngestModule.postToBB.md5Hash"))
340  .append("</th>"); //NON-NLS
341  detailsSb.append("<td>").append(md5Hash).append("</td>"); //NON-NLS
342  detailsSb.append("</tr>"); //NON-NLS
343 
344  detailsSb.append("<tr>"); //NON-NLS
345  detailsSb.append("<th>") //NON-NLS
346  .append(NbBundle.getMessage(this.getClass(), "HashDbIngestModule.postToBB.hashsetName"))
347  .append("</th>"); //NON-NLS
348  detailsSb.append("<td>").append(hashSetName).append("</td>"); //NON-NLS
349  detailsSb.append("</tr>"); //NON-NLS
350 
351  detailsSb.append("</table>"); //NON-NLS
352 
354  NbBundle.getMessage(this.getClass(),
355  "HashDbIngestModule.postToBB.knownBadMsg",
356  abstractFile.getName()),
357  detailsSb.toString(),
358  abstractFile.getName() + md5Hash,
359  badFile));
360  }
361  services.fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME, ARTIFACT_TYPE.TSK_HASHSET_HIT, Collections.singletonList(badFile)));
362  } catch (TskException ex) {
363  logger.log(Level.WARNING, "Error creating blackboard artifact", ex); //NON-NLS
364  }
365  }
366 
367  private static synchronized void postSummary(long jobId,
368  List<HashDb> knownBadHashSets, List<HashDb> knownHashSets) {
369  IngestJobTotals jobTotals = getTotalsForIngestJobs(jobId);
370  totalsForIngestJobs.remove(jobId);
371 
372  if ((!knownBadHashSets.isEmpty()) || (!knownHashSets.isEmpty())) {
373  StringBuilder detailsSb = new StringBuilder();
374  //details
375  detailsSb.append("<table border='0' cellpadding='4' width='280'>"); //NON-NLS
376 
377  detailsSb.append("<tr><td>") //NON-NLS
378  .append(NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.knownBadsFound"))
379  .append("</td>"); //NON-NLS
380  detailsSb.append("<td>").append(jobTotals.totalKnownBadCount.get()).append("</td></tr>"); //NON-NLS
381 
382  detailsSb.append("<tr><td>") //NON-NLS
383  .append(NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.totalCalcTime"))
384  .append("</td><td>").append(jobTotals.totalCalctime.get()).append("</td></tr>\n"); //NON-NLS
385  detailsSb.append("<tr><td>") //NON-NLS
386  .append(NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.totalLookupTime"))
387  .append("</td><td>").append(jobTotals.totalLookuptime.get()).append("</td></tr>\n"); //NON-NLS
388  detailsSb.append("</table>"); //NON-NLS
389 
390  detailsSb.append("<p>") //NON-NLS
391  .append(NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.databasesUsed"))
392  .append("</p>\n<ul>"); //NON-NLS
393  for (HashDb db : knownBadHashSets) {
394  detailsSb.append("<li>").append(db.getHashSetName()).append("</li>\n"); //NON-NLS
395  }
396 
397  detailsSb.append("</ul>"); //NON-NLS
398 
401  HashLookupModuleFactory.getModuleName(),
402  NbBundle.getMessage(HashDbIngestModule.class,
403  "HashDbIngestModule.complete.hashLookupResults"),
404  detailsSb.toString()));
405  }
406  }
407 
408  @Override
409  public void shutDown() {
410  if (refCounter.decrementAndGet(jobId) == 0) {
411  postSummary(jobId, knownBadHashSets, knownHashSets);
412  }
413  }
414 }
static IngestMessage createDataMessage(String source, String subject, String detailsHtml, String uniqueKey, BlackboardArtifact data)
static IngestMessage createErrorMessage(String source, String subject, String detailsHtml)
void startUp(org.sleuthkit.autopsy.ingest.IngestJobContext context)
static IngestMessage createMessage(MessageType messageType, String source, String subject, String detailsHtml)
static synchronized IngestJobTotals getTotalsForIngestJobs(long ingestJobId)
void updateEnabledHashSets(List< HashDb > allHashSets, List< HashDb > enabledHashSets)
void postHashSetHitToBlackboard(AbstractFile abstractFile, String md5Hash, String hashSetName, String comment, boolean showInboxMessage)
static void submitNormalizedTimingMetric(TimingMetric metric, long normalization)
void postMessage(final IngestMessage message)
void fireModuleDataEvent(ModuleDataEvent moduleDataEvent)
static synchronized void postSummary(long jobId, List< HashDb > knownBadHashSets, List< HashDb > knownHashSets)
static void error(String title, String message)
synchronized void indexArtifact(BlackboardArtifact artifact)
Definition: Blackboard.java:59
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static IngestMessage createWarningMessage(String source, String subject, String detailsHtml)
static synchronized IngestServices getInstance()

Copyright © 2012-2016 Basis Technology. Generated on: Mon May 7 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.