merge from 6896-testing

This commit is contained in:
Greg DiCristofaro 2020-11-04 08:57:38 -05:00
commit 066249e9ea
63 changed files with 7678 additions and 628 deletions

View File

@ -172,7 +172,7 @@
<get src="https://drive.google.com/uc?id=1ns2olaWsBu_c4EoE4Seh8t_B3U5RnLKd" dest="${test-input}/CommonFilesAttrs_img1_v1.vhd" skipexisting="true"/>
</target>
<target name="get-deps" depends="init-ivy,getTSKJars,get-thirdparty-dependencies,get-InternalPythonModules, download-binlist">
<mkdir dir="${ext.dir}"/>
<copy file="${thirdparty.dir}/LICENSE-2.0.txt" todir="${ext.dir}" />
@ -207,11 +207,10 @@
<globmapper from="*" to="*-MERGED"/>
</copy>
</target>
<!--sets up integration test system properties, calls underlying test-init and then sets up the pathing jar-->
<target name="test-init" depends="setup-integration-props,projectized-common.test-init,integration-pathing-jar" />
<!--sets up integration test system properties, calls underlying test-init,
get test data files if they have not been retrieved, and then sets up the pathing jar-->
<target name="test-init" depends="setup-integration-props,projectized-common.test-init,getTestDataFiles,qa-functional-pathing-jar" />
<target name="test-qa-functional">
@ -249,7 +248,7 @@
classpath length issues on windows. More information on this technique can be found here:
https://stackoverflow.com/a/201969.
-->
<target name="integration-pathing-jar" depends="projectized-common.test-init">
<target name="qa-functional-pathing-jar" depends="projectized-common.test-init">
<sequential>
<!--set up pathing jar based on module.run.classpath as classpath-->
<path id="test.qa-functional.pathing-jar.module-cp.classpath" path="${module.run.classpath}"/>
@ -290,4 +289,4 @@
<antcall target="projectized-common.test-qa-functional" />
</sequential>
</target>
</project>
</project>

View File

@ -96,7 +96,7 @@ Metadata.tableRowTitle.mimeType=MIME Type
Metadata.tableRowTitle.name=Name
Metadata.tableRowTitle.sectorSize=Sector Size
Metadata.tableRowTitle.sha1=SHA1
Metadata.tableRowTitle.sha256=SHA256
Metadata.tableRowTitle.sha256=SHA-256
Metadata.tableRowTitle.size=Size
Metadata.tableRowTitle.fileNameAlloc=File Name Allocation
Metadata.tableRowTitle.metadataAlloc=Metadata Allocation

View File

