diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java b/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java index 3dee2839996272ad689bd02beef13d9af647b64d..373c5b5fed7d34abf8a7d1a76f383c272097caf5 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java @@ -72,6 +72,9 @@ public class Artifacts { private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED); + + private static BlackboardArtifact.Type MALWARE_ARTIFACT_TYPE = null; + private static final String MALWARE_HITS = "TSK_MALWARE"; /** * Base class for a parent node of artifacts. @@ -242,6 +245,16 @@ static class TypeFactory extends ChildFactory.Detachable<TypeNodeKey> implements */ @SuppressWarnings("deprecation") private static TypeNodeKey getTypeKey(BlackboardArtifact.Type type, SleuthkitCase skCase, long dsObjId) { + + // ELTODO + if (MALWARE_ARTIFACT_TYPE == null) { + try { + MALWARE_ARTIFACT_TYPE = skCase.getArtifactType(MALWARE_HITS); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to get TSK_MALWARE artifact type from database : ", ex); //NON-NLS + } + } + int typeId = type.getTypeID(); if (TSK_EMAIL_MSG.getTypeID() == typeId) { EmailExtracted.RootNode emailNode = new EmailExtracted(skCase, dsObjId).new RootNode(); @@ -267,7 +280,9 @@ private static TypeNodeKey getTypeKey(BlackboardArtifact.Type type, SleuthkitCas } else if (TSK_HASHSET_HIT.getTypeID() == typeId) { HashsetHits.RootNode hashsetHits = new HashsetHits(skCase, dsObjId).new RootNode(); return new TypeNodeKey(hashsetHits, TSK_HASHSET_HIT); - + } else if (MALWARE_ARTIFACT_TYPE != null && MALWARE_ARTIFACT_TYPE.getTypeID() == typeId) { + MalwareHits.RootNode malwareHits = new MalwareHits(skCase, dsObjId).new RootNode(); + return new TypeNodeKey(malwareHits, MALWARE_ARTIFACT_TYPE); } else { return new TypeNodeKey(type, dsObjId); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java index d433d0d6df40cd81369cdec5e01914fc3e4b8f71..5ebd7c421603c041c72192f2bee24774244ed478 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java @@ -60,6 +60,8 @@ public interface AutopsyItemVisitor<T> { T visit(KeywordHits kh); T visit(HashsetHits hh); + + T visit(MalwareHits mh); T visit(EmailExtracted ee); @@ -169,6 +171,11 @@ public T visit(HashsetHits hh) { return defaultVisit(hh); } + @Override + public T visit(MalwareHits mh) { + return defaultVisit(mh); + } + @Override public T visit(InterestingHits ih) { return defaultVisit(ih); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index 85e715382eac73ae9d081fa03e5153ef55c25d77..b80fa67d9404915dff1bfb2dc506ecc0b25a06a5 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -111,6 +111,8 @@ public interface DisplayableItemNodeVisitor<T> { T visit(HashsetHits.RootNode hhrn); T visit(HashsetHits.HashsetNameNode hhsn); + + T visit(MalwareHits.RootNode mhrn); T visit(EmailExtracted.RootNode eern); @@ -431,6 +433,11 @@ public T visit(HashsetHits.HashsetNameNode hhsn) { return defaultVisit(hhsn); } + @Override + public T visit(MalwareHits.RootNode mhrn) { + return defaultVisit(mhrn); + } + @Override public T visit(InterestingHits.RootNode ihrn) { return defaultVisit(ihrn); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/MalwareHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/MalwareHits.java new file mode 100755 index 0000000000000000000000000000000000000000..6c3a9f74d63005ce0aa0ba440f6eb8aa89cf56a0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/MalwareHits.java @@ -0,0 +1,331 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.WeakListeners; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.datamodel.Artifacts.UpdatableCountTypeNode; +import org.sleuthkit.datamodel.AnalysisResult; +import org.sleuthkit.datamodel.Score; + +/** + * Malware hits node support. Inner classes have all of the nodes in the tree. + */ +@NbBundle.Messages({ + "MalwareHits_malwareTypeDisplayName=Malware",}) +public class MalwareHits implements AutopsyVisitableItem { + + private static final String MALWARE_HITS = "TSK_MALWARE"; + private static BlackboardArtifact.Type MALWARE_ARTIFACT_TYPE = null; + private static final String DISPLAY_NAME = Bundle.MalwareHits_malwareTypeDisplayName(); // ELTODO get from database + private static final Logger logger = Logger.getLogger(MalwareHits.class.getName()); + private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED); + private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.DATA_ADDED); + private SleuthkitCase skCase; + private final MalwareResults malwareResults; + private final long filteringDSObjId; // 0 if not filtering/grouping by data source + + /** + * Constructor + * + * @param skCase Case DB + * + */ + public MalwareHits(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public MalwareHits(SleuthkitCase skCase, long objId) { + this.skCase = skCase; + this.filteringDSObjId = objId; + malwareResults = new MalwareResults(); + } + + @Override + public <T> T accept(AutopsyItemVisitor<T> visitor) { + return visitor.visit(this); + } + + /** + * Stores all of the malware results in a single class that is observable + * for the child nodes + */ + private class MalwareResults extends Observable { + + // list of artifacts + // NOTE: the list can be accessed by multiple worker threads and needs to be synchronized + private final Set<Long> malwareHits = new HashSet<>(); + + MalwareResults() { + update(); + } + + Set<Long> getArtifactIds() { + synchronized (malwareHits) { + return Collections.unmodifiableSet(malwareHits); + } + } + + @SuppressWarnings("deprecation") + final void update() { + synchronized (malwareHits) { + malwareHits.clear(); + } + + if (skCase == null) { + return; + } + + if (MALWARE_ARTIFACT_TYPE == null) { + try { + MALWARE_ARTIFACT_TYPE = skCase.getArtifactType(MALWARE_HITS); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to get TSK_MALWARE artifact type from database : ", ex); //NON-NLS + return; + } + } + + String query = "SELECT blackboard_artifacts.artifact_obj_id " //NON-NLS + + "FROM blackboard_artifacts,tsk_analysis_results WHERE " //NON-NLS + + "blackboard_artifacts.artifact_type_id=" + MALWARE_ARTIFACT_TYPE.getTypeID() + + " AND tsk_analysis_results.artifact_obj_id=blackboard_artifacts.artifact_obj_id" //NON-NLS + + " AND (tsk_analysis_results.significance=" + Score.Significance.NOTABLE.getId() + + " OR tsk_analysis_results.significance=" + Score.Significance.LIKELY_NONE.getId() + " )"; // ELTODO LIKELY_NOTABLE + if (filteringDSObjId > 0) { + query += " AND blackboard_artifacts.data_source_obj_id = " + filteringDSObjId; + } + + try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { + ResultSet resultSet = dbQuery.getResultSet(); + synchronized (malwareHits) { + while (resultSet.next()) { + long artifactObjId = resultSet.getLong("artifact_obj_id"); //NON-NLS + malwareHits.add(artifactObjId); + } + } + } catch (TskCoreException | SQLException ex) { + logger.log(Level.WARNING, "SQL Exception occurred: ", ex); //NON-NLS + } + + setChanged(); + notifyObservers(); + } + } + + /** + * Top-level node for all malware hits + */ + public class RootNode extends UpdatableCountTypeNode { + + public RootNode() { + super(Children.create(new HitFactory(DISPLAY_NAME), true), + Lookups.singleton(DISPLAY_NAME), + DISPLAY_NAME, + filteringDSObjId, + MALWARE_ARTIFACT_TYPE); + + super.setName(MALWARE_HITS); + //this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/hashset_hits.png"); // ELTODO + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "MalwareHits.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "MalwareHits.createSheet.name.displayName"), + NbBundle.getMessage(this.getClass(), "MalwareHits.createSheet.name.desc"), + getName())); + + return sheet; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + /** + * Node for a hash set name + + public class HashsetNameNode extends DisplayableItemNode implements Observer { + + private final String hashSetName; + + public HashsetNameNode(String hashSetName) { + super(Children.create(new HitFactory(hashSetName), true), Lookups.singleton(hashSetName)); + super.setName(hashSetName); + this.hashSetName = hashSetName; + updateDisplayName(); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/hashset_hits.png"); // ELTODO + malwareResults.addObserver(this); + } + + // Update the count in the display name + private void updateDisplayName() { + super.setDisplayName(hashSetName + " (" + malwareResults.getArtifactIds(hashSetName).size() + ")"); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "MalwareHits.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "MalwareHits.createSheet.name.displayName"), + NbBundle.getMessage(this.getClass(), "MalwareHits.createSheet.name.desc"), + getName())); + + return sheet; + } + + @Override + public <T> T accept(DisplayableItemNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + + @Override + public String getItemType() { + // For custom settings for each hash set, return + *getClass().getName() + hashSetName instead. + return getClass().getName(); + } + }*/ + + /** + * Creates the nodes for the malware hits. + */ + private class HitFactory extends BaseChildFactory<AnalysisResult> implements Observer { + + private final Map<Long, AnalysisResult> artifactHits = new HashMap<>(); + + private HitFactory(String nodeName) { + super(nodeName); + } + + @Override + protected void onAdd() { + malwareResults.addObserver(this); + } + + @Override + protected void onRemove() { + malwareResults.deleteObserver(this); + } + + @Override + protected Node createNodeForKey(AnalysisResult key) { + return new BlackboardArtifactNode(key); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + + @Override + protected List<AnalysisResult> makeKeys() { + if (skCase != null) { + + malwareResults.getArtifactIds().forEach((id) -> { + try { + if (!artifactHits.containsKey(id)) { + AnalysisResult art = skCase.getBlackboard().getAnalysisResultById(id); + //Cache attributes while we are off the EDT. + //See JIRA-5969 + art.getAttributes(); + artifactHits.put(id, art); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS + } + }); + return new ArrayList<>(artifactHits.values()); + } + return Collections.emptyList(); + } + } +}