show hash hit pin on groups and add hash hit counts to tooltip

This commit is contained in:
jmillman 2015-07-28 13:22:39 -04:00
parent 497ce91da4
commit 6537ebe92c
10 changed files with 289 additions and 118 deletions

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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<Long> eventIDs, String description, DescriptionLOD lod) {
private final Set<Long> hashHits;
public AggregateEvent(Interval spanningInterval, EventType type, Set<Long> eventIDs, Set<Long> 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<String> 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<Long> 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<Long> ids = new HashSet<>(ag1.getEventIDs());
ids.addAll(ag2.getEventIDs());
Sets.SetView<Long> idsUnion = Sets.union(ag1.getEventIDs(), ag2.getEventIDs());
Sets.SetView<Long> 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() {

View File

@ -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<ZoomParams> currentStateProperty) {
@ -151,6 +152,9 @@ public final class FilteredEventsModel {
public TimeLineEvent getEventById(Long eventID) {
return repo.getEventById(eventID);
}
public Set<TimeLineEvent> getEventsById(Collection<Long> eventIDs) {
return repo.getEventsById(eventIDs);
}
public Set<Long> getEventIDs(Interval timeRange, Filter filter) {
final Interval overlap;
@ -279,5 +283,4 @@ public final class FilteredEventsModel {
return requestedLOD.get();
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Copyright 2014-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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;
}

View File

@ -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<Long> 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

View File

@ -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<TimeLineEvent> getEventsById(Collection<Long> eventIDs) {
return eventIDs.stream()
.map(idToEventCache::getUnchecked)
.collect(Collectors.toSet());
}
public List<AggregateEvent> 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<Void, ProgressWindow.ProgressUpdate> {
@ -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++;

View File

@ -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";

View File

@ -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();
}
}

View File

@ -27,6 +27,7 @@ import javafx.collections.FXCollections;
public class RootFilter extends IntersectionFilter<Filter> {
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<Filter> {
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<Filter> {
@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;

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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<DescriptionLOD> descLOD = new SimpleObjectProperty<>();
private DescriptionVisibility descrVis;
private final SleuthkitCase sleuthkitCase;
private final FilteredEventsModel eventsModel;
private Map<String, Long> 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(
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<BlackboardArtifact> 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<String, Long> 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()))));
getEvent().getSpan().getEnd().toString(TimeLineController.getZonedFormatter()))
+ (collect.isEmpty() ? "" : "\n\nHash Set Hits\n" + collect));
Tooltip.install(AggregateEventNode.this, tooltip);
}
}
public Pane getSubNodePane() {

View File

@ -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