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