@ -137,7 +137,7 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
"Metadata.tableRowTitle.mimeType=MIME Type",
"Metadata.nodeText.truncated=(results truncated)",
"Metadata.tableRowTitle.sha1=SHA1",
"Metadata.tableRowTitle.sha256=SHA256",
"Metadata.tableRowTitle.sha256=SHA-256",
"Metadata.tableRowTitle.imageType=Type",
"Metadata.tableRowTitle.sectorSize=Sector Size",
"Metadata.tableRowTitle.timezone=Time Zone",
@ -182,6 +182,11 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
md5 = NbBundle.getMessage(this.getClass(), "Metadata.tableRowContent.md5notCalc");
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.md5"), md5);
String sha256 = file.getSha256Hash();
if (sha256 == null) {
sha256 = NbBundle.getMessage(this.getClass(), "Metadata.tableRowContent.md5notCalc");
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.sha256"), sha256);
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.hashLookupResults"), file.getKnown().toString());
addAcquisitionDetails(sb, dataSource);

View File

@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.contentviewers;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
@ -97,6 +98,9 @@ final class PDFViewer implements FileTypeViewer {
// Add the IcePDF view to the center of our container.
this.container.add(icePdfPanel, BorderLayout.CENTER);
// Disable all components until the document is ready to view.
enableComponents(container, false);
// Document is the 'M' in IcePDFs MVC set up. Read the data needed to
// populate the model in the background.
@ -122,12 +126,13 @@ final class PDFViewer implements FileTypeViewer {
// will cause UI widgets to be updated.
try {
Document doc = get();
controller.openDocument(doc, null);
controller.openDocument(doc, file.getName());
// This makes the PDF viewer appear as one continuous
// document, which is the default for most popular PDF viewers.
controller.setPageViewMode(DocumentViewControllerImpl.ONE_COLUMN_VIEW, true);
// This makes it possible to select text by left clicking and dragging.
controller.setDisplayTool(DocumentViewModelImpl.DISPLAY_TOOL_TEXT_SELECTION);
enableComponents(container, true);
} catch (InterruptedException ex) {
// Do nothing.
} catch (ExecutionException ex) {
@ -140,10 +145,28 @@ final class PDFViewer implements FileTypeViewer {
file.getId(), file.getName()), ex);
showErrorDialog();
}
} catch (Throwable ex) {
logger.log(Level.WARNING, String.format("PDF content viewer "
+ "was unable to open document with id %d and name %s",
file.getId(), file.getName()), ex);
}
}
}.execute();
}
/**
* Recursively enable/disable all components in this content viewer.
* This will disable/enable all internal IcePDF Swing components too.
*/
private void enableComponents(Container container, boolean enabled) {
Component[] components = container.getComponents();
for(Component component : components) {
component.setEnabled(enabled);
if (component instanceof Container) {
enableComponents((Container)component, enabled);
}
}
}
@Override
public Component getComponent() {

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2012-2014 Basis Technology Corp.
* Copyright 2012-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -54,6 +54,40 @@ import org.xml.sax.SAXException;
* -Loading documents from disk
*/
public class XMLUtil {
private static DocumentBuilder getDocumentBuilder() throws ParserConfigurationException {
// See JIRA-6958 for details about class loading and jaxb.
ClassLoader original = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(XMLUtil.class.getClassLoader());
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
return builderFactory.newDocumentBuilder();
} finally {
Thread.currentThread().setContextClassLoader(original);
}
}
private static SchemaFactory getSchemaFactory(String schemaLanguage) {
// See JIRA-6958 for details about class loading and jaxb.
ClassLoader original = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(XMLUtil.class.getClassLoader());
return SchemaFactory.newInstance(schemaLanguage);
} finally {
Thread.currentThread().setContextClassLoader(original);
}
}
private static TransformerFactory getTransformerFactory() {
// See JIRA-6958 for details about class loading and jaxb.
ClassLoader original = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(XMLUtil.class.getClassLoader());
return TransformerFactory.newInstance();
} finally {
Thread.currentThread().setContextClassLoader(original);
}
}
/**
* Creates a W3C DOM.
@ -63,9 +97,7 @@ public class XMLUtil {
* @throws ParserConfigurationException
*/
public static Document createDocument() throws ParserConfigurationException {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
return builder.newDocument();
return getDocumentBuilder().newDocument();
}
/**
@ -100,8 +132,7 @@ public class XMLUtil {
* @throws IOException
*/
public static Document loadDocument(String docPath) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
DocumentBuilder builder = getDocumentBuilder();
Document doc = builder.parse(new FileInputStream(docPath));
return doc;
}
@ -119,7 +150,7 @@ public class XMLUtil {
public static <T> void validateDocument(final Document doc, Class<T> clazz, String schemaResourceName) throws SAXException, IOException {
PlatformUtil.extractResourceToUserConfigDir(clazz, schemaResourceName, false);
File schemaFile = new File(Paths.get(PlatformUtil.getUserConfigDirectory(), schemaResourceName).toAbsolutePath().toString());
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
SchemaFactory schemaFactory = getSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema(schemaFile);
Validator validator = schema.newValidator();
validator.validate(new DOMSource(doc), new DOMResult());
@ -140,7 +171,7 @@ public class XMLUtil {
* @throws IOException
*/
public static void saveDocument(final Document doc, String encoding, String docPath) throws TransformerConfigurationException, FileNotFoundException, UnsupportedEncodingException, TransformerException, IOException {
TransformerFactory xf = TransformerFactory.newInstance();
TransformerFactory xf = getTransformerFactory();
xf.setAttribute("indent-number", 1); //NON-NLS
Transformer xformer = xf.newTransformer();
xformer.setOutputProperty(OutputKeys.METHOD, "xml"); //NON-NLS
@ -178,7 +209,7 @@ public class XMLUtil {
try {
PlatformUtil.extractResourceToUserConfigDir(clazz, schemaFile, false);
File schemaLoc = new File(PlatformUtil.getUserConfigDirectory() + File.separator + schemaFile);
SchemaFactory schm = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
SchemaFactory schm = getSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI);
try {
Schema schema = schm.newSchema(schemaLoc);
Validator validator = schema.newValidator();
@ -226,10 +257,9 @@ public class XMLUtil {
*/
// TODO: Deprecate.
public static <T> Document loadDoc(Class<T> clazz, String xmlPath) {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
Document ret = null;
try {
DocumentBuilder builder = builderFactory.newDocumentBuilder();
DocumentBuilder builder = getDocumentBuilder();
ret = builder.parse(new FileInputStream(xmlPath));
} catch (ParserConfigurationException e) {
Logger.getLogger(clazz.getName()).log(Level.SEVERE, "Error loading XML file " + xmlPath + " : can't initialize parser.", e); //NON-NLS
@ -268,7 +298,7 @@ public class XMLUtil {
*/
// TODO: Deprecate.
public static <T> boolean saveDoc(Class<T> clazz, String xmlPath, String encoding, final Document doc) {
TransformerFactory xf = TransformerFactory.newInstance();
TransformerFactory xf = getTransformerFactory();
xf.setAttribute("indent-number", 1); //NON-NLS
boolean success = false;
try {

View File

@ -278,6 +278,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
"AbstractAbstractFileNode.typeMetaColLbl=Type(Meta)",
"AbstractAbstractFileNode.knownColLbl=Known",
"AbstractAbstractFileNode.md5HashColLbl=MD5 Hash",
"AbstractAbstractFileNode.sha256HashColLbl=SHA-256 Hash",
"AbstractAbstractFileNode.objectId=Object ID",
"AbstractAbstractFileNode.mimeType=MIME Type",
"AbstractAbstractFileNode.extensionColLbl=Extension"})
@ -305,6 +306,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
TYPE_META(AbstractAbstractFileNode_typeMetaColLbl()),
KNOWN(AbstractAbstractFileNode_knownColLbl()),
MD5HASH(AbstractAbstractFileNode_md5HashColLbl()),
SHA256HASH(AbstractAbstractFileNode_sha256HashColLbl()),
ObjectID(AbstractAbstractFileNode_objectId()),
MIMETYPE(AbstractAbstractFileNode_mimeType()),
EXTENSION(AbstractAbstractFileNode_extensionColLbl());
@ -358,6 +360,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
properties.add(new NodeProperty<>(KNOWN.toString(), KNOWN.toString(), NO_DESCR, content.getKnown().getName()));
properties.add(new NodeProperty<>(LOCATION.toString(), LOCATION.toString(), NO_DESCR, getContentPath(content)));
properties.add(new NodeProperty<>(MD5HASH.toString(), MD5HASH.toString(), NO_DESCR, StringUtils.defaultString(content.getMd5Hash())));
properties.add(new NodeProperty<>(SHA256HASH.toString(), SHA256HASH.toString(), NO_DESCR, StringUtils.defaultString(content.getSha256Hash())));
properties.add(new NodeProperty<>(MIMETYPE.toString(), MIMETYPE.toString(), NO_DESCR, StringUtils.defaultString(content.getMIMEType())));
properties.add(new NodeProperty<>(EXTENSION.toString(), EXTENSION.toString(), NO_DESCR, content.getNameExtension()));
@ -577,6 +580,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
map.put(FLAGS_META.toString(), content.getMetaFlagsAsString());
map.put(KNOWN.toString(), content.getKnown().getName());
map.put(MD5HASH.toString(), StringUtils.defaultString(content.getMd5Hash()));
map.put(SHA256HASH.toString(), StringUtils.defaultString(content.getSha256Hash()));
map.put(MIMETYPE.toString(), StringUtils.defaultString(content.getMIMEType()));
map.put(EXTENSION.toString(), content.getNameExtension());
}

View File

@ -30,6 +30,7 @@ AbstractAbstractFileNode.modifiedTimeColLbl=Modified Time
AbstractAbstractFileNode.nameColLbl=Name
AbstractAbstractFileNode.objectId=Object ID
AbstractAbstractFileNode.originalName=Original Name
AbstractAbstractFileNode.sha256HashColLbl=SHA-256 Hash
AbstractAbstractFileNode.sizeColLbl=Size
AbstractAbstractFileNode.tagsProperty.displayName=Tags
AbstractAbstractFileNode.typeDirColLbl=Type(Dir)

View File

@ -529,9 +529,11 @@ final public class Accounts implements AutopsyVisitableItem {
+ getRejectedArtifactFilterClause(); //NON-NLS
try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
ResultSet rs = results.getResultSet();) {
List<Long> tempList = new ArrayList<>();
while (rs.next()) {
list.add(rs.getLong("artifact_id")); //NON-NLS
tempList.add(rs.getLong("artifact_id")); // NON-NLS
}
list.addAll(tempList);
} catch (TskCoreException | SQLException ex) {
LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
}

View File

@ -398,6 +398,20 @@ final class DataSourceInfoUtilities {
BlackboardAttribute attr = getAttributeOrNull(artifact, attributeType);
return (attr == null) ? null : attr.getValueLong();
}
/**
* Retrieves the int value of a certain attribute type from an artifact.
*
* @param artifact The artifact.
* @param attributeType The attribute type.
*
* @return The 'getValueInt()' value or null if the attribute could not be
* retrieved.
*/
static Integer getIntOrNull(BlackboardArtifact artifact, Type attributeType) {
BlackboardAttribute attr = getAttributeOrNull(artifact, attributeType);
return (attr == null) ? null : attr.getValueInt();
}
/**
* Retrieves the long value of a certain attribute type from an artifact and

View File

@ -118,7 +118,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
dataSource,
DATETIME_ATT,
DataSourceInfoUtilities.SortOrder.DESCENDING,
10);
maxCount);
List<RecentFileDetails> fileDetails = new ArrayList<>();
for (BlackboardArtifact artifact : artifactList) {
@ -134,12 +134,11 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
} else if (attribute.getAttributeType().equals(PATH_ATT)) {
path = attribute.getValueString();
}
if (accessedTime != null) {
fileDetails.add(new RecentFileDetails(path, accessedTime));
}
}
if (accessedTime != null && accessedTime != 0) {
fileDetails.add(new RecentFileDetails(path, accessedTime));
}
}
return fileDetails;
@ -190,7 +189,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
path = attribute.getValueString();
}
}
if (accessedTime != null) {
if (accessedTime != null && accessedTime != 0L) {
fileDetails.add(new RecentDownloadDetails(path, accessedTime, domain));
}
}
@ -215,6 +214,10 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
return Collections.emptyList();
}
if (maxCount < 0) {
throw new IllegalArgumentException("Invalid maxCount passed to getRecentAttachments, value must be equal to or greater than 0");
}
return createListFromMap(buildAttachmentMap(dataSource), maxCount);
}
@ -241,7 +244,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
}
BlackboardArtifact messageArtifact = skCase.getBlackboardArtifact(attribute.getValueLong());
if (isMessageArtifact(messageArtifact)) {
if (messageArtifact != null && isMessageArtifact(messageArtifact)) {
Content content = artifact.getParent();
if (content instanceof AbstractFile) {
String sender;

View File

@ -1,370 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.datasourcesummary.datamodel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultArtifactUpdateGovernor;
import java.io.File;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Provides information to populate Top Programs Summary queries.
*/
public class TopProgramsSummary implements DefaultArtifactUpdateGovernor {
private static final Set<Integer> ARTIFACT_UPDATE_TYPE_IDS = new HashSet<>(Arrays.asList(
ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID()
));
/**
* A SQL join type.
*/
private enum JoinType {
LEFT,
RIGHT,
INNER,
OUTER
}
/**
* A blackboard attribute value column.
*/
private enum AttributeColumn {
value_text,
value_int32,
value_int64
}
/**
* The suffix joined to a key name for use as an identifier of a query.
*/
private static final String QUERY_SUFFIX = "_query";
/**
* Functions that determine the folder name of a list of path elements. If
* not matched, function returns null.
*/
private static final List<Function<List<String>, String>> SHORT_FOLDER_MATCHERS = Arrays.asList(
// handle Program Files and Program Files (x86) - if true, return the next folder
(pathList) -> {
if (pathList.size() < 2) {
return null;
}
String rootParent = pathList.get(0).toUpperCase();
if ("PROGRAM FILES".equals(rootParent) || "PROGRAM FILES (X86)".equals(rootParent)) {
return pathList.get(1);
} else {
return null;
}
},
// if there is a folder named "APPLICATION DATA" or "APPDATA"
(pathList) -> {
for (String pathEl : pathList) {
String uppered = pathEl.toUpperCase();
if ("APPLICATION DATA".equals(uppered) || "APPDATA".equals(uppered)) {
return "AppData";
}
}
return null;
}
);
/**
* Creates a sql statement querying the blackboard attributes table for a
* particular attribute type and returning a specified value. That query
* also joins with the blackboard artifact table.
*
* @param joinType The type of join statement to create.
* @param attributeColumn The blackboard attribute column that should be
* returned.
* @param attrType The attribute type to query for.
* @param keyName The aliased name of the attribute to return. This
* is also used to calculate the alias of the query
* same as getFullKey.
* @param bbaName The blackboard artifact table alias.
*
* @return The generated sql statement.
*/
private static String getAttributeJoin(JoinType joinType, AttributeColumn attributeColumn, BlackboardAttribute.ATTRIBUTE_TYPE attrType, String keyName, String bbaName) {
String queryName = keyName + QUERY_SUFFIX;
String innerQueryName = "inner_attribute_" + queryName;
return "\n" + joinType + " JOIN (\n"
+ " SELECT \n"
+ " " + innerQueryName + ".artifact_id,\n"
+ " " + innerQueryName + "." + attributeColumn + " AS " + keyName + "\n"
+ " FROM blackboard_attributes " + innerQueryName + "\n"
+ " WHERE " + innerQueryName + ".attribute_type_id = " + attrType.getTypeID() + " -- " + attrType.name() + "\n"
+ ") " + queryName + " ON " + queryName + ".artifact_id = " + bbaName + ".artifact_id\n";
}
/**
* Given a column key, creates the full name for the column key.
*
* @param key The column key.
*
* @return The full identifier for the column key.
*/
private static String getFullKey(String key) {
return key + QUERY_SUFFIX + "." + key;
}
/**
* Constructs a SQL 'where' statement from a list of clauses and puts
* parenthesis around each clause.
*
* @param clauses The clauses
*
* @return The generated 'where' statement.
*/
private static String getWhereString(List<String> clauses) {
if (clauses.isEmpty()) {
return "";
}
List<String> parenthesized = clauses.stream()
.map(c -> "(" + c + ")")
.collect(Collectors.toList());
return "\nWHERE " + String.join("\n AND ", parenthesized) + "\n";
}
/**
* Generates a [column] LIKE sql clause.
*
* @param column The column identifier.
* @param likeString The string that will be used as column comparison.
* @param isLike if false, the statement becomes NOT LIKE.
*
* @return The generated statement.
*/
private static String getLikeClause(String column, String likeString, boolean isLike) {
return column + (isLike ? "" : " NOT") + " LIKE '" + likeString + "'";
}
private final SleuthkitCaseProvider provider;
public TopProgramsSummary() {
this(SleuthkitCaseProvider.DEFAULT);
}
public TopProgramsSummary(SleuthkitCaseProvider provider) {
this.provider = provider;
}
@Override
public Set<Integer> getArtifactTypeIdsForRefresh() {
return ARTIFACT_UPDATE_TYPE_IDS;
}
/**
* Retrieves a list of the top programs used on the data source. Currently
* determines this based off of which prefetch results return the highest
* count.
*
* @param dataSource The data source.
* @param count The number of programs to return.
*
* @return The top results objects found.
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
* @throws SQLException
*/
public List<TopProgramsResult> getTopPrograms(DataSource dataSource, int count)
throws SleuthkitCaseProviderException, TskCoreException, SQLException {
if (dataSource == null || count <= 0) {
return Collections.emptyList();
}
// ntosboot should be ignored
final String ntosBootIdentifier = "NTOSBOOT";
// programs in windows directory to be ignored
final String windowsDir = "/WINDOWS%";
final String nameParam = "name";
final String pathParam = "path";
final String runCountParam = "run_count";
final String lastRunParam = "last_run";
String bbaQuery = "bba";
final String query = "SELECT\n"
+ " " + getFullKey(nameParam) + " AS " + nameParam + ",\n"
+ " " + getFullKey(pathParam) + " AS " + pathParam + ",\n"
+ " MAX(" + getFullKey(runCountParam) + ") AS " + runCountParam + ",\n"
+ " MAX(" + getFullKey(lastRunParam) + ") AS " + lastRunParam + "\n"
+ "FROM blackboard_artifacts " + bbaQuery + "\n"
+ getAttributeJoin(JoinType.INNER, AttributeColumn.value_text, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, nameParam, bbaQuery)
+ getAttributeJoin(JoinType.LEFT, AttributeColumn.value_text, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH, pathParam, bbaQuery)
+ getAttributeJoin(JoinType.LEFT, AttributeColumn.value_int32, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COUNT, runCountParam, bbaQuery)
+ getAttributeJoin(JoinType.LEFT, AttributeColumn.value_int64, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, lastRunParam, bbaQuery)
+ getWhereString(Arrays.asList(
bbaQuery + ".artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID(),
bbaQuery + ".data_source_obj_id = " + dataSource.getId(),
// exclude ntosBootIdentifier from results
getLikeClause(getFullKey(nameParam), ntosBootIdentifier, false),
// exclude windows directory items from results
getFullKey(pathParam) + " IS NULL OR " + getLikeClause(getFullKey(pathParam), windowsDir, false)
))
+ "GROUP BY " + getFullKey(nameParam) + ", " + getFullKey(pathParam) + "\n"
+ "ORDER BY \n"
+ " MAX(" + getFullKey(runCountParam) + ") DESC,\n"
+ " MAX(" + getFullKey(lastRunParam) + ") DESC,\n"
+ " " + getFullKey(nameParam) + " ASC";
DataSourceInfoUtilities.ResultSetHandler<List<TopProgramsResult>> handler = (resultSet) -> {
List<TopProgramsResult> progResults = new ArrayList<>();
boolean quitAtCount = false;
while (resultSet.next() && (!quitAtCount || progResults.size() < count)) {
long lastRunEpoch = resultSet.getLong(lastRunParam);
Date lastRun = (resultSet.wasNull()) ? null : new Date(lastRunEpoch * 1000);
Long runCount = resultSet.getLong(runCountParam);
if (resultSet.wasNull()) {
runCount = null;
}
if (lastRun != null || runCount != null) {
quitAtCount = true;
}
progResults.add(new TopProgramsResult(
resultSet.getString(nameParam),
resultSet.getString(pathParam),
runCount,
lastRun));
}
return progResults;
};
try (SleuthkitCase.CaseDbQuery dbQuery = provider.get().executeQuery(query);
ResultSet resultSet = dbQuery.getResultSet()) {
return handler.process(resultSet);
}
}
/**
* Determines a short folder name if any. Otherwise, returns empty string.
*
* @param strPath The string path.
* @param applicationName The application name.
*
* @return The short folder name or empty string if not found.
*/
public String getShortFolderName(String strPath, String applicationName) {
if (strPath == null) {
return "";
}
List<String> pathEls = new ArrayList<>(Arrays.asList(applicationName));
File file = new File(strPath);
while (file != null && StringUtils.isNotBlank(file.getName())) {
pathEls.add(file.getName());
file = file.getParentFile();
}
Collections.reverse(pathEls);
for (Function<List<String>, String> matchEntry : SHORT_FOLDER_MATCHERS) {
String result = matchEntry.apply(pathEls);
if (StringUtils.isNotBlank(result)) {
return result;
}
}
return "";
}
/**
* Describes a result of a program run on a datasource.
*/
public static class TopProgramsResult {
private final String programName;
private final String programPath;
private final Long runTimes;
private final Date lastRun;
/**
* Main constructor.
*
* @param programName The name of the program.
* @param programPath The path of the program.
* @param runTimes The number of runs.
*/
TopProgramsResult(String programName, String programPath, Long runTimes, Date lastRun) {
this.programName = programName;
this.programPath = programPath;
this.runTimes = runTimes;
this.lastRun = lastRun;
}
/**
* @return The name of the program
*/
public String getProgramName() {
return programName;
}
/**
* @return The path of the program.
*/
public String getProgramPath() {
return programPath;
}
/**
* @return The number of run times or null if not present.
*/
public Long getRunTimes() {
return runTimes;
}
/**
* @return The last time the program was run or null if not present.
*/
public Date getLastRun() {
return lastRun;
}
}
}

View File

@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.datasourcesummary.datamodel;
import java.io.File;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultArtifactUpdateGovernor;
import java.util.ArrayList;
import java.util.Arrays;
@ -30,6 +31,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -54,6 +56,36 @@ import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
*/
public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
/**
* Functions that determine the folder name of a list of path elements. If
* not matched, function returns null.
*/
private static final List<Function<List<String>, String>> SHORT_FOLDER_MATCHERS = Arrays.asList(
// handle Program Files and Program Files (x86) - if true, return the next folder
(pathList) -> {
if (pathList.size() < 2) {
return null;
}
String rootParent = pathList.get(0).toUpperCase();
if ("PROGRAM FILES".equals(rootParent) || "PROGRAM FILES (X86)".equals(rootParent)) {
return pathList.get(1);
} else {
return null;
}
},
// if there is a folder named "APPLICATION DATA" or "APPDATA"
(pathList) -> {
for (String pathEl : pathList) {
String uppered = pathEl.toUpperCase();
if ("APPLICATION DATA".equals(uppered) || "APPDATA".equals(uppered)) {
return "AppData";
}
}
return null;
}
);
private static final BlackboardArtifact.Type TYPE_DEVICE_ATTACHED = new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_DEVICE_ATTACHED);
private static final BlackboardArtifact.Type TYPE_WEB_HISTORY = new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_HISTORY);
@ -69,17 +101,51 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
private static final BlackboardAttribute.Type TYPE_DATETIME_START = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_START);
private static final BlackboardAttribute.Type TYPE_DATETIME_END = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_END);
private static final BlackboardAttribute.Type TYPE_DOMAIN = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DOMAIN);
private static final BlackboardAttribute.Type TYPE_PROG_NAME = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME);
private static final BlackboardAttribute.Type TYPE_PATH = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH);
private static final BlackboardAttribute.Type TYPE_COUNT = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_COUNT);
private static final String NTOS_BOOT_IDENTIFIER = "NTOSBOOT";
private static final String WINDOWS_PREFIX = "/WINDOWS";
private static final Comparator<TopAccountResult> TOP_ACCOUNT_RESULT_DATE_COMPARE = (a, b) -> a.getLastAccess().compareTo(b.getLastAccess());
private static final Comparator<TopWebSearchResult> TOP_WEBSEARCH_RESULT_DATE_COMPARE = (a, b) -> a.getDateAccessed().compareTo(b.getDateAccessed());
/**
* Sorts TopProgramsResults pushing highest run time count then most recent
* run and then the program name that comes earliest in the alphabet.
*/
private static final Comparator<TopProgramsResult> TOP_PROGRAMS_RESULT_COMPARE = (a, b) -> {
// first priority for sorting is the run times
// if non-0, this is the return value for the comparator
int runTimesCompare = nullableCompare(a.getRunTimes(), b.getRunTimes());
if (runTimesCompare != 0) {
return -runTimesCompare;
}
// second priority for sorting is the last run date
// if non-0, this is the return value for the comparator
int lastRunCompare = nullableCompare(
a.getLastRun() == null ? null : a.getLastRun().getTime(),
b.getLastRun() == null ? null : b.getLastRun().getTime());
if (lastRunCompare != 0) {
return -lastRunCompare;
}
// otherwise sort alphabetically
return (a.getProgramName() == null ? "" : a.getProgramName())
.compareToIgnoreCase((b.getProgramName() == null ? "" : b.getProgramName()));
};
private static final Set<Integer> ARTIFACT_UPDATE_TYPE_IDS = new HashSet<>(Arrays.asList(
ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID(),
ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(),
ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(),
ARTIFACT_TYPE.TSK_CALLLOG.getTypeID(),
ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID(),
ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID()
ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID(),
ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID()
));
private static final Set<String> DEVICE_EXCLUDE_LIST = new HashSet<>(Arrays.asList("ROOT_HUB", "ROOT_HUB20"));
@ -539,6 +605,189 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
.collect(Collectors.toList());
}
/**
* Determines a short folder name if any. Otherwise, returns empty string.
*
* @param strPath The string path.
* @param applicationName The application name.
*
* @return The short folder name or empty string if not found.
*/
public String getShortFolderName(String strPath, String applicationName) {
if (strPath == null) {
return "";
}
List<String> pathEls = new ArrayList<>(Arrays.asList(applicationName));
File file = new File(strPath);
while (file != null && org.apache.commons.lang.StringUtils.isNotBlank(file.getName())) {
pathEls.add(file.getName());
file = file.getParentFile();
}
Collections.reverse(pathEls);
for (Function<List<String>, String> matchEntry : SHORT_FOLDER_MATCHERS) {
String result = matchEntry.apply(pathEls);
if (org.apache.commons.lang.StringUtils.isNotBlank(result)) {
return result;
}
}
return "";
}
/**
* Creates a TopProgramsResult from a TSK_PROG_RUN blackboard artifact.
*
* @param artifact The TSK_PROG_RUN blackboard artifact.
*
* @return The generated TopProgramsResult.
*/
private TopProgramsResult getTopProgramsResult(BlackboardArtifact artifact) {
String programName = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_PROG_NAME);
// ignore items with no name or a ntos boot identifier
if (StringUtils.isBlank(programName) || NTOS_BOOT_IDENTIFIER.equalsIgnoreCase(programName)) {
return null;
}
String path = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_PATH);
// ignore windows directory
if (StringUtils.startsWithIgnoreCase(path, WINDOWS_PREFIX)) {
return null;
}
Integer count = DataSourceInfoUtilities.getIntOrNull(artifact, TYPE_COUNT);
Long longCount = (count == null) ? null : (long) count;
return new TopProgramsResult(
programName,
path,
longCount,
DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME)
);
}
/**
* Retrieves the maximum date given two (possibly null) dates.
*
* @param date1 First date.
* @param date2 Second date.
*
* @return The maximum non-null date or null if both items are null.
*/
private static Date getMax(Date date1, Date date2) {
if (date1 == null) {
return date2;
} else if (date2 == null) {
return date1;
} else {
return date1.compareTo(date2) > 0 ? date1 : date2;
}
}
/**
* Returns the compare value favoring the higher non-null number.
*
* @param long1 First possibly null long.
* @param long2 Second possibly null long.
*
* @return Returns the compare value: 1,0,-1 favoring the higher non-null
* value.
*/
private static int nullableCompare(Long long1, Long long2) {
if (long1 == null && long2 == null) {
return 0;
} else if (long1 != null && long2 == null) {
return 1;
} else if (long1 == null && long2 != null) {
return -1;
}
return Long.compare(long1, long2);
}
/**
* Returns true if number is non-null and higher than 0.
*
* @param longNum The number.
*
* @return True if non-null and higher than 0.
*/
private static boolean isPositiveNum(Long longNum) {
return longNum != null && longNum > 0;
}
/**
* Retrieves the top programs results for the given data source limited to
* the count provided as a parameter. The highest run times are at the top
* of the list. If that information isn't available the last run date is
* used. If both, the last run date and the number of run times are
* unavailable, the programs will be sorted alphabetically, the count will
* be ignored and all items will be returned.
*
* @param dataSource The datasource. If the datasource is null, an empty
* list will be returned.
* @param count The number of results to return. This value must be > 0
* or an IllegalArgumentException will be thrown.
*
* @return The sorted list and limited to the count if last run or run count
* information is available on any item.
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
*/
public List<TopProgramsResult> getTopPrograms(DataSource dataSource, int count) throws SleuthkitCaseProviderException, TskCoreException {
assertValidCount(count);
if (dataSource == null) {
return Collections.emptyList();
}
// Get TopProgramsResults for each TSK_PROG_RUN artifact
Collection<TopProgramsResult> results = caseProvider.get().getBlackboard().getArtifacts(ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID(), dataSource.getId())
.stream()
// convert to a TopProgramsResult object or null if missing critical information
.map((art) -> getTopProgramsResult(art))
// remove any null items
.filter((res) -> res != null)
// group by the program name and program path
// The value will be a TopProgramsResult with the max run times
// and most recent last run date for each program name / program path pair.
.collect(Collectors.toMap(
res -> Pair.of(res.getProgramName(), res.getProgramPath()),
res -> res,
(res1, res2) -> {
return new TopProgramsResult(
res1.getProgramName(),
res1.getProgramPath(),
getMax(res1.getRunTimes(), res2.getRunTimes()),
getMax(res1.getLastRun(), res2.getLastRun()));
})).values();
List<TopProgramsResult> orderedResults = results.stream()
.sorted(TOP_PROGRAMS_RESULT_COMPARE)
.collect(Collectors.toList());
// only limit the list to count if there is no last run date and no run times.
if (!orderedResults.isEmpty()) {
TopProgramsResult topResult = orderedResults.get(0);
// if run times / last run information is available, the first item should have some value,
// and then the items should be limited accordingly.
if (isPositiveNum(topResult.getRunTimes())
|| (topResult.getLastRun() != null && isPositiveNum(topResult.getLastRun().getTime()))) {
return orderedResults.stream().limit(count).collect(Collectors.toList());
}
}
// otherwise return the alphabetized list with no limit applied.
return orderedResults;
}
/**
* Object containing information about a web search artifact.
*/
@ -722,4 +971,57 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
return lastVisit;
}
}
/**
* Describes a result of a program run on a datasource.
*/
public static class TopProgramsResult {
private final String programName;
private final String programPath;
private final Long runTimes;
private final Date lastRun;
/**
* Main constructor.
*
* @param programName The name of the program.
* @param programPath The path of the program.
* @param runTimes The number of runs.
*/
TopProgramsResult(String programName, String programPath, Long runTimes, Date lastRun) {
this.programName = programName;
this.programPath = programPath;
this.runTimes = runTimes;
this.lastRun = lastRun;
}
/**
* @return The name of the program
*/
public String getProgramName() {
return programName;
}
/**
* @return The path of the program.
*/
public String getProgramPath() {
return programPath;
}
/**
* @return The number of run times or null if not present.
*/
public Long getRunTimes() {
return runTimes;
}
/**
* @return The last time the program was run or null if not present.
*/
public Date getLastRun() {
return lastRun;
}
}
}

View File

