19 package org.sleuthkit.autopsy.modules.hashdatabase;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.concurrent.atomic.AtomicLong;
27 import java.util.function.Function;
28 import java.util.logging.Level;
29 import java.util.stream.Stream;
30 import org.openide.util.NbBundle;
31 import org.openide.util.NbBundle.Messages;
46 import org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
48 import org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
62 "HashDbIngestModule.noKnownBadHashDbSetMsg=No notable hash set.",
63 "HashDbIngestModule.knownBadFileSearchWillNotExecuteWarn=Notable file search will not be executed.",
64 "HashDbIngestModule.noKnownHashDbSetMsg=No known hash set.",
65 "HashDbIngestModule.knownFileSearchWillNotExecuteWarn=Known file search will not be executed.",
66 "# {0} - fileName",
"HashDbIngestModule.lookingUpKnownBadHashValueErr=Error encountered while looking up notable hash value for {0}.",
67 "# {0} - fileName",
"HashDbIngestModule.lookingUpNoChangeHashValueErr=Error encountered while looking up no change hash value for {0}.",
68 "# {0} - fileName",
"HashDbIngestModule.lookingUpKnownHashValueErr=Error encountered while looking up known hash value for {0}.",})
73 private final Function<AbstractFile, String> knownBadLookupError
74 = (file) -> Bundle.HashDbIngestModule_lookingUpKnownBadHashValueErr(file.getName());
76 private final Function<AbstractFile, String> noChangeLookupError
77 = (file) -> Bundle.HashDbIngestModule_lookingUpNoChangeHashValueErr(file.getName());
79 private final Function<AbstractFile, String> knownLookupError
80 = (file) -> Bundle.HashDbIngestModule_lookingUpKnownHashValueErr(file.getName());
82 private static final int MAX_COMMENT_SIZE = 500;
86 private final HashLookupModuleSettings
settings;
87 private final List<HashDb> knownBadHashSets =
new ArrayList<>();
88 private final List<HashDb> knownHashSets =
new ArrayList<>();
89 private final List<HashDb> noChangeHashSets =
new ArrayList<>();
91 private static final HashMap<Long, IngestJobTotals> totalsForIngestJobs =
new HashMap<>();
100 private final AtomicLong totalKnownBadCount =
new AtomicLong(0);
101 private final AtomicLong totalNoChangeCount =
new AtomicLong(0);
102 private final AtomicLong totalCalctime =
new AtomicLong(0);
103 private final AtomicLong totalLookuptime =
new AtomicLong(0);
108 if (totals == null) {
110 totalsForIngestJobs.put(ingestJobId, totals);
125 this.settings = settings;
131 jobId = context.getJobId();
132 if (!hashDbManager.verifyAllDatabasesLoadedCorrectly()) {
133 throw new IngestModuleException(
"Could not load all hash sets");
140 getTotalsForIngestJobs(jobId);
143 if (knownBadHashSets.isEmpty()) {
146 Bundle.HashDbIngestModule_noKnownBadHashDbSetMsg(),
147 Bundle.HashDbIngestModule_knownBadFileSearchWillNotExecuteWarn()));
150 if (knownHashSets.isEmpty()) {
153 Bundle.HashDbIngestModule_noKnownHashDbSetMsg(),
154 Bundle.HashDbIngestModule_knownFileSearchWillNotExecuteWarn()));
166 for (
HashDb db : allHashSets) {
167 if (settings.isHashSetEnabled(db)) {
170 switch (db.getKnownFilesType()) {
172 knownHashSets.add(db);
175 knownBadHashSets.add(db);
178 noChangeHashSets.add(db);
181 throw new TskCoreException(
"Unknown KnownFilesType: " + db.getKnownFilesType());
184 }
catch (TskCoreException ex) {
185 logger.log(Level.WARNING,
"Error getting index status for " + db.getDisplayName() +
" hash set", ex);
193 "HashDbIngestModule.dialogTitle.errorFindingArtifacts=Error Finding Artifacts: {0}",
195 "HashDbIngestModule.errorMessage.lookingForFileArtifacts=Error encountered while looking for existing artifacts for {0}."
202 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
206 if (shouldSkip(file)) {
215 calculateHashes(file, totals);
216 }
catch (TskCoreException ex) {
217 logger.log(Level.WARNING, String.format(
"Error calculating hash of file '%s' (id=%d).", file.getName(), file.getId()), ex);
220 NbBundle.getMessage(this.getClass(),
"HashDbIngestModule.fileReadErrorMsg", file.getName()),
221 NbBundle.getMessage(
this.getClass(),
"HashDbIngestModule.calcHashValueErr",
222 file.getParentPath() + file.getName(),
223 file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC) ?
"Allocated File" :
"Deleted File")));
231 totals.
totalLookuptime, knownBadHashSets, TskData.FileKnown.BAD, knownBadLookupError);
233 boolean foundBad = knownBadResult.isFound();
234 if (knownBadResult.isError()) {
240 totals.
totalLookuptime, noChangeHashSets, TskData.FileKnown.UNKNOWN, noChangeLookupError);
242 if (noChangeResult.isError()) {
250 for (
HashDb db : knownHashSets) {
252 long lookupstart = System.currentTimeMillis();
253 if (db.lookupMD5Quick(file)) {
254 file.setKnown(TskData.FileKnown.KNOWN);
257 long delta = (System.currentTimeMillis() - lookupstart);
260 }
catch (TskException ex) {
261 reportLookupError(ex, file, knownLookupError);
279 if ((file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)
280 || file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) {
295 if ((knownHashSets.isEmpty()) && (knownBadHashSets.isEmpty()) && (!settings.shouldCalculateHashes())) {
311 private void reportLookupError(TskException ex, AbstractFile file, Function<AbstractFile, String> lookupErrorMessage) {
312 logger.log(Level.WARNING, String.format(
313 "Couldn't lookup notable hash for file '%s' (id=%d) - see sleuthkit log for details", file.getName(), file.getId()), ex);
316 NbBundle.getMessage(this.getClass(),
"HashDbIngestModule.hashLookupErrorMsg", file.getName()),
317 lookupErrorMessage.apply(file)));
373 List<HashDb> hashSets, TskData.FileKnown statusIfFound, Function<AbstractFile, String> lookupErrorMessage) {
375 boolean found =
false;
376 boolean wasError =
false;
377 for (
HashDb db : hashSets) {
379 long lookupstart = System.currentTimeMillis();
380 HashHitInfo hashInfo = db.lookupMD5(file);
381 if (null != hashInfo) {
384 totalCount.incrementAndGet();
385 file.setKnown(statusIfFound);
386 String comment = generateComment(hashInfo);
387 if (!createArtifactIfNotExists(file, comment, db)) {
391 long delta = (System.currentTimeMillis() - lookupstart);
392 totalLookupTime.addAndGet(delta);
394 }
catch (TskException ex) {
395 reportLookupError(ex, file, lookupErrorMessage);
412 ArrayList<String> comments = hashInfo.getComments();
414 for (String c : comments) {
419 if (comment.length() > MAX_COMMENT_SIZE) {
420 comment = comment.substring(0, MAX_COMMENT_SIZE) +
"...";
441 List<BlackboardAttribute> attributesList =
new ArrayList<>();
444 Blackboard tskBlackboard = skCase.getBlackboard();
445 if (tskBlackboard.artifactExists(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, attributesList) ==
false) {
446 postHashSetHitToBlackboard(file, file.getMd5Hash(), db, comment);
448 }
catch (TskCoreException ex) {
449 logger.log(Level.SEVERE, String.format(
450 "A problem occurred while checking for existing artifacts for file '%s' (id=%d).", file.getName(), file.getId()), ex);
453 Bundle.HashDbIngestModule_dialogTitle_errorFindingArtifacts(file.getName()),
454 Bundle.HashDbIngestModule_errorMessage_lookingForFileArtifacts(file.getName())));
470 String md5Hash = file.getMd5Hash();
471 String sha256Hash = file.getSha256Hash();
472 if ((md5Hash != null && ! md5Hash.isEmpty())
473 && (sha256Hash != null && ! sha256Hash.isEmpty())) {
478 long calcstart = System.currentTimeMillis();
479 List<HashUtility.HashResult> newHashResults =
480 HashUtility.calculateHashes(file, Arrays.asList(HashUtility.HashType.MD5,HashUtility.HashType.SHA256 ));
481 if (file.getSize() > 0) {
485 if (file.getSize() < 1000000) {
492 for (HashUtility.HashResult hash : newHashResults) {
493 if (hash.getType().equals(HashUtility.HashType.MD5)) {
494 file.setMd5Hash(hash.getValue());
495 }
else if (hash.getType().equals(HashUtility.HashType.SHA256)) {
496 file.setSha256Hash(hash.getValue());
499 long delta = (System.currentTimeMillis() - calcstart);
500 totals.totalCalctime.addAndGet(delta);
509 if (knownFilesType == null) {
510 return Score.SCORE_UNKNOWN;
512 switch (knownFilesType) {
514 return Score.SCORE_NONE;
516 return Score.SCORE_NOTABLE;
519 return Score.SCORE_UNKNOWN;
531 "HashDbIngestModule.indexError.message=Failed to index hashset hit artifact for keyword search."
537 List<BlackboardAttribute> attributes = Arrays.asList(
538 new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, moduleName, db.getDisplayName()),
539 new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_HASH_MD5, moduleName, md5Hash),
540 new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COMMENT, moduleName, comment)
544 BlackboardArtifact badFile = abstractFile.newAnalysisResult(
546 null, db.getDisplayName(), null,
548 ).getAnalysisResult();
555 blackboard.postArtifact(badFile, moduleName);
556 }
catch (Blackboard.BlackboardException ex) {
557 logger.log(Level.SEVERE,
"Unable to index blackboard artifact " + badFile.getArtifactID(), ex);
559 Bundle.HashDbIngestModule_indexError_message(), badFile.getDisplayName());
563 StringBuilder detailsSb =
new StringBuilder();
565 detailsSb.append(
"<table border='0' cellpadding='4' width='280'>");
567 detailsSb.append(
"<tr>");
568 detailsSb.append(
"<th>")
569 .append(NbBundle.getMessage(
this.getClass(),
"HashDbIngestModule.postToBB.fileName"))
571 detailsSb.append(
"<td>")
572 .append(abstractFile.getName())
574 detailsSb.append(
"</tr>");
576 detailsSb.append(
"<tr>");
577 detailsSb.append(
"<th>")
578 .append(NbBundle.getMessage(
this.getClass(),
"HashDbIngestModule.postToBB.md5Hash"))
580 detailsSb.append(
"<td>").append(md5Hash).append(
"</td>");
581 detailsSb.append(
"</tr>");
583 detailsSb.append(
"<tr>");
584 detailsSb.append(
"<th>")
585 .append(NbBundle.getMessage(
this.getClass(),
"HashDbIngestModule.postToBB.hashsetName"))
587 detailsSb.append(
"<td>").append(db.getDisplayName()).append(
"</td>");
588 detailsSb.append(
"</tr>");
590 detailsSb.append(
"</table>");
593 NbBundle.getMessage(this.getClass(),
"HashDbIngestModule.postToBB.knownBadMsg", abstractFile.getName()),
594 detailsSb.toString(),
595 abstractFile.getName() + md5Hash,
598 }
catch (TskException ex) {
599 logger.log(Level.WARNING,
"Error creating blackboard artifact", ex);
611 @Messages(
"HashDbIngestModule.complete.noChangesFound=No Change items found:")
612 private static synchronized
void postSummary(
long jobId, List<
HashDb> knownBadHashSets,
613 List<
HashDb> noChangeHashSets, List<
HashDb> knownHashSets) {
616 totalsForIngestJobs.remove(jobId);
618 if ((!knownBadHashSets.isEmpty()) || (!knownHashSets.isEmpty()) || (!noChangeHashSets.isEmpty())) {
619 StringBuilder detailsSb =
new StringBuilder();
622 "<table border='0' cellpadding='4' width='280'>" +
623 "<tr><td>" + NbBundle.getMessage(
HashDbIngestModule.class,
"HashDbIngestModule.complete.knownBadsFound") +
"</td>" +
626 "<tr><td>" + Bundle.HashDbIngestModule_complete_noChangesFound() +
"</td>" +
629 "<tr><td>" + NbBundle.getMessage(
HashDbIngestModule.class,
"HashDbIngestModule.complete.totalCalcTime") +
630 "</td><td>" + jobTotals.
totalCalctime.get() +
"</td></tr>\n" +
632 "<tr><td>" + NbBundle.getMessage(
HashDbIngestModule.class,
"HashDbIngestModule.complete.totalLookupTime") +
633 "</td><td>" + jobTotals.
totalLookuptime.get() +
"</td></tr>\n</table>" +
635 "<p>" + NbBundle.getMessage(
HashDbIngestModule.class,
"HashDbIngestModule.complete.databasesUsed") +
"</p>\n<ul>");
637 Stream.concat(knownBadHashSets.stream(), noChangeHashSets.stream()).forEach((db) -> {
641 detailsSb.append(
"</ul>");
646 NbBundle.getMessage(
HashDbIngestModule.class,
"HashDbIngestModule.complete.hashLookupResults"),
647 detailsSb.toString()));
654 postSummary(jobId, knownBadHashSets, noChangeHashSets, knownHashSets);
abstract boolean getSendIngestMessages()
synchronized long decrementAndGet(long jobId)
ProcessResult process(AbstractFile file)
static IngestMessage createDataMessage(String source, String subject, String detailsHtml, String uniqueKey, BlackboardArtifact data)
final HashLookupModuleSettings settings
static IngestMessage createErrorMessage(String source, String subject, String detailsHtml)
void initializeHashsets(List< HashDb > allHashSets)
final AtomicLong totalNoChangeCount
abstract String getHashSetName()
void startUp(org.sleuthkit.autopsy.ingest.IngestJobContext context)
synchronized long incrementAndGet(long jobId)
static IngestMessage createMessage(MessageType messageType, String source, String subject, String detailsHtml)
static synchronized IngestJobTotals getTotalsForIngestJobs(long ingestJobId)
final AtomicLong totalLookuptime
void calculateHashes(AbstractFile file, IngestJobTotals totals)
final AtomicLong totalCalctime
static TimingMetric getTimingMetric(String name)
final SleuthkitCase skCase
static synchronized HashDbManager getInstance()
FindInHashsetsResult findInHashsets(AbstractFile file, AtomicLong totalCount, AtomicLong totalLookupTime, List< HashDb > hashSets, TskData.FileKnown statusIfFound, Function< AbstractFile, String > lookupErrorMessage)
final AtomicLong totalKnownBadCount
abstract HashDb.KnownFilesType getKnownFilesType()
void postMessage(final IngestMessage message)
void reportLookupError(TskException ex, AbstractFile file, Function< AbstractFile, String > lookupErrorMessage)
SleuthkitCase getSleuthkitCase()
synchronized List< HashDb > getAllHashSets()
static void submitTimingMetric(TimingMetric metric)
static void error(String title, String message)
synchronized static Logger getLogger(String name)
static Case getCurrentCaseThrows()
static IngestMessage createWarningMessage(String source, String subject, String detailsHtml)
boolean createArtifactIfNotExists(AbstractFile file, String comment, HashDb db)
boolean shouldSkip(AbstractFile file)
void postHashSetHitToBlackboard(AbstractFile abstractFile, String md5Hash, HashDb db, String comment)
static void submitNormalizedTimingMetric(TimingMetric metric, long normalization)
String generateComment(HashHitInfo hashInfo)
static String getModuleName()
Score getScore(HashDb.KnownFilesType knownFilesType)
static synchronized IngestServices getInstance()