19 package org.sleuthkit.autopsy.modules.stix;
28 import java.util.List;
29 import java.util.ArrayList;
30 import java.util.Date;
31 import java.util.TimeZone;
32 import java.text.ParseException;
33 import java.text.SimpleDateFormat;
34 import org.mitre.cybox.common_2.ConditionApplicationEnum;
36 import org.mitre.cybox.objects.FileObjectType;
37 import org.mitre.cybox.objects.WindowsExecutableFileObjectType;
38 import org.mitre.cybox.common_2.ConditionTypeEnum;
39 import org.mitre.cybox.common_2.DatatypeEnum;
40 import org.mitre.cybox.common_2.HashType;
41 import org.mitre.cybox.common_2.DateTimeObjectPropertyType;
42 import org.mitre.cybox.common_2.StringObjectPropertyType;
43 import org.mitre.cybox.common_2.UnsignedLongObjectPropertyType;
48 class EvalFileObj
extends EvaluatableObject {
50 private final FileObjectType obj;
52 public EvalFileObj(FileObjectType a_obj, String a_id, String a_spacing) {
59 public synchronized ObservableResult evaluate() {
61 Case case1 = Case.getCurrentCase();
62 SleuthkitCase sleuthkitCase = case1.getSleuthkitCase();
65 String whereClause =
"";
67 if (obj.getSizeInBytes() != null) {
69 String newClause = processULongObject(obj.getSizeInBytes(),
"size");
70 whereClause = addClause(whereClause, newClause);
71 }
catch (TskCoreException ex) {
72 addWarning(ex.getLocalizedMessage());
76 if (obj.getFileName() != null) {
78 String newClause = processStringObject(obj.getFileName(),
"name");
79 whereClause = addClause(whereClause, newClause);
80 }
catch (TskCoreException ex) {
81 addWarning(ex.getLocalizedMessage());
85 if (obj.getFileExtension() != null) {
86 if ((obj.getFileExtension().getCondition() == null)
87 || (obj.getFileExtension().getCondition() == ConditionTypeEnum.EQUALS)) {
88 String newClause =
"name LIKE \'%" + obj.getFileExtension().getValue() +
"\'";
89 whereClause = addClause(whereClause, newClause);
92 "Could not process condition " + obj.getFileExtension().getCondition().value() +
" on file extension");
96 if (obj.getFilePath() != null) {
99 String[] parts = obj.getFilePath().getValue().toString().split(
"##comma##");
100 String finalPathStr =
"";
102 for (String filePath : parts) {
104 String currentFilePath = filePath;
107 if (currentFilePath.matches(
"^[A-Za-z]:.*")) {
108 currentFilePath = currentFilePath.substring(2);
112 currentFilePath = currentFilePath.replace(
"\\",
"/");
115 if (!currentFilePath.startsWith(
"/")) {
116 currentFilePath =
"/" + currentFilePath;
120 if (!currentFilePath.endsWith(
"/")) {
121 int lastSlash = currentFilePath.lastIndexOf(
'/');
122 if (lastSlash >= 0) {
123 currentFilePath = currentFilePath.substring(0, lastSlash + 1);
128 if (!finalPathStr.isEmpty()) {
129 finalPathStr +=
"##comma##";
131 finalPathStr += currentFilePath;
134 String newClause = processStringObject(finalPathStr, obj.getFilePath().getCondition(),
135 obj.getFilePath().getApplyCondition(),
"parent_path");
137 whereClause = addClause(whereClause, newClause);
138 }
catch (TskCoreException ex) {
139 addWarning(ex.getLocalizedMessage());
143 if (obj.getCreatedTime() != null) {
145 String newClause = processTimestampObject(obj.getCreatedTime(),
"crtime");
146 whereClause = addClause(whereClause, newClause);
147 }
catch (TskCoreException ex) {
148 addWarning(ex.getLocalizedMessage());
152 if (obj.getModifiedTime() != null) {
154 String newClause = processTimestampObject(obj.getModifiedTime(),
"mtime");
155 whereClause = addClause(whereClause, newClause);
156 }
catch (TskCoreException ex) {
157 addWarning(ex.getLocalizedMessage());
161 if (obj.getAccessedTime() != null) {
163 String newClause = processTimestampObject(obj.getAccessedTime(),
"atime");
164 whereClause = addClause(whereClause, newClause);
165 }
catch (TskCoreException ex) {
166 addWarning(ex.getLocalizedMessage());
170 if (obj.getHashes() != null) {
171 for (HashType h : obj.getHashes().getHashes()) {
172 if (h.getSimpleHashValue() != null) {
173 if (h.getType().getValue().equals(
"MD5")) {
174 String newClause =
"";
175 if(h.getSimpleHashValue().getValue().toString().toLowerCase().contains(
"##comma##")){
176 String[] parts = h.getSimpleHashValue().getValue().toString().toLowerCase().split(
"##comma##");
177 String hashList =
"";
179 if(!hashList.isEmpty()){
182 hashList +=
"\'" + s +
"\'";
184 newClause =
"md5 IN (" + hashList +
")";
187 newClause =
"md5=\'" + h.getSimpleHashValue().getValue().toString().toLowerCase() +
"\'";
189 whereClause = addClause(whereClause, newClause);
191 addWarning(
"Could not process hash type " + h.getType().getValue().toString());
194 addWarning(
"Could not process non-simple hash value");
199 if (obj instanceof WindowsExecutableFileObjectType) {
200 WindowsExecutableFileObjectType winExe = (WindowsExecutableFileObjectType) obj;
201 if (winExe.getHeaders() != null) {
202 if (winExe.getHeaders().getFileHeader() != null) {
203 if (winExe.getHeaders().getFileHeader().getTimeDateStamp() != null) {
205 String result = convertTimestampString(winExe.getHeaders().getFileHeader().getTimeDateStamp().getValue().toString());
206 String newClause = processNumericFields(result,
207 winExe.getHeaders().getFileHeader().getTimeDateStamp().getCondition(),
208 winExe.getHeaders().getFileHeader().getTimeDateStamp().getApplyCondition(),
210 whereClause = addClause(whereClause, newClause);
211 }
catch (TskCoreException ex) {
212 addWarning(ex.getLocalizedMessage());
219 String unsupportedFields = listUnsupportedFields();
220 if (!unsupportedFields.isEmpty()) {
221 addWarning(
"Unsupported fields: " + unsupportedFields);
224 if (whereClause.length() > 0) {
226 List<AbstractFile> matchingFiles = sleuthkitCase.findAllFilesWhere(whereClause);
228 if (!matchingFiles.isEmpty()) {
230 if (listSecondaryFields().isEmpty()) {
232 List<StixArtifactData> artData =
new ArrayList<StixArtifactData>();
233 for (AbstractFile a : matchingFiles) {
234 artData.add(
new StixArtifactData(a,
id,
"FileObject"));
237 return new ObservableResult(
id,
"FileObject: Found " + matchingFiles.size() +
" matches for " + whereClause + getPrintableWarnings(),
238 spacing, ObservableResult.ObservableState.TRUE, artData);
242 List<AbstractFile> secondaryHits =
new ArrayList<AbstractFile>();
244 for (AbstractFile file : matchingFiles) {
245 boolean passedTests =
true;
247 if (obj.isIsMasqueraded() != null) {
248 List<BlackboardArtifact> arts = file.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED);
249 boolean isMasq =
false;
250 if (!arts.isEmpty()) {
254 if (obj.isIsMasqueraded() != isMasq) {
260 if (obj.getFileFormat() != null) {
262 boolean foundMatch =
false;
263 String formatsFound =
"";
264 List<BlackboardArtifact> arts = file.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO);
265 for (BlackboardArtifact artifact : arts) {
266 for (BlackboardAttribute attr : artifact.getAttributes()) {
267 if (attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FILE_TYPE_SIG.getTypeID()) {
268 if (!formatsFound.isEmpty()) {
269 formatsFound +=
", ";
271 formatsFound += attr.getValueString();
272 if (attr.getValueString().equalsIgnoreCase(obj.getFileFormat().getValue().toString())) {
278 String type = attr.getValueString().replaceFirst(
"^.*/",
"");
281 if (compareStringObject(type, ConditionTypeEnum.CONTAINS, null, obj.getFileFormat().getValue().toString())) {
293 if (formatsFound.isEmpty()) {
294 addWarning(
"Warning: Did not match File_Format field " + obj.getFileFormat().getValue().toString()
295 +
" (no file formats found)");
298 addWarning(
"Warning: Did not match File_Format field " + obj.getFileFormat().getValue().toString()
299 +
" against " + formatsFound);
305 secondaryHits.add(file);
309 if (secondaryHits.isEmpty()) {
311 return new ObservableResult(
id,
"FileObject: Found " + matchingFiles.size() +
" matches for " + whereClause
312 +
" but none for secondary tests on " + listSecondaryFields() + getPrintableWarnings(),
313 spacing, ObservableResult.ObservableState.FALSE, null);
315 List<StixArtifactData> artData =
new ArrayList<StixArtifactData>();
316 for (AbstractFile a : secondaryHits) {
317 artData.add(
new StixArtifactData(a,
id,
"FileObject"));
319 return new ObservableResult(
id,
"FileObject: Found " + secondaryHits.size() +
" matches for " + whereClause
320 +
" and secondary tests on " + listSecondaryFields() + getPrintableWarnings(),
321 spacing, ObservableResult.ObservableState.TRUE, artData);
325 return new ObservableResult(
id,
"FileObject: Found no matches for " + whereClause + getPrintableWarnings(),
326 spacing, ObservableResult.ObservableState.FALSE, null);
328 }
catch (TskCoreException ex) {
329 return new ObservableResult(
id,
"FileObject: Exception during evaluation: " + ex.getLocalizedMessage(),
330 spacing, ObservableResult.ObservableState.INDETERMINATE, null);
336 return new ObservableResult(
id,
"FileObject: No evaluatable fields " + getPrintableWarnings(), spacing,
337 ObservableResult.ObservableState.INDETERMINATE, null);
346 private String listSecondaryFields() {
347 String secondaryFields =
"";
349 if (obj.isIsMasqueraded() != null) {
350 secondaryFields +=
"is_masqueraded ";
353 if (obj.getFileFormat() != null) {
354 secondaryFields +=
"File_Format ";
357 return secondaryFields;
365 private String listUnsupportedFields() {
366 String unsupportedFields =
"";
368 if (obj.isIsPacked() != null) {
369 unsupportedFields +=
"is_packed ";
371 if (obj.getDevicePath() != null) {
372 unsupportedFields +=
"Device_Path ";
374 if (obj.getFullPath() != null) {
375 unsupportedFields +=
"Full_Path ";
377 if (obj.getMagicNumber() != null) {
378 unsupportedFields +=
"Magic_Number ";
380 if (obj.getDigitalSignatures() != null) {
381 unsupportedFields +=
"Digital_Signatures ";
383 if (obj.getFileAttributesList() != null) {
384 unsupportedFields +=
"File_Attributes_List ";
386 if (obj.getPermissions() != null) {
387 unsupportedFields +=
"Permissions ";
389 if (obj.getUserOwner() != null) {
390 unsupportedFields +=
"User_Owner ";
392 if (obj.getPackerList() != null) {
393 unsupportedFields +=
"Packer_List ";
395 if (obj.getPeakEntropy() != null) {
396 unsupportedFields +=
"Peak_Entropy ";
398 if (obj.getSymLinks() != null) {
399 unsupportedFields +=
"Sym_Links ";
401 if (obj.getByteRuns() != null) {
402 unsupportedFields +=
"Bytes_Runs ";
404 if (obj.getExtractedFeatures() != null) {
405 unsupportedFields +=
"Extracted_Features ";
407 if (obj.getEncryptionAlgorithm() != null) {
408 unsupportedFields +=
"Encryption_Algorithm ";
410 if (obj.getDecryptionKey() != null) {
411 unsupportedFields +=
"Decryption_Key ";
413 if (obj.getCompressionMethod() != null) {
414 unsupportedFields +=
"Compression_Method ";
416 if (obj.getCompressionVersion() != null) {
417 unsupportedFields +=
"Compression_Version ";
419 if (obj.getCompressionComment() != null) {
420 unsupportedFields +=
"Compression_Comment ";
423 return unsupportedFields;
433 private static long convertTimestamp(String timeStr)
throws ParseException {
434 SimpleDateFormat dateFormat =
new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss'Z'");
435 dateFormat.setTimeZone(TimeZone.getTimeZone(
"GMT"));
436 Date parsedDate = dateFormat.parse(timeStr);
438 Long unixTime = parsedDate.getTime() / 1000;
452 private static String processULongObject(UnsignedLongObjectPropertyType longObj, String fieldName)
453 throws TskCoreException {
455 return processNumericFields(longObj.getValue().toString(), longObj.getCondition(),
456 longObj.getApplyCondition(), fieldName);
469 private static String processNumericFields(String valueStr, ConditionTypeEnum typeCondition,
470 ConditionApplicationEnum applyCondition, String fieldName)
471 throws TskCoreException {
473 if ((typeCondition == null)
474 || ((typeCondition != ConditionTypeEnum.INCLUSIVE_BETWEEN)
475 && (typeCondition != ConditionTypeEnum.EXCLUSIVE_BETWEEN))) {
477 String fullClause =
"";
479 if (valueStr.isEmpty()) {
480 throw new TskCoreException(
"Empty value field");
483 String[] parts = valueStr.split(
"##comma##");
485 for (String valuePart : parts) {
486 String partialClause;
488 if ((typeCondition == null)
489 || (typeCondition == ConditionTypeEnum.EQUALS)) {
491 partialClause = fieldName +
"=" + valuePart;
492 }
else if (typeCondition == ConditionTypeEnum.DOES_NOT_EQUAL) {
493 partialClause = fieldName +
"!=" + valuePart;
494 }
else if (typeCondition == ConditionTypeEnum.GREATER_THAN) {
495 partialClause = fieldName +
">" + valuePart;
496 }
else if (typeCondition == ConditionTypeEnum.GREATER_THAN_OR_EQUAL) {
497 partialClause = fieldName +
">=" + valuePart;
498 }
else if (typeCondition == ConditionTypeEnum.LESS_THAN) {
499 partialClause = fieldName +
"<" + valuePart;
500 }
else if (typeCondition == ConditionTypeEnum.LESS_THAN_OR_EQUAL) {
501 partialClause = fieldName +
"<=" + valuePart;
503 throw new TskCoreException(
"Could not process condition " + typeCondition.value() +
" on " + fieldName);
506 if (fullClause.isEmpty()) {
508 if (parts.length > 1) {
511 if (applyCondition == ConditionApplicationEnum.NONE) {
512 fullClause +=
" NOT ";
514 fullClause += partialClause;
516 if (applyCondition == ConditionApplicationEnum.ALL) {
517 fullClause +=
" AND " + partialClause;
518 }
else if (applyCondition == ConditionApplicationEnum.NONE) {
519 fullClause +=
" AND NOT " + partialClause;
521 fullClause +=
" OR " + partialClause;
526 if (parts.length > 1) {
533 if (typeCondition == ConditionTypeEnum.INCLUSIVE_BETWEEN) {
534 String[] parts = valueStr.split(
"##comma##");
535 if (parts.length != 2) {
536 throw new TskCoreException(
"Unexpected number of arguments in INCLUSIVE_BETWEEN on " + fieldName
537 +
"(" + valueStr +
")");
539 return (fieldName +
">=" + parts[0] +
" AND " + fieldName +
"<=" + parts[1]);
541 String[] parts = valueStr.split(
"##comma##");
542 if (parts.length != 2) {
543 throw new TskCoreException(
"Unexpected number of arguments in EXCLUSIVE_BETWEEN on " + fieldName
544 +
"(" + valueStr +
")");
546 return (fieldName +
">" + parts[0] +
" AND " + fieldName +
"<" + parts[1]);
559 private static String processStringObject(StringObjectPropertyType stringObj, String fieldName)
560 throws TskCoreException {
562 return processStringObject(stringObj.getValue().toString(), stringObj.getCondition(),
563 stringObj.getApplyCondition(), fieldName);
576 public static String processStringObject(String valueStr, ConditionTypeEnum condition,
577 ConditionApplicationEnum applyCondition, String fieldName)
578 throws TskCoreException {
580 String fullClause =
"";
581 String lowerFieldName =
"lower(" + fieldName +
")";
583 if (valueStr.isEmpty()) {
584 throw new TskCoreException(
"Empty value field");
587 String[] parts = valueStr.split(
"##comma##");
589 for (String value : parts) {
590 String lowerValue = value.toLowerCase();
591 String partialClause;
592 if ((condition == null)
593 || (condition == ConditionTypeEnum.EQUALS)) {
594 partialClause = lowerFieldName +
"=\'" + lowerValue +
"\'";
595 }
else if (condition == ConditionTypeEnum.DOES_NOT_EQUAL) {
596 partialClause = lowerFieldName +
" !=\'%" + lowerValue +
"%\'";
597 }
else if (condition == ConditionTypeEnum.CONTAINS) {
598 partialClause = lowerFieldName +
" LIKE \'%" + lowerValue +
"%\'";
599 }
else if (condition == ConditionTypeEnum.DOES_NOT_CONTAIN) {
600 partialClause = lowerFieldName +
" NOT LIKE \'%" + lowerValue +
"%\'";
601 }
else if (condition == ConditionTypeEnum.STARTS_WITH) {
602 partialClause = lowerFieldName +
" LIKE \'" + lowerValue +
"%\'";
603 }
else if (condition == ConditionTypeEnum.ENDS_WITH) {
604 partialClause = lowerFieldName +
" LIKE \'%" + lowerValue +
"\'";
606 throw new TskCoreException(
"Could not process condition " + condition.value() +
" on " + fieldName);
609 if (fullClause.isEmpty()) {
611 if (parts.length > 1) {
614 if (applyCondition == ConditionApplicationEnum.NONE) {
615 fullClause +=
" NOT ";
617 fullClause += partialClause;
619 if (applyCondition == ConditionApplicationEnum.ALL) {
620 fullClause +=
" AND " + partialClause;
621 }
else if (applyCondition == ConditionApplicationEnum.NONE) {
622 fullClause +=
" AND NOT " + partialClause;
624 fullClause +=
" OR " + partialClause;
629 if (parts.length > 1) {
645 private static String processTimestampObject(DateTimeObjectPropertyType dateObj, String fieldName)
646 throws TskCoreException {
648 if (DatatypeEnum.DATE_TIME == dateObj.getDatatype()) {
651 String result = convertTimestampString(dateObj.getValue().toString());
652 return processNumericFields(result, dateObj.getCondition(), dateObj.getApplyCondition(), fieldName);
655 throw new TskCoreException(
"Found non DATE_TIME field on " + fieldName);
667 private static String convertTimestampString(String timestampStr)
668 throws TskCoreException {
671 if (timestampStr.length() > 0) {
672 String[] parts = timestampStr.split(
"##comma##");
674 for (
int i = 0; i < parts.length - 1; i++) {
675 long unixTime = convertTimestamp(parts[i]);
676 result += unixTime +
"##comma##";
678 result += convertTimestamp(parts[parts.length - 1]);
681 }
catch (java.text.ParseException ex) {
682 throw new TskCoreException(
"Error parsing timestamp string " + timestampStr);
694 private static String addClause(String a_clause, String a_newClause) {
696 if ((a_clause == null) || a_clause.isEmpty()) {
700 return (a_clause +
" AND " + a_newClause);