@ -29,12 +29,11 @@ import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopProgramsSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopAccountResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDeviceAttachedResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopWebSearchResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopProgramsSummary.TopProgramsResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDomainsResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopProgramsResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
@ -227,35 +226,30 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel();
private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents;
private final TopProgramsSummary topProgramsData;
private final UserActivitySummary userActivityData;
/**
* Creates a new UserActivityPanel.
*/
public UserActivityPanel() {
this(new TopProgramsSummary(), new UserActivitySummary());
this(new UserActivitySummary());
}
/**
* Creates a new UserActivityPanel.
*
* @param topProgramsData Class from which to obtain top programs data.
* @param userActivityData Class from which to obtain remaining user
* activity data.
*/
public UserActivityPanel(
TopProgramsSummary topProgramsData,
UserActivitySummary userActivityData) {
super(topProgramsData, userActivityData);
this.topProgramsData = topProgramsData;
public UserActivityPanel(UserActivitySummary userActivityData) {
super(userActivityData);
this.userActivityData = userActivityData;
// set up data acquisition methods
this.dataFetchComponents = Arrays.asList(
// top programs query
new DataFetchComponents<DataSource, List<TopProgramsResult>>(
(dataSource) -> topProgramsData.getTopPrograms(dataSource, TOP_PROGS_COUNT),
(dataSource) -> userActivityData.getTopPrograms(dataSource, TOP_PROGS_COUNT),
(result) -> {
showResultWithModuleCheck(topProgramsTable, result,
IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
@ -307,7 +301,7 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
* @return The underlying short folder name if one exists.
*/
private String getShortFolderName(String path, String appName) {
return this.topProgramsData.getShortFolderName(path, appName);
return this.userActivityData.getShortFolderName(path, appName);
}
@Override

View File

@ -49,6 +49,7 @@ SizeSearchPanel.sizeCompareComboBox.lessThan=less than
FileSearchPanel.searchButton.text=Search
MimeTypePanel.mimeTypeCheckBox.text=MIME Type:
HashSearchPanel.md5CheckBox.text=MD5:
Sha256HashSearchPanel.sha256CheckBox.text=SHA-256:
HashSearchPanel.emptyHashMsg.text=Must enter something for hash search.
FileSearchPanel.errorLabel.text=\
DataSourcePanel.dataSourceCheckBox.label=Data Source:
@ -58,3 +59,5 @@ DataSourcePanel.dataSourceNoteLabel.text=*Note: Multiple data sources can be sel
DateSearchPanel.noLimitLabel.text=*Empty fields mean "No Limit"
DateSearchPanel.dateFormatLabel.text=*The date format is mm/dd/yyyy
MimeTypePanel.noteLabel.text=*Note: Multiple MIME types can be selected
HashSearchPanel.sha256CheckBox.text=SHA-256:
HashSearchPanel.sha256TextField.text=

View File

@ -5,7 +5,9 @@ FileSearchPanel.emptyNode.display.text=No results found.
HashSearchFilter.errorMessage.emptyHash=Hash data is empty.
HashSearchFilter.errorMessage.wrongCharacter=MD5 contains invalid hex characters.
# {0} - hash data length
HashSearchFilter.errorMessage.wrongLength=Input length({0}), doesn''t match the MD5 length(32).
HashSearchFilter.errorMessage.wrongLengthMd5=Input length({0}), doesn''t match the MD5 length(32).
# {0} - hash data length
HashSearchFilter.errorMessage.wrongLengthSha256=Input length({0}), doesn''t match the SHA-256 length(64).
KnownStatusSearchFilter.errorMessage.noKnownStatusCheckboxSelected=At least one known status checkbox must be selected.
MimeTypeFilter.errorMessage.emptyMimeType=At least one MIME type must be selected.
NameSearchFilter.errorMessage.emtpyName=Please input a name to search.
@ -63,6 +65,7 @@ SizeSearchPanel.sizeCompareComboBox.lessThan=less than
FileSearchPanel.searchButton.text=Search
MimeTypePanel.mimeTypeCheckBox.text=MIME Type:
HashSearchPanel.md5CheckBox.text=MD5:
Sha256HashSearchPanel.sha256CheckBox.text=SHA-256:
HashSearchPanel.emptyHashMsg.text=Must enter something for hash search.
FileSearchPanel.errorLabel.text=\
DataSourcePanel.dataSourceCheckBox.label=Data Source:
@ -72,3 +75,5 @@ DataSourcePanel.dataSourceNoteLabel.text=*Note: Multiple data sources can be sel
DateSearchPanel.noLimitLabel.text=*Empty fields mean "No Limit"
DateSearchPanel.dateFormatLabel.text=*The date format is mm/dd/yyyy
MimeTypePanel.noteLabel.text=*Note: Multiple MIME types can be selected
HashSearchPanel.sha256CheckBox.text=SHA-256:
HashSearchPanel.sha256TextField.text=

View File

@ -41,18 +41,36 @@ class HashSearchFilter extends AbstractFileSearchFilter<HashSearchPanel> {
@Override
public boolean isEnabled() {
return this.getComponent().getHashCheckBox().isSelected();
return (this.getComponent().getMd5HashCheckBox().isSelected()
|| this.getComponent().getSha256HashCheckBox().isSelected());
}
@Override
public String getPredicate() throws FilterValidationException {
String md5Hash = this.getComponent().getSearchTextField().getText();
String predicate = "";
if (this.getComponent().getMd5HashCheckBox().isSelected()) {
String md5Hash = this.getComponent().getMd5TextField().getText();
if (md5Hash.isEmpty()) {
throw new FilterValidationException(EMPTY_HASH_MESSAGE);
if (md5Hash.isEmpty()) {
throw new FilterValidationException(EMPTY_HASH_MESSAGE);
}
predicate = "md5 = '" + md5Hash.toLowerCase() + "'"; //NON-NLS
}
if (this.getComponent().getSha256HashCheckBox().isSelected()) {
String sha256Hash = this.getComponent().getSha256TextField().getText();
if (sha256Hash.isEmpty()) {
throw new FilterValidationException(EMPTY_HASH_MESSAGE);
}
if (predicate.isEmpty()) {
predicate = "sha256 = '" + sha256Hash.toLowerCase() + "'"; //NON-NLS
} else {
predicate = "( " + predicate + " AND sha256 = '" + sha256Hash.toLowerCase() + "')"; //NON-NLS
}
}
return "md5 = '" + md5Hash.toLowerCase() + "'"; //NON-NLS
return predicate;
}
@Override
@ -63,23 +81,45 @@ class HashSearchFilter extends AbstractFileSearchFilter<HashSearchPanel> {
@Override
@Messages({
"HashSearchFilter.errorMessage.emptyHash=Hash data is empty.",
"# {0} - hash data length", "HashSearchFilter.errorMessage.wrongLength=Input length({0}), doesn''t match the MD5 length(32).",
"# {0} - hash data length",
"HashSearchFilter.errorMessage.wrongLengthMd5=Input length({0}), doesn''t match the MD5 length(32).",
"# {0} - hash data length",
"HashSearchFilter.errorMessage.wrongLengthSha256=Input length({0}), doesn''t match the SHA-256 length(64).",
"HashSearchFilter.errorMessage.wrongCharacter=MD5 contains invalid hex characters."
})
public boolean isValid() {
String inputHashData = this.getComponent().getSearchTextField().getText();
if (inputHashData.isEmpty()) {
setLastError(Bundle.HashSearchFilter_errorMessage_emptyHash());
return false;
if (this.getComponent().getMd5HashCheckBox().isSelected()) {
String inputHashData = this.getComponent().getMd5TextField().getText();
if (inputHashData.isEmpty()) {
setLastError(Bundle.HashSearchFilter_errorMessage_emptyHash());
return false;
}
if (inputHashData.length() != 32) {
setLastError(Bundle.HashSearchFilter_errorMessage_wrongLengthMd5(inputHashData.length()));
return false;
}
if (!inputHashData.matches("[0-9a-fA-F]+")) {
setLastError(Bundle.HashSearchFilter_errorMessage_wrongCharacter());
return false;
}
}
if (inputHashData.length() != 32) {
setLastError(Bundle.HashSearchFilter_errorMessage_wrongLength(inputHashData.length()));
return false;
}
if (!inputHashData.matches("[0-9a-fA-F]+")) {
setLastError(Bundle.HashSearchFilter_errorMessage_wrongCharacter());
return false;
if (this.getComponent().getSha256HashCheckBox().isSelected()) {
String inputHashData = this.getComponent().getSha256TextField().getText();
if (inputHashData.isEmpty()) {
setLastError(Bundle.HashSearchFilter_errorMessage_emptyHash());
return false;
}
if (inputHashData.length() != 64) {
setLastError(Bundle.HashSearchFilter_errorMessage_wrongLengthSha256(inputHashData.length()));
return false;
}
if (!inputHashData.matches("[0-9a-fA-F]+")) {
setLastError(Bundle.HashSearchFilter_errorMessage_wrongCharacter());
return false;
}
}
return true;
}
}

View File

@ -56,34 +56,67 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="hashCheckBox" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="sha256CheckBox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="sha256TextField" pref="254" max="32767" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Component id="md5CheckBox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="md5TextField" max="32767" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Component id="searchTextField" min="-2" pref="247" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="103" groupAlignment="3" attributes="0">
<Component id="hashCheckBox" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="searchTextField" alignment="3" min="-2" max="-2" attributes="0"/>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="3" attributes="0">
<Component id="md5CheckBox" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="md5TextField" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="sha256CheckBox" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="sha256TextField" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JCheckBox" name="hashCheckBox">
<Component class="javax.swing.JCheckBox" name="md5CheckBox">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="HashSearchPanel.md5CheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="hashCheckBoxActionPerformed"/>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="md5CheckBoxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JTextField" name="searchTextField">
<Component class="javax.swing.JTextField" name="md5TextField">
</Component>
<Component class="javax.swing.JCheckBox" name="sha256CheckBox">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="HashSearchPanel.sha256CheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="sha256CheckBoxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JTextField" name="sha256TextField">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="HashSearchPanel.sha256TextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Form>

View File

@ -45,19 +45,19 @@ class HashSearchPanel extends javax.swing.JPanel {
private void customizeComponents() {
searchTextField.setComponentPopupMenu(rightClickMenu);
md5TextField.setComponentPopupMenu(rightClickMenu);
ActionListener actList = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JMenuItem jmi = (JMenuItem) e.getSource();
if (jmi.equals(cutMenuItem)) {
searchTextField.cut();
md5TextField.cut();
} else if (jmi.equals(copyMenuItem)) {
searchTextField.copy();
md5TextField.copy();
} else if (jmi.equals(pasteMenuItem)) {
searchTextField.paste();
md5TextField.paste();
} else if (jmi.equals(selectAllMenuItem)) {
searchTextField.selectAll();
md5TextField.selectAll();
}
}
};
@ -65,7 +65,24 @@ class HashSearchPanel extends javax.swing.JPanel {
copyMenuItem.addActionListener(actList);
pasteMenuItem.addActionListener(actList);
selectAllMenuItem.addActionListener(actList);
this.searchTextField.getDocument().addDocumentListener(new DocumentListener() {
this.md5TextField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}
@Override
public void removeUpdate(DocumentEvent e) {
firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}
@Override
public void changedUpdate(DocumentEvent e) {
firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}
});
this.sha256TextField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
@ -84,17 +101,27 @@ class HashSearchPanel extends javax.swing.JPanel {
}
JCheckBox getHashCheckBox() {
return hashCheckBox;
JCheckBox getMd5HashCheckBox() {
return md5CheckBox;
}
JTextField getSearchTextField() {
return searchTextField;
JTextField getMd5TextField() {
return md5TextField;
}
JCheckBox getSha256HashCheckBox() {
return sha256CheckBox;
}
JTextField getSha256TextField() {
return sha256TextField;
}
void setComponentsEnabled() {
boolean enabled = hashCheckBox.isSelected();
this.searchTextField.setEnabled(enabled);
boolean md5Enabled = md5CheckBox.isSelected();
this.md5TextField.setEnabled(md5Enabled);
boolean sha256Enabled = sha256CheckBox.isSelected();
this.sha256TextField.setEnabled(sha256Enabled);
}
/**
@ -111,8 +138,10 @@ class HashSearchPanel extends javax.swing.JPanel {
copyMenuItem = new javax.swing.JMenuItem();
pasteMenuItem = new javax.swing.JMenuItem();
selectAllMenuItem = new javax.swing.JMenuItem();
hashCheckBox = new javax.swing.JCheckBox();
searchTextField = new javax.swing.JTextField();
md5CheckBox = new javax.swing.JCheckBox();
md5TextField = new javax.swing.JTextField();
sha256CheckBox = new javax.swing.JCheckBox();
sha256TextField = new javax.swing.JTextField();
cutMenuItem.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "NameSearchPanel.cutMenuItem.text")); // NOI18N
rightClickMenu.add(cutMenuItem);
@ -126,48 +155,75 @@ class HashSearchPanel extends javax.swing.JPanel {
selectAllMenuItem.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "NameSearchPanel.selectAllMenuItem.text")); // NOI18N
rightClickMenu.add(selectAllMenuItem);
hashCheckBox.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "HashSearchPanel.md5CheckBox.text")); // NOI18N
hashCheckBox.addActionListener(new java.awt.event.ActionListener() {
md5CheckBox.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "HashSearchPanel.md5CheckBox.text")); // NOI18N
md5CheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
hashCheckBoxActionPerformed(evt);
md5CheckBoxActionPerformed(evt);
}
});
sha256CheckBox.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "HashSearchPanel.sha256CheckBox.text")); // NOI18N
sha256CheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
sha256CheckBoxActionPerformed(evt);
}
});
sha256TextField.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "HashSearchPanel.sha256TextField.text")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(0, 0, 0)
.addComponent(hashCheckBox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(searchTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 247, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(0, 0, 0))
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(sha256CheckBox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(sha256TextField, javax.swing.GroupLayout.DEFAULT_SIZE, 254, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
.addComponent(md5CheckBox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(md5TextField)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(hashCheckBox)
.addComponent(searchTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(md5CheckBox)
.addComponent(md5TextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(sha256CheckBox)
.addComponent(sha256TextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
);
}// </editor-fold>//GEN-END:initComponents
private void hashCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_hashCheckBoxActionPerformed
private void md5CheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_md5CheckBoxActionPerformed
setComponentsEnabled();
firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}//GEN-LAST:event_hashCheckBoxActionPerformed
}//GEN-LAST:event_md5CheckBoxActionPerformed
private void sha256CheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sha256CheckBoxActionPerformed
setComponentsEnabled();
firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}//GEN-LAST:event_sha256CheckBoxActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JMenuItem copyMenuItem;
private javax.swing.JMenuItem cutMenuItem;
private javax.swing.JCheckBox hashCheckBox;
private javax.swing.JCheckBox md5CheckBox;
private javax.swing.JTextField md5TextField;
private javax.swing.JMenuItem pasteMenuItem;
private javax.swing.JPopupMenu rightClickMenu;
private javax.swing.JTextField searchTextField;
private javax.swing.JMenuItem selectAllMenuItem;
private javax.swing.JCheckBox sha256CheckBox;
private javax.swing.JTextField sha256TextField;
// End of variables declaration//GEN-END:variables
void addActionListener(ActionListener l) {
searchTextField.addActionListener(l);
md5TextField.addActionListener(l);
}
}

View File

@ -1274,6 +1274,9 @@ final class IngestJobPipeline {
}
}
}
// If a data source had no tasks in progress it may now be complete.
checkForStageCompleted();
}
/**

View File

@ -18,8 +18,8 @@
*/
package org.sleuthkit.autopsy.modules.hashdatabase;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@ -208,10 +208,17 @@ public class HashDbIngestModule implements FileIngestModule {
// Safely get a reference to the totalsForIngestJobs object
IngestJobTotals totals = getTotalsForIngestJobs(jobId);
// calc hash value
String md5Hash = getHash(file, totals);
if (md5Hash == null) {
return ProcessResult.ERROR;
// calc hash values
try {
calculateHashes(file, totals);
} catch (TskCoreException ex) {
logger.log(Level.WARNING, String.format("Error calculating hash of file '%s' (id=%d).", file.getName(), file.getId()), ex); //NON-NLS
services.postMessage(IngestMessage.createErrorMessage(
HashLookupModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "HashDbIngestModule.fileReadErrorMsg", file.getName()),
NbBundle.getMessage(this.getClass(), "HashDbIngestModule.calcHashValueErr",
file.getParentPath() + file.getName(),
file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC) ? "Allocated File" : "Deleted File")));
}
// the processing result of handling this file
@ -451,50 +458,46 @@ public class HashDbIngestModule implements FileIngestModule {
}
/**
* Retrieves the md5 hash for a file or generates one if no one exists on
* the file.
* Generates hashes for the given file if they haven't already been set.
* Hashes are saved to the AbstractFile object.
*
* @param file The file in order to determine the hash.
* @param totals The timing metrics for this process.
*
* @return The found or determined md5 hash or null if none could be
* determined.
*/
private String getHash(AbstractFile file, IngestJobTotals totals) {
private void calculateHashes(AbstractFile file, IngestJobTotals totals) throws TskCoreException {
// First check if we've already calculated the hashes.
String md5Hash = file.getMd5Hash();
if (md5Hash != null && md5Hash.isEmpty()) {
return md5Hash;
String sha256Hash = file.getSha256Hash();
if ((md5Hash != null && ! md5Hash.isEmpty())
&& (sha256Hash != null && ! sha256Hash.isEmpty())) {
return;
}
try {
TimingMetric metric = HealthMonitor.getTimingMetric("Disk Reads: Hash calculation");
long calcstart = System.currentTimeMillis();
md5Hash = HashUtility.calculateMd5Hash(file);
if (file.getSize() > 0) {
// Surprisingly, the hash calculation does not seem to be correlated that
// strongly with file size until the files get large.
// Only normalize if the file size is greater than ~1MB.
if (file.getSize() < 1000000) {
HealthMonitor.submitTimingMetric(metric);
} else {
// In testing, this normalization gave reasonable resuls
HealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000);
}
TimingMetric metric = HealthMonitor.getTimingMetric("Disk Reads: Hash calculation");
long calcstart = System.currentTimeMillis();
List<HashUtility.HashResult> newHashResults =
HashUtility.calculateHashes(file, Arrays.asList(HashUtility.HashType.MD5,HashUtility.HashType.SHA256 ));
if (file.getSize() > 0) {
// Surprisingly, the hash calculation does not seem to be correlated that
// strongly with file size until the files get large.
// Only normalize if the file size is greater than ~1MB.
if (file.getSize() < 1000000) {
HealthMonitor.submitTimingMetric(metric);
} else {
// In testing, this normalization gave reasonable resuls
HealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000);
}
file.setMd5Hash(md5Hash);
long delta = (System.currentTimeMillis() - calcstart);
totals.totalCalctime.addAndGet(delta);
return md5Hash;
} catch (IOException ex) {
logger.log(Level.WARNING, String.format("Error calculating hash of file '%s' (id=%d).", file.getName(), file.getId()), ex); //NON-NLS
services.postMessage(IngestMessage.createErrorMessage(
HashLookupModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "HashDbIngestModule.fileReadErrorMsg", file.getName()),
NbBundle.getMessage(this.getClass(), "HashDbIngestModule.calcHashValueErr",
file.getParentPath() + file.getName(),
file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC) ? "Allocated File" : "Deleted File")));
return null;
}
for (HashUtility.HashResult hash : newHashResults) {
if (hash.getType().equals(HashUtility.HashType.MD5)) {
file.setMd5Hash(hash.getValue());
} else if (hash.getType().equals(HashUtility.HashType.SHA256)) {
file.setSha256Hash(hash.getValue());
}
}
long delta = (System.currentTimeMillis() - calcstart);
totals.totalCalctime.addAndGet(delta);
}
/**

View File

@ -198,7 +198,6 @@ final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter {
// for extracted virtual machines there is no manifest XML file to read data source ID from so use parent data source ID.
// ingest the data sources
ingestVirtualMachineImage(Paths.get(folder, file));
logger.log(Level.INFO, "Ingest complete for virtual machine file {0} in folder {1}", new Object[]{file, folder}); //NON-NLS
} catch (InterruptedException ex) {
logger.log(Level.INFO, "Interrupted while ingesting virtual machine file " + file + " in folder " + folder, ex); //NON-NLS
} catch (IOException ex) {
@ -287,8 +286,8 @@ final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter {
}
/*
* If the image was added, analyze it with the ingest modules for this
* ingest context.
* If the image was added, start analysis on it with the ingest modules for this
* ingest context. Note that this does not wait for ingest to complete.
*/
if (!dspCallback.vmDataSources.isEmpty()) {
Case.getCurrentCaseThrows().notifyDataSourceAdded(dspCallback.vmDataSources.get(0), taskId);
@ -300,7 +299,7 @@ final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter {
IngestServices.getInstance().postMessage(IngestMessage.createMessage(IngestMessage.MessageType.INFO,
VMExtractorIngestModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.addedVirtualMachineImage.message", vmFile.toString())));
IngestManager.getInstance().queueIngestJob(dataSourceContent, ingestJobSettings);
IngestManager.getInstance().beginIngestJob(dataSourceContent, ingestJobSettings);
} else {
Case.getCurrentCaseThrows().notifyFailedAddingDataSource(taskId);
}

View File

@ -1117,7 +1117,7 @@ public class PortableCaseReportModule implements ReportModule {
newContent = portableSkCase.addLocalFile(abstractFile.getName(), relativePath, abstractFile.getSize(),
abstractFile.getCtime(), abstractFile.getCrtime(), abstractFile.getAtime(), abstractFile.getMtime(),
abstractFile.getMd5Hash(), abstractFile.getKnown(), abstractFile.getMIMEType(),
abstractFile.getMd5Hash(), abstractFile.getSha256Hash(), abstractFile.getKnown(), abstractFile.getMIMEType(),
true, TskData.EncodingType.NONE,
newParent, trans);
} catch (IOException ex) {

View File

@ -228,12 +228,19 @@ public class STIXReportModule implements GeneralReportModule {
*/
private STIXPackage loadSTIXFile(String stixFileName) throws JAXBException {
// Create STIXPackage object from xml.
File file = new File(stixFileName);
JAXBContext jaxbContext = JAXBContext.newInstance("org.mitre.stix.stix_1:org.mitre.stix.common_1:org.mitre.stix.indicator_2:" //NON-NLS
+ "org.mitre.cybox.objects:org.mitre.cybox.cybox_2:org.mitre.cybox.common_2"); //NON-NLS
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
STIXPackage stix = (STIXPackage) jaxbUnmarshaller.unmarshal(file);
return stix;
// See JIRA-6958 for details about class loading and jaxb.
ClassLoader original = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(STIXReportModule.class.getClassLoader());
File file = new File(stixFileName);
JAXBContext jaxbContext = JAXBContext.newInstance("org.mitre.stix.stix_1:org.mitre.stix.common_1:org.mitre.stix.indicator_2:" //NON-NLS
+ "org.mitre.cybox.objects:org.mitre.cybox.cybox_2:org.mitre.cybox.common_2"); //NON-NLS
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
STIXPackage stix = (STIXPackage) jaxbUnmarshaller.unmarshal(file);
return stix;
} finally {
Thread.currentThread().setContextClassLoader(original);
}
}
/**

View File

@ -1 +1,3 @@
Netbeans platform does not properly scope classloaders while running qa-functional test code. The result is that NoClassDefError's occur in instances where an external jar (i.e. importing a class from common-io) is referenced in test code and the same external jar is referenced in multiple NBM's. Importing from external jars in qa-functional should be avoided. See jira issue 6954 for more information.
Netbeans platform does not properly scope classloaders while running qa-functional test code. The result is that NoClassDefError's occur in instances where an external jar (i.e. importing a class from common-io) is referenced in test code and the same external jar is referenced in multiple NBM's. Importing from external jars in qa-functional should be avoided. See jira issue 6954 for more information.
Many of the functional tests require external data sources. The ant target 'getTestDataFiles' must be run successfully to download the files. This should occur as a part of the 'test-init' ant target in build.xml.

View File

@ -1256,7 +1256,8 @@ public class CentralRepoDatamodelTest extends TestCase {
List<CorrelationAttributeInstance.Type> types = CentralRepository.getInstance().getDefinedCorrelationTypes();
// We expect 11 total - 10 default and the custom one made earlier
assertTrue("getDefinedCorrelationTypes returned " + types.size() + " entries - expected 11", types.size() == 11);
// Note: this test will need to be updated based on the current default items defined in the correlation_types table
assertTrue("getDefinedCorrelationTypes returned " + types.size() + " entries - expected 28", types.size() == 28);
} catch (CentralRepoException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex.getMessage());
@ -1267,7 +1268,8 @@ public class CentralRepoDatamodelTest extends TestCase {
List<CorrelationAttributeInstance.Type> types = CentralRepository.getInstance().getEnabledCorrelationTypes();
// We expect 10 - the custom type is disabled
assertTrue("getDefinedCorrelationTypes returned " + types.size() + " enabled entries - expected 10", types.size() == 10);
// Note: this test will need to be updated based on the current default items defined in the correlation_types table
assertTrue("getDefinedCorrelationTypes returned " + types.size() + " enabled entries - expected 27", types.size() == 27);
} catch (CentralRepoException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex.getMessage());
@ -1278,7 +1280,8 @@ public class CentralRepoDatamodelTest extends TestCase {
List<CorrelationAttributeInstance.Type> types = CentralRepository.getInstance().getSupportedCorrelationTypes();
// We expect 10 - the custom type is not supported
assertTrue("getDefinedCorrelationTypes returned " + types.size() + " supported entries - expected 10", types.size() == 10);
// Note: this test will need to be updated based on the current default items defined in the correlation_types table
assertTrue("getDefinedCorrelationTypes returned " + types.size() + " supported entries - expected 27", types.size() == 27);
} catch (CentralRepoException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex.getMessage());

View File

@ -219,7 +219,9 @@ class InterCaseTestUtils {
// kitchenSink.add(keywordSearchTemplate);
this.kitchenShink = new IngestJobSettings(InterCaseTestUtils.class.getCanonicalName(), IngestType.ALL_MODULES, kitchenSink);
}
void setupCorrelationTypes() {
try {
Collection<CorrelationAttributeInstance.Type> types = CentralRepository.getInstance().getDefinedCorrelationTypes();

View File

@ -259,7 +259,7 @@ public class IngestFileFiltersTest extends NbTestCase {
IngestUtils.runIngestJob(currentCase.getDataSources(), ingestJobSettings);
FileManager fileManager = currentCase.getServices().getFileManager();
List<AbstractFile> results = fileManager.findFiles("%%");
assertEquals(70, results.size());
assertEquals(71, results.size());
int carvedJpgGifFiles = 0;
for (AbstractFile file : results) {
if (file.getNameExtension().equalsIgnoreCase("jpg") || file.getNameExtension().equalsIgnoreCase("gif")) { //Unalloc file and .jpg files in dir1, dir2, $CarvedFiles, root directory should have MIME type

View File

@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datasourcesummary.datamodel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.junit.Assert;
import org.junit.Rule;
@ -35,6 +36,7 @@ import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.autopsy.testutils.TskMockUtils;
import static org.mockito.Mockito.*;
import org.sleuthkit.autopsy.testutils.RandomizationUtils;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE;
@ -42,7 +44,7 @@ import org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALU
/**
* Unit tests for DataSourceInfoUtilities.getArtifacts
*/
public class GetArtifactsTest {
public class DataSourceInfoUtilitiesTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@ -145,38 +147,13 @@ public class GetArtifactsTest {
List<BlackboardArtifact> toRet = new ArrayList<>();
for (int i = 0; i < values.size(); i++) {
toRet.add(TskMockUtils.mockArtifact(new BlackboardArtifact.Type(artifactType), 1000 + i, dataSource,
toRet.add(TskMockUtils.getArtifact(new BlackboardArtifact.Type(artifactType), 1000 + i, dataSource,
attrMaker.make(attrType, "TEST SOURCE", values.get(i))));
}
return toRet;
}
/**
* Returns list in 0, n-1, 1, n-2 ... order. Deterministic so same results
* each time, but not in original order.
*
* @return Mixed up list.
*/
private <T> List<T> getMixedUp(List<T> list) {
int forward = 0;
int backward = list.size() - 1;
List<T> newList = new ArrayList<>();
while (forward <= backward) {
newList.add(list.get(forward));
if (forward < backward) {
newList.add(list.get(backward));
}
forward++;
backward--;
}
return newList;
}
/**
* Does a basic test passing a list of generated artifacts in mixed up order
* to DataSourceInfoUtilities.getArtifacts and expecting a sorted list to be
@ -194,11 +171,11 @@ public class GetArtifactsTest {
private <T> void testSorted(ARTIFACT_TYPE artifactType, ATTRIBUTE_TYPE attrType, List<T> values,
AttrMaker<T> attrMaker, SortOrder sortOrder, int count) throws TskCoreException {
DataSource dataSource = TskMockUtils.mockDataSource(1);
DataSource dataSource = TskMockUtils.getDataSource(1);
List<BlackboardArtifact> sortedArtifacts = getArtifacts(artifactType, new BlackboardAttribute.Type(attrType),
dataSource, values, attrMaker);
List<BlackboardArtifact> mixedUpArtifacts = getMixedUp(sortedArtifacts);
List<BlackboardArtifact> mixedUpArtifacts = RandomizationUtils.getMixedUp(sortedArtifacts);
List<BlackboardArtifact> expectedArtifacts = count == 0
? sortedArtifacts
@ -250,17 +227,17 @@ public class GetArtifactsTest {
}
@Test
public void testSortAscending() throws TskCoreException {
public void getArtifacts_sortAscending() throws TskCoreException {
testAscDesc(SortOrder.ASCENDING);
}
@Test
public void testSortDescending() throws TskCoreException {
public void getArtifacts_sortDescending() throws TskCoreException {
testAscDesc(SortOrder.DESCENDING);
}
@Test
public void testLimits() throws TskCoreException {
public void getArtifacts_limits() throws TskCoreException {
List<Integer> integers = Arrays.asList(22, 31, 42, 50, 60);
testSorted(ARTIFACT_TYPE.TSK_PROG_RUN, ATTRIBUTE_TYPE.TSK_COUNT, integers, BlackboardAttribute::new, SortOrder.ASCENDING, 3);
testSorted(ARTIFACT_TYPE.TSK_PROG_RUN, ATTRIBUTE_TYPE.TSK_COUNT, integers, BlackboardAttribute::new, SortOrder.ASCENDING, 5);
@ -281,11 +258,11 @@ public class GetArtifactsTest {
private <T> void testFailOnBadAttrType(BlackboardArtifact.Type artifactType, BlackboardAttribute.Type attributeType, T val,
AttrMaker<T> attrMaker) throws TskCoreException {
DataSource dataSource = TskMockUtils.mockDataSource(1);
DataSource dataSource = TskMockUtils.getDataSource(1);
List<BlackboardArtifact> artifacts = Arrays.asList(
TskMockUtils.mockArtifact(artifactType, 2, dataSource, attrMaker.make(attributeType, "TEST SOURCE", val)),
TskMockUtils.mockArtifact(artifactType, 3, dataSource, attrMaker.make(attributeType, "TEST SOURCE", val))
TskMockUtils.getArtifact(artifactType, 2, dataSource, attrMaker.make(attributeType, "TEST SOURCE", val)),
TskMockUtils.getArtifact(artifactType, 3, dataSource, attrMaker.make(attributeType, "TEST SOURCE", val))
);
test(artifactType,
dataSource,
@ -299,7 +276,7 @@ public class GetArtifactsTest {
}
@Test
public void testFailOnJson() throws TskCoreException {
public void getArtifacts_failOnJson() throws TskCoreException {
testFailOnBadAttrType(
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_GPS_ROUTE),
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_WAYPOINTS),
@ -308,7 +285,7 @@ public class GetArtifactsTest {
}
@Test
public void testFailOnBytes() throws TskCoreException {
public void getArtifacts_failOnBytes() throws TskCoreException {
testFailOnBadAttrType(
new BlackboardArtifact.Type(999, "BYTE_ARRAY_TYPE", "Byte Array Type"),
new BlackboardAttribute.Type(999, "BYTE_ARR_ATTR_TYPE", "Byte Array Attribute Type", TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.BYTE),
@ -317,20 +294,20 @@ public class GetArtifactsTest {
}
@Test
public void testPurgeAttrNotPresent() throws TskCoreException {
public void getArtifacts_purgeAttrNotPresent() throws TskCoreException {
long day = 24 * 60 * 60;
DataSource dataSource = TskMockUtils.mockDataSource(1);
DataSource dataSource = TskMockUtils.getDataSource(1);
BlackboardArtifact.Type ART_TYPE = new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_PROG_RUN);
BlackboardArtifact mock1 = TskMockUtils.mockArtifact(ART_TYPE, 10, dataSource,
BlackboardArtifact mock1 = TskMockUtils.getArtifact(ART_TYPE, 10, dataSource,
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT, "TEST SOURCE", 5),
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, "TEST SOURCE", day));
BlackboardArtifact mock2 = TskMockUtils.mockArtifact(ART_TYPE, 20, dataSource,
BlackboardArtifact mock2 = TskMockUtils.getArtifact(ART_TYPE, 20, dataSource,
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT, "TEST SOURCE", 6));
BlackboardArtifact mock3 = TskMockUtils.mockArtifact(ART_TYPE, 30, dataSource,
BlackboardArtifact mock3 = TskMockUtils.getArtifact(ART_TYPE, 30, dataSource,
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT, "TEST SOURCE", 7),
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, "TEST SOURCE", 3 * day));
@ -346,20 +323,20 @@ public class GetArtifactsTest {
}
@Test
public void testMultAttrsPresent() throws TskCoreException {
public void getArtifacts_multipleAttrsPresent() throws TskCoreException {
long day = 24 * 60 * 60;
DataSource dataSource = TskMockUtils.mockDataSource(1);
DataSource dataSource = TskMockUtils.getDataSource(1);
BlackboardArtifact.Type ART_TYPE = new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_PROG_RUN);
BlackboardArtifact mock1 = TskMockUtils.mockArtifact(ART_TYPE, 10, dataSource,
BlackboardArtifact mock1 = TskMockUtils.getArtifact(ART_TYPE, 10, dataSource,
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT, "TEST SOURCE", 7),
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, "TEST SOURCE", day));
BlackboardArtifact mock2 = TskMockUtils.mockArtifact(ART_TYPE, 20, dataSource,
BlackboardArtifact mock2 = TskMockUtils.getArtifact(ART_TYPE, 20, dataSource,
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT, "TEST SOURCE", 6));
BlackboardArtifact mock3 = TskMockUtils.mockArtifact(ART_TYPE, 30, dataSource,
BlackboardArtifact mock3 = TskMockUtils.getArtifact(ART_TYPE, 30, dataSource,
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT, "TEST SOURCE", 5),
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, "TEST SOURCE", 3 * day));
@ -375,9 +352,9 @@ public class GetArtifactsTest {
}
@Test
public void testTskCoreExceptionThrown() throws TskCoreException {
public void getArtifacts_tskCoreExceptionThrown() throws TskCoreException {
test(new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_ACCOUNT),
TskMockUtils.mockDataSource(1),
TskMockUtils.getDataSource(1),
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE),
SortOrder.ASCENDING,
0,
@ -388,9 +365,9 @@ public class GetArtifactsTest {
}
@Test
public void testThrowOnLessThan0() throws TskCoreException {
public void getArtifacts_throwOnLessThan0() throws TskCoreException {
test(new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_ACCOUNT),
TskMockUtils.mockDataSource(1),
TskMockUtils.getDataSource(1),
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE),
SortOrder.ASCENDING,
-1,
@ -401,9 +378,9 @@ public class GetArtifactsTest {
}
@Test
public void testEmptyListReturned() throws TskCoreException {
public void getArtifacts_emptyListReturned() throws TskCoreException {
test(new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_ACCOUNT),
TskMockUtils.mockDataSource(1),
TskMockUtils.getDataSource(1),
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE),
SortOrder.ASCENDING,
0,
@ -412,4 +389,76 @@ public class GetArtifactsTest {
Collections.emptyList(),
null);
}
/**
* Retrieves the value of an artifact.
*/
private interface GetAttrVal<T> {
/**
* A method for retrieving the value of an artifact.
* @param artifact The artifact.
* @param type The type of attribute.
* @return The value.
*/
T getOrNull(BlackboardArtifact artifact, BlackboardAttribute.Type type);
}
private <T> void testNullAttrValue(String id, GetAttrVal<T> getter, ARTIFACT_TYPE artifactType,
ATTRIBUTE_TYPE attributeType, T nonNullVal)
throws TskCoreException {
BlackboardAttribute.Type attrType = new BlackboardAttribute.Type(attributeType);
BlackboardArtifact.Type artType = new BlackboardArtifact.Type(artifactType);
BlackboardArtifact noAttribute = TskMockUtils.getArtifact(artType, 1000,
TskMockUtils.getDataSource(1), new ArrayList<>());
T nullValue = getter.getOrNull(noAttribute, attrType);
Assert.assertNull(String.format("Expected function %s to return null when no attribute present", id), nullValue);
BlackboardArtifact hasAttribute = TskMockUtils.getArtifact(artType, 1000,
TskMockUtils.getDataSource(1), TskMockUtils.getAttribute(attributeType, nonNullVal));
T valueReceived = getter.getOrNull(hasAttribute, attrType);
Assert.assertEquals(String.format("%s did not return the same value present in the attribute", id), nonNullVal, valueReceived);
}
@Test
public void getStringOrNull_handlesNull() throws TskCoreException {
testNullAttrValue("getStringOrNull", DataSourceInfoUtilities::getStringOrNull,
ARTIFACT_TYPE.TSK_ACCOUNT, ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE, "Skype");
}
@Test
public void getIntOrNull_handlesNull() throws TskCoreException {
testNullAttrValue("getIntOrNull", DataSourceInfoUtilities::getIntOrNull,
ARTIFACT_TYPE.TSK_PROG_RUN, ATTRIBUTE_TYPE.TSK_COUNT, 16);
}
@Test
public void getLongOrNull_handlesNull() throws TskCoreException {
testNullAttrValue("getLongOrNull", DataSourceInfoUtilities::getLongOrNull,
ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT, ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, 1001L);
}
@Test
public void getDateOrNull_handlesNull() throws TskCoreException {
BlackboardAttribute.Type attrType = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME);
BlackboardArtifact.Type artType = new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING);
long dateTime = 24 * 60 * 60 * 42;
BlackboardArtifact noAttribute = TskMockUtils.getArtifact(artType, 1000,
TskMockUtils.getDataSource(1), new ArrayList<>());
Date nullValue = DataSourceInfoUtilities.getDateOrNull(noAttribute, attrType);
Assert.assertNull(nullValue);
BlackboardArtifact hasAttribute = TskMockUtils.getArtifact(artType, 1000,
TskMockUtils.getDataSource(1), TskMockUtils.getAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, dateTime));
Date curVal = DataSourceInfoUtilities.getDateOrNull(hasAttribute, attrType);
Assert.assertEquals(dateTime, curVal.getTime() / 1000);
}
}

View File

@ -0,0 +1,57 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.datasourcesummary.datamodel;
import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.sleuthkit.datamodel.Blackboard;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Common tools for mocking in data source summary.
*/
public final class DataSourceSummaryMockUtils {
/**
* Creates a pair of a mock SleuthkitCase and mock Blackboard.
*
* @param returnArr The return result when calling getArtifacts on the
* blackboard.
*
* @return The pair of a mock SleuthkitCase and mock Blackboard.
*
* @throws TskCoreException
*/
static Pair<SleuthkitCase, Blackboard> getArtifactsTSKMock(List<BlackboardArtifact> returnArr) throws TskCoreException {
SleuthkitCase mockCase = mock(SleuthkitCase.class);
Blackboard mockBlackboard = mock(Blackboard.class);
when(mockCase.getBlackboard()).thenReturn(mockBlackboard);
when(mockBlackboard.getArtifacts(anyInt(), anyLong())).thenReturn(returnArr);
return Pair.of(mockCase, mockBlackboard);
}
private DataSourceSummaryMockUtils() {
}
}

View File

@ -0,0 +1,681 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.datasourcesummary.datamodel;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.Assert;
import static org.junit.Assert.fail;
import org.junit.Test;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentAttachmentDetails;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentDownloadDetails;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentFileDetails;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
import org.sleuthkit.autopsy.testutils.RandomizationUtils;
import org.sleuthkit.autopsy.testutils.TskMockUtils;
import org.sleuthkit.datamodel.Blackboard;
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.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Tests for RecentFilesSummaryTest
*/
public class RecentFilesSummaryTest {
/**
* An interface for calling methods in RecentFilesSummary in a uniform
* manner.
*/
private interface RecentFilesMethod<T> {
/**
* Means of acquiring data from a method in RecentFilesSummary.
*
* @param recentFilesSummary The RecentFilesSummary object.
* @param dataSource The datasource.
* @param count The number of items to retrieve.
*
* @return The method's return data.
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
*/
List<T> fetch(RecentFilesSummary recentFilesSummary, DataSource dataSource, int count)
throws SleuthkitCaseProviderException, TskCoreException;
}
private static final RecentFilesMethod<RecentFileDetails> RECENT_DOCS_FUNCT
= (summary, dataSource, count) -> summary.getRecentlyOpenedDocuments(dataSource, count);
private static final RecentFilesMethod<RecentDownloadDetails> RECENT_DOWNLOAD_FUNCT
= (summary, dataSource, count) -> summary.getRecentDownloads(dataSource, count);
private static final RecentFilesMethod<RecentAttachmentDetails> RECENT_ATTACHMENT_FUNCT
= (summary, dataSource, count) -> summary.getRecentAttachments(dataSource, count);
/**
* If -1 count passed to method, should throw IllegalArgumentException.
*
* @param method The method to call.
* @param methodName The name of the metho
*
* @throws TskCoreException
* @throws SleuthkitCaseProviderException
*/
private <T> void testNonPositiveCount_ThrowsError(RecentFilesMethod<T> method, String methodName)
throws TskCoreException, SleuthkitCaseProviderException {
Pair<SleuthkitCase, Blackboard> casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(null);
DataSource dataSource = TskMockUtils.getDataSource(1);
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
try {
method.fetch(summary, dataSource, -1);
fail("Expected method " + methodName + " to fail on negative count.");
} catch (IllegalArgumentException ignored) {
verify(casePair.getRight(),
never().description("Expected negative count for " + methodName + " to not call any methods in SleuthkitCase."))
.getArtifacts(anyInt(), anyLong());
}
}
@Test
public void getRecentlyOpenedDocuments_nonPositiveCount_ThrowsError() throws TskCoreException, SleuthkitCaseProviderException {
testNonPositiveCount_ThrowsError(RECENT_DOCS_FUNCT, "getRecentlyOpenedDocuments");
}
@Test
public void getRecentDownloads_nonPositiveCount_ThrowsError() throws TskCoreException, SleuthkitCaseProviderException {
testNonPositiveCount_ThrowsError(RECENT_DOWNLOAD_FUNCT, "getRecentDownloads");
}
@Test
public void getRecentAttachments_nonPositiveCount_ThrowsError() throws TskCoreException, SleuthkitCaseProviderException {
testNonPositiveCount_ThrowsError(RECENT_ATTACHMENT_FUNCT, "getRecentAttachments");
}
/**
* Tests that if no data source provided, an empty list is returned and
* SleuthkitCase isn't called.
*
* @param recentFilesMethod The method to call.
* @param methodName The name of the method
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
*/
private <T> void testNoDataSource_ReturnsEmptyList(RecentFilesMethod<T> recentFilesMethod, String methodName)
throws SleuthkitCaseProviderException, TskCoreException {
Pair<SleuthkitCase, Blackboard> casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(null);
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
List<? extends T> items = recentFilesMethod.fetch(summary, null, 10);
Assert.assertNotNull("Expected method " + methodName + " to return an empty list.", items);
Assert.assertEquals("Expected method " + methodName + " to return an empty list.", 0, items.size());
verify(casePair.getRight(),
never().description("Expected null datasource for " + methodName + " to not call any methods in SleuthkitCase."))
.getArtifacts(anyInt(), anyLong());
}
@Test
public void getRecentlyOpenedDocuments_noDataSource_ReturnsEmptyList() throws TskCoreException, SleuthkitCaseProviderException {
testNoDataSource_ReturnsEmptyList(RECENT_DOCS_FUNCT, "getRecentlyOpenedDocuments");
}
@Test
public void getRecentDownloads_noDataSource_ReturnsEmptyList() throws TskCoreException, SleuthkitCaseProviderException {
testNoDataSource_ReturnsEmptyList(RECENT_DOWNLOAD_FUNCT, "getRecentDownloads");
}
@Test
public void getRecentAttachments_noDataSource_ReturnsEmptyList() throws TskCoreException, SleuthkitCaseProviderException {
testNonPositiveCount_ThrowsError(RECENT_ATTACHMENT_FUNCT, "getRecentAttachments");
}
/**
* If SleuthkitCase returns no results, an empty list is returned.
*
* @param recentFilesMethod The method to call.
* @param methodName The name of the method.
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
*/
private <T> void testNoReturnedResults_ReturnsEmptyList(RecentFilesMethod<T> recentFilesMethod, String methodName)
throws SleuthkitCaseProviderException, TskCoreException {
Pair<SleuthkitCase, Blackboard> casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(Collections.emptyList());
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
DataSource dataSource = TskMockUtils.getDataSource(1);
List<? extends T> items = recentFilesMethod.fetch(summary, dataSource, 10);
Assert.assertNotNull("Expected method " + methodName + " to return an empty list.", items);
Assert.assertEquals("Expected method " + methodName + " to return an empty list.", 0, items.size());
verify(casePair.getRight(),
times(1).description("Expected " + methodName + " to call Blackboard once."))
.getArtifacts(anyInt(), anyLong());
}
@Test
public void getRecentlyOpenedDocuments_noReturnedResults_ReturnsEmptyList() throws TskCoreException, SleuthkitCaseProviderException {
testNoReturnedResults_ReturnsEmptyList(RECENT_DOCS_FUNCT, "getRecentlyOpenedDocuments");
}
@Test
public void getRecentDownloads_noReturnedResults_ReturnsEmptyList() throws TskCoreException, SleuthkitCaseProviderException {
testNoReturnedResults_ReturnsEmptyList(RECENT_DOWNLOAD_FUNCT, "getRecentDownloads");
}
@Test
public void getRecentAttachments_testNoDataSource_ReturnsEmptyList() throws TskCoreException, SleuthkitCaseProviderException {
testNoReturnedResults_ReturnsEmptyList(RECENT_ATTACHMENT_FUNCT, "getRecentAttachments");
}
private static final long DAY_SECONDS = 24 * 60 * 60;
/**
* A means of creating a number representing seconds from epoch where the
* lower the idx, the more recent the time.
*/
private static final Function<Integer, Long> dateTimeRetriever = (idx) -> (365 - idx) * DAY_SECONDS + 1;
/**
* Gets a mock BlackboardArtifact.
*
* @param ds The data source to which the artifact belongs.
* @param artifactId The artifact id.
* @param artType The artifact type.
* @param attributeArgs The mapping of attribute type to value for each
* attribute in the artifact.
*
* @return The mock artifact.
*/
private BlackboardArtifact getArtifact(DataSource ds, long artifactId, ARTIFACT_TYPE artType, List<Pair<ATTRIBUTE_TYPE, Object>> attributeArgs) {
try {
List<BlackboardAttribute> attributes = attributeArgs.stream()
.filter((arg) -> arg != null && arg.getLeft() != null && arg.getRight() != null)
.map((arg) -> {
return TskMockUtils.getAttribute(arg.getLeft(), arg.getRight());
})
.collect(Collectors.toList());
return TskMockUtils.getArtifact(new BlackboardArtifact.Type(artType), artifactId, ds, attributes);
} catch (TskCoreException ex) {
fail("There was an error mocking an artifact.");
return null;
}
}
/**
* Returns a mock artifact for getRecentlyOpenedDocuments.
*
* @param ds The datasource for the artifact.
* @param artifactId The artifact id.
* @param dateTime The time in seconds from epoch.
* @param path The path for the document.
*
* @return The mock artifact with pertinent attributes.
*/
private BlackboardArtifact getRecentDocumentArtifact(DataSource ds, long artifactId, Long dateTime, String path) {
return getArtifact(ds, artifactId, ARTIFACT_TYPE.TSK_RECENT_OBJECT, Arrays.asList(
Pair.of(ATTRIBUTE_TYPE.TSK_DATETIME, dateTime),
Pair.of(ATTRIBUTE_TYPE.TSK_PATH, path)
));
}
@Test
public void getRecentlyOpenedDocuments_sortedByDateTimeAndLimited() throws SleuthkitCaseProviderException, TskCoreException {
Function<Integer, String> pathRetriever = (idx) -> "/path/to/downloads/" + idx;
DataSource dataSource = TskMockUtils.getDataSource(1);
int countRequest = 10;
for (int countToGenerate : new int[]{1, 9, 10, 11}) {
// generate artifacts for each artifact
List<BlackboardArtifact> artifacts = new ArrayList<>();
for (int idx = 0; idx < countToGenerate; idx++) {
BlackboardArtifact artifact = getRecentDocumentArtifact(dataSource,
1000 + idx, dateTimeRetriever.apply(idx), pathRetriever.apply(idx));
artifacts.add(artifact);
}
// run through method
Pair<SleuthkitCase, Blackboard> casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(RandomizationUtils.getMixedUp(artifacts));
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
List<RecentFileDetails> results = summary.getRecentlyOpenedDocuments(dataSource, countRequest);
// verify results
int expectedCount = Math.min(countRequest, countToGenerate);
Assert.assertNotNull(results);
Assert.assertEquals(expectedCount, results.size());
for (int i = 0; i < expectedCount; i++) {
Assert.assertEquals(dateTimeRetriever.apply(i), results.get(i).getDateAsLong());
Assert.assertEquals(pathRetriever.apply(i), results.get(i).getPath());
}
}
}
@Test
public void getRecentlyOpenedDocuments_filtersMissingData() throws SleuthkitCaseProviderException, TskCoreException {
DataSource dataSource = TskMockUtils.getDataSource(1);
BlackboardArtifact successItem = getRecentDocumentArtifact(dataSource, 1001, DAY_SECONDS, "/a/path");
BlackboardArtifact nullTime = getRecentDocumentArtifact(dataSource, 1002, null, "/a/path2");
BlackboardArtifact zeroTime = getRecentDocumentArtifact(dataSource, 10021, 0L, "/a/path2a");
List<BlackboardArtifact> artifacts = Arrays.asList(nullTime, zeroTime, successItem);
Pair<SleuthkitCase, Blackboard> casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(RandomizationUtils.getMixedUp(artifacts));
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
List<RecentFileDetails> results = summary.getRecentlyOpenedDocuments(dataSource, 10);
// verify results (only successItem)
Assert.assertNotNull(results);
Assert.assertEquals(1, results.size());
Assert.assertEquals((Long) DAY_SECONDS, results.get(0).getDateAsLong());
Assert.assertTrue("/a/path".equalsIgnoreCase(results.get(0).getPath()));
}
/**
* Creates a mock blackboard artifact for getRecentDownloads.
*
* @param ds The datasource.
* @param artifactId The artifact id.
* @param dateTime The time in seconds from epoch.
* @param domain The domain.
* @param path The path for the download.
*
* @return The mock artifact.
*/
private BlackboardArtifact getRecentDownloadArtifact(DataSource ds, long artifactId, Long dateTime, String domain, String path) {
return getArtifact(ds, artifactId, ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, Arrays.asList(
Pair.of(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, dateTime),
Pair.of(ATTRIBUTE_TYPE.TSK_DOMAIN, domain),
Pair.of(ATTRIBUTE_TYPE.TSK_PATH, path)
));
}
@Test
public void getRecentDownloads_sortedByDateTimeAndLimited() throws SleuthkitCaseProviderException, TskCoreException {
Function<Integer, String> domainRetriever = (idx) -> String.format("www.domain%d.com", idx);
Function<Integer, String> pathRetriever = (idx) -> "/path/to/downloads/doc" + idx + ".pdf";
// run through method
DataSource dataSource = TskMockUtils.getDataSource(1);
int countRequest = 10;
for (int countToGenerate : new int[]{1, 9, 10, 11}) {
// generate artifacts for each artifact
List<BlackboardArtifact> artifacts = new ArrayList<>();
for (int idx = 0; idx < countToGenerate; idx++) {
BlackboardArtifact artifact = getRecentDownloadArtifact(dataSource,
1000 + idx, dateTimeRetriever.apply(idx), domainRetriever.apply(idx),
pathRetriever.apply(idx));
artifacts.add(artifact);
}
// call method
Pair<SleuthkitCase, Blackboard> casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(RandomizationUtils.getMixedUp(artifacts));
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
List<RecentDownloadDetails> results = summary.getRecentDownloads(dataSource, countRequest);
// verify results
int expectedCount = Math.min(countRequest, countToGenerate);
Assert.assertNotNull(results);
Assert.assertEquals(expectedCount, results.size());
for (int i = 0; i < expectedCount; i++) {
Assert.assertEquals(dateTimeRetriever.apply(i), results.get(i).getDateAsLong());
Assert.assertEquals(pathRetriever.apply(i), results.get(i).getPath());
Assert.assertEquals(domainRetriever.apply(i), results.get(i).getWebDomain());
}
}
}
@Test
public void getRecentDownloads_filtersMissingData() throws SleuthkitCaseProviderException, TskCoreException {
DataSource dataSource = TskMockUtils.getDataSource(1);
BlackboardArtifact successItem = getRecentDownloadArtifact(dataSource, 1001, DAY_SECONDS, "domain1.com", "/a/path1");
BlackboardArtifact nullTime = getRecentDownloadArtifact(dataSource, 1002, null, "domain2.com", "/a/path2");
BlackboardArtifact zeroTime = getRecentDownloadArtifact(dataSource, 10021, 0L, "domain2a.com", "/a/path2a");
List<BlackboardArtifact> artifacts = Arrays.asList(nullTime, zeroTime, successItem);
Pair<SleuthkitCase, Blackboard> casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(RandomizationUtils.getMixedUp(artifacts));
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
// call method
List<RecentDownloadDetails> results = summary.getRecentDownloads(dataSource, 10);
// verify results
Assert.assertNotNull(results);
Assert.assertEquals(1, results.size());
Assert.assertEquals((Long) DAY_SECONDS, results.get(0).getDateAsLong());
Assert.assertTrue("/a/path1".equalsIgnoreCase(results.get(0).getPath()));
}
/**
* getRecentAttachments method has special setup conditions. This class
* encapsulates all the SleuthkitCase/BlackboardArtifact setup for on
* possible return item.
*/
private class AttachmentArtifactItem {
private final Integer messageArtifactTypeId;
private final boolean associatedAttrFormed;
private final String emailFrom;
private final Long messageTime;
private final boolean isParent;
private final String fileParentPath;
private final String fileName;
/**
* Constructor with all parameters.
*
* @param messageArtifactTypeId The type id for the artifact or null if
* no message artifact to be created.
* @param emailFrom Who the message is from or null not to
* include attribute.
* @param messageTime Time in seconds from epoch or null not
* to include attribute.
* @param fileParentPath The parent AbstractFile's path value.
* @param fileName The parent AbstractFile's filename
* value.
* @param associatedAttrFormed If false, the TSK_ASSOCIATED_OBJECT
* artifact has no attribute (even though
* it is required).
* @param hasParent Whether or not the artifact has a parent
* AbstractFile.
*/
AttachmentArtifactItem(Integer messageArtifactTypeId, String emailFrom, Long messageTime,
String fileParentPath, String fileName,
boolean associatedAttrFormed, boolean hasParent) {
this.messageArtifactTypeId = messageArtifactTypeId;
this.associatedAttrFormed = associatedAttrFormed;
this.emailFrom = emailFrom;
this.messageTime = messageTime;
this.isParent = hasParent;
this.fileParentPath = fileParentPath;
this.fileName = fileName;
}
/**
* Convenience constructor where defaults of required attributes and
* SleuthkitCase assumed.
*
* @param messageArtifactTypeId The type id for the artifact or null if
* no message artifact to be created.
* @param emailFrom Who the message is from or null not to
* include attribute.
* @param messageTime Time in seconds from epoch or null not
* to include attribute.
* @param fileParentPath The parent AbstractFile's path value.
* @param fileName The parent AbstractFile's filename
* value.
*/
AttachmentArtifactItem(Integer messageArtifactTypeId, String emailFrom, Long messageTime, String fileParentPath, String fileName) {
this(messageArtifactTypeId, emailFrom, messageTime, fileParentPath, fileName, true, true);
}
boolean isAssociatedAttrFormed() {
return associatedAttrFormed;
}
String getEmailFrom() {
return emailFrom;
}
Long getMessageTime() {
return messageTime;
}
boolean hasParent() {
return isParent;
}
String getFileParentPath() {
return fileParentPath;
}
String getFileName() {
return fileName;
}
Integer getMessageArtifactTypeId() {
return messageArtifactTypeId;
}
}
/**
* Sets up the associated artifact message for the TSK_ASSOCIATED_OBJECT.
*
* @param artifacts The mapping of artifact id to artifact.
* @param item The record to setup.
* @param dataSource The datasource.
* @param associatedId The associated attribute id.
* @param artifactId The artifact id.
*
* @return The associated Artifact blackboard attribute.
*
* @throws TskCoreException
*/
private BlackboardAttribute setupAssociatedMessage(Map<Long, BlackboardArtifact> artifacts, AttachmentArtifactItem item,
DataSource dataSource, Long associatedId, Long artifactId) throws TskCoreException {
BlackboardAttribute associatedAttr = TskMockUtils.getAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, associatedId);
if (item.getMessageArtifactTypeId() == null) {
return associatedAttr;
}
// find the artifact type or null if not found
ARTIFACT_TYPE messageType = Stream.of(ARTIFACT_TYPE.values())
.filter((artType) -> artType.getTypeID() == item.getMessageArtifactTypeId())
.findFirst()
.orElse(null);
// if there is a message type, create the artifact
if (messageType != null) {
List<BlackboardAttribute> attributes = new ArrayList<>();
if (item.getEmailFrom() != null) {
attributes.add(TskMockUtils.getAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_FROM, item.getEmailFrom()));
}
if (item.getMessageTime() != null) {
attributes.add(TskMockUtils.getAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_SENT, item.getMessageTime()));
}
artifacts.put(associatedId, TskMockUtils.getArtifact(
new BlackboardArtifact.Type(messageType), artifactId, dataSource, attributes));
}
return associatedAttr;
}
/**
* Since getRecentAttachments does not simply query one type of artifact and
* return results, this method sets up a mock SleuthkitCase and Blackboard
* to return pertinent data.
*
* @param items Each attachment item where each item could represent a
* return result if fully formed.
*
* @return The mock SleuthkitCase and Blackboard.
*/
private Pair<SleuthkitCase, Blackboard> getRecentAttachmentArtifactCase(List<AttachmentArtifactItem> items) {
SleuthkitCase skCase = mock(SleuthkitCase.class);
Blackboard blackboard = mock(Blackboard.class);
when(skCase.getBlackboard()).thenReturn(blackboard);
DataSource dataSource = TskMockUtils.getDataSource(1);
long objIdCounter = 100;
Map<Long, BlackboardArtifact> artifacts = new HashMap<>();
try {
for (AttachmentArtifactItem item : items) {
BlackboardAttribute associatedAttr = null;
// if the associated attribute is fully formed,
// create the associated attribute and related artifact
if (item.isAssociatedAttrFormed()) {
associatedAttr = setupAssociatedMessage(artifacts, item, dataSource, ++objIdCounter, ++objIdCounter);
}
// create the content parent for the associated object if one should be present
Content parent = (item.hasParent())
? TskMockUtils.getAbstractFile(++objIdCounter, item.getFileParentPath(), item.getFileName())
: null;
Long associatedId = ++objIdCounter;
artifacts.put(associatedId, TskMockUtils.getArtifact(
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT),
parent, associatedId, dataSource, associatedAttr));
}
// set up the blackboard to return artifacts that match the type id.
when(blackboard.getArtifacts(anyInt(), anyLong())).thenAnswer((inv) -> {
Object[] args = inv.getArguments();
int artifactType = (Integer) args[0];
return artifacts.values().stream()
.filter(art -> art.getArtifactTypeID() == artifactType)
.collect(Collectors.toList());
});
// also set up the sleuthkitcase to return the artifact with the matching id or null.
when(skCase.getBlackboardArtifact(anyLong())).thenAnswer((inv2) -> {
Object[] args2 = inv2.getArguments();
long id = (Long) args2[0];
return artifacts.get(id);
});
return Pair.of(skCase, blackboard);
} catch (TskCoreException ex) {
fail("There was an error while creating SleuthkitCase for getRecentAttachments");
return null;
}
}
@Test
public void getRecentAttachments_sortedByDateTimeAndLimited() throws SleuthkitCaseProviderException, TskCoreException {
DataSource dataSource = TskMockUtils.getDataSource(1);
// a deterministic means of transforming an index into a particular attribute type so that they can be created
// and compared on return
Function<Integer, String> emailFromRetriever = (idx) -> String.format("person%d@basistech.com", idx);
Function<Integer, String> pathRetriever = (idx) -> "/path/to/attachment/" + idx;
Function<Integer, String> fileNameRetriever = (idx) -> String.format("%d-filename.png", idx);
int countRequest = 10;
for (int countToGenerate : new int[]{1, 9, 10, 11}) {
// set up the items in the sleuthkit case
List<AttachmentArtifactItem> items = IntStream.range(0, countToGenerate)
.mapToObj((idx) -> new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(),
emailFromRetriever.apply(idx), dateTimeRetriever.apply(idx),
pathRetriever.apply(idx), fileNameRetriever.apply(idx)))
.collect(Collectors.toList());
List<AttachmentArtifactItem> mixedUpItems = RandomizationUtils.getMixedUp(items);
Pair<SleuthkitCase, Blackboard> casePair = getRecentAttachmentArtifactCase(mixedUpItems);
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
// retrieve results
List<RecentAttachmentDetails> results = summary.getRecentAttachments(dataSource, countRequest);
// verify results
int expectedCount = Math.min(countRequest, countToGenerate);
Assert.assertNotNull(results);
Assert.assertEquals(expectedCount, results.size());
for (int i = 0; i < expectedCount; i++) {
RecentAttachmentDetails result = results.get(i);
Assert.assertEquals(dateTimeRetriever.apply(i), result.getDateAsLong());
Assert.assertTrue(emailFromRetriever.apply(i).equalsIgnoreCase(result.getSender()));
Assert.assertTrue(Paths.get(pathRetriever.apply(i), fileNameRetriever.apply(i)).toString()
.equalsIgnoreCase(result.getPath()));
}
}
}
@Test
public void getRecentAttachments_filterData() throws SleuthkitCaseProviderException, TskCoreException {
// setup data
DataSource dataSource = TskMockUtils.getDataSource(1);
AttachmentArtifactItem successItem = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(),
"person@sleuthkit.com", DAY_SECONDS, "/parent/path", "msg.pdf");
AttachmentArtifactItem successItem2 = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(),
"person_on_skype", DAY_SECONDS + 1, "/parent/path/to/skype", "skype.png");
AttachmentArtifactItem wrongArtType = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_CALLLOG.getTypeID(),
"5555675309", DAY_SECONDS + 2, "/path/to/callog/info", "callog.dat");
AttachmentArtifactItem missingTimeStamp = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(),
"person2@sleuthkit.com", null, "/parent/path", "msg2.pdf");
AttachmentArtifactItem zeroTimeStamp = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(),
"person2a@sleuthkit.com", 0L, "/parent/path", "msg2a.png");
AttachmentArtifactItem noParentFile = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(),
"person4@sleuthkit.com", DAY_SECONDS + 4, "/parent/path", "msg4.jpg", true, false);
AttachmentArtifactItem noAssocAttr = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(),
"person3@sleuthkit.com", DAY_SECONDS + 5, "/parent/path", "msg5.gif", false, true);
AttachmentArtifactItem missingAssocArt = new AttachmentArtifactItem(null,
"person3@sleuthkit.com", DAY_SECONDS + 6, "/parent/path", "msg6.pdf");
List<AttachmentArtifactItem> items = Arrays.asList(successItem, successItem2,
wrongArtType, missingTimeStamp, zeroTimeStamp,
noParentFile, noAssocAttr, missingAssocArt);
Pair<SleuthkitCase, Blackboard> casePair = getRecentAttachmentArtifactCase(items);
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
// get data
List<RecentAttachmentDetails> results = summary.getRecentAttachments(dataSource, 10);
// verify results
Assert.assertNotNull(results);
Assert.assertEquals(2, results.size());
RecentAttachmentDetails successItem2Details = results.get(0);
RecentAttachmentDetails successItemDetails = results.get(1);
Assert.assertEquals(successItemDetails.getDateAsLong(), (Long) DAY_SECONDS);
Assert.assertTrue(Paths.get(successItem.getFileParentPath(), successItem.getFileName())
.toString().equalsIgnoreCase(successItemDetails.getPath()));
Assert.assertTrue(successItem.getEmailFrom().equalsIgnoreCase(successItemDetails.getSender()));
Assert.assertEquals(successItem2Details.getDateAsLong(), (Long) (DAY_SECONDS + 1));
Assert.assertTrue(Paths.get(successItem2.getFileParentPath(), successItem2.getFileName())
.toString().equalsIgnoreCase(successItem2Details.getPath()));
Assert.assertTrue(successItem2.getEmailFrom().equalsIgnoreCase(successItem2Details.getSender()));
}
}

View File

@ -32,7 +32,7 @@ public class DomainSearchTestUtils {
public static ResultDomain mockDomainResult(String domain, long start, long end,
long totalVisits, long visits, long filesDownloaded, long dataSourceId) {
Content dataSource = TskMockUtils.mockDataSource(dataSourceId);
Content dataSource = TskMockUtils.getDataSource(dataSourceId);
return new ResultDomain(domain, start, end, totalVisits,
visits, filesDownloaded, dataSource);
}

View File

@ -0,0 +1,56 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.testutils;
import java.util.ArrayList;
import java.util.List;
/**
* Tools for pseudo-randomization.
*/
public final class RandomizationUtils {
/**
* Returns list in 0, n-1, 1, n-2 ... order. Deterministic so same results
* each time, but not in original order.
*
* @return Mixed up list.
*/
public static <T> List<T> getMixedUp(List<T> list) {
int forward = 0;
int backward = list.size() - 1;
List<T> newList = new ArrayList<>();
while (forward <= backward) {
newList.add(list.get(forward));
if (forward < backward) {
newList.add(list.get(backward));
}
forward++;
backward--;
}
return newList;
}
private RandomizationUtils() {
}
}

View File

@ -19,15 +19,26 @@
package org.sleuthkit.autopsy.testutils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException;
import org.sleuthkit.autopsy.texttranslation.TextTranslationService;
import org.sleuthkit.autopsy.texttranslation.TranslationException;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
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.TskCoreException;
@ -44,7 +55,7 @@ public class TskMockUtils {
*
* @return The mocked datasource.
*/
public static DataSource mockDataSource(long dataSourceId) {
public static DataSource getDataSource(long dataSourceId) {
DataSource dataSource = mock(DataSource.class);
when(dataSource.getName()).thenReturn("");
when(dataSource.getId()).thenReturn(dataSourceId);
@ -65,14 +76,35 @@ public class TskMockUtils {
*
* @throws TskCoreException
*/
public static BlackboardArtifact mockArtifact(BlackboardArtifact.Type artifactType, long artifactId,
public static BlackboardArtifact getArtifact(BlackboardArtifact.Type artifactType, long artifactId,
DataSource dataSource, BlackboardAttribute... attributes) throws TskCoreException {
return getArtifact(artifactType, null, artifactId, dataSource, attributes);
}
/**
* Gets a mock Blackboard artifact.
*
* @param artifactType The artifact type for the artifact.
* @param parent The parent file of the artifact.
* @param artifactId The artifact id.
* @param dataSource The datasource.
* @param attributes The attributes for the artifact.
*
* @return The mocked artifact.
*
* @throws TskCoreException
*/
public static BlackboardArtifact getArtifact(BlackboardArtifact.Type artifactType, Content parent, long artifactId,
DataSource dataSource, BlackboardAttribute... attributes) throws TskCoreException {
BlackboardArtifact artifact = mock(BlackboardArtifact.class);
final Map<BlackboardAttribute.Type, BlackboardAttribute> attributeTypes = Stream.of(attributes)
.filter(attr -> attr != null)
.collect(Collectors.toMap((attr) -> attr.getAttributeType(), Function.identity()));
when(artifact.getParent()).thenReturn(parent);
when(artifact.getArtifactID()).thenReturn(artifactId);
when(artifact.getArtifactTypeID()).thenReturn(artifactType.getTypeID());
@ -89,6 +121,146 @@ public class TskMockUtils {
return artifact;
}
public static BlackboardArtifact getArtifact(BlackboardArtifact.Type artifactType, long artifactId,
DataSource dataSource, List<BlackboardAttribute> attributes) throws TskCoreException {
return getArtifact(artifactType, artifactId, dataSource, attributes.toArray(new BlackboardAttribute[0]));
}
private static final String DEFAULT_ATTR_SOURCE = "TEST SOURCE";
public static BlackboardAttribute getAttribute(ATTRIBUTE_TYPE attrType, Object value) {
return getAttribute(new BlackboardAttribute.Type(attrType), DEFAULT_ATTR_SOURCE, value);
}
public static BlackboardAttribute getAttribute(BlackboardAttribute.Type attrType, String source, Object value) {
switch (attrType.getValueType()) {
case STRING:
case JSON:
if (value instanceof String) {
return new BlackboardAttribute(attrType, source, (String) value);
}
break;
case DATETIME:
case LONG:
if (value instanceof Long) {
return new BlackboardAttribute(attrType, source, (Long) value);
}
break;
case INTEGER:
if (value instanceof Integer) {
return new BlackboardAttribute(attrType, source, (Integer) value);
}
break;
case DOUBLE:
if (value instanceof Double) {
return new BlackboardAttribute(attrType, source, (Double) value);
}
break;
case BYTE:
if (value instanceof byte[]) {
return new BlackboardAttribute(attrType, source, (byte[]) value);
}
break;
default:
throw new IllegalArgumentException(String.format("Unknown attribute value type: %s", attrType.getValueType()));
}
throw new IllegalArgumentException(String.format("Attribute type expected type of %s but received argument of %s", attrType.getValueType(), value));
}
/**
* Returns a mock TextTranslationService.
*
* @param onTranslate A function that performs the translation. If null, a
* null result is always returned for .translate method.
* @param hasProvider What to return for the hasProvider method.
*
* @return The mocked text translation service.
*
* @throws NoServiceProviderException
* @throws TranslationException
*/
public static TextTranslationService getTextTranslationService(Function<String, String> onTranslate, boolean hasProvider)
throws NoServiceProviderException, TranslationException {
TextTranslationService translationService = mock(TextTranslationService.class);
when(translationService.hasProvider()).thenReturn(hasProvider);
when(translationService.translate(anyString())).thenAnswer((invocation) -> {
if (onTranslate == null) {
throw new NoServiceProviderException("No onTranslate function provided");
}
Object[] args = invocation.getArguments();
String input = (String) args[0];
return (input == null) ? null : onTranslate.apply(input);
});
return translationService;
}
/**
* Returns an AbstractFile mocking getPath and getName.
*
* @param objId The object id.
* @param path The path for the file.
* @param name The name
*
* @return
*/
public static AbstractFile getAbstractFile(long objId, String path, String name) {
AbstractFile mocked = mock(AbstractFile.class);
when(mocked.getId()).thenReturn(objId);
when(mocked.getName()).thenReturn(name);
when(mocked.getParentPath()).thenReturn(path);
return mocked;
}
private static void setConsoleHandler(Logger logger) {
// taken from https://stackoverflow.com/a/981230
// Handler for console (reuse it if it already exists)
Handler consoleHandler = null;
//see if there is already a console handler
for (Handler handler : logger.getHandlers()) {
if (handler instanceof ConsoleHandler) {
//found the console handler
consoleHandler = handler;
break;
}
}
if (consoleHandler == null) {
//there was no console handler found, create a new one
consoleHandler = new ConsoleHandler();
logger.addHandler(consoleHandler);
}
//set the console handler to fine:
consoleHandler.setLevel(java.util.logging.Level.FINEST);
}
/**
* Retrieves an autopsy logger that does not write to disk.
*
* @param loggerName The name of the logger.
*
* @return The autopsy logger for the console
*
* @throws InstantiationException
* @throws IllegalStateException
*/
public static Logger getJavaLogger(String loggerName) {
// The logger doesn't appear to respond well to mocking with mockito.
// It appears that the issue may have to do with mocking methods in the java.* packages
// since the autopsy logger extends the java.util.logging.Logger class:
// https://javadoc.io/static/org.mockito/mockito-core/3.5.13/org/mockito/Mockito.html#39
Logger logger = Logger.getLogger(loggerName);
setConsoleHandler(logger);
return logger;
}
private TskMockUtils() {
}
}

View File

@ -2930,7 +2930,9 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
String eventType = event.getPropertyName();
if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
synchronized (ingestLock) {
ingestLock.notify();
if (! IngestManager.getInstance().isIngestRunning()) {
ingestLock.notify();
}
}
}
}

View File

@ -24,7 +24,7 @@
<!-- Verify that the java version running is . -->
<fail message="Unsupported Java version: ${ant.java.version}.
Make sure that the Java version is 1.8.0_66 or higher."
unless="supported-java-versions"/>
unless="supported-java-versions"/>
<!-- Determine platform and include specific file -->
<condition property="os.family" value="unix">
@ -68,22 +68,54 @@
</target>
<target name="clean" depends="suite.clean">
<delete includeEmptyDirs="true" failonerror="false">
<delete includeEmptyDirs="true" failonerror="false">
<fileset dir="docs\doxygen-user\user-docs" includes="**/*"/>
</delete>
</delete>
<delete includeEmptyDirs="true" failonerror="false">
<fileset dir="docs\doxygen\doxygen_docs\api-docs" includes="**/*"/>
</delete>
</delete>
<delete includeemptydirs="true" failonerror="false">
<fileset dir="${basedir}/docs/doxygen-dev/build-docs" includes="**/*"/>
</delete>
</target>
<!-- This target is similar to the regular test target that calls test on all nbm's,
but this target excludes the Testing nbm which runs the regression tests -->
<target name="test-no-regression" depends="build" description="Runs tests for all modules in the suite excluding the regression tests of the Testing NBM.">
<!--taken from https://stackoverflow.com/a/10859103; remove "Testing" from the modules and provide 'modulesNoTesting' as result. -->
<property name="modulesBeforeChange" value="${modules}"/>
<script language="javascript">
<![CDATA[
var before = project.getProperty("modulesBeforeChange");
var separator = ":";
var testingNbm = "Testing";
var beforeSplit = before.split(separator);
var items = [];
for (var i = 0; i < beforeSplit.length; i++) {
if (beforeSplit[i].toUpperCase() !== testingNbm.toUpperCase()) {
items.push(beforeSplit[i]);
}
}
var itemsJoined = items.join(separator);
project.setNewProperty("modulesNoTesting", itemsJoined);
]]>
</script>
<sortsuitemodules unsortedmodules="${modulesNoTesting}" sortedmodulesproperty="modules.test.sorted" sorttests="true"/>
<!-- continue on fail -->
<property name="continue.after.failing.tests" value="true"/>
<subant target="test" buildpath="${modules.test.sorted}" inheritrefs="false" inheritall="false">
<property name="cluster.path.evaluated" value="${cluster.path.evaluated}"/> <!-- Just for speed of pre-7.0 projects -->
<property name="harness.taskdefs.done" value="${harness.taskdefs.done}"/> <!-- optimization -->
<property name="continue.after.failing.tests" value="${continue.after.failing.tests}"/>
</subant>
</target>
<!-- This target will create a custom ZIP file for us. It first uses the general
ZIP target and then opens it up and adds in any files that we want. This is where we customize the
version number. -->
ZIP target and then opens it up and adds in any files that we want. This is where we customize the
version number. -->
<target name="build-zip" depends="doxygen, suite.build-zip"> <!--,findJRE" -->
<property name="release.dir" value="${nbdist.dir}/${app.name}"/>
@ -128,15 +160,15 @@
<!-- for Japanese localized version add option: -Duser.language=ja -->
<if>
<equals arg1="${os.family}" arg2="mac"/>
<then>
<property name="jvm.options" value="&quot;${jvm-value} -J-Xdock:name=${app.title}&quot;"/>
</then>
<else>
<property name="jvm.options" value="&quot;${jvm-value}&quot;"/>
</else>
</if>
<if>
<equals arg1="${os.family}" arg2="mac"/>
<then>
<property name="jvm.options" value="&quot;${jvm-value} -J-Xdock:name=${app.title}&quot;"/>
</then>
<else>
<property name="jvm.options" value="&quot;${jvm-value}&quot;"/>
</else>
</if>
<propertyfile file="${app.property.file}">
<!-- Note: can be higher on 64 bit systems, should be in sync with project.properties -->
@ -146,9 +178,9 @@
<replace file="${app.property.file}" token="@JVM_OPTIONS" value="${jvm.options}" />
<!-- We want to remove the dlls in autopsy/modules/lib because they will
shadow the files in the autopsy/modules/lib/ARCHITECTURE folder in the JAR.
These files are legacy from when we used to copy the dlls to this location.
This check should do away in the future. Added Sept '13-->
shadow the files in the autopsy/modules/lib/ARCHITECTURE folder in the JAR.
These files are legacy from when we used to copy the dlls to this location.
This check should do away in the future. Added Sept '13-->
<delete failonerror="false">
<fileset dir="${zip-tmp}/${app.name}/autopsy/modules/lib">
<include name="libtsk_jni.dll" />
@ -188,9 +220,9 @@
<target name="input-build-type" unless="build.type">
<input addProperty="build.type"
message="Enter the desired build type:"
validargs="DEVELOPMENT,RELEASE"
defaultvalue="DEVELOPMENT"/>
message="Enter the desired build type:"
validargs="DEVELOPMENT,RELEASE"
defaultvalue="DEVELOPMENT"/>
</target>
<target name="input-version" unless="app.version">
@ -228,21 +260,21 @@
<echo>${app.name} branding</echo>
<propertyfile
file="${branding.dir}/core/core.jar/org/netbeans/core/startup/Bundle.properties"
comment="Updated by build script">
file="${branding.dir}/core/core.jar/org/netbeans/core/startup/Bundle.properties"
comment="Updated by build script">
<entry key="currentVersion" value="${app.title} ${app.version}" />
</propertyfile>
<propertyfile
file="${branding.dir}/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties"
comment="Updated by build script">
file="${branding.dir}/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties"
comment="Updated by build script">
<entry key="CTL_MainWindow_Title" value="${app.title} ${app.version}" />
<entry key="CTL_MainWindow_Title_No_Project" value="${app.title} ${app.version}" />
</propertyfile>
<propertyfile
file="${basedir}/Core/src/org/sleuthkit/autopsy/coreutils/Version.properties"
comment="Updated by build script">
file="${basedir}/Core/src/org/sleuthkit/autopsy/coreutils/Version.properties"
comment="Updated by build script">
<entry key="app.name" value="${app.title}" />
<entry key="app.version" value="${app.version}" />
<entry key="build.type" value="${build.type}" />
@ -329,7 +361,7 @@
<antcall target="build-installer-${os.family}" />
</target>
<target name="chmod_executables" >
<target name="chmod_executables" >
<chmod perm="a+x">
<fileset dir="${cluster}/markmckinnon" casesensitive="no" id="mm">
<include name="*_linux"/>
@ -337,4 +369,4 @@
</fileset>
</chmod>
</target>
</project>
</project>

39
thirdparty/yara/ReadMe.txt vendored Executable file
View File

@ -0,0 +1,39 @@
This folder contains the projects you need for building and testing the yarabridge.dll and YaraJNIWrapper.jar.
bin:
Contains the built dll and jar.
yarabridge:
VS project to create the dll that wraps the the libyara library.
YaraJNIWrapper:
Simple jar file that contains the native JNI methods for accessing the yarabridge.dll.
Steps for building yarabridge, YaraJNIWrapper and YaraWrapperTest.
1. Clone the yara repo at the same level as you have the autopsy repo. https://github.com/VirusTotal/yara
2. Build libyara:
- Open the project yara/windows/2015/yara.sln
- Build Release x64.
3. Open the yarabridge project and build Release x64.
-If you have link issues, make sure you build release x64 in the previous step.
-This project will automatically copy the built dll to the bin folder.
4. Build YaraJNIWrapper
- Open in netbeans and select Build.
- Manually move the newly build jar file to the bin folder. After building the jar file can be found in
yara/YaraJNIWrapper/dist/
- Any matching rules will appear on the CL or the output of the project.
5. Test
- Open the YaraWrapperTest
- In the Run Properties you need to specify the path to a compiled yara rule file and a file to search.
There are sample files in YaraWrapperTest\resources.
- If you would like to make your own compiled rule file you can use the yarac tool that can be found
in yara/windows/vs2015/Release, if its not there go back to the yara project and build all of the
projects.
Troubleshooting:
- When building libyara make sure that you are building the vs2015 project (There is a vs2017 project too).
The paths in the yarabrige package are relative, but assume
that you are building the release version of the dll with the vs2015 project.
- Don't forget to move the YaraJNIWrapper.jar after you build it.

73
thirdparty/yara/YaraJNIWrapper/build.xml vendored Executable file
View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- You may freely edit this file. See commented blocks below for -->
<!-- some examples of how to customize the build. -->
<!-- (If you delete it and reopen the project it will be recreated.) -->
<!-- By default, only the Clean and Build commands use this build script. -->
<!-- Commands such as Run, Debug, and Test only use this build script if -->
<!-- the Compile on Save feature is turned off for the project. -->
<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
<!-- in the project's Project Properties dialog box.-->
<project name="YaraJNIWrapper" default="default" basedir=".">
<description>Builds, tests, and runs the project YaraJNIWrapper.</description>
<import file="nbproject/build-impl.xml"/>
<!--
There exist several targets which are by default empty and which can be
used for execution of your tasks. These targets are usually executed
before and after some main targets. They are:
-pre-init: called before initialization of project properties
-post-init: called after initialization of project properties
-pre-compile: called before javac compilation
-post-compile: called after javac compilation
-pre-compile-single: called before javac compilation of single file
-post-compile-single: called after javac compilation of single file
-pre-compile-test: called before javac compilation of JUnit tests
-post-compile-test: called after javac compilation of JUnit tests
-pre-compile-test-single: called before javac compilation of single JUnit test
-post-compile-test-single: called after javac compilation of single JUunit test
-pre-jar: called before JAR building
-post-jar: called after JAR building
-post-clean: called after cleaning build products
(Targets beginning with '-' are not intended to be called on their own.)
Example of inserting an obfuscator after compilation could look like this:
<target name="-post-compile">
<obfuscate>
<fileset dir="${build.classes.dir}"/>
</obfuscate>
</target>
For list of available properties check the imported
nbproject/build-impl.xml file.
Another way to customize the build is by overriding existing main targets.
The targets of interest are:
-init-macrodef-javac: defines macro for javac compilation
-init-macrodef-junit: defines macro for junit execution
-init-macrodef-debug: defines macro for class debugging
-init-macrodef-java: defines macro for class execution
-do-jar: JAR building
run: execution of project
-javadoc-build: Javadoc generation
test-report: JUnit report generation
An example of overriding the target for project execution could look like this:
<target name="run" depends="YaraJNIWrapper-impl.jar">
<exec dir="bin" executable="launcher.exe">
<arg file="${dist.jar}"/>
</exec>
</target>
Notice that the overridden target depends on the jar target and not only on
the compile target as the regular run target does. Again, for a list of available
properties which you can use, check the target you are overriding in the
nbproject/build-impl.xml file.
-->
</project>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/2" lastBookmarkId="0"/>
</project-private>

View File

@ -0,0 +1,93 @@
annotation.processing.enabled=true
annotation.processing.enabled.in.editor=false
annotation.processing.processor.options=
annotation.processing.processors.list=
annotation.processing.run.all.processors=true
annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
build.classes.dir=${build.dir}/classes
build.classes.excludes=**/*.java,**/*.form
# This directory is removed when the project is cleaned:
build.dir=build
build.generated.dir=${build.dir}/generated
build.generated.sources.dir=${build.dir}/generated-sources
# Only compile against the classpath explicitly listed here:
build.sysclasspath=ignore
build.test.classes.dir=${build.dir}/test/classes
build.test.results.dir=${build.dir}/test/results
# Uncomment to specify the preferred debugger connection transport:
#debug.transport=dt_socket
debug.classpath=\
${run.classpath}
debug.modulepath=\
${run.modulepath}
debug.test.classpath=\
${run.test.classpath}
debug.test.modulepath=\
${run.test.modulepath}
# Files in build.classes.dir which should be excluded from distribution jar
dist.archive.excludes=
# This directory is removed when the project is cleaned:
dist.dir=dist
dist.jar=${dist.dir}/YaraJNIWrapper.jar
dist.javadoc.dir=${dist.dir}/javadoc
dist.jlink.dir=${dist.dir}/jlink
dist.jlink.output=${dist.jlink.dir}/YaraJNIWrapper
excludes=
includes=**
jar.compress=false
javac.classpath=
# Space-separated list of extra javac options
javac.compilerargs=
javac.deprecation=false
javac.external.vm=true
javac.modulepath=
javac.processormodulepath=
javac.processorpath=\
${javac.classpath}
javac.source=1.8
javac.target=1.8
javac.test.classpath=\
${javac.classpath}:\
${build.classes.dir}
javac.test.modulepath=\
${javac.modulepath}
javac.test.processorpath=\
${javac.test.classpath}
javadoc.additionalparam=
javadoc.author=false
javadoc.encoding=${source.encoding}
javadoc.html5=false
javadoc.noindex=false
javadoc.nonavbar=false
javadoc.notree=false
javadoc.private=false
javadoc.splitindex=true
javadoc.use=true
javadoc.version=false
javadoc.windowtitle=
# The jlink additional root modules to resolve
jlink.additionalmodules=
# The jlink additional command line parameters
jlink.additionalparam=
jlink.launcher=true
jlink.launcher.name=YaraJNIWrapper
meta.inf.dir=${src.dir}/META-INF
mkdist.disabled=true
platform.active=default_platform
run.classpath=\
${javac.classpath}:\
${build.classes.dir}
# Space-separated list of JVM arguments used when running the project.
# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
# To set system properties for unit tests define test-sys-prop.name=value:
run.jvmargs=
run.modulepath=\
${javac.modulepath}
run.test.classpath=\
${javac.test.classpath}:\
${build.test.classes.dir}
run.test.modulepath=\
${javac.test.modulepath}
source.encoding=UTF-8
src.dir=src
test.src.dir=test

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://www.netbeans.org/ns/project/1">
<type>org.netbeans.modules.java.j2seproject</type>
<configuration>
<data xmlns="http://www.netbeans.org/ns/j2se-project/3">
<name>YaraJNIWrapper</name>
<source-roots>
<root id="src.dir"/>
</source-roots>
<test-roots>
<root id="test.src.dir"/>
</test-roots>
</data>
</configuration>
</project>

View File

@ -0,0 +1,68 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.yara;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* native JNI interface to the yarabridge dll.
*/
public class YaraJNIWrapper {
// Load the yarabridge.dll which should be located in the same directory as
// the jar file. If we need to use this code for debugging the dll this
// code will need to be modified to add that support.
static {
Path directoryPath = null;
try {
directoryPath = Paths.get(YaraJNIWrapper.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent().toAbsolutePath();
} catch (URISyntaxException ex) {
Logger.getLogger(YaraJNIWrapper.class.getName()).log(Level.SEVERE, null, ex);
}
String libraryPath = Paths.get(directoryPath != null ? directoryPath.toString() : "", "yarabridge.dll").toAbsolutePath().toString();
System.load(libraryPath);
}
/**
* Returns a list of rules that were found in the given byteBuffer.
*
* The rule path must be to a yara compile rule file.
*
* @param compiledRulesPath
* @param byteBuffer
*
* @return List of rules found rules. Null maybe returned if error occurred.
*
* @throws YaraWrapperException
*/
static public native List<String> findRuleMatch(String compiledRulesPath, byte[] byteBuffer) throws YaraWrapperException;
/**
* private constructor.
*/
private YaraJNIWrapper() {
}
}

View File

@ -0,0 +1,37 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.yara;
/**
*
* An exception class for the YaraWrapper Library
*/
public class YaraWrapperException extends Exception{
private static final long serialVersionUID = 1L;
/**
* Create exception containing the error message
*
* @param msg Error message
*/
public YaraWrapperException(String msg) {
super(msg);
}
}

73
thirdparty/yara/YaraWrapperTest/build.xml vendored Executable file
View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- You may freely edit this file. See commented blocks below for -->
<!-- some examples of how to customize the build. -->
<!-- (If you delete it and reopen the project it will be recreated.) -->
<!-- By default, only the Clean and Build commands use this build script. -->
<!-- Commands such as Run, Debug, and Test only use this build script if -->
<!-- the Compile on Save feature is turned off for the project. -->
<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
<!-- in the project's Project Properties dialog box.-->
<project name="YaraWrapperTest" default="default" basedir=".">
<description>Builds, tests, and runs the project YaraWrapperTest.</description>
<import file="nbproject/build-impl.xml"/>
<!--
There exist several targets which are by default empty and which can be
used for execution of your tasks. These targets are usually executed
before and after some main targets. They are:
-pre-init: called before initialization of project properties
-post-init: called after initialization of project properties
-pre-compile: called before javac compilation
-post-compile: called after javac compilation
-pre-compile-single: called before javac compilation of single file
-post-compile-single: called after javac compilation of single file
-pre-compile-test: called before javac compilation of JUnit tests
-post-compile-test: called after javac compilation of JUnit tests
-pre-compile-test-single: called before javac compilation of single JUnit test
-post-compile-test-single: called after javac compilation of single JUunit test
-pre-jar: called before JAR building
-post-jar: called after JAR building
-post-clean: called after cleaning build products
(Targets beginning with '-' are not intended to be called on their own.)
Example of inserting an obfuscator after compilation could look like this:
<target name="-post-compile">
<obfuscate>
<fileset dir="${build.classes.dir}"/>
</obfuscate>
</target>
For list of available properties check the imported
nbproject/build-impl.xml file.
Another way to customize the build is by overriding existing main targets.
The targets of interest are:
-init-macrodef-javac: defines macro for javac compilation
-init-macrodef-junit: defines macro for junit execution
-init-macrodef-debug: defines macro for class debugging
-init-macrodef-java: defines macro for class execution
-do-jar: JAR building
run: execution of project
-javadoc-build: Javadoc generation
test-report: JUnit report generation
An example of overriding the target for project execution could look like this:
<target name="run" depends="YaraWrapperTest-impl.jar">
<exec dir="bin" executable="launcher.exe">
<arg file="${dist.jar}"/>
</exec>
</target>
Notice that the overridden target depends on the jar target and not only on
the compile target as the regular run target does. Again, for a list of available
properties which you can use, check the target you are overriding in the
nbproject/build-impl.xml file.
-->
</project>

3
thirdparty/yara/YaraWrapperTest/manifest.mf vendored Executable file
View File

@ -0,0 +1,3 @@
Manifest-Version: 1.0
X-COMMENT: Main-Class will be added automatically by build

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/2" lastBookmarkId="0"/>
</project-private>

View File

@ -0,0 +1,99 @@
annotation.processing.enabled=true
annotation.processing.enabled.in.editor=false
annotation.processing.processors.list=
annotation.processing.run.all.processors=true
annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
application.title=YaraWrapperTest
application.vendor=kelly
build.classes.dir=${build.dir}/classes
build.classes.excludes=**/*.java,**/*.form
# This directory is removed when the project is cleaned:
build.dir=build
build.generated.dir=${build.dir}/generated
build.generated.sources.dir=${build.dir}/generated-sources
# Only compile against the classpath explicitly listed here:
build.sysclasspath=ignore
build.test.classes.dir=${build.dir}/test/classes
build.test.results.dir=${build.dir}/test/results
# Uncomment to specify the preferred debugger connection transport:
#debug.transport=dt_socket
debug.classpath=\
${run.classpath}
debug.modulepath=\
${run.modulepath}
debug.test.classpath=\
${run.test.classpath}
debug.test.modulepath=\
${run.test.modulepath}
# Files in build.classes.dir which should be excluded from distribution jar
dist.archive.excludes=
# This directory is removed when the project is cleaned:
dist.dir=dist
dist.jar=${dist.dir}/YaraWrapperTest.jar
dist.javadoc.dir=${dist.dir}/javadoc
dist.jlink.dir=${dist.dir}/jlink
dist.jlink.output=${dist.jlink.dir}/YaraWrapperTest
endorsed.classpath=
excludes=
file.reference.YaraJNIWrapper.jar=../bin/YaraJNIWrapper.jar
includes=**
jar.compress=false
javac.classpath=\
${file.reference.YaraJNIWrapper.jar}
# Space-separated list of extra javac options
javac.compilerargs=
javac.deprecation=false
javac.external.vm=true
javac.modulepath=
javac.processormodulepath=
javac.processorpath=\
${javac.classpath}
javac.source=1.8
javac.target=1.8
javac.test.classpath=\
${javac.classpath}:\
${build.classes.dir}
javac.test.modulepath=\
${javac.modulepath}
javac.test.processorpath=\
${javac.test.classpath}
javadoc.additionalparam=
javadoc.author=false
javadoc.encoding=${source.encoding}
javadoc.html5=false
javadoc.noindex=false
javadoc.nonavbar=false
javadoc.notree=false
javadoc.private=false
javadoc.splitindex=true
javadoc.use=true
javadoc.version=false
javadoc.windowtitle=
# The jlink additional root modules to resolve
jlink.additionalmodules=
# The jlink additional command line parameters
jlink.additionalparam=
jlink.launcher=true
jlink.launcher.name=YaraWrapperTest
main.class=org.sleuthkit.autopsy.yara.YaraWrapperTest
manifest.file=manifest.mf
meta.inf.dir=${src.dir}/META-INF
mkdist.disabled=false
platform.active=default_platform
run.classpath=\
${javac.classpath}:\
${build.classes.dir}
# Space-separated list of JVM arguments used when running the project.
# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
# To set system properties for unit tests define test-sys-prop.name=value:
run.jvmargs=
run.modulepath=\
${javac.modulepath}
run.test.classpath=\
${javac.test.classpath}:\
${build.test.classes.dir}
run.test.modulepath=\
${javac.test.modulepath}
source.encoding=UTF-8
src.dir=src
test.src.dir=test

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://www.netbeans.org/ns/project/1">
<type>org.netbeans.modules.java.j2seproject</type>
<configuration>
<data xmlns="http://www.netbeans.org/ns/j2se-project/3">
<name>YaraWrapperTest</name>
<source-roots>
<root id="src.dir"/>
</source-roots>
<test-roots>
<root id="test.src.dir"/>
</test-roots>
</data>
</configuration>
</project>

Binary file not shown.

View File

@ -0,0 +1 @@
Hello World

View File

@ -0,0 +1,81 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.yara;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.sleuthkit.autopsy.yara.YaraJNIWrapper;
import org.sleuthkit.autopsy.yara.YaraWrapperException;
/**
* Tests the YaraJNIWrapper code.
*/
public class YaraWrapperTest {
private static final Logger logger = Logger.getLogger(YaraWrapperTest.class.getName());
public static void main(String[] args) {
if (args.length < 2) {
System.out.println("Please supply two arguments, a yara compiled rule path and a path to the file to scan.");
return;
}
testFileRuleMatch(args[0], args[1]);
}
/**
* Call the YaraJNIWrapper FindRuleMatch with the given path and output the
* results to the cl.
*
* @param compiledRulePath Path to yara compiled rule file
* @param filePath Path to file
*/
private static void testFileRuleMatch(String compiledRulePath, String filePath) {
Path path = Paths.get(filePath);
try {
byte[] data = Files.readAllBytes(path);
List<String> list = YaraJNIWrapper.findRuleMatch(compiledRulePath, data);
if (list != null) {
if (list.isEmpty()) {
System.out.println("FindRuleMatch return an empty list");
} else {
System.out.println("Matching Rules:");
for (String s : list) {
System.out.println(s);
}
}
} else {
logger.log(Level.SEVERE, "FindRuleMatch return a null list");
}
} catch (IOException | YaraWrapperException ex) {
logger.log(Level.SEVERE, "Error thrown from yarabridge", ex);
}
}
}

BIN
thirdparty/yara/bin/YaraJNIWrapper.jar vendored Executable file

Binary file not shown.

BIN
thirdparty/yara/bin/yarabridge.dll vendored Executable file

Binary file not shown.

28
thirdparty/yara/yarabridge/yarabridge.sln vendored Executable file
View File

@ -0,0 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "yarabridge", "yarabridge\yarabridge.vcxproj", "{7922D123-F27A-427B-B3EF-964F5E79FDDA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7922D123-F27A-427B-B3EF-964F5E79FDDA}.Debug|x64.ActiveCfg = Debug|x64
{7922D123-F27A-427B-B3EF-964F5E79FDDA}.Debug|x64.Build.0 = Debug|x64
{7922D123-F27A-427B-B3EF-964F5E79FDDA}.Debug|x86.ActiveCfg = Debug|Win32
{7922D123-F27A-427B-B3EF-964F5E79FDDA}.Debug|x86.Build.0 = Debug|Win32
{7922D123-F27A-427B-B3EF-964F5E79FDDA}.Release|x64.ActiveCfg = Release|x64
{7922D123-F27A-427B-B3EF-964F5E79FDDA}.Release|x64.Build.0 = Release|x64
{7922D123-F27A-427B-B3EF-964F5E79FDDA}.Release|x86.ActiveCfg = Release|Win32
{7922D123-F27A-427B-B3EF-964F5E79FDDA}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,138 @@
/*
** YaraBridge
**
** Brian Carrier [carrier <at> sleuthkit [dot] org]
** Copyright (c) 2010-2018 Brian Carrier. All Rights reserved
**
** This software is distributed under the Common Public License 1.0
**
*/
#include<stdio.h>
#include<jni.h>
#include "YaraJNIWrapper.h"
#include "yara.h"
#include <vector>
#include <algorithm>
#include <iostream>
#include <numeric>
using std::string;
using std::vector;
/*
Callback method to be passed to yr_rules_scan_mem method.
user_data is expected to be a pointer to a string vector.
*/
static int callback(
YR_SCAN_CONTEXT* context,
int message,
void* message_data,
void* user_data)
{
if (message == CALLBACK_MSG_RULE_MATCHING) {
YR_RULE *rule = (YR_RULE *)message_data;
((std::vector<std::string>*)user_data)->push_back(rule->identifier);
}
return CALLBACK_CONTINUE;
}
/*
Throw a new instance of YaraWrapperException with the given message.
Unlike in JAVA throwing this exception will not stop the execution
of the method from which it is thrown.
*/
static void throwException(JNIEnv *env, char * msg) {
jclass cls;
cls = env->FindClass("org/sleuthkit/autopsy/yara/YaraWrapperException");
if (cls == NULL) {
fprintf(stderr, "Failed to throw YaraWrapperException, cannot find class\n");
return;
}
env->ThrowNew(cls, msg);
}
/*
Generic method that will create a Java ArrayList object populating it with
the strings from the given vector.
*/
jobject createArrayList(JNIEnv *env, std::vector<std::string> vector) {
jclass cls_arrayList = env->FindClass("java/util/ArrayList");
jmethodID constructor = env->GetMethodID(cls_arrayList, "<init>", "(I)V");
jmethodID method_add = env->GetMethodID(cls_arrayList, "add", "(Ljava/lang/Object;)Z");
jobject list = env->NewObject(cls_arrayList, constructor, vector.size());
for (std::string str : vector) {
jstring element = env->NewStringUTF(str.c_str());
env->CallBooleanMethod(list, method_add, element);
env->DeleteLocalRef(element);
}
return list;
}
/*
* Class: org_sleuthkit_autopsy_yara_YaraJNIWrapper
* Method: FindRuleMatch
* Signature: (Ljava/lang/String;[B)Ljava/util/List;
*/
JNIEXPORT jobject JNICALL Java_org_sleuthkit_autopsy_yara_YaraJNIWrapper_findRuleMatch
(JNIEnv * env, jclass cls, jstring compiledRulePath, jbyteArray fileByteArray) {
char errorMessage[256];
const char *nativeString = env->GetStringUTFChars(compiledRulePath, 0);
jobject resultList = NULL;
int result;
if ((result = yr_initialize()) != ERROR_SUCCESS) {
sprintf_s(errorMessage, "libyara initialization error (%d)\n", result);
throwException(env, errorMessage);
return resultList;
}
while (1) {
YR_RULES *rules = NULL;
if ((result = yr_rules_load(nativeString, &rules)) != ERROR_SUCCESS) {
sprintf_s(errorMessage, "Failed to load compiled yara rules (%d)\n", result);
throwException(env, errorMessage);
break;
}
boolean isCopy;
int byteArrayLength = env->GetArrayLength(fileByteArray);
if (byteArrayLength == 0) {
throwException(env, "Unable to scan for matches. File byte array size was 0.");
break;
}
jbyte* nativeByteArray = env->GetByteArrayElements(fileByteArray, &isCopy);
int flags = 0;
std::vector<std::string> scanResults;
result = yr_rules_scan_mem(rules, (unsigned char*)nativeByteArray, byteArrayLength, flags, callback, &scanResults, 1000000);
env->ReleaseByteArrayElements(fileByteArray, nativeByteArray, 0);
if (result != ERROR_SUCCESS) {
sprintf_s(errorMessage, "Yara file scan failed (%d)\n", result);
throwException(env, errorMessage);
break;
}
resultList = createArrayList(env, scanResults);
break;
}
env->ReleaseStringUTFChars(compiledRulePath, nativeString);
yr_finalize();
return resultList;
}

View File

@ -0,0 +1,21 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_sleuthkit_autopsy_yara_YaraJNIWrapper */
#ifndef _Included_org_sleuthkit_autopsy_yara_YaraJNIWrapper
#define _Included_org_sleuthkit_autopsy_yara_YaraJNIWrapper
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: org_sleuthkit_autopsy_yara_YaraJNIWrapper
* Method: FindRuleMatch
* Signature: (Ljava/lang/String;[B)Ljava/util/List;
*/
JNIEXPORT jobject JNICALL Java_org_sleuthkit_autopsy_yara_YaraJNIWrapper_findRuleMatch
(JNIEnv *, jclass, jstring, jbyteArray);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,8 @@
// stdafx.cpp : source file that includes just the standard includes
// yarabridge.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information
#include "stdafx.h"
// TODO: reference any additional headers you need in STDAFX.H
// and not in this file

View File

@ -0,0 +1,16 @@
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#pragma once
#include "targetver.h"
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files:
#include <windows.h>
// TODO: reference additional headers your program requires here

View File

@ -0,0 +1,8 @@
#pragma once
// Including SDKDDKVer.h defines the highest available Windows platform.
// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
#include <SDKDDKVer.h>

View File

@ -0,0 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{7922D123-F27A-427B-B3EF-964F5E79FDDA}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>yarabridge</RootNamespace>
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>$(JDK_HOME)\include\win32;$(JDK_HOME)\include;..\..\..\..\..\yara\libyara\include;$(IncludePath)</IncludePath>
<LibraryPath>$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>$(JDK_HOME)\include\win32;$(JDK_HOME)\include;..\..\..\..\..\yara\libyara\include;$(IncludePath)</IncludePath>
<LibraryPath>$(LibraryPath)</LibraryPath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;YARABRIDGE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;YARABRIDGE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(ProjectDir)..\..\..\..\..\yara\windows\vs2015\libyara\Release;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>ws2_32.lib;crypt32.lib;libyara64.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>copy "$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).dll" "$(SolutionDir)..\bin\$(ProjectName).dll"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;YARABRIDGE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;YARABRIDGE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(JDK_HOME)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(ProjectDir)..\..\..\..\..\yara\windows\vs2015\libyara\Release;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>ws2_32.lib;crypt32.lib;libyara64.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>copy "$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).dll" "$(SolutionDir)..\bin\$(ProjectName).dll"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<Text Include="ReadMe.txt" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="YaraJNIWrapper.h" />
<ClInclude Include="stdafx.h" />
<ClInclude Include="targetver.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp" />
<ClCompile Include="YaraJNIWrapper.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -23,9 +23,11 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.regex.Matcher;
@ -79,7 +81,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
private static final int MBOX_SIZE_TO_SPLIT = 1048576000;
private Case currentCase;
/**
* Empty constructor.
*/
@ -152,7 +154,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
return ProcessResult.ERROR;
}
}
if (isMbox) {
return processMBox(abstractFile);
}
@ -168,7 +170,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
if (isVcardFile) {
return processVcard(abstractFile);
}
return ProcessResult.OK;
}
@ -211,12 +213,13 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
PstParser parser = new PstParser(services);
PstParser.ParseResult result = parser.open(file, abstractFile.getId());
switch( result) {
case OK:
Iterator<EmailMessage> pstMsgIterator = parser.getEmailMessageIterator();
if (pstMsgIterator != null) {
processEmails(parser.getPartialEmailMessages(), pstMsgIterator , abstractFile);
processEmails(parser.getPartialEmailMessages(), pstMsgIterator, abstractFile);
if (context.fileIngestIsCancelled()) {
return ProcessResult.OK;
}
@ -272,7 +275,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
if (file.delete() == false) {
logger.log(Level.INFO, "Failed to delete temp file: {0}", file.getName()); //NON-NLS
}
return ProcessResult.OK;
}
@ -451,7 +454,9 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
List<AbstractFile> derivedFiles = new ArrayList<>();
BlackboardArtifact msgArtifact = addEmailArtifact(message, abstractFile);
AccountFileInstanceCache accountFileInstanceCache = new AccountFileInstanceCache(abstractFile, currentCase);
BlackboardArtifact msgArtifact = addEmailArtifact(message, abstractFile, accountFileInstanceCache);
accountFileInstanceCache.clear();
if ((msgArtifact != null) && (message.hasAttachment())) {
derivedFiles.addAll(handleAttachments(message.getAttachments(), abstractFile, msgArtifact));
@ -527,7 +532,11 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
* @param fullMessageIterator
* @param abstractFile
*/
private void processEmails(List<EmailMessage> partialEmailsForThreading, Iterator<EmailMessage> fullMessageIterator, AbstractFile abstractFile) {
private void processEmails(List<EmailMessage> partialEmailsForThreading, Iterator<EmailMessage> fullMessageIterator,
AbstractFile abstractFile) {
// Create cache for accounts
AccountFileInstanceCache accountFileInstanceCache = new AccountFileInstanceCache(abstractFile, currentCase);
// Putting try/catch around this to catch any exception and still allow
// the creation of the artifacts to continue.
@ -560,7 +569,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
}
}
BlackboardArtifact msgArtifact = addEmailArtifact(current, abstractFile);
BlackboardArtifact msgArtifact = addEmailArtifact(current, abstractFile, accountFileInstanceCache);
if ((msgArtifact != null) && (current.hasAttachment())) {
derivedFiles.addAll(handleAttachments(current.getAttachments(), abstractFile, msgArtifact ));
@ -676,7 +685,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
* @return The generated e-mail message artifact.
*/
@Messages({"ThunderbirdMboxFileIngestModule.addArtifact.indexError.message=Failed to index email message detected artifact for keyword search."})
private BlackboardArtifact addEmailArtifact(EmailMessage email, AbstractFile abstractFile) {
private BlackboardArtifact addEmailArtifact(EmailMessage email, AbstractFile abstractFile, AccountFileInstanceCache accountFileInstanceCache) {
BlackboardArtifact bbart = null;
List<BlackboardAttribute> bbattributes = new ArrayList<>();
String to = email.getRecipients();
@ -706,7 +715,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
if (senderAddressList.size() == 1) {
senderAddress = senderAddressList.get(0);
try {
senderAccountInstance = currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, senderAddress, EmailParserModuleFactory.getModuleName(), abstractFile);
senderAccountInstance = accountFileInstanceCache.getAccountInstance(senderAddress);
}
catch(TskCoreException ex) {
logger.log(Level.WARNING, "Failed to create account for email address " + senderAddress, ex); //NON-NLS
@ -731,9 +740,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
return null;
}
try {
AccountFileInstance recipientAccountInstance =
currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, addr,
EmailParserModuleFactory.getModuleName(), abstractFile);
AccountFileInstance recipientAccountInstance = accountFileInstanceCache.getAccountInstance(addr);
recipientAccountInstances.add(recipientAccountInstance);
}
catch(TskCoreException ex) {
@ -835,6 +842,56 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
}
}
/**
* Cache for storing AccountFileInstance.
* The idea is that emails will be used multiple times in a file and
* we shouldn't do a database lookup each time.
*/
static private class AccountFileInstanceCache {
private final Map<String, AccountFileInstance> cacheMap;
private final AbstractFile file;
private final Case currentCase;
/**
* Create a new cache. Caches are linked to a specific file.
* @param file
* @param currentCase
*/
AccountFileInstanceCache(AbstractFile file, Case currentCase) {
cacheMap= new HashMap<>();
this.file = file;
this.currentCase = currentCase;
}
/**
* Get the account file instance from the cache or the database.
*
* @param email The email for this account.
*
* @return The corresponding AccountFileInstance
*
* @throws TskCoreException
*/
AccountFileInstance getAccountInstance(String email) throws TskCoreException {
if (cacheMap.containsKey(email)) {
return cacheMap.get(email);
}
AccountFileInstance accountInstance =
currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, email,
EmailParserModuleFactory.getModuleName(), file);
cacheMap.put(email, accountInstance);
return accountInstance;
}
/**
* Clears the cache.
*/
void clear() {
cacheMap.clear();
}
}
/**
* Post an error message for the user.
*