Merge pull request #1029 from APriestman/stix

Adding STIX module code
This commit is contained in:
Richard Cordovano 2015-02-05 12:39:43 -05:00
commit 272208c345
24 changed files with 4328 additions and 1 deletions

View File

@ -1,10 +1,13 @@
file.reference.jdom-2.0.5-contrib.jar=release/modules/ext/jdom-2.0.5-contrib.jar
file.reference.jdom-2.0.5.jar=release/modules/ext/jdom-2.0.5.jar
file.reference.jython.jar=C:\\autopsy\\Core\\release\\modules\\ext\\jython.jar
file.reference.jython.jar-1=release/modules/ext/jython.jar
file.reference.metadata-extractor-2.6.2.jar=release/modules/ext/metadata-extractor-2.6.2.jar
file.reference.Rejistry-1.0-SNAPSHOT.jar=release/modules/ext/Rejistry-1.0-SNAPSHOT.jar
file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbinding-AllPlatforms.jar
file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar
file.reference.sqlite-jdbc-3.7.15-M1.jar=release/modules/ext/sqlite-jdbc-3.7.15-M1.jar
file.reference.StixLib.jar=release/modules/ext/StixLib.jar
file.reference.tika-core-1.2.jar=release/modules/ext/tika-core-1.2.jar
file.reference.Tsk_DataModel.jar=release/modules/ext/Tsk_DataModel.jar
file.reference.xmpcore.jar=release/modules/ext/xmpcore.jar

View File

@ -215,10 +215,18 @@
<runtime-relative-path>ext/jdom-2.0.5.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jdom-2.0.5.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/Rejistry-1.0-SNAPSHOT.jar</runtime-relative-path>
<binary-origin>release/modules/ext/Rejistry-1.0-SNAPSHOT.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/sevenzipjbinding.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sevenzipjbinding.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/StixLib.jar</runtime-relative-path>
<binary-origin>release/modules/ext/StixLib.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/sqlite-jdbc-3.7.15-M1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sqlite-jdbc-3.7.15-M1.jar</binary-origin>

Binary file not shown.

Binary file not shown.

View File

@ -320,7 +320,11 @@
<attr name="instanceCreate" methodvalue="org.sleuthkit.autopsy.report.ReportKML.getDefault"/>
<attr name="position" intvalue="904"/>
</file>
<file name="org-sleuthkit-autopsy-modules-stix-STIXReportModule.instance">
<attr name="instanceOf" stringvalue="org.sleuthkit.autopsy.report.GeneralReportModule"/>
<attr name="instanceCreate" methodvalue="org.sleuthkit.autopsy.modules.stix.STIXReportModule.getDefault"/>
<attr name="position" intvalue="910"/>
</file>
<!--<folder name="JavaHelp">
<file name="casemodule-helpset.xml" url="casemodule-helpset.xml">
<attr name="position" intvalue="3075"/>

View File

@ -0,0 +1,8 @@
OpenIDE-Module-Name=stixModule
STIXReportModule.getName.text=STIX
STIXReportModule.getDesc.text=Generate a report by running a collection of STIX (Structured Threat Information eXpression) files against the data sources. Also creates artifacts under Interesting Files.
STIXReportModule.progress.readSTIX=Parsing STIX files
STIXReportModuleConfigPanel.jLabel2.text=Select a STIX file or directory of STIX files
STIXReportModuleConfigPanel.jTextField1.text=
STIXReportModuleConfigPanel.jButton1.text=Choose file
STIXReportModuleConfigPanel.jCheckBox1.text=Include results for false indicators in output file

View File

