From 6537ebe92cb762c1f15cba33d8857efe8bda16a4 Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 28 Jul 2015 13:22:39 -0400 Subject: [PATCH] show hash hit pin on groups and add hash hit counts to tooltip --- .../timeline/events/AggregateEvent.java | 33 ++--- .../timeline/events/FilteredEventsModel.java | 9 +- .../timeline/events/TimeLineEvent.java | 40 +++--- .../autopsy/timeline/events/db/EventDB.java | 104 ++++++++++----- .../timeline/events/db/EventsRepository.java | 23 +++- .../autopsy/timeline/events/db/SQLHelper.java | 7 + .../timeline/filters/HashHitFilter.java | 56 ++++++++ .../autopsy/timeline/filters/RootFilter.java | 8 +- .../ui/detailview/AggregateEventNode.java | 125 +++++++++++++----- .../timeline/ui/detailview/Bundle.properties | 2 +- 10 files changed, 289 insertions(+), 118 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitFilter.java diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/AggregateEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/events/AggregateEvent.java index 938d4f5fe0..bf04a26188 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/events/AggregateEvent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/AggregateEvent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-15 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,14 +18,11 @@ */ package org.sleuthkit.autopsy.timeline.events; -import com.google.common.collect.Collections2; +import com.google.common.collect.Sets; import java.util.Collections; -import java.util.HashSet; -import java.util.List; import java.util.Set; import javax.annotation.concurrent.Immutable; import org.joda.time.Interval; -import org.openide.util.NbBundle; import org.sleuthkit.autopsy.timeline.events.type.EventType; import org.sleuthkit.autopsy.timeline.utils.IntervalUtils; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; @@ -47,26 +44,19 @@ public class AggregateEvent { private final DescriptionLOD lod; - public AggregateEvent(Interval spanningInterval, EventType type, Set eventIDs, String description, DescriptionLOD lod) { + private final Set hashHits; + + public AggregateEvent(Interval spanningInterval, EventType type, Set eventIDs, Set hashHits, String description, DescriptionLOD lod) { this.span = spanningInterval; this.type = type; + this.hashHits = hashHits; this.description = description; this.eventIDs = eventIDs; this.lod = lod; } - public AggregateEvent(Interval spanningInterval, EventType type, List events, String description, DescriptionLOD lod) { - - this.span = spanningInterval; - this.type = type; - this.description = description; - - this.eventIDs = new HashSet<>(Collections2.transform(events, Long::valueOf)); - this.lod = lod; - } - /** @return the actual interval from the first event to the last event */ public Interval getSpan() { return span; @@ -76,6 +66,10 @@ public class AggregateEvent { return Collections.unmodifiableSet(eventIDs); } + public Set getEventIDsWithHashHits() { + return Collections.unmodifiableSet(hashHits); + } + public String getDescription() { return description; } @@ -101,11 +95,10 @@ public class AggregateEvent { if (!ag1.getDescription().equals(ag2.getDescription())) { throw new IllegalArgumentException("aggregate events are not compatible they have different descriptions"); } - HashSet ids = new HashSet<>(ag1.getEventIDs()); - ids.addAll(ag2.getEventIDs()); + Sets.SetView idsUnion = Sets.union(ag1.getEventIDs(), ag2.getEventIDs()); + Sets.SetView hashHitsUnion = Sets.union(ag1.getEventIDsWithHashHits(), ag2.getEventIDsWithHashHits()); - //TODO: check that types/descriptions are actually the same -jm - return new AggregateEvent(IntervalUtils.span(ag1.span, ag2.span), ag1.getType(), ids, ag1.getDescription(), ag1.lod); + return new AggregateEvent(IntervalUtils.span(ag1.span, ag2.span), ag1.getType(), idsUnion, hashHitsUnion, ag1.getDescription(), ag1.lod); } public DescriptionLOD getLOD() { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/events/FilteredEventsModel.java index 31becd2b18..71bf913f3b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/events/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/FilteredEventsModel.java @@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.timeline.events.type.RootEventType; import org.sleuthkit.autopsy.timeline.filters.DataSourceFilter; import org.sleuthkit.autopsy.timeline.filters.DataSourcesFilter; import org.sleuthkit.autopsy.timeline.filters.Filter; +import org.sleuthkit.autopsy.timeline.filters.HashHitFilter; import org.sleuthkit.autopsy.timeline.filters.HideKnownFilter; import org.sleuthkit.autopsy.timeline.filters.IntersectionFilter; import org.sleuthkit.autopsy.timeline.filters.RootFilter; @@ -106,7 +107,7 @@ public final class FilteredEventsModel { dataSourceFilter.setSelected(Boolean.TRUE); dataSourcesFilter.addDataSourceFilter(dataSourceFilter); }); - return new RootFilter(new HideKnownFilter(), new TextFilter(), new TypeFilter(RootEventType.getInstance()), dataSourcesFilter); + return new RootFilter(new HideKnownFilter(), new HashHitFilter(), new TextFilter(), new TypeFilter(RootEventType.getInstance()), dataSourcesFilter); } public FilteredEventsModel(EventsRepository repo, ReadOnlyObjectProperty currentStateProperty) { @@ -151,6 +152,9 @@ public final class FilteredEventsModel { public TimeLineEvent getEventById(Long eventID) { return repo.getEventById(eventID); } + public Set getEventsById(Collection eventIDs) { + return repo.getEventsById(eventIDs); + } public Set getEventIDs(Interval timeRange, Filter filter) { final Interval overlap; @@ -206,7 +210,7 @@ public final class FilteredEventsModel { * @return the smallest interval spanning all the events from the * repository, ignoring any filters or requested ranges */ - public Interval getSpanningInterval() { + public Interval getSpanningInterval() { return new Interval(getMinTime() * 1000, 1000 + getMaxTime() * 1000, DateTimeZone.UTC); } @@ -279,5 +283,4 @@ public final class FilteredEventsModel { return requestedLOD.get(); } - } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/TimeLineEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/events/TimeLineEvent.java index b1340db234..be7e04e631 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/events/TimeLineEvent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/TimeLineEvent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-15 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,9 @@ public class TimeLineEvent { private final Long eventID; - private final Long fileID, time; + private final Long fileID; + + private final Long time; private final Long artifactID; @@ -38,6 +40,26 @@ public class TimeLineEvent { private final TskData.FileKnown known; + private final boolean hashHit; + + public TimeLineEvent(Long eventID, Long objID, Long artifactID, Long time, EventType type, String fullDescription, String medDescription, String shortDescription, TskData.FileKnown known, boolean hashHit) { + this.eventID = eventID; + this.fileID = objID; + this.artifactID = artifactID; + this.time = time; + this.subType = type; + + this.fullDescription = fullDescription; + this.medDescription = medDescription; + this.shortDescription = shortDescription; + this.known = known; + this.hashHit = hashHit; + } + + public boolean isHashHit() { + return hashHit; + } + public Long getArtifactID() { return artifactID; } @@ -71,20 +93,6 @@ public class TimeLineEvent { return shortDescription; } - public TimeLineEvent(Long eventID, Long objID, Long artifactID, Long time, - EventType type, String fullDescription, String medDescription, String shortDescription, TskData.FileKnown known) { - this.eventID = eventID; - this.fileID = objID; - this.artifactID = artifactID; - this.time = time; - this.subType = type; - - this.fullDescription = fullDescription; - this.medDescription = medDescription; - this.shortDescription = shortDescription; - this.known = known; - } - public TskData.FileKnown getKnown() { return known; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/events/db/EventDB.java index e29c453840..d913392341 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/events/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/db/EventDB.java @@ -30,7 +30,6 @@ import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -44,6 +43,8 @@ import java.util.TimeZone; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTimeZone; import org.joda.time.Interval; @@ -89,7 +90,8 @@ public class EventDB { FULL_DESCRIPTION("full_description"), // NON-NLS MED_DESCRIPTION("med_description"), // NON-NLS SHORT_DESCRIPTION("short_description"), // NON-NLS - TIME("time"); // NON-NLS + TIME("time"), + HASH_HIT("hash_hit"); // NON-NLS private final String columnName; @@ -358,10 +360,10 @@ public class EventDB { return getDBInfo(DBInfoKey.LAST_OBJECT_ID, -1); } - boolean hasDataSourceInfo() { + boolean hasNewColumns() { /* this relies on the fact that no tskObj has ID 0 but 0 is the default * value for the datasource_id column in the events table. */ - return hasDataSourceIDColumn() + return hasHashHitColumn() && hasDataSourceIDColumn() && (getDataSourceIDs().isEmpty() == false); } @@ -466,26 +468,35 @@ public class EventDB { + " full_description TEXT, " // NON-NLS + " med_description TEXT, " // NON-NLS + " short_description TEXT, " // NON-NLS - + " known_state INTEGER)"; // NON-NLS + + " known_state INTEGER," + + " hash_hit INTEGER)"; //boolean // NON-NLS stmt.execute(sql); } catch (SQLException ex) { LOGGER.log(Level.SEVERE, "problem creating database table", ex); // NON-NLS } - boolean hasDSInfo = hasDataSourceIDColumn(); - if (hasDSInfo == false) { + if (hasDataSourceIDColumn() == false) { try (Statement stmt = con.createStatement()) { String sql = "ALTER TABLE events ADD COLUMN datasource_id INTEGER"; // NON-NLS stmt.execute(sql); } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "problem creating database table", ex); // NON-NLS + LOGGER.log(Level.SEVERE, "problem upgrading database table", ex); // NON-NLS + } + } + + if (hasHashHitColumn() == false) { + try (Statement stmt = con.createStatement()) { + String sql = "ALTER TABLE events ADD COLUMN hash_hit INTEGER"; // NON-NLS + stmt.execute(sql); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "problem upgrading database table", ex); // NON-NLS } } try { insertRowStmt = prepareStatement( - "INSERT INTO events (datasource_id,file_id ,artifact_id, time, sub_type, base_type, full_description, med_description, short_description, known_state) " // NON-NLS - + "VALUES (?,?,?,?,?,?,?,?,?,?)"); // NON-NLS + "INSERT INTO events (datasource_id,file_id ,artifact_id, time, sub_type, base_type, full_description, med_description, short_description, known_state, hash_hit) " // NON-NLS + + "VALUES (?,?,?,?,?,?,?,?,?,?,?)"); // NON-NLS getDataSourceIDsStmt = prepareStatement("select distinct datasource_id from events"); // NON-NLS getMaxTimeStmt = prepareStatement("select Max(time) as max from events"); // NON-NLS @@ -496,14 +507,7 @@ public class EventDB { } catch (SQLException sQLException) { LOGGER.log(Level.SEVERE, "failed to prepareStatment", sQLException); // NON-NLS } - if (hasDataSourceInfo() == false) { - try (Statement stmt = con.createStatement()) { - String sql = "ALTER TABLE events ADD COLUMN datasource_id INTEGER"; // NON-NLS - stmt.execute(sql); - } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "problem creating database table", ex); // NON-NLS - } - } + try (Statement stmt = con.createStatement()) { String sql = "CREATE INDEX if not exists file_idx ON events(file_id)"; // NON-NLS @@ -551,24 +555,41 @@ public class EventDB { } } - private boolean hasDataSourceIDColumn() { + /** + * + * @param dbColumn the value of dbColumn + * + * @return the boolean + */ + private boolean hasDBColumn(final EventTableColumn dbColumn) { try (Statement stmt = con.createStatement()) { ResultSet executeQuery = stmt.executeQuery("PRAGMA table_info(events)"); while (executeQuery.next()) { - if (EventTableColumn.DATA_SOURCE_ID.toString().equals(executeQuery.getString("name"))) { + if (dbColumn.toString().equals(executeQuery.getString("name"))) { return true; } } } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "problem creating database table", ex); // NON-NLS + LOGGER.log(Level.SEVERE, "problem executing pragma", ex); // NON-NLS } return false; } - void insertEvent(long time, EventType type, long datasourceID, Long objID, Long artifactID, String fullDescription, String medDescription, String shortDescription, TskData.FileKnown known) { + private boolean hasDataSourceIDColumn() { + return hasDBColumn(EventTableColumn.DATA_SOURCE_ID); + } + + private boolean hasHashHitColumn() { + return hasDBColumn(EventTableColumn.HASH_HIT); + } + + void insertEvent(long time, EventType type, long datasourceID, Long objID, + Long artifactID, String fullDescription, String medDescription, + String shortDescription, TskData.FileKnown known, boolean hashHit) { + EventTransaction trans = beginTransaction(); - insertEvent(time, type, datasourceID, objID, artifactID, fullDescription, medDescription, shortDescription, known, trans); + insertEvent(time, type, datasourceID, objID, artifactID, fullDescription, medDescription, shortDescription, known, hashHit, trans); commitTransaction(trans, true); } @@ -576,10 +597,14 @@ public class EventDB { * use transactions to update files * * @param f - * @param tr + * @param transaction */ - void insertEvent(long time, EventType type, long datasourceID, Long objID, Long artifactID, String fullDescription, String medDescription, String shortDescription, TskData.FileKnown known, EventTransaction tr) { - if (tr.isClosed()) { + void insertEvent(long time, EventType type, long datasourceID, Long objID, + Long artifactID, String fullDescription, String medDescription, + String shortDescription, TskData.FileKnown known, boolean hashHit, + EventTransaction transaction) { + + if (transaction.isClosed()) { throw new IllegalArgumentException("can't update database with closed transaction"); // NON-NLS } int typeNum; @@ -591,7 +616,7 @@ public class EventDB { DBLock.lock(); try { - //"INSERT INTO events (datasource_id, file_id ,artifact_id, time, sub_type, base_type, full_description, med_description, short_description) " + //"INSERT INTO events (datasource_id,file_id ,artifact_id, time, sub_type, base_type, full_description, med_description, short_description, known_state, hashHit) " insertRowStmt.clearParameters(); insertRowStmt.setLong(1, datasourceID); if (objID != null) { @@ -618,6 +643,7 @@ public class EventDB { insertRowStmt.setString(9, shortDescription); insertRowStmt.setByte(10, known == null ? TskData.FileKnown.UNKNOWN.getFileKnownValue() : known.getFileKnownValue()); + insertRowStmt.setInt(11, hashHit ? 1 : 0); insertRowStmt.executeUpdate(); @@ -693,16 +719,15 @@ public class EventDB { } private TimeLineEvent constructTimeLineEvent(ResultSet rs) throws SQLException { - EventType type = RootEventType.allTypes.get(rs.getInt(EventTableColumn.SUB_TYPE.toString())); return new TimeLineEvent(rs.getLong(EventTableColumn.EVENT_ID.toString()), rs.getLong(EventTableColumn.FILE_ID.toString()), rs.getLong(EventTableColumn.ARTIFACT_ID.toString()), - rs.getLong(EventTableColumn.TIME.toString()), - type, + rs.getLong(EventTableColumn.TIME.toString()), RootEventType.allTypes.get(rs.getInt(EventTableColumn.SUB_TYPE.toString())), rs.getString(EventTableColumn.FULL_DESCRIPTION.toString()), rs.getString(EventTableColumn.MED_DESCRIPTION.toString()), rs.getString(EventTableColumn.SHORT_DESCRIPTION.toString()), - TskData.FileKnown.valueOf(rs.getByte(EventTableColumn.KNOWN.toString()))); + TskData.FileKnown.valueOf(rs.getByte(EventTableColumn.KNOWN.toString())), + rs.getInt(EventTableColumn.HASH_HIT.toString()) != 0); } /** @@ -815,7 +840,9 @@ public class EventDB { //get all agregate events in this time unit DBLock.lock(); - String query = "select strftime('" + strfTimeFormat + "',time , 'unixepoch'" + (TimeLineController.getTimeZone().get().equals(TimeZone.getDefault()) ? ", 'localtime'" : "") + ") as interval, group_concat(event_id) as event_ids, Min(time), Max(time), " + descriptionColumn + ", " + useSubTypeHelper(useSubTypes) + String query = "select strftime('" + strfTimeFormat + "',time , 'unixepoch'" + (TimeLineController.getTimeZone().get().equals(TimeZone.getDefault()) ? ", 'localtime'" : "") + ") as interval," + + " group_concat(event_id) as event_ids, Min(time), Max(time), " + descriptionColumn + ", " + useSubTypeHelper(useSubTypes) + // + " , (select group_concat(event_id) as ids_with_hash_hits from events where event_id IN event_ids)" + " from events where time >= " + start + " and time < " + end + " and " + SQLHelper.getSQLWhere(filter) // NON-NLS + " group by interval, " + useSubTypeHelper(useSubTypes) + " , " + descriptionColumn // NON-NLS + " order by Min(time)"; // NON-NLS @@ -831,12 +858,23 @@ public class EventDB { stopwatch.stop(); //System.out.println(stopwatch.elapsedMillis() / 1000.0 + " seconds"); while (rs.next()) { + String eventIDS = rs.getString("event_ids"); + HashSet hashHits = new HashSet<>(); + try (Statement st2 = con.createStatement();) { + + ResultSet executeQuery = st2.executeQuery("select event_id from events where event_id in (" + eventIDS + ") and hash_hit = 1"); + while (executeQuery.next()) { + hashHits.add(executeQuery.getLong(EventTableColumn.EVENT_ID.toString())); + } + } + EventType type = useSubTypes ? RootEventType.allTypes.get(rs.getInt(EventTableColumn.SUB_TYPE.toString())) : BaseTypes.values()[rs.getInt(EventTableColumn.BASE_TYPE.toString())]; AggregateEvent aggregateEvent = new AggregateEvent( new Interval(rs.getLong("Min(time)") * 1000, rs.getLong("Max(time)") * 1000, TimeLineController.getJodaTimeZone()), // NON-NLS type, - Arrays.asList(rs.getString("event_ids").split(",")), // NON-NLS + Stream.of(eventIDS.split(",")).map(Long::valueOf).collect(Collectors.toSet()), // NON-NLS + hashHits, rs.getString(descriptionColumn), lod); //put events in map from type/descrition -> event diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/events/db/EventsRepository.java index 7bfa9dd439..0fcf49f014 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/events/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/db/EventsRepository.java @@ -33,6 +33,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; +import java.util.stream.Collectors; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableMap; @@ -175,6 +176,13 @@ public class EventsRepository { return idToEventCache.getUnchecked(eventID); } + public Set getEventsById(Collection eventIDs) { + return eventIDs.stream() + .map(idToEventCache::getUnchecked) + .collect(Collectors.toSet()); + + } + public List getAggregatedEvents(ZoomParams params) { return aggregateEventsCache.getUnchecked(params); @@ -213,7 +221,7 @@ public class EventsRepository { } public boolean hasDataSourceInfo() { - return eventDB.hasDataSourceInfo(); + return eventDB.hasNewColumns(); } private class DBPopulationWorker extends SwingWorker { @@ -268,20 +276,21 @@ public class EventsRepository { String shortDesc = datasourceName + "/" + StringUtils.defaultIfBlank(rootFolder, ""); String medD = datasourceName + parentPath; final TskData.FileKnown known = f.getKnown(); + boolean hashHit = f.getArtifactsCount(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT) > 0; //insert it into the db if time is > 0 => time is legitimate (drops logical files) long time; if (f.getAtime() > 0) { - eventDB.insertEvent(f.getAtime(), FileSystemTypes.FILE_ACCESSED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, trans); + eventDB.insertEvent(f.getAtime(), FileSystemTypes.FILE_ACCESSED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashHit, trans); } if (f.getMtime() > 0) { - eventDB.insertEvent(f.getMtime(), FileSystemTypes.FILE_MODIFIED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, trans); + eventDB.insertEvent(f.getMtime(), FileSystemTypes.FILE_MODIFIED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashHit, trans); } if (f.getCtime() > 0) { - eventDB.insertEvent(f.getCtime(), FileSystemTypes.FILE_CHANGED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, trans); + eventDB.insertEvent(f.getCtime(), FileSystemTypes.FILE_CHANGED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashHit, trans); } if (f.getCrtime() > 0) { - eventDB.insertEvent(f.getCrtime(), FileSystemTypes.FILE_CREATED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, trans); + eventDB.insertEvent(f.getCrtime(), FileSystemTypes.FILE_CREATED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashHit, trans); } process(Arrays.asList(new ProgressWindow.ProgressUpdate(i, numFiles, @@ -381,7 +390,9 @@ public class EventsRepository { if (eventDescription != null && eventDescription.getTime() > 0L) { //insert it into the db if time is > 0 => time is legitimate long datasourceID = skCase.getContentById(bbart.getObjectID()).getDataSource().getId(); - eventDB.insertEvent(eventDescription.getTime(), type, datasourceID, bbart.getObjectID(), bbart.getArtifactID(), eventDescription.getFullDescription(), eventDescription.getMedDescription(), eventDescription.getShortDescription(), null, trans); + + boolean hashHit = skCase.getAbstractFileById(bbart.getObjectID()).getArtifactsCount(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT) > 0; + eventDB.insertEvent(eventDescription.getTime(), type, datasourceID, bbart.getObjectID(), bbart.getArtifactID(), eventDescription.getFullDescription(), eventDescription.getMedDescription(), eventDescription.getShortDescription(), null, hashHit, trans); } i++; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/db/SQLHelper.java b/Core/src/org/sleuthkit/autopsy/timeline/events/db/SQLHelper.java index 788cd02cf1..f85a1fd651 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/events/db/SQLHelper.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/db/SQLHelper.java @@ -14,6 +14,7 @@ import org.sleuthkit.autopsy.timeline.filters.AbstractFilter; import org.sleuthkit.autopsy.timeline.filters.DataSourceFilter; import org.sleuthkit.autopsy.timeline.filters.DataSourcesFilter; import org.sleuthkit.autopsy.timeline.filters.Filter; +import org.sleuthkit.autopsy.timeline.filters.HashHitFilter; import org.sleuthkit.autopsy.timeline.filters.HideKnownFilter; import org.sleuthkit.autopsy.timeline.filters.IntersectionFilter; import org.sleuthkit.autopsy.timeline.filters.TextFilter; @@ -59,6 +60,8 @@ public class SQLHelper { result = getSQLWhere((DataSourcesFilter) filter); } else if (filter instanceof HideKnownFilter) { result = getSQLWhere((HideKnownFilter) filter); + } else if (filter instanceof HashHitFilter) { + result = getSQLWhere((HashHitFilter) filter); } else if (filter instanceof TextFilter) { result = getSQLWhere((TextFilter) filter); } else if (filter instanceof TypeFilter) { @@ -79,6 +82,10 @@ public class SQLHelper { return (filter.isSelected()) ? "(" + EventDB.EventTableColumn.KNOWN.toString() + " is not '" + TskData.FileKnown.KNOWN.getFileKnownValue() + "')" // NON-NLS : "1"; } + static String getSQLWhere(HashHitFilter filter) { + return (filter.isSelected()) ? "(" + EventDB.EventTableColumn.HASH_HIT.toString() + " is not 0)" // NON-NLS + : "1"; + } static String getSQLWhere(DataSourceFilter filter) { return (filter.isSelected()) ? "(" + EventDB.EventTableColumn.DATA_SOURCE_ID.toString() + " = '" + filter.getDataSourceID() + "')" : "1"; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitFilter.java new file mode 100644 index 0000000000..60d07339ce --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/HashHitFilter.java @@ -0,0 +1,56 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.timeline.filters; + +import org.openide.util.NbBundle; + +/** + * + */ +public class HashHitFilter extends AbstractFilter { + + @Override + @NbBundle.Messages("hashHitFilter.displayName.text=Show only Hash Hits") + public String getDisplayName() { + return Bundle.hashHitFilter_displayName_text(); + } + + public HashHitFilter() { + super(); + getActiveProperty().set(false); + } + + @Override + public HashHitFilter copyOf() { + HashHitFilter hashHitFilter = new HashHitFilter(); + hashHitFilter.setSelected(isSelected()); + hashHitFilter.setDisabled(isDisabled()); + return hashHitFilter; + } + + @Override + public String getHTMLReportString() { + return "only hash hits" + getStringCheckBox();// NON-NLS + } + + @Override + public int hashCode() { + return 7; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final HashHitFilter other = (HashHitFilter) obj; + + return isSelected() == other.isSelected(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java index 4c0a43e16b..bce66f303a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/RootFilter.java @@ -27,6 +27,7 @@ import javafx.collections.FXCollections; public class RootFilter extends IntersectionFilter { private final HideKnownFilter knwonFilter; + private final HashHitFilter hashFilter; private final TextFilter textFilter; private final TypeFilter typeFilter; private final DataSourcesFilter dataSourcesFilter; @@ -35,9 +36,10 @@ public class RootFilter extends IntersectionFilter { return dataSourcesFilter; } - public RootFilter(HideKnownFilter knownFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter) { - super(FXCollections.observableArrayList(knownFilter, textFilter, dataSourceFilter, typeFilter)); + public RootFilter(HideKnownFilter knownFilter,HashHitFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter) { + super(FXCollections.observableArrayList(knownFilter,hashFilter, textFilter, dataSourceFilter, typeFilter)); this.knwonFilter = knownFilter; + this.hashFilter = hashFilter; this.textFilter = textFilter; this.typeFilter = typeFilter; this.dataSourcesFilter = dataSourceFilter; @@ -45,7 +47,7 @@ public class RootFilter extends IntersectionFilter { @Override public RootFilter copyOf() { - RootFilter filter = new RootFilter(knwonFilter.copyOf(), textFilter.copyOf(), typeFilter.copyOf(), dataSourcesFilter.copyOf()); + RootFilter filter = new RootFilter(knwonFilter.copyOf(),hashFilter.copyOf(), textFilter.copyOf(), typeFilter.copyOf(), dataSourcesFilter.copyOf()); filter.setSelected(isSelected()); filter.setDisabled(isDisabled()); return filter; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AggregateEventNode.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AggregateEventNode.java index fd7b63c010..9f1cf1b0d3 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AggregateEventNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/AggregateEventNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-15 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,13 @@ */ package org.sleuthkit.autopsy.timeline.ui.detailview; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.ExecutionException; +import java.util.logging.Level; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.beans.property.SimpleObjectProperty; @@ -58,17 +63,25 @@ import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.ColorUtilities; import org.sleuthkit.autopsy.coreutils.LoggedTask; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.events.AggregateEvent; +import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel; +import org.sleuthkit.autopsy.timeline.events.TimeLineEvent; import org.sleuthkit.autopsy.timeline.filters.Filter; import org.sleuthkit.autopsy.timeline.filters.TextFilter; import org.sleuthkit.autopsy.timeline.filters.TypeFilter; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD; import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; /** Represents an {@link AggregateEvent} in a {@link EventDetailChart}. */ public class AggregateEventNode extends StackPane { + private static final Image HASH_PIN = new Image(AggregateEventNode.class.getResourceAsStream("/org/sleuthkit/autopsy/images/hashset_hits.png")); private final static Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png"); // NON-NLS private final static Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS @@ -130,15 +143,25 @@ public class AggregateEventNode extends StackPane { private SimpleObjectProperty descLOD = new SimpleObjectProperty<>(); private DescriptionVisibility descrVis; + private final SleuthkitCase sleuthkitCase; + private final FilteredEventsModel eventsModel; + private Map hashSetCounts = null; + private Tooltip tooltip; public AggregateEventNode(final AggregateEvent event, AggregateEventNode parentEventNode, EventDetailChart chart) { this.event = event; descLOD.set(event.getLOD()); this.parentEventNode = parentEventNode; this.chart = chart; + sleuthkitCase = chart.getController().getAutopsyCase().getSleuthkitCase(); + eventsModel = chart.getController().getEventsModel(); final Region region = new Region(); HBox.setHgrow(region, Priority.ALWAYS); - final HBox hBox = new HBox(descrLabel, countLabel, region, minusButton, plusButton); + ImageView imageView = new ImageView(HASH_PIN); + final HBox hBox = new HBox(descrLabel, countLabel, region, imageView, minusButton, plusButton); + if (event.getEventIDsWithHashHits().isEmpty()) { + hBox.getChildren().remove(imageView); + } hBox.setPrefWidth(USE_COMPUTED_SIZE); hBox.setMinWidth(USE_PREF_SIZE); hBox.setPadding(new Insets(2, 5, 2, 5)); @@ -229,11 +252,41 @@ public class AggregateEventNode extends StackPane { } private void installTooltip() { - Tooltip.install(AggregateEventNode.this, new Tooltip( - NbBundle.getMessage(this.getClass(), "AggregateEventNode.installTooltip.text", - getEvent().getEventIDs().size(), getEvent().getType(), getEvent().getDescription(), - getEvent().getSpan().getStart().toString(TimeLineController.getZonedFormatter()), - getEvent().getSpan().getEnd().toString(TimeLineController.getZonedFormatter())))); + + if (tooltip == null) { + String collect = ""; + if (!event.getEventIDsWithHashHits().isEmpty()) { + if (Objects.isNull(hashSetCounts)) { + hashSetCounts = new HashMap<>(); + try { + for (TimeLineEvent tle : eventsModel.getEventsById(event.getEventIDsWithHashHits())) { + ArrayList blackboardArtifacts = sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, tle.getFileID()); + for (BlackboardArtifact artf : blackboardArtifacts) { + for (BlackboardAttribute attr : artf.getAttributes()) { + if (attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) { + hashSetCounts.merge(attr.getValueString(), 1L, Long::sum); + }; + } + } + } + } catch (TskCoreException ex) { + Logger.getLogger(AggregateEventNode.class.getName()).log(Level.SEVERE, "Error getting hashset hit info for event.", ex); + } + } + + collect = hashSetCounts.entrySet().stream() + .map((Map.Entry t) -> t.getKey() + " : " + t.getValue()) + .collect(Collectors.joining("\n")); + + } + tooltip = new Tooltip( + NbBundle.getMessage(this.getClass(), "AggregateEventNode.installTooltip.text", + getEvent().getEventIDs().size(), getEvent().getType(), getEvent().getDescription(), + getEvent().getSpan().getStart().toString(TimeLineController.getZonedFormatter()), + getEvent().getSpan().getEnd().toString(TimeLineController.getZonedFormatter())) + + (collect.isEmpty() ? "" : "\n\nHash Set Hits\n" + collect)); + Tooltip.install(AggregateEventNode.this, tooltip); + } } public Pane getSubNodePane() { @@ -371,36 +424,36 @@ public class AggregateEventNode extends StackPane { LoggedTask> loggedTask = new LoggedTask>( NbBundle.getMessage(this.getClass(), "AggregateEventNode.loggedTask.name"), true) { - @Override - protected List call() throws Exception { - //query for the sub-clusters - List aggregatedEvents = chart.getFilteredEvents().getAggregatedEvents(new ZoomParams(span, - chart.getFilteredEvents().eventTypeZoom().get(), - combinedFilter, - newLOD)); - //for each sub cluster make an AggregateEventNode to visually represent it, and set x-position - return aggregatedEvents.stream().map((AggregateEvent t) -> { - AggregateEventNode subNode = new AggregateEventNode(t, AggregateEventNode.this, chart); - subNode.setLayoutX(chart.getXAxis().getDisplayPosition(new DateTime(t.getSpan().getStartMillis())) - getLayoutXCompensation()); - return subNode; - }).collect(Collectors.toList()); // return list of AggregateEventNodes representing subclusters - } + @Override + protected List call() throws Exception { + //query for the sub-clusters + List aggregatedEvents = chart.getFilteredEvents().getAggregatedEvents(new ZoomParams(span, + chart.getFilteredEvents().eventTypeZoom().get(), + combinedFilter, + newLOD)); + //for each sub cluster make an AggregateEventNode to visually represent it, and set x-position + return aggregatedEvents.stream().map((AggregateEvent t) -> { + AggregateEventNode subNode = new AggregateEventNode(t, AggregateEventNode.this, chart); + subNode.setLayoutX(chart.getXAxis().getDisplayPosition(new DateTime(t.getSpan().getStartMillis())) - getLayoutXCompensation()); + return subNode; + }).collect(Collectors.toList()); // return list of AggregateEventNodes representing subclusters + } - @Override - protected void succeeded() { - try { - chart.setCursor(Cursor.WAIT); - //assign subNodes and request chart layout - getSubNodePane().getChildren().setAll(get()); - setDescriptionVisibility(descrVis); - chart.setRequiresLayout(true); - chart.requestChartLayout(); - chart.setCursor(null); - } catch (InterruptedException | ExecutionException ex) { - Exceptions.printStackTrace(ex); - } - } - }; + @Override + protected void succeeded() { + try { + chart.setCursor(Cursor.WAIT); + //assign subNodes and request chart layout + getSubNodePane().getChildren().setAll(get()); + setDescriptionVisibility(descrVis); + chart.setRequiresLayout(true); + chart.requestChartLayout(); + chart.setCursor(null); + } catch (InterruptedException | ExecutionException ex) { + Exceptions.printStackTrace(ex); + } + } + }; //start task chart.getController().monitorTask(loggedTask); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/Bundle.properties b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/Bundle.properties index 9262cce702..7b87db0380 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/Bundle.properties @@ -1,5 +1,5 @@ Timeline.ui.detailview.tooltip.text={0}\nRight-click to remove.\nRight-drag to reposition. -AggregateEventNode.installTooltip.text={0} {1} events\n{2}\nbetween {3}\nand {4} +AggregateEventNode.installTooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand \t{4} AggregateEventNode.loggedTask.name=Load sub events DetailViewPane.loggedTask.name=Update Details DetailViewPane.loggedTask.preparing=preparing