Merge pull request #7136 from sleuthkit/release-4.19.0
Merge release 4.19.0 branch into develop branch
@ -5,7 +5,10 @@ CentralRepoCommentDialog.title.addEditCentralRepoComment=Add/Edit Central Reposi
|
||||
OpenIDE-Module-Name=Central Repository
|
||||
OpenIDE-Module-Display-Category=Ingest Module
|
||||
OpenIDE-Module-Short-Description=Central Repository Ingest Module
|
||||
OpenIDE-Module-Long-Description=Central Repository ingest module and central database. \n\nThe Central Repository ingest module stores attributes of artifacts matching selected correlation types into a central database.\nStored attributes are used in future cases to correlate and analyzes files and artifacts during ingest.
|
||||
OpenIDE-Module-Long-Description=\
|
||||
Central Repository ingest module and central database. \n\n\
|
||||
The Central Repository ingest module stores attributes of artifacts matching selected correlation types into a central database.\n\
|
||||
Stored attributes are used in future cases to correlate and analyzes files and artifacts during ingest.
|
||||
CentralRepoCommentDialog.commentLabel.text=Comment:
|
||||
CentralRepoCommentDialog.okButton.text=&OK
|
||||
CentralRepoCommentDialog.cancelButton.text=C&ancel
|
||||
|
@ -67,7 +67,7 @@ import org.sleuthkit.datamodel.TskData;
|
||||
public final class OtherOccurrences {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(OtherOccurrences.class.getName());
|
||||
|
||||
|
||||
private static final String UUID_PLACEHOLDER_STRING = "NoCorrelationAttributeInstance";
|
||||
|
||||
private OtherOccurrences() {
|
||||
@ -76,7 +76,7 @@ public final class OtherOccurrences {
|
||||
/**
|
||||
* Determine what attributes can be used for correlation based on the node.
|
||||
*
|
||||
* @param node The node to correlate
|
||||
* @param node The node to correlate
|
||||
* @param osAccount the osAccount to correlate
|
||||
*
|
||||
* @return A list of attributes that can be used for correlation
|
||||
@ -101,23 +101,23 @@ public final class OtherOccurrences {
|
||||
TskData.FileKnown.KNOWN,
|
||||
osAccount.getId());
|
||||
|
||||
ret.add(correlationAttributeInstance);
|
||||
ret.add(correlationAttributeInstance);
|
||||
} catch (CentralRepoException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Cannot get central repository for OsAccount: %s.", osAccountAddr.get()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, String.format("Cannot get central repository for OsAccount: %s.", osAccountAddr.get()), ex); //NON-NLS
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.WARNING, String.format("Exception while getting open case looking up osAccount %s.", osAccountAddr.get()), ex); //NON-NLS
|
||||
} catch (CorrelationAttributeNormalizationException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Exception with Correlation Attribute Normalization for osAccount %s.", osAccountAddr.get()), ex); //NON-NLS
|
||||
logger.log(Level.WARNING, String.format("Exception while getting open case looking up osAccount %s.", osAccountAddr.get()), ex); //NON-NLS
|
||||
} catch (CorrelationAttributeNormalizationException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Exception with Correlation Attribute Normalization for osAccount %s.", osAccountAddr.get()), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.INFO, String.format("Unable to check create CorrelationAttribtueInstance for osAccount %s.", osAccountAddr.get()), ex);
|
||||
logger.log(Level.INFO, String.format("Unable to check create CorrelationAttribtueInstance for osAccount %s.", osAccountAddr.get()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine what attributes can be used for correlation based on the node.
|
||||
* If EamDB is not enabled, get the default Files correlation.
|
||||
@ -245,10 +245,10 @@ public final class OtherOccurrences {
|
||||
* artifact. If the central repo is not enabled, this will only return files
|
||||
* from the current case with matching MD5 hashes.
|
||||
*
|
||||
* @param file The current file.
|
||||
* @param deviceId The device ID for the current data source.
|
||||
* @param file The current file.
|
||||
* @param deviceId The device ID for the current data source.
|
||||
* @param dataSourceName The name of the current data source.
|
||||
* @param corAttr CorrelationAttribute to query for
|
||||
* @param corAttr CorrelationAttribute to query for
|
||||
*
|
||||
* @return A collection of correlated artifact instances
|
||||
*/
|
||||
@ -280,12 +280,12 @@ public final class OtherOccurrences {
|
||||
UniquePathKey uniquePathKey = new UniquePathKey(newNode);
|
||||
nodeDataMap.put(uniquePathKey, newNode);
|
||||
}
|
||||
if (file != null && corAttr.getCorrelationType().getDisplayName().equals("Files")) {
|
||||
List<AbstractFile> caseDbFiles = getCaseDbMatches(corAttr, openCase, file);
|
||||
}
|
||||
if (file != null && corAttr.getCorrelationType().getDisplayName().equals("Files")) {
|
||||
List<AbstractFile> caseDbFiles = getCaseDbMatches(corAttr, openCase, file);
|
||||
|
||||
for (AbstractFile caseDbFile : caseDbFiles) {
|
||||
addOrUpdateNodeData(openCase, nodeDataMap, caseDbFile);
|
||||
}
|
||||
for (AbstractFile caseDbFile : caseDbFiles) {
|
||||
addOrUpdateNodeData(openCase, nodeDataMap, caseDbFile);
|
||||
}
|
||||
}
|
||||
return nodeDataMap;
|
||||
@ -387,7 +387,7 @@ public final class OtherOccurrences {
|
||||
public static String makeDataSourceString(String caseUUID, String deviceId, String dataSourceName) {
|
||||
return caseUUID + deviceId + dataSourceName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the list of Eam Cases and determines the earliest case creation
|
||||
* date. Sets the label to display the earliest date string to the user.
|
||||
@ -417,8 +417,8 @@ public final class OtherOccurrences {
|
||||
|
||||
return dateStringDisplay;
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
|
||||
@NbBundle.Messages({
|
||||
"OtherOccurrences.csvHeader.case=Case",
|
||||
"OtherOccurrences.csvHeader.device=Device",
|
||||
"OtherOccurrences.csvHeader.dataSource=Data Source",
|
||||
|
@ -85,7 +85,7 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
||||
private AbstractFile file = null;
|
||||
|
||||
private SwingWorker<?, ?> worker;
|
||||
|
||||
|
||||
// Initializing the JFileChooser in a thread to prevent a block on the EDT
|
||||
// see https://stackoverflow.com/questions/49792375/jfilechooser-is-very-slow-when-using-windows-look-and-feel
|
||||
private final FutureTask<JFileChooser> futureFileChooser = new FutureTask<>(JFileChooser::new);
|
||||
@ -101,7 +101,7 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
||||
this.correlationAttributes = new ArrayList<>();
|
||||
initComponents();
|
||||
customizeComponents();
|
||||
|
||||
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
executor.execute(futureFileChooser);
|
||||
}
|
||||
@ -223,7 +223,7 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
||||
String caseDisplayName = Bundle.OtherOccurrencesPanel_caseDetailsDialog_noCaseNameError();
|
||||
String details = Bundle.OtherOccurrencesPanel_caseDetailsDialog_noDetails();
|
||||
try {
|
||||
if (-1 != selectedRowViewIdx) {
|
||||
if (-1 != selectedRowViewIdx && filesTableModel.getRowCount() > 0) {
|
||||
CentralRepository dbManager = CentralRepository.getInstance();
|
||||
int selectedRowModelIdx = filesTable.convertRowIndexToModel(selectedRowViewIdx);
|
||||
List<NodeData> rowList = filesTableModel.getListOfNodesForFile(selectedRowModelIdx);
|
||||
@ -253,9 +253,9 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
||||
|
||||
private void saveToCSV() throws NoCurrentCaseException {
|
||||
if (casesTableModel.getRowCount() > 0) {
|
||||
|
||||
if(CSVFileChooser == null) {
|
||||
try{
|
||||
|
||||
if (CSVFileChooser == null) {
|
||||
try {
|
||||
CSVFileChooser = futureFileChooser.get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
// If something happened with the thread try and
|
||||
@ -264,7 +264,7 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
||||
CSVFileChooser = new JFileChooser();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Calendar now = Calendar.getInstance();
|
||||
String fileName = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS_other_data_sources.csv", now);
|
||||
CSVFileChooser.setCurrentDirectory(new File(Case.getCurrentCaseThrows().getExportDirectory()));
|
||||
@ -329,8 +329,9 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
||||
}
|
||||
|
||||
casesTableModel.clearTable();
|
||||
|
||||
OtherOccurrenceOneTypeWorker.OneTypeData data = get();
|
||||
|
||||
OtherOccurrenceOneTypeWorker.OneTypeData data = get();
|
||||
correlationAttributes.addAll(data.getCorrelationAttributesToAdd());
|
||||
for (CorrelationCase corCase : data.getCaseNames().values()) {
|
||||
casesTableModel.addCorrelationCase(new CorrelationCaseWrapper(corCase));
|
||||
}
|
||||
@ -339,12 +340,13 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
||||
casesTableModel.addCorrelationCase(NO_ARTIFACTS_CASE);
|
||||
} else if (caseCount == 0) {
|
||||
casesTableModel.addCorrelationCase(NO_RESULTS_CASE);
|
||||
}
|
||||
String earliestDate = data.getEarliestCaseDate();
|
||||
earliestCaseDate.setText(earliestDate.isEmpty() ? Bundle.OtherOccurrencesPanel_earliestCaseNotAvailable() : earliestDate);
|
||||
foundInLabel.setText(String.format(Bundle.OtherOccurrencesPanel_foundIn_text(), data.getTotalCount(), caseCount, data.getDataSourceCount()));
|
||||
if (caseCount > 0) {
|
||||
casesTable.setRowSelectionInterval(0, 0);
|
||||
} else {
|
||||
String earliestDate = data.getEarliestCaseDate();
|
||||
earliestCaseDate.setText(earliestDate.isEmpty() ? Bundle.OtherOccurrencesPanel_earliestCaseNotAvailable() : earliestDate);
|
||||
foundInLabel.setText(String.format(Bundle.OtherOccurrencesPanel_foundIn_text(), data.getTotalCount(), caseCount, data.getDataSourceCount()));
|
||||
if (caseCount > 0) {
|
||||
casesTable.setRowSelectionInterval(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
@ -389,12 +391,13 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
||||
casesTableModel.addCorrelationCase(NO_ARTIFACTS_CASE);
|
||||
} else if (caseCount == 0) {
|
||||
casesTableModel.addCorrelationCase(NO_RESULTS_CASE);
|
||||
}
|
||||
String earliestDate = data.getEarliestCaseDate();
|
||||
earliestCaseDate.setText(earliestDate.isEmpty() ? Bundle.OtherOccurrencesPanel_earliestCaseNotAvailable() : earliestDate);
|
||||
foundInLabel.setText(String.format(Bundle.OtherOccurrencesPanel_foundIn_text(), data.getInstanceDataCount(), caseCount, data.getDataSourceCount()));
|
||||
if (caseCount > 0) {
|
||||
casesTable.setRowSelectionInterval(0, 0);
|
||||
} else {
|
||||
String earliestDate = data.getEarliestCaseDate();
|
||||
earliestCaseDate.setText(earliestDate.isEmpty() ? Bundle.OtherOccurrencesPanel_earliestCaseNotAvailable() : earliestDate);
|
||||
foundInLabel.setText(String.format(Bundle.OtherOccurrencesPanel_foundIn_text(), data.getInstanceDataCount(), caseCount, data.getDataSourceCount()));
|
||||
if (caseCount > 0) {
|
||||
casesTable.setRowSelectionInterval(0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -420,7 +423,7 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
|
||||
worker = new SelectionWorker(correlationAttributes, file, deviceId, dataSourceName) {
|
||||
@ -440,31 +443,32 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
||||
currentCaseName = null;
|
||||
logger.log(Level.WARNING, "Unable to get current case for other occurrences content viewer", ex);
|
||||
}
|
||||
|
||||
for (NodeData nodeData : correlatedNodeDataMap.values()) {
|
||||
for (int selectedRow : selectedCaseIndexes) {
|
||||
try {
|
||||
if (nodeData.isCentralRepoNode()) {
|
||||
if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)) != null
|
||||
&& casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) {
|
||||
if (casesTableModel.getRowCount() > 0) {
|
||||
for (NodeData nodeData : correlatedNodeDataMap.values()) {
|
||||
for (int selectedRow : selectedCaseIndexes) {
|
||||
try {
|
||||
if (nodeData.isCentralRepoNode()) {
|
||||
if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)) != null
|
||||
&& casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) {
|
||||
dataSourcesTableModel.addNodeData(nodeData);
|
||||
}
|
||||
} else if (currentCaseName != null && (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(currentCaseName))) {
|
||||
dataSourcesTableModel.addNodeData(nodeData);
|
||||
}
|
||||
} else if (currentCaseName != null && (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(currentCaseName))) {
|
||||
dataSourcesTableModel.addNodeData(nodeData);
|
||||
} catch (CentralRepoException ex) {
|
||||
logger.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex);
|
||||
}
|
||||
} catch (CentralRepoException ex) {
|
||||
logger.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dataSourcesTable.getRowCount() > 0) {
|
||||
if (dataSourcesTableModel.getRowCount() > 0) {
|
||||
dataSourcesTable.setRowSelectionInterval(0, 0);
|
||||
}
|
||||
|
||||
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to update OtherOccurrencesPanel on data source selection", ex);
|
||||
logger.log(Level.SEVERE, "Failed to update OtherOccurrencesPanel on data source selection", ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -496,25 +500,29 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
||||
|
||||
try {
|
||||
Map<UniquePathKey, NodeData> correlatedNodeDataMap = get();
|
||||
for (NodeData nodeData : correlatedNodeDataMap.values()) {
|
||||
for (int selectedDataSourceRow : selectedDataSources) {
|
||||
try {
|
||||
if (nodeData.isCentralRepoNode()) {
|
||||
if (dataSourcesTableModel.getCaseUUIDForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())
|
||||
&& dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) {
|
||||
filesTableModel.addNodeData(nodeData);
|
||||
}
|
||||
} else {
|
||||
if (dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) {
|
||||
filesTableModel.addNodeData(nodeData);
|
||||
if (dataSourcesTableModel.getRowCount() > 0) {
|
||||
for (NodeData nodeData : correlatedNodeDataMap.values()) {
|
||||
for (int selectedDataSourceRow : selectedDataSources) {
|
||||
int rowModelIndex = dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow);
|
||||
try {
|
||||
if (nodeData.isCentralRepoNode()) {
|
||||
if (dataSourcesTableModel.getCaseUUIDForRow(rowModelIndex).equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())
|
||||
&& dataSourcesTableModel.getDeviceIdForRow(rowModelIndex).equals(nodeData.getDeviceID())) {
|
||||
filesTableModel.addNodeData(nodeData);
|
||||
}
|
||||
} else {
|
||||
if (dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) {
|
||||
filesTableModel.addNodeData(nodeData);
|
||||
}
|
||||
}
|
||||
} catch (CentralRepoException ex) {
|
||||
logger.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex);
|
||||
}
|
||||
} catch (CentralRepoException ex) {
|
||||
logger.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filesTable.getRowCount() > 0) {
|
||||
|
||||
if (filesTableModel.getRowCount() > 0) {
|
||||
filesTable.setRowSelectionInterval(0, 0);
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
@ -535,18 +543,20 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
||||
private void updateOnFileSelection() {
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
try {
|
||||
if (filesTable.getSelectedRowCount() == 1) {
|
||||
if (filesTableModel.getRowCount() > 0 && filesTable.getSelectedRowCount() == 1) {
|
||||
//if there is one file selected update the deatils to show the data for that file
|
||||
occurrencePanel = new OccurrencePanel(filesTableModel.getListOfNodesForFile(filesTable.convertRowIndexToModel(filesTable.getSelectedRow())));
|
||||
} else if (dataSourcesTable.getSelectedRowCount() == 1) {
|
||||
} else if (dataSourcesTableModel.getRowCount() > 0 && dataSourcesTable.getSelectedRowCount() == 1) {
|
||||
//if no files were selected and only one data source is selected update the information to reflect the data source
|
||||
String caseName = dataSourcesTableModel.getCaseNameForRow(dataSourcesTable.convertRowIndexToModel(dataSourcesTable.getSelectedRow()));
|
||||
String dsName = dataSourcesTableModel.getValueAt(dataSourcesTable.convertRowIndexToModel(dataSourcesTable.getSelectedRow()), 0).toString();
|
||||
String caseCreatedDate = "";
|
||||
for (int row : casesTable.getSelectedRows()) {
|
||||
if (casesTableModel.getValueAt(casesTable.convertRowIndexToModel(row), 0).toString().equals(caseName)) {
|
||||
caseCreatedDate = getCaseCreatedDate(row);
|
||||
break;
|
||||
if (casesTableModel.getRowCount() > 0) {
|
||||
for (int row : casesTable.getSelectedRows()) {
|
||||
if (casesTableModel.getValueAt(casesTable.convertRowIndexToModel(row), 0).toString().equals(caseName)) {
|
||||
caseCreatedDate = getCaseCreatedDate(row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
occurrencePanel = new OccurrencePanel(caseName, caseCreatedDate, dsName);
|
||||
@ -555,7 +565,7 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
||||
//update the information to reflect the case
|
||||
String createdDate;
|
||||
String caseName = "";
|
||||
if (casesTable.getRowCount() > 0) {
|
||||
if (casesTableModel.getRowCount() > 0) {
|
||||
caseName = casesTableModel.getValueAt(casesTable.convertRowIndexToModel(casesTable.getSelectedRow()), 0).toString();
|
||||
}
|
||||
if (caseName.isEmpty()) {
|
||||
@ -586,7 +596,7 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
||||
*/
|
||||
private String getCaseCreatedDate(int caseTableRowIdx) {
|
||||
try {
|
||||
if (CentralRepository.isEnabled()) {
|
||||
if (CentralRepository.isEnabled() && casesTableModel.getRowCount() > 0) {
|
||||
CorrelationCase partialCase;
|
||||
partialCase = casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(caseTableRowIdx));
|
||||
if (partialCase == null) {
|
||||
@ -614,11 +624,11 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
||||
|
||||
/**
|
||||
* Construct a new SelectionWorker.
|
||||
*
|
||||
*
|
||||
* @param coAtInstances
|
||||
* @param abstractFile
|
||||
* @param deviceIdStr
|
||||
* @param dataSourceNameStr
|
||||
* @param dataSourceNameStr
|
||||
*/
|
||||
SelectionWorker(Collection<CorrelationAttributeInstance> coAtInstances, AbstractFile abstractFile, String deviceIdStr, String dataSourceNameStr) {
|
||||
this.coAtInstances = coAtInstances;
|
||||
@ -632,12 +642,12 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
||||
Map<UniquePathKey, NodeData> correlatedNodeDataMap = new HashMap<>();
|
||||
for (CorrelationAttributeInstance corAttr : coAtInstances) {
|
||||
correlatedNodeDataMap.putAll(OtherOccurrences.getCorrelatedInstances(abstractFile, deviceIdStr, dataSourceNameStr, corAttr));
|
||||
|
||||
if(isCancelled()) {
|
||||
|
||||
if (isCancelled()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return correlatedNodeDataMap;
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.HashUtility;
|
||||
import org.sleuthkit.datamodel.InvalidAccountIDException;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
@ -192,7 +194,11 @@ public class CorrelationAttributeUtil {
|
||||
|
||||
} else if (artifactTypeID == ARTIFACT_TYPE.TSK_INSTALLED_PROG.getTypeID()) {
|
||||
BlackboardAttribute setNameAttr = sourceArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH));
|
||||
String pathAttrString = null;
|
||||
if (setNameAttr != null) {
|
||||
pathAttrString = setNameAttr.getValueString();
|
||||
}
|
||||
if (pathAttrString != null && !pathAttrString.isEmpty()) {
|
||||
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH, CorrelationAttributeInstance.INSTALLED_PROGS_TYPE_ID);
|
||||
} else {
|
||||
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, CorrelationAttributeInstance.INSTALLED_PROGS_TYPE_ID);
|
||||
@ -221,7 +227,7 @@ public class CorrelationAttributeUtil {
|
||||
}
|
||||
return correlationAttrs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Makes a correlation attribute instance from a phone number attribute of
|
||||
* an artifact.
|
||||
@ -389,9 +395,17 @@ public class CorrelationAttributeUtil {
|
||||
private static CorrelationAttributeInstance makeCorrAttr(BlackboardArtifact artifact, CorrelationAttributeInstance.Type correlationType, String value) {
|
||||
try {
|
||||
Case currentCase = Case.getCurrentCaseThrows();
|
||||
AbstractFile bbSourceFile = currentCase.getSleuthkitCase().getAbstractFileById(artifact.getObjectID());
|
||||
if (null == bbSourceFile) {
|
||||
logger.log(Level.SEVERE, "Error creating artifact instance. Abstract File was null."); // NON-NLS
|
||||
Content sourceContent = currentCase.getSleuthkitCase().getContentById(artifact.getObjectID());
|
||||
if (null == sourceContent) {
|
||||
logger.log(Level.SEVERE, "Error creating artifact instance of type {0}. Failed to load content with ID: {1} associated with artifact with ID: {2}",
|
||||
new Object[]{correlationType.getDisplayName(), artifact.getObjectID(), artifact.getId()}); // NON-NLS
|
||||
return null;
|
||||
}
|
||||
|
||||
Content ds = sourceContent.getDataSource();
|
||||
if (ds == null) {
|
||||
logger.log(Level.SEVERE, "Error creating artifact instance of type {0}. Failed to load data source for content with ID: {1}",
|
||||
new Object[]{correlationType.getDisplayName(), artifact.getObjectID()}); // NON-NLS
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -401,17 +415,24 @@ public class CorrelationAttributeUtil {
|
||||
correlationType,
|
||||
value,
|
||||
correlationCase,
|
||||
CorrelationDataSource.fromTSKDataSource(correlationCase, bbSourceFile.getDataSource()),
|
||||
CorrelationDataSource.fromTSKDataSource(correlationCase, ds),
|
||||
"",
|
||||
"",
|
||||
TskData.FileKnown.UNKNOWN,
|
||||
bbSourceFile.getId());
|
||||
sourceContent.getId());
|
||||
} else {
|
||||
if (! (sourceContent instanceof AbstractFile)) {
|
||||
logger.log(Level.SEVERE, "Error creating artifact instance of type {0}. Source content of artifact with ID: {1} is not an AbstractFile",
|
||||
new Object[]{correlationType.getDisplayName(), artifact.getId()});
|
||||
return null;
|
||||
}
|
||||
AbstractFile bbSourceFile = (AbstractFile) sourceContent;
|
||||
|
||||
return new CorrelationAttributeInstance(
|
||||
correlationType,
|
||||
value,
|
||||
correlationCase,
|
||||
CorrelationDataSource.fromTSKDataSource(correlationCase, bbSourceFile.getDataSource()),
|
||||
CorrelationDataSource.fromTSKDataSource(correlationCase, ds),
|
||||
bbSourceFile.getParentPath() + bbSourceFile.getName(),
|
||||
"",
|
||||
TskData.FileKnown.UNKNOWN,
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Central Repository
|
||||
*
|
||||
* Copyright 2017-2020 Basis Technology Corp.
|
||||
* Copyright 2017-2021 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -30,9 +30,7 @@ import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
@ -64,9 +62,9 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
|
||||
import org.sleuthkit.datamodel.Tag;
|
||||
import org.sleuthkit.autopsy.events.AutopsyEvent;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.datamodel.Blackboard;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME;
|
||||
import org.sleuthkit.datamodel.OsAccount;
|
||||
@ -678,7 +676,9 @@ public final class CaseEventListener implements PropertyChangeListener {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!CentralRepository.isEnabled()) {
|
||||
//Nothing to do here if the central repo is not enabled or if ingest is running but is set to not save data/make artifacts
|
||||
if (!CentralRepository.isEnabled()
|
||||
|| (IngestManager.getInstance().isIngestRunning() && !(IngestEventsListener.isFlagSeenDevices() || IngestEventsListener.shouldCreateCrProperties()))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -706,32 +706,36 @@ public final class CaseEventListener implements PropertyChangeListener {
|
||||
TskData.FileKnown.KNOWN,
|
||||
osAccount.getId());
|
||||
|
||||
dbManager.addArtifactInstance(correlationAttributeInstance);
|
||||
// Save to the database if requested
|
||||
if(IngestEventsListener.shouldCreateCrProperties()) {
|
||||
dbManager.addArtifactInstance(correlationAttributeInstance);
|
||||
}
|
||||
|
||||
List<CorrelationAttributeInstance> previousOccurences = dbManager.getArtifactInstancesByTypeValue(CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.OSACCOUNT_TYPE_ID), correlationAttributeInstance.getCorrelationValue());
|
||||
List<String> caseDisplayNames;
|
||||
for (CorrelationAttributeInstance instance : previousOccurences) {
|
||||
if (!instance.getCorrelationCase().getCaseUUID().equals(correlationAttributeInstance.getCorrelationCase().getCaseUUID())) {
|
||||
caseDisplayNames = dbManager.getListCasesHavingArtifactInstances(correlationAttributeInstance.getCorrelationType(), correlationAttributeInstance.getCorrelationValue());
|
||||
SleuthkitCase tskCase = osAccount.getSleuthkitCase();
|
||||
Blackboard blackboard = tskCase.getBlackboard();
|
||||
// Look up and create artifacts for previously seen accounts if requested
|
||||
if (IngestEventsListener.isFlagSeenDevices()) {
|
||||
List<CorrelationAttributeInstance> previousOccurences = dbManager.getArtifactInstancesByTypeValue(CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.OSACCOUNT_TYPE_ID), correlationAttributeInstance.getCorrelationValue());
|
||||
for (CorrelationAttributeInstance instance : previousOccurences) {
|
||||
if (!instance.getCorrelationCase().getCaseUUID().equals(correlationAttributeInstance.getCorrelationCase().getCaseUUID())) {
|
||||
SleuthkitCase tskCase = osAccount.getSleuthkitCase();
|
||||
Blackboard blackboard = tskCase.getBlackboard();
|
||||
|
||||
Collection<BlackboardAttribute> attributesForNewArtifact = Arrays.asList(
|
||||
new BlackboardAttribute(
|
||||
TSK_SET_NAME, MODULE_NAME,
|
||||
Bundle.CaseEventsListener_prevExists_text()),
|
||||
new BlackboardAttribute(
|
||||
TSK_COMMENT, MODULE_NAME,
|
||||
Bundle.CaseEventsListener_prevCaseComment_text()));
|
||||
BlackboardArtifact newAnalysisResult = osAccount.newAnalysisResult(
|
||||
BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT, Score.SCORE_LIKELY_NOTABLE,
|
||||
null, Bundle.CaseEventsListener_prevExists_text(), null, attributesForNewArtifact, osAccountInstance.getDataSource().getId()).getAnalysisResult();
|
||||
try {
|
||||
// index the artifact for keyword search
|
||||
blackboard.postArtifact(newAnalysisResult, MODULE_NAME);
|
||||
break;
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Unable to index blackboard artifact " + newAnalysisResult.getArtifactID(), ex); //NON-NLS
|
||||
Collection<BlackboardAttribute> attributesForNewArtifact = Arrays.asList(
|
||||
new BlackboardAttribute(
|
||||
TSK_SET_NAME, MODULE_NAME,
|
||||
Bundle.CaseEventsListener_prevExists_text()),
|
||||
new BlackboardAttribute(
|
||||
TSK_COMMENT, MODULE_NAME,
|
||||
Bundle.CaseEventsListener_prevCaseComment_text()));
|
||||
BlackboardArtifact newAnalysisResult = osAccount.newAnalysisResult(
|
||||
BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT, Score.SCORE_LIKELY_NOTABLE,
|
||||
null, Bundle.CaseEventsListener_prevExists_text(), null, attributesForNewArtifact, osAccountInstance.getDataSource().getId()).getAnalysisResult();
|
||||
try {
|
||||
// index the artifact for keyword search
|
||||
blackboard.postArtifact(newAnalysisResult, MODULE_NAME);
|
||||
break;
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Unable to index blackboard artifact " + newAnalysisResult.getArtifactID(), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
IngestSettingsPanel.ingestSettingsLabel.text=Ingest Settings
|
||||
IngestSettingsPanel.flagTaggedNotableItemsCheckbox.text=Flag items previously tagged as notable
|
||||
IngestSettingsPanel.flagPreviouslySeenDevicesCheckbox.text=Flag devices previously seen in other cases
|
||||
IngestSettingsPanel.flagPreviouslySeenDevicesCheckbox.text=Flag devices and users previously seen in other cases
|
||||
IngestSettingsPanel.createCorrelationPropertiesCheckbox.text=Save items to the Central Repository
|
||||
|
@ -11,5 +11,5 @@ CentralRepoIngestModuleFactory.ingestmodule.desc=Saves properties to the central
|
||||
CentralRepoIngestModuleFactory.ingestmodule.name=Central Repository
|
||||
IngestSettingsPanel.ingestSettingsLabel.text=Ingest Settings
|
||||
IngestSettingsPanel.flagTaggedNotableItemsCheckbox.text=Flag items previously tagged as notable
|
||||
IngestSettingsPanel.flagPreviouslySeenDevicesCheckbox.text=Flag devices previously seen in other cases
|
||||
IngestSettingsPanel.flagPreviouslySeenDevicesCheckbox.text=Flag devices and users previously seen in other cases
|
||||
IngestSettingsPanel.createCorrelationPropertiesCheckbox.text=Save items to the Central Repository
|
||||
|
@ -29,7 +29,7 @@
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace pref="47" max="32767" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
|
@ -88,7 +88,7 @@ final class IngestSettingsPanel extends IngestModuleIngestJobSettingsPanel {
|
||||
.addComponent(flagTaggedNotableItemsCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(flagPreviouslySeenDevicesCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(createCorrelationPropertiesCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
|
||||
.addContainerGap(47, Short.MAX_VALUE))
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
|
@ -39,7 +39,8 @@ GlobalSettingsPanel.askForCentralRepoDbChoice.sqliteChoice.text=Use SQLite
|
||||
GlobalSettingsPanel.onMultiUserChange.disabledMu.description=The Central Repository will be reconfigured to use a local SQLite database.
|
||||
GlobalSettingsPanel.onMultiUserChange.disabledMu.description2=Press Configure PostgreSQL to change to a PostgreSQL database.
|
||||
GlobalSettingsPanel.onMultiUserChange.disabledMu.title=Central Repository Change Necessary
|
||||
GlobalSettingsPanel.onMultiUserChange.enable.description=Do you want to update the Central Repository to use this PostgreSQL server?
|
||||
# {0} - server name
|
||||
GlobalSettingsPanel.onMultiUserChange.enable.description=Do you want to update the Central Repository to use the PostgreSQL server on {0}?
|
||||
GlobalSettingsPanel.onMultiUserChange.enable.description2=Any data in an existing SQLite Central Repository will not be transferred to the new database.
|
||||
GlobalSettingsPanel.onMultiUserChange.enable.title=Central Repository
|
||||
GlobalSettingsPanel.testCurrentConfiguration.dbDoesNotExist.message=Database does not exist.
|
||||
|
@ -47,6 +47,7 @@ import java.util.logging.Level;
|
||||
import javax.swing.ImageIcon;
|
||||
import org.openide.util.ImageUtilities;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.DatabaseTestResult;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.PostgresSettingsLoader;
|
||||
|
||||
|
||||
|
||||
@ -152,7 +153,8 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"GlobalSettingsPanel.onMultiUserChange.enable.title=Central Repository",
|
||||
"GlobalSettingsPanel.onMultiUserChange.enable.description=Do you want to update the Central Repository to use this PostgreSQL server?",
|
||||
"# {0} - server name",
|
||||
"GlobalSettingsPanel.onMultiUserChange.enable.description=Do you want to update the Central Repository to use the PostgreSQL server on {0}?",
|
||||
"GlobalSettingsPanel.onMultiUserChange.enable.description2=Any data in an existing SQLite Central Repository will not be transferred to the new database."
|
||||
})
|
||||
public static void onMultiUserChange(Component parent, boolean muPreviouslySelected, boolean muCurrentlySelected) {
|
||||
@ -161,10 +163,12 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
|
||||
|
||||
if (!muPreviouslySelected && muCurrentlySelected) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
PostgresCentralRepoSettings multiUserSettings
|
||||
= new PostgresCentralRepoSettings(PostgresSettingsLoader.MULTIUSER_SETTINGS_LOADER);
|
||||
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(parent,
|
||||
"<html><body>"
|
||||
+ "<div style='width: 400px;'>"
|
||||
+ "<p>" + Bundle.GlobalSettingsPanel_onMultiUserChange_enable_description() + "</p>"
|
||||
+ "<p>" + Bundle.GlobalSettingsPanel_onMultiUserChange_enable_description(multiUserSettings.getHost()) + "</p>"
|
||||
+ "<p style='margin-top: 10px'>" + Bundle.GlobalSettingsPanel_onMultiUserChange_enable_description2() + "</p>"
|
||||
+ "</div>"
|
||||
+ "</body></html>",
|
||||
|
@ -128,8 +128,10 @@ public final class InterCasePanel extends javax.swing.JPanel {
|
||||
}
|
||||
});
|
||||
for (CorrelationAttributeInstance.Type type : types) {
|
||||
correlationTypeFilters.put(type.getDisplayName(), type);
|
||||
this.correlationTypeComboBox.addItem(type.getDisplayName());
|
||||
if (! type.getDbTableName().contains("os_account") && ! type.getDbTableName().contains("installed_program")) {
|
||||
correlationTypeFilters.put(type.getDisplayName(), type);
|
||||
this.correlationTypeComboBox.addItem(type.getDisplayName());
|
||||
}
|
||||
}
|
||||
} catch (CentralRepoException ex) {
|
||||
logger.log(Level.WARNING, "Error getting correlation types", ex);
|
||||
|
@ -80,8 +80,7 @@ class DataSourceGroupingNode extends DisplayableItemNode {
|
||||
new DataArtifacts(dsObjId),
|
||||
new AnalysisResults(dsObjId),
|
||||
new OsAccounts(Case.getCurrentCaseThrows().getSleuthkitCase(), dsObjId),
|
||||
new Tags(dsObjId),
|
||||
new Reports()
|
||||
new Tags(dsObjId)
|
||||
));
|
||||
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
|
@ -291,11 +291,18 @@ public class DataResultFilterNode extends FilterNode {
|
||||
NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewFileInDir.text"), ban));
|
||||
}
|
||||
} else if (artifactTypeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) {
|
||||
//action to go to the source artifact
|
||||
actionsList.add(new ViewSourceArtifactAction(DataResultFilterNode_viewSourceArtifact_text(), ba));
|
||||
// action to go to the source file of the artifact
|
||||
actionsList.add(new ViewContextAction(
|
||||
NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewSrcFileInDir.text"), ban));
|
||||
try {
|
||||
if (ba.getAttribute(BlackboardAttribute.Type.TSK_ASSOCIATED_ARTIFACT) != null) {
|
||||
//action to go to the source artifact
|
||||
actionsList.add(new ViewSourceArtifactAction(DataResultFilterNode_viewSourceArtifact_text(), ba));
|
||||
|
||||
// action to go to the source file of the artifact
|
||||
actionsList.add(new ViewContextAction(
|
||||
NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewSrcFileInDir.text"), ban));
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.WARNING, "Error looking up attributes for artifact with ID=" + ba.getId());
|
||||
}
|
||||
} else {
|
||||
// if the artifact links to another file, add an action to go to
|
||||
// that file
|
||||
|
@ -96,6 +96,17 @@ final class DomainDetailsPanel extends JPanel {
|
||||
runDomainWorker((DomainArtifactsTabPanel) selectedComponent, true);
|
||||
} else if (!StringUtils.isBlank(domain) && selectedComponent instanceof MiniTimelinePanel) {
|
||||
runMiniTimelineWorker((MiniTimelinePanel) selectedComponent, true);
|
||||
} else if (selectedComponent instanceof OtherOccurrencesPanel) {
|
||||
if (CentralRepository.isEnabled()) {
|
||||
try {
|
||||
((OtherOccurrencesPanel) selectedComponent).populateTableForOneType(CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.DOMAIN_TYPE_ID), domain);
|
||||
} catch (CentralRepoException ex) {
|
||||
logger.log(Level.INFO, "Central repository exception while trying to get instances by type and value for domain: " + domain, ex);
|
||||
((OtherOccurrencesPanel) selectedComponent).reset();
|
||||
}
|
||||
} else {
|
||||
((OtherOccurrencesPanel) selectedComponent).reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015-2020 Basis Technology Corp.
|
||||
* Copyright 2015-2021 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -19,7 +19,6 @@
|
||||
package org.sleuthkit.autopsy.experimental.autoingest;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@ -28,18 +27,16 @@ import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpression;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
@Immutable
|
||||
@ServiceProvider(service = ManifestFileParser.class)
|
||||
@ -50,6 +47,7 @@ public final class AutopsyManifestFileParser implements ManifestFileParser {
|
||||
private static final String CASE_NAME_XPATH = "/AutopsyManifest/CaseName/text()";
|
||||
private static final String DEVICE_ID_XPATH = "/AutopsyManifest/DeviceId/text()";
|
||||
private static final String DATA_SOURCE_NAME_XPATH = "/AutopsyManifest/DataSource/text()";
|
||||
private static final Logger logger = Logger.getLogger(AutopsyManifestFileParser.class.getName());
|
||||
|
||||
@Override
|
||||
public boolean fileIsManifest(Path filePath) {
|
||||
@ -78,6 +76,7 @@ public final class AutopsyManifestFileParser implements ManifestFileParser {
|
||||
} catch (Exception ex) {
|
||||
// If the above call to createManifestDOM threw an exception
|
||||
// try to fix the given XML file.
|
||||
logger.log(Level.WARNING, String.format("Failed to create DOM for manifest at %s, attempting repair", filePath), ex);
|
||||
tempPath = ManifestFileParser.makeTidyManifestFile(filePath);
|
||||
doc = ManifestFileParser.createManifestDOM(tempPath);
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found
|
||||
KeywordSearchResultFactory.query.exception.msg=Could not perform the query
|
||||
OpenIDE-Module-Display-Category=Ingest Module
|
||||
|
||||
OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\n\The module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found.
|
||||
OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\nThe module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found.
|
||||
OpenIDE-Module-Name=KeywordSearch
|
||||
OptionsCategory_Name_KeywordSearchOptions=Keyword Search
|
||||
OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search
|
||||
@ -403,7 +403,7 @@ ExtractedContentPanel.pageOfLabel.text=of
|
||||
ExtractedContentPanel.pageCurLabel.text=-
|
||||
ExtractedContentPanel.pagesLabel.text=Page:
|
||||
KeywordSearchJobSettingsPanel.ocrCheckBox.text=Enable Optical Character Recognition (OCR)
|
||||
KeywordSearchJobSettingsPanel.limitedOcrCheckbox.text=<html>Only process images which are over 100KB in size or extracted from a document (Beta)</html>
|
||||
KeywordSearchJobSettingsPanel.limitedOcrCheckbox.text=<html>Only process PDFs, MS Office docs and images which are over 100KB in size or extracted from another file (Beta)</html>
|
||||
KeywordSearchJobSettingsPanel.ocrOnlyCheckbox.text=Only index text extracted using OCR
|
||||
TextZoomPanel.zoomInButton.text=
|
||||
TextZoomPanel.zoomOutButton.text=
|
||||
|
@ -147,7 +147,6 @@ public final class KeywordSearchJobSettings implements IngestModuleIngestJobSett
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public long getVersionNumber() {
|
||||
|
@ -82,7 +82,7 @@ Results will be opened in a separate Results Viewer for every search executed. I
|
||||
|
||||
\section ad_hoc_kw_lists Keyword Lists
|
||||
|
||||
In addition to being selected during ingest, keyword lists can also be run through the Keyword Lists button. For information on setting up these keyword lists, see the \ref keywordListsTab section of the ingest module documentation.
|
||||
In addition to being selected during ingest, keyword lists can also be run through the Keyword Lists button. For information on setting up these keyword lists, see the \ref keyword_keywordListsTab section of the ingest module documentation.
|
||||
|
||||
Lists created using the Keyword Search Configuration Dialog can be manually searched by the user by pressing on the 'Keyword Lists' button and selecting the check boxes corresponding to the lists to be searched. The search can be restricted to only certain data sources by selecting the checkbox near the bottom and then highlighting the data sources to search within. Multiple data sources can be selected used shift+left click or control+left click. Once everything has been configured, press "Search" to begin the search. The "Save search results" checkbox determines whether the search results will be saved to the case database.
|
||||
|
||||
|
@ -111,7 +111,11 @@ Descriptions of the property types:
|
||||
- <b>ICCID Number</b>
|
||||
- ICCID properties are currently only created by custom Autopsy modules.
|
||||
- <b>Credit Card</b>
|
||||
- Credid Card properties are created by the \ref keyword_search_page.
|
||||
- Credit Card properties are created by the \ref keyword_search_page.
|
||||
- <b>OS Account</b>
|
||||
- OS account properties are created by the disk image data source processor and the \ref recent_activity_page.
|
||||
- <b>Installed Programs</b>
|
||||
- Installed program properties are created primarily by the \ref recent_activity_page.
|
||||
- <b> App-specific Accounts (Facebook, Twitter, etc)</b>
|
||||
- These properties primarily come from the \ref android_analyzer_page.
|
||||
|
||||
@ -148,7 +152,7 @@ There are three settings for the Central Repository ingest module:
|
||||
<ul>
|
||||
<li><b>Save items to the Central Repository</b> - This should only be unselected in the rare case that you don't want to add any properties from the current data source to the central repository, but still want to flag past occurrences.
|
||||
<li><b>Flag items previously tagged as notable</b> - Enabling this causes Interesting Item/File artifacts to be created when properties matching those previously flagged are found. See the next section \ref cr_tagging for details.
|
||||
<li><b>Flag previously seen devices</b> - When this is enabled, an Interesting Item artifact will be created if any device-related property (USB, MAC Address, IMSI, IMEI, ICCID) is found that is already in the central repository, regardless of whether they have been flagged.
|
||||
<li><b>Flag previously seen devices and users</b> - When this is enabled, an Interesting Item artifact will be created if any device-related property (USB, MAC Address, IMSI, IMEI, ICCID) or an OS account is found that is already in the central repository, regardless of whether they have been flagged.
|
||||
</li>
|
||||
|
||||
\subsection cr_tagging Tagging Files and Artifacts
|
||||
|
@ -75,9 +75,9 @@ Registry hive files can be viewed in a format similar to a registry editor.
|
||||
|
||||
\image html content_viewer_registry.png
|
||||
|
||||
\section cv_metadata File Metadata
|
||||
\section cv_metadata File Metadata / Source File Metadata
|
||||
|
||||
The File Metadata tab displays basic information about the file, such as type, size, and hash. It also displays the output of the Sleuth Kit istat tool.
|
||||
The File Metadata tab displays basic information about the file selected or the file associated with the result, such as type, size, and hash. It also displays the output of the Sleuth Kit istat tool.
|
||||
|
||||
\image html content_viewer_metadata.png
|
||||
|
||||
@ -87,14 +87,20 @@ The OS Accounts tab displays information on the OS account associated with a giv
|
||||
|
||||
\image html content_viewer_os_account.png
|
||||
|
||||
\section cv_results Results
|
||||
\section cv_results Data Artifacts
|
||||
|
||||
The Results tab is active when selecting items with associated results such as keyword hits, call logs, and messages. The exact fields displayed depend on the type of result. The two images below show the Results tab for a call log and a web bookmark.
|
||||
The Data Artifacts tab shows the artifacts associated with the item selected in the result viewer such as web bookmarks, call logs, and messages. The exact fields displayed depend on the type of data artifact. The two images below show the Data Artifacts tab for a call log and a web bookmark.
|
||||
|
||||
\image html content_viewer_results_call.png
|
||||
<br>
|
||||
\image html content_viewer_results_bookmark.png
|
||||
|
||||
\section cv_analysis_results Analysis Results
|
||||
|
||||
The Analysis Results tab shows all analysis results associated with the item selected in the result viewer. If you select an analysis result, it will auto-scroll to that result in the list. Analysis results come from data such as hash set hits, interesting items, and keyword hits. The image below shows web category analysis results.
|
||||
|
||||
\image html content_viewer_analysis_result_webcat.png
|
||||
|
||||
\section cv_context Context
|
||||
|
||||
The Context tab shows information on where a file came from and allows you to navigate to the original result. For example, it can show the the URL for downloaded files and the email message a file was attached to. In the image below you can see the context for an image that was sent as an email attachment.
|
||||
|
@ -21,18 +21,18 @@ or select the "Tools", "File Search by Attributes".
|
||||
There are several categories that you can use to filter and show the directories and files within the images in the current opened case.
|
||||
The categories are:
|
||||
\li Name:
|
||||
Search for all files and directory whose name contains the pattern given.
|
||||
Search for all files and directories whose name contains the pattern given. Search is on the file/directory name only and does not look at the parent path.
|
||||
Note: it doesn't support regular expression and keyword matching.
|
||||
\li Size:
|
||||
Search for all files and directory whose size matches the pattern given. The pattern can be "equal to", "greater than", and "less than". The unit for the size can be "Byte(s)", "KB", "MB", "GB", and "TB".
|
||||
Search for all files and directories whose size matches the pattern given. The pattern can be "equal to", "greater than", and "less than". The unit for the size can be "Byte(s)", "KB", "MB", "GB", and "TB".
|
||||
\li MIME Type:
|
||||
Search for all files with the selected MIME type. Multiple types can be used by holding SHIFT or CTRL while selecting.
|
||||
\li MD5:
|
||||
Search for all files with the given MD5 hash.
|
||||
\li Date:
|
||||
Search for all files and directory whose "date property" is within the date range given. The "date properties" are "Modified Date", "Accessed Date", "Changed Date", and "Created Date". You must also specify the timezone for the date given.
|
||||
Search for all files and directories whose "date property" is within the date range given. The "date properties" are "Modified Date", "Accessed Date", "Changed Date", and "Created Date". You must also specify the timezone for the date given.
|
||||
\li Known Status:
|
||||
Search for all files and directory whose known status is recognized as either Unknown, Known, or Known Bad. For more on Known Status, see the \ref hash_db_page.
|
||||
Search for all files whose known status is recognized as either Unknown, Known, or Known Bad. For more on Known Status, see the \ref hash_db_page.
|
||||
To use any of these filters, check the box next to the category and click "Search" button to start the search process. The result will show up in the "Result Viewer".
|
||||
\li Data Source:
|
||||
Search only within the specified data source instead of the entire case. Note that multiple data sources can be selected by holding SHIFT or CTRL while selecting.
|
||||
|
@ -19,7 +19,7 @@ Hosts are displayed in the \ref tree_viewer_page. Depending on the \ref view_opt
|
||||
|
||||
\subsection host_os_accounts OS Accounts
|
||||
|
||||
OS accounts can be viewed in the OS Accounts node under Results. Each OS account is associated with a host, and the host information is displayed in the OS Account tab of the content viewer.
|
||||
OS accounts can be viewed in the OS Accounts node of the tree viewer. Each OS account is associated with a host, and the host information is displayed in the OS Account tab of the content viewer.
|
||||
|
||||
\image html host_os_accounts.png
|
||||
|
||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 59 KiB |
@ -18,11 +18,11 @@ Refer to \ref ad_hoc_keyword_search_page for more details on specifying regular
|
||||
\section keyword_search_configuration_dialog Keyword Search Configuration Dialog
|
||||
|
||||
The keyword search configuration dialog has three tabs, each with its own purpose:
|
||||
\li The \ref keywordListsTab is used to add, remove, and modify keyword search lists.
|
||||
\li The \ref stringExtractionTab is used to enable language scripts and extraction type.
|
||||
\li The \ref generalSettingsTab is used to configure the ingest timings and display information.
|
||||
\li The \ref keyword_keywordListsTab is used to add, remove, and modify keyword search lists.
|
||||
\li The \ref keyword_stringExtractionTab is used to enable language scripts and extraction type.
|
||||
\li The \ref keyword_generalSettingsTab is used to configure the ingest timings and display information.
|
||||
|
||||
## Lists tab {#keywordListsTab}
|
||||
\subsection keyword_keywordListsTab Lists tab
|
||||
|
||||
The Lists tab is used to create/import and add content to keyword lists. To create a list, select the 'New List' button and choose a name for the new Keyword List. Once the list has been created, keywords can be added to it (see \ref ad_hoc_kw_types_section for more information on keyword types). Lists can be added to the keyword search ingest process; searches will happen at regular intervals as content is added to the index.
|
||||
|
||||
@ -40,7 +40,7 @@ Under the Keyword list is the option to send ingest inbox messages for each hit.
|
||||
|
||||
\image html keyword-search-inbox.PNG
|
||||
|
||||
## String Extraction tab {#stringExtractionTab}
|
||||
\subsection keyword_stringExtractionTab String Extraction tab
|
||||
The string extraction setting defines how strings are extracted from files from which text cannot be extracted normally because their file formats are not supported. This is the case with arbitrary binary files (such as the page file) and chunks of unallocated space that represent deleted files.
|
||||
When we extract strings from binary files we need to interpret sequences of bytes as text differently, depending on the possible text encoding and script/language used. In many cases we don't know in advance what the specific encoding/language the text is encoded in. However, it helps if the investigator is looking for a specific language, because by selecting less languages the indexing performance will be improved and the number of false positives will be reduced.
|
||||
|
||||
@ -50,20 +50,36 @@ The default setting is to search for English strings only, encoded as either UTF
|
||||
The user can also use the String Viewer first and try different script/language settings, and see which settings give satisfactory results for the type of text relevant to the investigation. Then the same setting that works for the investigation can be applied to the keyword search ingest.
|
||||
|
||||
|
||||
## General Settings tab {#generalSettingsTab}
|
||||
\subsection keyword_generalSettingsTab General Settings tab
|
||||
|
||||
\image html keyword-search-configuration-dialog-general.PNG
|
||||
|
||||
### NIST NSRL Support
|
||||
\subsubsection keyword_nsrl NIST NSRL Support
|
||||
The hash lookup ingest service can be configured to use the NIST NSRL hash set of known files. The keyword search advanced configuration dialog "General" tab contains an option to skip keyword indexing and search on files that have previously marked as "known" and uninteresting files. Selecting this option can greatly reduce size of the index and improve ingest performance. In most cases, user does not need to keyword search for "known" files.
|
||||
|
||||
### Result update frequency during ingest
|
||||
\subsubsection keyword_update_freq Result update frequency during ingest
|
||||
To control how frequently searches are executed during ingest, the user can adjust the timing setting available in the keyword search advanced configuration dialog "General" tab. Setting the number of minutes lower will result in more frequent index updates and searches being executed and the user will be able to see results more in real-time. However, more frequent updates can affect the overall performance, especially on lower-end systems, and can potentially lengthen the overall time needed for the ingest to complete.
|
||||
|
||||
One can also choose to have no periodic searches. This will speed up the ingest. Users choosing this option can run their keyword searches once the entire keyword search index is complete.
|
||||
|
||||
### Optical Character Recognition
|
||||
There is also a setting to enable Optical Character Recognition (OCR). If enabled, text may be extracted from supported image types. Enabling this feature will make the keyword search module take longer to run, and the results are not perfect. The secondary checkbox can make OCR run faster by only processing large images and images extracted from documents.
|
||||
\section keyword_usage Using the Module
|
||||
|
||||
Search queries can be executed manually by the user at any time, as long as there are some files already indexed and ready to be searched. Searching before indexing is complete will naturally only search indexes that are already compiled.
|
||||
|
||||
See \ref ingest_page "Ingest" for more information on ingest in general.
|
||||
|
||||
Once there are files in the index, \ref ad_hoc_keyword_search_page will be available for use to manually search at any time.
|
||||
|
||||
\subsection keyword_ingest_settings Ingest Settings
|
||||
|
||||
The Ingest Settings for the Keyword Search module allow the user to enable or disable the specific built-in search expressions, Phone Numbers, IP Addresses, Email Addresses, and URLs. Using the Advanced button (covered below), one can add custom keyword groups.
|
||||
|
||||
\image html keyword-search-ingest-settings.PNG
|
||||
|
||||
\subsubsection keyword_ocr Optical Character Recognition
|
||||
\anchor keyword_search_ocr_config
|
||||
|
||||
There is also a setting to enable Optical Character Recognition (OCR). If enabled, text may be extracted from supported image types. Enabling this feature will make the keyword search module take longer to run, and the results are not perfect.
|
||||
|
||||
The following shows a sample image containing text:
|
||||
|
||||
@ -73,7 +89,12 @@ The "Indexed Text" tab shows the results when running the keyword search module
|
||||
|
||||
\image html keyword-search-ocr-indexed-text.png
|
||||
|
||||
\anchor keyword_search_ocr_config
|
||||
The two options to related to OCR are the following:
|
||||
<ul>
|
||||
<li>Only index text extracted from an image. This will prevent keyword search from indexing text found in text files, docs, etc.
|
||||
<li>Only run on large images and documents and extracted files. With this selected, OCR will only be performed on images over 100KB and PDFs/Office docs. It will also run on images of any size that were extracted from another file.
|
||||
</ul>
|
||||
|
||||
By default, OCR is only configured for English text. Its configuration depends on the presence of language files (called "traineddata" files)
|
||||
that exist in a location that Autopsy can understand. To add support for more languages, you will need to download additional "traineddata"
|
||||
and move them to the right location. The following steps breakdown this process for you:
|
||||
@ -88,28 +109,8 @@ and move them to the right location. The following steps breakdown this process
|
||||
|
||||
The language files will now be supported when OCR is enabled in the Keyword Search Settings.
|
||||
|
||||
<!----------------------------------------->
|
||||
|
||||
<br>
|
||||
Using the Module
|
||||
======
|
||||
Search queries can be executed manually by the user at any time, as long as there are some files already indexed and ready to be searched. Searching before indexing is complete will naturally only search indexes that are already compiled.
|
||||
|
||||
See \ref ingest_page "Ingest" for more information on ingest in general.
|
||||
|
||||
Once there are files in the index, \ref ad_hoc_keyword_search_page will be available for use to manually search at any time.
|
||||
|
||||
<!----------------------------------->
|
||||
|
||||
Ingest Settings
|
||||
------
|
||||
The Ingest Settings for the Keyword Search module allow the user to enable or disable the specific built-in search expressions, Phone Numbers, IP Addresses, Email Addresses, and URLs. Using the Advanced button (covered below), one can add custom keyword groups.
|
||||
|
||||
\image html keyword-search-ingest-settings.PNG
|
||||
|
||||
|
||||
Seeing Results
|
||||
------
|
||||
\section keyword_results Seeing Results
|
||||
|
||||
The Keyword Search module will save the search results regardless whether the search is performed by the ingest process, or manually by the user. The saved results are available in the Directory Tree in the left hand side panel.
|
||||
|
||||
|
@ -25,8 +25,8 @@ These columns display the following information:
|
||||
<ul>
|
||||
<li> (S)core column - indicates whether the item is interesting or notable.
|
||||
<ul>
|
||||
<li>Displays a red icon if the file is a match for a notable hashset or has been tagged with a notable tag.
|
||||
<li>Displays a yellow icon if the file has an interesting item match or has been tagged with a non-notable tag.
|
||||
<li>Displays a red icon if at least one child analysis result is notable or the file is tagged with a notable tag.
|
||||
<li>Displays a yellow icon if at least one child analysis result is likely notable or the file has a tag.
|
||||
</ul>
|
||||
<li> (C)omment column - indicates whether the item has a comment in the Central Repository or has a comment associated with a tag.
|
||||
<li> (O)ther occurrences column - indicates how many data sources in the Central Repository contain this item. The count will include the selected item.
|
||||
|
@ -3,10 +3,12 @@
|
||||
[TOC]
|
||||
|
||||
|
||||
The tree on the left-hand side of the main window is where you can browse the files in the data sources in the case and find saved results from automated analyis (ingest). The tree has five main areas:
|
||||
The tree on the left-hand side of the main window is where you can browse the files in the data sources in the case and find saved results from automated analyis (ingest). The tree has seven main areas:
|
||||
- <b>Persons / Hosts / Data Sources:</b> This shows the directory tree hierarchy of the data sources. You can navigate to a specific file or directory here. Each data source added to the case is represented as a distinct sub tree. If you add a data source multiple times, it shows up multiple times.
|
||||
- <b>Views:</b> Specific types of files from the data sources are shown here, aggregated by type or other properties. Files here can come from more than one data source.
|
||||
- <b>Results:</b> This is where you can see the results from both the automated analysis (ingest) running in the background and your search results.
|
||||
- <b>File Views:</b> Specific types of files from the data sources are shown here, aggregated by type or other properties. Files here can come from more than one data source.
|
||||
- <b>Data Artifacts:</b> This isone of the main places where results from running \ref ingest_page appear.
|
||||
- <b>Analysis Results:</b> This is the other main place where results from running \ref ingest_page appear.
|
||||
- <b>OS Accounts:</b> This is where you can see the results from both the automated analysis (ingest) running in the background and your search results.
|
||||
- <b>Tags:</b> This is where files and results that have been \ref tagging_page "tagged" are shown.
|
||||
- <b>Reports:</b> Reports that you have generated, or that ingest modules have created, show up here.
|
||||
|
||||
@ -43,22 +45,28 @@ Unallocated space is the chunks of a file system that are currently not being us
|
||||
An example of the single file extraction option is shown below.
|
||||
\image html extracting-unallocated-space.PNG
|
||||
|
||||
\section ui_tree_views Views
|
||||
\section ui_tree_views File Views
|
||||
|
||||
Views filter all the files in the case by some property of the file.
|
||||
- <b>File Types</b> Sorts files by file extension or by MIME type, and shows them in the appropriate group. For example, files with .mp3 and .wav extensions end up in the "Audio" group.
|
||||
- <b>Deleted Files</b> Displays files that have been deleted, but the names have been recovered.
|
||||
- <b>File Size</b> Sorts files based on size.
|
||||
|
||||
\section ui_tree_results Data Artifacts
|
||||
|
||||
\section ui_tree_results Results
|
||||
- <b>Extracted Content:</b> Many ingest modules will place results here; EXIF metadata, GPS locations, or Web history for example.
|
||||
- <b>Keyword Hits:</b> Keyword search hits show up here.
|
||||
- <b>Hashset Hits:</b> Hashset hits show up here.
|
||||
- <b>E-Mail Messages:</b> Email messages show up here.
|
||||
- <b>Interesting Items:</b> Things deemed interesting show up here.
|
||||
- <b>Accounts:</b> Credit card accounts show up here.
|
||||
- <b>Tags:</b> Any item you tag shows up here so you can find it again easily.
|
||||
This section shows the data artifacts created by running ingest. In general, data artifacts contain concrete information extracted from the data source. For example, call logs and messages from communication logs or web bookmarks extracted from a browser database.
|
||||
|
||||
\section ui_tree_analysis_results Analysis Results
|
||||
|
||||
This section shows the analysis results created by running ingest. In general, analysis results contain information that the user has indicated they are interested in. For example, if the user sets up a list of \ref hash_db_page "notable hashes", any hash set hits will appear here.
|
||||
|
||||
\section ui_tree_os_accounts OS Accounts
|
||||
|
||||
This section shows the OS accounts found in the case. See \ref host_os_accounts for an example.
|
||||
|
||||
\section ui_tree_tags Tags
|
||||
|
||||
Any item you tag shows up here so you can find it again easily. See \ref tagging_page for more information.
|
||||
|
||||
\section ui_tree_reports Reports
|
||||
|
||||
|