@ -0,0 +1,226 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.modules.stix;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException;
import java.util.List;
import java.util.ArrayList;
import org.mitre.cybox.objects.AccountObjectType;
import org.mitre.cybox.objects.UserAccountObjectType;
import org.mitre.cybox.objects.WindowsUserAccount;
/**
*
*/
class EvalAccountObj extends EvaluatableObject {
private AccountObjectType obj;
public EvalAccountObj(AccountObjectType a_obj, String a_id, String a_spacing) {
obj = a_obj;
id = a_id;
spacing = a_spacing;
}
@Override
public synchronized ObservableResult evaluate() {
setWarnings("");
// Fields we can search for:
// UserAccount: Home_Directory, Username
// WinUserAccount: SID
if (!(obj instanceof UserAccountObjectType)) {
return new ObservableResult(id, "AccountObject: Can not process \"Account\" - need a User_Account or Windows_User_Account",
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
// For displaying what we were looking for in the results
String searchString = "";
// Check which fields are present and record them
boolean haveHomeDir = false;
boolean haveUsername = false;
boolean haveSID = false;
UserAccountObjectType userAccountObj = (UserAccountObjectType) obj;
if (userAccountObj.getHomeDirectory() != null) {
haveHomeDir = true;
searchString = "HomeDir \"" + userAccountObj.getHomeDirectory().getValue().toString() + "\"";
}
if (userAccountObj.getUsername() != null) {
haveUsername = true;
if (!searchString.isEmpty()) {
searchString += " and ";
}
searchString += "Username \"" + userAccountObj.getUsername().getValue().toString() + "\"";
}
WindowsUserAccount winUserObj = null;
if (obj instanceof WindowsUserAccount) {
winUserObj = (WindowsUserAccount) obj;
if (winUserObj.getSecurityID() != null) {
haveSID = true;
if (!searchString.isEmpty()) {
searchString += " and ";
}
searchString += "SID \"" + winUserObj.getSecurityID().getValue().toString() + "\"";
}
}
if (!(haveHomeDir || haveUsername || haveSID)) {
return new ObservableResult(id, "AccountObject: No evaluatable fields found",
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
// Set warnings for any unsupported fields
setUnsupportedFieldWarnings();
// The assumption here is that there aren't going to be too many network shares, so we
// can cycle through all of them.
try {
List<BlackboardArtifact> finalHits = new ArrayList<BlackboardArtifact>();
Case case1 = Case.getCurrentCase();
SleuthkitCase sleuthkitCase = case1.getSleuthkitCase();
List<BlackboardArtifact> artList
= sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_ACCOUNT);
for (BlackboardArtifact art : artList) {
boolean foundHomeDirMatch = false;
boolean foundUsernameMatch = false;
boolean foundSIDMatch = false;
for (BlackboardAttribute attr : art.getAttributes()) {
if ((attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH.getTypeID())
&& (haveHomeDir)) {
foundHomeDirMatch = compareStringObject(userAccountObj.getHomeDirectory(), attr.getValueString());
}
if ((attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME.getTypeID())
&& (haveUsername)) {
foundUsernameMatch = compareStringObject(userAccountObj.getUsername(), attr.getValueString());
}
if ((attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_ID.getTypeID())
&& (haveSID) && (winUserObj != null)) {
foundSIDMatch = compareStringObject(winUserObj.getSecurityID(), attr.getValueString());
}
}
if (((!haveHomeDir) || foundHomeDirMatch)
&& ((!haveUsername) || foundUsernameMatch)
&& ((!haveSID) || foundSIDMatch)) {
finalHits.add(art);
}
}
// Check if we found any matches
if (!finalHits.isEmpty()) {
List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
for (BlackboardArtifact a : finalHits) {
artData.add(new StixArtifactData(a.getObjectID(), id, "Account"));
}
return new ObservableResult(id, "AccountObject: Found a match for " + searchString,
spacing, ObservableResult.ObservableState.TRUE, artData);
}
// Didn't find any matches
return new ObservableResult(id, "AccountObject: No matches found for " + searchString,
spacing, ObservableResult.ObservableState.FALSE, null);
} catch (TskCoreException ex) {
return new ObservableResult(id, "AccountObject: Exception during evaluation: " + ex.getLocalizedMessage(),
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
}
/**
* Set up the warning for any fields in the object that aren't supported.
*/
private void setUnsupportedFieldWarnings() {
List<String> fieldNames = new ArrayList<String>();
if (obj.getDescription() != null) {
fieldNames.add("Description");
}
if (obj.getDomain() != null) {
fieldNames.add("Domain");
}
if (obj.getAuthentications() != null) {
fieldNames.add("Authentication");
}
if (obj.getCreationDate() != null) {
fieldNames.add("Creation_Date");
}
if (obj.getModifiedDate() != null) {
fieldNames.add("Modified_Date");
}
if (obj.getLastAccessedTime() != null) {
fieldNames.add("Last_Accessed_Time");
}
if (obj instanceof UserAccountObjectType) {
UserAccountObjectType userAccountObj = (UserAccountObjectType) obj;
if (userAccountObj.getFullName() != null) {
fieldNames.add("Full_Name");
}
if (userAccountObj.getGroupList() != null) {
fieldNames.add("Group_List");
}
if (userAccountObj.getLastLogin() != null) {
fieldNames.add("Last_Login");
}
if (userAccountObj.getPrivilegeList() != null) {
fieldNames.add("Privilege_List");
}
if (userAccountObj.getScriptPath() != null) {
fieldNames.add("Script_Path");
}
if (userAccountObj.getUserPasswordAge() != null) {
fieldNames.add("User_Password_Age");
}
}
if (obj instanceof WindowsUserAccount) {
WindowsUserAccount winUserObj = (WindowsUserAccount) obj;
if (winUserObj.getSecurityType() != null) {
fieldNames.add("Security_Type");
}
}
String warningStr = "";
for (String name : fieldNames) {
if (!warningStr.isEmpty()) {
warningStr += ", ";
}
warningStr += name;
}
addWarning("Unsupported field(s): " + warningStr);
}
}

View File

@ -0,0 +1,186 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.modules.stix;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException;
import java.util.List;
import java.util.ArrayList;
import org.mitre.cybox.common_2.ConditionApplicationEnum;
import org.mitre.cybox.common_2.ConditionTypeEnum;
import org.mitre.cybox.objects.Address;
/**
*
*/
class EvalAddressObj extends EvaluatableObject {
private final Address obj;
public EvalAddressObj(Address a_obj, String a_id, String a_spacing) {
obj = a_obj;
id = a_id;
spacing = a_spacing;
}
@Override
public synchronized ObservableResult evaluate() {
setWarnings("");
if (obj.getAddressValue() == null) {
return new ObservableResult(id, "AddressObject: No address value field found",
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
String origAddressStr = obj.getAddressValue().getValue().toString();
// For now, we don't support "NONE" because it honestly doesn't seem like it
// would ever appear in practice.
if (((obj.getAddressValue().getApplyCondition() != null)
&& (obj.getAddressValue().getApplyCondition() == ConditionApplicationEnum.NONE))) {
return new ObservableResult(id, "AddressObject: Can not process apply condition " + obj.getAddressValue().getApplyCondition().toString()
+ " on Address object", spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
// Set warnings for any unsupported fields
setUnsupportedFieldWarnings();
Case case1 = Case.getCurrentCase();
SleuthkitCase sleuthkitCase = case1.getSleuthkitCase();
try {
// Need to check that every part of the string had at least one match
// in the AND case
boolean everyPartMatched = true;
List<BlackboardArtifact> combinedArts = new ArrayList<BlackboardArtifact>();
String searchString = "";
String[] parts = origAddressStr.split("##comma##");
for (String addressStr : parts) {
// Update the string to show in the results
if (!searchString.isEmpty()) {
if ((obj.getAddressValue().getApplyCondition() != null)
&& (obj.getAddressValue().getApplyCondition() == ConditionApplicationEnum.ALL)) {
searchString += " AND ";
} else {
searchString += " OR ";
}
}
searchString += addressStr;
if ((obj.getAddressValue().getCondition() == null)
|| (obj.getAddressValue().getCondition() == ConditionTypeEnum.EQUALS)) {
List<BlackboardArtifact> arts = sleuthkitCase.getBlackboardArtifacts(
BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD,
addressStr);
if (arts.isEmpty()) {
everyPartMatched = false;
} else {
combinedArts.addAll(arts);
}
} else {
// This is inefficient, but the easiest way to do it.
List<BlackboardArtifact> finalHits = new ArrayList<BlackboardArtifact>();
// Get all the URL artifacts
List<BlackboardArtifact> artList
= sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT);
for (BlackboardArtifact art : artList) {
for (BlackboardAttribute attr : art.getAttributes()) {
if (attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()) {
if (compareStringObject(addressStr, obj.getAddressValue().getCondition(),
obj.getAddressValue().getApplyCondition(), attr.getValueString())) {
finalHits.add(art);
}
}
}
}
if (finalHits.isEmpty()) {
everyPartMatched = false;
} else {
combinedArts.addAll(finalHits);
}
}
}
// If we're in the ALL case, make sure every piece matched
if ((obj.getAddressValue().getApplyCondition() != null)
&& (obj.getAddressValue().getApplyCondition() == ConditionApplicationEnum.ALL)
&& (!everyPartMatched)) {
return new ObservableResult(id, "AddressObject: No matches for " + searchString,
spacing, ObservableResult.ObservableState.FALSE, null);
}
if (!combinedArts.isEmpty()) {
List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
for (BlackboardArtifact a : combinedArts) {
artData.add(new StixArtifactData(a.getObjectID(), id, "AddressObject"));
}
return new ObservableResult(id, "AddressObject: Found a match for " + searchString,
spacing, ObservableResult.ObservableState.TRUE, artData);
}
return new ObservableResult(id, "AddressObject: Found no matches for " + searchString,
spacing, ObservableResult.ObservableState.FALSE, null);
} catch (TskCoreException ex) {
return new ObservableResult(id, "AddressObject: Exception during evaluation: " + ex.getLocalizedMessage(),
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
}
/**
* Set up the warning for any fields in the object that aren't supported.
*/
private void setUnsupportedFieldWarnings() {
List<String> fieldNames = new ArrayList<String>();
if (obj.getVLANName() != null) {
fieldNames.add("VLAN_Name");
}
if (obj.getVLANName() != null) {
fieldNames.add("VLAN_Num");
}
String warningStr = "";
for (String name : fieldNames) {
if (!warningStr.isEmpty()) {
warningStr += ", ";
}
warningStr += name;
}
addWarning("Unsupported field(s): " + warningStr);
}
}

View File

@ -0,0 +1,274 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.modules.stix;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException;
import java.util.List;
import java.util.ArrayList;
import org.mitre.cybox.objects.EmailMessage;
import org.mitre.cybox.objects.Address;
/**
*
*/
class EvalEmailObj extends EvaluatableObject {
private final EmailMessage obj;
private List<BlackboardArtifact> finalHits;
public EvalEmailObj(EmailMessage a_obj, String a_id, String a_spacing) {
obj = a_obj;
id = a_id;
spacing = a_spacing;
finalHits = null;
}
@Override
public synchronized ObservableResult evaluate() {
setWarnings("");
List<BlackboardArtifact> toHits = null;
boolean hadToFields = false;
List<BlackboardArtifact> ccHits = null;
boolean hadCcFields = false;
List<BlackboardArtifact> fromHits = null;
boolean hadFromField = false;
List<BlackboardArtifact> subjectHits = null;
boolean hadSubjectField = false;
if (obj.getHeader() != null) {
if ((obj.getHeader().getTo() != null)
&& (obj.getHeader().getTo().getRecipients() != null)
&& (!obj.getHeader().getTo().getRecipients().isEmpty())) {
for (Address addr : obj.getHeader().getTo().getRecipients()) {
if (addr.getAddressValue() != null) {
hadToFields = true;
try {
toHits = findArtifactsBySubstring(addr.getAddressValue(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_TO);
} catch (TskCoreException ex) {
addWarning(ex.getLocalizedMessage());
}
}
}
}
if ((obj.getHeader().getCC() != null)
&& (obj.getHeader().getCC().getRecipients() != null)
&& (!obj.getHeader().getCC().getRecipients().isEmpty())) {
for (Address addr : obj.getHeader().getCC().getRecipients()) {
if (addr.getAddressValue() != null) {
hadCcFields = true;
try {
ccHits = findArtifactsBySubstring(addr.getAddressValue(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_CC);
} catch (TskCoreException ex) {
addWarning(ex.getLocalizedMessage());
}
}
}
}
if ((obj.getHeader().getFrom() != null)
&& (obj.getHeader().getFrom().getAddressValue() != null)) {
hadFromField = true;
try {
fromHits = findArtifactsBySubstring(obj.getHeader().getFrom().getAddressValue(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_FROM);
} catch (TskCoreException ex) {
addWarning(ex.getLocalizedMessage());
}
}
if ((obj.getHeader().getSubject() != null)
&& (obj.getHeader().getSubject().getValue() != null)) {
hadSubjectField = true;
try {
subjectHits = findArtifactsBySubstring(obj.getHeader().getSubject(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT);
} catch (TskCoreException ex) {
addWarning(ex.getLocalizedMessage());
}
}
}
// Make sure at least one test had some data
if ((!hadToFields) && (!hadFromField) && (!hadCcFields) && (!hadSubjectField)) {
return new ObservableResult(id, "EmailMessage: Could not find any parsable EmailMessage fields "
+ getPrintableWarnings(),
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
// Check if there were more fields that aren't currently supported
String fieldNames = getListOfUnsupportedFields();
if (fieldNames.length() > 0) {
addWarning("Unsupported field(s) found: " + fieldNames);
}
// Find the artifacts that matched all of the fields
finalHits = null;
boolean finalHitsStarted = false;
if (hadToFields) {
combineHits(toHits, finalHitsStarted);
finalHitsStarted = true;
}
if (hadCcFields) {
combineHits(ccHits, finalHitsStarted);
finalHitsStarted = true;
}
if (hadFromField) {
combineHits(fromHits, finalHitsStarted);
finalHitsStarted = true;
}
if (hadSubjectField) {
combineHits(subjectHits, finalHitsStarted);
finalHitsStarted = true;
}
if (!finalHitsStarted) {
// We didn't find any fields that could be evaluated
return new ObservableResult(id, "EmailMessage: EmailObj parsing incomplete " + getPrintableWarnings(),
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
// If there are any artifacts left in finalHits, we have a match
if (finalHits.size() > 0) {
List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
for (BlackboardArtifact a : finalHits) {
artData.add(new StixArtifactData(a.getObjectID(), id, "EmailMessage"));
}
return new ObservableResult(id, "EmailMessage: " + finalHits.size() + " matching artifacts found " + getPrintableWarnings(),
spacing, ObservableResult.ObservableState.TRUE, artData);
} else {
return new ObservableResult(id, "EmailMessage: No matching artifacts found " + getPrintableWarnings(),
spacing, ObservableResult.ObservableState.FALSE, null);
}
}
/**
* Add a set of hits to the final set of hits. Removes any artifacts that
* aren't found in the new set. The final list is the artifacts found in all
* sets.
*
* @param newHits The new hits to add to the list
* @param finalHitsStarted Whether we've started the list or not
*/
private void combineHits(List<BlackboardArtifact> newHits, boolean finalHitsStarted) {
if (finalHitsStarted && (finalHits != null)) {
finalHits.retainAll(newHits);
} else {
finalHits = newHits;
}
}
/**
* Test to see if the Email Object has any fields set that we don't support
* right now.
*
* @return a list of unsupported fields found.
*/
private String getListOfUnsupportedFields() {
String fieldNames = "";
if (obj.getHeader() != null) {
if (obj.getHeader().getReceivedLines() != null) {
fieldNames += "Received_Lines ";
}
if (obj.getHeader().getBCC() != null) {
fieldNames += "BCC ";
}
if (obj.getHeader().getInReplyTo() != null) {
fieldNames += "In_Reply_To ";
}
if (obj.getHeader().getDate() != null) {
fieldNames += "Date ";
}
if (obj.getHeader().getMessageID() != null) {
fieldNames += "Message_ID ";
}
if (obj.getHeader().getSender() != null) {
fieldNames += "Sender ";
}
if (obj.getHeader().getReplyTo() != null) {
fieldNames += "Reply_To ";
}
if (obj.getHeader().getErrorsTo() != null) {
fieldNames += "Errors_To ";
}
if (obj.getHeader().getBoundary() != null) {
fieldNames += "Boundary ";
}
if (obj.getHeader().getContentType() != null) {
fieldNames += "Content_Type ";
}
if (obj.getHeader().getMIMEVersion() != null) {
fieldNames += "MIME_Version ";
}
if (obj.getHeader().getPrecedence() != null) {
fieldNames += "Precedence ";
}
if (obj.getHeader().getUserAgent() != null) {
fieldNames += "User_Agent ";
}
if (obj.getHeader().getXMailer() != null) {
fieldNames += "X_Mailer ";
}
if (obj.getHeader().getXOriginatingIP() != null) {
fieldNames += "X_Originiating_IP ";
}
if (obj.getHeader().getXPriority() != null) {
fieldNames += "X_Priority ";
}
}
if (obj.getEmailServer() != null) {
fieldNames += "Email_Server ";
}
if (obj.getRawBody() != null) {
fieldNames += "Raw_Body ";
}
if (obj.getRawHeader() != null) {
fieldNames += "Raw_Header ";
}
if (obj.getAttachments() != null) {
fieldNames += "Attachments ";
}
if (obj.getLinks() != null) {
fieldNames += "Links ";
}
return fieldNames;
}
}

View File

@ -0,0 +1,689 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.modules.stix;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException;
import java.util.List;
import java.util.ArrayList;
import java.util.Date;
import java.util.TimeZone;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import org.mitre.cybox.common_2.ConditionApplicationEnum;
import org.mitre.cybox.objects.FileObjectType;
import org.mitre.cybox.objects.WindowsExecutableFileObjectType;
import org.mitre.cybox.common_2.ConditionTypeEnum;
import org.mitre.cybox.common_2.DatatypeEnum;
import org.mitre.cybox.common_2.HashType;
import org.mitre.cybox.common_2.DateTimeObjectPropertyType;
import org.mitre.cybox.common_2.StringObjectPropertyType;
import org.mitre.cybox.common_2.UnsignedLongObjectPropertyType;
/**
*
*/
class EvalFileObj extends EvaluatableObject {
private final FileObjectType obj;
public EvalFileObj(FileObjectType a_obj, String a_id, String a_spacing) {
obj = a_obj;
id = a_id;
spacing = a_spacing;
}
@Override
public synchronized ObservableResult evaluate() {
Case case1 = Case.getCurrentCase();
SleuthkitCase sleuthkitCase = case1.getSleuthkitCase();
setWarnings("");
String whereClause = "";
if (obj.getSizeInBytes() != null) {
try {
String newClause = processULongObject(obj.getSizeInBytes(), "size");
whereClause = addClause(whereClause, newClause);
} catch (TskCoreException ex) {
addWarning(ex.getLocalizedMessage());
}
}
if (obj.getFileName() != null) {
try {
String newClause = processStringObject(obj.getFileName(), "name");
whereClause = addClause(whereClause, newClause);
} catch (TskCoreException ex) {
addWarning(ex.getLocalizedMessage());
}
}
if (obj.getFileExtension() != null) {
if ((obj.getFileExtension().getCondition() == null)
|| (obj.getFileExtension().getCondition() == ConditionTypeEnum.EQUALS)) {
String newClause = "name LIKE \'%" + obj.getFileExtension().getValue() + "\'";
whereClause = addClause(whereClause, newClause);
} else {
addWarning(
"Could not process condition " + obj.getFileExtension().getCondition().value() + " on file extension");
}
}
if (obj.getFilePath() != null) {
try {
String[] parts = obj.getFilePath().getValue().toString().split("##comma##");
String finalPathStr = "";
for (String filePath : parts) {
// First, we need to normalize the path
String currentFilePath = filePath;
// Remove the drive letter
if (currentFilePath.matches("^[A-Za-z]:.*")) {
currentFilePath = currentFilePath.substring(2);
}
// Change any backslashes to forward slashes
currentFilePath = currentFilePath.replace("\\", "/");
// The path needs to start with a slash
if (!currentFilePath.startsWith("/")) {
currentFilePath = "/" + currentFilePath;
}
// If the path does not end in a slash, the final part should be the file name.
if (!currentFilePath.endsWith("/")) {
int lastSlash = currentFilePath.lastIndexOf('/');
if (lastSlash >= 0) {
currentFilePath = currentFilePath.substring(0, lastSlash + 1);
}
}
// Reconstruct the path string (which may be multi-part)
if (!finalPathStr.isEmpty()) {
finalPathStr += "##comma##";
}
finalPathStr += currentFilePath;
}
String newClause = processStringObject(finalPathStr, obj.getFilePath().getCondition(),
obj.getFilePath().getApplyCondition(), "parent_path");
whereClause = addClause(whereClause, newClause);
} catch (TskCoreException ex) {
addWarning(ex.getLocalizedMessage());
}
}
if (obj.getCreatedTime() != null) {
try {
String newClause = processTimestampObject(obj.getCreatedTime(), "crtime");
whereClause = addClause(whereClause, newClause);
} catch (TskCoreException ex) {
addWarning(ex.getLocalizedMessage());
}
}
if (obj.getModifiedTime() != null) {
try {
String newClause = processTimestampObject(obj.getModifiedTime(), "mtime");
whereClause = addClause(whereClause, newClause);
} catch (TskCoreException ex) {
addWarning(ex.getLocalizedMessage());
}
}
if (obj.getAccessedTime() != null) {
try {
String newClause = processTimestampObject(obj.getAccessedTime(), "atime");
whereClause = addClause(whereClause, newClause);
} catch (TskCoreException ex) {
addWarning(ex.getLocalizedMessage());
}
}
if (obj.getHashes() != null) {
for (HashType h : obj.getHashes().getHashes()) {
if (h.getSimpleHashValue() != null) {
if (h.getType().getValue().equals("MD5")) {
String newClause = "md5=\'" + h.getSimpleHashValue().getValue() + "\'";
whereClause = addClause(whereClause, newClause);
} else {
addWarning("Could not process hash type " + h.getType().getValue().toString());
}
} else {
addWarning("Could not process non-simple hash value");
}
}
}
if (obj instanceof WindowsExecutableFileObjectType) {
WindowsExecutableFileObjectType winExe = (WindowsExecutableFileObjectType) obj;
if (winExe.getHeaders() != null) {
if (winExe.getHeaders().getFileHeader() != null) {
if (winExe.getHeaders().getFileHeader().getTimeDateStamp() != null) {
try {
String result = convertTimestampString(winExe.getHeaders().getFileHeader().getTimeDateStamp().getValue().toString());
String newClause = processNumericFields(result,
winExe.getHeaders().getFileHeader().getTimeDateStamp().getCondition(),
winExe.getHeaders().getFileHeader().getTimeDateStamp().getApplyCondition(),
"crtime");
whereClause = addClause(whereClause, newClause);
} catch (TskCoreException ex) {
addWarning(ex.getLocalizedMessage());
}
}
}
}
}
String unsupportedFields = listUnsupportedFields();
if (!unsupportedFields.isEmpty()) {
addWarning("Unsupported fields: " + unsupportedFields);
}
if (whereClause.length() > 0) {
try {
List<AbstractFile> matchingFiles = sleuthkitCase.findAllFilesWhere(whereClause);
if (!matchingFiles.isEmpty()) {
if (listSecondaryFields().isEmpty()) {
List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
for (AbstractFile a : matchingFiles) {
artData.add(new StixArtifactData(a, id, "FileObject"));
}
return new ObservableResult(id, "FileObject: Found " + matchingFiles.size() + " matches for " + whereClause + getPrintableWarnings(),
spacing, ObservableResult.ObservableState.TRUE, artData);
} else {
// We need to tag the matching files in Autopsy, so keep track of them
List<AbstractFile> secondaryHits = new ArrayList<AbstractFile>();
for (AbstractFile file : matchingFiles) {
boolean passedTests = true;
if (obj.isIsMasqueraded() != null) {
List<BlackboardArtifact> arts = file.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED);
boolean isMasq = false;
if (!arts.isEmpty()) {
isMasq = true;
}
if (obj.isIsMasqueraded() != isMasq) {
passedTests = false;
}
}
if (obj.getFileFormat() != null) {
boolean foundMatch = false;
String formatsFound = "";
List<BlackboardArtifact> arts = file.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO);
for (BlackboardArtifact artifact : arts) {
for (BlackboardAttribute attr : artifact.getAttributes()) {
if (attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FILE_TYPE_SIG.getTypeID()) {
if (!formatsFound.isEmpty()) {
formatsFound += ", ";
}
formatsFound += attr.getValueString();
if (attr.getValueString().equalsIgnoreCase(obj.getFileFormat().getValue().toString())) {
foundMatch = true;
}
// Try again looking for the last part of the Autopsy version as a substring
// of the STIX version
String type = attr.getValueString().replaceFirst("^.*/", "");
// This is reversed of how the comparison normally go
if (compareStringObject(type, ConditionTypeEnum.CONTAINS, null, obj.getFileFormat().getValue().toString())) {
foundMatch = true;
}
}
}
}
// It looks like the STIX file formats can be different than what Autopsy stores
// (mime vs. unix file), so don't kill a file based on this field not matching.
//if (!foundMatch) {
// passedTests = false;
//}
if (formatsFound.isEmpty()) {
addWarning("Warning: Did not match File_Format field " + obj.getFileFormat().getValue().toString()
+ " (no file formats found)");
} else {
if (!foundMatch) {
addWarning("Warning: Did not match File_Format field " + obj.getFileFormat().getValue().toString()
+ " against " + formatsFound);
}
}
}
if (passedTests) {
secondaryHits.add(file);
}
}
if (secondaryHits.isEmpty()) {
return new ObservableResult(id, "FileObject: Found " + matchingFiles.size() + " matches for " + whereClause
+ " but none for secondary tests on " + listSecondaryFields() + getPrintableWarnings(),
spacing, ObservableResult.ObservableState.FALSE, null);
} else {
List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
for (AbstractFile a : secondaryHits) {
artData.add(new StixArtifactData(a, id, "FileObject"));
}
return new ObservableResult(id, "FileObject: Found " + secondaryHits.size() + " matches for " + whereClause
+ " and secondary tests on " + listSecondaryFields() + getPrintableWarnings(),
spacing, ObservableResult.ObservableState.TRUE, artData);
}
}
} else {
return new ObservableResult(id, "FileObject: Found no matches for " + whereClause + getPrintableWarnings(),
spacing, ObservableResult.ObservableState.FALSE, null);
}
} catch (TskCoreException ex) {
return new ObservableResult(id, "FileObject: Exception during evaluation: " + ex.getLocalizedMessage(),
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
} else {
}
return new ObservableResult(id, "FileObject: No evaluatable fields " + getPrintableWarnings(), spacing,
ObservableResult.ObservableState.INDETERMINATE, null);
}
/**
* Create a list of secondary fields. These are the ones that we only test
* on the matches for the primary fields.
*
* @return List of secondary fields
*/
private String listSecondaryFields() {
String secondaryFields = "";
if (obj.isIsMasqueraded() != null) {
secondaryFields += "is_masqueraded ";
}
if (obj.getFileFormat() != null) {
secondaryFields += "File_Format ";
}
return secondaryFields;
}
/**
* List unsupported fields found in the object.
*
* @return List of unsupported fields
*/
private String listUnsupportedFields() {
String unsupportedFields = "";
if (obj.isIsPacked() != null) {
unsupportedFields += "is_packed ";
}
if (obj.getDevicePath() != null) {
unsupportedFields += "Device_Path ";
}
if (obj.getFullPath() != null) {
unsupportedFields += "Full_Path ";
}
if (obj.getMagicNumber() != null) {
unsupportedFields += "Magic_Number ";
}
if (obj.getDigitalSignatures() != null) {
unsupportedFields += "Digital_Signatures ";
}
if (obj.getFileAttributesList() != null) {
unsupportedFields += "File_Attributes_List ";
}
if (obj.getPermissions() != null) {
unsupportedFields += "Permissions ";
}
if (obj.getUserOwner() != null) {
unsupportedFields += "User_Owner ";
}
if (obj.getPackerList() != null) {
unsupportedFields += "Packer_List ";
}
if (obj.getPeakEntropy() != null) {
unsupportedFields += "Peak_Entropy ";
}
if (obj.getSymLinks() != null) {
unsupportedFields += "Sym_Links ";
}
if (obj.getByteRuns() != null) {
unsupportedFields += "Bytes_Runs ";
}
if (obj.getExtractedFeatures() != null) {
unsupportedFields += "Extracted_Features ";
}
if (obj.getEncryptionAlgorithm() != null) {
unsupportedFields += "Encryption_Algorithm ";
}
if (obj.getDecryptionKey() != null) {
unsupportedFields += "Decryption_Key ";
}
if (obj.getCompressionMethod() != null) {
unsupportedFields += "Compression_Method ";
}
if (obj.getCompressionVersion() != null) {
unsupportedFields += "Compression_Version ";
}
if (obj.getCompressionComment() != null) {
unsupportedFields += "Compression_Comment ";
}
return unsupportedFields;
}
/**
* Convert timestamp string into a long.
*
* @param timeStr
* @return
* @throws ParseException
*/
private static long convertTimestamp(String timeStr) throws ParseException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
Date parsedDate = dateFormat.parse(timeStr);
Long unixTime = parsedDate.getTime() / 1000;
return unixTime;
}
/**
* Return the SQL clause for an unsigned long object. Splits into fields and
* call the more generic version of the function.
*
* @param longObj The Cybox UnsignedLong object
* @param fieldName Name of the field to test against
* @return SQL clause
* @throws TskCoreException
*/
private static String processULongObject(UnsignedLongObjectPropertyType longObj, String fieldName)
throws TskCoreException {
return processNumericFields(longObj.getValue().toString(), longObj.getCondition(),
longObj.getApplyCondition(), fieldName);
}
/**
* Return the SQL clause for a numeric object.
*
* @param valueStr Value (as string)
* @param typeCondition Cybox condition
* @param applyCondition Cybox apply_condition
* @param fieldName Name of the field to test against
* @return SQL clause
* @throws TskCoreException
*/
private static String processNumericFields(String valueStr, ConditionTypeEnum typeCondition,
ConditionApplicationEnum applyCondition, String fieldName)
throws TskCoreException {
if ((typeCondition == null)
|| ((typeCondition != ConditionTypeEnum.INCLUSIVE_BETWEEN)
&& (typeCondition != ConditionTypeEnum.EXCLUSIVE_BETWEEN))) {
String fullClause = "";
if (valueStr.isEmpty()) {
throw new TskCoreException("Empty value field");
}
String[] parts = valueStr.split("##comma##");
for (String valuePart : parts) {
String partialClause;
if ((typeCondition == null)
|| (typeCondition == ConditionTypeEnum.EQUALS)) {
partialClause = fieldName + "=" + valuePart;
} else if (typeCondition == ConditionTypeEnum.DOES_NOT_EQUAL) {
partialClause = fieldName + "!=" + valuePart;
} else if (typeCondition == ConditionTypeEnum.GREATER_THAN) {
partialClause = fieldName + ">" + valuePart;
} else if (typeCondition == ConditionTypeEnum.GREATER_THAN_OR_EQUAL) {
partialClause = fieldName + ">=" + valuePart;
} else if (typeCondition == ConditionTypeEnum.LESS_THAN) {
partialClause = fieldName + "<" + valuePart;
} else if (typeCondition == ConditionTypeEnum.LESS_THAN_OR_EQUAL) {
partialClause = fieldName + "<=" + valuePart;
} else {
throw new TskCoreException("Could not process condition " + typeCondition.value() + " on " + fieldName);
}
if (fullClause.isEmpty()) {
if (parts.length > 1) {
fullClause += "( ";
}
if (applyCondition == ConditionApplicationEnum.NONE) {
fullClause += " NOT ";
}
fullClause += partialClause;
} else {
if (applyCondition == ConditionApplicationEnum.ALL) {
fullClause += " AND " + partialClause;
} else if (applyCondition == ConditionApplicationEnum.NONE) {
fullClause += " AND NOT " + partialClause;
} else {
fullClause += " OR " + partialClause;
}
}
}
if (parts.length > 1) {
fullClause += " )";
}
return fullClause;
} else {
// I don't think apply conditions make sense for these two.
if (typeCondition == ConditionTypeEnum.INCLUSIVE_BETWEEN) {
String[] parts = valueStr.split("##comma##");
if (parts.length != 2) {
throw new TskCoreException("Unexpected number of arguments in INCLUSIVE_BETWEEN on " + fieldName
+ "(" + valueStr + ")");
}
return (fieldName + ">=" + parts[0] + " AND " + fieldName + "<=" + parts[1]);
} else {
String[] parts = valueStr.split("##comma##");
if (parts.length != 2) {
throw new TskCoreException("Unexpected number of arguments in EXCLUSIVE_BETWEEN on " + fieldName
+ "(" + valueStr + ")");
}
return (fieldName + ">" + parts[0] + " AND " + fieldName + "<" + parts[1]);
}
}
}
/**
* Return the SQL clause for a String object
*
* @param stringObj The full Cybox String object
* @param fieldName Name of the field we're testing against
* @return SQL clause
* @throws TskCoreException
*/
private static String processStringObject(StringObjectPropertyType stringObj, String fieldName)
throws TskCoreException {
return processStringObject(stringObj.getValue().toString(), stringObj.getCondition(),
stringObj.getApplyCondition(), fieldName);
}
/**
* Return the SQL clause for a String object
*
* @param valueStr Value as a string
* @param condition Cybox condition
* @param applyCondition Cybox apply_condition
* @param fieldName Name of the field we're testing against
* @return SQL clause
* @throws TskCoreException
*/
public static String processStringObject(String valueStr, ConditionTypeEnum condition,
ConditionApplicationEnum applyCondition, String fieldName)
throws TskCoreException {
String fullClause = "";
String lowerFieldName = "lower(" + fieldName + ")";
if (valueStr.isEmpty()) {
throw new TskCoreException("Empty value field");
}
String[] parts = valueStr.split("##comma##");
for (String value : parts) {
String lowerValue = value.toLowerCase();
String partialClause;
if ((condition == null)
|| (condition == ConditionTypeEnum.EQUALS)) {
partialClause = lowerFieldName + "=\'" + lowerValue + "\'";
} else if (condition == ConditionTypeEnum.DOES_NOT_EQUAL) {
partialClause = lowerFieldName + " !=\'%" + lowerValue + "%\'";
} else if (condition == ConditionTypeEnum.CONTAINS) {
partialClause = lowerFieldName + " LIKE \'%" + lowerValue + "%\'";
} else if (condition == ConditionTypeEnum.DOES_NOT_CONTAIN) {
partialClause = lowerFieldName + " NOT LIKE \'%" + lowerValue + "%\'";
} else if (condition == ConditionTypeEnum.STARTS_WITH) {
partialClause = lowerFieldName + " LIKE \'" + lowerValue + "%\'";
} else if (condition == ConditionTypeEnum.ENDS_WITH) {
partialClause = lowerFieldName + " LIKE \'%" + lowerValue + "\'";
} else {
throw new TskCoreException("Could not process condition " + condition.value() + " on " + fieldName);
}
if (fullClause.isEmpty()) {
if (parts.length > 1) {
fullClause += "( ";
}
if (applyCondition == ConditionApplicationEnum.NONE) {
fullClause += " NOT ";
}
fullClause += partialClause;
} else {
if (applyCondition == ConditionApplicationEnum.ALL) {
fullClause += " AND " + partialClause;
} else if (applyCondition == ConditionApplicationEnum.NONE) {
fullClause += " AND NOT " + partialClause;
} else {
fullClause += " OR " + partialClause;
}
}
}
if (parts.length > 1) {
fullClause += " )";
}
return fullClause;
}
/**
* Create the SQL clause for a timestamp object. Converts the time into a
* numeric field and then creates the clause from that.
*
* @param dateObj Cybox DateTimeObject
* @param fieldName Name of the field we're testing against
* @return SQL clause
* @throws TskCoreException
*/
private static String processTimestampObject(DateTimeObjectPropertyType dateObj, String fieldName)
throws TskCoreException {
if (DatatypeEnum.DATE_TIME == dateObj.getDatatype()) {
// Change the string into unix timestamps
String result = convertTimestampString(dateObj.getValue().toString());
return processNumericFields(result, dateObj.getCondition(), dateObj.getApplyCondition(), fieldName);
} else {
throw new TskCoreException("Found non DATE_TIME field on " + fieldName);
}
}
/**
* Convert a timestamp string into a numeric one. Leave it as a string since
* that's what we get from other object types.
*
* @param timestampStr
* @return String version with timestamps replaced by numeric values
* @throws TskCoreException
*/
private static String convertTimestampString(String timestampStr)
throws TskCoreException {
try {
String result = "";
if (timestampStr.length() > 0) {
String[] parts = timestampStr.split("##comma##");
for (int i = 0; i < parts.length - 1; i++) {
long unixTime = convertTimestamp(parts[i]);
result += unixTime + "##comma##";
}
result += convertTimestamp(parts[parts.length - 1]);
}
return result;
} catch (java.text.ParseException ex) {
throw new TskCoreException("Error parsing timestamp string " + timestampStr);
}
}
/**
* Add a new clause to the existing clause
*
* @param a_clause Current clause
* @param a_newClause New clause
* @return Full clause
*/
private static String addClause(String a_clause, String a_newClause) {
if ((a_clause == null) || a_clause.isEmpty()) {
return a_newClause;
}
return (a_clause + " AND " + a_newClause);
}
}

View File

@ -0,0 +1,161 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.modules.stix;
import java.util.ArrayList;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException;
import java.util.List;
import org.mitre.cybox.common_2.ConditionApplicationEnum;
import org.mitre.cybox.objects.WindowsNetworkShare;
/**
*
*/
class EvalNetworkShareObj extends EvaluatableObject {
private final WindowsNetworkShare obj;
public EvalNetworkShareObj(WindowsNetworkShare a_obj, String a_id, String a_spacing) {
obj = a_obj;
id = a_id;
spacing = a_spacing;
}
@Override
public synchronized ObservableResult evaluate() {
setWarnings("");
if ((obj.getNetname() == null) && (obj.getLocalPath() == null)) {
return new ObservableResult(id, "NetworkShareObjet: No remote name or local path found",
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
// For displaying what we were looking for in the results
String searchString = "";
if (obj.getNetname() != null) {
searchString += "Netname \"" + obj.getNetname().getValue() + "\"";
// The apply conditions ALL or NONE probably won't work correctly. Neither seems
// all that likely to come up in practice, so just give a warning.
if ((obj.getNetname().getApplyCondition() != null)
&& (obj.getNetname().getApplyCondition() != ConditionApplicationEnum.ANY)) {
addWarning("Apply condition " + obj.getNetname().getApplyCondition().value()
+ " may not work correctly");
}
}
if (obj.getLocalPath() != null) {
if (!searchString.isEmpty()) {
searchString += " and ";
}
searchString += "LocalPath \"" + obj.getLocalPath().getValue() + "\"";
// Same as above - the apply conditions ALL or NONE probably won't work correctly. Neither seems
// all that likely to come up in practice, so just give a warning.
if ((obj.getLocalPath().getApplyCondition() != null)
&& (obj.getLocalPath().getApplyCondition() != ConditionApplicationEnum.ANY)) {
addWarning("Apply condition " + obj.getLocalPath().getApplyCondition().value()
+ " may not work correctly");
}
}
setUnsupportedFieldWarnings();
// The assumption here is that there aren't going to be too many network shares, so we
// can cycle through all of them.
try {
List<BlackboardArtifact> finalHits = new ArrayList<BlackboardArtifact>();
Case case1 = Case.getCurrentCase();
SleuthkitCase sleuthkitCase = case1.getSleuthkitCase();
List<BlackboardArtifact> artList
= sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_REMOTE_DRIVE);
for (BlackboardArtifact art : artList) {
boolean foundRemotePathMatch = false;
boolean foundLocalPathMatch = false;
for (BlackboardAttribute attr : art.getAttributes()) {
if ((attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_REMOTE_PATH.getTypeID())
&& (obj.getNetname() != null)) {
foundRemotePathMatch = compareStringObject(obj.getNetname(), attr.getValueString());
}
if ((attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCAL_PATH.getTypeID())
&& (obj.getLocalPath() != null)) {
foundLocalPathMatch = compareStringObject(obj.getLocalPath(), attr.getValueString());
}
}
// Check whether we found everything we were looking for
if (((foundRemotePathMatch) || (obj.getNetname() == null))
&& ((foundLocalPathMatch) || (obj.getLocalPath() == null))) {
finalHits.add(art);
}
}
// Check if we found any matches
if (!finalHits.isEmpty()) {
List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
for (BlackboardArtifact a : finalHits) {
artData.add(new StixArtifactData(a.getObjectID(), id, "NetworkShare"));
}
return new ObservableResult(id, "NetworkShareObject: Found a match for " + searchString,
spacing, ObservableResult.ObservableState.TRUE, artData);
}
// Didn't find any matches
return new ObservableResult(id, "NetworkObject: No matches found for " + searchString,
spacing, ObservableResult.ObservableState.FALSE, null);
} catch (TskCoreException ex) {
return new ObservableResult(id, "NetworkObject: Exception during evaluation: " + ex.getLocalizedMessage(),
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
}
private void setUnsupportedFieldWarnings() {
List<String> fieldNames = new ArrayList<String>();
if (obj.getCurrentUses() != null) {
fieldNames.add("Current_Uses");
}
if (obj.getMaxUses() != null) {
fieldNames.add("Max_Uses");
}
if (obj.getType() != null) {
fieldNames.add("Type");
}
String warningStr = "";
for (String name : fieldNames) {
if (!warningStr.isEmpty()) {
warningStr += ", ";
}
warningStr += name;
}
addWarning("Unsupported field(s): " + warningStr);
}
}

View File

@ -0,0 +1,452 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.modules.stix;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile;
import java.util.List;
import java.util.ArrayList;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.File;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import org.mitre.cybox.objects.WindowsRegistryKey;
import org.mitre.cybox.common_2.ConditionTypeEnum;
import com.williballenthin.rejistry.*;
/**
*
*/
class EvalRegistryObj extends EvaluatableObject {
private final WindowsRegistryKey obj;
private final List<RegistryFileInfo> regFiles = new ArrayList<RegistryFileInfo>();
public EvalRegistryObj(WindowsRegistryKey a_obj, String a_id, String a_spacing, List<RegistryFileInfo> a_regFiles) {
obj = a_obj;
id = a_id;
spacing = a_spacing;
regFiles.addAll(a_regFiles);
}
private EvalRegistryObj() {
obj = null;
id = null;
spacing = "";
}
@Override
public synchronized ObservableResult evaluate() {
setWarnings("");
// Key name is required
if (obj.getKey() == null) {
return new ObservableResult(id, "RegistryObject: No key found",
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
// For now, only support a full string match
if (!((obj.getKey().getCondition() == null)
|| (obj.getKey().getCondition() == ConditionTypeEnum.EQUALS))) {
return new ObservableResult(id, "RegistryObject: Can not support condition " + obj.getKey().getCondition()
+ " on Key field",
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
setUnsupportedFieldWarnings();
// Make a list of hives to test
List<RegistryFileInfo> hiveList = new ArrayList<RegistryFileInfo>();
if (obj.getHive() == null) {
// If the hive field is missing, add everything
hiveList.addAll(regFiles);
} else if (obj.getHive().getValue().toString().startsWith("HKEY")) {
// If the hive name is HKEY_LOCAL_MACHINE, add the ones from the config directory.
// Otherwise, add the others
for (RegistryFileInfo regFile : regFiles) {
if (regFile.abstractFile.getParentPath() != null) {
Pattern pattern = Pattern.compile("system32", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(regFile.abstractFile.getParentPath());
if (matcher.find()) {
// Looking for system files and found one, so add it to the list
if (obj.getHive().getValue().toString().equalsIgnoreCase("HKEY_LOCAL_MACHINE")) {
hiveList.add(regFile);
}
} else {
// Looking for non-system files and found one, so add it to the list
if (!obj.getHive().getValue().toString().equalsIgnoreCase("HKEY_LOCAL_MACHINE")) {
hiveList.add(regFile);
}
}
}
}
} else {
// Otherwise, try to match the name
String stixHiveName = obj.getHive().getValue().toString();
// The temp files will end \Temp\STIX\(hive)_(number)
Pattern pattern = Pattern.compile("Temp.STIX." + stixHiveName, Pattern.CASE_INSENSITIVE);
for (RegistryFileInfo hive : regFiles) {
Matcher matcher = pattern.matcher(hive.tempFileName);
if (matcher.find()) {
hiveList.add(hive);
}
}
// If nothing matched, add all the files
if (hiveList.isEmpty()) {
hiveList.addAll(regFiles);
}
}
// This is unlikely to happen unless we have no registry files to test against
if (hiveList.isEmpty()) {
return new ObservableResult(id, "RegistryObject: No matching registry hives found",
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
for (RegistryFileInfo hive : hiveList) {
try {
ObservableResult result = testRegistryFile(hive);
if (result.isTrue()) {
return result;
}
} catch (Exception ex) {
// The registry parser seems to throw lots of different types of exceptions,
// so make sure to catch them all by this point. Malformed registry files
// in particular cause problems.
addWarning("Error processing registry file " + hive);
}
}
if (obj.getHive() == null) {
return new ObservableResult(id, "RegistryObject: Could not find key " + obj.getKey().getValue(),
spacing, ObservableResult.ObservableState.FALSE, null);
}
return new ObservableResult(id, "RegistryObject: Could not find key " + obj.getKey().getValue()
+ " in hive " + obj.getHive().getValue(),
spacing, ObservableResult.ObservableState.FALSE, null);
}
/**
* Test the Registry object against one registry file.
*
* @param a_regInfo The registry file
* @return Result of the test
*/
private ObservableResult testRegistryFile(RegistryFileInfo a_regInfo) {
try {
RegistryKey root = openRegistry(a_regInfo.tempFileName);
RegistryKey result = findKey(root, obj.getKey().getValue().toString());
if (result == null) {
// Take another shot looking for the key minus the first part of the path (sometimes the
// hive file name is here). This should only happen if the hive name started
// with "HKEY"
if((obj.getHive() != null)
&& obj.getHive().getValue().toString().startsWith("HKEY")){
String[] parts = obj.getKey().getValue().toString().split("\\\\");
String newKey = "";
for (int i = 1; i < parts.length; i++) {
if (newKey.length() > 0) {
newKey += "\\";
}
newKey += parts[i];
}
result = findKey(root, newKey);
}
if (result == null) {
return new ObservableResult(id, "RegistryObject: Could not find key " + obj.getKey().getValue(),
spacing, ObservableResult.ObservableState.FALSE, null);
}
}
if ((obj.getValues() == null) || (obj.getValues().getValues().isEmpty())) {
// No values to test
List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
artData.add(new StixArtifactData(a_regInfo.abstractFile.getId(), id, "Registry"));
return new ObservableResult(id, "RegistryObject: Found key " + obj.getKey().getValue(),
spacing, ObservableResult.ObservableState.TRUE, artData);
}
// Test all the values
for (org.mitre.cybox.objects.RegistryValueType stixRegValue : obj.getValues().getValues()) {
try {
for (RegistryValue valFromFile : result.getValueList()) {
// Test if the name field matches (if present)
boolean nameSuccess = true; // True if the name matches or isn't present
if (stixRegValue.getName() != null) {
try {
nameSuccess = compareStringObject(stixRegValue.getName(), valFromFile.getName());
} catch (UnsupportedEncodingException ex) {
nameSuccess = false;
}
}
boolean valueSuccess = true;
if (nameSuccess && (stixRegValue.getData() != null)) {
switch (valFromFile.getValueType()) {
case REG_SZ:
case REG_EXPAND_SZ:
try {
valueSuccess = compareStringObject(stixRegValue.getData(),
valFromFile.getValue().getAsString());
} catch (UnsupportedEncodingException ex) {
valueSuccess = false;
}
break;
case REG_DWORD:
case REG_BIG_ENDIAN:
case REG_QWORD:
// Only support "equals" for now.
if ((stixRegValue.getData().getCondition() == null)
|| (stixRegValue.getData().getCondition() == ConditionTypeEnum.EQUALS)) {
// Try to convert the STIX string to a long
try {
long stixValue = Long.decode(stixRegValue.getData().getValue().toString());
try {
valueSuccess = (stixValue == valFromFile.getValue().getAsNumber());
} catch (UnsupportedEncodingException ex) {
valueSuccess = false;
}
} catch (NumberFormatException ex) {
// We probably weren't looking at a numeric field to begin with,
// so getting this exception isn't really an error.
valueSuccess = false;
}
} else {
valueSuccess = false;
}
break;
default:
// Nothing to do here. These are the types we don't handle:
// REG_BIN, REG_FULL_RESOURCE_DESCRIPTOR, REG_LINK, REG_MULTI_SZ, REG_NONE,
// REG_RESOURCE_LIST, REG_RESOURCE_REQUIREMENTS_LIST
}
}
if (nameSuccess && valueSuccess) {
// Found a match for all values
List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
artData.add(new StixArtifactData(a_regInfo.abstractFile.getId(), id, "Registry"));
return new ObservableResult(id, "RegistryObject: Found key " + obj.getKey().getValue()
+ " and value " + stixRegValue.getName().getValue().toString()
+ " = " + stixRegValue.getData().getValue().toString(),
spacing, ObservableResult.ObservableState.TRUE, artData);
}
}
} catch (Exception ex) {
// Broad catch here becase the registry parser can create all kinds of exceptions beyond what it reports.
return new ObservableResult(id, "RegistryObject: Exception during evaluation: " + ex.getLocalizedMessage(),
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
}
} catch (TskCoreException ex) {
return new ObservableResult(id, "RegistryObject: Exception during evaluation: " + ex.getLocalizedMessage(),
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
return new ObservableResult(id, "RegistryObject: Not done",
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
public RegistryKey openRegistry(String hive) throws TskCoreException {
try {
RegistryHiveFile regFile = new RegistryHiveFile(new File(hive));
RegistryKey root = regFile.getRoot();
return root;
} catch (IOException ex) {
throw new TskCoreException("Error opening registry file - " + ex.getLocalizedMessage());
} catch (RegistryParseException ex) {
throw new TskCoreException("Error opening root node of registry - " + ex.getLocalizedMessage());
}
}
/**
* Go down the registry tree to find a key with the given name.
*
* @param root Root of the registry hive
* @param name Name of the subkey to seach for
* @return The matching subkey or null if not found
*/
public RegistryKey findKey(RegistryKey root, String name) {
RegistryKey currentKey = root;
// Split the key name into parts
String[] parts = name.split("\\\\");
for (String part : parts) {
if (part.length() > 0) {
try {
currentKey = currentKey.getSubkey(part);
} catch (Exception ex) {
// We get an exception if the value wasn't found (not a RegistryParseException).
// There doesn't seem to be a cleaner way to test for existance without cycling though
// everything ourselves. (Broad catch because things other than RegistryParseException
// can happen)
return null;
}
}
}
// If we make it this far, we've found it
return currentKey;
}
/**
* Copy all registry hives to the temp directory and return the list of
* created files.
*
* @return Paths to copied registry files.
*/
public static List<RegistryFileInfo> copyRegistryFiles() throws TskCoreException {
// First get all the abstract files
List<AbstractFile> regFilesAbstract = findRegistryFiles();
// List to hold all the extracted file names plus their abstract file
List<RegistryFileInfo> regFilesLocal = new ArrayList<RegistryFileInfo>();
// Make the temp directory
String tmpDir = Case.getCurrentCase().getTempDirectory() + File.separator + "STIX"; //NON-NLS
File dir = new File(tmpDir);
if (dir.exists() == false) {
dir.mkdirs();
}
long index = 1;
for (AbstractFile regFile : regFilesAbstract) {
String regFileName = regFile.getName();
String regFileNameLocal = tmpDir + File.separator + regFileName + "_" + index;
File regFileNameLocalFile = new File(regFileNameLocal);
try {
// Don't save any unallocated versions
if (regFile.getMetaFlagsAsString().contains("Allocated")) {
ContentUtils.writeToFile(regFile, regFileNameLocalFile);
regFilesLocal.add(new EvalRegistryObj().new RegistryFileInfo(regFile, regFileNameLocal));
}
} catch (IOException ex) {
throw new TskCoreException(ex.getLocalizedMessage());
}
index++;
}
return regFilesLocal;
}
/**
* Search for the registry hives on the system. Mostly copied from
* RecentActivity
*/
private static List<AbstractFile> findRegistryFiles() throws TskCoreException {
List<AbstractFile> registryFiles = new ArrayList<AbstractFile>();
org.sleuthkit.autopsy.casemodule.services.FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
for (Content ds : Case.getCurrentCase().getDataSources()) {
// find the user-specific ntuser-dat files
registryFiles.addAll(fileManager.findFiles(ds, "ntuser.dat")); //NON-NLS
// find the system hives
String[] regFileNames = new String[]{"system", "software", "security", "sam"}; //NON-NLS
for (String regFileName : regFileNames) {
List<AbstractFile> allRegistryFiles = fileManager.findFiles(ds, regFileName, "/system32/config"); //NON-NLS
for (AbstractFile regFile : allRegistryFiles) {
// Don't want anything from regback
if (!regFile.getParentPath().contains("RegBack")) {
registryFiles.add(regFile);
}
}
}
}
return registryFiles;
}
private void setUnsupportedFieldWarnings() {
List<String> fieldNames = new ArrayList<String>();
if (obj.getNumberValues() != null) {
fieldNames.add("Number_Values");
}
if (obj.getModifiedTime() != null) {
fieldNames.add("Modified_Time");
}
if (obj.getCreatorUsername() != null) {
fieldNames.add("Creator_Username");
}
if (obj.getHandleList() != null) {
fieldNames.add("Handle_List");
}
if (obj.getNumberSubkeys() != null) {
fieldNames.add("Number_Subkeys");
}
if (obj.getSubkeys() != null) {
fieldNames.add("Subkeys");
}
if (obj.getByteRuns() != null) {
fieldNames.add("Byte_Runs");
}
String warningStr = "";
for (String name : fieldNames) {
if (!warningStr.isEmpty()) {
warningStr += ", ";
}
warningStr += name;
}
addWarning("Unsupported field(s): " + warningStr);
}
/**
* Class to keep track of the abstract file and temp file that goes with
* each registry hive.
*/
public class RegistryFileInfo {
private final AbstractFile abstractFile;
private final String tempFileName;
public RegistryFileInfo(AbstractFile a_abstractFile, String a_tempFileName) {
abstractFile = a_abstractFile;
tempFileName = a_tempFileName;
}
}
}

View File

@ -0,0 +1,302 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.modules.stix;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.OSInfo;
import org.sleuthkit.datamodel.OSUtility;
import java.util.List;
import java.util.ArrayList;
import org.mitre.cybox.objects.SystemObjectType;
import org.mitre.cybox.objects.WindowsSystem;
/**
*
*/
class EvalSystemObj extends EvaluatableObject {
private final SystemObjectType obj;
public EvalSystemObj(SystemObjectType a_obj, String a_id, String a_spacing) {
obj = a_obj;
id = a_id;
spacing = a_spacing;
}
@Override
public synchronized ObservableResult evaluate() {
setWarnings("");
// For displaying what we were looking for in the results
String searchString = "";
// Check which fields are present and record them
boolean haveHostname = false;
// boolean haveDomain = false;
boolean haveProcArch = false;
boolean haveTempDir = false;
boolean haveProductName = false;
boolean haveSystemRoot = false;
boolean haveProductID = false;
boolean haveOwner = false;
boolean haveOrganization = false;
if (obj.getHostname() != null) {
haveHostname = true;
searchString = "Hostname \"" + obj.getHostname().getValue().toString() + "\"";
}
if(obj.getProcessorArchitecture() != null){
haveProcArch = true;
if(! searchString.isEmpty()){
searchString += " and ";
}
searchString += "Processor architecture \"" + obj.getProcessorArchitecture().getValue().toString() + "\"";
}
WindowsSystem winSysObj = null;
if (obj instanceof WindowsSystem) {
winSysObj = (WindowsSystem) obj;
if (winSysObj.getProductID() != null) {
haveProductID = true;
if (!searchString.isEmpty()) {
searchString += " and ";
}
searchString += "Product ID \"" + winSysObj.getProductID().getValue().toString() + "\"";
}
if (winSysObj.getProductName() != null) {
haveProductName = true;
if (!searchString.isEmpty()) {
searchString += " and ";
}
searchString += "Product Name \"" + winSysObj.getProductName().getValue().toString() + "\"";
}
if (winSysObj.getRegisteredOrganization() != null) {
haveOrganization = true;
if (!searchString.isEmpty()) {
searchString += " and ";
}
searchString += "Registered Org \"" + winSysObj.getRegisteredOrganization().getValue().toString() + "\"";
}
if (winSysObj.getRegisteredOwner() != null) {
haveOwner = true;
if (!searchString.isEmpty()) {
searchString += " and ";
}
searchString += "Registered Owner \"" + winSysObj.getRegisteredOwner().getValue().toString() + "\"";
}
if (winSysObj.getWindowsSystemDirectory() != null) {
haveSystemRoot = true;
if (!searchString.isEmpty()) {
searchString += " and ";
}
searchString += "System root \"" + winSysObj.getWindowsSystemDirectory().getValue().toString() + "\"";
}
if (winSysObj.getWindowsTempDirectory() != null) {
haveTempDir = true;
if (!searchString.isEmpty()) {
searchString += " and ";
}
searchString += "Temp dir \"" + winSysObj.getWindowsTempDirectory().getValue().toString() + "\"";
}
}
// Return if we have nothing to search for
if (!(haveHostname || haveProcArch
|| haveTempDir || haveProductName || haveSystemRoot || haveProductID
|| haveOwner || haveOrganization)) {
return new ObservableResult(id, "SystemObject: No evaluatable fields found",
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
setUnsupportedFieldWarnings();
try {
Case case1 = Case.getCurrentCase();
SleuthkitCase sleuthkitCase = case1.getSleuthkitCase();
List<OSInfo> osInfoList = OSUtility.getOSInfo(sleuthkitCase);
List<BlackboardArtifact> finalHits = new ArrayList<BlackboardArtifact>();
if (!osInfoList.isEmpty()) {
for (OSInfo info : osInfoList) {
boolean foundHostnameMatch = false;
//boolean foundDomainMatch = false;
boolean foundProcArchMatch = false;
boolean foundTempDirMatch = false;
boolean foundProductNameMatch = false;
boolean foundSystemRootMatch = false;
boolean foundProductIDMatch = false;
boolean foundOwnerMatch = false;
boolean foundOrganizationMatch = false;
if (haveHostname) {
foundHostnameMatch = compareStringObject(obj.getHostname(), info.getCompName());
}
if (haveProcArch) {
foundProcArchMatch = compareStringObject(obj.getProcessorArchitecture().getValue().toString(),
obj.getProcessorArchitecture().getCondition(),
obj.getProcessorArchitecture().getApplyCondition(),
info.getAttributeValue(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROCESSOR_ARCHITECTURE));
}
if (haveTempDir && (winSysObj != null)) {
foundTempDirMatch = compareStringObject(winSysObj.getWindowsTempDirectory(),
info.getAttributeValue(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEMP_DIR));
}
if (haveProductName && (winSysObj != null)) {
foundProductNameMatch = compareStringObject(winSysObj.getProductName(),
info.getAttributeValue(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME));
}
if (haveSystemRoot && (winSysObj != null)) {
foundSystemRootMatch = compareStringObject(winSysObj.getWindowsSystemDirectory(),
info.getAttributeValue(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH));
}
if (haveProductID && (winSysObj != null)) {
foundProductIDMatch = compareStringObject(winSysObj.getProductID(),
info.getAttributeValue(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PRODUCT_ID));
}
if (haveOwner && (winSysObj != null)) {
foundOwnerMatch = compareStringObject(winSysObj.getRegisteredOwner(),
info.getAttributeValue(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_OWNER));
}
if (haveOrganization && (winSysObj != null)) {
foundOrganizationMatch = compareStringObject(winSysObj.getRegisteredOrganization(),
info.getAttributeValue(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ORGANIZATION));
}
if (((!haveHostname) || foundHostnameMatch)
&& ((!haveProcArch) || foundProcArchMatch)
&& ((!haveTempDir) || foundTempDirMatch)
&& ((!haveProductName) || foundProductNameMatch)
&& ((!haveSystemRoot) || foundSystemRootMatch)
&& ((!haveProductID) || foundProductIDMatch)
&& ((!haveOwner) || foundOwnerMatch)
&& ((!haveOrganization) || foundOrganizationMatch)) {
finalHits.addAll(info.getArtifacts());
}
}
if (!finalHits.isEmpty()) {
List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
for (BlackboardArtifact a : finalHits) {
artData.add(new StixArtifactData(a.getObjectID(), id, "System"));
}
return new ObservableResult(id, "SystemObject: Found a match for " + searchString,
spacing, ObservableResult.ObservableState.TRUE, artData);
}
// Didn't find any matches
return new ObservableResult(id, "SystemObject: No matches found for " + searchString,
spacing, ObservableResult.ObservableState.FALSE, null);
} else {
return new ObservableResult(id, "SystemObject: No OS artifacts found",
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
} catch (TskCoreException ex) {
return new ObservableResult(id, "SystemObject: Exception during evaluation: " + ex.getLocalizedMessage(),
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
}
/**
* Set up the warning for any fields in the object that aren't supported.
*/
private void setUnsupportedFieldWarnings() {
List<String> fieldNames = new ArrayList<String>();
if (obj.getAvailablePhysicalMemory() != null) {
fieldNames.add("Available_Physical_Memory");
}
if (obj.getBIOSInfo() != null) {
fieldNames.add("BIOS_Info");
}
if (obj.getDate() != null) {
fieldNames.add("Date");
}
if (obj.getLocalTime() != null) {
fieldNames.add("Local_Time");
}
if (obj.getNetworkInterfaceList() != null) {
fieldNames.add("Network_Interface_List");
}
if (obj.getOS() != null) {
fieldNames.add("OS");
}
if(obj.getProcessor() != null){
fieldNames.add("Processor");
}
if (obj.getSystemTime() != null) {
fieldNames.add("System_Time");
}
if (obj.getTimezoneDST() != null) {
fieldNames.add("Timezone_DST");
}
if (obj.getTimezoneStandard() != null) {
fieldNames.add("Timezone_Standard");
}
if (obj.getTotalPhysicalMemory() != null) {
fieldNames.add("Total_Physical_Memory");
}
if (obj.getUptime() != null) {
fieldNames.add("Uptime");
}
if (obj.getUsername() != null) {
fieldNames.add("Username");
}
if (obj instanceof WindowsSystem) {
WindowsSystem winSysObj = (WindowsSystem) obj;
if (winSysObj.getDomains() != null) {
fieldNames.add("Domain");
}
if (winSysObj.getGlobalFlagList() != null) {
fieldNames.add("Global_Flag_List");
}
if (winSysObj.getNetBIOSName() != null) {
fieldNames.add("NetBIOS_Name");
}
if (winSysObj.getOpenHandleList() != null) {
fieldNames.add("Open_Handle_List");
}
if (winSysObj.getWindowsDirectory() != null) {
fieldNames.add("Windows_Directory");
}
}
String warningStr = "";
for (String name : fieldNames) {
if (!warningStr.isEmpty()) {
warningStr += ", ";
}
warningStr += name;
}
addWarning("Unsupported field(s): " + warningStr);
}
}

View File

@ -0,0 +1,149 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.modules.stix;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException;
import java.util.List;
import java.util.ArrayList;
import org.mitre.cybox.common_2.ConditionApplicationEnum;
import org.mitre.cybox.objects.URIObjectType;
/**
*
*/
class EvalURIObj extends EvaluatableObject {
private final URIObjectType obj;
public EvalURIObj(URIObjectType a_obj, String a_id, String a_spacing) {
obj = a_obj;
id = a_id;
spacing = a_spacing;
}
@Override
public synchronized ObservableResult evaluate() {
setWarnings("");
if (obj.getValue() == null) {
return new ObservableResult(id, "URIObject: No URI value field found",
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
String addressStr = obj.getValue().getValue().toString();
// Strip off http:// or https://
String modifiedAddressStr = addressStr.toLowerCase();
modifiedAddressStr = modifiedAddressStr.replaceAll("http(s)?://", "");
// Since we have single URL artifacts, ALL and NONE conditions probably don't make sense to test
if (!((obj.getValue().getApplyCondition() == null)
|| (obj.getValue().getApplyCondition() == ConditionApplicationEnum.ANY))) {
return new ObservableResult(id, "URIObject: Can not process apply condition " + obj.getValue().getApplyCondition().toString()
+ " on URI object", spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
Case case1 = Case.getCurrentCase();
SleuthkitCase sleuthkitCase = case1.getSleuthkitCase();
try {
/*
if ((obj.getValue().getCondition() == null)
|| (obj.getValue().getCondition() == ConditionTypeEnum.EQUALS)) {
// Old version - uses a database query but only works on full strings.
// It will be faster to use this in the "equals" case
String[] parts = addressStr.split("##comma##");
List<BlackboardArtifact> arts = new ArrayList<BlackboardArtifact>();
for (String part : parts) {
arts.addAll(sleuthkitCase.getBlackboardArtifacts(
BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD,
part));
}
if (!arts.isEmpty()) {
List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
for (BlackboardArtifact a : arts) {
artData.add(new StixArtifactData(a.getObjectID(), id, "URIObject"));
}
return new ObservableResult(id, "URIObject: Found " + arts.size() + " matches for address = \"" + addressStr + "\"",
spacing, ObservableResult.ObservableState.TRUE, artData);
} else {
return new ObservableResult(id, "URIObject: Found no matches for address = \"" + addressStr + "\"",
spacing, ObservableResult.ObservableState.FALSE, null);
}
} else {*/
// This is inefficient, but the easiest way to do it.
List<BlackboardArtifact> finalHits = new ArrayList<BlackboardArtifact>();
// Get all the URL artifacts
List<BlackboardArtifact> artList
= sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT);
for (BlackboardArtifact art : artList) {
for (BlackboardAttribute attr : art.getAttributes()) {
if (attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()) {
String modifiedAttrString = attr.getValueString();
if (modifiedAttrString != null) {
modifiedAttrString = modifiedAttrString.toLowerCase();
modifiedAttrString = modifiedAttrString.replaceAll("http(s)?://", "");
}
if (compareStringObject(modifiedAddressStr, obj.getValue().getCondition(),
obj.getValue().getApplyCondition(), modifiedAttrString)) {
finalHits.add(art);
}
}
}
}
if (!finalHits.isEmpty()) {
List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
for (BlackboardArtifact a : finalHits) {
artData.add(new StixArtifactData(a.getObjectID(), id, "UriObject"));
}
return new ObservableResult(id, "UriObject: Found a match for " + addressStr,
spacing, ObservableResult.ObservableState.TRUE, artData);
}
return new ObservableResult(id, "URIObject: Found no matches for " + addressStr,
spacing, ObservableResult.ObservableState.FALSE, null);
/*}*/
} catch (TskCoreException ex) {
return new ObservableResult(id, "URIObject: Exception during evaluation: " + ex.getLocalizedMessage(),
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
}
}

View File

@ -0,0 +1,320 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.modules.stix;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException;
import java.util.List;
import java.util.ArrayList;
import org.mitre.cybox.common_2.AnyURIObjectPropertyType;
import org.mitre.cybox.objects.URLHistory;
import org.mitre.cybox.objects.URLHistoryEntryType;
/**
*
*/
class EvalURLHistoryObj extends EvaluatableObject {
private final URLHistory obj;
public EvalURLHistoryObj(URLHistory a_obj, String a_id, String a_spacing) {
obj = a_obj;
id = a_id;
spacing = a_spacing;
}
@Override
public synchronized ObservableResult evaluate() {
setWarnings("");
if ((obj.getBrowserInformation() == null) && (obj.getURLHistoryEntries() == null)) {
return new ObservableResult(id, "URLHistoryObject: No browser info or history entries found",
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
// For displaying what we were looking for in the results
String baseSearchString = "";
String finalResultsStr = "";
// The browser info is the same for each entry
boolean haveBrowserName = false;
if (obj.getBrowserInformation() != null) {
if (obj.getBrowserInformation().getName() != null) {
haveBrowserName = true;
}
baseSearchString = "Browser \"" + obj.getBrowserInformation().getName() + "\"";
}
// Matching artifacts
List<BlackboardArtifact> finalHits = new ArrayList<BlackboardArtifact>();
if (obj.getURLHistoryEntries() != null) {
for (URLHistoryEntryType entry : obj.getURLHistoryEntries()) {
boolean haveURL = false;
boolean haveHostname = false;
boolean haveReferrer = false;
boolean havePageTitle = false;
boolean haveUserProfile = false;
setUnsupportedEntryFieldWarnings(entry);
// At present, the search string doesn't get reported (because there could be different ones
// for multiple URL History Entries) but it's good for debugging.
String searchString = baseSearchString;
if ((entry.getURL() != null) && (entry.getURL().getValue() != null)) {
haveURL = true;
if (!searchString.isEmpty()) {
searchString += " and ";
}
searchString += "URL \"" + entry.getURL().getValue().getValue() + "\"";
}
if ((entry.getReferrerURL() != null) && (entry.getReferrerURL().getValue() != null)) {
haveReferrer = true;
if (!searchString.isEmpty()) {
searchString += " and ";
}
searchString += "Referrer \"" + entry.getReferrerURL().getValue().getValue() + "\"";
}
if (entry.getUserProfileName() != null) {
haveUserProfile = true;
if (!searchString.isEmpty()) {
searchString += " and ";
}
searchString += "UserProfile \"" + entry.getUserProfileName().getValue() + "\"";
}
if (entry.getPageTitle() != null) {
havePageTitle = true;
if (!searchString.isEmpty()) {
searchString += " and ";
}
searchString += "Page title \"" + entry.getPageTitle().getValue() + "\"";
}
if ((entry.getHostname() != null) && (entry.getHostname().getHostnameValue() != null)) {
haveHostname = true;
if (!searchString.isEmpty()) {
searchString += " and ";
}
searchString += "Hostname \"" + entry.getHostname().getHostnameValue().getValue() + "\"";
}
if (!finalResultsStr.isEmpty()) {
finalResultsStr += ", ";
}
finalResultsStr += searchString;
if (!(haveURL || haveHostname || haveReferrer
|| havePageTitle || haveUserProfile || haveBrowserName)) {
return new ObservableResult(id, "URLHistoryObject: No evaluatable fields found",
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
try {
Case case1 = Case.getCurrentCase();
SleuthkitCase sleuthkitCase = case1.getSleuthkitCase();
List<BlackboardArtifact> artList
= sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY);
for (BlackboardArtifact art : artList) {
boolean foundURLMatch = false;
boolean foundHostnameMatch = false;
boolean foundReferrerMatch = false;
boolean foundPageTitleMatch = false;
boolean foundUserProfileMatch = false;
boolean foundBrowserNameMatch = false;
for (BlackboardAttribute attr : art.getAttributes()) {
if ((attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL.getTypeID())
&& (haveURL)) {
if (entry.getURL().getValue() instanceof AnyURIObjectPropertyType) {
foundURLMatch = compareStringObject(entry.getURL().getValue().getValue().toString(),
entry.getURL().getValue().getCondition(),
entry.getURL().getValue().getApplyCondition(),
attr.getValueString());
} else {
addWarning("Non-AnyURIObjectPropertyType found in URL value field");
}
}
if ((attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN.getTypeID())
&& (haveHostname)) {
foundHostnameMatch = compareStringObject(entry.getHostname().getHostnameValue(),
attr.getValueString());
}
if ((attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_REFERRER.getTypeID())
&& (haveReferrer)) {
if (entry.getReferrerURL().getValue() instanceof AnyURIObjectPropertyType) {
foundReferrerMatch = compareStringObject(entry.getReferrerURL().getValue().getValue().toString(),
entry.getURL().getValue().getCondition(),
entry.getURL().getValue().getApplyCondition(),
attr.getValueString());
} else {
addWarning("Non-AnyURIObjectPropertyType found in URL value field");
}
}
if ((attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE.getTypeID())
&& (havePageTitle)) {
//System.out.println("Page title: " + attr.getValueString() + " " + entry.getPageTitle());
foundPageTitleMatch = compareStringObject(entry.getPageTitle(),
attr.getValueString());
}
if ((attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME.getTypeID())
&& (haveUserProfile)) {
foundUserProfileMatch = compareStringObject(entry.getUserProfileName(),
attr.getValueString());
}
if ((attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())
&& (haveBrowserName)) {
foundBrowserNameMatch = compareStringObject(obj.getBrowserInformation().getName(),
null, null, attr.getValueString());
}
}
if (((!haveURL) || foundURLMatch)
&& ((!haveHostname) || foundHostnameMatch)
&& ((!haveReferrer) || foundReferrerMatch)
&& ((!havePageTitle) || foundPageTitleMatch)
&& ((!haveUserProfile) || foundUserProfileMatch)
&& ((!haveBrowserName) || foundBrowserNameMatch)) {
finalHits.add(art);
}
}
} catch (TskCoreException ex) {
return new ObservableResult(id, "URLHistoryObject: Exception during evaluation: " + ex.getLocalizedMessage(),
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
}
if (!finalHits.isEmpty()) {
List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
for (BlackboardArtifact a : finalHits) {
artData.add(new StixArtifactData(a.getObjectID(), id, "URLHistory"));
}
return new ObservableResult(id, "URLHistoryObject: Found at least one match for " + finalResultsStr,
spacing, ObservableResult.ObservableState.TRUE, artData);
}
// Didn't find any matches
return new ObservableResult(id, "URLHistoryObject: No matches found for " + finalResultsStr,
spacing, ObservableResult.ObservableState.FALSE, null);
} else if (haveBrowserName) {
// It doesn't seem too useful, but we can just search for the browser name
// if there aren't any URL entries
try {
Case case1 = Case.getCurrentCase();
SleuthkitCase sleuthkitCase = case1.getSleuthkitCase();
List<BlackboardArtifact> artList
= sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY);
for (BlackboardArtifact art : artList) {
boolean foundBrowserNameMatch = false;
for (BlackboardAttribute attr : art.getAttributes()) {
if ((attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID())
&& (haveBrowserName)) {
foundBrowserNameMatch = compareStringObject(obj.getBrowserInformation().getName(),
null, null, attr.getValueString());
}
}
if (foundBrowserNameMatch) {
finalHits.add(art);
}
}
if (!finalHits.isEmpty()) {
List<StixArtifactData> artData = new ArrayList<StixArtifactData>();
for (BlackboardArtifact a : finalHits) {
artData.add(new StixArtifactData(a.getObjectID(), id, "URLHistory"));
}
return new ObservableResult(id, "URLHistoryObject: Found at least one match",
spacing, ObservableResult.ObservableState.TRUE, artData);
}
// Didn't find any matches
return new ObservableResult(id, "URLHistoryObject: No matches found for " + baseSearchString,
spacing, ObservableResult.ObservableState.FALSE, null);
} catch (TskCoreException ex) {
return new ObservableResult(id, "URLHistoryObject: Exception during evaluation: " + ex.getLocalizedMessage(),
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
} else {
// Nothing to search for
return new ObservableResult(id, "URLHistoryObject: No evaluatable fields found",
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
}
/**
* Set up the warning for any fields in the URL_History_Entry object that
* aren't supported.
*/
private void setUnsupportedEntryFieldWarnings(URLHistoryEntryType entry) {
List<String> fieldNames = new ArrayList<String>();
if (entry.getUserProfileName() != null) {
fieldNames.add("User_Profile_Name");
}
if (entry.getVisitCount() != null) {
fieldNames.add("Visit_Count");
}
if (entry.getManuallyEnteredCount() != null) {
fieldNames.add("Manually_Entered_Count");
}
if (entry.getModificationDateTime() != null) {
fieldNames.add("Modification_DateTime");
}
if (entry.getExpirationDateTime() != null) {
fieldNames.add("Expiration_DateTime");
}
if (entry.getFirstVisitDateTime() != null) {
fieldNames.add("First_Visit_DateTime");
}
if (entry.getLastVisitDateTime() != null) {
fieldNames.add("Last_Visit_DateTime");
}
String warningStr = "";
for (String name : fieldNames) {
if (!warningStr.isEmpty()) {
warningStr += ", ";
}
warningStr += name;
}
addWarning("Unsupported URL_History_Entry field(s): " + warningStr);
}
}

View File

@ -0,0 +1,253 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.modules.stix;
import java.util.ArrayList;
import java.util.List;
import org.mitre.cybox.common_2.ConditionApplicationEnum;
import org.mitre.cybox.common_2.ConditionTypeEnum;
import org.mitre.cybox.common_2.StringObjectPropertyType;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
*
*/
abstract class EvaluatableObject {
private String warnings;
protected String id;
protected String spacing;
abstract public ObservableResult evaluate();
/**
* Set the warnings string to the given value.
*
* @param a_warnings
*/
public void setWarnings(String a_warnings) {
warnings = a_warnings;
}
/**
* Get the warnings string. This should not be used to print the final
* version of the warnings.
*
* @return
*/
public String getWarnings() {
return warnings;
}
/**
* Add to the warnings string.
*
* @param a_newWarning
*/
public void addWarning(String a_newWarning) {
if ((warnings == null) || warnings.isEmpty()) {
warnings = a_newWarning;
}
warnings = warnings + ", " + a_newWarning;
}
/**
* Find a list of artifacts with the given attribute type that contain the
* String Object. All comparisons will look for substrings of the Blackboard
* artifacts that match the String Object.
*
* @param item
* @param attrType
* @return
* @throws TskCoreException
*/
public List<BlackboardArtifact> findArtifactsBySubstring(StringObjectPropertyType item,
BlackboardAttribute.ATTRIBUTE_TYPE attrType) throws TskCoreException {
if (item.getValue() == null) {
throw new TskCoreException("Error: Value field is null");
}
if (item.getCondition() == null) {
addWarning("Warning: No condition given for " + attrType.getDisplayName() + " field, using substring comparison");
} else if (item.getCondition() != ConditionTypeEnum.CONTAINS) {
addWarning("Warning: Ignoring condition " + item.getCondition() + " for "
+ attrType.getDisplayName() + " field and doing substring comparison");
}
List<BlackboardArtifact> hits = null;
try {
Case case1 = Case.getCurrentCase();
SleuthkitCase sleuthkitCase = case1.getSleuthkitCase();
String[] parts = item.getValue().toString().split("##comma##");
if ((item.getApplyCondition() == null)
|| (item.getApplyCondition() == ConditionApplicationEnum.ANY)) {
for (String part : parts) {
if (hits == null) {
// Note that this searches for artifacts with "part" as a substring
hits = sleuthkitCase.getBlackboardArtifacts(
attrType,
part, false);
} else {
hits.addAll(sleuthkitCase.getBlackboardArtifacts(
attrType,
part, false));
}
}
} else if ((item.getApplyCondition() != null)
|| (item.getApplyCondition() == ConditionApplicationEnum.ALL)) {
boolean firstRound = true;
for (String part : parts) {
if (firstRound) {
hits = sleuthkitCase.getBlackboardArtifacts(
attrType,
part, false);
firstRound = false;
} else if (hits != null) {
hits.retainAll(sleuthkitCase.getBlackboardArtifacts(
attrType,
part, false));
} else {
// After first round; hits is still null
// I don't think this should happen but if it does we're done
return new ArrayList<BlackboardArtifact>();
}
}
} else {
throw new TskCoreException("Error: Can not apply NONE condition in search");
}
} catch (TskCoreException ex) {
addWarning(ex.getLocalizedMessage());
}
return hits;
}
/**
* Compare a CybOX String Object with a given string.
*
* @param stringObj The CybOX String Object
* @param strField The string to compare against
* @return true if strField is a match for the CybOX object
* @throws TskCoreException
*/
public static boolean compareStringObject(StringObjectPropertyType stringObj, String strField)
throws TskCoreException {
if (stringObj.getValue() == null) {
throw new TskCoreException("Error: Value field is null");
}
String valueStr = stringObj.getValue().toString();
ConditionTypeEnum condition = stringObj.getCondition();
ConditionApplicationEnum applyCondition = stringObj.getApplyCondition();
return compareStringObject(valueStr, condition, applyCondition, strField);
}
/**
* Compare a string with CybOX conditions to a given string.
*
* @param valueStr The CybOX string
* @param condition EQUALS, CONTAINS, STARTS_WITH, etc
* @param applyCondition ANY, ALL, NONE
* @param strField The string to compare against
* @return true if strField is a match for the CybOX valueStr and conditions
* @throws TskCoreException
*/
public static boolean compareStringObject(String valueStr, ConditionTypeEnum condition,
ConditionApplicationEnum applyCondition, String strField)
throws TskCoreException {
if (valueStr == null) {
throw new TskCoreException("Error: Value field is null");
}
String[] parts = valueStr.split("##comma##");
String lowerFieldName = strField.toLowerCase();
for (String value : parts) {
boolean partialResult;
if ((condition == null)
|| (condition == ConditionTypeEnum.EQUALS)) {
partialResult = value.equalsIgnoreCase(strField);
} else if (condition == ConditionTypeEnum.DOES_NOT_EQUAL) {
partialResult = !value.equalsIgnoreCase(strField);
} else if (condition == ConditionTypeEnum.CONTAINS) {
partialResult = lowerFieldName.contains(value.toLowerCase());
} else if (condition == ConditionTypeEnum.DOES_NOT_CONTAIN) {
partialResult = !lowerFieldName.contains(value.toLowerCase());
} else if (condition == ConditionTypeEnum.STARTS_WITH) {
partialResult = lowerFieldName.startsWith(value.toLowerCase());
} else if (condition == ConditionTypeEnum.ENDS_WITH) {
partialResult = lowerFieldName.endsWith(value.toLowerCase());
} else {
throw new TskCoreException("Could not process condition " + condition.value() + " on " + value);
}
// Do all the short-circuiting
if (applyCondition == ConditionApplicationEnum.NONE) {
if (partialResult == true) {
// Failed
return false;
}
} else if (applyCondition == ConditionApplicationEnum.ALL) {
if (partialResult == false) {
// Failed
return false;
}
} else {
// Default is "any"
if (partialResult == true) {
return true;
}
}
}
// At this point we're done and didn't short-circuit, so ALL or NONE conditions were true,
// and ANY was false
if ((applyCondition == ConditionApplicationEnum.NONE)
|| (applyCondition == ConditionApplicationEnum.ALL)) {
return true;
}
return false;
}
/**
* Format the warnings that will be printed. Basically, just put parentheses
* around them if the string isn't empty.
*
* @return
*/
public String getPrintableWarnings() {
String warningsToPrint = "";
if ((getWarnings() != null)
&& (!getWarnings().isEmpty())) {
warningsToPrint = " (" + getWarnings() + ")";
}
return warningsToPrint;
}
}

View File

@ -0,0 +1,198 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.modules.stix;
import java.util.List;
import java.util.ArrayList;
import org.mitre.cybox.cybox_2.OperatorTypeEnum;
/**
*
*/
class ObservableResult {
public enum ObservableState {
TRUE("true "),
FALSE("false "),
INDETERMINATE("indeterminate");
private final String label;
private ObservableState(String s) {
label = s;
}
@Override
public String toString() {
return label;
}
}
private ObservableState state = null;
private String description = "";
private List<StixArtifactData> artifacts;
public ObservableResult(String a_id, String a_desc, String a_spacing,
ObservableState a_state, List<StixArtifactData> a_artifacts) {
state = a_state;
description = a_spacing + a_id + "\t" + a_state + "\t" + a_desc + "\r\n";
artifacts = a_artifacts;
}
public ObservableResult(OperatorTypeEnum a_operator, String a_spacing) {
state = ObservableState.INDETERMINATE;
description = a_spacing + a_operator + "\r\n";
artifacts = new ArrayList<StixArtifactData>();
}
public ObservableState getState() {
return state;
}
/**
* Returns true if the ObservableResult is currently true. Note: A false
* result here does not mean the state is false; it could also be
* indeterminate.
*
* @return true if the ObservableResult is true, false if it is false or
* indeterminate
*/
public boolean isTrue() {
return (state == ObservableState.TRUE);
}
/**
* Returns true if the ObservableResult is currently false. Note: A false
* result here does not mean the state is true; it could also be
* indeterminate.
*
* @return true if the ObservableResult is false, false if it is true or
* indeterminate
*/
public boolean isFalse() {
return (state == ObservableState.FALSE);
}
public String getDescription() {
return description;
}
public List<StixArtifactData> getArtifacts() {
return artifacts;
}
/**
* Add a new result to the current state
*
* @param a_result The new result to add
* @param a_operator AND or OR
*/
public void addResult(ObservableResult a_result, OperatorTypeEnum a_operator) {
addResult(a_result.getDescription(), a_result.getState(),
a_result.getArtifacts(), a_operator);
}
/**
* Add a new result to the current state.
*
* @param a_description Description of the observable and testing done
* @param a_state State of what we're adding (true, false, or indeterminate)
* @param a_operator AND or OR
*/
private void addResult(String a_description, ObservableState a_state,
List<StixArtifactData> a_artifacts, OperatorTypeEnum a_operator) {
addToDesc(a_description);
if (a_operator == OperatorTypeEnum.AND) {
if (a_state == ObservableState.FALSE) {
// If we now have a false, the whole thing is false regardless of previous state.
// Clear out any existing artifacts.
state = ObservableState.FALSE;
artifacts.clear();
} else if (a_state == ObservableState.INDETERMINATE) {
// Don't change the current state, and don't save the new artifacts
// (though there probably wouldn't be any)
} else {
if (state == ObservableState.FALSE) {
// Previous state false + new state true => stay false
} else if (state == ObservableState.TRUE) {
// Previous state true + new state true => stay true and add artifacts
if ((artifacts == null) && (a_artifacts != null)) {
artifacts = new ArrayList<StixArtifactData>();
}
if (a_artifacts != null) {
artifacts.addAll(a_artifacts);
}
} else {
// If the previous state was indeterminate, change it to true and add artifacts
state = ObservableState.TRUE;
if ((artifacts == null) && (a_artifacts != null)) {
artifacts = new ArrayList<StixArtifactData>();
}
if (a_artifacts != null) {
artifacts.addAll(a_artifacts);
}
}
}
} else {
if (a_state == ObservableState.TRUE) {
// If we now have a true, the whole thing is true regardless of previous state.
// Add the new artifacts.
state = ObservableState.TRUE;
if ((artifacts == null) && (a_artifacts != null)) {
artifacts = new ArrayList<StixArtifactData>();
}
if (a_artifacts != null) {
artifacts.addAll(a_artifacts);
}
} else if (a_state == ObservableState.INDETERMINATE) {
// Don't change the current state and don't record it to the
// description string (later we should save these in some way)
} else {
if (state == ObservableState.FALSE) {
// Previous state false + new state false => stay false
} else if (state == ObservableState.TRUE) {
// Previous state true + new state false => stay true
} else {
// Previous state indeterminate + new state false => change to false
state = ObservableState.FALSE;
}
}
}
}
/**
* Add to the description string. Mostly just to make things cleaner by not
* testing for null all over the place.
*
* @param a_desc New part of the description to add
*/
private void addToDesc(String a_desc) {
if (description == null) {
description = a_desc;
} else {
description += a_desc;
}
}
}

View File

@ -0,0 +1,628 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.modules.stix;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.logging.Level;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.util.Arrays;
import javax.swing.JPanel;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import org.mitre.cybox.cybox_2.ObjectType;
import org.mitre.cybox.cybox_2.Observable;
import org.mitre.cybox.cybox_2.ObservableCompositionType;
import org.mitre.stix.common_1.IndicatorBaseType;
import org.mitre.stix.indicator_2.Indicator;
import org.mitre.stix.stix_1.STIXPackage;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.report.GeneralReportModule;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.report.ReportProgressPanel;
import org.sleuthkit.datamodel.TskCoreException;
import org.mitre.cybox.cybox_2.OperatorTypeEnum;
import org.mitre.cybox.objects.Address;
import org.mitre.cybox.objects.FileObjectType;
import org.mitre.cybox.objects.URIObjectType;
import org.mitre.cybox.objects.EmailMessage;
import org.mitre.cybox.objects.WindowsNetworkShare;
import org.mitre.cybox.objects.AccountObjectType;
import org.mitre.cybox.objects.SystemObjectType;
import org.mitre.cybox.objects.URLHistory;
import org.mitre.cybox.objects.WindowsRegistryKey;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
/**
*
*/
public class STIXReportModule implements GeneralReportModule {
private static final Logger logger = Logger.getLogger(STIXReportModule.class.getName());
private STIXReportModuleConfigPanel configPanel;
private static STIXReportModule instance = null;
private String reportPath;
private boolean reportAllResults;
private final Map<String, ObjectType> idToObjectMap = new HashMap<String, ObjectType>();
private final Map<String, ObservableResult> idToResult = new HashMap<String, ObservableResult>();
private List<EvalRegistryObj.RegistryFileInfo> registryFileData = null;
private final boolean skipShortCircuit = true;
private BufferedWriter output = null;
// Hidden constructor for the report
private STIXReportModule() {
}
// Get the default implementation of this report
public static synchronized STIXReportModule getDefault() {
if (instance == null) {
instance = new STIXReportModule();
}
return instance;
}
/**
* .
*
* @param path path to save the report
* @param progressPanel panel to update the report's progress
*/
@Override
public void generateReport(String path, ReportProgressPanel progressPanel) {
// Start the progress bar and setup the report
progressPanel.setIndeterminate(false);
progressPanel.start();
progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.readSTIX"));
reportPath = path + getRelativeFilePath();
// Check if the user wants to display all output or just hits
reportAllResults = configPanel.getShowAllResults();
// Set up the output file
try {
File file = new File(reportPath);
output = new BufferedWriter(new FileWriter(file));
} catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Unable to open STIX report file %s", reportPath), ex);
MessageNotifyUtil.Notify.show("STIXReportModule",
"Unable to open STIX report file " + reportPath,
MessageNotifyUtil.MessageType.ERROR);
progressPanel.complete();
progressPanel.updateStatusLabel("Completed with errors");
return;
}
// Keep track of whether any errors occur during processing
boolean hadErrors = false;
// Process the file/directory name entry
String stixFileName = configPanel.getStixFile();
File stixFile = new File(stixFileName);
if (!stixFile.exists()) {
logger.log(Level.SEVERE, String.format("Unable to open STIX file/directory %s", stixFileName));
MessageNotifyUtil.Notify.show("STIXReportModule",
"Unable to open STIX file/directory " + stixFileName,
MessageNotifyUtil.MessageType.ERROR);
progressPanel.complete();
progressPanel.updateStatusLabel("Could not open file/directory " + stixFileName);
return;
}
// Store the path
ModuleSettings.setConfigSetting("STIX", "defaultPath", stixFileName);
// Create array of stix file(s)
File[] stixFiles;
if (stixFile.isFile()) {
stixFiles = new File[1];
stixFiles[0] = stixFile;
} else {
stixFiles = stixFile.listFiles();
}
// Set the length of the progress bar - we increment twice for each file
progressPanel.setMaximumProgress(stixFiles.length * 2 + 1);
// Process each STIX file
for (File file : stixFiles) {
try {
processFile(file.getAbsolutePath(), progressPanel);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Unable to process STIX file %s", file), ex);
MessageNotifyUtil.Notify.show("STIXReportModule",
ex.getLocalizedMessage(),
MessageNotifyUtil.MessageType.ERROR);
hadErrors = true;
}
}
// Close the output file
if (output != null) {
try {
output.close();
} catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Error closing STIX report file %s", reportPath), ex);
}
}
// Set the progress bar to done. If any errors occurred along the way, modify
// the "complete" message to indicate this.
progressPanel.complete();
if (hadErrors) {
progressPanel.updateStatusLabel("Completed with errors");
}
}
/**
* Process a STIX file.
*
* @param stixFile - Name of the file
* @param progressPanel - Progress panel (for updating)
* @throws TskCoreException
*/
private void processFile(String stixFile, ReportProgressPanel progressPanel) throws
TskCoreException {
// Load the STIX file
STIXPackage stix;
stix = loadSTIXFile(stixFile);
printFileHeader(stixFile);
// Save any observables listed up front
processObservables(stix);
progressPanel.increment();
// Make copies of the registry files
registryFileData = EvalRegistryObj.copyRegistryFiles();
// Process the indicators
processIndicators(stix);
progressPanel.increment();
}
/**
* Load a STIX-formatted XML file into a STIXPackage object.
*
* @param stixFileName Name of the STIX file to unmarshal
* @return Unmarshalled file contents
* @throws TskCoreException
*/
private STIXPackage loadSTIXFile(String stixFileName) throws TskCoreException {
try {
// 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:"
+ "org.mitre.cybox.objects:org.mitre.cybox.cybox_2:org.mitre.cybox.common_2");
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
STIXPackage stix = (STIXPackage) jaxbUnmarshaller.unmarshal(file);
return stix;
} catch (JAXBException ex) {
logger.log(Level.SEVERE, String.format("Unable to load STIX file %s", stixFileName), ex.getLocalizedMessage());
throw new TskCoreException("Error loading STIX file (" + ex.toString() + ")");
}
}
/**
* Do the initial processing of the list of observables. For each
* observable, save it in a map using the ID as key.
*
* @param stix STIXPackage
*/
private void processObservables(STIXPackage stix) {
if (stix.getObservables() != null) {
List<Observable> obs = stix.getObservables().getObservables();
for (Observable o : obs) {
if (o.getId() != null) {
saveToObjectMap(o);
}
}
}
}
/**
* Process all STIX indicators and save results to output file and create
* artifacts.
*
* @param stix STIXPackage
*/
private void processIndicators(STIXPackage stix) throws TskCoreException {
if (stix.getIndicators() != null) {
List<IndicatorBaseType> s = stix.getIndicators().getIndicators();
for (IndicatorBaseType t : s) {
if (t instanceof Indicator) {
Indicator ind = (Indicator) t;
if (ind.getObservable() != null) {
if (ind.getObservable().getObject() != null) {
ObservableResult result = evaluateSingleObservable(ind.getObservable(), "");
if (result.isTrue() || reportAllResults) {
writeResultsToFile(ind, result.getDescription(), result.isTrue());
}
if (result.isTrue()) {
saveResultsAsArtifacts(ind, result);
}
} else if (ind.getObservable().getObservableComposition() != null) {
ObservableResult result = evaluateObservableComposition(ind.getObservable().getObservableComposition(), " ");
if (result.isTrue() || reportAllResults) {
writeResultsToFile(ind, result.getDescription(), result.isTrue());
}
if (result.isTrue()) {
saveResultsAsArtifacts(ind, result);
}
}
}
}
}
}
}
/**
* Create the artifacts saved in the observable result.
*
* @param ind
* @param result
* @throws TskCoreException
*/
private void saveResultsAsArtifacts(Indicator ind, ObservableResult result) throws TskCoreException {
if (result.getArtifacts() == null) {
return;
}
// Count of how many artifacts have been created for this indicator.
int count = 0;
for (StixArtifactData s : result.getArtifacts()) {
// Figure out what name to use for this indicator. If it has a title,
// use that. Otherwise use the ID. If both are missing, use a
// generic heading.
if (ind.getTitle() != null) {
s.createArtifact(ind.getTitle());
} else if (ind.getId() != null) {
s.createArtifact(ind.getId().toString());
} else {
s.createArtifact("Unnamed indicator(s)");
}
// Trying to protect against the case where we end up with tons of artifacts
// for a single observable because the condition was not restrictive enough
count++;
if (count > 1000) {
MessageNotifyUtil.Notify.show("STIXReportModule",
"Too many STIX-related artifacts generated for " + ind.getId() + ". Only saving first 1000.",
MessageNotifyUtil.MessageType.INFO);
break;
}
}
}
/**
* Write the full results string to the output file.
*
* @param ind - Used to get the title, ID, and description of the indicator
* @param resultStr - Full results for this indicator
* @param found - true if the indicator was found in datasource(s)
*/
private void writeResultsToFile(Indicator ind, String resultStr, boolean found) {
if (output != null) {
try {
if (found) {
output.write("----------------\r\n"
+ "Found indicator:\r\n");
} else {
output.write("-----------------------\r\n"
+ "Did not find indicator:\r\n");
}
if (ind.getTitle() != null) {
output.write("Title: " + ind.getTitle() + "\r\n");
} else {
output.write("\r\n");
}
if (ind.getId() != null) {
output.write("ID: " + ind.getId() + "\r\n");
}
if (ind.getDescription() != null) {
String desc = ind.getDescription().getValue();
desc = desc.trim();
output.write("Description: " + desc + "\r\n");
}
output.write("\r\nObservable results:\r\n" + resultStr + "\r\n\r\n");
} catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex);
}
}
}
/**
* Write the a header for the current file to the output file.
*
* @param a_fileName
*/
private void printFileHeader(String a_fileName) {
if (output != null) {
try {
char[] chars = new char[a_fileName.length() + 8];
Arrays.fill(chars, '#');
String header = new String(chars);
output.write("\r\n" + header);
output.write("\r\n");
output.write("### " + a_fileName + " ###\r\n");
output.write(header + "\r\n\r\n");
} catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex);
}
}
}
/**
* Use the ID or ID ref to create a key into the observable map.
*
* @param obs
* @return
*/
private String makeMapKey(Observable obs) {
QName idQ;
if (obs.getId() != null) {
idQ = obs.getId();
} else if (obs.getIdref() != null) {
idQ = obs.getIdref();
} else {
return "";
}
return idQ.getLocalPart();
}
/**
* Save an observable in the object map.
*
* @param obs
*/
private void saveToObjectMap(Observable obs) {
if (obs.getObject() != null) {
idToObjectMap.put(makeMapKey(obs), obs.getObject());
}
}
/**
* Evaluate an observable composition. Can be called recursively.
*
* @param comp The observable composition object to evaluate
* @param spacing Used to formatting the output
* @return The status of the composition
* @throws TskCoreException
*/
private ObservableResult evaluateObservableComposition(ObservableCompositionType comp, String spacing) throws TskCoreException {
if (comp.getOperator() == null) {
throw new TskCoreException("No operator found in composition");
}
if (comp.getObservables() != null) {
List<Observable> obsList = comp.getObservables();
// Split based on the type of composition (AND vs OR)
if (comp.getOperator() == OperatorTypeEnum.AND) {
ObservableResult result = new ObservableResult(OperatorTypeEnum.AND, spacing);
for (Observable o : obsList) {
ObservableResult newResult; // The combined result for the composition
if (o.getObservableComposition() != null) {
newResult = evaluateObservableComposition(o.getObservableComposition(), spacing + " ");
if (result == null) {
result = newResult;
} else {
result.addResult(newResult, OperatorTypeEnum.AND);
}
} else {
newResult = evaluateSingleObservable(o, spacing + " ");
if (result == null) {
result = newResult;
} else {
result.addResult(newResult, OperatorTypeEnum.AND);
}
}
if ((!skipShortCircuit) && !result.isFalse()) {
// For testing purposes (and maybe in general), may not want to short-circuit
return result;
}
}
// At this point, all comparisions should have been true (or indeterminate)
if (result == null) {
// This really shouldn't happen, but if we have an empty composition,
// indeterminate seems like a reasonable result
return new ObservableResult("", "", spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
return result;
} else {
ObservableResult result = new ObservableResult(OperatorTypeEnum.OR, spacing);
for (Observable o : obsList) {
ObservableResult newResult;// The combined result for the composition
if (o.getObservableComposition() != null) {
newResult = evaluateObservableComposition(o.getObservableComposition(), spacing + " ");
if (result == null) {
result = newResult;
} else {
result.addResult(newResult, OperatorTypeEnum.OR);
}
} else {
newResult = evaluateSingleObservable(o, spacing + " ");
if (result == null) {
result = newResult;
} else {
result.addResult(newResult, OperatorTypeEnum.OR);
}
}
if ((!skipShortCircuit) && result.isTrue()) {
// For testing (and maybe in general), may not want to short-circuit
return result;
}
}
// At this point, all comparisions were false (or indeterminate)
if (result == null) {
// This really shouldn't happen, but if we have an empty composition,
// indeterminate seems like a reasonable result
return new ObservableResult("", "", spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
return result;
}
} else {
throw new TskCoreException("No observables found in list");
}
}
/**
* Evaluate one observable and return the result. This is at the end of the
* observable composition tree and will not be called recursively.
*
* @param obs The observable object to evaluate
* @param spacing For formatting the output
* @return The status of the observable
* @throws TskCoreException
*/
private ObservableResult evaluateSingleObservable(Observable obs, String spacing) throws TskCoreException {
// If we've already calculated this one, return the saved value
if (idToResult.containsKey(makeMapKey(obs))) {
return idToResult.get(makeMapKey(obs));
}
if (obs.getIdref() == null) {
// We should have the object data right here (as opposed to elsewhere in the STIX file).
// Save it to the map.
if (obs.getId() != null) {
saveToObjectMap(obs);
}
if (obs.getObject() != null) {
ObservableResult result = evaluateObject(obs.getObject(), spacing, makeMapKey(obs));
idToResult.put(makeMapKey(obs), result);
return result;
}
}
if (idToObjectMap.containsKey(makeMapKey(obs))) {
ObservableResult result = evaluateObject(idToObjectMap.get(makeMapKey(obs)), spacing, makeMapKey(obs));
idToResult.put(makeMapKey(obs), result);
return result;
}
throw new TskCoreException("Error loading/finding object for observable " + obs.getIdref());
}
/**
* Evaluate a STIX object.
*
*
* @param obj The object to evaluate against the datasource(s)
* @param spacing For formatting the output
* @return
*/
private ObservableResult evaluateObject(ObjectType obj, String spacing, String id) {
EvaluatableObject evalObj;
if (obj.getProperties() instanceof FileObjectType) {
evalObj = new EvalFileObj((FileObjectType) obj.getProperties(), id, spacing);
} else if (obj.getProperties() instanceof Address) {
evalObj = new EvalAddressObj((Address) obj.getProperties(), id, spacing);
} else if (obj.getProperties() instanceof URIObjectType) {
evalObj = new EvalURIObj((URIObjectType) obj.getProperties(), id, spacing);
} else if (obj.getProperties() instanceof EmailMessage) {
evalObj = new EvalEmailObj((EmailMessage) obj.getProperties(), id, spacing);
} else if (obj.getProperties() instanceof WindowsNetworkShare) {
evalObj = new EvalNetworkShareObj((WindowsNetworkShare) obj.getProperties(), id, spacing);
} else if (obj.getProperties() instanceof AccountObjectType) {
evalObj = new EvalAccountObj((AccountObjectType) obj.getProperties(), id, spacing);
} else if (obj.getProperties() instanceof SystemObjectType) {
evalObj = new EvalSystemObj((SystemObjectType) obj.getProperties(), id, spacing);
} else if (obj.getProperties() instanceof URLHistory) {
evalObj = new EvalURLHistoryObj((URLHistory) obj.getProperties(), id, spacing);
} else if (obj.getProperties() instanceof WindowsRegistryKey) {
evalObj = new EvalRegistryObj((WindowsRegistryKey) obj.getProperties(), id, spacing, registryFileData);
} else {
// Try to get the object type as a string
String type = obj.getProperties().toString();
type = type.substring(0, type.indexOf("@"));
if ((type.lastIndexOf(".") + 1) < type.length()) {
type = type.substring(type.lastIndexOf(".") + 1);
}
return new ObservableResult(id, type + " not supported",
spacing, ObservableResult.ObservableState.INDETERMINATE, null);
}
// Evalutate the object
return evalObj.evaluate();
}
@Override
public String getName() {
String name = NbBundle.getMessage(this.getClass(), "STIXReportModule.getName.text");
return name;
}
@Override
public String getRelativeFilePath() {
return "stix.txt"; //NON-NLS
}
@Override
public String getDescription() {
String desc = NbBundle.getMessage(this.getClass(), "STIXReportModule.getDesc.text");
return desc;
}
@Override
public JPanel getConfigurationPanel() {
configPanel = new STIXReportModuleConfigPanel();
return configPanel;
}
}

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="jLabel2" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="jTextField1" min="-2" pref="292" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="jButton1" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="jCheckBox1" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace pref="73" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="jLabel2" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="jTextField1" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="jButton1" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="jCheckBox1" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="225" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JLabel" name="jLabel2">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/stix/Bundle.properties" key="STIXReportModuleConfigPanel.jLabel2.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
</AuxValues>
</Component>
<Component class="javax.swing.JTextField" name="jTextField1">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/stix/Bundle.properties" key="STIXReportModuleConfigPanel.jTextField1.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="jTextField1ActionPerformed"/>
<EventHandler event="keyReleased" listener="java.awt.event.KeyListener" parameters="java.awt.event.KeyEvent" handler="jTextField1KeyReleased"/>
<EventHandler event="keyTyped" listener="java.awt.event.KeyListener" parameters="java.awt.event.KeyEvent" handler="jTextField1KeyTyped"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="jButton1">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/stix/Bundle.properties" key="STIXReportModuleConfigPanel.jButton1.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="jButton1ActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
</AuxValues>
</Component>
<Component class="javax.swing.JCheckBox" name="jCheckBox1">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/stix/Bundle.properties" key="STIXReportModuleConfigPanel.jCheckBox1.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="jCheckBox1ActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
</AuxValues>
</Component>
</SubComponents>
</Form>

View File

@ -0,0 +1,193 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.modules.stix;
import java.io.File;
import javax.swing.JFileChooser;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
/**
*
*/
public class STIXReportModuleConfigPanel extends javax.swing.JPanel {
String stixFile = null;
boolean showAllResults;
/**
* Creates new form STIXReportModuleConfigPanel
*/
public STIXReportModuleConfigPanel() {
initComponents();
// Set the default path to the last one used
if ((ModuleSettings.getConfigSetting("STIX", "defaultPath") != null)
&& (!ModuleSettings.getConfigSetting("STIX", "defaultPath").isEmpty())) {
jTextField1.setText(ModuleSettings.getConfigSetting("STIX", "defaultPath"));
stixFile = ModuleSettings.getConfigSetting("STIX", "defaultPath");
}
// Set the results checkbox to the last one used
if ((ModuleSettings.getConfigSetting("STIX", "showAllResults") != null)
&& (!ModuleSettings.getConfigSetting("STIX", "showAllResults").isEmpty())) {
if (ModuleSettings.getConfigSetting("STIX", "showAllResults").equals("true")) {
jCheckBox1.setSelected(true);
showAllResults = true;
} else {
jCheckBox1.setSelected(false);
showAllResults = false;
}
} else {
showAllResults = false;
}
}
public String getStixFile() {
return stixFile;
}
public boolean getShowAllResults() {
return showAllResults;
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
jLabel2 = new javax.swing.JLabel();
jTextField1 = new javax.swing.JTextField();
jButton1 = new javax.swing.JButton();
jCheckBox1 = new javax.swing.JCheckBox();
jLabel2.setText(org.openide.util.NbBundle.getMessage(STIXReportModuleConfigPanel.class, "STIXReportModuleConfigPanel.jLabel2.text")); // NOI18N
jTextField1.setText(org.openide.util.NbBundle.getMessage(STIXReportModuleConfigPanel.class, "STIXReportModuleConfigPanel.jTextField1.text")); // NOI18N
jTextField1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jTextField1ActionPerformed(evt);
}
});
jTextField1.addKeyListener(new java.awt.event.KeyAdapter() {
public void keyReleased(java.awt.event.KeyEvent evt) {
jTextField1KeyReleased(evt);
}
public void keyTyped(java.awt.event.KeyEvent evt) {
jTextField1KeyTyped(evt);
}
});
jButton1.setText(org.openide.util.NbBundle.getMessage(STIXReportModuleConfigPanel.class, "STIXReportModuleConfigPanel.jButton1.text")); // NOI18N
jButton1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jButton1ActionPerformed(evt);
}
});
jCheckBox1.setText(org.openide.util.NbBundle.getMessage(STIXReportModuleConfigPanel.class, "STIXReportModuleConfigPanel.jCheckBox1.text")); // NOI18N
jCheckBox1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jCheckBox1ActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jLabel2)
.addGroup(layout.createSequentialGroup()
.addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, 292, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jButton1))
.addComponent(jCheckBox1))
.addContainerGap(73, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(jLabel2)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jButton1))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(jCheckBox1)
.addContainerGap(225, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
private void jTextField1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jTextField1ActionPerformed
}//GEN-LAST:event_jTextField1ActionPerformed
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
File currentSelection = new File(jTextField1.getText());
if (currentSelection.exists()) {
fileChooser.setCurrentDirectory(currentSelection);
}
int result = fileChooser.showOpenDialog(null);
if (result == JFileChooser.APPROVE_OPTION) {
stixFile = fileChooser.getSelectedFile().getAbsolutePath();
jTextField1.setText(stixFile);
}
}//GEN-LAST:event_jButton1ActionPerformed
private void jTextField1KeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_jTextField1KeyTyped
}//GEN-LAST:event_jTextField1KeyTyped
private void jTextField1KeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_jTextField1KeyReleased
stixFile = jTextField1.getText();
}//GEN-LAST:event_jTextField1KeyReleased
private void jCheckBox1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBox1ActionPerformed
// TODO add your handling code here:
showAllResults = jCheckBox1.isSelected();
if (showAllResults) {
ModuleSettings.setConfigSetting("STIX", "showAllResults", "true");
} else {
ModuleSettings.setConfigSetting("STIX", "showAllResults", "false");
}
}//GEN-LAST:event_jCheckBox1ActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton jButton1;
private javax.swing.JCheckBox jCheckBox1;
private javax.swing.JLabel jLabel2;
private javax.swing.JTextField jTextField1;
// End of variables declaration//GEN-END:variables
}

View File

@ -0,0 +1,76 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.modules.stix;
import java.util.ArrayList;
import java.util.Collection;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
*
*/
class StixArtifactData {
private AbstractFile file;
private final String observableId;
private final String objType;
public StixArtifactData(AbstractFile a_file, String a_observableId, String a_objType) {
file = a_file;
observableId = a_observableId;
objType = a_objType;
}
public StixArtifactData(long a_objId, String a_observableId, String a_objType) {
Case case1 = Case.getCurrentCase();
SleuthkitCase sleuthkitCase = case1.getSleuthkitCase();
try {
file = sleuthkitCase.getAbstractFileById(a_objId);
} catch (TskCoreException ex) {
file = null;
}
observableId = a_observableId;
objType = a_objType;
}
public void createArtifact(String a_title) throws TskCoreException {
Collection<BlackboardAttribute> attrs = new ArrayList<BlackboardAttribute>();
String setName;
if (a_title != null) {
setName = "STIX Indicator - " + a_title;
} else {
setName = "STIX Indicator - (no title)";
}
BlackboardArtifact bba = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(), "Stix", setName));
bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE.getTypeID(), "Stix", observableId));
bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY.getTypeID(), "Stix", objType));
}
public void print() {
System.out.println(" " + observableId + " " + file.getName());
}
}

View File

@ -666,6 +666,7 @@ INPUT = main.dox \
quick_start.dox \
image_viewer.dox \
timeline.dox \
stix.dox \
case_mgmt.dox
# This tag can be used to specify the character encoding of the source files

View File

@ -18,6 +18,7 @@ The following topics are available here:
- \subpage case_mgmt_page
- \subpage image_viewer_page
- \subpage timeline_page
- \subpage stix_page
If the topic you need is not listed, refer to the Help system in the tool or the wiki (http://wiki.sleuthkit.org/index.php?title=Autopsy_User%27s_Guide).

View File

@ -0,0 +1,94 @@
/*! \page stix_page STIX
Overview
========
This document outlines the use of the STIX feature of Autopsy. This feature allows one or more Structured Threat Information Exchange (STIX) files to be run against a data source, reporting which indicators were found in the data source. More information about STIX can be found at https://stix.mitre.org/.
This document assumes basic familiarity with Autopsy.
Quick Start
===========
-# Create a case as normal and add a disk image (or folder of files) as a data source. To get the most out of the STIX module, ensure that the following ingest modules are selected:
- Recent Activity
- Hash Lookup (Check box to calculate MD5 hashes even with no database selected)
- File Type Identification
- Keyword Search (URL, IP, and Email addresses)
- Email Parser
- Extension Mismatch Detector
-# After the image has been added and ingest is complete, click the Report button then select STIX. Next choose either a single STIX file or a directory of STIX files to run against the image. It is possible to do this while ingest is running but the results will be incomplete.
-# Once the STIX report module is complete, there will be two sets of results:
- Entries will be created under Interesting Items in the Autopsy tree, under a subheading for each indicator.
- A log of which indicators/observables were found is generated by the report module (Follow the link on the Report Generation Progess window)
Supported CybOX Objects
=======================
- Address Object
- Address_Value
- Email Message Object
- To
- CC
- From
- Subject
- File Object
- Size_In_Bytes
- File_Name
- File_Path
- File_Extension
- Modified_Time
- Accessed_Time
- Created_Time
- Hashes (MD5 only)
- File_Format
- is_masqueraded
- URI Object
- Value
- URL History Object
- Browser_Information (Name)
- URL
- Hostname
- Referrer_URL
- Page_Title
- User_Profile_Name
- User Account Object
- Home_Directory
- Username
- Win Executable File Object
- Time_Date_Stamp
- Windows Network Share Object
- Local_Path
- Netname
- Win Registry Key Object
- Key (Required)
- Hive
- Values
- System Object
- Hostname
- Processor_Architecture
- Win System Object
- Product_ID
- Product_Name
- Registered_Owner
- Registered_Organization
- Windows_System_Directory
- Windows_Temp_Directory
- Win User Account Object
- SID
Limitations
===========
- As shown in the list above, not all CybOX objects/fields are currently supported. When an unsupported object/field is found in an observable, its status is set to "indeterminate" instead of true or false. These indeterminate fields will not change the result of the observable composition (i.e., if the rest is true, the overall result will stay as true).
- Not all ConditionTypeEnum values are supported. It varies by field, but generally on String fields the following work: EQUALS, DOES_NOT_EQUAL, CONTAINS, DOES_NOT_CONTAIN, STARTS_WITH, ENDS_WITH. If a condtion type is not supported there will be a warning in the log file.
- Related objects are not processed
*/